From e36285f20c60b2068dcf44f82c9b70fe2289f0bb Mon Sep 17 00:00:00 2001 From: EOT3000 <43685885+EOT3000@users.noreply.github.com> Date: Mon, 13 Apr 2020 18:16:12 -0400 Subject: [PATCH] send resource packs A lot of this code is nukkit-credits in the classes --- .../geysermc/connector/GeyserConnector.java | 6 +- .../network/UpstreamPacketHandler.java | 73 +++++++++++-- .../network/session/GeyserSession.java | 2 +- .../translators/block/BlockTranslator.java | 3 + .../network/translators/forge/Blocks.java | 40 +++++++ .../geysermc/connector/utils/FileUtils.java | 7 ++ .../geysermc/connector/utils/MathUtils.java | 11 ++ .../connector/utils/ResourcePack.java | 102 ++++++++++++++++++ .../connector/utils/ResourcePackManifest.java | 88 +++++++++++++++ .../org/geysermc/connector/utils/Toolbox.java | 1 + 10 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/forge/Blocks.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index fb93a27d..f88639c7 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -42,6 +42,7 @@ import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.thread.PingPassthroughThread; +import org.geysermc.connector.utils.ResourcePack; import org.geysermc.connector.utils.Toolbox; import org.geysermc.common.IGeyserConfiguration; @@ -60,6 +61,8 @@ public class GeyserConnector { public static final String NAME = "Geyser"; public static final String VERSION = "1.0-SNAPSHOT"; + //Change this on every game version + public static final String GAME_VERSION = "1.14.0"; private final Map players = new HashMap<>(); @@ -103,8 +106,9 @@ public class GeyserConnector { logger.setDebug(config.isDebugMode()); - Toolbox.init(); Translators.start(); + Toolbox.init(); + ResourcePack.loadPacks(); commandMap = new GeyserCommandMap(this); remoteServer = new RemoteServer(config.getRemote().getAddress(), config.getRemote().getPort()); diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 6fffcda8..8e3eef08 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -31,7 +31,11 @@ import org.geysermc.common.IGeyserConfiguration; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.Registry; -import org.geysermc.connector.utils.LoginEncryptionUtils; +import org.geysermc.connector.utils.*; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.UUID; public class UpstreamPacketHandler extends LoggingPacketHandler { @@ -57,24 +61,55 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.getUpstream().sendPacket(playStatus); ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); + for(ResourcePack resourcePack : ResourcePack.PACKS.values()) { + ResourcePackManifest.Header header = resourcePack.getManifest().getHeader(); + String version = header.getVersion()[0] + "." + header.getVersion()[1] + "." + header.getVersion()[2]; + resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(header.getUuid().toString(), version, resourcePack.getFile().length(), "", "", "", false)); + } + resourcePacksInfo.setForcedToAccept(true); session.getUpstream().sendPacket(resourcePacksInfo); return true; } @Override public boolean handle(ResourcePackClientResponsePacket packet) { + System.out.println(packet.getStatus()); switch (packet.getStatus()) { case COMPLETED: session.connect(connector.getRemoteServer()); connector.getLogger().info("Player connected with username " + session.getAuthData().getName()); break; - case HAVE_ALL_PACKS: - ResourcePackStackPacket stack = new ResourcePackStackPacket(); - stack.setExperimental(false); - stack.setForcedToAccept(false); - stack.setGameVersion("*"); - session.getUpstream().sendPacket(stack); + + case SEND_PACKS: + for(String id : packet.getPackIds()) { + ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket(); + ResourcePack pack = ResourcePack.PACKS.get(id.split("_")[0]); + ResourcePackManifest.Header header = pack.getManifest().getHeader(); + + data.setPackId(header.getUuid()); + data.setChunkCount(pack.getFile().length()/ResourcePack.CHUNK_SIZE); + data.setCompressedPackSize(pack.getFile().length()); + data.setMaxChunkSize(ResourcePack.CHUNK_SIZE); + data.setHash(pack.getSha256()); + + session.getUpstream().sendPacket(data); + } break; + + case HAVE_ALL_PACKS: + ResourcePackStackPacket stackPacket = new ResourcePackStackPacket(); + + stackPacket.setExperimental(false); + stackPacket.setForcedToAccept(true); + stackPacket.setGameVersion(GeyserConnector.GAME_VERSION); + for(ResourcePack pack : ResourcePack.PACKS.values()) { + ResourcePackManifest.Header header = pack.getManifest().getHeader(); + String version = header.getVersion()[0] + "." + header.getVersion()[1] + "." + header.getVersion()[2]; + stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), version, "")); + } + session.getUpstream().sendPacket(stackPacket); + break; + default: session.getUpstream().disconnect("disconnectionScreen.resourcePack"); break; @@ -126,4 +161,28 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { boolean defaultHandler(BedrockPacket packet) { return translateAndDefault(packet); } + + @Override + public boolean handle(ResourcePackChunkRequestPacket packet) { + ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket(); + ResourcePack pack = ResourcePack.PACKS.get(data.getPackId().toString()); + + data.setChunkIndex(packet.getChunkIndex()); + data.setProgress(packet.getChunkIndex()*ResourcePack.CHUNK_SIZE); + data.setPackVersion(packet.getPackVersion()); + data.setPackId(packet.getPackId()); + byte[] packData = new byte[(int) MathUtils.constrain(pack.getFile().length(), 0, ResourcePack.CHUNK_SIZE)]; + + try (InputStream inputStream = new FileInputStream(pack.getFile())) { + int offset = packet.getChunkIndex()*ResourcePack.CHUNK_SIZE; + + inputStream.read(packData, offset, packData.length); + } catch (Exception e) { + e.printStackTrace(); + } + data.setData(packData); + + session.getUpstream().sendPacket(data); + return true; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index a2d8a726..ceab8555 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -400,6 +400,6 @@ public class GeyserSession implements CommandSender { startGamePacket.setItemEntries(Toolbox.ITEMS); startGamePacket.setVanillaVersion("*"); // startGamePacket.setMovementServerAuthoritative(true); - upstream.sendPacket(startGamePacket); + upstream.sendPacketImmediately(startGamePacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java index c782e099..92be5c56 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java @@ -41,8 +41,10 @@ import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.geysermc.connector.GeyserConnector; +//import org.geysermc.connector.network.translators.forge.Blocks; import org.geysermc.connector.utils.Toolbox; +import java.io.File; import java.io.InputStream; import java.util.*; @@ -64,6 +66,7 @@ public class BlockTranslator { ListTag blocksTag; try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { blocksTag = (ListTag) nbtInputStream.readTag(); + //Blocks.registerBlocks(new File("mods")); } catch (Exception e) { throw new AssertionError("Unable to get blocks from runtime block states", e); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/forge/Blocks.java b/connector/src/main/java/org/geysermc/connector/network/translators/forge/Blocks.java new file mode 100644 index 00000000..f74ceb1f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/forge/Blocks.java @@ -0,0 +1,40 @@ +/*package org.geysermc.connector.network.translators.forge; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.File; +import java.io.InputStream; +import java.util.Map; +import java.util.zip.ZipFile; + +public class Blocks { + public static final ObjectMapper JSON_MAPPER = new ObjectMapper().enable(JsonParser.Feature.ALLOW_COMMENTS); + + public static void registerBlocks(File directory) throws Exception { + for(File file : directory.listFiles()) { + ZipFile zip = new ZipFile(file); + + zip.stream().forEach((x) -> { + if(x.getName().contains("blockstates") && x.getName().endsWith(".json")) { + try { + System.out.println(x.getName()); + InputStream stream = zip.getInputStream(x); + + TypeReference> type = new TypeReference>() {}; + + registerBlock(JSON_MAPPER.readValue(stream, type)); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + } + + public static void registerBlock(Map map) { + System.out.println(map); + } +}*/ diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java index 3070e743..22248db3 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -25,6 +25,8 @@ package org.geysermc.connector.utils; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; @@ -40,6 +42,11 @@ public class FileUtils { return objectMapper.readValue(src, valueType); } + public static T loadJson(InputStream src, Class valueType) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()).enable(JsonParser.Feature.IGNORE_UNDEFINED).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + return objectMapper.readValue(src, valueType); + } + public static File fileOrCopiedFromResource(String name, Function s) throws IOException { return fileOrCopiedFromResource(new File(name), name, s); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java index 79f8c274..272dadd8 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java @@ -31,4 +31,15 @@ public class MathUtils { int truncated = (int) floatNumber; return floatNumber > truncated ? truncated + 1 : truncated; } + + public static double constrain(double num, double min, double max) { + if(num > max) { + num = max; + } + if(num < min) { + num = min; + } + + return num; + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java new file mode 100644 index 00000000..74697526 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java @@ -0,0 +1,102 @@ +package org.geysermc.connector.utils; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.voxelwind.server.jni.hash.JavaHash; +import com.voxelwind.server.jni.hash.NativeHash; +import com.voxelwind.server.jni.hash.VoxelwindHash; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.PooledByteBufAllocator; +import net.md_5.bungee.jni.NativeCode; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.floodgate.util.EncryptionUtil; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipFile; + +public class ResourcePack { + public static final Map PACKS = new HashMap<>(); + public static final NativeCode HASH = new NativeCode<>("native-hash", JavaHash.class, NativeHash.class); + public static final int CHUNK_SIZE = 1048576; + + private boolean hashed; + private byte[] sha256; + private File file; + private ResourcePackManifest manifest; + private ResourcePackManifest.Version version; + + public static void loadPacks() { + File directory = new File("packs"); + + for(File file : directory.listFiles()) { + try { + ZipFile zip = new ZipFile(file); + + zip.stream().forEach((x) -> { + if(x.getName().contains("manifest.json")) { + try { + ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class); + + ResourcePack pack = new ResourcePack(); + + pack.file = file; + pack.manifest = manifest; + pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion()); + + PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().error(file.getName() + " " + "is broken!"); + e.printStackTrace(); + } + } + } + + /** + * author: NukkitX + * Nukkit Project + */ + //TODO: calculate this separately + public byte[] getSha256() { + if (!hashed) { + VoxelwindHash hash = HASH.newInstance(); + ByteBuf bytes = null; + try { + bytes = PooledByteBufAllocator.DEFAULT.directBuffer(Math.toIntExact(Files.size(file.toPath()))); // Hopefully there is not a resource pack big enough to need a long... + bytes.writeBytes(Files.readAllBytes(file.toPath())); + hash.update(bytes); + sha256 = hash.digest(); + } catch (Exception e) { + throw new RuntimeException("Could not calculate pack hash", e); + } finally { + if (bytes != null) { + bytes.release(); + } + } + } + return sha256; + } + + public File getFile() { + return file; + } + + public ResourcePackManifest getManifest() { + return manifest; + } + + public ResourcePackManifest.Version getVersion() { + return version; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java b/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java new file mode 100644 index 00000000..9f6c1d5f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java @@ -0,0 +1,88 @@ +package org.geysermc.connector.utils; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import lombok.Value; + +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; + +/** + * author: NukkitX + * Nukkit Project + */ +@Getter +@EqualsAndHashCode +public class ResourcePackManifest { + @JsonProperty("format_version") + private Integer formatVersion; + private Header header; + private Collection modules; + protected Collection dependencies; + + public Collection getModules() { + return Collections.unmodifiableCollection(modules); + } + + @Getter + @ToString + public static class Header { + private String description; + private String name; + private UUID uuid; + private int[] version; + @JsonProperty("min_engine_version") + private int[] minimumSupportedMinecraftVersion; + } + + @Getter + @ToString + public static class Module { + private String description; + private String name; + private UUID uuid; + private int[] version; + } + + @Getter + @ToString + public static class Dependency { + private UUID uuid; + private int[] version; + } + + @Value + public static class Version { + private final int major; + private final int minor; + private final int patch; + + public static Version fromString(String ver) { + String[] split = ver.replace(']', ' ') + .replace('[', ' ') + .replaceAll(" ", "").split(","); + + return new Version(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2])); + } + + public static Version fromArray(int[] ver) { + return new Version(ver[0], ver[1], ver[2]); + } + + private Version(int major, int minor, int patch) { + this.major = major; + this.minor = minor; + this.patch = patch; + } + + + @Override + public String toString() { + return major + "." + minor + "." + patch; + } + } +} + diff --git a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java index 45802196..562fb3c5 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java @@ -65,6 +65,7 @@ public class Toolbox { biomesTag = (CompoundTag) biomenbtInputStream.readTag(); BIOMES = biomesTag; } catch (Exception ex) { + ex.printStackTrace(); GeyserConnector.getInstance().getLogger().warning("Failed to get biomes from biome definitions, is there something wrong with the file?"); throw new AssertionError(ex); }