From eb634ce3f47587b2d99f8bb758548306b2b615f8 Mon Sep 17 00:00:00 2001 From: brokiem Date: Wed, 16 Jun 2021 15:22:44 +0800 Subject: [PATCH] add files --- .gitignore | 37 ++ README.md | 14 + pom.xml | 96 ++++++ src/main/java/org/barrelmc/barrel/Barrel.java | 24 ++ .../org/barrelmc/barrel/auth/JoseStuff.java | 66 ++++ .../org/barrelmc/barrel/config/Config.java | 36 ++ .../org/barrelmc/barrel/math/Vector3.java | 60 ++++ .../barrel/network/BedrockBatchHandler.java | 31 ++ .../barrel/network/JavaPacketHandler.java | 40 +++ .../network/translator/PacketTranslator.java | 324 ++++++++++++++++++ .../network/translator/TranslatorUtils.java | 21 ++ .../org/barrelmc/barrel/player/Player.java | 218 ++++++++++++ .../barrelmc/barrel/server/ProxyServer.java | 195 +++++++++++ .../barrelmc/barrel/utils/FileManager.java | 21 ++ 14 files changed, 1183 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/org/barrelmc/barrel/Barrel.java create mode 100644 src/main/java/org/barrelmc/barrel/auth/JoseStuff.java create mode 100644 src/main/java/org/barrelmc/barrel/config/Config.java create mode 100644 src/main/java/org/barrelmc/barrel/math/Vector3.java create mode 100644 src/main/java/org/barrelmc/barrel/network/BedrockBatchHandler.java create mode 100644 src/main/java/org/barrelmc/barrel/network/JavaPacketHandler.java create mode 100644 src/main/java/org/barrelmc/barrel/network/translator/PacketTranslator.java create mode 100644 src/main/java/org/barrelmc/barrel/network/translator/TranslatorUtils.java create mode 100644 src/main/java/org/barrelmc/barrel/player/Player.java create mode 100644 src/main/java/org/barrelmc/barrel/server/ProxyServer.java create mode 100644 src/main/java/org/barrelmc/barrel/utils/FileManager.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe04b98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# Eclipse +.classpath +.project +.settings/ + +# Intellij +.idea/ +*.iml +*.iws + +# Mac +.DS_Store + +# Maven +log/ +target/ + +# Inclusions +!lib/** + +# Compiled Files +*.class +bin/ +build/ +/bin1/ +java/build/** \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b3c447 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Barrel Proxy + +(WIP) A proxy to connect to Minecraft: Bedrock Edition servers with Minecraft: Java edition written in Java + +## Need implemented + +- Chunks +- Inventory +- Xbox Auth +- And More... + +## Credits + +- [EZ4H](https://github.com/Project-EZ4H/EZ4H) (Archived) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..230160f --- /dev/null +++ b/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + org.barrelmc.barrel + Barrel + 1.0.0-SNAPSHOT + + + + nukkitx-repo-release + https://repo.nukkitx.com/maven-releases/ + + + nukkitx-repo-snapshot + https://repo.nukkitx.com/maven-snapshots/ + + + jitpack.io + https://jitpack.io + + + + + + com.nukkitx.protocol + bedrock-v440 + 2.8.0-SNAPSHOT + compile + + + com.github.Steveice10 + MCProtocolLib + 1.16.5-2 + + + + org.yaml + snakeyaml + 1.28 + + + com.alibaba + fastjson + 1.2.47 + + + org.projectlombok + lombok + RELEASE + compile + + + com.auth0 + java-jwt + 3.16.0 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + + org.barrelmc.barrel.Barrel + + + + + + package + + shade + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + UTF-8 + + + + + \ No newline at end of file diff --git a/src/main/java/org/barrelmc/barrel/Barrel.java b/src/main/java/org/barrelmc/barrel/Barrel.java new file mode 100644 index 0000000..adfba4f --- /dev/null +++ b/src/main/java/org/barrelmc/barrel/Barrel.java @@ -0,0 +1,24 @@ +/* + * ____ _ + * | __ ) __ _ _ __ _ __ ___ | | + * | _ \ / _` | | '__| | '__| / _ \ | | + * | |_) | | (_| | | | | | | __/ | | + * |____/ \__,_| |_| |_| \___| |_| + * + * Copyright (c) 2021 BarrelMC + * BarrelMC/Barrel is licensed under the MIT License + */ + +package org.barrelmc.barrel; + +import org.barrelmc.barrel.server.ProxyServer; + +public class Barrel { + + public static String DATA_PATH = System.getProperty("user.dir") + "/"; + + public static void main(String[] args) { + System.out.println("Starting Barrel Proxy software"); + new ProxyServer(DATA_PATH); + } +} diff --git a/src/main/java/org/barrelmc/barrel/auth/JoseStuff.java b/src/main/java/org/barrelmc/barrel/auth/JoseStuff.java new file mode 100644 index 0000000..fffe1ab --- /dev/null +++ b/src/main/java/org/barrelmc/barrel/auth/JoseStuff.java @@ -0,0 +1,66 @@ +package org.barrelmc.barrel.auth; + +import java.security.SignatureException; + +// https://github.com/Project-EZ4H/EZ4H/blob/main/src/main/java/me/liuli/ez4h/minecraft/auth/JoseStuff.java +public class JoseStuff { + + public static byte[] DERToJOSE(byte[] derSignature, AlgorithmType algorithmType) throws SignatureException { + // DER Structure: http://crypto.stackexchange.com/a/1797 + boolean derEncoded = derSignature[0] == 0x30 && derSignature.length != algorithmType.ecNumberSize * 2; + if (!derEncoded) { + throw new SignatureException("Invalid DER signature format."); + } + + final byte[] joseSignature = new byte[algorithmType.ecNumberSize * 2]; + + //Skip 0x30 + int offset = 1; + if (derSignature[1] == (byte) 0x81) { + //Skip sign + offset++; + } + + //Convert to unsigned. Should match DER length - offset + int encodedLength = derSignature[offset++] & 0xff; + if (encodedLength != derSignature.length - offset) { + throw new SignatureException("Invalid DER signature format."); + } + + //Skip 0x02 + offset++; + + //Obtain R number length (Includes padding) and skip it + int rLength = derSignature[offset++]; + if (rLength > algorithmType.ecNumberSize + 1) { + throw new SignatureException("Invalid DER signature format."); + } + int rPadding = algorithmType.ecNumberSize - rLength; + //Retrieve R number + System.arraycopy(derSignature, offset + Math.max(-rPadding, 0), joseSignature, Math.max(rPadding, 0), rLength + Math.min(rPadding, 0)); + + //Skip R number and 0x02 + offset += rLength + 1; + + //Obtain S number length. (Includes padding) + int sLength = derSignature[offset++]; + if (sLength > algorithmType.ecNumberSize + 1) { + throw new SignatureException("Invalid DER signature format."); + } + int sPadding = algorithmType.ecNumberSize - sLength; + //Retrieve R number + System.arraycopy(derSignature, offset + Math.max(-sPadding, 0), joseSignature, algorithmType.ecNumberSize + Math.max(sPadding, 0), sLength + Math.min(sPadding, 0)); + + return joseSignature; + } + + public enum AlgorithmType { + ECDSA256(32), ECDSA384(48); + + public int ecNumberSize; + + AlgorithmType(int ecNumberSize) { + this.ecNumberSize = ecNumberSize; + } + } +} diff --git a/src/main/java/org/barrelmc/barrel/config/Config.java b/src/main/java/org/barrelmc/barrel/config/Config.java new file mode 100644 index 0000000..e218b59 --- /dev/null +++ b/src/main/java/org/barrelmc/barrel/config/Config.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 BarrelMC + * BarrelMC/Barrel is licensed under the MIT License + */ + +package org.barrelmc.barrel.config; + +import lombok.Getter; +import lombok.Setter; + +public class Config { + + @Setter + @Getter + public String bindAddress; + + @Setter + @Getter + public Integer port; + + @Setter + @Getter + public String motd; + + @Setter + @Getter + public String bedrockAddress; + + @Setter + @Getter + public Integer bedrockPort; + + @Setter + @Getter + public String auth; +} diff --git a/src/main/java/org/barrelmc/barrel/math/Vector3.java b/src/main/java/org/barrelmc/barrel/math/Vector3.java new file mode 100644 index 0000000..1adaf62 --- /dev/null +++ b/src/main/java/org/barrelmc/barrel/math/Vector3.java @@ -0,0 +1,60 @@ +package org.barrelmc.barrel.math; + +import com.nukkitx.math.GenericMath; +import com.nukkitx.math.vector.Vector3f; +import lombok.Getter; +import lombok.Setter; + +public class Vector3 { + + @Setter + @Getter + public double x; + @Setter + @Getter + public double y; + @Setter + @Getter + public double z; + @Setter + @Getter + public float yaw; + @Setter + @Getter + public float pitch; + + public int getFloorX() { + return GenericMath.floor(this.x); + } + + public int getFloorY() { + return GenericMath.floor(this.y); + } + + public int getFloorZ() { + return GenericMath.floor(this.z); + } + + public void setPosition(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void setLocation(double x, double y, double z, float yaw, float pitch) { + this.x = x; + this.y = y; + this.z = z; + this.yaw = yaw; + this.pitch = pitch; + } + + public void setRotation(float yaw, float pitch) { + this.yaw = yaw; + this.pitch = pitch; + } + + public Vector3f getVector3f() { + return Vector3f.from(this.x, this.y + 1.62, this.z); + } +} diff --git a/src/main/java/org/barrelmc/barrel/network/BedrockBatchHandler.java b/src/main/java/org/barrelmc/barrel/network/BedrockBatchHandler.java new file mode 100644 index 0000000..ed9ce7d --- /dev/null +++ b/src/main/java/org/barrelmc/barrel/network/BedrockBatchHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 BarrelMC + * BarrelMC/Barrel is licensed under the MIT License + */ + +package org.barrelmc.barrel.network; + +import com.nukkitx.protocol.bedrock.BedrockPacket; +import com.nukkitx.protocol.bedrock.BedrockSession; +import com.nukkitx.protocol.bedrock.handler.BatchHandler; +import io.netty.buffer.ByteBuf; +import org.barrelmc.barrel.network.translator.PacketTranslator; +import org.barrelmc.barrel.player.Player; + +import java.util.Collection; + +public class BedrockBatchHandler implements BatchHandler { + + private final Player player; + + public BedrockBatchHandler(Player player) { + this.player = player; + } + + @Override + public void handle(BedrockSession bedrockSession, ByteBuf byteBuf, Collection collection) { + for (BedrockPacket packet : collection) { + PacketTranslator.translateToJava(packet, this.player); + } + } +} diff --git a/src/main/java/org/barrelmc/barrel/network/JavaPacketHandler.java b/src/main/java/org/barrelmc/barrel/network/JavaPacketHandler.java new file mode 100644 index 0000000..ad18396 --- /dev/null +++ b/src/main/java/org/barrelmc/barrel/network/JavaPacketHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 BarrelMC + * BarrelMC/Barrel is licensed under the MIT License + */ + +package org.barrelmc.barrel.network; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.MinecraftConstants; +import com.github.steveice10.mc.protocol.packet.login.client.LoginStartPacket; +import com.github.steveice10.packetlib.event.session.PacketReceivedEvent; +import com.github.steveice10.packetlib.event.session.SessionAdapter; +import org.barrelmc.barrel.network.translator.PacketTranslator; +import org.barrelmc.barrel.player.Player; +import org.barrelmc.barrel.server.ProxyServer; + +import java.util.UUID; + +public class JavaPacketHandler extends SessionAdapter { + + private Player player = null; + + @Override + public void packetReceived(PacketReceivedEvent event) { + if (this.player == null) { + if (event.getPacket() instanceof LoginStartPacket) { + LoginStartPacket loginPacket = event.getPacket(); + new Player(loginPacket, event.getSession()); + + UUID uuid = UUID.nameUUIDFromBytes((loginPacket.getUsername()).getBytes()); + GameProfile gameProfile = new GameProfile(uuid, loginPacket.getUsername()); + event.getSession().setFlag(MinecraftConstants.PROFILE_KEY, gameProfile); + + this.player = ProxyServer.getInstance().getPlayerByName(loginPacket.getUsername()); + } + } else { + PacketTranslator.translateToBedrock(event.getPacket(), this.player); + } + } +} diff --git a/src/main/java/org/barrelmc/barrel/network/translator/PacketTranslator.java b/src/main/java/org/barrelmc/barrel/network/translator/PacketTranslator.java new file mode 100644 index 0000000..7af5296 --- /dev/null +++ b/src/main/java/org/barrelmc/barrel/network/translator/PacketTranslator.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2021 BarrelMC + * BarrelMC/Barrel is licensed under the MIT License + */ + +package org.barrelmc.barrel.network.translator; + +import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; +import com.github.steveice10.mc.protocol.data.game.chunk.Column; +import com.github.steveice10.mc.protocol.data.game.entity.EntityStatus; +import com.github.steveice10.mc.protocol.data.game.entity.player.Animation; +import com.github.steveice10.mc.protocol.data.game.world.notify.ClientNotification; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientSettingsPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.*; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityAnimationPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityHeadLookPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityStatusPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPlayerPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerNotifyClientPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateTimePacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.packetlib.packet.Packet; +import com.nimbusds.jwt.SignedJWT; +import com.nukkitx.math.vector.Vector2f; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.BedrockPacket; +import com.nukkitx.protocol.bedrock.data.PlayerActionType; +import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.util.EncryptionUtils; +import org.barrelmc.barrel.player.Player; +import org.barrelmc.barrel.server.ProxyServer; + +import javax.crypto.SecretKey; +import java.net.URI; +import java.security.interfaces.ECPublicKey; +import java.util.Base64; + +public class PacketTranslator { + + public static void translateToJava(BedrockPacket pk, Player player) { + // Login Process Start + // ------------------- Server To Client Handshake Packet + if (pk instanceof ServerToClientHandshakePacket) { + try { + SignedJWT saltJwt = SignedJWT.parse(((ServerToClientHandshakePacket) pk).getJwt()); + URI x5u = saltJwt.getHeader().getX509CertURL(); + ECPublicKey serverKey = EncryptionUtils.generateKey(x5u.toASCIIString()); + SecretKey key = EncryptionUtils.getSecretKey( + player.getPrivateKey(), + serverKey, + Base64.getDecoder().decode(saltJwt.getJWTClaimsSet().getStringClaim("salt")) + ); + player.getBedrockClient().getSession().enableEncryption(key); + } catch (Exception e) { + throw new RuntimeException(e); + } + + ClientToServerHandshakePacket clientToServerHandshake = new ClientToServerHandshakePacket(); + player.getBedrockClient().getSession().sendPacketImmediately(clientToServerHandshake); + } + + // ------------------- Resource Packs Info Packet + if (pk instanceof ResourcePacksInfoPacket) { + ResourcePackClientResponsePacket response = new ResourcePackClientResponsePacket(); + response.setStatus(ResourcePackClientResponsePacket.Status.HAVE_ALL_PACKS); + player.getBedrockClient().getSession().sendPacketImmediately(response); + } + + // ------------------- Resource Pack Stack Packet + if (pk instanceof ResourcePackStackPacket) { + ResourcePackClientResponsePacket response = new ResourcePackClientResponsePacket(); + response.setStatus(ResourcePackClientResponsePacket.Status.COMPLETED); + player.getBedrockClient().getSession().sendPacketImmediately(response); + } + + // ------------------- Start Game Packet + if (pk instanceof StartGamePacket) { + StartGamePacket packet = (StartGamePacket) pk; + ServerEntityStatusPacket serverEntityStatusPacket = new ServerEntityStatusPacket((int) packet.getRuntimeEntityId(), EntityStatus.PLAYER_OP_PERMISSION_LEVEL_0); + + ServerJoinGamePacket serverJoinGamePacket = new ServerJoinGamePacket( + (int) packet.getRuntimeEntityId(), false, + TranslatorUtils.translateGamemodeToJE(packet.getPlayerGameType()), + TranslatorUtils.translateGamemodeToJE(packet.getPlayerGameType()), + 1, new String[]{"minecraft:world"}, ProxyServer.getInstance().getDimensionTag(), + ProxyServer.getInstance().getOverworldTag(), "minecraft:world", 100, + 0, 16, false, true, false, false + ); + + Vector3f position = packet.getPlayerPosition(); + Vector2f rotation = packet.getRotation(); + ServerPlayerPositionRotationPacket serverPlayerPositionRotationPacket = new ServerPlayerPositionRotationPacket(position.getX(), position.getY(), position.getZ(), rotation.getY(), rotation.getX(), 1); + + player.getJavaSession().send(serverJoinGamePacket); + player.getJavaSession().send(serverEntityStatusPacket); + player.getJavaSession().send(serverPlayerPositionRotationPacket); + + SetLocalPlayerAsInitializedPacket setLocalPlayerAsInitializedPacket = new SetLocalPlayerAsInitializedPacket(); + setLocalPlayerAsInitializedPacket.setRuntimeEntityId(packet.getRuntimeEntityId()); + player.getBedrockClient().getSession().sendPacket(setLocalPlayerAsInitializedPacket); + + player.setRuntimeEntityId((int) packet.getRuntimeEntityId()); + } + // Login process end + + if (pk instanceof AddPlayerPacket) { + AddPlayerPacket packet = (AddPlayerPacket) pk; + + Vector3f position = packet.getPosition(); + Vector3f rotation = packet.getRotation(); + + player.getJavaSession().send(new ServerSpawnPlayerPacket((int) packet.getRuntimeEntityId(), packet.getUuid(), position.getX(), position.getY(), position.getZ(), rotation.getY(), rotation.getX())); + } + + if (pk instanceof SetTimePacket) { + player.getJavaSession().send(new ServerUpdateTimePacket(0, ((SetTimePacket) pk).getTime())); + } + + if (pk instanceof MovePlayerPacket) { + MovePlayerPacket packet = (MovePlayerPacket) pk; + Vector3f position = packet.getPosition(), rotation = packet.getRotation(); + + if (packet.getRuntimeEntityId() == player.getRuntimeEntityId()) { + ServerPlayerPositionRotationPacket serverPlayerPositionRotationPacket = new ServerPlayerPositionRotationPacket(position.getX(), position.getY() - 1.62, position.getZ(), rotation.getY(), rotation.getX(), 1); + player.getJavaSession().send(serverPlayerPositionRotationPacket); + player.setPosition(position.getX(), position.getY() - 1.62, position.getZ()); + } else { + player.getJavaSession().send(new ServerEntityHeadLookPacket((int) packet.getRuntimeEntityId(), rotation.getZ())); + } + } + + if (pk instanceof MoveEntityAbsolutePacket) { + MoveEntityAbsolutePacket packet = (MoveEntityAbsolutePacket) pk; + Vector3f rotation = packet.getRotation(); + + player.getJavaSession().send(new ServerEntityHeadLookPacket((int) packet.getRuntimeEntityId(), rotation.getZ())); + } + + if (pk instanceof UpdatePlayerGameTypePacket) { + UpdatePlayerGameTypePacket packet = (UpdatePlayerGameTypePacket) pk; + + if (packet.getEntityId() == player.getRuntimeEntityId()) { + player.getJavaSession().send(new ServerNotifyClientPacket(ClientNotification.CHANGE_GAMEMODE, TranslatorUtils.translateGamemodeToJE(packet.getGameType()))); + } + } + + if (pk instanceof AnimatePacket) { + AnimatePacket packet = (AnimatePacket) pk; + + switch (packet.getAction()) { + case SWING_ARM: { + player.getJavaSession().send(new ServerEntityAnimationPacket((int) packet.getRuntimeEntityId(), Animation.SWING_ARM)); + break; + } + case WAKE_UP: { + player.getJavaSession().send(new ServerEntityAnimationPacket((int) packet.getRuntimeEntityId(), Animation.LEAVE_BED)); + break; + } + case CRITICAL_HIT: { + player.getJavaSession().send(new ServerEntityAnimationPacket((int) packet.getRuntimeEntityId(), Animation.CRITICAL_HIT)); + break; + } + case MAGIC_CRITICAL_HIT: { + player.getJavaSession().send(new ServerEntityAnimationPacket((int) packet.getRuntimeEntityId(), Animation.ENCHANTMENT_CRITICAL_HIT)); + break; + } + } + } + + if (pk instanceof LevelChunkPacket) { + LevelChunkPacket packet = (LevelChunkPacket) pk; + Chunk chunk = new Chunk(); + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + chunk.set(x, y, z, y == 0 ? 100 : y); + } + } + } + + Chunk[] chunks = new Chunk[16]; + chunks[5] = chunk; + CompoundTag heightMap = new CompoundTag("MOTION_BLOCKING"); + + ServerChunkDataPacket chunkPacket = new ServerChunkDataPacket(new Column(packet.getChunkX(), packet.getChunkZ(), chunks, new CompoundTag[0], heightMap, new int[1024])); + player.getJavaSession().send(chunkPacket); + } + + if (pk instanceof TextPacket) { + TextPacket packet = (TextPacket) pk; + + player.sendMessage(packet.getMessage()); + } + } + + public static void translateToBedrock(Packet pk, Player player) { + if (pk instanceof ClientPlayerStatePacket) { + ClientPlayerStatePacket packet = (ClientPlayerStatePacket) pk; + switch (packet.getState()) { + case START_SNEAKING: { + PlayerActionPacket playerActionPacket = new PlayerActionPacket(); + playerActionPacket.setAction(PlayerActionType.START_SNEAK); + playerActionPacket.setBlockPosition(Vector3i.ZERO); + playerActionPacket.setFace(0); + playerActionPacket.setRuntimeEntityId(player.getRuntimeEntityId()); + player.getBedrockClient().getSession().sendPacket(playerActionPacket); + break; + } + case STOP_SNEAKING: { + PlayerActionPacket playerActionPacket = new PlayerActionPacket(); + playerActionPacket.setAction(PlayerActionType.STOP_SNEAK); + playerActionPacket.setBlockPosition(Vector3i.ZERO); + playerActionPacket.setFace(0); + playerActionPacket.setRuntimeEntityId(player.getRuntimeEntityId()); + player.getBedrockClient().getSession().sendPacket(playerActionPacket); + break; + } + case START_SPRINTING: { + PlayerActionPacket playerActionPacket = new PlayerActionPacket(); + playerActionPacket.setAction(PlayerActionType.START_SPRINT); + playerActionPacket.setBlockPosition(Vector3i.ZERO); + playerActionPacket.setFace(0); + playerActionPacket.setRuntimeEntityId(player.getRuntimeEntityId()); + player.getBedrockClient().getSession().sendPacket(playerActionPacket); + break; + } + case STOP_SPRINTING: { + PlayerActionPacket playerActionPacket = new PlayerActionPacket(); + playerActionPacket.setAction(PlayerActionType.STOP_SPRINT); + playerActionPacket.setBlockPosition(Vector3i.ZERO); + playerActionPacket.setFace(0); + playerActionPacket.setRuntimeEntityId(player.getRuntimeEntityId()); + player.getBedrockClient().getSession().sendPacket(playerActionPacket); + break; + } + } + } + + if (pk instanceof ClientPlayerSwingArmPacket) { + AnimatePacket animatePacket = new AnimatePacket(); + + animatePacket.setAction(AnimatePacket.Action.SWING_ARM); + animatePacket.setRuntimeEntityId(player.getRuntimeEntityId()); + player.getBedrockClient().getSession().sendPacket(animatePacket); + } + + if (pk instanceof ClientChatPacket) { + ClientChatPacket chatPacket = (ClientChatPacket) pk; + TextPacket textPacket = new TextPacket(); + + textPacket.setType(TextPacket.Type.CHAT); + textPacket.setNeedsTranslation(false); + textPacket.setSourceName(chatPacket.getMessage()); + textPacket.setMessage(chatPacket.getMessage()); + textPacket.setXuid(""); + textPacket.setPlatformChatId(""); + player.getBedrockClient().getSession().sendPacket(textPacket); + } + + if (pk instanceof ClientSettingsPacket) { + ClientSettingsPacket settingsPacket = (ClientSettingsPacket) pk; + RequestChunkRadiusPacket chunkRadiusPacket = new RequestChunkRadiusPacket(); + + chunkRadiusPacket.setRadius(settingsPacket.getRenderDistance()); + player.getBedrockClient().getSession().sendPacket(chunkRadiusPacket); + } + + if (pk instanceof ClientPlayerRotationPacket) { + ClientPlayerRotationPacket packet = (ClientPlayerRotationPacket) pk; + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + + movePlayerPacket.setMode(MovePlayerPacket.Mode.HEAD_ROTATION); + movePlayerPacket.setOnGround(packet.isOnGround()); + movePlayerPacket.setRuntimeEntityId(player.getRuntimeEntityId()); + movePlayerPacket.setRidingRuntimeEntityId(0); + movePlayerPacket.setPosition(player.getVector3f()); + movePlayerPacket.setRotation(Vector3f.from(packet.getPitch(), packet.getYaw(), 0)); + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); + movePlayerPacket.setEntityType(0); + + player.setRotation(packet.getYaw(), packet.getPitch()); + player.getBedrockClient().getSession().sendPacket(movePlayerPacket); + } + + if (pk instanceof ClientPlayerPositionRotationPacket) { + ClientPlayerPositionRotationPacket packet = (ClientPlayerPositionRotationPacket) pk; + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + + movePlayerPacket.setRuntimeEntityId(player.getRuntimeEntityId()); + movePlayerPacket.setPosition(player.getVector3f()); + movePlayerPacket.setRotation(Vector3f.from(packet.getPitch(), packet.getYaw(), packet.getYaw())); + movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); + movePlayerPacket.setOnGround(packet.isOnGround()); + movePlayerPacket.setRidingRuntimeEntityId(0); + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); + movePlayerPacket.setEntityType(0); + + player.setLocation(packet.getX(), packet.getY(), packet.getZ(), packet.getYaw(), packet.getPitch()); + player.getBedrockClient().getSession().sendPacket(movePlayerPacket); + } + + if (pk instanceof ClientPlayerPositionPacket) { + ClientPlayerPositionPacket packet = (ClientPlayerPositionPacket) pk; + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + + movePlayerPacket.setRuntimeEntityId(player.getRuntimeEntityId()); + movePlayerPacket.setPosition(player.getVector3f()); + movePlayerPacket.setRotation(Vector3f.from(player.getPitch(), player.getYaw(), player.getYaw())); + movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); + movePlayerPacket.setOnGround(packet.isOnGround()); + movePlayerPacket.setRidingRuntimeEntityId(0); + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); + movePlayerPacket.setEntityType(0); + + player.setPosition(packet.getX(), packet.getY(), packet.getZ()); + player.getBedrockClient().getSession().sendPacket(movePlayerPacket); + } + } +} diff --git a/src/main/java/org/barrelmc/barrel/network/translator/TranslatorUtils.java b/src/main/java/org/barrelmc/barrel/network/translator/TranslatorUtils.java new file mode 100644 index 0000000..47e88fc --- /dev/null +++ b/src/main/java/org/barrelmc/barrel/network/translator/TranslatorUtils.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 BarrelMC + * BarrelMC/Barrel is licensed under the MIT License + */ + +package org.barrelmc.barrel.network.translator; + +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.nukkitx.protocol.bedrock.data.GameType; + +public class TranslatorUtils { + + public static GameMode translateGamemodeToJE(GameType gameType) { + String gameTypeString = gameType.toString(); + if (gameTypeString.contains("VIEWER")) { + gameTypeString = GameMode.SPECTATOR.name(); + } + + return GameMode.valueOf(gameTypeString); + } +} diff --git a/src/main/java/org/barrelmc/barrel/player/Player.java b/src/main/java/org/barrelmc/barrel/player/Player.java new file mode 100644 index 0000000..0765ddb --- /dev/null +++ b/src/main/java/org/barrelmc/barrel/player/Player.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2021 BarrelMC + * BarrelMC/Barrel is licensed under the MIT License + */ + +package org.barrelmc.barrel.player; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerChatPacket; +import com.github.steveice10.mc.protocol.packet.login.client.LoginStartPacket; +import com.github.steveice10.packetlib.Session; +import com.nukkitx.protocol.bedrock.BedrockClient; +import com.nukkitx.protocol.bedrock.packet.LoginPacket; +import com.nukkitx.protocol.bedrock.util.EncryptionUtils; +import io.netty.util.AsciiString; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import org.barrelmc.barrel.auth.JoseStuff; +import org.barrelmc.barrel.config.Config; +import org.barrelmc.barrel.math.Vector3; +import org.barrelmc.barrel.network.BedrockBatchHandler; +import org.barrelmc.barrel.server.ProxyServer; + +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.time.Instant; +import java.util.Base64; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +public class Player extends Vector3 { + + @Getter + private final Session javaSession; + @Getter + private BedrockClient bedrockClient; + + @Getter + private ECPublicKey publicKey; + @Getter + private ECPrivateKey privateKey; + + @Setter + @Getter + private int runtimeEntityId; + @Getter + private String username; + @Getter + private String xuid; + @Getter + private String UUID; + + public Player(LoginStartPacket loginPacket, Session javaSession) { + this.javaSession = javaSession; + + //if (ProxyServer.getInstance().getConfig().getAuth().equals("offline")) { + this.offlineLogin(loginPacket); + //} + } + + private void offlineLogin(LoginStartPacket javaLoginPacket) { + InetSocketAddress bindAddress = new InetSocketAddress("0.0.0.0", ThreadLocalRandom.current().nextInt(30000, 60000)); + BedrockClient client = new BedrockClient(bindAddress); + + this.xuid = ""; + this.username = javaLoginPacket.getUsername(); + this.UUID = java.util.UUID.randomUUID().toString(); + this.bedrockClient = client; + ProxyServer.getInstance().getOnlinePlayers().put(javaLoginPacket.getUsername(), this); + + client.bind().join(); + + Config config = ProxyServer.getInstance().getConfig(); + InetSocketAddress bedrockAddress = new InetSocketAddress(config.getBedrockAddress(), config.getBedrockPort()); + client.connect(bedrockAddress).whenComplete((session, throwable) -> { + if (throwable != null) { + javaSession.disconnect("Server offline " + throwable); + return; + } + + session.setPacketCodec(ProxyServer.getInstance().getBedrockPacketCodec()); + session.addDisconnectHandler((reason) -> javaSession.disconnect("Client disconnected! " + reason.toString())); + session.setBatchHandler(new BedrockBatchHandler(this)); + session.sendPacketImmediately(this.getLoginPacket()); + }).join(); + } + + private LoginPacket getLoginPacket() { + LoginPacket loginPacket = new LoginPacket(); + + KeyPair ecdsa384KeyPair = EncryptionUtils.createKeyPair(); + this.publicKey = (ECPublicKey) ecdsa384KeyPair.getPublic(); + this.privateKey = (ECPrivateKey) ecdsa384KeyPair.getPrivate(); + + String publicKeyBase64 = Base64.getEncoder().encodeToString(this.publicKey.getEncoded()); + + JSONObject chain = new JSONObject(); + chain.put("exp", Instant.now().getEpochSecond() + TimeUnit.HOURS.toSeconds(6)); + chain.put("identityPublicKey", publicKeyBase64); + chain.put("nbf", Instant.now().getEpochSecond() - TimeUnit.HOURS.toSeconds(6)); + + JSONObject extraData = new JSONObject(); + extraData.put("identity", this.UUID); + extraData.put("displayName", this.username); + chain.put("extraData", extraData); + + JSONObject jwtHeader = new JSONObject(); + jwtHeader.put("alg", "ES384"); + jwtHeader.put("x5u", publicKeyBase64); + + String header = Base64.getUrlEncoder().withoutPadding().encodeToString(jwtHeader.toJSONString().getBytes()); + String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(chain.toJSONString().getBytes()); + + byte[] dataToSign = (header + "." + payload).getBytes(); + byte[] signatureBytes = null; + try { + Signature signature = Signature.getInstance("SHA384withECDSA"); + signature.initSign(this.privateKey); + signature.update(dataToSign); + signatureBytes = JoseStuff.DERToJOSE(signature.sign(), JoseStuff.AlgorithmType.ECDSA384); + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException ignored) { + } + + String signatureString = Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes); + + String jwt = header + "." + payload + "." + signatureString; + + JSONArray chainDataJsonArray = new JSONArray(); + chainDataJsonArray.add(jwt); + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("chain", chainDataJsonArray); + + loginPacket.setChainData(new AsciiString(jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8))); + loginPacket.setSkinData(new AsciiString(this.getSkinData())); + loginPacket.setProtocolVersion(ProxyServer.getInstance().getBedrockPacketCodec().getProtocolVersion()); + return loginPacket; + } + + private String getSkinData() { + String publicKeyBase64 = Base64.getEncoder().encodeToString(this.publicKey.getEncoded()); + + JSONObject jwtHeader = new JSONObject(); + jwtHeader.put("alg", "ES384"); + jwtHeader.put("x5u", publicKeyBase64); + + JSONObject skinData = new JSONObject(); + + skinData.put("AnimatedImageData", new JSONArray()); + skinData.put("ArmSize", ""); + skinData.put("CapeData", ""); + skinData.put("CapeId", ""); + skinData.put("PlayFabId", java.util.UUID.randomUUID().toString()); + skinData.put("CapeImageHeight", 0); + skinData.put("CapeImageWidth", 0); + skinData.put("CapeOnClassicSkin", false); + skinData.put("ClientRandomId", new Random().nextLong()); + skinData.put("CurrentInputMode", 1); + skinData.put("DefaultInputMode", 1); + skinData.put("DeviceId", java.util.UUID.randomUUID().toString()); + skinData.put("DeviceModel", "Barrel"); + skinData.put("DeviceOS", 7); + skinData.put("GameVersion", ProxyServer.getInstance().getBedrockPacketCodec().getMinecraftVersion()); + skinData.put("GuiScale", 0); + skinData.put("LanguageCode", "en_US"); + skinData.put("PersonaPieces", new JSONArray()); + skinData.put("PersonaSkin", false); + skinData.put("PieceTintColors", new JSONArray()); + skinData.put("PlatformOfflineId", ""); + skinData.put("PlatformOnlineId", ""); + skinData.put("PremiumSkin", false); + skinData.put("SelfSignedId", this.UUID); + skinData.put("ServerAddress", ProxyServer.getInstance().getConfig().getBedrockAddress() + ":" + ProxyServer.getInstance().getConfig().getBedrockPort()); + skinData.put("SkinAnimationData", ""); + skinData.put("SkinColor", "#0"); + skinData.put("SkinData", "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ll9B/5ZfQf+WX0H/lm9b/6p9Zv+Wb1v/lm9b/4dVO/+WX0H/ll9B/5ZfQf+qfWb/lm9b/6p9Zv+Wb1v/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9WScz/VknM/1ZJzP9WScz/KCgo/ygoKP8oKCj/KCgo/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AKio/wDMzP8AzMz/AKio/1ExJf9qQDD/UTEl/2pAMP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/VknM/1ZJzP9WScz/VknM/ygoKP8oKCj/KCgo/ygoKP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wDMzP8AzMz/AMzM/wDMzP9RMSX/akAw/1ExJf9qQDD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/1ZJzP9WScz/VknM/1ZJzP8oKCj/KCgo/ygoKP8oKCj/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AqKj/AMzM/wDMzP8AzMz/akAw/1ExJf9qQDD/UTEl/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9WScz/VknM/1ZJzP9WScz/KCgo/ygoKP8oKCj/KCgo/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AKio/wDMzP8AzMz/AKio/2pAMP9RMSX/akAw/1ExJf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8wKHL/MChy/yYhW/8wKHL/Rjql/0Y6pf9GOqX/Rjql/zAocv8mIVv/MChy/zAocv86MYn/Rjql/0Y6pf9GOqX/AH9//wB/f/8Af3//AH9//wCoqP8Ar6//AKio/wCenv8Af3//AH9//wB/f/8Af3//AK+v/wCvr/8Ar6//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"); + skinData.put("SkinGeometryData", Base64.getEncoder().encodeToString("{\"format_version\":\"1.12.0\",\"minecraft:geometry\":[{\"bones\":[{\"name\":\"body\",\"parent\":\"waist\",\"pivot\":[0,24,0]},{\"name\":\"waist\",\"pivot\":[0,12,0]},{\"cubes\":[{\"origin\":[-5,8,3],\"size\":[10,16,1],\"uv\":[0,0]}],\"name\":\"cape\",\"parent\":\"body\",\"pivot\":[0,24,3],\"rotation\":[0,180,0]}],\"description\":{\"identifier\":\"geometry.cape\",\"texture_height\":32,\"texture_width\":64}},{\"bones\":[{\"name\":\"root\",\"pivot\":[0,0,0]},{\"cubes\":[{\"origin\":[-4,12,-2],\"size\":[8,12,4],\"uv\":[16,16]}],\"name\":\"body\",\"parent\":\"waist\",\"pivot\":[0,24,0]},{\"name\":\"waist\",\"parent\":\"root\",\"pivot\":[0,12,0]},{\"cubes\":[{\"origin\":[-4,24,-4],\"size\":[8,8,8],\"uv\":[0,0]}],\"name\":\"head\",\"parent\":\"body\",\"pivot\":[0,24,0]},{\"name\":\"cape\",\"parent\":\"body\",\"pivot\":[0,24,3]},{\"cubes\":[{\"inflate\":0.5,\"origin\":[-4,24,-4],\"size\":[8,8,8],\"uv\":[32,0]}],\"name\":\"hat\",\"parent\":\"head\",\"pivot\":[0,24,0]},{\"cubes\":[{\"origin\":[4,12,-2],\"size\":[4,12,4],\"uv\":[32,48]}],\"name\":\"leftArm\",\"parent\":\"body\",\"pivot\":[5,22,0]},{\"cubes\":[{\"inflate\":0.25,\"origin\":[4,12,-2],\"size\":[4,12,4],\"uv\":[48,48]}],\"name\":\"leftSleeve\",\"parent\":\"leftArm\",\"pivot\":[5,22,0]},{\"name\":\"leftItem\",\"parent\":\"leftArm\",\"pivot\":[6,15,1]},{\"cubes\":[{\"origin\":[-8,12,-2],\"size\":[4,12,4],\"uv\":[40,16]}],\"name\":\"rightArm\",\"parent\":\"body\",\"pivot\":[-5,22,0]},{\"cubes\":[{\"inflate\":0.25,\"origin\":[-8,12,-2],\"size\":[4,12,4],\"uv\":[40,32]}],\"name\":\"rightSleeve\",\"parent\":\"rightArm\",\"pivot\":[-5,22,0]},{\"locators\":{\"lead_hold\":[-6,15,1]},\"name\":\"rightItem\",\"parent\":\"rightArm\",\"pivot\":[-6,15,1]},{\"cubes\":[{\"origin\":[-0.1,0,-2],\"size\":[4,12,4],\"uv\":[16,48]}],\"name\":\"leftLeg\",\"parent\":\"root\",\"pivot\":[1.9,12,0]},{\"cubes\":[{\"inflate\":0.25,\"origin\":[-0.1,0,-2],\"size\":[4,12,4],\"uv\":[0,48]}],\"name\":\"leftPants\",\"parent\":\"leftLeg\",\"pivot\":[1.9,12,0]},{\"cubes\":[{\"origin\":[-3.9,0,-2],\"size\":[4,12,4],\"uv\":[0,16]}],\"name\":\"rightLeg\",\"parent\":\"root\",\"pivot\":[-1.9,12,0]},{\"cubes\":[{\"inflate\":0.25,\"origin\":[-3.9,0,-2],\"size\":[4,12,4],\"uv\":[0,32]}],\"name\":\"rightPants\",\"parent\":\"rightLeg\",\"pivot\":[-1.9,12,0]},{\"cubes\":[{\"inflate\":0.25,\"origin\":[-4,12,-2],\"size\":[8,12,4],\"uv\":[16,32]}],\"name\":\"jacket\",\"parent\":\"body\",\"pivot\":[0,24,0]}],\"description\":{\"identifier\":\"geometry.humanoid.custom\",\"texture_height\":64,\"texture_width\":64,\"visible_bounds_height\":2,\"visible_bounds_offset\":[0,1,0],\"visible_bounds_width\":1}},{\"bones\":[{\"name\":\"root\",\"pivot\":[0,0,0]},{\"name\":\"waist\",\"parent\":\"root\",\"pivot\":[0,12,0]},{\"cubes\":[{\"origin\":[-4,12,-2],\"size\":[8,12,4],\"uv\":[16,16]}],\"name\":\"body\",\"parent\":\"waist\",\"pivot\":[0,24,0]},{\"cubes\":[{\"origin\":[-4,24,-4],\"size\":[8,8,8],\"uv\":[0,0]}],\"name\":\"head\",\"parent\":\"body\",\"pivot\":[0,24,0]},{\"cubes\":[{\"inflate\":0.5,\"origin\":[-4,24,-4],\"size\":[8,8,8],\"uv\":[32,0]}],\"name\":\"hat\",\"parent\":\"head\",\"pivot\":[0,24,0]},{\"cubes\":[{\"origin\":[-3.9,0,-2],\"size\":[4,12,4],\"uv\":[0,16]}],\"name\":\"rightLeg\",\"parent\":\"root\",\"pivot\":[-1.9,12,0]},{\"cubes\":[{\"inflate\":0.25,\"origin\":[-3.9,0,-2],\"size\":[4,12,4],\"uv\":[0,32]}],\"name\":\"rightPants\",\"parent\":\"rightLeg\",\"pivot\":[-1.9,12,0]},{\"cubes\":[{\"origin\":[-0.1,0,-2],\"size\":[4,12,4],\"uv\":[16,48]}],\"mirror\":true,\"name\":\"leftLeg\",\"parent\":\"root\",\"pivot\":[1.9,12,0]},{\"cubes\":[{\"inflate\":0.25,\"origin\":[-0.1,0,-2],\"size\":[4,12,4],\"uv\":[0,48]}],\"name\":\"leftPants\",\"parent\":\"leftLeg\",\"pivot\":[1.9,12,0]},{\"cubes\":[{\"origin\":[4,11.5,-2],\"size\":[3,12,4],\"uv\":[32,48]}],\"name\":\"leftArm\",\"parent\":\"body\",\"pivot\":[5,21.5,0]},{\"cubes\":[{\"inflate\":0.25,\"origin\":[4,11.5,-2],\"size\":[3,12,4],\"uv\":[48,48]}],\"name\":\"leftSleeve\",\"parent\":\"leftArm\",\"pivot\":[5,21.5,0]},{\"name\":\"leftItem\",\"parent\":\"leftArm\",\"pivot\":[6,14.5,1]},{\"cubes\":[{\"origin\":[-7,11.5,-2],\"size\":[3,12,4],\"uv\":[40,16]}],\"name\":\"rightArm\",\"parent\":\"body\",\"pivot\":[-5,21.5,0]},{\"cubes\":[{\"inflate\":0.25,\"origin\":[-7,11.5,-2],\"size\":[3,12,4],\"uv\":[40,32]}],\"name\":\"rightSleeve\",\"parent\":\"rightArm\",\"pivot\":[-5,21.5,0]},{\"locators\":{\"lead_hold\":[-6,14.5,1]},\"name\":\"rightItem\",\"parent\":\"rightArm\",\"pivot\":[-6,14.5,1]},{\"cubes\":[{\"inflate\":0.25,\"origin\":[-4,12,-2],\"size\":[8,12,4],\"uv\":[16,32]}],\"name\":\"jacket\",\"parent\":\"body\",\"pivot\":[0,24,0]},{\"name\":\"cape\",\"parent\":\"body\",\"pivot\":[0,24,-3]}],\"description\":{\"identifier\":\"geometry.humanoid.customSlim\",\"texture_height\":64,\"texture_width\":64,\"visible_bounds_height\":2,\"visible_bounds_offset\":[0,1,0],\"visible_bounds_width\":1}}]}".getBytes())); + skinData.put("SkinId", this.UUID + ".Custom"); + skinData.put("SkinImageHeight", 64); + skinData.put("SkinImageWidth", 64); + skinData.put("SkinResourcePatch", "ewogICAiZ2VvbWV0cnkiIDogewogICAgICAiZGVmYXVsdCIgOiAiZ2VvbWV0cnkuaHVtYW5vaWQuY3VzdG9tIgogICB9Cn0K"); + skinData.put("ThirdPartyName", this.username); + skinData.put("ThirdPartyNameOnly", false); + skinData.put("UIProfile", 0); + + String header = Base64.getUrlEncoder().withoutPadding().encodeToString(jwtHeader.toJSONString().getBytes()); + String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(skinData.toJSONString().getBytes()); + + byte[] dataToSign = (header + "." + payload).getBytes(); + byte[] signatureBytes = null; + try { + Signature signature = Signature.getInstance("SHA384withECDSA"); + signature.initSign(this.privateKey); + signature.update(dataToSign); + signatureBytes = JoseStuff.DERToJOSE(signature.sign(), JoseStuff.AlgorithmType.ECDSA384); + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException ignored) { + } + String signatureString = Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes); + + return header + "." + payload + "." + signatureString; + } + + public void sendMessage(String message) { + this.javaSession.send(new ServerChatPacket(Component.text(message))); + } + + public void disconnect(String reason) { + this.getBedrockClient().getSession().disconnect(); + this.javaSession.disconnect(reason); + ProxyServer.getInstance().getOnlinePlayers().remove(username); + } +} diff --git a/src/main/java/org/barrelmc/barrel/server/ProxyServer.java b/src/main/java/org/barrelmc/barrel/server/ProxyServer.java new file mode 100644 index 0000000..26a479e --- /dev/null +++ b/src/main/java/org/barrelmc/barrel/server/ProxyServer.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2021 BarrelMC + * BarrelMC/Barrel is licensed under the MIT License + */ + +package org.barrelmc.barrel.server; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.auth.service.SessionService; +import com.github.steveice10.mc.protocol.MinecraftConstants; +import com.github.steveice10.mc.protocol.MinecraftProtocol; +import com.github.steveice10.mc.protocol.data.status.PlayerInfo; +import com.github.steveice10.mc.protocol.data.status.ServerStatusInfo; +import com.github.steveice10.mc.protocol.data.status.VersionInfo; +import com.github.steveice10.mc.protocol.data.status.handler.ServerInfoBuilder; +import com.github.steveice10.opennbt.tag.builtin.*; +import com.github.steveice10.packetlib.Server; +import com.github.steveice10.packetlib.event.server.ServerAdapter; +import com.github.steveice10.packetlib.event.server.ServerClosedEvent; +import com.github.steveice10.packetlib.event.server.SessionAddedEvent; +import com.github.steveice10.packetlib.event.server.SessionRemovedEvent; +import com.github.steveice10.packetlib.tcp.TcpServer; +import com.nukkitx.protocol.bedrock.BedrockPacketCodec; +import com.nukkitx.protocol.bedrock.v440.Bedrock_v440; +import lombok.Getter; +import net.kyori.adventure.text.Component; +import org.barrelmc.barrel.config.Config; +import org.barrelmc.barrel.network.JavaPacketHandler; +import org.barrelmc.barrel.player.Player; +import org.yaml.snakeyaml.Yaml; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class ProxyServer { + + @Getter + private static ProxyServer instance = null; + @Getter + private final Map onlinePlayers = new ConcurrentHashMap<>(); + @Getter + private final BedrockPacketCodec bedrockPacketCodec = Bedrock_v440.V440_CODEC; + + @Getter + private final Path dataPath; + + @Getter + private Config config; + + public ProxyServer(String dataPath) { + instance = this; + this.dataPath = Paths.get(dataPath); + if (!this.initConfig()) { + System.out.println("Config file not found! Terminating..."); + System.exit(0); + } + + this.startServer(); + } + + private boolean initConfig() { + try { + InputStream inputStream = new FileInputStream(this.dataPath.toString() + "/config.yml"); + this.config = (new Yaml()).loadAs(inputStream, Config.class); + return true; + } catch (FileNotFoundException ignored) { + } + + return false; + } + + private void startServer() { + SessionService sessionService = new SessionService(); + + Server server = new TcpServer(this.config.getBindAddress(), this.config.getPort(), MinecraftProtocol.class); + server.setGlobalFlag(MinecraftConstants.SESSION_SERVICE_KEY, sessionService); + server.setGlobalFlag(MinecraftConstants.VERIFY_USERS_KEY, false); + server.setGlobalFlag(MinecraftConstants.SERVER_INFO_BUILDER_KEY, (ServerInfoBuilder) session -> new ServerStatusInfo(new VersionInfo(MinecraftConstants.GAME_VERSION, MinecraftConstants.PROTOCOL_VERSION), new PlayerInfo(10, 0, new GameProfile[0]), Component.text(this.config.getMotd()), null)); + server.setGlobalFlag(MinecraftConstants.SERVER_COMPRESSION_THRESHOLD, 100); + server.addListener(new ServerAdapter() { + @Override + public void serverClosed(ServerClosedEvent event) { + // TODO: disconnect all bedrock client + System.out.println("Server closed."); + } + + @Override + public void sessionAdded(SessionAddedEvent event) { + event.getSession().addListener(new JavaPacketHandler()); + } + + @Override + public void sessionRemoved(SessionRemovedEvent event) { + GameProfile profile = event.getSession().getFlag(MinecraftConstants.PROFILE_KEY); + + Player player = getPlayerByName(profile.getName()); + player.disconnect("logged out"); + } + }); + + System.out.println("Binding to " + this.config.getBindAddress() + " on port " + this.config.getPort()); + server.bind(); + System.out.println("BarrelProxy is running on [" + this.config.getBindAddress() + "::" + this.config.getPort() + "]"); + } + + public Player getPlayerByName(String username) { + return this.onlinePlayers.get(username); + } + + public CompoundTag getDimensionTag() { + CompoundTag tag = new CompoundTag(""); + + CompoundTag dimensionTypes = new CompoundTag("minecraft:dimension_type"); + dimensionTypes.put(new StringTag("type", "minecraft:dimension_type")); + ListTag dimensionTag = new ListTag("value"); + CompoundTag overworldTag = convertToValue("minecraft:overworld", getOverworldTag().getValue()); + dimensionTag.add(overworldTag); + dimensionTypes.put(dimensionTag); + tag.put(dimensionTypes); + + CompoundTag biomeTypes = new CompoundTag("minecraft:worldgen/biome"); + biomeTypes.put(new StringTag("type", "minecraft:worldgen/biome")); + ListTag biomeTag = new ListTag("value"); + CompoundTag plainsTag = convertToValue("minecraft:plains", getPlainsTag().getValue()); + biomeTag.add(plainsTag); + biomeTypes.put(biomeTag); + tag.put(biomeTypes); + + return tag; + } + + public CompoundTag getOverworldTag() { + CompoundTag overworldTag = new CompoundTag(""); + overworldTag.put(new StringTag("name", "minecraft:overworld")); + overworldTag.put(new ByteTag("piglin_safe", (byte) 0)); + overworldTag.put(new ByteTag("natural", (byte) 1)); + overworldTag.put(new FloatTag("ambient_light", 0f)); + overworldTag.put(new StringTag("infiniburn", "minecraft:infiniburn_overworld")); + overworldTag.put(new ByteTag("respawn_anchor_works", (byte) 0)); + overworldTag.put(new ByteTag("has_skylight", (byte) 1)); + overworldTag.put(new ByteTag("bed_works", (byte) 1)); + overworldTag.put(new StringTag("effects", "minecraft:overworld")); + overworldTag.put(new ByteTag("has_raids", (byte) 1)); + overworldTag.put(new IntTag("logical_height", 256)); + overworldTag.put(new FloatTag("coordinate_scale", 1f)); + overworldTag.put(new ByteTag("ultrawarm", (byte) 0)); + overworldTag.put(new ByteTag("has_ceiling", (byte) 0)); + return overworldTag; + } + + private CompoundTag getPlainsTag() { + CompoundTag plainsTag = new CompoundTag(""); + plainsTag.put(new StringTag("name", "minecraft:plains")); + plainsTag.put(new StringTag("precipitation", "rain")); + plainsTag.put(new FloatTag("depth", 0.125f)); + plainsTag.put(new FloatTag("temperature", 0.8f)); + plainsTag.put(new FloatTag("scale", 0.05f)); + plainsTag.put(new FloatTag("downfall", 0.4f)); + plainsTag.put(new StringTag("category", "plains")); + + CompoundTag effects = new CompoundTag("effects"); + effects.put(new LongTag("sky_color", 7907327)); + effects.put(new LongTag("water_fog_color", 329011)); + effects.put(new LongTag("fog_color", 12638463)); + effects.put(new LongTag("water_color", 4159204)); + + CompoundTag moodSound = new CompoundTag("mood_sound"); + moodSound.put(new IntTag("tick_delay", 6000)); + moodSound.put(new FloatTag("offset", 2.0f)); + moodSound.put(new StringTag("sound", "minecraft:ambient.cave")); + moodSound.put(new IntTag("block_search_extent", 8)); + + effects.put(moodSound); + + plainsTag.put(effects); + + return plainsTag; + } + + private CompoundTag convertToValue(String name, Map values) { + CompoundTag tag = new CompoundTag(name); + tag.put(new StringTag("name", name)); + tag.put(new IntTag("id", 0)); + CompoundTag element = new CompoundTag("element"); + element.setValue(values); + tag.put(element); + + return tag; + } +} diff --git a/src/main/java/org/barrelmc/barrel/utils/FileManager.java b/src/main/java/org/barrelmc/barrel/utils/FileManager.java new file mode 100644 index 0000000..f90cc04 --- /dev/null +++ b/src/main/java/org/barrelmc/barrel/utils/FileManager.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 BarrelMC + * BarrelMC/Barrel is licensed under the MIT License + */ + +package org.barrelmc.barrel.utils; + +import java.nio.file.Files; +import java.nio.file.Paths; + +public class FileManager { + + public static String getFileContents(String path) { + try { + return new String(Files.readAllBytes(Paths.get(path))); + } catch (Exception ignored) { + } + + return null; + } +}