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

OneTimePack

2 | 3 |

Send the same resource pack only one time.

4 | 5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 | 17 | ## About OneTimePack 18 | If you have mutliple servers in a proxy with same resource pack you probably experienced the annoying situation where you already downloaded or denied the resource pack, but when you move to other server, the resource pack request window appears again. 19 | 20 | OneTimePack plugin acts like a filter in your proxy (bungeecord or velocity) server to avoid double sending the same resource pack. 21 | 22 | ![](https://i.imgur.com/VT65YA0.png) 23 | ![](https://i.imgur.com/LTE71zn.png) 24 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'com.gradleup.shadow' version libs.versions.shadow apply false 4 | } 5 | 6 | subprojects { 7 | apply plugin: 'java' 8 | apply plugin: 'idea' 9 | 10 | idea { 11 | module { 12 | downloadJavadoc = true 13 | downloadSources = true 14 | } 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | if (project.name != 'common') { 23 | implementation project(':common') 24 | } 25 | compileOnly 'org.jetbrains:annotations:26.0.2' 26 | compileOnly fileTree(dir: 'libs', includes: ['*.jar']) 27 | } 28 | 29 | compileJava { 30 | options.encoding = 'UTF-8' 31 | } 32 | 33 | java { 34 | sourceCompatibility = JavaVersion.VERSION_17 35 | targetCompatibility = JavaVersion.VERSION_17 36 | } 37 | } 38 | 39 | jar { 40 | enabled = false 41 | } -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compileOnly libs.netty.buffer 3 | compileOnly libs.gson 4 | compileOnly libs.guava 5 | } -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/OneTimePack.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.module.TinySettings; 6 | import com.saicone.onetimepack.module.TinyYaml; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.io.File; 10 | import java.util.UUID; 11 | import java.util.function.Supplier; 12 | 13 | public class OneTimePack { 14 | 15 | private static OneTimePack instance; 16 | public static TinySettings SETTINGS = new TinyYaml("settings.yml"); 17 | private static int logLevel = 2; 18 | 19 | private final Provider provider; 20 | private final Processor processor; 21 | 22 | @NotNull 23 | public static OneTimePack get() { 24 | return instance; 25 | } 26 | 27 | public static void log(int level, @NotNull String s) { 28 | if (logLevel >= level) { 29 | get().getProvider().log(level, s); 30 | } 31 | } 32 | 33 | public static void log(int level, @NotNull Supplier msg) { 34 | if (logLevel >= level) { 35 | get().getProvider().log(level, msg.get()); 36 | } 37 | } 38 | 39 | public static void log(int level, @NotNull Throwable throwable) { 40 | if (logLevel >= level) { 41 | throwable.printStackTrace(); 42 | } 43 | } 44 | 45 | public static void log(int level, @NotNull Throwable throwable, @NotNull String s) { 46 | if (logLevel >= level) { 47 | get().getProvider().log(level, s); 48 | throwable.printStackTrace(); 49 | } 50 | } 51 | 52 | public OneTimePack(@NotNull Provider provider, @NotNull Processor processor) { 53 | if (instance != null) { 54 | throw new RuntimeException(OneTimePack.class.getSimpleName() + " is already initialized"); 55 | } 56 | instance = this; 57 | this.provider = provider; 58 | this.processor = processor; 59 | } 60 | 61 | public void onLoad() { 62 | SETTINGS.load(provider.getPluginFolder()); 63 | logLevel = SETTINGS.getInt("plugin.log-level", 2); 64 | processor.load(); 65 | } 66 | 67 | public void onReload() { 68 | SETTINGS.load(provider.getPluginFolder()); 69 | logLevel = SETTINGS.getInt("plugin.log-level", 2); 70 | processor.reload(); 71 | } 72 | 73 | public void onEnable() { 74 | processor.enable(); 75 | } 76 | 77 | public void onDisable() { 78 | processor.disable(); 79 | } 80 | 81 | @NotNull 82 | public Provider getProvider() { 83 | return provider; 84 | } 85 | 86 | public @NotNull Processor getPacketHandler() { 87 | return processor; 88 | } 89 | 90 | public interface Provider { 91 | 92 | @NotNull 93 | PacketUser getUser(@NotNull UUID uniqueId); 94 | 95 | @NotNull 96 | File getPluginFolder(); 97 | 98 | void log(int level, @NotNull String s); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/core/PackBehavior.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | public enum PackBehavior { 6 | 7 | STACK, 8 | OVERRIDE; 9 | 10 | @NotNull 11 | public static PackBehavior of(@NotNull String s) { 12 | if (s.equalsIgnoreCase("OVERRIDE")) { 13 | return OVERRIDE; 14 | } else { 15 | return STACK; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/core/PackResult.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import org.jetbrains.annotations.Contract; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | public enum PackResult { 8 | 9 | SUCCESS_DOWNLOAD, 10 | DECLINED, 11 | FAILED_DOWNLOAD, 12 | ACCEPTED, 13 | DOWNLOADED(0), 14 | INVALID_URL(2), 15 | FAILED_RELOAD(2), 16 | DISCARDED(1); 17 | 18 | public static final PackResult[] VALUES = values(); 19 | 20 | private final int fallback; 21 | 22 | PackResult() { 23 | this.fallback = 0; 24 | } 25 | 26 | PackResult(int fallback) { 27 | this.fallback = fallback; 28 | } 29 | 30 | public int getFallback() { 31 | return fallback; 32 | } 33 | 34 | @NotNull 35 | public static > PackResult from(@NotNull E value) { 36 | return from(value, SUCCESS_DOWNLOAD); 37 | } 38 | 39 | @Nullable 40 | @Contract("_, !null -> !null") 41 | public static > PackResult from(@NotNull E value, @Nullable PackResult def) { 42 | return of(value.ordinal(), def); 43 | } 44 | 45 | @NotNull 46 | public static PackResult of(int ordinal) { 47 | return of(ordinal, SUCCESS_DOWNLOAD); 48 | } 49 | 50 | @Nullable 51 | @Contract("_, !null -> !null") 52 | public static PackResult of(int ordinal, @Nullable PackResult def) { 53 | if (ordinal < VALUES.length) { 54 | return VALUES[ordinal]; 55 | } 56 | return def; 57 | } 58 | 59 | @NotNull 60 | public static PackResult of(@NotNull String s) { 61 | return of(s, SUCCESS_DOWNLOAD); 62 | } 63 | 64 | @Nullable 65 | @Contract("_, !null -> !null") 66 | public static PackResult of(@NotNull String s, @Nullable PackResult def) { 67 | if (s.equalsIgnoreCase("none")) { 68 | return def; 69 | } 70 | for (PackResult value : VALUES) { 71 | if (value.name().equalsIgnoreCase(s)) { 72 | return value; 73 | } 74 | } 75 | return def; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/core/PacketUser.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import com.google.common.base.Suppliers; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.util.HashMap; 8 | import java.util.LinkedHashMap; 9 | import java.util.Map; 10 | import java.util.UUID; 11 | import java.util.function.Supplier; 12 | 13 | public abstract class PacketUser { 14 | 15 | private static final UUID DUMMY_ID = new UUID(0, 0); 16 | private static final int MINECRAFT_1_20_3 = 765; 17 | 18 | private final transient Supplier uniquePack = Suppliers.memoize(() -> getProtocolVersion() < MINECRAFT_1_20_3); 19 | 20 | private final Map cachedPacks = new LinkedHashMap<>(); 21 | private final Map cachedResults = new HashMap<>(); 22 | 23 | public boolean isUniquePack() { 24 | return uniquePack.get(); 25 | } 26 | 27 | @NotNull 28 | public abstract UUID getUniqueId(); 29 | 30 | public abstract int getProtocolVersion(); 31 | 32 | @Nullable 33 | public abstract String getServer(); 34 | 35 | @Nullable 36 | public PackT getPack() { 37 | if (cachedPacks.isEmpty()) { 38 | return null; 39 | } 40 | if (isUniquePack()) { 41 | return cachedPacks.get(DUMMY_ID); 42 | } 43 | return cachedPacks.entrySet().iterator().next().getValue(); 44 | } 45 | 46 | @NotNull 47 | public Map getPacks() { 48 | return cachedPacks; 49 | } 50 | 51 | @Nullable 52 | public PackResult getResult() { 53 | if (cachedResults.isEmpty()) { 54 | return null; 55 | } 56 | if (isUniquePack()) { 57 | return cachedResults.get(DUMMY_ID); 58 | } 59 | return cachedResults.entrySet().iterator().next().getValue(); 60 | } 61 | 62 | @Nullable 63 | public PackResult getResult(@Nullable UUID uniqueId) { 64 | if (isUniquePack()) { 65 | return cachedResults.get(DUMMY_ID); 66 | } 67 | return cachedResults.get(uniqueId); 68 | } 69 | 70 | @Nullable 71 | public PackResult getResult(@NotNull UUID id, @NotNull ProtocolOptions options) { 72 | return cachedResults.getOrDefault(id, options.getDefaultStatus()); 73 | } 74 | 75 | @NotNull 76 | public Map getResults() { 77 | return cachedResults; 78 | } 79 | 80 | @Nullable 81 | public UUID contains(@NotNull PackT packet, @NotNull ProtocolOptions options) { 82 | for (Map.Entry entry : cachedPacks.entrySet()) { 83 | if (options.getComparator().matches(entry.getValue(), packet)) { 84 | return entry.getKey(); 85 | } 86 | } 87 | return null; 88 | } 89 | 90 | public void putPack(@Nullable UUID id, @NotNull PackT packet) { 91 | if (id == null || isUniquePack()) { 92 | cachedPacks.put(DUMMY_ID, packet); 93 | } else { 94 | cachedPacks.put(id, packet); 95 | } 96 | } 97 | 98 | public > void putResult(@Nullable UUID id, @NotNull E result) { 99 | if (id == null || isUniquePack()) { 100 | cachedResults.put(DUMMY_ID, PackResult.from(result)); 101 | } else { 102 | cachedResults.put(id, PackResult.from(result)); 103 | } 104 | } 105 | 106 | public void putResult(@Nullable UUID id, @NotNull PackResult result) { 107 | if (id == null || isUniquePack()) { 108 | cachedResults.put(DUMMY_ID, result); 109 | } else { 110 | cachedResults.put(id, result); 111 | } 112 | } 113 | 114 | public void removePack() { 115 | cachedPacks.clear(); 116 | } 117 | 118 | public void removePack(@Nullable UUID id) { 119 | if (id == null || isUniquePack()) { 120 | cachedPacks.remove(DUMMY_ID); 121 | } else { 122 | cachedPacks.remove(id); 123 | } 124 | } 125 | 126 | public void handleResult(@Nullable UUID id) { 127 | if (id == null || isUniquePack()) { 128 | cachedPacks.clear(); 129 | } else { 130 | cachedPacks.remove(id); 131 | } 132 | } 133 | 134 | public void clear() { 135 | cachedPacks.clear(); 136 | cachedResults.clear(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/core/ProtocolOptions.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import com.saicone.onetimepack.module.TinySettings; 5 | import com.saicone.onetimepack.util.ValueComparator; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | public class ProtocolOptions { 10 | 11 | private final boolean enabled; 12 | private final ValueComparator comparator; 13 | private final PackResult defaultStatus; 14 | private final PackBehavior behavior; 15 | private final boolean send; 16 | private final boolean clear; 17 | private final boolean remove; 18 | private final int minProtocol; 19 | 20 | @NotNull 21 | public static ProtocolOptions valueOf(@NotNull ProtocolState state, @NotNull ValueComparator.Provider provider) { 22 | return valueOf(new String[] { 23 | "protocol." + state.name().toLowerCase() + ".", 24 | "protocol.default." 25 | }, provider); 26 | } 27 | 28 | @NotNull 29 | public static ProtocolOptions valueOf(@NotNull String group, @NotNull ProtocolState state, @NotNull ValueComparator.Provider provider) { 30 | return valueOf(new String[] { 31 | "group." + group + ".protocol." + state.name().toLowerCase() + ".", 32 | "group." + group + ".protocol.default.", 33 | "protocol." + state.name().toLowerCase() + ".", 34 | "protocol.default." 35 | }, provider); 36 | } 37 | 38 | @NotNull 39 | public static ProtocolOptions valueOf(@NotNull String[] paths, @NotNull ValueComparator.Provider provider) { 40 | final TinySettings config = OneTimePack.SETTINGS; 41 | return new ProtocolOptions<>( 42 | config.getRecursively(paths, (section, path) -> section.getBoolean(path + "enabled"), true), 43 | ValueComparator.read(config.getRecursively(paths, (section, path) -> section.getString(path + "comparator"), "!UUID OR !HASH OR URL"), provider), 44 | PackResult.of(config.getRecursively(paths, (section, path) -> section.getString(path + "default-status"), "none"), null), 45 | PackBehavior.of(config.getRecursively(paths, (section, path) -> section.getString(path + "behavior"), "OVERRIDE")), 46 | config.getRecursively(paths, (section, path) -> section.getBoolean(path + "send"), false), 47 | config.getRecursively(paths, (section, path) -> section.getBoolean(path + "clear"), false), 48 | config.getRecursively(paths, (section, path) -> section.getBoolean(path + "remove"), false), 49 | config.getRecursively(paths, (section, path) -> section.getInt(path + "min-protocol"), -1) 50 | ); 51 | } 52 | 53 | public ProtocolOptions(boolean enabled, @NotNull ValueComparator comparator, @Nullable PackResult defaultStatus, @NotNull PackBehavior behavior, boolean send, boolean clear, boolean remove, int minProtocol) { 54 | this.enabled = enabled; 55 | this.comparator = comparator; 56 | this.defaultStatus = defaultStatus; 57 | this.behavior = behavior; 58 | this.send = send; 59 | this.clear = clear; 60 | this.remove = remove; 61 | this.minProtocol = minProtocol; 62 | } 63 | 64 | @NotNull 65 | public ValueComparator getComparator() { 66 | return comparator; 67 | } 68 | 69 | @Nullable 70 | public PackResult getDefaultStatus() { 71 | return defaultStatus; 72 | } 73 | 74 | @NotNull 75 | public PackBehavior getBehavior() { 76 | return behavior; 77 | } 78 | 79 | public int getMinProtocol() { 80 | return minProtocol; 81 | } 82 | 83 | public boolean isEnabled() { 84 | return enabled; 85 | } 86 | 87 | public boolean sendDuplicated() { 88 | return send; 89 | } 90 | 91 | public boolean allowClear() { 92 | return clear; 93 | } 94 | 95 | public boolean allowRemove() { 96 | return remove; 97 | } 98 | } -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/core/ProtocolState.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | public enum ProtocolState { 7 | 8 | HANDSHAKING, 9 | STATUS, 10 | LOGIN, 11 | PLAY, 12 | CONFIGURATION; 13 | 14 | private static final ProtocolState[] VALUES = values(); 15 | 16 | @Nullable 17 | public static ProtocolState of(@NotNull String s) { 18 | for (ProtocolState state : VALUES) { 19 | if (state.name().equalsIgnoreCase(s)) { 20 | return state; 21 | } 22 | } 23 | return null; 24 | } 25 | 26 | @NotNull 27 | public static ProtocolState of(@NotNull Enum e) { 28 | return VALUES[e.ordinal()]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/core/ServerGroup.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.core; 2 | 3 | import com.saicone.onetimepack.OneTimePack; 4 | import com.saicone.onetimepack.module.TinySettings; 5 | import com.saicone.onetimepack.util.ValueComparator; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.HashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | public class ServerGroup { 13 | 14 | private final String id; 15 | private final Set servers; 16 | private final Map> protocols; 17 | 18 | @NotNull 19 | public static ServerGroup valueOf(@NotNull String id, @NotNull ValueComparator.Provider provider) { 20 | final TinySettings config = OneTimePack.SETTINGS; 21 | return new ServerGroup<>( 22 | id, 23 | new HashSet<>(config.getStringList("group." + id + ".servers")), 24 | Map.of( 25 | ProtocolState.PLAY, ProtocolOptions.valueOf(id, ProtocolState.PLAY, provider), 26 | ProtocolState.CONFIGURATION, ProtocolOptions.valueOf(id, ProtocolState.CONFIGURATION, provider) 27 | ) 28 | ); 29 | } 30 | 31 | public ServerGroup(@NotNull String id, @NotNull Set servers, @NotNull Map> protocols) { 32 | this.id = id; 33 | this.servers = servers; 34 | this.protocols = protocols; 35 | } 36 | 37 | @NotNull 38 | public String getId() { 39 | return id; 40 | } 41 | 42 | @NotNull 43 | public Set getServers() { 44 | return servers; 45 | } 46 | 47 | @NotNull 48 | public ProtocolOptions getOptions(@NotNull ProtocolState state) { 49 | return protocols.get(state); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /common/src/main/java/com/saicone/onetimepack/module/TinySettings.java: -------------------------------------------------------------------------------- 1 | package com.saicone.onetimepack.module; 2 | 3 | import com.saicone.onetimepack.util.FileUtils; 4 | import org.jetbrains.annotations.Contract; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.BufferedWriter; 10 | import java.io.File; 11 | import java.io.FileReader; 12 | import java.io.FileWriter; 13 | import java.io.IOException; 14 | import java.io.Reader; 15 | import java.io.Writer; 16 | import java.lang.reflect.Array; 17 | import java.util.ArrayList; 18 | import java.util.LinkedHashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Set; 22 | import java.util.function.BiFunction; 23 | import java.util.function.Consumer; 24 | import java.util.function.Function; 25 | 26 | public abstract class TinySettings { 27 | 28 | private final String fileName; 29 | private final Map data; 30 | 31 | public TinySettings(@NotNull String fileName) { 32 | this(fileName, new LinkedHashMap<>()); 33 | } 34 | 35 | public TinySettings(@NotNull String fileName, @NotNull Map data) { 36 | this.fileName = fileName; 37 | this.data = data; 38 | } 39 | 40 | @NotNull 41 | public String getFileName() { 42 | return fileName; 43 | } 44 | 45 | @NotNull 46 | public Map getData() { 47 | return data; 48 | } 49 | 50 | @Nullable 51 | public Object get(@NotNull String path) { 52 | return get(path.toLowerCase().split("\\.")); 53 | } 54 | 55 | @Nullable 56 | @SuppressWarnings("unchecked") 57 | public Object get(@NotNull String[] path) { 58 | if (path.length < 1) { 59 | return null; 60 | } 61 | if (path.length == 1) { 62 | return data.get(path[0]); 63 | } 64 | Map map = data; 65 | for (int i = 0; i < path.length; i++) { 66 | if (i + 1 >= path.length) { 67 | break; 68 | } 69 | final Object object = map.get(path[i]); 70 | if (object instanceof Map) { 71 | map = (Map) object; 72 | } else { 73 | return null; 74 | } 75 | } 76 | return map.get(path[path.length - 1]); 77 | } 78 | 79 | @Nullable 80 | @Contract("_, !null, _ -> !null") 81 | public T get(@NotNull String path, @Nullable T def, @NotNull Function parser) { 82 | return get(path.toLowerCase().split("\\."), def, parser); 83 | } 84 | 85 | @Nullable 86 | @Contract("_, !null, _ -> !null") 87 | public T get(@NotNull String[] path, @Nullable T def, @NotNull Function parser) { 88 | final Object object = get(path); 89 | final T parsed = object == null ? null : parser.apply(object); 90 | return parsed == null ? def : parsed; 91 | } 92 | 93 | @Nullable 94 | @Contract("_, _, !null -> !null") 95 | public 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 clazz, @NotNull Supplier 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 clazz, @NotNull Supplier 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 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 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 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 | ) --------------------------------------------------------------------------------