├── .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 | }
--------------------------------------------------------------------------------