Skip to content

Commit e073ec8

Browse files
committed
Respawn bedrock players after entering the server address to close the chat screen
1 parent 87d7ee2 commit e073ec8

File tree

8 files changed

+164
-50
lines changed

8 files changed

+164
-50
lines changed

src/main/java/net/lenni0451/miniconnect/server/protocol/LobbyPacketRegistry.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public LobbyPacketRegistry() {
3636
this.registerPacket(MCPackets.S2C_SET_EQUIPMENT, S2CSetEquipmentPacket::new);
3737
this.registerPacket(MCPackets.S2C_CONTAINER_SET_DATA, S2CContainerSetDataPacket::new);
3838
this.registerPacket(MCPackets.S2C_PLAYER_ABILITIES, S2CPlayerAbilitiesPacket::new);
39+
this.registerPacket(MCPackets.S2C_RESPAWN, S2CRespawnPacket::new);
3940
}
4041

4142
}

src/main/java/net/lenni0451/miniconnect/server/protocol/ProtocolConstants.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.Protocol1_21_4To1_21_2;
44
import com.viaversion.viaversion.api.Via;
5+
import com.viaversion.viaversion.api.minecraft.chunks.*;
56
import com.viaversion.viaversion.api.protocol.Protocol;
67
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
78
import lombok.SneakyThrows;
@@ -12,13 +13,17 @@
1213
import net.lenni0451.mcstructs.nbt.io.NbtReadTracker;
1314
import net.lenni0451.mcstructs.nbt.tags.CompoundTag;
1415
import net.lenni0451.mcstructs.text.serializer.TextComponentCodec;
16+
import net.lenni0451.miniconnect.server.protocol.packets.model.CommonPlayerSpawnInfo;
17+
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.S2CGameEventPacket;
18+
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.S2CLevelChunkWithLightPacket;
19+
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.S2CPlayerAbilitiesPacket;
20+
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.S2CPlayerPositionPacket;
21+
import net.lenni0451.miniconnect.server.states.StateHandler;
22+
import net.raphimc.viabedrock.protocol.data.enums.java.GameEventType;
1523

1624
import java.io.InputStream;
1725
import java.io.InputStreamReader;
18-
import java.util.Arrays;
19-
import java.util.HashMap;
20-
import java.util.List;
21-
import java.util.Map;
26+
import java.util.*;
2227
import java.util.function.Function;
2328

