├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── common ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── saicone │ │ └── onetimepack │ │ ├── OneTimePack.java │ │ ├── core │ │ ├── PackBehavior.java │ │ ├── PackResult.java │ │ ├── PacketUser.java │ │ ├── Processor.java │ │ ├── ProtocolOptions.java │ │ ├── ProtocolState.java │ │ └── ServerGroup.java │ │ ├── module │ │ ├── TinySettings.java │ │ └── TinyYaml.java │ │ └── util │ │ ├── FileUtils.java │ │ ├── InfoReader.java │ │ └── ValueComparator.java │ └── resources │ ├── mappings.json │ └── settings.yml ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── module ├── module-bungee │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── saicone │ │ └── onetimepack │ │ └── core │ │ ├── BungeeProcessor.java │ │ └── packet │ │ ├── ResourcePackPop.java │ │ ├── ResourcePackPush.java │ │ └── ResourcePackStatus.java ├── module-mappings │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── saicone │ │ └── onetimepack │ │ ├── module │ │ └── Mappings.java │ │ └── util │ │ └── ProtocolVersion.java ├── module-packetevents │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── saicone │ │ └── onetimepack │ │ └── core │ │ ├── PacketEventsProcessor.java │ │ └── packet │ │ ├── CommonPacketWrapper.java │ │ ├── ResourcePackPop.java │ │ ├── ResourcePackPush.java │ │ └── ResourcePackStatus.java ├── module-protocolize │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── saicone │ │ └── onetimepack │ │ ├── core │ │ ├── MappedProtocolizeProcessor.java │ │ ├── ProtocolizeProcessor.java │ │ └── packet │ │ │ ├── ResourcePackPop.java │ │ │ ├── ResourcePackPush.java │ │ │ └── ResourcePackStatus.java │ │ └── module │ │ └── listener │ │ └── PacketListener.java ├── module-velocity │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── saicone │ │ └── onetimepack │ │ └── core │ │ └── VelocityProcessor.java └── module-vpacketevents │ ├── build.gradle │ └── src │ └── main │ └── java │ └── com │ └── saicone │ └── onetimepack │ └── core │ └── VPacketEventsProcessor.java ├── platform ├── platform-bungee │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── saicone │ │ └── onetimepack │ │ ├── BungeePlugin.java │ │ └── core │ │ └── BungeePacketUser.java └── platform-velocity │ ├── build.gradle │ └── src │ └── main │ └── java │ └── com │ └── saicone │ └── onetimepack │ ├── VelocityPlugin.java │ └── core │ └── VelocityPacketUser.java ├── plugin ├── build.gradle ├── bungeecord-api │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── saicone │ │ │ └── onetimepack │ │ │ └── BungeeBootstrap.java │ │ └── resources │ │ └── bungee.yml ├── bungeecord-packetevents │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── saicone │ │ │ └── onetimepack │ │ │ └── BungeeBootstrap.java │ │ └── resources │ │ └── bungee.yml ├── bungeecord-protocolize │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── saicone │ │ │ └── onetimepack │ │ │ └── BungeeBootstrap.java │ │ └── resources │ │ └── bungee.yml ├── velocity-api │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── saicone │ │ └── onetimepack │ │ └── VelocityBootstrap.java ├── velocity-packetevents │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── saicone │ │ └── onetimepack │ │ └── VelocityBootstrap.java ├── velocity-protocolize │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── saicone │ │ └── onetimepack │ │ ├── VelocityBootstrap.java │ │ └── core │ │ └── VelocityProtocolizeProcessor.java └── velocity-vpacketevents │ ├── build.gradle │ └── src │ └── main │ └── java │ └── com │ └── saicone │ └── onetimepack │ └── VelocityBootstrap.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Rubenicos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
T getRecursively(@NotNull P[] paths, @NotNull BiFunction getter, @Nullable T def) { 96 | for (@NotNull P path : paths) { 97 | final T value = getter.apply(this, path); 98 | if (value != null) { 99 | return value; 100 | } 101 | } 102 | return def; 103 | } 104 | 105 | @Nullable 106 | public String getString(@NotNull String path) { 107 | return getString(path, null); 108 | } 109 | 110 | @Nullable 111 | @Contract("_, !null -> !null") 112 | public String getString(@NotNull String path, @Nullable String def) { 113 | return get(path, def, String::valueOf); 114 | } 115 | 116 | @NotNull 117 | public List getStringList(@NotNull String path) { 118 | return getList(path, String::valueOf); 119 | } 120 | 121 | @Nullable 122 | public Integer getInt(@NotNull String path) { 123 | return getInt(path, null); 124 | } 125 | 126 | @Nullable 127 | @Contract("_, !null -> !null") 128 | public Integer getInt(@NotNull String path, @Nullable Integer def) { 129 | return get(path, def, object -> { 130 | if (object instanceof Number number) { 131 | return number.intValue(); 132 | } 133 | try { 134 | return Integer.parseInt(String.valueOf(object)); 135 | } catch (NumberFormatException e) { 136 | return null; 137 | } 138 | }); 139 | } 140 | 141 | @Nullable 142 | public Boolean getBoolean(@NotNull String path) { 143 | return getBoolean(path, null); 144 | } 145 | 146 | @Nullable 147 | @Contract("_, !null -> !null") 148 | public Boolean getBoolean(@NotNull String path, @Nullable Boolean def) { 149 | return get(path, def, object -> { 150 | if (object instanceof Number number) { 151 | if (number.longValue() == 1) { 152 | return true; 153 | } else if (number.longValue() == 0) { 154 | return false; 155 | } else { 156 | return null; 157 | } 158 | } 159 | return switch (String.valueOf(object)) { 160 | case "true", "yes", "y" -> true; 161 | case "false", "no", "n" -> false; 162 | default -> null; 163 | }; 164 | }); 165 | } 166 | 167 | @NotNull 168 | public List getList(@NotNull String path, @NotNull Function function) { 169 | return getList(path.toLowerCase().split("\\."), function); 170 | } 171 | 172 | @NotNull 173 | public List getList(@NotNull String[] path, @NotNull Function function) { 174 | final List list = new ArrayList<>(); 175 | final Object object = get(path); 176 | if (object == null) { 177 | return list; 178 | } 179 | final Consumer executor = value -> { 180 | if (value != null) { 181 | final T t = function.apply(value); 182 | if (t != null) { 183 | list.add(t); 184 | } 185 | } 186 | }; 187 | if (object instanceof Iterable> iterable) { 188 | for (Object value : iterable) { 189 | executor.accept(value); 190 | } 191 | } else if (object instanceof Object[] array) { 192 | for (Object value : array) { 193 | executor.accept(value); 194 | } 195 | } else if (object.getClass().isArray()) { 196 | final int length = Array.getLength(object); 197 | for (int i = 0; i < length; i++) { 198 | final Object value = Array.get(object, i); 199 | executor.accept(value); 200 | } 201 | } else { 202 | executor.accept(object); 203 | } 204 | return list; 205 | } 206 | 207 | @NotNull 208 | @SuppressWarnings("unchecked") 209 | public Set getKeys(@NotNull String path) { 210 | final Object object = get(path.toLowerCase().split("\\.")); 211 | return object instanceof Map,?> ? ((Map) object).keySet() : Set.of(); 212 | } 213 | 214 | public void load(@NotNull File folder) { 215 | data.clear(); 216 | final File file = FileUtils.saveResource(folder, fileName, false); 217 | if (file != null) { 218 | try (BufferedReader reader = new BufferedReader(new FileReader(file))) { 219 | final Object result = read(reader); 220 | if (result instanceof Map,?> map) { 221 | for (Map.Entry, ?> entry : map.entrySet()) { 222 | data.put(String.valueOf(entry.getKey()), entry.getValue()); 223 | } 224 | } 225 | } catch (IOException e) { 226 | e.printStackTrace(); 227 | } 228 | } 229 | } 230 | 231 | public void save(@NotNull File file) { 232 | try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { 233 | write(writer); 234 | } catch (IOException e) { 235 | e.printStackTrace(); 236 | } 237 | } 238 | 239 | @Nullable 240 | public abstract Object read(@NotNull Reader reader) throws IOException; 241 | 242 | public abstract void write(@NotNull Writer writer) throws IOException; 243 | } 244 | -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/module/TinyYaml.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.module; 2 | 3 | import com.saicone.onetimepack.util.InfoReader; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.io.BufferedWriter; 8 | import java.io.IOException; 9 | import java.io.Reader; 10 | import java.io.Writer; 11 | import java.util.ArrayList; 12 | import java.util.LinkedHashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.StringJoiner; 16 | 17 | public class TinyYaml extends TinySettings { 18 | 19 | private static final int ROOT = 0; 20 | private static final int MAP = 1; 21 | private static final int LIST = 2; 22 | private static final int SCALAR = 3; 23 | private static final int LITERAL_BLOCK_SCALAR = 4; 24 | 25 | public TinyYaml(@NotNull String fileName) { 26 | super(fileName); 27 | } 28 | 29 | public TinyYaml(@NotNull String fileName, @NotNull Map data) { 30 | super(fileName, data); 31 | } 32 | 33 | public @Nullable Object read(@NotNull Reader reader) throws IOException { 34 | return read(new InfoReader(reader, s -> s.startsWith("#")), 0, ROOT); 35 | } 36 | 37 | @Nullable 38 | protected Object read(@NotNull InfoReader reader, int prefix, int state) throws IOException { 39 | if (state == ROOT || state == MAP) { 40 | final Map map = new LinkedHashMap<>(); 41 | String line; 42 | while ((line = reader.walk()) != null) { 43 | final int spaces = spaces(line); 44 | if (state == MAP && spaces < prefix) { 45 | reader.with(line); 46 | break; 47 | } 48 | line = line.trim(); 49 | final int separator = line.indexOf(':'); 50 | if (separator < 1) { 51 | continue; 52 | } 53 | final Object value; 54 | if (line.length() - 1 == separator || line.substring(separator + 1).trim().startsWith("#")) { 55 | final String nextLine = reader.fly(); 56 | final int nextSpaces; 57 | if (nextLine == null || (nextSpaces = spaces(nextLine)) < (state == ROOT ? 1 : prefix)) { 58 | value = ""; 59 | } else { 60 | if (nextLine.trim().startsWith("-")) { 61 | value = read(reader, prefix, LIST); 62 | } else { 63 | value = read(reader, nextSpaces, MAP); 64 | } 65 | } 66 | } else { 67 | value = read(reader.with(line.substring(separator + 1)), prefix, SCALAR); 68 | } 69 | if (value != null) { 70 | map.put(line.substring(0, separator), value); 71 | } 72 | } 73 | return map; 74 | } else if (state == LIST) { 75 | final List list = new ArrayList<>(); 76 | String line; 77 | while ((line = reader.walk()) != null) { 78 | final int spaces = spaces(line); 79 | if (spaces < prefix) { 80 | reader.with(line); 81 | break; 82 | } 83 | final String trim = line.trim(); 84 | if (trim.isEmpty()) { 85 | continue; 86 | } 87 | if (trim.charAt(0) != '-') { 88 | reader.with(line); 89 | break; 90 | } 91 | line = trim.substring(1); 92 | final int totalSpaces = spaces + spaces(line) + 1; 93 | line = line.trim(); 94 | if (line.isEmpty()) { 95 | list.add(""); 96 | continue; 97 | } 98 | final char first = line.charAt(0); 99 | final Object value; 100 | if (first == '-') { 101 | value = read(reader.with(" ".repeat(totalSpaces) + line), totalSpaces, LIST); 102 | } else if (line.indexOf(':') > 0 && first != '\"' && first != '\'') { 103 | value = read(reader.with(" ".repeat(totalSpaces) + line), totalSpaces, MAP); 104 | } else { 105 | value = read(reader.with(line), spaces + 1, SCALAR); 106 | } 107 | if (value != null) { 108 | list.add(value); 109 | } 110 | } 111 | return list; 112 | } else if (state == SCALAR) { 113 | final String line = reader.walk().trim(); 114 | final Object value = value(line); 115 | if (!line.isEmpty() && line.charAt(0) != '\"' && line.charAt(0) != '\'') { 116 | if (value == "|-") { 117 | return read(reader, prefix + 1, LITERAL_BLOCK_SCALAR); 118 | } else if (value == "{}") { 119 | return new LinkedHashMap<>(); 120 | } else if (value == "[]") { 121 | return new ArrayList<>(); 122 | } 123 | } 124 | return value; 125 | } else if (state == LITERAL_BLOCK_SCALAR) { 126 | final StringJoiner joiner = new StringJoiner("\n"); 127 | String line; 128 | while ((line = reader.walk()) != null) { 129 | final int spaces = spaces(line); 130 | if (spaces < prefix) { 131 | reader.with(line); 132 | break; 133 | } 134 | joiner.add(line.trim()); 135 | } 136 | return joiner.toString(); 137 | } 138 | return null; 139 | } 140 | 141 | @NotNull 142 | private static Object value(@NotNull String s) { 143 | String trim = s.trim(); 144 | if (trim.isEmpty()) { 145 | return ""; 146 | } 147 | final char first = trim.charAt(0); 148 | if (first == '\"' || first == '\'') { 149 | if (trim.length() == 1) { 150 | return ""; 151 | } 152 | final int single = trim.lastIndexOf(first, 1); 153 | final int comment = trim.lastIndexOf('#', 1); 154 | if (single > 0 && comment > 0) { 155 | if (comment > single) { 156 | trim = trim.substring(0, comment).trim(); 157 | } 158 | } 159 | return trim.substring(1); 160 | } 161 | final int comment = trim.indexOf('#'); 162 | if (comment == 0) { 163 | return ""; 164 | } else if (comment > 0) { 165 | trim = trim.substring(0, comment); 166 | } 167 | if (trim.equals("true")) { 168 | return true; 169 | } else if (trim.equals("false")) { 170 | return false; 171 | } else { 172 | try { 173 | return Long.parseLong(trim); 174 | } catch (NumberFormatException ignored) { } 175 | try { 176 | return Double.parseDouble(trim); 177 | } catch (NumberFormatException ignored) { } 178 | } 179 | return trim; 180 | } 181 | 182 | private static int spaces(@NotNull String s) { 183 | int spaces = 0; 184 | for (char c : s.toCharArray()) { 185 | if (c != ' ') { 186 | break; 187 | } 188 | spaces++; 189 | } 190 | return spaces; 191 | } 192 | 193 | public void write(@NotNull Writer writer) throws IOException { 194 | write(writer, " "); 195 | } 196 | 197 | public void write(@NotNull Writer writer, @NotNull String indent) throws IOException { 198 | if (this.getData().isEmpty()) { 199 | writer.write(""); 200 | return; 201 | } 202 | write( 203 | writer instanceof BufferedWriter ? (BufferedWriter) writer : new BufferedWriter(writer), 204 | indent.isEmpty() ? " " : indent, 205 | "", 206 | this.getData(), 207 | ROOT 208 | ); 209 | } 210 | 211 | protected int write(@NotNull BufferedWriter writer, @NotNull String indent, @NotNull String prefix, @NotNull Object object, int state) throws IOException { 212 | if (object instanceof Map, ?> map) { 213 | if (map.isEmpty()) { 214 | writer.write(" {}"); 215 | return SCALAR; 216 | } 217 | // map: 218 | // key: 219 | if (state == MAP) { 220 | writer.newLine(); 221 | } 222 | boolean first = state == LIST; 223 | for (Map.Entry, ?> entry : map.entrySet()) { 224 | if (first) { 225 | // list: 226 | // - key: value 227 | writer.write(" " + entry.getKey() + ":"); 228 | first = false; 229 | } else { 230 | // key: value 231 | writer.write(prefix + entry.getKey() + ":"); 232 | } 233 | final int result = write(writer, indent, prefix + indent, entry.getValue(), MAP); 234 | if (result == SCALAR) { 235 | writer.newLine(); 236 | } 237 | // key1: value1 238 | // 239 | // key2: value2 240 | if (state == ROOT) { 241 | writer.newLine(); 242 | } 243 | } 244 | return MAP; 245 | } else if (object instanceof List> list) { 246 | if (list.isEmpty()) { 247 | writer.write(" []"); 248 | return SCALAR; 249 | } 250 | // map: 251 | // - 252 | if (state == MAP) { 253 | writer.newLine(); 254 | } 255 | boolean first = state == LIST; 256 | for (Object value : list) { 257 | if (first) { 258 | // list: 259 | // - - value 260 | writer.write(" -"); 261 | first = false; 262 | } else { 263 | // - value 264 | writer.write(prefix + indent + "-"); 265 | } 266 | final int result = write(writer, indent, prefix + indent + " ", value, LIST); 267 | if (result == SCALAR) { 268 | writer.newLine(); 269 | } 270 | } 271 | return LIST; 272 | } else if (object instanceof Boolean || object instanceof Number) { 273 | writer.write(" " + object); 274 | } else if (object instanceof String str && str.contains("\n") && !str.equals("\n")) { 275 | // key: |- 276 | // line1 277 | // line2 278 | writer.write(" |-"); 279 | for (String line : str.split("\n")) { 280 | writer.newLine(); 281 | writer.write(prefix + indent + line); 282 | } 283 | } else { 284 | writer.write(" \"" + object + "\""); 285 | } 286 | return SCALAR; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.InputStreamReader; 11 | import java.net.MalformedURLException; 12 | import java.net.URL; 13 | import java.nio.file.Files; 14 | import java.nio.file.StandardCopyOption; 15 | 16 | public class FileUtils { 17 | 18 | FileUtils() { 19 | } 20 | 21 | @NotNull 22 | public static File getFile(@NotNull File folder, @NotNull String path) { 23 | File file = folder; 24 | for (String s : path.split("/")) { 25 | file = new File(file, s); 26 | } 27 | return file; 28 | } 29 | 30 | @Nullable 31 | public static InputStream getResource(@NotNull String path) { 32 | return getResource(FileUtils.class, path); 33 | } 34 | 35 | @Nullable 36 | public static InputStream getResource(@NotNull Class> clazz, @NotNull String path) { 37 | return clazz.getClassLoader().getResourceAsStream(path); 38 | } 39 | 40 | @Nullable 41 | public static File saveResource(@NotNull File folder, @NotNull String path, boolean replace) { 42 | return saveResource(FileUtils.class, folder, path, replace); 43 | } 44 | 45 | @Nullable 46 | public static File saveResource(@NotNull Class> clazz, @NotNull File folder, @NotNull String path, boolean replace) { 47 | final InputStream input = getResource(clazz, path); 48 | if (input != null) { 49 | final File file = getFile(folder, path); 50 | if (!file.getParentFile().exists()) { 51 | file.getParentFile().mkdirs(); 52 | } 53 | if (file.exists() && !replace) { 54 | return file; 55 | } 56 | try { 57 | Files.copy(input, file.toPath(), StandardCopyOption.REPLACE_EXISTING); 58 | return file; 59 | } catch (IOException ignored) { } 60 | } 61 | return null; 62 | } 63 | 64 | @Nullable 65 | public static String readFromFile(@NotNull File file) { 66 | try { 67 | return String.join("", Files.readAllLines(file.toPath())); 68 | } catch (IOException e) { 69 | e.printStackTrace(); 70 | } 71 | return null; 72 | } 73 | 74 | @Nullable 75 | public static String readFromUrl(@NotNull String url) { 76 | try { 77 | return readFromUrl(new URL(url)); 78 | } catch (MalformedURLException e) { 79 | e.printStackTrace(); 80 | } 81 | return null; 82 | } 83 | 84 | @Nullable 85 | public static String readFromUrl(@NotNull URL url) { 86 | try (InputStream in = url.openStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { 87 | final StringBuilder out = new StringBuilder(); 88 | String line; 89 | while ((line = reader.readLine()) != null) { 90 | out.append(line); 91 | } 92 | 93 | return out.toString(); 94 | } catch (IOException e) { 95 | e.printStackTrace(); 96 | } 97 | return null; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/util/InfoReader.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.util; 2 | 3 | import org.jetbrains.annotations.Contract; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.Closeable; 9 | import java.io.IOException; 10 | import java.io.Reader; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.function.Predicate; 14 | 15 | public class InfoReader implements Closeable { 16 | 17 | private final BufferedReader reader; 18 | private final Predicate comment; 19 | 20 | private transient final List toConsume = new ArrayList<>(); 21 | 22 | public InfoReader(@NotNull Reader reader) { 23 | this(reader, s -> false); 24 | } 25 | 26 | public InfoReader(@NotNull Reader reader, @NotNull Predicate comment) { 27 | this.reader = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); 28 | this.comment = comment; 29 | } 30 | 31 | @NotNull 32 | public BufferedReader getReader() { 33 | return reader; 34 | } 35 | 36 | @NotNull 37 | public Predicate getComment() { 38 | return comment; 39 | } 40 | 41 | @Nullable 42 | protected String readNextLine() throws IOException { 43 | String line; 44 | while ((line = this.reader.readLine()) != null) { 45 | final String trim = line.trim(); 46 | if (trim.isEmpty() || this.comment.test(trim)) { 47 | continue; 48 | } 49 | break; 50 | } 51 | return line; 52 | } 53 | 54 | @NotNull 55 | @Contract("_ -> this") 56 | public InfoReader with(@NotNull String s) { 57 | toConsume.add(0, s); 58 | return this; 59 | } 60 | 61 | @Nullable 62 | public String walk() throws IOException { 63 | if (!toConsume.isEmpty()) { 64 | return toConsume.remove(0); 65 | } 66 | return readNextLine(); 67 | } 68 | 69 | @Nullable 70 | public String fly() throws IOException { 71 | final String line = readNextLine(); 72 | if (line != null) { 73 | toConsume.add(0, line); 74 | } 75 | return line; 76 | } 77 | 78 | @Override 79 | public void close() throws IOException { 80 | this.reader.close(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/util/ValueComparator.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.Objects; 7 | 8 | @FunctionalInterface 9 | public interface ValueComparator { 10 | 11 | @NotNull 12 | static ValueComparator read(@NotNull String str, @NotNull Provider provider) { 13 | ValueComparator result = null; 14 | for (String block : str.split("(?i) (AND|&&) ")) { 15 | ValueComparator append = null; 16 | if (block.contains(" OR ")) { 17 | for (String optional : block.split("(?i) (OR|[|][|]) ")) { 18 | final ValueComparator comparator = provider.readComparator(optional); 19 | if (append == null) { 20 | append = comparator; 21 | } else if (comparator != null) { 22 | append = append.or(comparator); 23 | } 24 | } 25 | } else { 26 | append = provider.readComparator(block); 27 | } 28 | 29 | if (append != null) { 30 | if (result == null) { 31 | result = append; 32 | } else { 33 | result = result.and(append); 34 | } 35 | } 36 | } 37 | return result != null ? result : e -> true; 38 | } 39 | 40 | @Nullable 41 | Object getValue(@NotNull E e); 42 | 43 | default boolean matches(@NotNull E e1, @NotNull E e2) { 44 | return Objects.equals(getValue(e1), getValue(e2)); 45 | } 46 | 47 | @NotNull 48 | default ValueComparator nonNull() { 49 | return new ValueComparator() { 50 | @Override 51 | public @Nullable Object getValue(@NotNull E e) { 52 | return ValueComparator.this.getValue(e); 53 | } 54 | 55 | @Override 56 | public boolean matches(@NotNull E e1, @NotNull E e2) { 57 | final Object value1 = getValue(e1); 58 | if (value1 == null) return false; 59 | final Object value2 = getValue(e2); 60 | if (value2 == null) return false; 61 | return value1.equals(value2); 62 | } 63 | }; 64 | } 65 | 66 | @NotNull 67 | default ValueComparator and(@NotNull ValueComparator comparator) { 68 | return new ValueComparator() { 69 | @Override 70 | public @Nullable Object getValue(@NotNull E e) { 71 | return ValueComparator.this.getValue(e); 72 | } 73 | 74 | @Override 75 | public boolean matches(@NotNull E e1, @NotNull E e2) { 76 | return ValueComparator.this.matches(e1, e2) && comparator.matches(e1, e2); 77 | } 78 | }; 79 | } 80 | 81 | @NotNull 82 | default ValueComparator or(@NotNull ValueComparator comparator) { 83 | return new ValueComparator() { 84 | @Override 85 | public @Nullable Object getValue(@NotNull E e) { 86 | return ValueComparator.this.getValue(e); 87 | } 88 | 89 | @Override 90 | public boolean matches(@NotNull E e1, @NotNull E e2) { 91 | return ValueComparator.this.matches(e1, e2) || comparator.matches(e1, e2); 92 | } 93 | }; 94 | } 95 | 96 | interface Provider { 97 | @Nullable 98 | ValueComparator readComparator(@NotNull String input); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /common/src/main/resources/mappings.json: -------------------------------------------------------------------------------- 1 | { 2 | "external": { 3 | "enabled": true, 4 | "url": "https://raw.githubusercontent.com/saicone/OneTimePack/main/common/src/main/resources/mappings.json" 5 | }, 6 | "packet": { 7 | "ResourcePackSend": { 8 | "1.8": 72, 9 | "1.9 - 1.11.2": 50, 10 | "1.12": 51, 11 | "1.12.1 - 1.12.2": 52, 12 | "1.13 - 1.13.2": 55, 13 | "1.14 - 1.14.4": 57, 14 | "1.15 - 1.15.2": 58, 15 | "1.16 - 1.16.1": 57, 16 | "1.16.2 - 1.16.5": 56, 17 | "1.17 - 1.18.2": 60, 18 | "1.19": 58, 19 | "1.19.1 - 1.19.2": 61, 20 | "1.19.3": 60, 21 | "1.19.4 - 1.20": 64, 22 | "1.20.2": 66, 23 | "1.20.3": 68, 24 | "766-767": 70, 25 | "768-769": 75, 26 | "770": 74 27 | }, 28 | "ResourcePackRemove": { 29 | "1.20.3": 67, 30 | "766-767": 69, 31 | "768-769": 74, 32 | "770": 73 33 | }, 34 | "ResourcePackStatus": { 35 | "1.8": 25, 36 | "1.9 - 1.11.2": 22, 37 | "1.12 - 1.12.2": 24, 38 | "1.13 - 1.13.2": 29, 39 | "1.14 - 1.15.2": 31, 40 | "1.16 - 1.16.1": 32, 41 | "1.16.2 - 1.18.2": 33, 42 | "1.19": 35, 43 | "1.19.1 - 1.20": 36, 44 | "1.20.2": 39, 45 | "1.20.3": 40, 46 | "766-767": 43, 47 | "768": 45, 48 | "769-770": 47 49 | } 50 | }, 51 | "packet-configuration": { 52 | "ResourcePackSend": { 53 | "1.20.2": 6, 54 | "1.20.3": 7, 55 | "766-770": 9 56 | }, 57 | "ResourcePackRemove": { 58 | "1.20.3": 6, 59 | "766-770": 8 60 | }, 61 | "ResourcePackStatus": { 62 | "1.20.2-1.20.3": 5, 63 | "766-770": 6 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /common/src/main/resources/settings.yml: -------------------------------------------------------------------------------- 1 | # Plugin related configuration 2 | plugin: 3 | # Log level to see messages in console: 4 | # 0 = disabled 5 | # 1 = errors 6 | # 2 = 1 + warnings 7 | # 3 = 2 + information 8 | # 4 = 3 + debug messages 9 | log-level: 2 10 | 11 | # Protocol state configuration 12 | protocol: 13 | # Default options to all protocols 14 | default: 15 | # Pack comparator: 16 | # UUID = Compare by unique ID (Only available for MC +1.20.3) 17 | # URL = Compare by download URL 18 | # HASH = Compare by 40 char hash ID 19 | # PROMPT = Compare using the prompt message (Only available for MC +1.17) 20 | # ALL = Check is the two resource packs are equals 21 | # ANY = Don't compare, just cancel any other pack sending to player 22 | # Use multiple comparisons with 'AND', or optional with 'OR' 23 | # Add '!' to compare object only if it's not null 24 | comparator: !UUID OR !HASH OR URL 25 | # Default pack status to send if player don't have any status saved: 26 | # SUCCESS_DOWNLOAD = The pack was successfully downloaded, and it's active by the client 27 | # DECLINED = The pack was declined by the player 28 | # FAILED_DOWNLOAD = The client cannot download the pack correctly 29 | # ACCEPTED = The pack is currently downloading by the client 30 | # DOWNLOADED = The pack is already downloaded by the client (this status only appear if the client is on configuration screen) 31 | # INVALID_URL = The pack URL cannot be used to download the resource pack 32 | # FAILED_RELOAD = The pack was downloaded by the client, but cannot be reapplied 33 | # DISCARDED = The pack is already downloaded, but was ignored 34 | # Set to 'none' to disable 35 | default-status: none 36 | # Pack behavior (Only for +1.20.3 clients): 37 | # STACK = Stack new resource pack into loaded packs 38 | # OVERRIDE = Clear all loaded packs on detect new resource pack 39 | behavior: OVERRIDE 40 | # Allow or not resource pack re-sending 41 | send: false 42 | # Allow or not resource pack remove for +1.20.3 clients 43 | remove: true 44 | # Allow or not all resource packs can be cleared for +1.20.3 clients 45 | clear: true 46 | # Set minimum protocol version to send resource pack to player, visit https://wiki.vg/Protocol_version_numbers 47 | # If a default status is set, will be sent to server 48 | min-protocol: -1 49 | # PLAY protocol options 50 | play: 51 | # Allow remove may generate problems with severs using ItemsAdder 52 | remove: false 53 | # Allow clear may generate problems with < 1.20.3 servers using ViaVersion 54 | clear: false 55 | # CONFIGURATION protocol options 56 | configuration: 57 | behavior: STACK 58 | 59 | # Server groups configuration 60 | group: 61 | # Group ID 62 | example: 63 | # Servers that the group belong 64 | servers: 65 | - myserver 66 | - otherserver 67 | - aserver 68 | # Override protocol configuration 69 | protocol: 70 | default: 71 | behavior: STACK 72 | 73 | # Experimental configuration 74 | experimental: 75 | # Send or not cached resource pack to 1.20.2 clients when CONFIGURATION protocol starts 76 | # This option solve resource pack clear on server change, but will make 1.20.2 players to re-download resource pack every time they switch servers 77 | send-cached-1-20-2: false 78 | # Send or not invalid resource packs to clients 79 | send-invalid: false -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=com.saicone.onetimepack 2 | version=2.1 3 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | metadata.format.version = "1.1" 2 | 3 | [versions] 4 | 5 | # Libraries 6 | netty = "4.2.0.Final" 7 | gson = "2.11.0" 8 | guava = "33.3.1-jre" 9 | 10 | velocity = "3.4.0-SNAPSHOT" 11 | bungeecord = "1.21-R0.3-SNAPSHOT" 12 | 13 | packetevents = "2.7.0" 14 | protocolize = "2.4.1" 15 | vpacketevents = "1.1.0" 16 | 17 | adventure = "4.20.0" 18 | 19 | # Plugins 20 | blossom = "1.3.0" 21 | shadow = "8.3.5" 22 | 23 | 24 | [libraries] 25 | 26 | netty-buffer = { module = "io.netty:netty-buffer", version.ref = "netty" } 27 | netty-transport = { module = "io.netty:netty-transport", version.ref = "netty" } 28 | gson = { module = "com.google.code.gson:gson", version.ref = "gson" } 29 | guava = { module = "com.google.guava:guava", version.ref = "guava" } 30 | 31 | velocity-api = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" } 32 | velocity-proxy = { module = "com.velocitypowered:velocity-proxy", version.ref = "velocity" } 33 | 34 | bungeecord-api = { module = "net.md-5:bungeecord-api", version.ref = "bungeecord" } 35 | bungeecord-protocol = { module = "net.md-5:bungeecord-protocol", version.ref = "bungeecord" } 36 | bungeecord-proxy = { module = "net.md-5:bungeecord-proxy", version.ref = "bungeecord" } 37 | 38 | packetevents-api = { module = "com.github.retrooper:packetevents-api", version.ref = "packetevents" } 39 | packetevents-bungeecord = { module = "com.github.retrooper:packetevents-bungeecord", version.ref = "packetevents" } 40 | packetevents-velocity = { module = "com.github.retrooper:packetevents-velocity", version.ref = "packetevents" } 41 | 42 | protocolize-api = { module = "dev.simplix:protocolize-api", version.ref = "protocolize" } 43 | protocolize-bungeecord = { module = "dev.simplix:protocolize-bungeecord", version.ref = "protocolize" } 44 | protocolize-velocity = { module = "dev.simplix:protocolize-velocity", version.ref = "protocolize" } 45 | 46 | vpacketevents-api = { module = "io.github.4drian3d:vpacketevents-api", version.ref = "vpacketevents" } 47 | 48 | adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure" } 49 | 50 | [plugins] 51 | 52 | blossom = { id = "net.kyori.blossom", version.ref = "blossom" } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saicone/OneTimePack/552ee79b481b7d385da5118bddf032c42cbeccd0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /module/module-bungee/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } 3 | } 4 | 5 | dependencies { 6 | implementation project(':module:module-mappings') 7 | compileOnly(libs.bungeecord.api) { 8 | exclude group: 'com.mojang' 9 | } 10 | compileOnly(libs.bungeecord.protocol) { 11 | exclude group: 'com.mojang' 12 | } 13 | } -------------------------------------------------------------------------------- /module/module-bungee/src/main/java/com/saicone/onetimepack/core/BungeeProcessor.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import com.saicone.onetimepack.core.packet.ResourcePackPop; 5 | import com.saicone.onetimepack.core.packet.ResourcePackPush; 6 | import com.saicone.onetimepack.core.packet.ResourcePackStatus; 7 | import com.saicone.onetimepack.module.Mappings; 8 | import com.saicone.onetimepack.util.ProtocolVersion; 9 | import com.saicone.onetimepack.util.ValueComparator; 10 | import net.md_5.bungee.api.connection.ProxiedPlayer; 11 | import net.md_5.bungee.connection.CancelSendSignal; 12 | import net.md_5.bungee.connection.DownstreamBridge; 13 | import net.md_5.bungee.connection.UpstreamBridge; 14 | import net.md_5.bungee.protocol.AbstractPacketHandler; 15 | import net.md_5.bungee.protocol.DefinedPacket; 16 | import net.md_5.bungee.protocol.Protocol; 17 | import net.md_5.bungee.protocol.ProtocolConstants; 18 | import org.jetbrains.annotations.NotNull; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.invoke.MethodHandles; 23 | import java.lang.reflect.Constructor; 24 | import java.lang.reflect.Field; 25 | import java.lang.reflect.Method; 26 | import java.util.Iterator; 27 | import java.util.List; 28 | import java.util.NoSuchElementException; 29 | import java.util.Optional; 30 | import java.util.UUID; 31 | import java.util.function.Supplier; 32 | 33 | public class BungeeProcessor extends Processor { 34 | 35 | private static final MethodHandle DOWNSTREAM_CON; 36 | private static final MethodHandle UPSTREAM_CON; 37 | 38 | private static final MethodHandle NEW_PROTOCOL_MAPPING; 39 | private static final MethodHandle REGISTER_PACKET; 40 | 41 | static { 42 | MethodHandle downstream$con = null; 43 | MethodHandle upstream$con = null; 44 | 45 | MethodHandle new$$ProtocolMapping = null; 46 | MethodHandle registerPacket = null; 47 | 48 | try { 49 | final MethodHandles.Lookup lookup = MethodHandles.lookup(); 50 | 51 | final Field field$downstream = DownstreamBridge.class.getDeclaredField("con"); 52 | field$downstream.setAccessible(true); 53 | downstream$con = lookup.unreflectGetter(field$downstream); 54 | 55 | final Field field$upstream = UpstreamBridge.class.getDeclaredField("con"); 56 | field$upstream.setAccessible(true); 57 | upstream$con = lookup.unreflectGetter(field$upstream); 58 | 59 | 60 | final Class> ProtocolMapping = Class.forName(Protocol.class.getName() + "$ProtocolMapping"); 61 | 62 | final Constructor> constructor$ProtocolMapping = ProtocolMapping.getDeclaredConstructor(int.class, int.class); 63 | constructor$ProtocolMapping.setAccessible(true); 64 | new$$ProtocolMapping = lookup.unreflectConstructor(constructor$ProtocolMapping); 65 | 66 | final Method method$registerPacket = Protocol.DirectionData.class.getDeclaredMethod("registerPacket", Class.class, Supplier.class, ProtocolMapping.arrayType()); 67 | method$registerPacket.setAccessible(true); 68 | registerPacket = lookup.unreflect(method$registerPacket); 69 | } catch (Throwable t) { 70 | t.printStackTrace(); 71 | } 72 | 73 | DOWNSTREAM_CON = downstream$con; 74 | UPSTREAM_CON = upstream$con; 75 | 76 | NEW_PROTOCOL_MAPPING = new$$ProtocolMapping; 77 | REGISTER_PACKET = registerPacket; 78 | } 79 | 80 | private Mappings> mappings; 81 | 82 | @Override 83 | public void onLoad() { 84 | // Fix non-existent protocol versions 85 | ProtocolVersion.getProtocols().put("1.11.2", ProtocolConstants.MINECRAFT_1_11_1); 86 | ProtocolVersion.getProtocols().put("1.16.5", ProtocolConstants.MINECRAFT_1_16_4); 87 | ProtocolVersion.getProtocols().put("1.19.2", ProtocolConstants.MINECRAFT_1_19_1); 88 | 89 | mappings = new Mappings<>(OneTimePack.get().getProvider().getPluginFolder(), "mappings.json", IntRangeEntry::new); 90 | mappings.load(); 91 | onLoad("ResourcePackSend", ResourcePackPush.class, ResourcePackPush::new, true); 92 | onLoad("ResourcePackRemove", ResourcePackPop.class, ResourcePackPop::new, true); 93 | onLoad("ResourcePackStatus", ResourcePackStatus.class, ResourcePackStatus::new, false); 94 | } 95 | 96 | protected void onLoad(@NotNull String name, @NotNull Class extends DefinedPacket> clazz, @NotNull Supplier extends DefinedPacket> constructor, boolean clientbound) { 97 | if (!mappings.contains(name)) { 98 | OneTimePack.log(1, "Cannot find mappings for " + clazz.getName() + " this will cause the plugin to not work correctly"); 99 | return; 100 | } 101 | 102 | try { 103 | final List> play = mappings.getMappings(name, "play"); 104 | if (play == null) { 105 | OneTimePack.log(1, "Cannot find PLAY mappings for " + clazz.getName() + " this will cause the plugin to not work correctly"); 106 | } else { 107 | onLoad(play, clientbound ? Protocol.GAME.TO_CLIENT : Protocol.GAME.TO_SERVER, clazz, constructor); 108 | } 109 | 110 | final List> configuration = mappings.getMappings(name, "configuration"); 111 | if (configuration == null) { 112 | OneTimePack.log(1, "Cannot find CONFIGURATION mappings for " + clazz.getName() + " this will cause the plugin to not work correctly"); 113 | } else { 114 | onLoad(configuration, clientbound ? Protocol.CONFIGURATION.TO_CLIENT : Protocol.CONFIGURATION.TO_SERVER, clazz, constructor); 115 | } 116 | } catch (Throwable t) { 117 | t.printStackTrace(); 118 | } 119 | } 120 | 121 | protected void onLoad(@NotNull List> entries, @NotNull Protocol.DirectionData direction, @NotNull Class extends DefinedPacket> clazz, @NotNull Supplier extends DefinedPacket> constructor) throws Throwable { 122 | final Object[] args = new Object[entries.size() + 3]; 123 | args[0] = direction; 124 | args[1] = clazz; 125 | args[2] = constructor; 126 | int index = 3; 127 | for (IntRangeEntry range : entries) { 128 | final int protocol = range.getMin(); 129 | args[index] = NEW_PROTOCOL_MAPPING.invoke(protocol, range.getValue()); 130 | index++; 131 | } 132 | 133 | REGISTER_PACKET.invokeWithArguments(args); 134 | } 135 | 136 | public void onPackPush(@NotNull ResourcePackPush packet, @NotNull AbstractPacketHandler handler) { 137 | final ProxiedPlayer player = getPlayer(handler); 138 | if (player == null) return; 139 | 140 | final Optional optional = onPackPush(player, state(packet.getProtocol()), packet, packet.getUniqueId(), packet.getHash()); 141 | if (optional == null) return; 142 | 143 | final PackResult result = optional.orElse(null); 144 | if (result == null) { 145 | throw CancelSendSignal.INSTANCE; 146 | } 147 | 148 | player.getServer().unsafe().sendPacket(packet.asStatus(result, player.getPendingConnection().getVersion())); 149 | OneTimePack.log(4, () -> "Sent cached result " + result.name() + " from user " + player.getUniqueId()); 150 | 151 | throw CancelSendSignal.INSTANCE; 152 | } 153 | 154 | public void onPackPop(@NotNull ResourcePackPop packet, @NotNull AbstractPacketHandler handler) throws Exception { 155 | final ProxiedPlayer player = getPlayer(handler); 156 | if (player != null && onPackPop(player, state(packet.getProtocol()), packet, packet.getUniqueId())) { 157 | throw CancelSendSignal.INSTANCE; 158 | } 159 | } 160 | 161 | public void onPackStatus(@NotNull ResourcePackStatus packet, @NotNull AbstractPacketHandler handler) throws Exception { 162 | final ProxiedPlayer player = getPlayer(handler); 163 | if (player != null) { 164 | onPackStatus(player, packet.getUniqueId(), packet.getResult()); 165 | } 166 | } 167 | 168 | @NotNull 169 | private ProtocolState state(@NotNull Protocol protocol) { 170 | // For some reason Bungeecord use a different protocol ordinal values 171 | return switch (protocol) { 172 | case HANDSHAKE -> ProtocolState.HANDSHAKING; 173 | case GAME -> ProtocolState.PLAY; 174 | case STATUS -> ProtocolState.STATUS; 175 | case LOGIN -> ProtocolState.LOGIN; 176 | case CONFIGURATION -> ProtocolState.CONFIGURATION; 177 | }; 178 | } 179 | 180 | @Nullable 181 | private ProxiedPlayer getPlayer(@NotNull AbstractPacketHandler handler) { 182 | if (handler instanceof DownstreamBridge) { 183 | try { 184 | return (ProxiedPlayer) DOWNSTREAM_CON.invoke(handler); 185 | } catch (Throwable t) { 186 | throw new RuntimeException(t); 187 | } 188 | } else if (handler instanceof UpstreamBridge) { 189 | try { 190 | return (ProxiedPlayer) UPSTREAM_CON.invoke(handler); 191 | } catch (Throwable t) { 192 | throw new RuntimeException(t); 193 | } 194 | } else { 195 | OneTimePack.log(2, "Received packed on invalid handler: " + handler.getClass()); 196 | return null; 197 | } 198 | } 199 | 200 | @Override 201 | protected @NotNull UUID getUserId(@NotNull ProxiedPlayer user) { 202 | return user.getUniqueId(); 203 | } 204 | 205 | @Override 206 | protected @Nullable ValueComparator getPackValue(@NotNull String name) { 207 | return switch (name) { 208 | case "UUID" -> ResourcePackPush::getUniqueId; 209 | case "URL" -> ResourcePackPush::getUrl; 210 | case "HASH" -> ResourcePackPush::getHash; 211 | case "PROMPT" -> pack -> pack.getPrompt() == null ? null : pack.getJsonPrompt(); 212 | case "ALL" -> pack -> pack; 213 | case "ANY" -> pack -> true; 214 | default -> null; 215 | }; 216 | } 217 | 218 | @Override 219 | public void clearPackets(@NotNull ProxiedPlayer user, @NotNull ProtocolState state) { 220 | user.unsafe().sendPacket(new ResourcePackPop(false, null)); 221 | } 222 | 223 | public static final class IntRangeEntry implements Iterable { 224 | 225 | private final int min; 226 | private final int max; 227 | private final T value; 228 | 229 | public IntRangeEntry(int min, int max, @NotNull T value) { 230 | this.min = min; 231 | this.max = max; 232 | this.value = value; 233 | } 234 | 235 | public int getMin() { 236 | return min; 237 | } 238 | 239 | public int getMax() { 240 | return max; 241 | } 242 | 243 | @NotNull 244 | public T getValue() { 245 | return value; 246 | } 247 | 248 | @Override 249 | public @NotNull Iterator iterator() { 250 | return new Iterator<>() { 251 | private int current = min; 252 | 253 | @Override 254 | public boolean hasNext() { 255 | return current <= max; 256 | } 257 | 258 | @Override 259 | public Integer next() { 260 | if (!hasNext()) throw new NoSuchElementException(); 261 | return current++; 262 | } 263 | }; 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /module/module-bungee/src/main/java/com/saicone/onetimepack/core/packet/ResourcePackPop.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core.packet; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import com.saicone.onetimepack.core.BungeeProcessor; 5 | import io.netty.buffer.ByteBuf; 6 | import net.md_5.bungee.protocol.AbstractPacketHandler; 7 | import net.md_5.bungee.protocol.DefinedPacket; 8 | import net.md_5.bungee.protocol.Protocol; 9 | import net.md_5.bungee.protocol.ProtocolConstants; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.util.Objects; 14 | import java.util.UUID; 15 | 16 | public class ResourcePackPop extends DefinedPacket { 17 | 18 | private boolean hasUniqueId; 19 | private UUID uniqueId; 20 | 21 | private transient Protocol protocol; 22 | 23 | public ResourcePackPop() { 24 | } 25 | 26 | public ResourcePackPop(boolean hasUniqueId, @Nullable UUID uniqueId) { 27 | this.hasUniqueId = hasUniqueId; 28 | this.uniqueId = uniqueId; 29 | } 30 | 31 | public boolean hasUniqueId() { 32 | return hasUniqueId; 33 | } 34 | 35 | @Nullable 36 | public UUID getUniqueId() { 37 | return uniqueId; 38 | } 39 | 40 | @NotNull 41 | public Protocol getProtocol() { 42 | return protocol == null ? Protocol.GAME : protocol; 43 | } 44 | 45 | public void setHasUniqueId(boolean hasUniqueId) { 46 | this.hasUniqueId = hasUniqueId; 47 | } 48 | 49 | public void setUniqueId(@Nullable UUID uniqueId) { 50 | this.uniqueId = uniqueId; 51 | } 52 | 53 | @Override 54 | public void handle(AbstractPacketHandler handler) throws Exception { 55 | if (OneTimePack.get().getPacketHandler() instanceof BungeeProcessor processor) { 56 | processor.onPackPop(this, handler); 57 | } 58 | } 59 | 60 | @Override 61 | public void read(ByteBuf buf, Protocol protocol, ProtocolConstants.Direction direction, int protocolVersion) { 62 | this.protocol = protocol; 63 | 64 | hasUniqueId = buf.readBoolean(); 65 | if (hasUniqueId) { 66 | uniqueId = readUUID(buf); 67 | } 68 | } 69 | 70 | @Override 71 | public void write(ByteBuf buf, Protocol protocol, ProtocolConstants.Direction direction, int protocolVersion) { 72 | this.protocol = protocol; 73 | 74 | buf.writeBoolean(hasUniqueId); 75 | if (hasUniqueId) { 76 | writeUUID(uniqueId, buf); 77 | } 78 | } 79 | 80 | @Override 81 | public boolean equals(Object obj) { 82 | if (this == obj) return true; 83 | if (obj == null || getClass() != obj.getClass()) return false; 84 | 85 | ResourcePackPop that = (ResourcePackPop) obj; 86 | 87 | if (hasUniqueId != that.hasUniqueId) return false; 88 | return Objects.equals(uniqueId, that.uniqueId); 89 | } 90 | 91 | @Override 92 | public int hashCode() { 93 | int result = (hasUniqueId ? 1 : 0); 94 | result = 31 * result + (uniqueId != null ? uniqueId.hashCode() : 0); 95 | return result; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | return "ClientboundResourcePackPopPacket{" + 101 | "hasUniqueId=" + hasUniqueId + 102 | (hasUniqueId ? ", uniqueId=" + uniqueId : "") + 103 | '}'; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /module/module-bungee/src/main/java/com/saicone/onetimepack/core/packet/ResourcePackPush.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core.packet; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | import com.saicone.onetimepack.OneTimePack; 6 | import com.saicone.onetimepack.core.BungeeProcessor; 7 | import com.saicone.onetimepack.core.PackResult; 8 | import io.netty.buffer.ByteBuf; 9 | import net.md_5.bungee.api.chat.BaseComponent; 10 | import net.md_5.bungee.protocol.AbstractPacketHandler; 11 | import net.md_5.bungee.protocol.ChatSerializer; 12 | import net.md_5.bungee.protocol.DefinedPacket; 13 | import net.md_5.bungee.protocol.Protocol; 14 | import net.md_5.bungee.protocol.ProtocolConstants; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | import java.nio.charset.StandardCharsets; 19 | import java.util.Objects; 20 | import java.util.UUID; 21 | 22 | public class ResourcePackPush extends DefinedPacket { 23 | 24 | public static final int MAX_HASH_LENGTH = 40; 25 | 26 | private UUID uniqueId; // Added in 1.20.3 27 | private String url; 28 | private String hash; 29 | 30 | // Added in 1.17 31 | private boolean forced; 32 | private boolean hasPromptMessage; 33 | private BaseComponent prompt; 34 | 35 | private transient Protocol protocol; 36 | 37 | public ResourcePackPush() { 38 | } 39 | 40 | @Deprecated(since = "1.17") 41 | public ResourcePackPush(@Nullable String url, @Nullable String hash) { 42 | this.url = url; 43 | this.hash = hash; 44 | } 45 | 46 | @Deprecated(since = "1.20.3") 47 | public ResourcePackPush(@Nullable String url, @Nullable String hash, boolean forced, boolean hasPromptMessage, @Nullable BaseComponent prompt) { 48 | this.url = url; 49 | this.hash = hash; 50 | this.forced = forced; 51 | this.hasPromptMessage = hasPromptMessage; 52 | this.prompt = prompt; 53 | } 54 | 55 | public ResourcePackPush(@Nullable UUID uniqueId, @Nullable String url, @Nullable String hash, boolean forced) { 56 | this.uniqueId = uniqueId; 57 | this.url = url; 58 | this.hash = hash; 59 | this.forced = forced; 60 | } 61 | 62 | public ResourcePackPush(@Nullable UUID uniqueId, @Nullable String url, @Nullable String hash, boolean forced, boolean hasPromptMessage, @Nullable BaseComponent prompt) { 63 | this.uniqueId = uniqueId; 64 | this.url = url; 65 | this.hash = hash; 66 | this.forced = forced; 67 | this.hasPromptMessage = hasPromptMessage; 68 | this.prompt = prompt; 69 | } 70 | 71 | public boolean isForced() { 72 | return forced; 73 | } 74 | 75 | public boolean hasPromptMessage() { 76 | return hasPromptMessage; 77 | } 78 | 79 | @Nullable 80 | public UUID getUniqueId() { 81 | return uniqueId; 82 | } 83 | 84 | @Nullable 85 | public String getUrl() { 86 | return url; 87 | } 88 | 89 | @Nullable 90 | public String getHash() { 91 | return hash; 92 | } 93 | 94 | @Nullable 95 | public BaseComponent getPrompt() { 96 | return prompt; 97 | } 98 | 99 | @Nullable 100 | public JsonElement getJsonPrompt() { 101 | if (prompt == null) { 102 | return null; 103 | } 104 | return getJsonPrompt(ChatSerializer.forVersion(ProtocolConstants.MINECRAFT_1_16).toJson(prompt)); 105 | } 106 | 107 | @NotNull 108 | private JsonElement getJsonPrompt(@NotNull JsonElement json) { 109 | if (json.isJsonArray()) { 110 | for (JsonElement element : json.getAsJsonArray()) { 111 | getJsonPrompt(element); 112 | } 113 | } else if (json.isJsonObject()) { 114 | final JsonObject object = json.getAsJsonObject(); 115 | if (object.has("color")) { 116 | object.addProperty("color", object.get("color").getAsString().toLowerCase()); 117 | } 118 | if (object.has("extra")) { 119 | getJsonPrompt(object.get("extra")); 120 | } 121 | } 122 | 123 | return json; 124 | } 125 | 126 | @NotNull 127 | public Protocol getProtocol() { 128 | return protocol == null ? Protocol.GAME : protocol; 129 | } 130 | 131 | public void setUniqueId(@Nullable UUID uniqueId) { 132 | this.uniqueId = uniqueId; 133 | } 134 | 135 | public void setUrl(@Nullable String url) { 136 | this.url = url; 137 | } 138 | 139 | public void setHash(@Nullable String hash) { 140 | this.hash = hash; 141 | } 142 | 143 | public void setForced(boolean forced) { 144 | this.forced = forced; 145 | } 146 | 147 | public void setHasPromptMessage(boolean hasPromptMessage) { 148 | this.hasPromptMessage = hasPromptMessage; 149 | } 150 | 151 | public void setPrompt(@Nullable BaseComponent prompt) { 152 | this.hasPromptMessage = prompt != null; 153 | this.prompt = prompt; 154 | } 155 | 156 | @Override 157 | public void handle(AbstractPacketHandler handler) throws Exception { 158 | if (OneTimePack.get().getPacketHandler() instanceof BungeeProcessor processor) { 159 | processor.onPackPush(this, handler); 160 | } 161 | } 162 | 163 | @Override 164 | public void read(ByteBuf buf, Protocol protocol, ProtocolConstants.Direction direction, int protocolVersion) { 165 | this.protocol = protocol; 166 | 167 | if (protocolVersion >= ProtocolConstants.MINECRAFT_1_20_3) { 168 | uniqueId = readUUID(buf); 169 | } 170 | url = readString(buf); 171 | hash = readString(buf, MAX_HASH_LENGTH); 172 | if (protocolVersion >= ProtocolConstants.MINECRAFT_1_17) { 173 | forced = buf.readBoolean(); 174 | hasPromptMessage = buf.readBoolean(); 175 | if (hasPromptMessage) { 176 | prompt = readBaseComponent(buf, protocolVersion); 177 | } 178 | } else { 179 | forced = false; 180 | hasPromptMessage = false; 181 | } 182 | } 183 | 184 | @Override 185 | public void write(ByteBuf buf, Protocol protocol, ProtocolConstants.Direction direction, int protocolVersion) { 186 | this.protocol = protocol; 187 | 188 | if (protocolVersion >= ProtocolConstants.MINECRAFT_1_20_3) { 189 | writeUUID(uniqueId, buf); 190 | } 191 | writeString(url, buf); 192 | writeString(hash, buf); 193 | if (protocolVersion >= ProtocolConstants.MINECRAFT_1_17) { 194 | buf.writeBoolean(forced); 195 | buf.writeBoolean(hasPromptMessage); 196 | if (hasPromptMessage) { 197 | writeBaseComponent(prompt, buf, protocolVersion); 198 | } 199 | } 200 | } 201 | 202 | @Override 203 | public boolean equals(Object obj) { 204 | if (this == obj) return true; 205 | if (obj == null || getClass() != obj.getClass()) return false; 206 | 207 | ResourcePackPush that = (ResourcePackPush) obj; 208 | 209 | if (forced != that.forced) return false; 210 | if (hasPromptMessage != that.hasPromptMessage) return false; 211 | if (!Objects.equals(uniqueId, that.uniqueId)) return false; 212 | if (!Objects.equals(url, that.url)) return false; 213 | if (!Objects.equals(hash, that.hash)) return false; 214 | return Objects.equals(prompt, that.prompt); 215 | } 216 | 217 | @Override 218 | public int hashCode() { 219 | int result = uniqueId != null ? uniqueId.hashCode() : 0; 220 | result = 31 * result + (url != null ? url.hashCode() : 0); 221 | result = 31 * result + (hash != null ? hash.hashCode() : 0); 222 | result = 31 * result + (forced ? 1 : 0); 223 | result = 31 * result + (hasPromptMessage ? 1 : 0); 224 | result = 31 * result + (prompt != null ? prompt.hashCode() : 0); 225 | return result; 226 | } 227 | 228 | @Override 229 | public String toString() { 230 | return "ClientboundResourcePackPushPacket{" + 231 | (uniqueId != null ? "uniqueId='" + uniqueId + "', " : "") + 232 | "url='" + url + '\'' + 233 | ", hash='" + hash + '\'' + 234 | ", forced=" + forced + 235 | ", hasPromptMessage=" + hasPromptMessage + 236 | (hasPromptMessage ? ", promptMessage='" + prompt + '\'' : "") + 237 | '}'; 238 | } 239 | 240 | @NotNull 241 | @SuppressWarnings("deprecation") 242 | public ResourcePackStatus asStatus(@NotNull PackResult result, int protocolVersion) { 243 | if (protocolVersion >= ProtocolConstants.MINECRAFT_1_20_3) { 244 | final UUID id = uniqueId != null ? uniqueId : UUID.nameUUIDFromBytes(url.getBytes(StandardCharsets.UTF_8)); 245 | return new ResourcePackStatus(id, result); 246 | } else if (protocolVersion >= ProtocolConstants.MINECRAFT_1_10) { 247 | return new ResourcePackStatus(result); 248 | } else { 249 | return new ResourcePackStatus(hash, result); 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /module/module-bungee/src/main/java/com/saicone/onetimepack/core/packet/ResourcePackStatus.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core.packet; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import com.saicone.onetimepack.core.BungeeProcessor; 5 | import com.saicone.onetimepack.core.PackResult; 6 | import io.netty.buffer.ByteBuf; 7 | import net.md_5.bungee.protocol.AbstractPacketHandler; 8 | import net.md_5.bungee.protocol.DefinedPacket; 9 | import net.md_5.bungee.protocol.Protocol; 10 | import net.md_5.bungee.protocol.ProtocolConstants; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.Objects; 15 | import java.util.UUID; 16 | 17 | public class ResourcePackStatus extends DefinedPacket { 18 | 19 | private UUID uniqueId; // Added in 1.20.3 20 | private String hash; // Removed in 1.10 21 | // 0: successfully loaded 22 | // 1: declined 23 | // 2: failed download 24 | // 3: accepted 25 | // 4: downloaded 26 | // 5: invalid URL 27 | // 6: failed to reload 28 | // 7: discarded 29 | private PackResult result; 30 | 31 | private transient Protocol protocol; 32 | 33 | public ResourcePackStatus() { 34 | } 35 | 36 | @Deprecated(since = "1.20.3") 37 | public ResourcePackStatus(@NotNull PackResult result) { 38 | this.result = result; 39 | } 40 | 41 | public ResourcePackStatus(@Nullable UUID uniqueId, @NotNull PackResult result) { 42 | this.uniqueId = uniqueId; 43 | this.result = result; 44 | } 45 | 46 | @Deprecated(since = "1.10") 47 | public ResourcePackStatus(@Nullable String hash, @NotNull PackResult result) { 48 | this.hash = hash; 49 | this.result = result; 50 | } 51 | 52 | @Nullable 53 | public UUID getUniqueId() { 54 | return uniqueId; 55 | } 56 | 57 | @Nullable 58 | public String getHash() { 59 | return hash; 60 | } 61 | 62 | @NotNull 63 | public PackResult getResult() { 64 | return result; 65 | } 66 | 67 | public int getResultOrdinal(int protocol) { 68 | if (result.ordinal() >= 4 && protocol < ProtocolConstants.MINECRAFT_1_20_3) { 69 | return result.getFallback(); 70 | } 71 | return result.ordinal(); 72 | } 73 | 74 | @NotNull 75 | public Protocol getProtocol() { 76 | return protocol == null ? Protocol.GAME : protocol; 77 | } 78 | 79 | public void setUniqueId(@Nullable UUID uniqueId) { 80 | this.uniqueId = uniqueId; 81 | } 82 | 83 | public void setHash(@Nullable String hash) { 84 | this.hash = hash; 85 | } 86 | 87 | public void setResult(@NotNull PackResult result) { 88 | this.result = result; 89 | } 90 | 91 | @Override 92 | public void handle(AbstractPacketHandler handler) throws Exception { 93 | if (OneTimePack.get().getPacketHandler() instanceof BungeeProcessor processor) { 94 | processor.onPackStatus(this, handler); 95 | } 96 | } 97 | 98 | @Override 99 | public void read(ByteBuf buf, Protocol protocol, ProtocolConstants.Direction direction, int protocolVersion) { 100 | this.protocol = protocol; 101 | 102 | if (protocolVersion >= ProtocolConstants.MINECRAFT_1_20_3) { 103 | uniqueId = readUUID(buf); 104 | } 105 | if (protocolVersion <= ProtocolConstants.MINECRAFT_1_9_4) { 106 | hash = readString(buf); 107 | } 108 | result = PackResult.of(readVarInt(buf)); 109 | } 110 | 111 | @Override 112 | public void write(ByteBuf buf, Protocol protocol, ProtocolConstants.Direction direction, int protocolVersion) { 113 | this.protocol = protocol; 114 | 115 | if (protocolVersion >= ProtocolConstants.MINECRAFT_1_20_3) { 116 | writeUUID(uniqueId, buf); 117 | } 118 | if (protocolVersion <= ProtocolConstants.MINECRAFT_1_9_4) { 119 | writeString(hash, buf); 120 | } 121 | writeVarInt(getResultOrdinal(protocolVersion), buf); 122 | } 123 | 124 | @Override 125 | public boolean equals(Object obj) { 126 | if (this == obj) return true; 127 | if (obj == null || getClass() != obj.getClass()) return false; 128 | 129 | ResourcePackStatus that = (ResourcePackStatus) obj; 130 | 131 | if (result != that.result) return false; 132 | if (!Objects.equals(uniqueId, that.uniqueId)) return false; 133 | return Objects.equals(hash, that.hash); 134 | } 135 | 136 | @Override 137 | public int hashCode() { 138 | int result1 = uniqueId != null ? uniqueId.hashCode() : 0; 139 | result1 = 31 * result1 + (hash != null ? hash.hashCode() : 0); 140 | result1 = 31 * result1 + (result != null ? result.ordinal() : -1); 141 | return result1; 142 | } 143 | 144 | @Override 145 | public String toString() { 146 | return "ServerboundResourcePackPacket{" + 147 | (uniqueId != null ? "uniqueId='" + uniqueId + "', " : "") + 148 | (hash != null ? "hash='" + hash + "', " : "") + 149 | "result=" + (result != null ? result.ordinal() : -1) + 150 | '}'; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /module/module-mappings/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compileOnly libs.gson 3 | compileOnly libs.guava 4 | } -------------------------------------------------------------------------------- /module/module-mappings/src/main/java/com/saicone/onetimepack/module/Mappings.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.module; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonParser; 5 | import com.saicone.onetimepack.OneTimePack; 6 | import com.saicone.onetimepack.util.FileUtils; 7 | import com.saicone.onetimepack.util.ProtocolVersion; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.io.File; 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public class Mappings { 18 | 19 | private final File folder; 20 | private final String fileName; 21 | private final Supplier supplier; 22 | 23 | private Map>> loaded = new HashMap<>(); 24 | 25 | public Mappings(@NotNull File folder, @NotNull String fileName, @NotNull Supplier supplier) { 26 | this.folder = folder; 27 | this.fileName = fileName; 28 | this.supplier = supplier; 29 | } 30 | 31 | @NotNull 32 | public File getFolder() { 33 | return folder; 34 | } 35 | 36 | @NotNull 37 | public String getFileName() { 38 | return fileName; 39 | } 40 | 41 | @NotNull 42 | public Supplier getSupplier() { 43 | return supplier; 44 | } 45 | 46 | @Nullable 47 | public List getMappings(@NotNull String name, @NotNull String protocol) { 48 | if (loaded.containsKey(name)) { 49 | return loaded.get(name).get(protocol); 50 | } else { 51 | return null; 52 | } 53 | } 54 | 55 | public boolean contains(@NotNull String name) { 56 | return loaded.containsKey(name); 57 | } 58 | 59 | public void load() { 60 | // Load mappings file 61 | final JsonObject jsonFile = loadPluginFile(); 62 | if (jsonFile == null) { 63 | OneTimePack.log(1, "Build-in mappings will be used by default"); 64 | loaded = new HashMap<>(); 65 | return; 66 | } 67 | 68 | // Check external 69 | final JsonObject external = jsonFile.getAsJsonObject("external"); 70 | if (external == null || !external.get("enabled").getAsBoolean()) { 71 | OneTimePack.log(3, "Mappings from " + fileName + " file will be used"); 72 | loaded = load(jsonFile); 73 | return; 74 | } 75 | 76 | // Load mappings from url 77 | final JsonObject jsonUrl = loadUrlFile(external.get("url").getAsString()); 78 | if (jsonUrl == null) { 79 | OneTimePack.log(1, "Mappings from " + fileName + " file will be used instead"); 80 | loaded = load(jsonFile); 81 | return; 82 | } 83 | OneTimePack.log(3, "Mappings from url will be used"); 84 | loaded = load(jsonUrl); 85 | } 86 | 87 | @NotNull 88 | private Map>> load(@NotNull JsonObject json) { 89 | final Map>> loaded = new HashMap<>(); 90 | for (String s : json.keySet()) { 91 | final String key = s.trim().toLowerCase(); 92 | if (!key.startsWith("packet")) { 93 | continue; 94 | } 95 | final Map> mappings = loadMappings(json.getAsJsonObject(s)); 96 | if (mappings == null || mappings.isEmpty()) { 97 | OneTimePack.log(1, "The provided json file doesn't contains mappings on '" + s + "' configuration"); 98 | continue; 99 | } 100 | final String[] split = key.split("-", 2); 101 | final String protocol = split.length > 1 ? split[1] : "play"; 102 | for (Map.Entry> entry : mappings.entrySet()) { 103 | loaded.computeIfAbsent(entry.getKey(), __ -> new HashMap<>()).put(protocol, entry.getValue()); 104 | } 105 | } 106 | return loaded; 107 | } 108 | 109 | @Nullable 110 | private Map> loadMappings(@Nullable JsonObject packets) { 111 | if (packets == null) { 112 | return null; 113 | } 114 | final Map> mappings = new HashMap<>(); 115 | for (String name : packets.keySet()) { 116 | final JsonObject packet = packets.getAsJsonObject(name); 117 | final List list = new ArrayList<>(); 118 | for (String s : packet.keySet()) { 119 | for (String ver : s.split("\\|")) { 120 | String[] version = ver.split("-"); 121 | int start = ProtocolVersion.getProtocol(version.length >= 1 ? version[0] : s); 122 | int end = version.length >= 2 ? ProtocolVersion.getProtocol(version[1]) : start; 123 | if (start < 0 || end < 0) { 124 | OneTimePack.log(1, "The parameter '" + ver + "' inside '" + s + "' is not a valid version range for " + name + " packet, so will be ignored"); 125 | continue; 126 | } 127 | 128 | int id = packet.get(s).getAsInt(); 129 | list.add(supplier.get(start, end, id)); 130 | OneTimePack.log(3, "Added ranged mapping for " + name + ": " + start + ',' + end + ',' + id); 131 | } 132 | } 133 | if (list.isEmpty()) { 134 | OneTimePack.log(2, "The packet '" + name + "' has empty mappings"); 135 | } else { 136 | OneTimePack.log(3, "Loaded " + list.size() + " mappings for " + name + " packet"); 137 | } 138 | mappings.put(name, list); 139 | } 140 | if (mappings.isEmpty()) { 141 | OneTimePack.log(2, "The provided json file doesn't have any mapping"); 142 | } 143 | return mappings; 144 | } 145 | 146 | @Nullable 147 | private JsonObject loadPluginFile() { 148 | final File file = FileUtils.saveResource(folder, fileName, false); 149 | if (file != null) { 150 | final String lines = FileUtils.readFromFile(file); 151 | if (lines != null) { 152 | if (!lines.trim().isEmpty()) { 153 | return JsonParser.parseString(lines).getAsJsonObject(); 154 | } else { 155 | OneTimePack.log(1, "The file " + fileName + " is empty"); 156 | } 157 | } else { 158 | OneTimePack.log(1, "Cannot read " + fileName + " file"); 159 | } 160 | } else { 161 | OneTimePack.log(1, "Cannot load " + fileName + " file from plugin JAR"); 162 | } 163 | return null; 164 | } 165 | 166 | @Nullable 167 | private JsonObject loadUrlFile(@Nullable String url) { 168 | if (url != null) { 169 | if (!url.trim().isEmpty()) { 170 | final String lines = FileUtils.readFromUrl(url); 171 | if (lines != null) { 172 | if (!lines.trim().isEmpty()) { 173 | return JsonParser.parseString(lines).getAsJsonObject(); 174 | } else { 175 | OneTimePack.log(1, "The url data is empty"); 176 | } 177 | } else { 178 | OneTimePack.log(1, "Cannot retrieve data from mappings url"); 179 | } 180 | } else { 181 | OneTimePack.log(1, "The provided URL cannot be empty"); 182 | } 183 | } else { 184 | OneTimePack.log(1, "The file " + fileName + " doesn't have any configured URL"); 185 | } 186 | return null; 187 | } 188 | 189 | @FunctionalInterface 190 | public interface Supplier { 191 | 192 | @NotNull 193 | T get(int start, int end, int id); 194 | } 195 | } -------------------------------------------------------------------------------- /module/module-mappings/src/main/java/com/saicone/onetimepack/util/ProtocolVersion.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.util; 2 | 3 | import com.google.common.base.Suppliers; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.lang.reflect.Field; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.function.Supplier; 13 | 14 | public class ProtocolVersion { 15 | 16 | private static final List> MAGIC_CLASSES = new ArrayList<>(); 17 | 18 | static { 19 | try { 20 | MAGIC_CLASSES.add(Class.forName("dev.simplix.protocolize.api.util.ProtocolVersions")); 21 | } catch (Throwable ignored) { } 22 | try { 23 | MAGIC_CLASSES.add(Class.forName("net.md_5.bungee.protocol.ProtocolConstants")); 24 | } catch (Throwable ignored) { } 25 | } 26 | 27 | private static final Supplier> protocols = Suppliers.memoize(() -> { 28 | final Map map = new HashMap<>(); 29 | for (Class> clazz : MAGIC_CLASSES) { 30 | for (Field field : clazz.getDeclaredFields()) { 31 | final String name = field.getName().toUpperCase(); 32 | if (name.startsWith("MINECRAFT_")) { 33 | try { 34 | map.put(name.substring(10).replace('_', '.').toLowerCase(), (int) field.get(null)); 35 | } catch (IllegalAccessException e) { 36 | e.printStackTrace(); 37 | } 38 | } 39 | } 40 | } 41 | return map; 42 | }); 43 | 44 | public static int getProtocol(@Nullable Object object) { 45 | if (object instanceof Integer) { 46 | return (int) object; 47 | } else if (object instanceof Number) { 48 | try { 49 | return Integer.parseInt(String.valueOf(object)); 50 | } catch (NumberFormatException ignored) { } 51 | } else if (object instanceof String) { 52 | final String s = ((String) object).trim().toLowerCase(); 53 | if (s.indexOf('.') > 0) { 54 | return getProtocol(s, -1); 55 | } 56 | try { 57 | return Integer.parseInt(s); 58 | } catch (NumberFormatException e) { 59 | return getProtocol(s, -1); 60 | } 61 | } 62 | return -1; 63 | } 64 | 65 | public static int getProtocol(@NotNull String s, int def) { 66 | return getProtocols().getOrDefault(s, def); 67 | } 68 | 69 | @NotNull 70 | public static Map getProtocols() { 71 | return protocols.get(); 72 | } 73 | } -------------------------------------------------------------------------------- /module/module-packetevents/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url 'https://repo.codemc.io/repository/maven-releases/' } 3 | } 4 | 5 | dependencies { 6 | compileOnly libs.packetevents.api 7 | compileOnly libs.adventure.api 8 | } -------------------------------------------------------------------------------- /module/module-packetevents/src/main/java/com/saicone/onetimepack/core/PacketEventsProcessor.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import com.github.retrooper.packetevents.PacketEvents; 4 | import com.github.retrooper.packetevents.event.PacketListener; 5 | import com.github.retrooper.packetevents.event.PacketListenerPriority; 6 | import com.github.retrooper.packetevents.event.PacketReceiveEvent; 7 | import com.github.retrooper.packetevents.event.PacketSendEvent; 8 | import com.github.retrooper.packetevents.protocol.ConnectionState; 9 | import com.github.retrooper.packetevents.protocol.packettype.PacketType; 10 | import com.github.retrooper.packetevents.protocol.player.ClientVersion; 11 | import com.github.retrooper.packetevents.protocol.player.User; 12 | import com.saicone.onetimepack.OneTimePack; 13 | import com.saicone.onetimepack.core.packet.ResourcePackPop; 14 | import com.saicone.onetimepack.core.packet.ResourcePackPush; 15 | import com.saicone.onetimepack.core.packet.ResourcePackStatus; 16 | import com.saicone.onetimepack.util.ValueComparator; 17 | import org.jetbrains.annotations.NotNull; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | import java.util.Map; 21 | import java.util.Optional; 22 | import java.util.UUID; 23 | 24 | public class PacketEventsProcessor extends Processor implements PacketListener { 25 | 26 | @Override 27 | public void onEnable() { 28 | // IDK why this is enabled by default 29 | PacketEvents.getAPI().getSettings().debug(false); 30 | PacketEvents.getAPI().getEventManager().registerListener(this, PacketListenerPriority.LOWEST); 31 | } 32 | 33 | @Override 34 | public void onPacketReceive(PacketReceiveEvent event) { 35 | if (event.getPacketType() == PacketType.Play.Client.RESOURCE_PACK_STATUS || event.getPacketType() == PacketType.Configuration.Client.RESOURCE_PACK_STATUS) { 36 | final ResourcePackStatus packet = new ResourcePackStatus(event); 37 | onPackStatus(event.getUser(), packet.getUniqueId(), packet.getResult()); 38 | } 39 | } 40 | 41 | @Override 42 | public void onPacketSend(PacketSendEvent event) { 43 | if (event.getPacketType() == PacketType.Play.Server.CONFIGURATION_START) { 44 | if (!isSendCached1_20_2() || event.getUser().getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_3)) { 45 | return; 46 | } 47 | final UUID uuid = event.getUser().getUUID(); 48 | if (getUsers().containsKey(uuid)) { 49 | OneTimePack.log(4, "The cached pack will be send for player due it's on configuration state"); 50 | event.getPostTasks().add(() -> { 51 | if (event.isCancelled()) return; 52 | for (Map.Entry entry : getUsers().get(uuid).getPacks().entrySet()) { 53 | final ResourcePackPush packet = entry.getValue().as(event.getUser().getConnectionState()); 54 | packet.setServerVersion(event.getUser().getClientVersion().toServerVersion()); 55 | event.getUser().sendPacket(packet); 56 | } 57 | OneTimePack.log(4, "Sent!"); 58 | }); 59 | } 60 | } else if (event.getPacketType() == PacketType.Configuration.Server.RESOURCE_PACK_SEND) { 61 | onPackPush(event, ConnectionState.CONFIGURATION); 62 | } else if (event.getPacketType() == PacketType.Play.Server.RESOURCE_PACK_SEND) { 63 | onPackPush(event, ConnectionState.PLAY); 64 | } else if (event.getPacketType() == PacketType.Configuration.Server.RESOURCE_PACK_REMOVE) { 65 | final ResourcePackPop packet = new ResourcePackPop(event); 66 | event.setCancelled(onPackPop(event.getUser(), ConnectionState.CONFIGURATION, packet, packet.getUniqueId())); 67 | } else if (event.getPacketType() == PacketType.Play.Server.RESOURCE_PACK_REMOVE) { 68 | final ResourcePackPop packet = new ResourcePackPop(event); 69 | event.setCancelled(onPackPop(event.getUser(), ConnectionState.PLAY, packet, packet.getUniqueId())); 70 | } 71 | } 72 | 73 | protected void onPackPush(@NotNull PacketSendEvent event, @NotNull ConnectionState state) { 74 | final ResourcePackPush packet = new ResourcePackPush(event); 75 | final Optional optional = onPackPush(event.getUser(), state, packet, packet.getUniqueId(), packet.getHash()); 76 | if (optional == null) return; 77 | 78 | event.setCancelled(true); 79 | 80 | final PackResult result = optional.orElse(null); 81 | if (result == null) return; 82 | 83 | final ResourcePackStatus cached = event.getUser().getClientVersion().isOlderThan(ClientVersion.V_1_20_3) 84 | ? new ResourcePackStatus(packet.getHash(), result) 85 | : new ResourcePackStatus(packet.getState(), packet.getUniqueId(), result); 86 | cached.setServerVersion(packet.getServerVersion()); 87 | PacketEvents.getAPI().getProtocolManager().receivePacket(event.getUser().getChannel(), cached); 88 | OneTimePack.log(4, () -> "Sent cached result " + cached + " from user " + event.getUser().getUUID()); 89 | } 90 | 91 | @Override 92 | protected @NotNull UUID getUserId(@NotNull User user) { 93 | return user.getUUID(); 94 | } 95 | 96 | @Override 97 | protected @Nullable ValueComparator getPackValue(@NotNull String name) { 98 | return switch (name) { 99 | case "UUID" -> ResourcePackPush::getUniqueId; 100 | case "URL" -> ResourcePackPush::getUrl; 101 | case "HASH" -> ResourcePackPush::getHash; 102 | case "PROMPT" -> ResourcePackPush::getPrompt; 103 | case "ALL" -> pack -> pack; 104 | case "ANY" -> pack -> true; 105 | default -> null; 106 | }; 107 | } 108 | 109 | @Override 110 | public void clearPackets(@NotNull User user, @NotNull ConnectionState state) { 111 | user.sendPacket(new ResourcePackPop(state, false, null)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /module/module-packetevents/src/main/java/com/saicone/onetimepack/core/packet/CommonPacketWrapper.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core.packet; 2 | 3 | import com.github.retrooper.packetevents.protocol.ConnectionState; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface CommonPacketWrapper> { 7 | 8 | @NotNull 9 | ConnectionState getState(); 10 | 11 | @NotNull 12 | T copy(); 13 | 14 | @NotNull 15 | @SuppressWarnings("unchecked") 16 | default T as(@NotNull ConnectionState state) { 17 | if (getState() == state) { 18 | return (T) this; 19 | } else { 20 | return copy(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /module/module-packetevents/src/main/java/com/saicone/onetimepack/core/packet/ResourcePackPop.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core.packet; 2 | 3 | import com.github.retrooper.packetevents.event.PacketSendEvent; 4 | import com.github.retrooper.packetevents.protocol.ConnectionState; 5 | import com.github.retrooper.packetevents.protocol.packettype.PacketType; 6 | import com.github.retrooper.packetevents.wrapper.PacketWrapper; 7 | import com.saicone.onetimepack.OneTimePack; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.Objects; 12 | import java.util.UUID; 13 | 14 | public class ResourcePackPop extends PacketWrapper implements CommonPacketWrapper { 15 | 16 | private final ConnectionState state; 17 | 18 | private boolean hasUniqueId; 19 | private UUID uniqueId; 20 | 21 | public ResourcePackPop(@NotNull PacketSendEvent event) { 22 | super(event, false); 23 | this.state = event.getPacketType() == PacketType.Configuration.Server.RESOURCE_PACK_REMOVE ? ConnectionState.CONFIGURATION : ConnectionState.PLAY; 24 | readEvent(event); 25 | } 26 | 27 | public ResourcePackPop(@NotNull ConnectionState state, boolean hasUniqueId, @Nullable UUID uniqueId) { 28 | super(state == ConnectionState.CONFIGURATION ? PacketType.Configuration.Server.RESOURCE_PACK_REMOVE : PacketType.Play.Server.RESOURCE_PACK_REMOVE); 29 | this.state = state; 30 | this.hasUniqueId = hasUniqueId; 31 | this.uniqueId = uniqueId; 32 | } 33 | 34 | @NotNull 35 | @Override 36 | public ConnectionState getState() { 37 | return state; 38 | } 39 | 40 | @Nullable 41 | public UUID getUniqueId() { 42 | return uniqueId; 43 | } 44 | 45 | public boolean hasUniqueId() { 46 | return hasUniqueId; 47 | } 48 | 49 | public void setHasUniqueId(boolean hasUniqueId) { 50 | this.hasUniqueId = hasUniqueId; 51 | } 52 | 53 | public void setUniqueId(@Nullable UUID uniqueId) { 54 | this.uniqueId = uniqueId; 55 | this.hasUniqueId = uniqueId != null; 56 | } 57 | 58 | @Override 59 | public void read() { 60 | hasUniqueId = readBoolean(); 61 | if (hasUniqueId) { 62 | uniqueId = readUUID(); 63 | } 64 | OneTimePack.log(4, () -> "[" + getState().name() + "] Packet#read() = " + this); 65 | } 66 | 67 | @Override 68 | public void write() { 69 | writeBoolean(hasUniqueId); 70 | if (hasUniqueId) { 71 | writeUUID(uniqueId); 72 | } 73 | } 74 | 75 | @Override 76 | public @NotNull ResourcePackPop copy() { 77 | return new ResourcePackPop(state, hasUniqueId, uniqueId); 78 | } 79 | 80 | @Override 81 | public void copy(ResourcePackPop wrapper) { 82 | hasUniqueId = wrapper.hasUniqueId; 83 | uniqueId = wrapper.uniqueId; 84 | } 85 | 86 | @Override 87 | public boolean equals(Object o) { 88 | if (this == o) return true; 89 | if (o == null || getClass() != o.getClass()) return false; 90 | 91 | ResourcePackPop that = (ResourcePackPop) o; 92 | 93 | if (hasUniqueId != that.hasUniqueId) return false; 94 | return Objects.equals(uniqueId, that.uniqueId); 95 | } 96 | 97 | @Override 98 | public int hashCode() { 99 | int result = (hasUniqueId ? 1 : 0); 100 | result = 31 * result + (uniqueId != null ? uniqueId.hashCode() : 0); 101 | return result; 102 | } 103 | 104 | @Override 105 | public String toString() { 106 | return "ClientboundResourcePackPop{" + 107 | "hasUniqueId=" + hasUniqueId + 108 | (hasUniqueId ? ", uniqueId=" + uniqueId : "") + 109 | '}'; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /module/module-packetevents/src/main/java/com/saicone/onetimepack/core/packet/ResourcePackPush.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core.packet; 2 | 3 | import com.github.retrooper.packetevents.event.PacketSendEvent; 4 | import com.github.retrooper.packetevents.manager.server.ServerVersion; 5 | import com.github.retrooper.packetevents.protocol.ConnectionState; 6 | import com.github.retrooper.packetevents.protocol.packettype.PacketType; 7 | import com.github.retrooper.packetevents.util.adventure.AdventureSerializer; 8 | import com.github.retrooper.packetevents.wrapper.PacketWrapper; 9 | import com.saicone.onetimepack.OneTimePack; 10 | import net.kyori.adventure.text.Component; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.Objects; 15 | import java.util.UUID; 16 | 17 | public class ResourcePackPush extends PacketWrapper implements CommonPacketWrapper { 18 | 19 | public static final int MAX_HASH_LENGTH = 40; 20 | 21 | private final ConnectionState state; 22 | 23 | private UUID uniqueId; // Added in 1.20.3 24 | private String url; 25 | private String hash; 26 | 27 | // Added in 1.17 28 | private boolean forced; 29 | private boolean hasPromptMessage; 30 | private Component prompt; 31 | 32 | public ResourcePackPush(@NotNull PacketSendEvent event) { 33 | super(event, false); 34 | this.state = event.getPacketType() == PacketType.Configuration.Server.RESOURCE_PACK_SEND ? ConnectionState.CONFIGURATION : ConnectionState.PLAY; 35 | readEvent(event); 36 | } 37 | 38 | public ResourcePackPush(@Nullable String url, @Nullable String hash) { 39 | this(ConnectionState.PLAY, null, url, hash, false, false, null); 40 | } 41 | 42 | public ResourcePackPush(@Nullable String url, @Nullable String hash, boolean forced, boolean hasPromptMessage, @Nullable Component prompt) { 43 | this(ConnectionState.PLAY, null, url, hash, forced, hasPromptMessage, prompt); 44 | } 45 | 46 | public ResourcePackPush(@NotNull ConnectionState state, @Nullable UUID uniqueId, @Nullable String url, @Nullable String hash, boolean forced, boolean hasPromptMessage, @Nullable Component prompt) { 47 | super(state == ConnectionState.CONFIGURATION ? PacketType.Configuration.Server.RESOURCE_PACK_SEND : PacketType.Play.Server.RESOURCE_PACK_SEND); 48 | this.state = state; 49 | this.uniqueId = uniqueId; 50 | this.url = url; 51 | this.hash = hash; 52 | this.forced = forced; 53 | this.hasPromptMessage = hasPromptMessage; 54 | this.prompt = prompt; 55 | } 56 | 57 | @NotNull 58 | @Override 59 | public ConnectionState getState() { 60 | return state; 61 | } 62 | 63 | @Nullable 64 | public UUID getUniqueId() { 65 | return uniqueId; 66 | } 67 | 68 | @Nullable 69 | public String getUrl() { 70 | return url; 71 | } 72 | 73 | @Nullable 74 | public String getHash() { 75 | return hash; 76 | } 77 | 78 | @Nullable 79 | public Component getPrompt() { 80 | return prompt; 81 | } 82 | 83 | public boolean isForced() { 84 | return forced; 85 | } 86 | 87 | public boolean hasPromptMessage() { 88 | return hasPromptMessage; 89 | } 90 | 91 | public void setUniqueId(@Nullable UUID uniqueId) { 92 | this.uniqueId = uniqueId; 93 | } 94 | 95 | public void setUrl(@Nullable String url) { 96 | this.url = url; 97 | } 98 | 99 | public void setHash(@Nullable String hash) { 100 | this.hash = hash; 101 | } 102 | 103 | public void setForced(boolean forced) { 104 | this.forced = forced; 105 | } 106 | 107 | public void setHasPromptMessage(boolean hasPromptMessage) { 108 | this.hasPromptMessage = hasPromptMessage; 109 | } 110 | 111 | public void setPrompt(@Nullable Component prompt) { 112 | this.prompt = prompt; 113 | this.hasPromptMessage = prompt != null; 114 | } 115 | 116 | @Override 117 | public void read() { 118 | if (getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_20_3)) { 119 | uniqueId = readUUID(); 120 | } 121 | 122 | url = readString(); 123 | hash = readString(MAX_HASH_LENGTH); 124 | if (getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_17)) { 125 | forced = readBoolean(); 126 | hasPromptMessage = readBoolean(); 127 | if (hasPromptMessage) { 128 | prompt = readComponent(); 129 | } 130 | } 131 | 132 | OneTimePack.log(4, () -> "[" + getState().name() + "] Packet#read() = " + this); 133 | } 134 | 135 | @Override 136 | public void write() { 137 | if (getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_20_3)) { 138 | writeUUID(uniqueId); 139 | } 140 | 141 | writeString(url); 142 | writeString(hash, MAX_HASH_LENGTH); 143 | if (getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_17)) { 144 | writeBoolean(forced); 145 | writeBoolean(hasPromptMessage); 146 | if (hasPromptMessage) { 147 | writeComponent(prompt); 148 | } 149 | } 150 | } 151 | 152 | @Override 153 | public @NotNull ResourcePackPush copy() { 154 | return new ResourcePackPush(state, uniqueId, url, hash, forced, hasPromptMessage, prompt); 155 | } 156 | 157 | @Override 158 | public void copy(ResourcePackPush wrapper) { 159 | uniqueId = wrapper.uniqueId; 160 | url = wrapper.url; 161 | hash = wrapper.hash; 162 | forced = wrapper.forced; 163 | hasPromptMessage = wrapper.hasPromptMessage; 164 | prompt = wrapper.prompt; 165 | } 166 | 167 | @Override 168 | public boolean equals(Object o) { 169 | if (this == o) return true; 170 | if (o == null || getClass() != o.getClass()) return false; 171 | 172 | ResourcePackPush that = (ResourcePackPush) o; 173 | 174 | if (forced != that.forced) return false; 175 | if (hasPromptMessage != that.hasPromptMessage) return false; 176 | if (!Objects.equals(uniqueId, that.uniqueId)) return false; 177 | if (!Objects.equals(url, that.url)) return false; 178 | if (!Objects.equals(hash, that.hash)) return false; 179 | return Objects.equals(prompt, that.prompt); 180 | } 181 | 182 | @Override 183 | public int hashCode() { 184 | int result = uniqueId != null ? uniqueId.hashCode() : 0; 185 | result = 31 * result + (url != null ? url.hashCode() : 0); 186 | result = 31 * result + (hash != null ? hash.hashCode() : 0); 187 | result = 31 * result + (forced ? 1 : 0); 188 | result = 31 * result + (hasPromptMessage ? 1 : 0); 189 | result = 31 * result + (prompt != null ? prompt.hashCode() : 0); 190 | return result; 191 | } 192 | 193 | @Override 194 | public String toString() { 195 | return "ClientboundResourcePackPush{" + 196 | (uniqueId != null ? "uniqueId='" + uniqueId + "', " : "") + 197 | "url='" + url + '\'' + 198 | ", hash='" + hash + '\'' + 199 | ", forced=" + forced + 200 | ", hasPromptMessage=" + hasPromptMessage + 201 | (hasPromptMessage ? ", promptMessage='" + AdventureSerializer.toJson(prompt) + '\'' : "") + 202 | '}'; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /module/module-packetevents/src/main/java/com/saicone/onetimepack/core/packet/ResourcePackStatus.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core.packet; 2 | 3 | import com.github.retrooper.packetevents.event.PacketReceiveEvent; 4 | import com.github.retrooper.packetevents.manager.server.ServerVersion; 5 | import com.github.retrooper.packetevents.protocol.ConnectionState; 6 | import com.github.retrooper.packetevents.protocol.packettype.PacketType; 7 | import com.github.retrooper.packetevents.wrapper.PacketWrapper; 8 | import com.saicone.onetimepack.OneTimePack; 9 | import com.saicone.onetimepack.core.PackResult; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.util.Objects; 14 | import java.util.UUID; 15 | 16 | public class ResourcePackStatus extends PacketWrapper implements CommonPacketWrapper { 17 | 18 | private final ConnectionState state; 19 | 20 | private UUID uniqueId; // Added in 1.20.3 21 | private String hash; // Removed in 1.10 22 | // 0: successfully loaded 23 | // 1: declined 24 | // 2: failed download 25 | // 3: accepted 26 | // 4: downloaded 27 | // 5: invalid URL 28 | // 6: failed to reload 29 | // 7: discarded 30 | private PackResult result; 31 | 32 | public ResourcePackStatus(@NotNull PacketReceiveEvent event) { 33 | super(event, false); 34 | this.state = event.getPacketType() == PacketType.Configuration.Client.RESOURCE_PACK_STATUS ? ConnectionState.CONFIGURATION : ConnectionState.PLAY; 35 | readEvent(event); 36 | } 37 | 38 | public ResourcePackStatus(@NotNull PackResult result) { 39 | this(ConnectionState.PLAY, null, null, result); 40 | } 41 | 42 | public ResourcePackStatus(@Nullable String hash, @NotNull PackResult result) { 43 | this(ConnectionState.PLAY, null, hash, result); 44 | } 45 | 46 | public ResourcePackStatus(@NotNull ConnectionState state, @Nullable UUID uniqueId, @NotNull PackResult result) { 47 | this(state, uniqueId, null, result); 48 | } 49 | 50 | ResourcePackStatus(@NotNull ConnectionState state, @Nullable UUID uniqueId, @Nullable String hash, @NotNull PackResult result) { 51 | super(state == ConnectionState.CONFIGURATION ? PacketType.Configuration.Client.RESOURCE_PACK_STATUS : PacketType.Play.Client.RESOURCE_PACK_STATUS); 52 | this.state = state; 53 | this.uniqueId = uniqueId; 54 | this.hash = hash; 55 | this.result = result; 56 | } 57 | 58 | @NotNull 59 | @Override 60 | public ConnectionState getState() { 61 | return state; 62 | } 63 | 64 | @Nullable 65 | public UUID getUniqueId() { 66 | return uniqueId; 67 | } 68 | 69 | @Nullable 70 | public String getHash() { 71 | return hash; 72 | } 73 | 74 | @NotNull 75 | public PackResult getResult() { 76 | return result; 77 | } 78 | 79 | public int getResultOrdinal() { 80 | if (result.ordinal() >= 4 && serverVersion.isOlderThan(ServerVersion.V_1_20_3)) { 81 | return result.getFallback(); 82 | } 83 | return result.ordinal(); 84 | } 85 | 86 | public void setUniqueId(@Nullable UUID uniqueId) { 87 | this.uniqueId = uniqueId; 88 | } 89 | 90 | public void setHash(@Nullable String hash) { 91 | this.hash = hash; 92 | } 93 | 94 | public void setResult(@NotNull PackResult result) { 95 | this.result = result; 96 | } 97 | 98 | @Override 99 | public void read() { 100 | if (getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_20_3)) { 101 | uniqueId = readUUID(); 102 | } 103 | if (getServerVersion().isOlderThan(ServerVersion.V_1_10)) { 104 | hash = readString(ResourcePackPush.MAX_HASH_LENGTH); 105 | } 106 | result = PackResult.of(readVarInt()); 107 | OneTimePack.log(4, () -> "[" + getState().name() + "] Packet#read() = " + this); 108 | } 109 | 110 | @Override 111 | public void write() { 112 | if (getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_20_3)) { 113 | writeUUID(uniqueId); 114 | } 115 | if (getServerVersion().isOlderThan(ServerVersion.V_1_10)) { 116 | writeString(hash, ResourcePackPush.MAX_HASH_LENGTH); 117 | } 118 | writeVarInt(getResultOrdinal()); 119 | } 120 | 121 | @Override 122 | public @NotNull ResourcePackStatus copy() { 123 | return new ResourcePackStatus(state, uniqueId, hash, result); 124 | } 125 | 126 | @Override 127 | public void copy(ResourcePackStatus wrapper) { 128 | uniqueId = wrapper.uniqueId; 129 | hash = wrapper.hash; 130 | result = wrapper.result; 131 | } 132 | 133 | @Override 134 | public boolean equals(Object o) { 135 | if (this == o) return true; 136 | if (o == null || getClass() != o.getClass()) return false; 137 | 138 | ResourcePackStatus that = (ResourcePackStatus) o; 139 | 140 | if (result != that.result) return false; 141 | if (!Objects.equals(uniqueId, that.uniqueId)) return false; 142 | return Objects.equals(hash, that.hash); 143 | } 144 | 145 | @Override 146 | public int hashCode() { 147 | int result1 = uniqueId != null ? uniqueId.hashCode() : 0; 148 | result1 = 31 * result1 + (hash != null ? hash.hashCode() : 0); 149 | result1 = 31 * result1 + (result != null ? result.ordinal() : -1); 150 | return result1; 151 | } 152 | 153 | @Override 154 | public String toString() { 155 | return "ServerboundResourcePack{" + 156 | (uniqueId != null ? "uniqueId='" + uniqueId + "', " : "") + 157 | (hash != null ? "hash='" + hash + "', " : "") + 158 | "result=" + (result != null ? result.ordinal() : -1) + 159 | '}'; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /module/module-protocolize/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url 'https://mvn.exceptionflug.de/repository/exceptionflug-public/' } 3 | } 4 | 5 | dependencies { 6 | implementation project(':module:module-mappings') 7 | compileOnly libs.protocolize.api 8 | compileOnly libs.netty.buffer 9 | compileOnly libs.gson 10 | compileOnly libs.guava 11 | } -------------------------------------------------------------------------------- /module/module-protocolize/src/main/java/com/saicone/onetimepack/core/MappedProtocolizeProcessor.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import com.saicone.onetimepack.core.packet.ResourcePackPop; 5 | import com.saicone.onetimepack.core.packet.ResourcePackPush; 6 | import com.saicone.onetimepack.core.packet.ResourcePackStatus; 7 | import com.saicone.onetimepack.module.Mappings; 8 | import com.saicone.onetimepack.util.ValueComparator; 9 | import dev.simplix.protocolize.api.Direction; 10 | import dev.simplix.protocolize.api.PacketDirection; 11 | import dev.simplix.protocolize.api.Protocol; 12 | import dev.simplix.protocolize.api.mapping.AbstractProtocolMapping; 13 | import dev.simplix.protocolize.api.mapping.ProtocolIdMapping; 14 | import dev.simplix.protocolize.api.packet.AbstractPacket; 15 | import dev.simplix.protocolize.api.player.ProtocolizePlayer; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import java.util.List; 20 | import java.util.function.Consumer; 21 | import java.util.function.Function; 22 | 23 | public class MappedProtocolizeProcessor extends ProtocolizeProcessor { 24 | 25 | public MappedProtocolizeProcessor(@NotNull Class startConfigurationClass) { 26 | super(startConfigurationClass); 27 | } 28 | 29 | @Override 30 | public void onLoad() { 31 | final Mappings mappings = new Mappings<>(OneTimePack.get().getProvider().getPluginFolder(), "mappings.json", AbstractProtocolMapping::rangedIdMapping); 32 | mappings.load(); 33 | register(mappings, "ResourcePackSend", ResourcePackPush::register); 34 | register(mappings, "ResourcePackRemove", ResourcePackPop::register); 35 | register(mappings, "ResourcePackStatus", ResourcePackStatus::register); 36 | } 37 | 38 | @Override 39 | protected void registerListeners() { 40 | getPacketListener().registerReceive(ResourcePackPush.Configuration.class, Direction.DOWNSTREAM, event -> { 41 | final ResourcePackPush packet = event.packet(); 42 | onPackPush(event, Protocol.CONFIGURATION, packet.getUniqueId(), packet.getHash()); 43 | }); 44 | getPacketListener().registerReceive(ResourcePackPush.Play.class, Direction.DOWNSTREAM, event -> { 45 | final ResourcePackPush packet = event.packet(); 46 | onPackPush(event, Protocol.PLAY, packet.getUniqueId(), packet.getHash()); 47 | }); 48 | getPacketListener().registerReceive(ResourcePackPop.Configuration.class, Direction.DOWNSTREAM, event -> { 49 | final ResourcePackPop packet = event.packet(); 50 | event.cancelled(onPackPop(event.player(), Protocol.CONFIGURATION, packet, packet.getUniqueId())); 51 | }); 52 | getPacketListener().registerReceive(ResourcePackPop.Play.class, Direction.DOWNSTREAM, event -> { 53 | final ResourcePackPop packet = event.packet(); 54 | event.cancelled(onPackPop(event.player(), Protocol.PLAY, packet, packet.getUniqueId())); 55 | }); 56 | getPacketListener().registerReceive(ResourcePackStatus.Configuration.class, Direction.UPSTREAM, event -> { 57 | final ResourcePackStatus packet = event.packet(); 58 | onPackStatus(event.player(), packet.getUniqueId(), packet.getResult()); 59 | }); 60 | getPacketListener().registerReceive(ResourcePackStatus.Play.class, Direction.UPSTREAM, event -> { 61 | final ResourcePackStatus packet = event.packet(); 62 | onPackStatus(event.player(), packet.getUniqueId(), packet.getResult()); 63 | }); 64 | } 65 | 66 | @Override 67 | protected @Nullable ValueComparator getPackValue(@NotNull String name) { 68 | return switch (name) { 69 | case "UUID" -> ResourcePackPush::getUniqueId; 70 | case "URL" -> ResourcePackPush::getUrl; 71 | case "HASH" -> ResourcePackPush::getHash; 72 | case "PROMPT" -> ResourcePackPush::getPrompt; 73 | case "ALL" -> pack -> pack; 74 | case "ANY" -> pack -> true; 75 | default -> null; 76 | }; 77 | } 78 | 79 | @Override 80 | protected @NotNull ResourcePackPush getPushPacket(@NotNull ResourcePackPush packet) { 81 | return packet.asConfiguration(); 82 | } 83 | 84 | @Override 85 | protected @NotNull ResourcePackStatus getStatusPacket(@NotNull Protocol protocol, @NotNull ResourcePackPush packet, @NotNull PackResult result) { 86 | if (protocol == Protocol.CONFIGURATION) { 87 | return new ResourcePackStatus.Configuration(packet.getUniqueId(), result); 88 | } else { 89 | if (packet.getUniqueId() == null) { 90 | return new ResourcePackStatus.Play(packet.getHash(), result); 91 | } else { 92 | return new ResourcePackStatus.Play(packet.getUniqueId(), result); 93 | } 94 | } 95 | } 96 | 97 | private void register(@NotNull Mappings mappings, @NotNull String name, @NotNull Consumer>> consumer) { 98 | if (!mappings.contains(name)) { 99 | consumer.accept(null); 100 | } else { 101 | consumer.accept(protocol -> mappings.getMappings(name, protocol)); 102 | } 103 | } 104 | 105 | @Override 106 | public void clearPackets(@NotNull ProtocolizePlayer player, @NotNull Protocol protocol) { 107 | final ResourcePackPop clearPacket = protocol == Protocol.CONFIGURATION ? new ResourcePackPop.Configuration() : new ResourcePackPop.Play(); 108 | if (protocol == Protocol.CONFIGURATION) { 109 | player.sendPacket(getWrappedPacket( 110 | clearPacket, 111 | Protocol.CONFIGURATION, 112 | PacketDirection.CLIENTBOUND, 113 | player.protocolVersion() 114 | )); 115 | } else { 116 | player.sendPacket(clearPacket); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /module/module-protocolize/src/main/java/com/saicone/onetimepack/core/ProtocolizeProcessor.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import com.saicone.onetimepack.module.listener.PacketListener; 5 | import dev.simplix.protocolize.api.Direction; 6 | import dev.simplix.protocolize.api.PacketDirection; 7 | import dev.simplix.protocolize.api.Platform; 8 | import dev.simplix.protocolize.api.Protocol; 9 | import dev.simplix.protocolize.api.Protocolize; 10 | import dev.simplix.protocolize.api.listener.PacketReceiveEvent; 11 | import dev.simplix.protocolize.api.packet.AbstractPacket; 12 | import dev.simplix.protocolize.api.player.ProtocolizePlayer; 13 | import dev.simplix.protocolize.api.util.ProtocolVersions; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import java.lang.invoke.MethodHandle; 18 | import java.lang.invoke.MethodHandles; 19 | import java.util.Map; 20 | import java.util.Optional; 21 | import java.util.UUID; 22 | 23 | public abstract class ProtocolizeProcessor extends Processor { 24 | 25 | private static final MethodHandle WRAPPER; 26 | 27 | static { 28 | final String name; 29 | if (Protocolize.platform() == Platform.BUNGEECORD) { 30 | name = "dev.simplix.protocolize.bungee.packet.BungeeCordProtocolizePacket"; 31 | } else { 32 | name = "dev.simplix.protocolize.velocity.packet.VelocityProtocolizePacket"; 33 | } 34 | MethodHandle wrapper = null; 35 | try { 36 | final Class> packetClass = Class.forName(name); 37 | wrapper = MethodHandles.lookup().unreflect(packetClass.getDeclaredMethod("wrapper", AbstractPacket.class)); 38 | } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) { 39 | e.printStackTrace(); 40 | } 41 | WRAPPER = wrapper; 42 | } 43 | 44 | private final Class startConfigurationClass; 45 | 46 | private PacketListener packetListener; 47 | 48 | public ProtocolizeProcessor(@NotNull Class startConfigurationClass) { 49 | this.startConfigurationClass = startConfigurationClass; 50 | } 51 | 52 | @Override 53 | public void onEnable() { 54 | packetListener = new PacketListener(); 55 | packetListener.registerSend(startConfigurationClass, Direction.UPSTREAM, event -> { 56 | if (!isSendCached1_20_2() || event.player().protocolVersion() >= ProtocolVersions.MINECRAFT_1_20_3) { 57 | return; 58 | } 59 | final UUID uuid = event.player().uniqueId(); 60 | if (getUsers().containsKey(uuid)) { 61 | OneTimePack.log(4, "The cached pack will be send for player due it's on configuration state"); 62 | // Send with new thread due Protocolize catch StartConfiguration packet before proxy itself 63 | new Thread(() -> { 64 | for (Map.Entry entry : getUsers().get(uuid).getPacks().entrySet()) { 65 | final PushT packet = getPushPacket(entry.getValue()); 66 | if (packet instanceof AbstractPacket) { 67 | event.player().sendPacket(getWrappedPacket( 68 | (AbstractPacket) packet, 69 | Protocol.CONFIGURATION, 70 | PacketDirection.CLIENTBOUND, 71 | event.player().protocolVersion() 72 | )); 73 | } else { 74 | event.player().sendPacket(packet); 75 | } 76 | } 77 | OneTimePack.log(4, "Sent!"); 78 | }).start(); 79 | } 80 | }); 81 | registerListeners(); 82 | } 83 | 84 | protected abstract void registerListeners(); 85 | 86 | protected void onPackPush(@NotNull PacketReceiveEvent extends PushT> event, @NotNull Protocol protocol, @Nullable UUID id, @Nullable Object hash) { 87 | final ProtocolizePlayer player = event.player(); 88 | final Optional optional = onPackPush(player, protocol, event.packet(), id, hash); 89 | if (optional == null) return; 90 | 91 | event.cancelled(true); 92 | 93 | final PackResult result = optional.orElse(null); 94 | if (result == null) return; 95 | 96 | final StatusT cached = getStatusPacket(protocol, event.packet(), result); 97 | if (protocol == Protocol.CONFIGURATION && cached instanceof AbstractPacket) { 98 | player.sendPacketToServer(getWrappedPacket( 99 | (AbstractPacket) cached, 100 | Protocol.CONFIGURATION, 101 | PacketDirection.SERVERBOUND, 102 | player.protocolVersion() 103 | )); 104 | } else { 105 | player.sendPacketToServer(cached); 106 | } 107 | OneTimePack.log(4, () -> "Sent cached result " + result.name() + " from user " + player.uniqueId()); 108 | } 109 | 110 | @NotNull 111 | public PacketListener getPacketListener() { 112 | return packetListener; 113 | } 114 | 115 | @Override 116 | protected @NotNull UUID getUserId(@NotNull ProtocolizePlayer player) { 117 | return player.uniqueId(); 118 | } 119 | 120 | @NotNull 121 | protected PushT getPushPacket(@NotNull PushT packet) { 122 | return packet; 123 | } 124 | 125 | @NotNull 126 | protected abstract StatusT getStatusPacket(@NotNull Protocol protocol, @NotNull PushT packet, @NotNull PackResult result); 127 | 128 | @NotNull 129 | public static Object getWrappedPacket(@NotNull AbstractPacket packet, @NotNull Protocol protocol, @NotNull PacketDirection direction, int protocolVersion) { 130 | final Object wrapped = Protocolize.protocolRegistration().createPacket(packet.getClass(), protocol, direction, protocolVersion); 131 | try { 132 | WRAPPER.invoke(wrapped, packet); 133 | } catch (Throwable t) { 134 | t.printStackTrace(); 135 | } 136 | return wrapped; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /module/module-protocolize/src/main/java/com/saicone/onetimepack/core/packet/ResourcePackPop.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core.packet; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import dev.simplix.protocolize.api.PacketDirection; 5 | import dev.simplix.protocolize.api.Protocol; 6 | import dev.simplix.protocolize.api.Protocolize; 7 | import dev.simplix.protocolize.api.mapping.AbstractProtocolMapping; 8 | import dev.simplix.protocolize.api.mapping.ProtocolIdMapping; 9 | import dev.simplix.protocolize.api.packet.AbstractPacket; 10 | import dev.simplix.protocolize.api.util.ProtocolUtil; 11 | import io.netty.buffer.ByteBuf; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | import java.util.Objects; 18 | import java.util.UUID; 19 | import java.util.function.Function; 20 | 21 | import static dev.simplix.protocolize.api.util.ProtocolVersions.*; 22 | 23 | public class ResourcePackPop extends AbstractPacket { 24 | 25 | public static final List DEFAULT_MAPPINGS = Arrays.asList( 26 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_20_3, MINECRAFT_1_20_3, 0x43), 27 | AbstractProtocolMapping.rangedIdMapping(766, 767, 0x45) 28 | ); 29 | 30 | public static final List DEFAULT_MAPPINGS_CONFIGURATION = Arrays.asList( 31 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_20_3, MINECRAFT_1_20_3, 0x06), 32 | AbstractProtocolMapping.rangedIdMapping(766, 767, 0x08) 33 | ); 34 | 35 | public static void register() { 36 | register(Protocol.PLAY, DEFAULT_MAPPINGS, ResourcePackPop.Play.class); 37 | register(Protocol.CONFIGURATION, DEFAULT_MAPPINGS_CONFIGURATION, ResourcePackPop.Configuration.class); 38 | } 39 | 40 | public static void register(@Nullable Function> provider) { 41 | if (provider == null) { 42 | register(); 43 | return; 44 | } 45 | final List play = provider.apply("play"); 46 | register(Protocol.PLAY, play == null ? DEFAULT_MAPPINGS : play, ResourcePackPop.Play.class); 47 | final List configuration = provider.apply("configuration"); 48 | register(Protocol.CONFIGURATION, configuration == null ? DEFAULT_MAPPINGS_CONFIGURATION : configuration, ResourcePackPop.Configuration.class); 49 | } 50 | 51 | public static void register(@NotNull Protocol protocol, @NotNull List mappings, Class extends ResourcePackPop> clazz) { 52 | if (mappings.isEmpty()) { 53 | return; 54 | } 55 | Protocolize.protocolRegistration().registerPacket( 56 | mappings, 57 | protocol, 58 | PacketDirection.CLIENTBOUND, 59 | clazz 60 | ); 61 | } 62 | 63 | private boolean hasUniqueId; 64 | private UUID uniqueId; 65 | 66 | public ResourcePackPop() { 67 | } 68 | 69 | public ResourcePackPop(boolean hasUniqueId, @Nullable UUID uniqueId) { 70 | this.hasUniqueId = hasUniqueId; 71 | this.uniqueId = uniqueId; 72 | } 73 | 74 | public boolean hasUniqueId() { 75 | return hasUniqueId; 76 | } 77 | 78 | @Nullable 79 | public UUID getUniqueId() { 80 | return uniqueId; 81 | } 82 | 83 | @NotNull 84 | public Protocol getProtocol() { 85 | return this instanceof Play ? Protocol.PLAY : Protocol.CONFIGURATION; 86 | } 87 | 88 | public void setHasUniqueId(boolean hasUniqueId) { 89 | this.hasUniqueId = hasUniqueId; 90 | } 91 | 92 | public void setUniqueId(@Nullable UUID uniqueId) { 93 | this.uniqueId = uniqueId; 94 | } 95 | 96 | @Override 97 | public void read(ByteBuf buf, PacketDirection direction, int protocol) { 98 | hasUniqueId = buf.readBoolean(); 99 | if (hasUniqueId) { 100 | uniqueId = ProtocolUtil.readUniqueId(buf); 101 | } 102 | OneTimePack.log(4, () -> "[" + getProtocol().name() + "] Packet#read() = " + this); 103 | } 104 | 105 | @Override 106 | public void write(ByteBuf buf, PacketDirection direction, int protocol) { 107 | buf.writeBoolean(hasUniqueId); 108 | if (hasUniqueId) { 109 | ProtocolUtil.writeUniqueId(buf, uniqueId); 110 | } 111 | } 112 | 113 | @NotNull 114 | public ResourcePackPop copy() { 115 | return new ResourcePackPop(hasUniqueId, uniqueId); 116 | } 117 | 118 | @NotNull 119 | public ResourcePackPop.Play asPlay() { 120 | if (this instanceof ResourcePackPop.Play) { 121 | return (ResourcePackPop.Play) this; 122 | } 123 | return new ResourcePackPop.Play(hasUniqueId, uniqueId); 124 | } 125 | 126 | @NotNull 127 | public ResourcePackPop.Configuration asConfiguration() { 128 | if (this instanceof ResourcePackPop.Configuration) { 129 | return (ResourcePackPop.Configuration) this; 130 | } 131 | return new ResourcePackPop.Configuration(hasUniqueId, uniqueId); 132 | } 133 | 134 | @Override 135 | public boolean equals(Object o) { 136 | if (this == o) return true; 137 | if (o == null || getClass() != o.getClass()) return false; 138 | 139 | ResourcePackPop that = (ResourcePackPop) o; 140 | 141 | if (hasUniqueId != that.hasUniqueId) return false; 142 | return Objects.equals(uniqueId, that.uniqueId); 143 | } 144 | 145 | @Override 146 | public int hashCode() { 147 | int result = (hasUniqueId ? 1 : 0); 148 | result = 31 * result + (uniqueId != null ? uniqueId.hashCode() : 0); 149 | return result; 150 | } 151 | 152 | @Override 153 | public String toString() { 154 | return "ClientboundResourcePackPop{" + 155 | "hasUniqueId=" + hasUniqueId + 156 | (hasUniqueId ? ", uniqueId=" + uniqueId : "") + 157 | '}'; 158 | } 159 | 160 | public static class Play extends ResourcePackPop { 161 | public Play() { 162 | } 163 | 164 | public Play(boolean hasUniqueId, @Nullable UUID uniqueId) { 165 | super(hasUniqueId, uniqueId); 166 | } 167 | } 168 | 169 | public static class Configuration extends ResourcePackPop { 170 | public Configuration() { 171 | } 172 | 173 | public Configuration(boolean hasUniqueId, @Nullable UUID uniqueId) { 174 | super(hasUniqueId, uniqueId); 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /module/module-protocolize/src/main/java/com/saicone/onetimepack/core/packet/ResourcePackStatus.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core.packet; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import com.saicone.onetimepack.core.PackResult; 5 | import dev.simplix.protocolize.api.PacketDirection; 6 | import dev.simplix.protocolize.api.Protocol; 7 | import dev.simplix.protocolize.api.Protocolize; 8 | import dev.simplix.protocolize.api.mapping.AbstractProtocolMapping; 9 | import dev.simplix.protocolize.api.mapping.ProtocolIdMapping; 10 | import dev.simplix.protocolize.api.packet.AbstractPacket; 11 | import dev.simplix.protocolize.api.util.ProtocolUtil; 12 | import io.netty.buffer.ByteBuf; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.Objects; 19 | import java.util.UUID; 20 | import java.util.function.Function; 21 | 22 | import static dev.simplix.protocolize.api.util.ProtocolVersions.*; 23 | 24 | public class ResourcePackStatus extends AbstractPacket { 25 | 26 | public static final List DEFAULT_MAPPINGS = Arrays.asList( 27 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_8, MINECRAFT_1_8, 0x19), 28 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_9, MINECRAFT_1_11_2, 0x16), 29 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_12, MINECRAFT_1_12_2, 0x18), 30 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_13, MINECRAFT_1_13_2, 0x1D), 31 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_14, MINECRAFT_1_15_2, 0x1F), 32 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_16, MINECRAFT_1_16_1, 0x20), 33 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_16_2, MINECRAFT_1_18_2, 0x21), 34 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_19, MINECRAFT_1_19, 0x23), 35 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_19_1, MINECRAFT_1_20_1, 0x24), 36 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_20_2, MINECRAFT_1_20_2, 0x27), 37 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_20_3, MINECRAFT_1_20_3, 0x28), 38 | AbstractProtocolMapping.rangedIdMapping(766, 767, 0x2B) 39 | ); 40 | 41 | public static final List DEFAULT_MAPPINGS_CONFIGURATION = Arrays.asList( 42 | AbstractProtocolMapping.rangedIdMapping(MINECRAFT_1_20_1, MINECRAFT_1_20_3, 0x05), 43 | AbstractProtocolMapping.rangedIdMapping(766, 767, 0x06) 44 | ); 45 | 46 | public static void register() { 47 | register(Protocol.PLAY, DEFAULT_MAPPINGS, ResourcePackStatus.Play.class); 48 | register(Protocol.CONFIGURATION, DEFAULT_MAPPINGS_CONFIGURATION, ResourcePackStatus.Configuration.class); 49 | } 50 | 51 | public static void register(@Nullable Function> provider) { 52 | if (provider == null) { 53 | register(); 54 | return; 55 | } 56 | final List play = provider.apply("play"); 57 | register(Protocol.PLAY, play == null ? DEFAULT_MAPPINGS : play, ResourcePackStatus.Play.class); 58 | final List configuration = provider.apply("configuration"); 59 | register(Protocol.CONFIGURATION, configuration == null ? DEFAULT_MAPPINGS_CONFIGURATION : configuration, ResourcePackStatus.Configuration.class); 60 | } 61 | 62 | public static void register(@NotNull Protocol protocol, @NotNull List mappings, Class extends ResourcePackStatus> clazz) { 63 | if (mappings.isEmpty()) { 64 | return; 65 | } 66 | Protocolize.protocolRegistration().registerPacket( 67 | mappings, 68 | protocol, 69 | PacketDirection.SERVERBOUND, 70 | clazz 71 | ); 72 | } 73 | 74 | private UUID uniqueId; // Added in 1.20.3 75 | private String hash; // Removed in 1.10 76 | // 0: successfully loaded 77 | // 1: declined 78 | // 2: failed download 79 | // 3: accepted 80 | // 4: downloaded 81 | // 5: invalid URL 82 | // 6: failed to reload 83 | // 7: discarded 84 | private PackResult result; 85 | 86 | @NotNull 87 | public static ResourcePackStatus of(@NotNull Protocol protocol, @NotNull ResourcePackPush packet, @NotNull PackResult result) { 88 | if (protocol == Protocol.CONFIGURATION) { 89 | return new ResourcePackStatus.Configuration(packet.getUniqueId(), result); 90 | } else { 91 | if (packet.getUniqueId() == null) { 92 | return new ResourcePackStatus.Play(packet.getHash(), result); 93 | } else { 94 | return new ResourcePackStatus.Play(packet.getUniqueId(), result); 95 | } 96 | } 97 | } 98 | 99 | public ResourcePackStatus() { 100 | } 101 | 102 | public ResourcePackStatus(@NotNull PackResult result) { 103 | this.result = result; 104 | } 105 | 106 | public ResourcePackStatus(@Nullable UUID uniqueId, @NotNull PackResult result) { 107 | this.uniqueId = uniqueId; 108 | this.result = result; 109 | } 110 | 111 | public ResourcePackStatus(@Nullable String hash, @NotNull PackResult result) { 112 | this.hash = hash; 113 | this.result = result; 114 | } 115 | 116 | @Nullable 117 | public UUID getUniqueId() { 118 | return uniqueId; 119 | } 120 | 121 | @Nullable 122 | public String getHash() { 123 | return hash; 124 | } 125 | 126 | @NotNull 127 | public PackResult getResult() { 128 | return result; 129 | } 130 | 131 | public int getResultOrdinal(int protocol) { 132 | if (result.ordinal() >= 4 && protocol < MINECRAFT_1_20_3) { 133 | return result.getFallback(); 134 | } 135 | return result.ordinal(); 136 | } 137 | 138 | public void setUniqueId(@Nullable UUID uniqueId) { 139 | this.uniqueId = uniqueId; 140 | } 141 | 142 | @NotNull 143 | public Protocol getProtocol() { 144 | return this instanceof Play ? Protocol.PLAY : Protocol.CONFIGURATION; 145 | } 146 | 147 | public void setHash(@Nullable String hash) { 148 | this.hash = hash; 149 | } 150 | 151 | public void setResult(@NotNull PackResult result) { 152 | this.result = result; 153 | } 154 | 155 | @Override 156 | public void read(ByteBuf buf, PacketDirection direction, int protocol) { 157 | if (protocol >= MINECRAFT_1_20_3) { 158 | uniqueId = ProtocolUtil.readUniqueId(buf); 159 | } 160 | if (protocol <= MINECRAFT_1_9_4) { 161 | hash = ProtocolUtil.readString(buf); 162 | } 163 | result = PackResult.of(ProtocolUtil.readVarInt(buf)); 164 | OneTimePack.log(4, () -> "[" + getProtocol().name() + "] Packet#read() = " + this); 165 | } 166 | 167 | @Override 168 | public void write(ByteBuf buf, PacketDirection direction, int protocol) { 169 | if (protocol >= MINECRAFT_1_20_3) { 170 | ProtocolUtil.writeUniqueId(buf, uniqueId); 171 | } 172 | if (protocol <= MINECRAFT_1_9_4) { 173 | ProtocolUtil.writeString(buf, hash); 174 | } 175 | ProtocolUtil.writeVarInt(buf, getResultOrdinal(protocol)); 176 | } 177 | 178 | @NotNull 179 | public ResourcePackStatus copy() { 180 | return uniqueId != null ? new ResourcePackStatus(uniqueId, result) : new ResourcePackStatus(hash, result); 181 | } 182 | 183 | @NotNull 184 | public ResourcePackStatus.Play asPlay() { 185 | if (this instanceof ResourcePackStatus.Play) { 186 | return (ResourcePackStatus.Play) this; 187 | } 188 | return uniqueId != null ? new ResourcePackStatus.Play(uniqueId, result) : new ResourcePackStatus.Play(hash, result); 189 | } 190 | 191 | @NotNull 192 | public ResourcePackStatus.Configuration asConfiguration() { 193 | if (this instanceof ResourcePackStatus.Configuration) { 194 | return (ResourcePackStatus.Configuration) this; 195 | } 196 | return new ResourcePackStatus.Configuration(uniqueId, result); 197 | } 198 | 199 | @Override 200 | public boolean equals(Object o) { 201 | if (this == o) return true; 202 | if (o == null || getClass() != o.getClass()) return false; 203 | 204 | ResourcePackStatus that = (ResourcePackStatus) o; 205 | 206 | if (result != that.result) return false; 207 | if (!Objects.equals(uniqueId, that.uniqueId)) return false; 208 | return Objects.equals(hash, that.hash); 209 | } 210 | 211 | @Override 212 | public int hashCode() { 213 | int result1 = uniqueId != null ? uniqueId.hashCode() : 0; 214 | result1 = 31 * result1 + (hash != null ? hash.hashCode() : 0); 215 | result1 = 31 * result1 + (result != null ? result.ordinal() : -1); 216 | return result1; 217 | } 218 | 219 | @Override 220 | public String toString() { 221 | return "ServerboundResourcePack{" + 222 | (uniqueId != null ? "uniqueId='" + uniqueId + "', " : "") + 223 | (hash != null ? "hash='" + hash + "', " : "") + 224 | "result=" + (result != null ? result.ordinal() : -1) + 225 | '}'; 226 | } 227 | 228 | public static class Play extends ResourcePackStatus { 229 | public Play() { 230 | } 231 | 232 | public Play(@NotNull PackResult result) { 233 | super(result); 234 | } 235 | 236 | public Play(@Nullable UUID uniqueId, @NotNull PackResult result) { 237 | super(uniqueId, result); 238 | } 239 | 240 | public Play(@Nullable String hash, @NotNull PackResult result) { 241 | super(hash, result); 242 | } 243 | } 244 | 245 | public static class Configuration extends ResourcePackStatus { 246 | public Configuration() { 247 | } 248 | 249 | public Configuration(@NotNull PackResult result) { 250 | super(result); 251 | } 252 | 253 | public Configuration(@Nullable UUID uniqueId, @NotNull PackResult result) { 254 | super(uniqueId, result); 255 | } 256 | } 257 | } -------------------------------------------------------------------------------- /module/module-protocolize/src/main/java/com/saicone/onetimepack/module/listener/PacketListener.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.module.listener; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import dev.simplix.protocolize.api.Direction; 5 | import dev.simplix.protocolize.api.Platform; 6 | import dev.simplix.protocolize.api.Protocolize; 7 | import dev.simplix.protocolize.api.listener.AbstractPacketListener; 8 | import dev.simplix.protocolize.api.listener.PacketReceiveEvent; 9 | import dev.simplix.protocolize.api.listener.PacketSendEvent; 10 | import dev.simplix.protocolize.api.packet.AbstractPacket; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.lang.reflect.Field; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.function.Consumer; 19 | 20 | public class PacketListener { 21 | 22 | private final Map> listeners = new HashMap<>(); 23 | 24 | public void registerReceive(@NotNull Class packet, @NotNull Direction direction, @NotNull Consumer> consumer) { 25 | registerReceive(packet, direction, 0, consumer); 26 | } 27 | 28 | public void registerReceive(@NotNull Class packet, @NotNull Direction direction, int priority, @NotNull Consumer> consumer) { 29 | final Listener listener = computeListener(packet, direction, priority); 30 | listener.setOnReceive(consumer); 31 | register(listener); 32 | } 33 | 34 | public void registerSend(@NotNull Class packet, @NotNull Direction direction, @NotNull Consumer> consumer) { 35 | registerSend(packet, direction, 0, consumer); 36 | } 37 | 38 | public void registerSend(@NotNull Class packet, @NotNull Direction direction, int priority, @NotNull Consumer> consumer) { 39 | final Listener listener = computeListener(packet, direction, priority); 40 | listener.setOnSend(consumer); 41 | register(listener); 42 | } 43 | 44 | @SuppressWarnings("unchecked") 45 | private void register(@NotNull Listener> listener) { 46 | if (listener.isRegistered()) { 47 | OneTimePack.log(4, "The listener of " + listener.type().getName() + " is registered in direction " + listener.direction().name()); 48 | return; 49 | } 50 | if (Protocolize.platform() == Platform.BUNGEECORD || AbstractPacket.class.isAssignableFrom(listener.type())) { 51 | listener.setRegistered(true); 52 | Protocolize.listenerProvider().registerListener(listener); 53 | } else { 54 | try { 55 | final Field field = Protocolize.listenerProvider().getClass().getDeclaredField("listeners"); 56 | field.setAccessible(true); 57 | final List> listeners = (List>) field.get(Protocolize.listenerProvider()); 58 | listeners.add(listener); 59 | } catch (Throwable t) { 60 | OneTimePack.log(1, t, "Cannot register listener of " + listener.type().getName() + " in direction " + listener.direction().name()); 61 | return; 62 | } 63 | } 64 | OneTimePack.log(4, "The listener of " + listener.type().getName() + " was registered in direction " + listener.direction().name()); 65 | } 66 | 67 | public void unregister() { 68 | OneTimePack.log(4, "The listeners was unregistered"); 69 | for (Map.Entry> entry : listeners.entrySet()) { 70 | if (entry.getValue().isRegistered()) { 71 | Protocolize.listenerProvider().unregisterListener(entry.getValue()); 72 | } 73 | } 74 | clear(); 75 | } 76 | 77 | public void unregister(@NotNull Class> packet) { 78 | listeners.entrySet().removeIf(entry -> { 79 | if (entry.getKey().getClazz() == packet) { 80 | Protocolize.listenerProvider().unregisterListener(entry.getValue()); 81 | return true; 82 | } else { 83 | return false; 84 | } 85 | }); 86 | } 87 | 88 | public void unregister(@NotNull Class> packet, @NotNull Direction direction) { 89 | listeners.entrySet().removeIf(entry -> { 90 | if (entry.getKey().getClazz() == packet && entry.getKey().getDirection() == direction) { 91 | Protocolize.listenerProvider().unregisterListener(entry.getValue()); 92 | return true; 93 | } else { 94 | return false; 95 | } 96 | }); 97 | } 98 | 99 | public void unregister(@NotNull Class> packet, int priority) { 100 | listeners.entrySet().removeIf(entry -> { 101 | if (entry.getKey().getClazz() == packet && entry.getKey().getPriority() == priority) { 102 | Protocolize.listenerProvider().unregisterListener(entry.getValue()); 103 | return true; 104 | } else { 105 | return false; 106 | } 107 | }); 108 | } 109 | 110 | public void unregister(@NotNull Class> packet, @NotNull Direction direction, int priority) { 111 | final ListenerKey key = new ListenerKey(packet, direction, priority); 112 | final Listener> listener = listeners.get(key); 113 | if (listener != null) { 114 | Protocolize.listenerProvider().unregisterListener(listener); 115 | listeners.remove(key); 116 | } 117 | } 118 | 119 | @SuppressWarnings("unchecked") 120 | @NotNull 121 | private Listener computeListener(@NotNull Class packet, @NotNull Direction direction, int priority) { 122 | final ListenerKey key = new ListenerKey(packet, direction, priority); 123 | if (!listeners.containsKey(key)) { 124 | listeners.put(key, new Listener<>(packet, direction, priority)); 125 | } 126 | return (Listener) listeners.get(key); 127 | } 128 | 129 | public void clear() { 130 | listeners.clear(); 131 | } 132 | 133 | private static class ListenerKey { 134 | 135 | private final Class> clazz; 136 | private final Direction direction; 137 | private final int priority; 138 | 139 | public ListenerKey(@NotNull Class> clazz, @NotNull Direction direction, int priority) { 140 | this.clazz = clazz; 141 | this.direction = direction; 142 | this.priority = priority; 143 | } 144 | 145 | @NotNull 146 | public Class> getClazz() { 147 | return clazz; 148 | } 149 | 150 | @NotNull 151 | public Direction getDirection() { 152 | return direction; 153 | } 154 | 155 | public int getPriority() { 156 | return priority; 157 | } 158 | 159 | @Override 160 | public boolean equals(Object o) { 161 | if (this == o) return true; 162 | if (o == null || getClass() != o.getClass()) return false; 163 | 164 | ListenerKey that = (ListenerKey) o; 165 | 166 | if (priority != that.priority) return false; 167 | if (!clazz.equals(that.clazz)) return false; 168 | return direction == that.direction; 169 | } 170 | 171 | @Override 172 | public int hashCode() { 173 | int result = clazz.hashCode(); 174 | result = 31 * result + direction.hashCode(); 175 | result = 31 * result + priority; 176 | return result; 177 | } 178 | } 179 | 180 | private static class Listener extends AbstractPacketListener { 181 | 182 | private boolean registered = false; 183 | private Consumer> onReceive; 184 | private Consumer> onSend; 185 | 186 | public Listener(@NotNull Class packet, @NotNull Direction direction) { 187 | this(packet, direction, 0); 188 | } 189 | 190 | public Listener(@NotNull Class packet, @NotNull Direction direction, int priority) { 191 | super(packet, direction, priority); 192 | } 193 | 194 | public boolean isRegistered() { 195 | return registered; 196 | } 197 | 198 | @Nullable 199 | public Consumer> getOnReceive() { 200 | return onReceive; 201 | } 202 | 203 | @Nullable 204 | public Consumer> getOnSend() { 205 | return onSend; 206 | } 207 | 208 | public void setRegistered(boolean registered) { 209 | this.registered = registered; 210 | } 211 | 212 | public void setOnReceive(@NotNull Consumer> onReceive) { 213 | this.onReceive = onReceive; 214 | } 215 | 216 | public void setOnSend(@NotNull Consumer> onSend) { 217 | this.onSend = onSend; 218 | } 219 | 220 | @Override 221 | public void packetReceive(PacketReceiveEvent event) { 222 | if (onReceive != null) { 223 | if (event.packet() == null) { 224 | OneTimePack.log(4, "The packet " + type().getName() + " was null"); 225 | event.cancelled(true); 226 | return; 227 | } 228 | onReceive.accept(event); 229 | } 230 | } 231 | 232 | @Override 233 | public void packetSend(PacketSendEvent event) { 234 | if (onSend != null) { 235 | if (event.packet() == null) { 236 | OneTimePack.log(4, "The packet " + type().getName() + " was null"); 237 | event.cancelled(true); 238 | return; 239 | } 240 | onSend.accept(event); 241 | } 242 | } 243 | } 244 | } -------------------------------------------------------------------------------- /module/module-velocity/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url 'https://repo.papermc.io/repository/maven-public/' } 3 | } 4 | 5 | dependencies { 6 | compileOnly libs.velocity.api 7 | } -------------------------------------------------------------------------------- /module/module-velocity/src/main/java/com/saicone/onetimepack/core/VelocityProcessor.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import com.saicone.onetimepack.util.ValueComparator; 5 | import com.velocitypowered.api.event.ResultedEvent; 6 | import com.velocitypowered.api.event.Subscribe; 7 | import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; 8 | import com.velocitypowered.api.event.player.ServerResourcePackRemoveEvent; 9 | import com.velocitypowered.api.event.player.ServerResourcePackSendEvent; 10 | import com.velocitypowered.api.event.player.configuration.PlayerEnteredConfigurationEvent; 11 | import com.velocitypowered.api.network.ProtocolState; 12 | import com.velocitypowered.api.network.ProtocolVersion; 13 | import com.velocitypowered.api.proxy.Player; 14 | import com.velocitypowered.api.proxy.ProxyServer; 15 | import com.velocitypowered.api.proxy.player.ResourcePackInfo; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import java.util.Optional; 20 | import java.util.UUID; 21 | 22 | public class VelocityProcessor extends Processor { 23 | 24 | private static final PlayerResourcePackStatusEvent.Status[] VALUES = PlayerResourcePackStatusEvent.Status.values(); 25 | 26 | private final ProxyServer proxy; 27 | private final Object plugin; 28 | 29 | public VelocityProcessor(@NotNull ProxyServer proxy, @NotNull Object plugin) { 30 | this.proxy = proxy; 31 | this.plugin = plugin; 32 | } 33 | 34 | @Override 35 | public void onEnable() { 36 | proxy.getEventManager().register(plugin, this); 37 | } 38 | 39 | @Subscribe 40 | public void onEnterConfiguration(PlayerEnteredConfigurationEvent event) { 41 | if (!isSendCached1_20_2() || event.player().getProtocolVersion().greaterThan(ProtocolVersion.MINECRAFT_1_20_2)) { 42 | return; 43 | } 44 | final UUID uuid = event.player().getUniqueId(); 45 | if (getUsers().containsKey(uuid)) { 46 | OneTimePack.log(4, "The cached pack will be send for player due it's on configuration state"); 47 | for (var entry : getUsers().get(uuid).getPacks().entrySet()) { 48 | event.player().sendResourcePackOffer(entry.getValue()); 49 | } 50 | OneTimePack.log(4, "Sent!"); 51 | } 52 | } 53 | 54 | @Subscribe 55 | public void onPackSend(ServerResourcePackSendEvent event) { 56 | final Player player = event.getServerConnection().getPlayer(); 57 | final ResourcePackInfo info = event.getProvidedResourcePack(); 58 | 59 | final Optional optional = onPackPush(player, player.getProtocolState(), info, info.getId(), info.getHash()); 60 | if (optional == null) return; 61 | 62 | event.setResult(ResultedEvent.GenericResult.denied()); 63 | 64 | final PackResult result = optional.orElse(null); 65 | if (result == null) return; 66 | 67 | // Async operation 68 | proxy.getScheduler().buildTask(plugin, () -> { 69 | proxy.getEventManager().fireAndForget(new PlayerResourcePackStatusEvent(player, info.getId(), VALUES[result.ordinal()], info)); 70 | OneTimePack.log(4, () -> "Sent cached result " + result.name() + " from user " + player.getUniqueId()); 71 | }).schedule(); 72 | } 73 | 74 | @Subscribe 75 | public void onPackRemove(ServerResourcePackRemoveEvent event) { 76 | final Player player = event.getServerConnection().getPlayer(); 77 | if (onPackPop(player, player.getProtocolState(), event, event.getPackId())) { 78 | event.setResult(ResultedEvent.GenericResult.denied()); 79 | } 80 | } 81 | 82 | @Subscribe 83 | public void onPackStatus(PlayerResourcePackStatusEvent event) { 84 | onPackStatus(event.getPlayer(), event.getPackId(), event.getStatus()); 85 | } 86 | 87 | @Override 88 | protected @NotNull UUID getUserId(@NotNull Player player) { 89 | return player.getUniqueId(); 90 | } 91 | 92 | @Override 93 | protected @Nullable ValueComparator getPackValue(@NotNull String name) { 94 | return switch (name) { 95 | case "UUID" -> ResourcePackInfo::getId; 96 | case "URL" -> ResourcePackInfo::getUrl; 97 | case "HASH" -> ResourcePackInfo::getHash; 98 | case "PROMPT" -> ResourcePackInfo::getPrompt; 99 | case "ALL" -> pack -> pack; 100 | case "ANY" -> pack -> true; 101 | default -> null; 102 | }; 103 | } 104 | 105 | @Override 106 | public void clearPackets(@NotNull Player player, @NotNull ProtocolState state) { 107 | player.clearResourcePacks(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /module/module-vpacketevents/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url 'https://repo.papermc.io/repository/maven-public/' } 3 | } 4 | 5 | dependencies { 6 | compileOnly(libs.vpacketevents.api) { 7 | exclude group: 'com.velocitypowered' 8 | } 9 | compileOnly libs.netty.transport 10 | compileOnly libs.velocity.api 11 | } -------------------------------------------------------------------------------- /module/module-vpacketevents/src/main/java/com/saicone/onetimepack/core/VPacketEventsProcessor.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import com.saicone.onetimepack.util.ValueComparator; 5 | import com.velocitypowered.api.event.ResultedEvent; 6 | import com.velocitypowered.api.event.Subscribe; 7 | import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; 8 | import com.velocitypowered.api.network.ProtocolState; 9 | import com.velocitypowered.api.network.ProtocolVersion; 10 | import com.velocitypowered.api.proxy.Player; 11 | import com.velocitypowered.api.proxy.ProxyServer; 12 | import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; 13 | import com.velocitypowered.proxy.connection.client.ConnectedPlayer; 14 | import com.velocitypowered.proxy.protocol.MinecraftPacket; 15 | import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket; 16 | import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket; 17 | import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; 18 | import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket; 19 | import io.github._4drian3d.vpacketevents.api.event.PacketReceiveEvent; 20 | import io.github._4drian3d.vpacketevents.api.event.PacketSendEvent; 21 | import org.jetbrains.annotations.NotNull; 22 | import org.jetbrains.annotations.Nullable; 23 | 24 | import java.util.Optional; 25 | import java.util.UUID; 26 | 27 | public class VPacketEventsProcessor extends Processor { 28 | 29 | private final ProxyServer proxy; 30 | private final Object plugin; 31 | 32 | public VPacketEventsProcessor(@NotNull ProxyServer proxy, @NotNull Object plugin) { 33 | this.proxy = proxy; 34 | this.plugin = plugin; 35 | } 36 | 37 | @Override 38 | public void onEnable() { 39 | proxy.getEventManager().register(plugin, this); 40 | } 41 | 42 | @Subscribe 43 | public void onPacketReceive(PacketReceiveEvent event) { 44 | final MinecraftPacket packet = event.getPacket(); 45 | if (packet instanceof ResourcePackResponsePacket response) { 46 | onPackStatus(event.getPlayer(), response.getId(), response.getStatus()); 47 | } 48 | } 49 | 50 | @Subscribe 51 | public void onPacketSend(PacketSendEvent event) { 52 | final MinecraftPacket packet = event.getPacket(); 53 | if (packet instanceof StartUpdatePacket) { 54 | if (!isSendCached1_20_2() || event.getPlayer().getProtocolVersion().greaterThan(ProtocolVersion.MINECRAFT_1_20_2)) { 55 | return; 56 | } 57 | final UUID uuid = event.getPlayer().getUniqueId(); 58 | if (getUsers().containsKey(uuid)) { 59 | OneTimePack.log(4, "The cached pack will be send for player due it's on configuration state"); 60 | // Send with new thread due player still on PLAY protocol 61 | new Thread(() -> { 62 | if (!event.getResult().isAllowed()) return; 63 | for (var entry : getUsers().get(uuid).getPacks().entrySet()) { 64 | ((ConnectedPlayer) event.getPlayer()).getConnection().write(entry.getValue()); 65 | } 66 | OneTimePack.log(4, "Sent!"); 67 | }).start(); 68 | } 69 | } else if (packet instanceof ResourcePackRequestPacket request) { 70 | onPackPush(event, request); 71 | } else if (packet instanceof RemoveResourcePackPacket remove) { 72 | if (onPackPop(event.getPlayer(), event.getPlayer().getProtocolState(), remove, remove.getId())) { 73 | event.setResult(ResultedEvent.GenericResult.denied()); 74 | } 75 | } 76 | } 77 | 78 | private void onPackPush(@NotNull PacketSendEvent event, @NotNull ResourcePackRequestPacket packet) { 79 | final Optional optional = onPackPush(event.getPlayer(), event.getPlayer().getProtocolState(), packet, packet.getId(), packet.getHash()); 80 | if (optional == null) return; 81 | 82 | event.setResult(ResultedEvent.GenericResult.denied()); 83 | 84 | final PackResult result = optional.orElse(null); 85 | if (result == null) return; 86 | 87 | event.getPlayer().getCurrentServer().ifPresent(server -> { 88 | final ResourcePackResponsePacket cached = new ResourcePackResponsePacket(packet.getId(), packet.getHash(), PlayerResourcePackStatusEvent.Status.values()[result.ordinal()]); 89 | ((VelocityServerConnection) server).getConnection().write(cached); 90 | OneTimePack.log(4, () -> "Sent cached result " + cached + " from user " + event.getPlayer().getUniqueId()); 91 | }); 92 | } 93 | 94 | @Override 95 | protected @NotNull UUID getUserId(@NotNull Player player) { 96 | return player.getUniqueId(); 97 | } 98 | 99 | @Override 100 | protected @Nullable ValueComparator getPackValue(@NotNull String name) { 101 | return switch (name) { 102 | case "UUID" -> ResourcePackRequestPacket::getId; 103 | case "URL" -> ResourcePackRequestPacket::getUrl; 104 | case "HASH" -> ResourcePackRequestPacket::getHash; 105 | case "PROMPT" -> pack -> pack.getPrompt() == null ? null : pack.getPrompt().getComponent(); 106 | case "ALL" -> pack -> pack; 107 | case "ANY" -> pack -> true; 108 | default -> null; 109 | }; 110 | } 111 | 112 | @Override 113 | public void clearPackets(@NotNull Player player, @NotNull ProtocolState state) { 114 | player.clearResourcePacks(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /platform/platform-bungee/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } 3 | } 4 | 5 | dependencies { 6 | compileOnly(libs.bungeecord.api) { 7 | exclude group: 'com.mojang' 8 | } 9 | } -------------------------------------------------------------------------------- /platform/platform-bungee/src/main/java/com/saicone/onetimepack/BungeePlugin.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack; 2 | 3 | import com.saicone.onetimepack.core.BungeePacketUser; 4 | import com.saicone.onetimepack.core.PacketUser; 5 | import com.saicone.onetimepack.core.Processor; 6 | import net.md_5.bungee.api.ChatColor; 7 | import net.md_5.bungee.api.CommandSender; 8 | import net.md_5.bungee.api.ProxyServer; 9 | import net.md_5.bungee.api.chat.BaseComponent; 10 | import net.md_5.bungee.api.chat.TextComponent; 11 | import net.md_5.bungee.api.connection.ProxiedPlayer; 12 | import net.md_5.bungee.api.event.PlayerDisconnectEvent; 13 | import net.md_5.bungee.api.plugin.Command; 14 | import net.md_5.bungee.api.plugin.Listener; 15 | import net.md_5.bungee.api.plugin.Plugin; 16 | import net.md_5.bungee.event.EventHandler; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | import java.io.File; 20 | import java.util.UUID; 21 | import java.util.logging.Level; 22 | 23 | public class BungeePlugin extends Plugin implements Listener, OneTimePack.Provider { 24 | 25 | private static BungeePlugin instance; 26 | 27 | public static BungeePlugin get() { 28 | return instance; 29 | } 30 | 31 | public BungeePlugin() { 32 | instance = this; 33 | new OneTimePack(this, initProcessor()); 34 | } 35 | 36 | @NotNull 37 | protected Processor, ?, ?> initProcessor() { 38 | throw new RuntimeException("Bungeecord plugin not implemented"); 39 | } 40 | 41 | @Override 42 | public void onLoad() { 43 | OneTimePack.get().onLoad(); 44 | } 45 | 46 | @Override 47 | public void onEnable() { 48 | OneTimePack.get().onEnable(); 49 | this.getProxy().getPluginManager().registerListener(this, this); 50 | this.getProxy().getPluginManager().registerCommand(this, new BungeeCommand()); 51 | } 52 | 53 | @Override 54 | public void onDisable() { 55 | OneTimePack.get().onDisable(); 56 | } 57 | 58 | @EventHandler 59 | public void onDisconnect(PlayerDisconnectEvent event) { 60 | OneTimePack.get().getPacketHandler().clear(event.getPlayer().getUniqueId()); 61 | } 62 | 63 | @Override 64 | public @NotNull PacketUser getUser(@NotNull UUID uniqueId) { 65 | final ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uniqueId); 66 | if (player == null) { 67 | throw new IllegalArgumentException("The player " + uniqueId + " is not connected"); 68 | } 69 | return new BungeePacketUser<>(player); 70 | } 71 | 72 | @Override 73 | public @NotNull File getPluginFolder() { 74 | return getDataFolder(); 75 | } 76 | 77 | @Override 78 | public void log(int level, @NotNull String s) { 79 | switch (level) { 80 | case 1: 81 | getLogger().log(Level.SEVERE, s); 82 | break; 83 | case 2: 84 | getLogger().log(Level.WARNING, s); 85 | break; 86 | default: 87 | getLogger().log(Level.INFO, s); 88 | break; 89 | } 90 | } 91 | 92 | @NotNull 93 | private static BaseComponent[] parseComponent(@NotNull String s) { 94 | return TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', s)); 95 | } 96 | 97 | public static class BungeeCommand extends Command { 98 | 99 | public BungeeCommand() { 100 | super("onetimepack", "onetimepack.use"); 101 | } 102 | 103 | @Override 104 | public void execute(CommandSender sender, String[] args) { 105 | if (args.length >= 1 && args[0].equalsIgnoreCase("reload")) { 106 | final long start = System.currentTimeMillis(); 107 | OneTimePack.get().onReload(); 108 | final long time = System.currentTimeMillis() - start; 109 | sender.sendMessage(parseComponent("&aPlugin successfully reloaded [&f" + time + " ms&a]")); 110 | return; 111 | } 112 | sender.sendMessage(parseComponent("&a&lOneTimePack &e&lv" + BungeePlugin.get().getDescription().getVersion())); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /platform/platform-bungee/src/main/java/com/saicone/onetimepack/core/BungeePacketUser.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import net.md_5.bungee.api.connection.ProxiedPlayer; 4 | import net.md_5.bungee.api.connection.Server; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.UUID; 9 | 10 | public class BungeePacketUser extends PacketUser { 11 | 12 | private final ProxiedPlayer player; 13 | 14 | public BungeePacketUser(@NotNull ProxiedPlayer player) { 15 | this.player = player; 16 | } 17 | 18 | @Override 19 | public @NotNull UUID getUniqueId() { 20 | return player.getUniqueId(); 21 | } 22 | 23 | @Override 24 | public int getProtocolVersion() { 25 | return player.getPendingConnection().getVersion(); 26 | } 27 | 28 | @Override 29 | public @Nullable String getServer() { 30 | final Server server = player.getServer(); 31 | return server == null ? null : server.getInfo().getName(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /platform/platform-velocity/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias libs.plugins.blossom 3 | } 4 | 5 | blossom { 6 | replaceTokenIn('src/main/java/com/saicone/onetimepack/VelocityPlugin.java') 7 | replaceToken '${version}', project.version 8 | } 9 | 10 | repositories { 11 | maven { url 'https://repo.papermc.io/repository/maven-public/' } 12 | } 13 | 14 | dependencies { 15 | compileOnly libs.velocity.api 16 | } -------------------------------------------------------------------------------- /platform/platform-velocity/src/main/java/com/saicone/onetimepack/VelocityPlugin.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack; 2 | 3 | import com.saicone.onetimepack.core.PacketUser; 4 | import com.saicone.onetimepack.core.Processor; 5 | import com.saicone.onetimepack.core.VelocityPacketUser; 6 | import com.velocitypowered.api.command.CommandSource; 7 | import com.velocitypowered.api.command.SimpleCommand; 8 | import com.velocitypowered.api.event.connection.DisconnectEvent; 9 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 10 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 11 | import com.velocitypowered.api.proxy.ProxyServer; 12 | import net.kyori.adventure.text.TextComponent; 13 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.slf4j.Logger; 16 | 17 | import java.io.File; 18 | import java.nio.file.Path; 19 | import java.util.List; 20 | import java.util.UUID; 21 | 22 | public class VelocityPlugin implements OneTimePack.Provider { 23 | 24 | private static VelocityPlugin instance; 25 | 26 | public static VelocityPlugin get() { 27 | return instance; 28 | } 29 | 30 | private final ProxyServer proxy; 31 | private final Logger logger; 32 | private final Path dataDirectory; 33 | 34 | public VelocityPlugin(ProxyServer server, Logger logger, Path dataDirectory) { 35 | instance = this; 36 | this.proxy = server; 37 | this.logger = logger; 38 | this.dataDirectory = dataDirectory; 39 | new OneTimePack(this, initProcessor()); 40 | } 41 | 42 | @NotNull 43 | protected Processor, ?, ?> initProcessor() { 44 | throw new RuntimeException("Velocity plugin not implemented"); 45 | } 46 | 47 | public void onEnable(ProxyInitializeEvent event) { 48 | OneTimePack.get().onLoad(); 49 | OneTimePack.get().onEnable(); 50 | getProxy().getCommandManager().register(getProxy().getCommandManager().metaBuilder("onetimepack").plugin(this).build(), new VelocityCommand()); 51 | } 52 | 53 | public void onDisable(ProxyShutdownEvent event) { 54 | OneTimePack.get().onDisable(); 55 | } 56 | 57 | public void onDisconnect(DisconnectEvent event) { 58 | OneTimePack.get().getPacketHandler().clear(event.getPlayer().getUniqueId()); 59 | } 60 | 61 | public ProxyServer getProxy() { 62 | return proxy; 63 | } 64 | 65 | public Logger getLogger() { 66 | return logger; 67 | } 68 | 69 | public Path getDataDirectory() { 70 | return dataDirectory; 71 | } 72 | 73 | @Override 74 | public @NotNull PacketUser getUser(@NotNull UUID uniqueId) { 75 | return getProxy().getPlayer(uniqueId) 76 | .map(player -> new VelocityPacketUser(player)) 77 | .orElseThrow(() -> new IllegalArgumentException("The player " + uniqueId + " is not connected")); 78 | } 79 | 80 | @Override 81 | public @NotNull File getPluginFolder() { 82 | return dataDirectory.toFile(); 83 | } 84 | 85 | @Override 86 | public void log(int level, @NotNull String s) { 87 | switch (level) { 88 | case 1: 89 | getLogger().error(s); 90 | break; 91 | case 2: 92 | getLogger().warn(s); 93 | break; 94 | default: 95 | getLogger().info(s); 96 | break; 97 | } 98 | } 99 | 100 | @NotNull 101 | public static TextComponent parseComponent(@NotNull String s) { 102 | return LegacyComponentSerializer.legacyAmpersand().deserialize(s); 103 | } 104 | 105 | public static class VelocityCommand implements SimpleCommand { 106 | 107 | private static final String VERSION = "${version}"; 108 | private static final List SUGGESTIONS = List.of("reload"); 109 | 110 | @Override 111 | public void execute(Invocation invocation) { 112 | final CommandSource source = invocation.source(); 113 | if (invocation.arguments().length >= 1 && invocation.arguments()[0].equalsIgnoreCase("reload")) { 114 | final long start = System.currentTimeMillis(); 115 | OneTimePack.get().onReload(); 116 | final long time = System.currentTimeMillis() - start; 117 | source.sendMessage(parseComponent("&aPlugin successfully reloaded [&f" + time + " ms&a]")); 118 | return; 119 | } 120 | source.sendMessage(parseComponent("&a&lOneTimePack &e&lv" + VERSION)); 121 | } 122 | 123 | @Override 124 | public boolean hasPermission(Invocation invocation) { 125 | return invocation.source().hasPermission("onetimepack.use"); 126 | } 127 | 128 | @Override 129 | public List suggest(Invocation invocation) { 130 | return SUGGESTIONS; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /platform/platform-velocity/src/main/java/com/saicone/onetimepack/core/VelocityPacketUser.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import com.velocitypowered.api.proxy.Player; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.util.UUID; 8 | 9 | public class VelocityPacketUser extends PacketUser { 10 | 11 | private final Player player; 12 | 13 | public VelocityPacketUser(@NotNull Player player) { 14 | this.player = player; 15 | } 16 | 17 | @Override 18 | public @NotNull UUID getUniqueId() { 19 | return player.getUniqueId(); 20 | } 21 | 22 | @Override 23 | public int getProtocolVersion() { 24 | return player.getProtocolVersion().getProtocol(); 25 | } 26 | 27 | @Override 28 | public @Nullable String getServer() { 29 | return player.getCurrentServer().map(server -> server.getServerInfo().getName()).orElse(null); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /plugin/build.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | apply plugin: 'com.gradleup.shadow' 3 | 4 | jar { 5 | dependsOn (shadowJar) 6 | } 7 | 8 | shadowJar { 9 | archiveBaseName.set(rootProject.name + '-' + project.name) 10 | archiveClassifier.set('') 11 | destinationDirectory.set(file(rootProject.layout.buildDirectory.dir('libs'))) 12 | if (project.name != 'bungeecord-protocolize' && project.name != 'bungeecord-api') { 13 | exclude 'mappings.json' 14 | } 15 | minimize() 16 | } 17 | } -------------------------------------------------------------------------------- /plugin/bungeecord-api/build.gradle: -------------------------------------------------------------------------------- 1 | processResources { 2 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 3 | from(sourceSets.main.resources.srcDirs) { 4 | include '**/*.yml' 5 | expand(project.properties) 6 | } 7 | } 8 | 9 | repositories { 10 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } 11 | } 12 | 13 | dependencies { 14 | implementation project(':platform:platform-bungee') 15 | implementation project(':module:module-bungee') 16 | compileOnly(libs.bungeecord.api) { 17 | exclude group: 'com.mojang' 18 | } 19 | } -------------------------------------------------------------------------------- /plugin/bungeecord-api/src/main/java/com/saicone/onetimepack/BungeeBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack; 2 | 3 | import com.saicone.onetimepack.core.BungeeProcessor; 4 | import com.saicone.onetimepack.core.Processor; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public class BungeeBootstrap extends BungeePlugin { 8 | @Override 9 | protected @NotNull Processor, ?, ?> initProcessor() { 10 | return new BungeeProcessor(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /plugin/bungeecord-api/src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: OneTimePack 2 | main: com.saicone.onetimepack.BungeeBootstrap 3 | version: ${version} 4 | description: Send the same resource pack only one time 5 | author: Rubenicos -------------------------------------------------------------------------------- /plugin/bungeecord-packetevents/build.gradle: -------------------------------------------------------------------------------- 1 | processResources { 2 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 3 | from(sourceSets.main.resources.srcDirs) { 4 | include '**/*.yml' 5 | expand(project.properties) 6 | } 7 | } 8 | 9 | repositories { 10 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } 11 | maven { url 'https://repo.codemc.io/repository/maven-releases/' } 12 | } 13 | 14 | dependencies { 15 | implementation project(':platform:platform-bungee') 16 | implementation project(':module:module-packetevents') 17 | compileOnly(libs.bungeecord.api) { 18 | exclude group: 'com.mojang' 19 | } 20 | compileOnly libs.packetevents.api 21 | compileOnly libs.packetevents.bungeecord 22 | } -------------------------------------------------------------------------------- /plugin/bungeecord-packetevents/src/main/java/com/saicone/onetimepack/BungeeBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack; 2 | 3 | import com.saicone.onetimepack.core.PacketEventsProcessor; 4 | import com.saicone.onetimepack.core.Processor; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public class BungeeBootstrap extends BungeePlugin { 8 | @Override 9 | protected @NotNull Processor, ?, ?> initProcessor() { 10 | return new PacketEventsProcessor(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /plugin/bungeecord-packetevents/src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: OneTimePack 2 | main: com.saicone.onetimepack.BungeeBootstrap 3 | version: ${version} 4 | description: Send the same resource pack only one time 5 | author: Rubenicos 6 | depends: [packetevents] -------------------------------------------------------------------------------- /plugin/bungeecord-protocolize/build.gradle: -------------------------------------------------------------------------------- 1 | processResources { 2 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 3 | from(sourceSets.main.resources.srcDirs) { 4 | include '**/*.yml' 5 | expand(project.properties) 6 | } 7 | } 8 | 9 | repositories { 10 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } 11 | maven { url 'https://mvn.exceptionflug.de/repository/exceptionflug-public/' } 12 | } 13 | 14 | dependencies { 15 | implementation project(':platform:platform-bungee') 16 | implementation project(':module:module-protocolize') 17 | compileOnly(libs.bungeecord.api) { 18 | exclude group: 'com.mojang' 19 | } 20 | compileOnly libs.protocolize.api 21 | compileOnly libs.protocolize.bungeecord 22 | } -------------------------------------------------------------------------------- /plugin/bungeecord-protocolize/src/main/java/com/saicone/onetimepack/BungeeBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack; 2 | 3 | import com.saicone.onetimepack.core.MappedProtocolizeProcessor; 4 | import com.saicone.onetimepack.core.Processor; 5 | import net.md_5.bungee.protocol.packet.StartConfiguration; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class BungeeBootstrap extends BungeePlugin { 9 | @Override 10 | protected @NotNull Processor, ?, ?> initProcessor() { 11 | return new MappedProtocolizeProcessor<>(StartConfiguration.class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugin/bungeecord-protocolize/src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: OneTimePack 2 | main: com.saicone.onetimepack.BungeeBootstrap 3 | version: ${version} 4 | description: Send the same resource pack only one time 5 | author: Rubenicos 6 | depends: [Protocolize] -------------------------------------------------------------------------------- /plugin/velocity-api/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias libs.plugins.blossom 3 | } 4 | 5 | blossom { 6 | replaceTokenIn('src/main/java/com/saicone/onetimepack/VelocityBootstrap.java') 7 | replaceToken '${version}', project.version 8 | } 9 | 10 | repositories { 11 | maven { url 'https://repo.papermc.io/repository/maven-public/' } 12 | } 13 | 14 | dependencies { 15 | implementation project(':platform:platform-velocity') 16 | implementation project(':module:module-velocity') 17 | compileOnly libs.velocity.api 18 | annotationProcessor libs.velocity.api 19 | } -------------------------------------------------------------------------------- /plugin/velocity-api/src/main/java/com/saicone/onetimepack/VelocityBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack; 2 | 3 | import com.google.inject.Inject; 4 | import com.saicone.onetimepack.core.Processor; 5 | import com.saicone.onetimepack.core.VelocityProcessor; 6 | import com.velocitypowered.api.event.Subscribe; 7 | import com.velocitypowered.api.event.connection.DisconnectEvent; 8 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 9 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 10 | import com.velocitypowered.api.plugin.Plugin; 11 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 12 | import com.velocitypowered.api.proxy.ProxyServer; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.slf4j.Logger; 15 | 16 | import java.nio.file.Path; 17 | 18 | @Plugin( 19 | id = "onetimepack", 20 | name = "OneTimePack", 21 | description = "Send the same resource pack only one time", 22 | version = "${version}", 23 | authors = "Rubenicos" 24 | ) 25 | public class VelocityBootstrap extends VelocityPlugin { 26 | 27 | @Inject 28 | public VelocityBootstrap(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { 29 | super(server, logger, dataDirectory); 30 | } 31 | 32 | @Override 33 | protected @NotNull Processor, ?, ?> initProcessor() { 34 | return new VelocityProcessor(getProxy(), this); 35 | } 36 | 37 | @Subscribe 38 | @Override 39 | public void onEnable(ProxyInitializeEvent event) { 40 | super.onEnable(event); 41 | } 42 | 43 | @Subscribe 44 | @Override 45 | public void onDisable(ProxyShutdownEvent event) { 46 | super.onDisable(event); 47 | } 48 | 49 | @Subscribe 50 | @Override 51 | public void onDisconnect(DisconnectEvent event) { 52 | super.onDisconnect(event); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /plugin/velocity-packetevents/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias libs.plugins.blossom 3 | } 4 | 5 | blossom { 6 | replaceTokenIn('src/main/java/com/saicone/onetimepack/VelocityBootstrap.java') 7 | replaceToken '${version}', project.version 8 | } 9 | 10 | repositories { 11 | maven { url 'https://repo.papermc.io/repository/maven-public/' } 12 | maven { url 'https://repo.codemc.io/repository/maven-releases/' } 13 | } 14 | 15 | dependencies { 16 | implementation project(':platform:platform-velocity') 17 | implementation project(':module:module-packetevents') 18 | compileOnly libs.velocity.api 19 | annotationProcessor libs.velocity.api 20 | compileOnly libs.packetevents.api 21 | compileOnly libs.packetevents.velocity 22 | } -------------------------------------------------------------------------------- /plugin/velocity-packetevents/src/main/java/com/saicone/onetimepack/VelocityBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack; 2 | 3 | import com.google.inject.Inject; 4 | import com.saicone.onetimepack.core.PacketEventsProcessor; 5 | import com.saicone.onetimepack.core.Processor; 6 | import com.velocitypowered.api.event.Subscribe; 7 | import com.velocitypowered.api.event.connection.DisconnectEvent; 8 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 9 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 10 | import com.velocitypowered.api.plugin.Dependency; 11 | import com.velocitypowered.api.plugin.Plugin; 12 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 13 | import com.velocitypowered.api.proxy.ProxyServer; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.slf4j.Logger; 16 | 17 | import java.nio.file.Path; 18 | 19 | @Plugin( 20 | id = "onetimepack", 21 | name = "OneTimePack", 22 | description = "Send the same resource pack only one time", 23 | version = "${version}", 24 | authors = "Rubenicos", 25 | dependencies = {@Dependency(id = "packetevents")} 26 | ) 27 | public class VelocityBootstrap extends VelocityPlugin { 28 | 29 | @Inject 30 | public VelocityBootstrap(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { 31 | super(server, logger, dataDirectory); 32 | } 33 | 34 | @Override 35 | protected @NotNull Processor, ?, ?> initProcessor() { 36 | return new PacketEventsProcessor(); 37 | } 38 | 39 | @Subscribe 40 | @Override 41 | public void onEnable(ProxyInitializeEvent event) { 42 | super.onEnable(event); 43 | } 44 | 45 | @Subscribe 46 | @Override 47 | public void onDisable(ProxyShutdownEvent event) { 48 | super.onDisable(event); 49 | } 50 | 51 | @Subscribe 52 | @Override 53 | public void onDisconnect(DisconnectEvent event) { 54 | super.onDisconnect(event); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /plugin/velocity-protocolize/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias libs.plugins.blossom 3 | } 4 | 5 | blossom { 6 | replaceTokenIn('src/main/java/com/saicone/onetimepack/VelocityBootstrap.java') 7 | replaceToken '${version}', project.version 8 | } 9 | 10 | repositories { 11 | maven { url 'https://repo.papermc.io/repository/maven-public/' } 12 | maven { url 'https://mvn.exceptionflug.de/repository/exceptionflug-public/' } 13 | } 14 | 15 | dependencies { 16 | implementation project(':platform:platform-velocity') 17 | implementation project(':module:module-protocolize') 18 | compileOnly libs.velocity.api 19 | annotationProcessor libs.velocity.api 20 | compileOnly libs.protocolize.api 21 | compileOnly libs.protocolize.velocity 22 | } -------------------------------------------------------------------------------- /plugin/velocity-protocolize/src/main/java/com/saicone/onetimepack/VelocityBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack; 2 | 3 | import com.google.inject.Inject; 4 | import com.saicone.onetimepack.core.Processor; 5 | import com.saicone.onetimepack.core.VelocityProtocolizeProcessor; 6 | import com.velocitypowered.api.event.Subscribe; 7 | import com.velocitypowered.api.event.connection.DisconnectEvent; 8 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 9 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 10 | import com.velocitypowered.api.plugin.Dependency; 11 | import com.velocitypowered.api.plugin.Plugin; 12 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 13 | import com.velocitypowered.api.proxy.ProxyServer; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.slf4j.Logger; 16 | 17 | import java.nio.file.Path; 18 | 19 | @Plugin( 20 | id = "onetimepack", 21 | name = "OneTimePack", 22 | description = "Send the same resource pack only one time", 23 | version = "${version}", 24 | authors = "Rubenicos", 25 | dependencies = {@Dependency(id = "protocolize")} 26 | ) 27 | public class VelocityBootstrap extends VelocityPlugin { 28 | 29 | @Inject 30 | public VelocityBootstrap(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { 31 | super(server, logger, dataDirectory); 32 | } 33 | 34 | @Override 35 | protected @NotNull Processor, ?, ?> initProcessor() { 36 | return new VelocityProtocolizeProcessor(getProxy()); 37 | } 38 | 39 | @Subscribe 40 | @Override 41 | public void onEnable(ProxyInitializeEvent event) { 42 | super.onEnable(event); 43 | } 44 | 45 | @Subscribe 46 | @Override 47 | public void onDisable(ProxyShutdownEvent event) { 48 | super.onDisable(event); 49 | } 50 | 51 | @Subscribe 52 | @Override 53 | public void onDisconnect(DisconnectEvent event) { 54 | super.onDisconnect(event); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /plugin/velocity-protocolize/src/main/java/com/saicone/onetimepack/core/VelocityProtocolizeProcessor.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import com.saicone.onetimepack.util.ValueComparator; 4 | import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; 5 | import com.velocitypowered.api.proxy.Player; 6 | import com.velocitypowered.api.proxy.ProxyServer; 7 | import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket; 8 | import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket; 9 | import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; 10 | import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket; 11 | import dev.simplix.protocolize.api.Direction; 12 | import dev.simplix.protocolize.api.Protocol; 13 | import dev.simplix.protocolize.api.player.ProtocolizePlayer; 14 | import net.kyori.adventure.audience.Audience; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | public class VelocityProtocolizeProcessor extends ProtocolizeProcessor { 19 | 20 | private final ProxyServer proxy; 21 | 22 | public VelocityProtocolizeProcessor(@NotNull ProxyServer proxy) { 23 | super(StartUpdatePacket.class); 24 | this.proxy = proxy; 25 | } 26 | 27 | @Override 28 | protected @Nullable ValueComparator getPackValue(@NotNull String name) { 29 | return switch (name) { 30 | case "UUID" -> ResourcePackRequestPacket::getId; 31 | case "URL" -> ResourcePackRequestPacket::getUrl; 32 | case "HASH" -> ResourcePackRequestPacket::getHash; 33 | case "PROMPT" -> pack -> pack.getPrompt() == null ? null : pack.getPrompt().getComponent(); 34 | case "ALL" -> pack -> pack; 35 | case "ANY" -> pack -> true; 36 | default -> null; 37 | }; 38 | } 39 | 40 | @Override 41 | protected void registerListeners() { 42 | getPacketListener().registerReceive(ResourcePackRequestPacket.class, Direction.DOWNSTREAM, event -> { 43 | final ResourcePackRequestPacket packet = event.packet(); 44 | proxy.getPlayer(event.player().uniqueId()).ifPresent(player -> { 45 | onPackPush(event, getProtocol(player), packet.getId(), packet.getHash()); 46 | }); 47 | }); 48 | getPacketListener().registerReceive(RemoveResourcePackPacket.class, Direction.DOWNSTREAM, event -> { 49 | final RemoveResourcePackPacket packet = event.packet(); 50 | proxy.getPlayer(event.player().uniqueId()).ifPresent(player -> { 51 | event.cancelled(onPackPop(event.player(), getProtocol(player), packet, packet.getId())); 52 | }); 53 | }); 54 | getPacketListener().registerReceive(ResourcePackResponsePacket.class, Direction.UPSTREAM, event -> { 55 | final ResourcePackResponsePacket packet = event.packet(); 56 | proxy.getPlayer(event.player().uniqueId()).ifPresent(player -> { 57 | onPackStatus(event.player(), packet.getId(), PackResult.of(packet.getStatus().ordinal())); 58 | }); 59 | }); 60 | } 61 | 62 | @NotNull 63 | private Protocol getProtocol(@NotNull Player player) { 64 | return switch (player.getProtocolState()) { 65 | case HANDSHAKE -> Protocol.HANDSHAKE; 66 | case STATUS -> Protocol.STATUS; 67 | case LOGIN -> Protocol.LOGIN; 68 | case CONFIGURATION -> Protocol.CONFIGURATION; 69 | case PLAY -> Protocol.PLAY; 70 | }; 71 | } 72 | 73 | @Override 74 | protected @NotNull ResourcePackResponsePacket getStatusPacket(@NotNull Protocol protocol, @NotNull ResourcePackRequestPacket packet, @NotNull PackResult result) { 75 | if (packet.getId() == null) { 76 | return new ResourcePackResponsePacket(null, packet.getHash(), PlayerResourcePackStatusEvent.Status.values()[result.ordinal()]); 77 | } else { 78 | return new ResourcePackResponsePacket(packet.getId(), null, PlayerResourcePackStatusEvent.Status.values()[result.ordinal()]); 79 | } 80 | } 81 | 82 | @Override 83 | public void clearPackets(@NotNull ProtocolizePlayer player, @NotNull Protocol state) { 84 | proxy.getPlayer(player.uniqueId()).ifPresent(Audience::clearResourcePacks); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /plugin/velocity-vpacketevents/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias libs.plugins.blossom 3 | } 4 | 5 | blossom { 6 | replaceTokenIn('src/main/java/com/saicone/onetimepack/VelocityBootstrap.java') 7 | replaceToken '${version}', project.version 8 | } 9 | 10 | repositories { 11 | maven { url 'https://repo.papermc.io/repository/maven-public/' } 12 | } 13 | 14 | dependencies { 15 | implementation project(':platform:platform-velocity') 16 | implementation project(':module:module-vpacketevents') 17 | compileOnly libs.velocity.api 18 | annotationProcessor libs.velocity.api 19 | } -------------------------------------------------------------------------------- /plugin/velocity-vpacketevents/src/main/java/com/saicone/onetimepack/VelocityBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack; 2 | 3 | import com.google.inject.Inject; 4 | import com.saicone.onetimepack.core.Processor; 5 | import com.saicone.onetimepack.core.VPacketEventsProcessor; 6 | import com.velocitypowered.api.event.Subscribe; 7 | import com.velocitypowered.api.event.connection.DisconnectEvent; 8 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 9 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 10 | import com.velocitypowered.api.plugin.Dependency; 11 | import com.velocitypowered.api.plugin.Plugin; 12 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 13 | import com.velocitypowered.api.proxy.ProxyServer; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.slf4j.Logger; 16 | 17 | import java.nio.file.Path; 18 | 19 | @Plugin( 20 | id = "onetimepack", 21 | name = "OneTimePack", 22 | description = "Send the same resource pack only one time", 23 | version = "${version}", 24 | authors = "Rubenicos", 25 | dependencies = {@Dependency(id = "vpacketevents")} 26 | ) 27 | public class VelocityBootstrap extends VelocityPlugin { 28 | 29 | @Inject 30 | public VelocityBootstrap(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { 31 | super(server, logger, dataDirectory); 32 | } 33 | 34 | @Override 35 | protected @NotNull Processor, ?, ?> initProcessor() { 36 | return new VPacketEventsProcessor(getProxy(), this); 37 | } 38 | 39 | @Subscribe 40 | @Override 41 | public void onEnable(ProxyInitializeEvent event) { 42 | super.onEnable(event); 43 | } 44 | 45 | @Subscribe 46 | @Override 47 | public void onDisable(ProxyShutdownEvent event) { 48 | super.onDisable(event); 49 | } 50 | 51 | @Subscribe 52 | @Override 53 | public void onDisconnect(DisconnectEvent event) { 54 | super.onDisconnect(event); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'OneTimePack' 2 | 3 | // Common project 4 | include 'common' 5 | 6 | // Modules 7 | include( 8 | 'module:module-bungee', 9 | 'module:module-mappings', 10 | 'module:module-packetevents', 11 | 'module:module-protocolize', 12 | 'module:module-velocity', 13 | 'module:module-vpacketevents' 14 | ) 15 | 16 | // Platforms 17 | include( 18 | 'platform:platform-bungee', 19 | 'platform:platform-velocity' 20 | ) 21 | 22 | // Plugin types 23 | include( 24 | 'plugin:bungeecord-api', 25 | 'plugin:bungeecord-packetevents', 26 | 'plugin:bungeecord-protocolize', 27 | 'plugin:velocity-api', 28 | 'plugin:velocity-packetevents', 29 | 'plugin:velocity-protocolize', 30 | 'plugin:velocity-vpacketevents' 31 | ) --------------------------------------------------------------------------------