Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ repositories {
}
}

configurations {
testImplementation.extendsFrom compileOnly
}

dependencies {
compileOnly "com.viaversion:viaversion-common:5.12.0"
compileOnly "org.yaml:snakeyaml:2.7"
Expand Down Expand Up @@ -101,6 +105,13 @@ dependencies {
toolImplementation "org.jsoup:jsoup:1.23.2"
toolImplementation "org.ow2.asm:asm-tree:9.11.1"
toolImplementation "net.lenni0451.commons:asm:1.9.2"

testImplementation "org.junit.jupiter:junit-jupiter:6.1.3"
testRuntimeOnly "org.junit.platform:junit-platform-launcher:6.1.3"
}

test {
useJUnitPlatform()
}

def registerToolTask(String name, String toolMainClass, String toolDescription) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,9 @@ public JsonBlockStateUpgradeSchema(final JsonObject jsonObject) {

if (tag.get("states") instanceof CompoundTag states) {
for (Pair<String, ?> property : toAdd) {
states.put(property.key(), NbtUtil.createTag(property.value()));
if (!states.contains(property.key())) {
states.put(property.key(), NbtUtil.createTag(property.value()));
}
}
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final L
}

final int rawAlgorithm = in.readUnsignedByte();
// NetworkSettings uses a uint16 sentinel; a compressed frame has only a uint8 header.
final PacketCompressionAlgorithm algorithm = rawAlgorithm == 0xFF ? PacketCompressionAlgorithm.None : PacketCompressionAlgorithm.getByValue(rawAlgorithm);
if (algorithm == null) { // Bedrock client drops the packet if the algorithm is unknown
ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Dropping packet with unknown PacketCompressionAlgorithm: " + rawAlgorithm);
Expand All @@ -95,7 +96,7 @@ protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final L

final CompressionAlgorithm compressionAlgorithm = this.getCompressionAlgorithm(algorithm);
if (compressionAlgorithm instanceof NoopCompression) {
out.add(in.retain());
out.add(in.readRetainedSlice(in.readableBytes()));
} else {
final ByteBuf uncompressedData = ctx.alloc().buffer();
compressionAlgorithm.decompress(in, uncompressedData); // Bedrock client would drop packets with invalid data, but this would be too insane to do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.StringTag;
import com.viaversion.viaversion.libs.fastutil.ints.IntIntPair;
import com.viaversion.viaversion.util.Key;
import net.raphimc.viabedrock.ViaBedrock;
import net.raphimc.viabedrock.api.resourcepack.definition.BiomeDefinitions;
Expand All @@ -29,6 +28,7 @@
import net.raphimc.viabedrock.protocol.BedrockProtocol;
import net.raphimc.viabedrock.protocol.data.enums.Dimension;
import net.raphimc.viabedrock.protocol.data.generated.java.RegistryKeys;
import net.raphimc.viabedrock.protocol.model.DimensionDefinition;
import net.raphimc.viabedrock.protocol.storage.GameSessionStorage;
import net.raphimc.viabedrock.protocol.storage.ResourcePackStorage;

Expand Down Expand Up @@ -62,13 +62,24 @@ private static void modifyDimensionRegistry(final GameSessionStorage gameSession
dimensionRegistry.getCompoundTag(Dimension.OVERWORLD.getKey()).putInt("height", 256);
dimensionRegistry.getCompoundTag(Dimension.OVERWORLD.getKey()).putInt("logical_height", 256);
}
for (Map.Entry<String, IntIntPair> entry : gameSession.getBedrockDimensionDefinitions().entrySet()) {
final CompoundTag dimensionTag = new CompoundTag();
final int height = entry.getValue().rightInt() - entry.getValue().leftInt();
dimensionTag.putInt("min_y", entry.getValue().leftInt());
dimensionTag.putInt("height", height);
dimensionTag.putInt("logical_height", height);
dimensionRegistry.getCompoundTag(entry.getKey()).putAll(dimensionTag);
for (Map.Entry<String, DimensionDefinition> entry : gameSession.getBedrockDimensionDefinitions().entrySet()) {
applyDimensionDefinition(dimensionRegistry.getCompoundTag(entry.getKey()), entry.getValue());
}
}

static void applyDimensionDefinition(final CompoundTag dimensionTag, final DimensionDefinition definition) {
dimensionTag.putInt("min_y", definition.minimumY());
dimensionTag.putInt("height", definition.height());
dimensionTag.putInt("logical_height", definition.height());
CompoundTag attributes = dimensionTag.getCompoundTag("attributes");
if (attributes == null) {
attributes = new CompoundTag();
dimensionTag.put("attributes", attributes);
}
attributes.putFloat("minecraft:visual/cloud_height", definition.cloudHeight());
if (!definition.renderClouds()) {
// Java's day timeline multiplies cloud color, preserving zero alpha.
attributes.putString("minecraft:visual/cloud_color", "#00ffffff");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock
* Copyright (C) 2023-2026 RK_01/RaphiMC and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viabedrock.protocol.model;

public record DimensionDefinition(int minimumY, int height, int cloudHeight, boolean renderClouds) {

public DimensionDefinition {
if (height <= 0) {
throw new IllegalArgumentException("Dimension height range must be positive");
}
Math.addExact(minimumY, height);
}

public int maximumY() {
return this.minimumY + this.height;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.libs.fastutil.ints.IntIntImmutablePair;
import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets;
import com.viaversion.viaversion.protocols.base.v1_7.ClientboundBaseProtocol1_7;
import com.viaversion.viaversion.protocols.v26_2to26_3.packet.ClientboundConfigurationPackets26_3;
Expand All @@ -55,6 +54,7 @@
import net.raphimc.viabedrock.protocol.data.enums.java.Relative;
import net.raphimc.viabedrock.protocol.data.enums.java.generated.PlayerInfoUpdateAction;
import net.raphimc.viabedrock.protocol.data.generated.java.Attributes;
import net.raphimc.viabedrock.protocol.model.DimensionDefinition;
import net.raphimc.viabedrock.protocol.model.*;
import net.raphimc.viabedrock.protocol.rewriter.BlockStateRewriter;
import net.raphimc.viabedrock.protocol.rewriter.ItemRewriter;
Expand Down Expand Up @@ -434,16 +434,9 @@ protected void register() {
final int count = wrapper.read(BedrockTypes.UNSIGNED_VAR_INT); // entry count
for (int i = 0; i < count; i++) {
final String dimensionIdentifier = wrapper.read(BedrockTypes.STRING); // dimension identifier
final int minimumHeight = wrapper.read(BedrockTypes.VAR_INT); // minimum height
final int maximumHeight = minimumHeight + wrapper.read(BedrockTypes.VAR_INT); // maximum height
wrapper.read(BedrockTypes.VAR_INT); // generator type
wrapper.read(BedrockTypes.VAR_INT); // dimension type
wrapper.read(BedrockTypes.UUID); // pack id
wrapper.read(BedrockTypes.STRING); // default biome
wrapper.read(BedrockTypes.VAR_INT); // cloud height
wrapper.read(Types.BOOLEAN); // render clouds
if (dimensionIdentifier.equals(Dimension.OVERWORLD.getKey())) { // Bedrock client currently only supports overworld
gameSession.putBedrockDimensionDefinition(dimensionIdentifier, new IntIntImmutablePair(minimumHeight, maximumHeight));
final DimensionDefinition definition = readDimensionDefinition(wrapper);
if (dimensionIdentifier.equals(Dimension.OVERWORLD.getKey())) { // Only Overworld overrides currently map to Java
gameSession.putBedrockDimensionDefinition(dimensionIdentifier, definition);
}
}
}, State.PLAY, (PacketHandler) PacketWrapper::cancel // Bedrock client ignores dimension data after start game
Expand Down Expand Up @@ -636,6 +629,18 @@ private static void handleJavaClientGameJoin(final UserConnection user) {
setTime.send(BedrockProtocol.class, false);
}

static DimensionDefinition readDimensionDefinition(final PacketWrapper wrapper) {
final int minimumY = wrapper.read(BedrockTypes.VAR_INT);
final int heightRange = wrapper.read(BedrockTypes.VAR_INT);
wrapper.read(BedrockTypes.VAR_INT); // generator type
wrapper.read(BedrockTypes.VAR_INT); // dimension type
wrapper.read(BedrockTypes.UUID); // pack id
wrapper.read(BedrockTypes.STRING); // default biome
final int cloudHeight = wrapper.read(BedrockTypes.VAR_INT);
final boolean renderClouds = wrapper.read(Types.BOOLEAN);
return new DimensionDefinition(minimumY, heightRange, cloudHeight, renderClouds);
}

private JoinPackets() {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.viaversion.api.connection.StoredObject;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.libs.fastutil.ints.IntIntImmutablePair;
import com.viaversion.viaversion.libs.fastutil.ints.IntIntPair;
import com.viaversion.viaversion.libs.mcstructs.text.TextComponent;
import net.raphimc.viabedrock.protocol.BedrockProtocol;
import net.raphimc.viabedrock.protocol.model.DimensionDefinition;
import net.raphimc.viabedrock.protocol.data.JavaRegistries;
import net.raphimc.viabedrock.protocol.data.enums.bedrock.generated.ChatRestrictionLevel;
import net.raphimc.viabedrock.protocol.data.enums.bedrock.generated.GameType;
Expand All @@ -38,7 +37,7 @@ public class GameSessionStorage extends StoredObject {

private CompoundTag javaRegistries;
private CompoundTag bedrockBiomeDefinitions = BedrockProtocol.MAPPINGS.getBedrockBiomeDefinitions();
private final Map<String, IntIntPair> bedrockDimensionDefinitions = new HashMap<>();
private final Map<String, DimensionDefinition> bedrockDimensionDefinitions = new HashMap<>();
private final Set<String> availableEntityIdentifiers = new HashSet<>(BedrockProtocol.MAPPINGS.getBedrockEntities().keySet());
private Semver bedrockVanillaVersion;
private boolean flatGenerator;
Expand All @@ -57,7 +56,7 @@ public class GameSessionStorage extends StoredObject {
public GameSessionStorage(final UserConnection user) {
super(user);

this.bedrockDimensionDefinitions.put("minecraft:the_nether", new IntIntImmutablePair(0, 128));
this.bedrockDimensionDefinitions.put("minecraft:the_nether", new DimensionDefinition(0, 128, 0, false));
}

public CompoundTag getJavaRegistries() {
Expand All @@ -76,11 +75,11 @@ public void setBedrockBiomeDefinitions(final CompoundTag bedrockBiomeDefinitions
this.javaRegistries = null;
}

public Map<String, IntIntPair> getBedrockDimensionDefinitions() {
public Map<String, DimensionDefinition> getBedrockDimensionDefinitions() {
return this.bedrockDimensionDefinitions;
}

public void putBedrockDimensionDefinition(final String dimensionIdentifier, final IntIntPair dimensionDefinition) {
public void putBedrockDimensionDefinition(final String dimensionIdentifier, final DimensionDefinition dimensionDefinition) {
this.bedrockDimensionDefinitions.put(dimensionIdentifier, dimensionDefinition);
this.javaRegistries = null;
}
Expand Down
Binary file modified src/main/resources/assets/viabedrock/skin_packs/vanilla.mcpack
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock
* Copyright (C) 2026 RK_01/RaphiMC and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package net.raphimc.viabedrock.api.chunk.blockstate;

import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.viaversion.libs.gson.JsonParser;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;

import static org.junit.jupiter.api.Assertions.*;

final class JsonBlockStateUpgradeSchemaTest {

@Test
void addsLegacyConnectionsWithoutReplacingCurrentConnections() throws Exception {
final JsonBlockStateUpgradeSchema schema;
try (var input = getClass().getResourceAsStream("/assets/viabedrock/block_state_upgrade_schema/0361_1.26.50_to_1.26.60.json")) {
assertNotNull(input);
schema = new JsonBlockStateUpgradeSchema(JsonParser.parseString(new String(input.readAllBytes(), StandardCharsets.UTF_8)).getAsJsonObject());
}
for (final String name : new String[]{"minecraft:redstone_wire", "minecraft:fire", "minecraft:chorus_plant"}) {
final var tag = new CompoundTag();
tag.putString("name", name);
tag.putInt("version", 1 << 24 | 21 << 16 | 60 << 8 | 33);
final var states = new CompoundTag();
final boolean redstone = name.equals("minecraft:redstone_wire");
if (redstone) {
states.putString("redstone_north", "up");
states.putInt("redstone_signal", 12);
} else {
states.putByte("minecraft:connection_north", (byte) 1);
}
tag.put("states", states);
schema.upgrade(tag);
if (redstone) {
assertEquals("up", states.getStringTag("redstone_north").getValue());
assertEquals("none", states.getStringTag("redstone_east").getValue());
assertEquals(12, states.getIntTag("redstone_signal").asInt());
} else {
assertEquals(1, states.getByteTag("minecraft:connection_north").asByte());
assertEquals(0, states.getByteTag("minecraft:connection_east").asByte());
assertEquals(name.equals("minecraft:chorus_plant"), states.contains("minecraft:connection_down"));
}
final var once = tag.copy();
schema.upgrade(tag);
assertEquals(once, tag);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock
* Copyright (C) 2026 RK_01/RaphiMC and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package net.raphimc.viabedrock.api.resourcepack;

import net.raphimc.viabedrock.api.resourcepack.content.ZipContent;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

final class VanillaSkinPackTest {

@Test
void loadsTheBundledSkinAndGeometryUsedDuringLogin() throws Exception {
try (var input = getClass().getResourceAsStream("/assets/viabedrock/skin_packs/vanilla.mcpack")) {
assertNotNull(input);
final var pack = new ResourcePack(new ZipContent(input.readAllBytes()));
final var image = pack.content().getImage("steve.png").getImage();
final var geometry = pack.content().getSortedJson("geometry.json").getAsJsonArray("minecraft:geometry").asList().stream()
.map(element -> element.getAsJsonObject().getAsJsonObject("description"))
.filter(description -> description.get("identifier").getAsString().equals("geometry.humanoid.custom"))
.findFirst().orElseThrow();
assertEquals(image.getWidth(), geometry.get("texture_width").getAsInt());
assertEquals(image.getHeight(), geometry.get("texture_height").getAsInt());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock
* Copyright (C) 2023-2026 RK_01/RaphiMC and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viabedrock.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import net.raphimc.viabedrock.protocol.data.enums.bedrock.generated.PacketCompressionAlgorithm;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

final class CompressionCodecTest {

@Test
void consumesAnUncompressedFrameWithoutDecodingItsPayloadAgain() {
final var channel = new EmbeddedChannel(new CompressionCodec(PacketCompressionAlgorithm.None, 0));
try {
assertTrue(channel.writeInbound(Unpooled.wrappedBuffer(new byte[]{(byte) 0xFF, 1, 3, 5, 7})));
final ByteBuf decoded = channel.readInbound();
try {
assertEquals(4, decoded.readableBytes());
assertEquals(0x01030507, decoded.readInt());
} finally {
decoded.release();
}
assertNull(channel.readInbound());
} finally {
channel.finishAndReleaseAll();
}
}

@Test
void readsTheByteFrameSentinelAfterNegotiatingTheUnsignedShortSentinel() {
final ByteBuf settings = Unpooled.wrappedBuffer(new byte[]{(byte) 0xFF, (byte) 0xFF});
final PacketCompressionAlgorithm negotiated = PacketCompressionAlgorithm.getByValue(settings.readUnsignedShortLE());
settings.release();
final var channel = new EmbeddedChannel(new CompressionCodec(negotiated, 0));
try {
assertTrue(channel.writeInbound(Unpooled.wrappedBuffer(new byte[]{(byte) 0xFF, 1, 3, 5, 7})));
final ByteBuf decoded = channel.readInbound();
try {
assertEquals(4, decoded.readableBytes());
assertEquals(0x01030507, decoded.readInt());
} finally {
decoded.release();
}
assertTrue(channel.writeOutbound(Unpooled.wrappedBuffer(new byte[]{2, 4, 6, 8})));
final ByteBuf encoded = channel.readOutbound();
try {
assertEquals(5, encoded.readableBytes());
assertEquals(0xFF, encoded.readUnsignedByte());
assertEquals(0x02040608, encoded.readInt());
} finally {
encoded.release();
}
} finally {
channel.finishAndReleaseAll();
}
}

}
Loading