├── .editorconfig ├── .gitattributes ├── .poggit.yml ├── LICENSE ├── plugin.yml └── src └── ref └── api └── addonsmanager ├── AddonsManager.php ├── Main.php ├── addons ├── Addons.php ├── FolderAddons.php ├── PluginAddons.php ├── ZippedAddons.php └── json │ ├── Manifest.php │ ├── ManifestDependencyEntry.php │ ├── ManifestEntry.php │ ├── ManifestHeader.php │ ├── ManifestMetadata.php │ └── ManifestModuleEntry.php └── builder └── SimpleAddonsBuilder.php /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 4 5 | indent_style = space 6 | insert_final_newline = false 7 | max_line_length = 120 8 | tab_width = 4 9 | ij_continuation_indent_size = 8 10 | ij_formatter_off_tag = @formatter:off 11 | ij_formatter_on_tag = @formatter:on 12 | ij_formatter_tags_enabled = false 13 | ij_smart_tabs = false 14 | ij_wrap_on_typing = false 15 | 16 | [.editorconfig] 17 | ij_editorconfig_align_group_field_declarations = false 18 | ij_editorconfig_space_after_colon = false 19 | ij_editorconfig_space_after_comma = true 20 | ij_editorconfig_space_before_colon = false 21 | ij_editorconfig_space_before_comma = false 22 | ij_editorconfig_spaces_around_assignment_operators = true 23 | 24 | 25 | [*.php] 26 | ij_continuation_indent_size = 4 27 | ij_smart_tabs = true 28 | ij_visual_guides = 10 29 | ij_php_align_assignments = false 30 | ij_php_align_class_constants = false 31 | ij_php_align_group_field_declarations = false 32 | ij_php_align_inline_comments = true 33 | ij_php_align_key_value_pairs = false 34 | ij_php_align_multiline_array_initializer_expression = true 35 | ij_php_align_multiline_binary_operation = false 36 | ij_php_align_multiline_chained_methods = false 37 | ij_php_align_multiline_extends_list = false 38 | ij_php_align_multiline_for = false 39 | ij_php_align_multiline_parameters = false 40 | ij_php_align_multiline_parameters_in_calls = false 41 | ij_php_align_multiline_ternary_operation = false 42 | ij_php_align_phpdoc_comments = false 43 | ij_php_align_phpdoc_param_names = true 44 | ij_php_anonymous_brace_style = end_of_line 45 | ij_php_api_weight = 28 46 | ij_php_array_initializer_new_line_after_left_brace = true 47 | ij_php_array_initializer_right_brace_on_new_line = true 48 | ij_php_array_initializer_wrap = on_every_item 49 | ij_php_assignment_wrap = off 50 | ij_php_author_weight = 28 51 | ij_php_binary_operation_sign_on_next_line = false 52 | ij_php_binary_operation_wrap = off 53 | ij_php_blank_lines_after_class_header = 0 54 | ij_php_blank_lines_after_function = 1 55 | ij_php_blank_lines_after_imports = 1 56 | ij_php_blank_lines_after_opening_tag = 0 57 | ij_php_blank_lines_after_package = 1 58 | ij_php_blank_lines_around_class = 1 59 | ij_php_blank_lines_around_constants = 0 60 | ij_php_blank_lines_around_field = 0 61 | ij_php_blank_lines_around_method = 1 62 | ij_php_blank_lines_before_class_end = 0 63 | ij_php_blank_lines_before_imports = 1 64 | ij_php_blank_lines_before_method_body = 0 65 | ij_php_blank_lines_before_package = 1 66 | ij_php_blank_lines_before_return_statement = 0 67 | ij_php_blank_lines_between_imports = 1 68 | ij_php_block_brace_style = end_of_line 69 | ij_php_call_parameters_new_line_after_left_paren = false 70 | ij_php_call_parameters_right_paren_on_new_line = false 71 | ij_php_call_parameters_wrap = off 72 | ij_php_catch_on_new_line = false 73 | ij_php_category_weight = 28 74 | ij_php_class_brace_style = end_of_line 75 | ij_php_comma_after_last_array_element = false 76 | ij_php_concat_spaces = true 77 | ij_php_copyright_weight = 28 78 | ij_php_deprecated_weight = 28 79 | ij_php_do_while_brace_force = never 80 | ij_php_else_if_style = combine 81 | ij_php_else_on_new_line = false 82 | ij_php_example_weight = 28 83 | ij_php_extends_keyword_wrap = off 84 | ij_php_extends_list_wrap = off 85 | ij_php_fields_default_visibility = private 86 | ij_php_filesource_weight = 28 87 | ij_php_finally_on_new_line = false 88 | ij_php_for_brace_force = never 89 | ij_php_for_statement_new_line_after_left_paren = false 90 | ij_php_for_statement_right_paren_on_new_line = false 91 | ij_php_for_statement_wrap = off 92 | ij_php_force_short_declaration_array_style = true 93 | ij_php_getters_setters_naming_style = camel_case 94 | ij_php_getters_setters_order_style = getters_first 95 | ij_php_global_weight = 28 96 | ij_php_group_use_wrap = split_into_lines 97 | ij_php_if_brace_force = never 98 | ij_php_if_lparen_on_next_line = false 99 | ij_php_if_rparen_on_next_line = false 100 | ij_php_ignore_weight = 28 101 | ij_php_import_sorting = alphabetic 102 | ij_php_indent_break_from_case = true 103 | ij_php_indent_case_from_switch = true 104 | ij_php_indent_code_in_php_tags = false 105 | ij_php_internal_weight = 28 106 | ij_php_keep_blank_lines_after_lbrace = 0 107 | ij_php_keep_blank_lines_before_right_brace = 0 108 | ij_php_keep_blank_lines_in_code = 1 109 | ij_php_keep_blank_lines_in_declarations = 1 110 | ij_php_keep_control_statement_in_one_line = true 111 | ij_php_keep_first_column_comment = false 112 | ij_php_keep_indents_on_empty_lines = false 113 | ij_php_keep_line_breaks = true 114 | ij_php_keep_rparen_and_lbrace_on_one_line = false 115 | ij_php_keep_simple_classes_in_one_line = true 116 | ij_php_keep_simple_methods_in_one_line = true 117 | ij_php_lambda_brace_style = end_of_line 118 | ij_php_license_weight = 28 119 | ij_php_line_comment_add_space = false 120 | ij_php_line_comment_at_first_column = true 121 | ij_php_link_weight = 28 122 | ij_php_lower_case_boolean_const = true 123 | ij_php_lower_case_keywords = true 124 | ij_php_lower_case_null_const = true 125 | ij_php_method_brace_style = end_of_line 126 | ij_php_method_call_chain_wrap = off 127 | ij_php_method_parameters_new_line_after_left_paren = false 128 | ij_php_method_parameters_right_paren_on_new_line = true 129 | ij_php_method_parameters_wrap = off 130 | ij_php_method_weight = 28 131 | ij_php_modifier_list_wrap = false 132 | ij_php_multiline_chained_calls_semicolon_on_new_line = false 133 | ij_php_namespace_brace_style = 1 134 | ij_php_new_line_after_php_opening_tag = false 135 | ij_php_null_type_position = in_the_end 136 | ij_php_package_weight = 28 137 | ij_php_param_weight = 0 138 | ij_php_parentheses_expression_new_line_after_left_paren = false 139 | ij_php_parentheses_expression_right_paren_on_new_line = false 140 | ij_php_phpdoc_blank_line_before_tags = true 141 | ij_php_phpdoc_blank_lines_around_parameters = true 142 | ij_php_phpdoc_keep_blank_lines = true 143 | ij_php_phpdoc_param_spaces_between_name_and_description = 1 144 | ij_php_phpdoc_param_spaces_between_tag_and_type = 1 145 | ij_php_phpdoc_param_spaces_between_type_and_name = 1 146 | ij_php_phpdoc_use_fqcn = false 147 | ij_php_phpdoc_wrap_long_lines = false 148 | ij_php_place_assignment_sign_on_next_line = false 149 | ij_php_place_parens_for_constructor = 0 150 | ij_php_property_read_weight = 28 151 | ij_php_property_weight = 28 152 | ij_php_property_write_weight = 28 153 | ij_php_return_type_on_new_line = false 154 | ij_php_return_weight = 1 155 | ij_php_see_weight = 28 156 | ij_php_since_weight = 28 157 | ij_php_sort_phpdoc_elements = true 158 | ij_php_space_after_colon = true 159 | ij_php_space_after_colon_in_return_type = true 160 | ij_php_space_after_comma = true 161 | ij_php_space_after_for_semicolon = true 162 | ij_php_space_after_quest = true 163 | ij_php_space_after_type_cast = true 164 | ij_php_space_after_unary_not = false 165 | ij_php_space_before_array_initializer_left_brace = false 166 | ij_php_space_before_catch_keyword = false 167 | ij_php_space_before_catch_left_brace = false 168 | ij_php_space_before_catch_parentheses = false 169 | ij_php_space_before_class_left_brace = false 170 | ij_php_space_before_closure_left_parenthesis = false 171 | ij_php_space_before_colon = true 172 | ij_php_space_before_colon_in_return_type = true 173 | ij_php_space_before_comma = false 174 | ij_php_space_before_do_left_brace = false 175 | ij_php_space_before_else_keyword = false 176 | ij_php_space_before_else_left_brace = false 177 | ij_php_space_before_finally_keyword = false 178 | ij_php_space_before_finally_left_brace = false 179 | ij_php_space_before_for_left_brace = false 180 | ij_php_space_before_for_parentheses = false 181 | ij_php_space_before_for_semicolon = false 182 | ij_php_space_before_if_left_brace = false 183 | ij_php_space_before_if_parentheses = false 184 | ij_php_space_before_method_call_parentheses = false 185 | ij_php_space_before_method_left_brace = false 186 | ij_php_space_before_method_parentheses = false 187 | ij_php_space_before_quest = true 188 | ij_php_space_before_short_closure_left_parenthesis = false 189 | ij_php_space_before_switch_left_brace = false 190 | ij_php_space_before_switch_parentheses = false 191 | ij_php_space_before_try_left_brace = false 192 | ij_php_space_before_unary_not = false 193 | ij_php_space_before_while_keyword = false 194 | ij_php_space_before_while_left_brace = false 195 | ij_php_space_before_while_parentheses = false 196 | ij_php_space_between_ternary_quest_and_colon = false 197 | ij_php_spaces_around_additive_operators = true 198 | ij_php_spaces_around_arrow = false 199 | ij_php_spaces_around_assignment_in_declare = false 200 | ij_php_spaces_around_assignment_operators = true 201 | ij_php_spaces_around_bitwise_operators = true 202 | ij_php_spaces_around_equality_operators = true 203 | ij_php_spaces_around_logical_operators = true 204 | ij_php_spaces_around_multiplicative_operators = true 205 | ij_php_spaces_around_null_coalesce_operator = true 206 | ij_php_spaces_around_relational_operators = true 207 | ij_php_spaces_around_shift_operators = true 208 | ij_php_spaces_around_unary_operator = false 209 | ij_php_spaces_around_var_within_brackets = false 210 | ij_php_spaces_within_array_initializer_braces = false 211 | ij_php_spaces_within_brackets = false 212 | ij_php_spaces_within_catch_parentheses = false 213 | ij_php_spaces_within_for_parentheses = false 214 | ij_php_spaces_within_if_parentheses = false 215 | ij_php_spaces_within_method_call_parentheses = false 216 | ij_php_spaces_within_method_parentheses = false 217 | ij_php_spaces_within_parentheses = false 218 | ij_php_spaces_within_short_echo_tags = true 219 | ij_php_spaces_within_switch_parentheses = false 220 | ij_php_spaces_within_while_parentheses = false 221 | ij_php_special_else_if_treatment = false 222 | ij_php_subpackage_weight = 28 223 | ij_php_ternary_operation_signs_on_next_line = false 224 | ij_php_ternary_operation_wrap = off 225 | ij_php_throws_weight = 2 226 | ij_php_todo_weight = 28 227 | ij_php_unknown_tag_weight = 28 228 | ij_php_upper_case_boolean_const = false 229 | ij_php_upper_case_null_const = false 230 | ij_php_uses_weight = 28 231 | ij_php_var_weight = 28 232 | ij_php_variable_naming_style = camel_case 233 | ij_php_version_weight = 28 234 | ij_php_while_brace_force = never 235 | ij_php_while_on_new_line = false 236 | 237 | [{*.yaml, *.yml}] 238 | indent_size = 2 239 | ij_yaml_keep_indents_on_empty_lines = false 240 | ij_yaml_keep_line_breaks = true 241 | ij_yaml_space_before_colon = false 242 | ij_yaml_spaces_within_braces = true 243 | ij_yaml_spaces_within_brackets = true -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.poggit.yml: -------------------------------------------------------------------------------- 1 | --- # Poggit-CI Manifest. Open the CI at https://poggit.pmmp.io/ci/refteams/refAddonsManager 2 | build-by-default: true 3 | branches: 4 | - pm4/main 5 | projects: 6 | refAddonsManager: 7 | path: "" 8 | libs: 9 | - src: presentkim-pm/remove-plugin-data-dir-trait/remove-plugin-data-dir-trait 10 | version: ^1.0.0 11 | ... 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /plugin.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: refAddonsManager 3 | main: ref\api\addonsmanager\Main 4 | version: 1.2.0 5 | api: [ 4.0.0 ] 6 | authors: [ ref-team, PresentKim ] 7 | description: A simple add-ons manager (resource pack and behavior pack at once) 8 | ... 9 | -------------------------------------------------------------------------------- /src/ref/api/addonsmanager/AddonsManager.php: -------------------------------------------------------------------------------- 1 | */ 55 | private array $resourcePacks = []; 56 | /** @var array */ 57 | private array $behaviorPacks = []; 58 | 59 | /** @var ResourcePackInfoEntry[] */ 60 | private array $resourcePackInfoEntries = []; 61 | /** @var BehaviorPackInfoEntry[] */ 62 | private array $behaviorPackInfoEntries = []; 63 | 64 | /** @var ResourcePackStackEntry[] */ 65 | private array $resourcePackStackEntries = []; 66 | /** @var ResourcePackStackEntry[] */ 67 | private array $behaviorPackStackEntries = []; 68 | 69 | public function __construct(){ 70 | $server = Server::getInstance(); 71 | $logger = $server->getLogger(); 72 | 73 | $logger->info("Loading addons..."); 74 | $addonsPath = $server->getDataPath() . "addons/"; 75 | if(!file_exists($addonsPath)){ 76 | mkdir($addonsPath, 0777, true); 77 | } 78 | 79 | foreach(array_diff(scandir($addonsPath), [".", ".."]) as $innerPath){ 80 | $realPath = $addonsPath . $innerPath; 81 | if(is_dir($realPath)){ 82 | if(file_exists("$realPath/" . Addons::MANIFEST_FILE)){ 83 | $this->register(new FolderAddons($realPath)); 84 | } 85 | }elseif(preg_match("/^(.+)\.(zip|mcpack)$/i", $realPath)){ 86 | $this->register(new ZippedAddons($realPath)); 87 | } 88 | } 89 | $logger->debug("Successfully loaded " . (count($this->resourcePacks) + count($this->behaviorPacks)) . " addons"); 90 | } 91 | 92 | /** @return $this */ 93 | public function register(Addons $addons) : self{ 94 | $id = strtolower($addons->getUuid()); 95 | $version = $addons->getVersion(); 96 | if($addons->getType() === ResourcePackType::RESOURCES){ 97 | $this->resourcePacks[$id] = $addons; 98 | $this->resourcePackInfoEntries[$id] = new ResourcePackInfoEntry($id, $version, $addons->getSize()); 99 | $this->resourcePackStackEntries[$id] = new ResourcePackStackEntry($id, $version, ""); 100 | }elseif($addons->getType() === ResourcePackType::BEHAVIORS){ 101 | $this->behaviorPacks[$id] = $addons; 102 | $this->behaviorPackInfoEntries[$id] = new BehaviorPackInfoEntry($id, $version, $addons->getSize()); 103 | $this->behaviorPackStackEntries[$id] = new ResourcePackStackEntry($id, $version, ""); 104 | }else{ 105 | throw new InvalidArgumentException("Invalid Addons type"); 106 | } 107 | return $this; 108 | } 109 | 110 | /** Remove the resource pack or behavior pack matching the specified UUID string, or null if the ID was not recognized. */ 111 | public function unregister(string $id) : self{ 112 | $id = strtolower($id); 113 | unset( 114 | $this->resourcePacks[$id], 115 | $this->resourcePackInfoEntries[$id], 116 | $this->resourcePackStackEntries[$id], 117 | $this->behaviorPacks[$id], 118 | $this->behaviorPackInfoEntries[$id], 119 | $this->behaviorPackStackEntries[$id] 120 | ); 121 | return $this; 122 | } 123 | 124 | /** Returns the resource pack or behavior pack matching the specified UUID string, or null if the ID was not recognized. */ 125 | public function get(string $id) : ?Addons{ 126 | $id = strtolower($id); 127 | return $this->resourcePacks[$id] ?? $this->behaviorPacks[$id] ?? null; 128 | } 129 | 130 | /** @return Addons[] */ 131 | public function getAllAddons() : array{ 132 | return array_values(array_merge($this->resourcePacks, $this->behaviorPacks)); 133 | } 134 | 135 | /** @return Addons[] */ 136 | public function getResourcePacks() : array{ 137 | return $this->resourcePacks; 138 | } 139 | 140 | /** @return Addons[] */ 141 | public function getBehaviorPacks() : array{ 142 | return $this->behaviorPacks; 143 | } 144 | 145 | /** 146 | * @return ResourcePackInfoEntry[] 147 | * @internal 148 | */ 149 | public function getResourcePackInfoEntries() : array{ 150 | return array_values($this->resourcePackInfoEntries); 151 | } 152 | 153 | /** 154 | * @return BehaviorPackInfoEntry[] 155 | * @internal 156 | */ 157 | public function getBehaviorPackInfoEntries() : array{ 158 | return array_values($this->behaviorPackInfoEntries); 159 | } 160 | 161 | /** 162 | * @return ResourcePackStackEntry[] 163 | * @internal 164 | */ 165 | public function getResourcePackStackEntries() : array{ 166 | return array_values($this->resourcePackStackEntries); 167 | } 168 | 169 | /** 170 | * @return ResourcePackStackEntry[] 171 | * @internal 172 | */ 173 | public function getBehaviorPackStackEntries() : array{ 174 | return array_values($this->behaviorPackStackEntries); 175 | } 176 | } -------------------------------------------------------------------------------- /src/ref/api/addonsmanager/Main.php: -------------------------------------------------------------------------------- 1 | true, // Additional Modding Capabilities 55 | "upcoming_creator_features" => true, // Upcoming Creator Features 56 | "gametest" => true, // Enable GameTest Framework 57 | "data_driven_items" => true, // Holiday Creator Features 58 | "experimental_molang_features" => true, // Experimental Molang Features 59 | ]; 60 | 61 | private AddonsManager $addonsManager; 62 | 63 | protected function onLoad() : void{ 64 | $this->addonsManager = AddonsManager::getInstance(); 65 | $this->removePluginDataDir(); 66 | } 67 | 68 | protected function onEnable() : void{ 69 | $this->getServer()->getPluginManager()->registerEvents($this, $this); 70 | } 71 | 72 | /** @priority LOWEST */ 73 | public function onDataPacketSendEvent(DataPacketSendEvent $event) : void{ 74 | foreach($event->getPackets() as $packet){ 75 | if($packet instanceof ResourcePacksInfoPacket){ 76 | foreach($this->addonsManager->getResourcePackInfoEntries() as $entry){ 77 | $packet->resourcePackEntries[] = $entry; 78 | } 79 | foreach($this->addonsManager->getBehaviorPackInfoEntries() as $entry){ 80 | $packet->behaviorPackEntries[] = $entry; 81 | } 82 | }elseif($packet instanceof ResourcePackStackPacket){ 83 | foreach($this->addonsManager->getResourcePackStackEntries() as $entry){ 84 | $packet->resourcePackStack[] = $entry; 85 | } 86 | foreach($this->addonsManager->getBehaviorPackStackEntries() as $entry){ 87 | $packet->behaviorPackStack[] = $entry; 88 | } 89 | }elseif($packet instanceof StartGamePacket){ 90 | $experiments = $packet->levelSettings->experiments; 91 | $experimentsArray = $experiments->getExperiments(); 92 | foreach(Main::OVERRIDDEN_EXPERIMENTS as $experiment => $enabled){ 93 | $experimentsArray[$experiment] = $enabled; 94 | } 95 | $packet->levelSettings->experiments = new Experiments($experimentsArray, $experiments->hasPreviouslyUsedExperiments()); 96 | } 97 | } 98 | } 99 | 100 | /** @priority LOWEST */ 101 | public function onDataPacketReceiveEvent(DataPacketReceiveEvent $event) : void{ 102 | $packet = $event->getPacket(); 103 | if( 104 | $packet instanceof ResourcePackClientResponsePacket && 105 | $packet->status === ResourcePackClientResponsePacket::STATUS_SEND_PACKS 106 | ){ 107 | $session = $event->getOrigin(); 108 | /** @var string[] */ 109 | $remained = []; 110 | foreach($packet->packIds as $key => $uuid){ 111 | $splitPos = strpos($uuid, "_"); 112 | if($splitPos !== false){ 113 | $uuid = substr($uuid, 0, $splitPos); 114 | } 115 | $addons = $this->addonsManager->get($uuid); 116 | if($addons !== null){ 117 | $session->sendDataPacket(ResourcePackDataInfoPacket::create( 118 | $addons->getUuid(), 119 | self::MAX_CHUNK_SIZE, 120 | (int) ceil($addons->getSize() / self::MAX_CHUNK_SIZE), 121 | $addons->getSize(), 122 | $addons->getSha256(), 123 | false, 124 | $addons->getType() 125 | )); 126 | }else{ 127 | $remained[$key] = $uuid; 128 | } 129 | } 130 | 131 | $session->getLogger()->debug("Player requested download of " . (count($packet->packIds) - count($remained)) . " addons"); 132 | $packet->packIds = $remained; 133 | }elseif( 134 | $packet instanceof ResourcePackChunkRequestPacket && 135 | ($addons = $this->addonsManager->get($packet->packId)) instanceof Addons 136 | ){ 137 | $event->getOrigin()->sendDataPacket(ResourcePackChunkDataPacket::create( 138 | $addons->getUuid(), 139 | $packet->chunkIndex, 140 | (self::MAX_CHUNK_SIZE * $packet->chunkIndex), 141 | $addons->getChunk(self::MAX_CHUNK_SIZE * $packet->chunkIndex, self::MAX_CHUNK_SIZE) 142 | )); 143 | $event->cancel(); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/ref/api/addonsmanager/addons/Addons.php: -------------------------------------------------------------------------------- 1 | ResourcePackType::RESOURCES, 62 | "data" => ResourcePackType::BEHAVIORS 63 | ]; 64 | 65 | public const MANIFEST_FILE = "manifest.json"; 66 | 67 | protected Manifest $manifest; 68 | protected int $type = ResourcePackType::INVALID; 69 | 70 | /** @var array innerPath => fileContents */ 71 | protected array $files = []; 72 | protected string $contents; 73 | protected string $sha256; 74 | 75 | /** 76 | * @param array $files innerPath => fileContents 77 | * 78 | * @throws ResourcePackException 79 | */ 80 | public function __construct(array $files){ 81 | $manifestFile = $files[self::MANIFEST_FILE] ?? null; 82 | if($manifestFile === null){ 83 | throw new ResourcePackException("manifest.json not found in the addons"); 84 | } 85 | 86 | try{ 87 | $this->manifest = self::parseManifestFile($manifestFile); 88 | }catch(JsonMapperException $e){ 89 | throw new ResourcePackException("Invalid manifest.json contents: " . $e->getMessage(), 0, $e); 90 | } 91 | 92 | $tempPath = tempnam(sys_get_temp_dir(), 'pm$'); 93 | $fullContents = ""; 94 | 95 | $archive = new ZipArchive(); 96 | $archive->open($tempPath, ZipArchive::CREATE | ZipArchive::OVERWRITE); 97 | foreach($files as $innerPath => $contents){ 98 | if(str_ends_with($innerPath, ".json")){ 99 | try{ 100 | $contents = json_encode((new CommentedJsonDecoder())->decode($contents), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 101 | }catch(RuntimeException){ 102 | } 103 | } 104 | $this->files[$innerPath] = $contents; 105 | $archive->addFromString($innerPath, $contents); 106 | $archive->setCompressionName($innerPath, ZipArchive::CM_DEFLATE64); 107 | $archive->setMtimeName($innerPath, 0); 108 | 109 | $fullContents .= $contents; 110 | } 111 | 112 | if(preg_match(self::MEANINGLESS_UUID_REGEX, $this->manifest->header->uuid) === 1){ 113 | $this->manifest->header->uuid = Uuid::fromString(md5($fullContents))->toString(); 114 | } 115 | if(!isset($this->manifest->modules) || !is_array($this->manifest->modules) || count($this->manifest->modules) === 0){ 116 | throw new ResourcePackException("Addons must have at least one module. (addons uuid: {$this->manifest->header->uuid})"); 117 | } 118 | foreach($this->manifest->modules as $key => $module){ 119 | $type = self::TYPE_MAP[$module->type] ?? ResourcePackType::INVALID; 120 | if($type === ResourcePackType::INVALID){ 121 | throw new ResourcePackException("Module type must be 'resource' and 'data', '$module->type' given. (module uuid: $module->uuid)"); 122 | } 123 | if($this->type !== ResourcePackType::INVALID && $this->type !== $type){ 124 | throw new ResourcePackException("Multiple types of modules cannot exist in one add-on. (module uuid: $module->uuid)"); 125 | } 126 | $this->type = $type; 127 | if(preg_match(self::MEANINGLESS_UUID_REGEX, $module->uuid) === 1){ 128 | $module->uuid = UUID::fromString(md5($fullContents . $key))->toString(); 129 | } 130 | } 131 | $manifestContents = json_encode($this->manifest, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 132 | $this->files[self::MANIFEST_FILE] = $manifestContents; 133 | $archive->addFromString(self::MANIFEST_FILE, $manifestContents); 134 | $archive->setCompressionName(self::MANIFEST_FILE, ZipArchive::CM_DEFLATE64); 135 | $archive->setMtimeName(self::MANIFEST_FILE, 0); 136 | $archive->close(); 137 | 138 | $this->contents = file_get_contents($tempPath); 139 | $this->sha256 = hash("sha256", $this->contents, true); 140 | unlink($tempPath); 141 | } 142 | 143 | /** 144 | * Returns the type of the addons. 145 | * 146 | * @see ResourcePackType 147 | */ 148 | public function getType() : int{ 149 | return $this->type; 150 | } 151 | 152 | /** Returns the Manifest object of the addons. */ 153 | public function getManifest() : Manifest{ 154 | return clone $this->manifest; 155 | } 156 | 157 | /** Returns the human-readable name of the addons */ 158 | public function getName() : string{ 159 | return $this->manifest->header->name; 160 | } 161 | 162 | /** Returns the addons UUID as a human-readable string */ 163 | public function getUuid() : string{ 164 | return $this->manifest->header->uuid; 165 | } 166 | 167 | /** Returns the size of the addons on disk in bytes. */ 168 | public function getSize() : int{ 169 | return strlen($this->contents) + 1; 170 | } 171 | 172 | /** Returns a version number for the addons in the format major.minor.patch */ 173 | public function getVersion() : string{ 174 | return implode(".", $this->manifest->header->version); 175 | } 176 | 177 | /** 178 | * Returns the raw SHA256 sum of the compressed addons zip. This is used by clients to validate addons downloads. 179 | * 180 | * @return string byte-array length 32 bytes 181 | */ 182 | public function getSha256() : string{ 183 | return $this->sha256; 184 | } 185 | 186 | /** 187 | * Returns a chunk of the addons zip as a byte-array for sending to clients. 188 | * 189 | * @param int $start Offset to start reading the chunk from 190 | * @param int $length Maximum length of data to return. 191 | * 192 | * @return string byte-array 193 | * @throws InvalidArgumentException if the chunk does not exist 194 | */ 195 | public function getChunk(int $start, int $length) : string{ 196 | return substr($this->contents, $start, $length); 197 | } 198 | 199 | /** @return string[] */ 200 | public function getFileList() : array{ 201 | return array_keys($this->files); 202 | } 203 | 204 | /** Returns the contents of the specified file. */ 205 | public function getFile(string $innerPath) : ?string{ 206 | return $this->files[$innerPath] ?? null; 207 | } 208 | 209 | /** 210 | * @param string $manifestFile 211 | * 212 | * @return Manifest 213 | * @throws JsonMapperException 214 | */ 215 | public static function parseManifestFile(string $manifestFile) : Manifest{ 216 | $manifestJson = (new CommentedJsonDecoder())->decode($manifestFile); 217 | if(!($manifestJson instanceof stdClass)){ 218 | throw new RuntimeException("manifest.json should contain a JSON object, not " . gettype($manifestJson)); 219 | } 220 | 221 | $mapper = new JsonMapper(); 222 | $mapper->bExceptionOnUndefinedProperty = true; 223 | $mapper->bExceptionOnMissingData = true; 224 | 225 | return $mapper->map($manifestJson, new Manifest()); 226 | } 227 | } -------------------------------------------------------------------------------- /src/ref/api/addonsmanager/addons/FolderAddons.php: -------------------------------------------------------------------------------- 1 | isFile()){ 51 | $realPath = $fileInfo->getPathname(); 52 | $innerPath = Path::makeRelative($realPath, $baseDir); 53 | 54 | $contents = file_get_contents($realPath); 55 | if($contents === false){ 56 | throw new ResourcePackException("Failed to open $realPath file"); 57 | } 58 | $files[$innerPath] = $contents; 59 | } 60 | } 61 | parent::__construct($files); 62 | } 63 | } -------------------------------------------------------------------------------- /src/ref/api/addonsmanager/addons/PluginAddons.php: -------------------------------------------------------------------------------- 1 | getResources() as $key => $fileInfo){ 43 | $path = Path::canonicalize($key); 44 | if(str_starts_with($path, $baseDir)){ 45 | $realPath = $fileInfo->getPathname(); 46 | $innerPath = Path::makeRelative($path, $baseDir); 47 | 48 | $contents = file_get_contents($realPath); 49 | if($contents === false){ 50 | throw new ResourcePackException("Failed to open $realPath file"); 51 | } 52 | $files[$innerPath] = $contents; 53 | } 54 | } 55 | parent::__construct($files); 56 | } 57 | } -------------------------------------------------------------------------------- /src/ref/api/addonsmanager/addons/ZippedAddons.php: -------------------------------------------------------------------------------- 1 | open($zipPath)) !== true){ 44 | throw new ResourcePackException("Encountered ZipArchive error code $openResult while trying to open $zipPath"); 45 | } 46 | $files = []; 47 | for($i = 0; $i < $archive->numFiles; ++$i){ 48 | $innerPath = $archive->getNameIndex($i); 49 | if(!str_ends_with($innerPath, '/')){ 50 | $files[$innerPath] = $archive->getFromIndex($i); 51 | } 52 | } 53 | parent::__construct($files); 54 | } 55 | } -------------------------------------------------------------------------------- /src/ref/api/addonsmanager/addons/json/Manifest.php: -------------------------------------------------------------------------------- 1 | format_version = $format_version; 68 | $manifest->header = $header; 69 | $manifest->modules = $modules; 70 | $manifest->metadata = $metadata; 71 | $manifest->capabilities = $capabilities; 72 | $manifest->dependencies = $dependencies; 73 | return $manifest; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/ref/api/addonsmanager/addons/json/ManifestDependencyEntry.php: -------------------------------------------------------------------------------- 1 | $value !== "" && $value !== null); 41 | } 42 | 43 | /** Perform a deep copy on clone */ 44 | public function __clone() : void{ 45 | foreach($this->jsonSerialize() as $key => $value){ 46 | if(is_object($value)){ 47 | $this->$key = clone $value; 48 | }elseif(is_array($value)){ 49 | $this->$key = array_map(static fn($v) => is_object($v) ? clone $v : $v, $value); 50 | }else{ 51 | $this->$key = $value; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/ref/api/addonsmanager/addons/json/ManifestHeader.php: -------------------------------------------------------------------------------- 1 | */ 47 | private array $files = []; 48 | 49 | /** @param array{int, int, int} $version */ 50 | public function __construct( 51 | private int $type, 52 | private string $name, 53 | private string $description = "", 54 | private string $uuid = "", 55 | private array $version = [1, 0, 0] 56 | ){ 57 | if($type !== ResourcePackType::RESOURCES && $type !== ResourcePackType::BEHAVIORS){ 58 | throw new ResourcePackException("Module type must be 'RESOURCES' or 'BEHAVIORS', '$type' given."); 59 | } 60 | } 61 | 62 | public function string(string $path, string $content) : self{ 63 | $this->files[$path] = $content; 64 | return $this; 65 | } 66 | 67 | public function json(string $path, mixed $json) : self{ 68 | $this->files[$path] = json_encode($json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 69 | return $this; 70 | } 71 | 72 | public function image(string $path, GdImage $image) : self{ 73 | $tempPath = tempnam(sys_get_temp_dir(), 'pm$'); 74 | imagepng($image, $tempPath); 75 | $this->files[$path] = file_get_contents($tempPath); 76 | unlink($tempPath); 77 | return $this; 78 | } 79 | 80 | public function build() : Addons{ 81 | $this->files[Addons::MANIFEST_FILE] = json_encode($this->generateManifest(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 82 | $addons = new Addons($this->files); 83 | unset($this->files[Addons::MANIFEST_FILE]); 84 | return $addons; 85 | } 86 | 87 | private function generateManifest() : Manifest{ 88 | return Manifest::create( 89 | 2, 90 | new ManifestHeader( 91 | $this->name, 92 | $this->uuid, 93 | $this->version, 94 | $this->description 95 | ), 96 | [ 97 | new ManifestModuleEntry( 98 | $this->type === ResourcePackType::RESOURCES ? "resources" : "data", 99 | "", 100 | [1, 0, 0] 101 | ) 102 | ] 103 | ); 104 | } 105 | } --------------------------------------------------------------------------------