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;
+ }
+}