2429
public class ProtocolConstants {
@@ -32,6 +37,7 @@ public class ProtocolConstants {
3237
public static final int CHUNK_SECTION_COUNT = 16; //16 for the end
3338
public static final byte[] FULL_LIGHT = new byte[2048];
3439
public static final Protocol<?, ?, ?, ?> VIA_PROTOCOL = Via.getManager().getProtocolManager().getProtocol(Protocol1_21_4To1_21_2.class);
40+
public static final CommonPlayerSpawnInfo DEFAULT_SPAWN_INFO = new CommonPlayerSpawnInfo(3, "minecraft:the_end", 0, 3, 0, false, false, null, 0, 0);
3541

3642
static {
3743
REGISTRIES = readCompound("registries.nbt", tag -> {
@@ -71,4 +77,21 @@ private static <T> T readJson(final String name, final Function<GsonElement, T>
7177
return mapper.apply(GsonParser.parse(new InputStreamReader(stream)));
7278
}
7379

80+
public static void sendSpawnInfo(final StateHandler stateHandler) {
81+
stateHandler.send(new S2CPlayerAbilitiesPacket(true, true, true, false, 0, 0));
82+
stateHandler.send(new S2CGameEventPacket(GameEventType.LEVEL_CHUNKS_LOAD_START.ordinal(), 0));
83+
for (int i = 0; i < 9; i++) {
84+
Chunk chunk = new Chunk1_18(i % 3, i / 3, new ChunkSection[ProtocolConstants.CHUNK_SECTION_COUNT], new com.viaversion.nbt.tag.CompoundTag(), new ArrayList<>());
85+
for (int s = 0; s < chunk.getSections().length; s++) {
86+
ChunkSection section = new ChunkSectionImpl(false);
87+
chunk.getSections()[s] = section;
88+
section.palette(PaletteType.BLOCKS).addId(0);
89+
section.addPalette(PaletteType.BIOMES, new DataPaletteImpl(ChunkSection.BIOME_SIZE));
90+
section.palette(PaletteType.BIOMES).addId(0);
91+
}
92+
stateHandler.send(new S2CLevelChunkWithLightPacket(chunk));
93+
}
94+
stateHandler.send(new S2CPlayerPositionPacket(0, 24, 1, 24, 0, 0, 0, 0, 0, 0));
95+
}
96+
7497
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package net.lenni0451.miniconnect.server.protocol.packets.model;
2+
3+
import com.viaversion.viaversion.api.minecraft.GlobalBlockPosition;
4+
import com.viaversion.viaversion.api.type.Types;
5+
import io.netty.buffer.ByteBuf;
6+
import lombok.AllArgsConstructor;
7+
import lombok.NoArgsConstructor;
8+
import net.raphimc.netminecraft.packet.PacketTypes;
9+
10+
@NoArgsConstructor
11+
@AllArgsConstructor
12+
public class CommonPlayerSpawnInfo {
13+
14+
public int dimension;
15+
public String world;
16+
public long levelSeed;
17+
public int gamemode;
18+
public int previousGamemode;
19+
public boolean debugWorld;
20+
public boolean flatWorld;
21+
public GlobalBlockPosition lastDeath;
22+
public int portalCooldown;
23+
public int seaLevel;
24+
25+
public void write(final ByteBuf byteBuf) {
26+
PacketTypes.writeVarInt(byteBuf, this.dimension);
27+
PacketTypes.writeString(byteBuf, this.world);
28+
byteBuf.writeLong(this.levelSeed);
29+
byteBuf.writeByte(this.gamemode);
30+
byteBuf.writeByte(this.previousGamemode);
31+
byteBuf.writeBoolean(this.debugWorld);
32+
byteBuf.writeBoolean(this.flatWorld);
33+
Types.OPTIONAL_GLOBAL_POSITION.write(byteBuf, this.lastDeath);
34+
PacketTypes.writeVarInt(byteBuf, this.portalCooldown);
35+
PacketTypes.writeVarInt(byteBuf, this.seaLevel);
36+
}
37+
38+
public CommonPlayerSpawnInfo copy() {
39+
return new CommonPlayerSpawnInfo(this.dimension, this.world, this.levelSeed, this.gamemode, this.previousGamemode, this.debugWorld, this.flatWorld, this.lastDeath, this.portalCooldown, this.seaLevel);
40+
}
41+
42+
}

src/main/java/net/lenni0451/miniconnect/server/protocol/packets/play/s2c/S2CLoginPacket.java

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package net.lenni0451.miniconnect.server.protocol.packets.play.s2c;
22

3-
import com.viaversion.viaversion.api.minecraft.GlobalBlockPosition;
4-
import com.viaversion.viaversion.api.type.Types;
53
import io.netty.buffer.ByteBuf;
64
import lombok.AllArgsConstructor;
75
import lombok.NoArgsConstructor;
86
import net.lenni0451.miniconnect.server.protocol.ProtocolConstants;
7+
import net.lenni0451.miniconnect.server.protocol.packets.model.CommonPlayerSpawnInfo;
98
import net.raphimc.netminecraft.packet.Packet;
109
import net.raphimc.netminecraft.packet.PacketTypes;
1110

@@ -21,16 +20,7 @@ public class S2CLoginPacket implements Packet {
2120
public boolean reduceDebugInfo;
2221
public boolean showDeathScreen;
2322
public boolean doLimitedCrafting;
24-
public int dimension;
25-
public String world;
26-
public long levelSeed;
27-
public int gamemode;
28-
public int previousGamemode;
29-
public boolean debugWorld;
30-
public boolean flatWorld;
31-
public GlobalBlockPosition lastDeath;
32-
public int portalCooldown;
33-
public int seaLevel;
23+
public CommonPlayerSpawnInfo spawnInfo;
3424
public boolean enforceSecureChat;
3525

3626
@Override
@@ -52,16 +42,7 @@ public void write(ByteBuf byteBuf, int protocolVersion) {
5242
byteBuf.writeBoolean(this.reduceDebugInfo);
5343
byteBuf.writeBoolean(this.showDeathScreen);
5444
byteBuf.writeBoolean(this.doLimitedCrafting);
55-
PacketTypes.writeVarInt(byteBuf, this.dimension);
56-
PacketTypes.writeString(byteBuf, this.world);
57-
byteBuf.writeLong(this.levelSeed);
58-
byteBuf.writeByte(this.gamemode);
59-
byteBuf.writeByte(this.previousGamemode);
60-
byteBuf.writeBoolean(this.debugWorld);
61-
byteBuf.writeBoolean(this.flatWorld);
62-
Types.OPTIONAL_GLOBAL_POSITION.write(byteBuf, this.lastDeath);
63-
PacketTypes.writeVarInt(byteBuf, this.portalCooldown);
64-
PacketTypes.writeVarInt(byteBuf, this.seaLevel);
45+
this.spawnInfo.write(byteBuf);
6546
byteBuf.writeBoolean(this.enforceSecureChat);
6647
}
6748

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package net.lenni0451.miniconnect.server.protocol.packets.play.s2c;
2+
3+
import io.netty.buffer.ByteBuf;
4+
import lombok.AllArgsConstructor;
5+
import lombok.NoArgsConstructor;
6+
import net.lenni0451.miniconnect.server.protocol.packets.model.CommonPlayerSpawnInfo;
7+
import net.raphimc.netminecraft.packet.Packet;
8+
9+
@NoArgsConstructor
10+
@AllArgsConstructor
11+
public class S2CRespawnPacket implements Packet {
12+
13+
public static final byte KEEP_ATTRIBUTE_MODIFIERS = 0b01;
14+
public static final byte KEEP_ENTITY_DATA = 0b10;
15+
public static final byte KEEP_ALL_DATA = 0b11;
16+
17+
public CommonPlayerSpawnInfo spawnInfo;
18+
public byte dataToKeep;
19+
20+
@Override
21+
public void read(ByteBuf byteBuf, int i) {
22+
throw new UnsupportedOperationException();
23+
}
24+
25+
@Override
26+
public void write(ByteBuf byteBuf, int i) {
27+
this.spawnInfo.write(byteBuf);
28+
byteBuf.writeByte(this.dataToKeep);
29+
}
30+
31+
}

src/main/java/net/lenni0451/miniconnect/server/states/PlayStateHandler.java

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
package net.lenni0451.miniconnect.server.states;
22

3-
import com.viaversion.nbt.tag.CompoundTag;
4-
import com.viaversion.viaversion.api.minecraft.chunks.*;
53
import io.netty.channel.Channel;
64
import net.lenni0451.lambdaevents.EventHandler;
75
import net.lenni0451.miniconnect.server.LobbyServerHandler;
86
import net.lenni0451.miniconnect.server.protocol.ProtocolConstants;
97
import net.lenni0451.miniconnect.server.protocol.packets.play.c2s.C2SChatCommandPacket;
108
import net.lenni0451.miniconnect.server.protocol.packets.play.c2s.C2SChatPacket;
11-
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.*;
9+
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.S2CKeepAlivePacket;
10+
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.S2CLoginPacket;
1211
import net.lenni0451.miniconnect.server.states.play.screen.ScreenHandler;
1312
import net.lenni0451.miniconnect.server.states.play.screen.impl.MainScreen;
14-
import net.raphimc.viabedrock.protocol.data.enums.java.GameEventType;
1513

16-
import java.util.ArrayList;
1714
import java.util.function.Function;
1815

1916
public class PlayStateHandler extends StateHandler {
@@ -27,21 +24,8 @@ public PlayStateHandler(final LobbyServerHandler handler, final Channel channel)
2724
}
2825

2926
private void init() {
30-
this.send(new S2CLoginPacket(0, false, 1, 1, 1, false, false, false, 3, "minecraft:the_end", 0, 3, 0, false, false, null, 0, 0, false));
31-
this.send(new S2CPlayerAbilitiesPacket(true, true, true, false, 0, 0));
32-
this.send(new S2CGameEventPacket(GameEventType.LEVEL_CHUNKS_LOAD_START.ordinal(), 0));
33-
for (int i = 0; i < 9; i++) {
34-
Chunk chunk = new Chunk1_18(i % 3, i / 3, new ChunkSection[ProtocolConstants.CHUNK_SECTION_COUNT], new CompoundTag(), new ArrayList<>());
35-
for (int s = 0; s < chunk.getSections().length; s++) {
36-
ChunkSection section = new ChunkSectionImpl(false);
37-
chunk.getSections()[s] = section;
38-
section.palette(PaletteType.BLOCKS).addId(0);
39-
section.addPalette(PaletteType.BIOMES, new DataPaletteImpl(ChunkSection.BIOME_SIZE));
40-
section.palette(PaletteType.BIOMES).addId(0);
41-
}
42-
this.send(new S2CLevelChunkWithLightPacket(chunk));
43-
}
44-
this.send(new S2CPlayerPositionPacket(0, 24, 1, 24, 0, 0, 0, 0, 0, 0));
27+
this.send(new S2CLoginPacket(0, false, 1, 1, 1, false, false, false, ProtocolConstants.DEFAULT_SPAWN_INFO, false));
28+
ProtocolConstants.sendSpawnInfo(this);
4529

4630
this.screenHandler = new ScreenHandler(this);
4731
this.handlerManager.register(this.screenHandler);

src/main/java/net/lenni0451/miniconnect/server/states/play/screen/impl/MainScreen.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@
1717
import net.lenni0451.miniconnect.Main;
1818
import net.lenni0451.miniconnect.server.model.PlayerConfig;
1919
import net.lenni0451.miniconnect.server.protocol.ProtocolConstants;
20-
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.S2CContainerSetContentPacket;
21-
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.S2COpenBookPacket;
22-
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.S2CSystemChatPacket;
23-
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.S2CTransferPacket;
20+
import net.lenni0451.miniconnect.server.protocol.packets.model.CommonPlayerSpawnInfo;
21+
import net.lenni0451.miniconnect.server.protocol.packets.play.s2c.*;
2422
import net.lenni0451.miniconnect.server.states.play.Tutorial;
2523
import net.lenni0451.miniconnect.server.states.play.screen.ItemList;
2624
import net.lenni0451.miniconnect.server.states.play.screen.Items;
2725
import net.lenni0451.miniconnect.server.states.play.screen.Screen;
2826
import net.lenni0451.miniconnect.server.states.play.screen.ScreenHandler;
2927
import net.lenni0451.miniconnect.utils.ChannelUtils;
28+
import net.lenni0451.miniconnect.utils.GeyserAPI;
3029
import net.lenni0451.miniconnect.utils.InetUtils;
3130
import net.raphimc.minecraftauth.MinecraftAuth;
3231
import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode;
@@ -72,6 +71,18 @@ public void init(ScreenHandler screenHandler, ItemList itemList) {
7271
if (InetUtils.isLocal(InetAddress.getByName(hostAndPort.getHost()))) throw new IllegalArgumentException();
7372
playerConfig.serverAddress = hostAndPort.getHost();
7473
playerConfig.serverPort = hostAndPort.getPortOrDefault(-1);
74+
if (GeyserAPI.isGeyserPlayer(playerConfig.uuid)) {
75+
//Respawn the player two times to force the chat to close
76+
//This only needs to be done because bedrock doesn't allow closing the chat otherwise
77+
CommonPlayerSpawnInfo spawnInfo = ProtocolConstants.DEFAULT_SPAWN_INFO.copy();
78+
spawnInfo.dimension = 2;
79+
spawnInfo.world = "minecraft:the_nether";
80+
spawnInfo.previousGamemode = 3;
81+
screenHandler.getStateHandler().send(new S2CRespawnPacket(spawnInfo, S2CRespawnPacket.KEEP_ALL_DATA));
82+
ProtocolConstants.sendSpawnInfo(screenHandler.getStateHandler());
83+
screenHandler.getStateHandler().send(new S2CRespawnPacket(ProtocolConstants.DEFAULT_SPAWN_INFO, S2CRespawnPacket.KEEP_ALL_DATA));
84+
ProtocolConstants.sendSpawnInfo(screenHandler.getStateHandler());
85+
}
7586
} catch (Throwable t) {
7687
screenHandler.getStateHandler().send(new S2CSystemChatPacket(new StringComponent("§cInvalid server address"), false));
7788
return false;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package net.lenni0451.miniconnect.utils;
2+
3+
import net.lenni0451.reflect.accessor.MethodAccessor;
4+
import net.raphimc.viaproxy.ViaProxy;
5+
import net.raphimc.viaproxy.plugins.ViaProxyPlugin;
6+
import net.raphimc.viaproxy.util.logging.Logger;
7+
8+
import java.lang.reflect.Method;
9+
import java.util.UUID;
10+
import java.util.function.Predicate;
11+
12+
/**
13+
* Use reflection to access the Geyser API because directly invoking it doesn't work because of the classloader isolation.
14+
*/
15+
public class GeyserAPI {
16+
17+
private static final Predicate<UUID> isBedrockPlayer;
18+
19+
static {
20+
Predicate<UUID> tempIsBedrockPlayer = uuid -> false;
21+
ViaProxyPlugin plugin = ViaProxy.getPluginManager().getPlugin("Geyser-ViaProxy");
22+
if (plugin != null) {
23+
try {
24+
ClassLoader geyserClassLoader = plugin.getClassLoader();
25+
Class<?> geyserClass = Class.forName("org.geysermc.api.Geyser", false, geyserClassLoader);
26+
Method apiMethod = geyserClass.getDeclaredMethod("api");
27+
Object geyserApiInstance = apiMethod.invoke(null);
28+
Method isBedrockPlayerMethod = geyserApiInstance.getClass().getDeclaredMethod("isBedrockPlayer", UUID.class);
29+
tempIsBedrockPlayer = MethodAccessor.makeInvoker(Predicate.class, geyserApiInstance, isBedrockPlayerMethod);
30+
} catch (Throwable t) {
31+
Logger.LOGGER.error("Failed to initialize Geyser-ViaProxy support", t);
32+
}
33+
}
34+
isBedrockPlayer = tempIsBedrockPlayer;
35+
}
36+
37+
public static boolean isGeyserPlayer(final UUID uuid) {
38+
return isBedrockPlayer.test(uuid);
39+
}
40+
41+
}

0 commit comments

Comments
 (0)