ViaLegacy/src/main/java/net/raphimc/vialegacy/protocols/classic/protocolc0_28_30toc0_28_30cpe/Protocolc0_30toc0_30cpe.java

371 lines
22 KiB
Java

/*
* This file is part of ViaLegacy - https://github.com/RaphiMC/ViaLegacy
* Copyright (C) 2023 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.vialegacy.protocols.classic.protocolc0_28_30toc0_28_30cpe;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.BlockChangeRecord;
import com.viaversion.viaversion.api.minecraft.BlockChangeRecord1_8;
import com.viaversion.viaversion.api.minecraft.Position;
import com.viaversion.viaversion.api.platform.providers.ViaProviders;
import com.viaversion.viaversion.api.protocol.AbstractProtocol;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.types.CustomByteType;
import net.raphimc.vialegacy.ViaLegacy;
import net.raphimc.vialegacy.api.data.BlockList1_6;
import net.raphimc.vialegacy.api.model.ChunkCoord;
import net.raphimc.vialegacy.api.model.IdAndData;
import net.raphimc.vialegacy.api.splitter.PreNettySplitter;
import net.raphimc.vialegacy.protocols.alpha.protocola1_0_16_2toa1_0_15.ClientboundPacketsa1_0_15;
import net.raphimc.vialegacy.protocols.alpha.protocola1_0_16_2toa1_0_15.Protocola1_0_16_2toa1_0_15;
import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.ClientboundPacketsc0_28;
import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.ServerboundPacketsc0_28;
import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.data.ClassicBlocks;
import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.model.ClassicLevel;
import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.storage.ClassicBlockRemapper;
import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.storage.ClassicLevelStorage;
import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.storage.ClassicOpLevelStorage;
import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.storage.ClassicProgressStorage;
import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.types.Typesc0_30;
import net.raphimc.vialegacy.protocols.classic.protocolc0_28_30toc0_28_30cpe.data.ClassicProtocolExtension;
import net.raphimc.vialegacy.protocols.classic.protocolc0_28_30toc0_28_30cpe.data.ExtendedClassicBlocks;
import net.raphimc.vialegacy.protocols.classic.protocolc0_28_30toc0_28_30cpe.storage.ExtBlockPermissionsStorage;
import net.raphimc.vialegacy.protocols.classic.protocolc0_28_30toc0_28_30cpe.storage.ExtensionProtocolMetadataStorage;
import net.raphimc.vialegacy.protocols.classic.protocolc0_28_30toc0_28_30cpe.task.ClassicPingTask;
import net.raphimc.vialegacy.protocols.release.protocol1_2_1_3to1_1.types.Types1_1;
import net.raphimc.vialegacy.protocols.release.protocol1_6_2to1_6_1.Protocol1_6_2to1_6_1;
import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.ClientboundPackets1_6_4;
import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Protocolc0_30toc0_30cpe extends AbstractProtocol<ClientboundPacketsc0_30cpe, ClientboundPacketsc0_28, ServerboundPacketsc0_30cpe, ServerboundPacketsc0_28> {
public Protocolc0_30toc0_30cpe() {
super(ClientboundPacketsc0_30cpe.class, ClientboundPacketsc0_28.class, ServerboundPacketsc0_30cpe.class, ServerboundPacketsc0_28.class);
}
@Override
protected void registerPackets() {
this.registerClientbound(ClientboundPacketsc0_30cpe.JOIN_GAME, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
if (wrapper.user().getProtocolInfo().getPipeline().contains(Protocol1_6_2to1_6_1.class)) {
final ExtensionProtocolMetadataStorage protocolMetadataStorage = wrapper.user().get(ExtensionProtocolMetadataStorage.class);
final PacketWrapper brand = PacketWrapper.create(ClientboundPackets1_6_4.PLUGIN_MESSAGE, wrapper.user());
brand.write(Types1_6_4.STRING, "MC|Brand");
final byte[] brandBytes = protocolMetadataStorage.getServerSoftwareName().getBytes(StandardCharsets.UTF_8);
brand.write(Type.SHORT, (short) brandBytes.length); // data length
brand.write(Type.REMAINING_BYTES, brandBytes); // data
wrapper.send(Protocolc0_30toc0_30cpe.class);
brand.send(Protocol1_6_2to1_6_1.class);
wrapper.cancel();
}
});
}
});
this.registerClientbound(ClientboundPacketsc0_30cpe.EXTENSION_PROTOCOL_INFO, null, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
wrapper.cancel();
final ExtensionProtocolMetadataStorage protocolMetadataStorage = wrapper.user().get(ExtensionProtocolMetadataStorage.class);
protocolMetadataStorage.setServerSoftwareName(wrapper.read(Typesc0_30.STRING)); // app name
protocolMetadataStorage.setExtensionCount(wrapper.read(Type.SHORT)); // extension count
final ClassicProgressStorage classicProgressStorage = wrapper.user().get(ClassicProgressStorage.class);
classicProgressStorage.progress = 0;
classicProgressStorage.upperBound = protocolMetadataStorage.getExtensionCount();
classicProgressStorage.status = "Receiving extension list...";
});
}
});
this.registerClientbound(ClientboundPacketsc0_30cpe.EXTENSION_PROTOCOL_ENTRY, null, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
wrapper.cancel();
final ExtensionProtocolMetadataStorage protocolMetadataStorage = wrapper.user().get(ExtensionProtocolMetadataStorage.class);
final String extensionName = wrapper.read(Typesc0_30.STRING); // name
final int extensionVersion = wrapper.read(Type.INT); // version
final ClassicProtocolExtension extension = ClassicProtocolExtension.byName(extensionName);
if (extension != null) {
protocolMetadataStorage.addServerExtension(extension, extensionVersion);
} else {
ViaLegacy.getPlatform().getLogger().warning("Received unknown classic protocol extension: (" + extensionName + " v" + extensionVersion + ")");
}
protocolMetadataStorage.incrementReceivedExtensions();
final ClassicProgressStorage classicProgressStorage = wrapper.user().get(ClassicProgressStorage.class);
classicProgressStorage.progress = protocolMetadataStorage.getReceivedExtensions();
if (protocolMetadataStorage.getReceivedExtensions() >= protocolMetadataStorage.getExtensionCount()) {
classicProgressStorage.status = "Sending extension list...";
final List<ClassicProtocolExtension> supportedExtensions = new ArrayList<>();
for (ClassicProtocolExtension protocolExtension : ClassicProtocolExtension.values()) {
if (protocolExtension.isSupported()) {
supportedExtensions.add(protocolExtension);
}
}
if (supportedExtensions.contains(ClassicProtocolExtension.BLOCK_PERMISSIONS)) {
wrapper.user().put(new ExtBlockPermissionsStorage(wrapper.user()));
}
final PacketWrapper extensionProtocolInfo = PacketWrapper.create(ServerboundPacketsc0_30cpe.EXTENSION_PROTOCOL_INFO, wrapper.user());
extensionProtocolInfo.write(Typesc0_30.STRING, "ClassiCube 1.3.5"); // app name
extensionProtocolInfo.write(Type.SHORT, (short) supportedExtensions.size()); // extension count
extensionProtocolInfo.sendToServer(Protocolc0_30toc0_30cpe.class);
for (ClassicProtocolExtension protocolExtension : supportedExtensions) {
final PacketWrapper extensionProtocolEntry = PacketWrapper.create(ServerboundPacketsc0_30cpe.EXTENSION_PROTOCOL_ENTRY, wrapper.user());
extensionProtocolEntry.write(Typesc0_30.STRING, protocolExtension.getName()); // name
extensionProtocolEntry.write(Type.INT, protocolExtension.getHighestSupportedVersion()); // version
extensionProtocolEntry.sendToServer(Protocolc0_30toc0_30cpe.class);
}
}
});
}
});
this.registerClientbound(ClientboundPacketsc0_30cpe.EXT_CUSTOM_BLOCKS_SUPPORT_LEVEL, null, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
wrapper.cancel();
final byte level = wrapper.read(Type.BYTE); // support level
if (level != 1) {
ViaLegacy.getPlatform().getLogger().info("Classic server supports CustomBlocks level " + level);
}
final PacketWrapper response = PacketWrapper.create(ServerboundPacketsc0_30cpe.EXT_CUSTOM_BLOCKS_SUPPORT_LEVEL, wrapper.user());
response.write(Type.BYTE, (byte) 1); // support level
response.sendToServer(Protocolc0_30toc0_30cpe.class);
});
}
});
this.registerClientbound(ClientboundPacketsc0_30cpe.EXT_HACK_CONTROL, null, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
wrapper.cancel();
final ClassicOpLevelStorage opLevelStorage = wrapper.user().get(ClassicOpLevelStorage.class);
final boolean flying = wrapper.read(Type.BOOLEAN); // flying
final boolean noClip = wrapper.read(Type.BOOLEAN); // no clip
final boolean speed = wrapper.read(Type.BOOLEAN); // speed
final boolean respawn = wrapper.read(Type.BOOLEAN); // respawn key
wrapper.read(Type.BOOLEAN); // third person view
wrapper.read(Type.SHORT); // jump height
opLevelStorage.updateHax(flying, noClip, speed, respawn);
});
}
});
this.registerClientbound(ClientboundPacketsc0_30cpe.EXT_SET_BLOCK_PERMISSION, null, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
wrapper.cancel();
final ExtBlockPermissionsStorage blockPermissionsStorage = wrapper.user().get(ExtBlockPermissionsStorage.class);
final byte blockId = wrapper.read(Type.BYTE); // block id
final boolean canPlace = wrapper.read(Type.BOOLEAN); // can place
final boolean canDelete = wrapper.read(Type.BOOLEAN); // can delete
if (canPlace) {
blockPermissionsStorage.addPlaceable(blockId);
} else {
blockPermissionsStorage.removePlaceable(blockId);
}
if (canDelete) {
blockPermissionsStorage.addBreakable(blockId);
} else {
blockPermissionsStorage.removeBreakable(blockId);
}
});
}
});
this.registerClientbound(ClientboundPacketsc0_30cpe.EXT_BULK_BLOCK_UPDATE, null, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
wrapper.cancel();
final ClassicLevelStorage levelStorage = wrapper.user().get(ClassicLevelStorage.class);
if (levelStorage == null || !levelStorage.hasReceivedLevel()) {
return;
}
final ClassicBlockRemapper remapper = wrapper.user().get(ClassicBlockRemapper.class);
final ClassicLevel level = levelStorage.getClassicLevel();
final int count = wrapper.read(Type.UNSIGNED_BYTE) + 1; // count
final byte[] indices = wrapper.read(new CustomByteType(1024)); // indices
final byte[] blocks = wrapper.read(new CustomByteType(256)); // blocks
if (wrapper.user().getProtocolInfo().getPipeline().contains(Protocola1_0_16_2toa1_0_15.class)) {
final Map<ChunkCoord, List<BlockChangeRecord>> records = new HashMap<>();
for (int i = 0; i < count; i++) {
final int index = (indices[i * 4] & 255) << 24 | (indices[i * 4 + 1] & 255) << 16 | (indices[i * 4 + 2] & 255) << 8 | (indices[i * 4 + 3] & 255);
final Position pos = new Position(index % level.getSizeX(), (index / level.getSizeX()) / level.getSizeZ(), (index / level.getSizeX()) % level.getSizeZ());
final byte blockId = blocks[i];
level.setBlock(pos, blockId);
if (!levelStorage.isChunkLoaded(pos)) continue;
final IdAndData mappedBlock = remapper.getMapper().get(blockId);
records.computeIfAbsent(new ChunkCoord(pos.x() >> 4, pos.z() >> 4), k -> new ArrayList<>()).add(new BlockChangeRecord1_8(pos.x() & 15, pos.y(), pos.z() & 15, mappedBlock.toCompressedData()));
}
for (Map.Entry<ChunkCoord, List<BlockChangeRecord>> entry : records.entrySet()) {
final PacketWrapper multiBlockChange = PacketWrapper.create(ClientboundPacketsa1_0_15.MULTI_BLOCK_CHANGE, wrapper.user());
multiBlockChange.write(Type.INT, entry.getKey().chunkX); // chunkX
multiBlockChange.write(Type.INT, entry.getKey().chunkZ); // chunkZ
multiBlockChange.write(Types1_1.BLOCK_CHANGE_RECORD_ARRAY, entry.getValue().toArray(new BlockChangeRecord[0])); // blockChangeRecords
multiBlockChange.send(Protocola1_0_16_2toa1_0_15.class);
}
}
});
}
});
this.registerClientbound(ClientboundPacketsc0_30cpe.EXT_TWO_WAY_PING, ClientboundPacketsc0_28.KEEP_ALIVE, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
final byte direction = wrapper.read(Type.BYTE); // direction
final short data = wrapper.read(Type.SHORT); // data
if (direction == 1) {
final PacketWrapper pingResponse = PacketWrapper.create(ServerboundPacketsc0_30cpe.EXT_TWO_WAY_PING, wrapper.user());
pingResponse.write(Type.BYTE, direction); // direction
pingResponse.write(Type.SHORT, data); // data
pingResponse.sendToServer(Protocolc0_30toc0_30cpe.class);
}
});
}
});
this.registerServerbound(State.LOGIN, ServerboundPacketsc0_28.LOGIN.getId(), ServerboundPacketsc0_30cpe.LOGIN.getId(), new PacketHandlers() {
@Override
public void register() {
map(Type.BYTE); // protocol id
map(Typesc0_30.STRING); // username
map(Typesc0_30.STRING); // mp pass
map(Type.BYTE); // op level
handler(wrapper -> {
wrapper.set(Type.BYTE, 1, (byte) 0x42); // extension protocol magic number
});
}
});
this.registerServerbound(ServerboundPacketsc0_28.CHAT_MESSAGE, new PacketHandlers() {
@Override
public void register() {
map(Type.BYTE); // sender id
map(Typesc0_30.STRING); // message
handler(wrapper -> {
final ExtensionProtocolMetadataStorage protocolMetadata = wrapper.user().get(ExtensionProtocolMetadataStorage.class);
if (!protocolMetadata.hasServerExtension(ClassicProtocolExtension.LONGER_MESSAGES, 1)) return;
wrapper.cancel();
String message = wrapper.get(Typesc0_30.STRING, 0);
while (!message.isEmpty()) {
final int pos = Math.min(message.length(), 64);
final String msg = message.substring(0, pos);
message = message.substring(pos);
final PacketWrapper chatMessage = PacketWrapper.create(ServerboundPacketsc0_30cpe.CHAT_MESSAGE, wrapper.user());
chatMessage.write(Type.BYTE, (byte) (!message.isEmpty() ? 1 : 0)); // 1 = more parts | 0 = last part
chatMessage.write(Typesc0_30.STRING, msg); // message
chatMessage.sendToServer(Protocolc0_30toc0_30cpe.class);
}
});
}
});
this.registerServerbound(ServerboundPacketsc0_28.PLAYER_BLOCK_PLACEMENT, new PacketHandlers() {
@Override
public void register() {
map(Typesc0_30.POSITION); // position
map(Type.BOOLEAN); // place block
map(Type.BYTE); // block id
handler(wrapper -> {
if (!wrapper.user().has(ExtBlockPermissionsStorage.class)) return;
final ExtBlockPermissionsStorage blockPermissions = wrapper.user().get(ExtBlockPermissionsStorage.class);
final ClassicLevel level = wrapper.user().get(ClassicLevelStorage.class).getClassicLevel();
final Position position = wrapper.get(Typesc0_30.POSITION, 0);
final boolean placeBlock = wrapper.get(Type.BOOLEAN, 0);
final int blockId = wrapper.get(Type.BYTE, 0);
int block = level.getBlock(position);
final boolean disallow = (placeBlock && !blockPermissions.isPlacingAllowed(blockId)) || (!placeBlock && !blockPermissions.isBreakingAllowed(block));
if (disallow) {
wrapper.cancel();
final PacketWrapper chatMessage = PacketWrapper.create(ClientboundPacketsc0_30cpe.CHAT_MESSAGE, wrapper.user());
chatMessage.write(Type.BYTE, (byte) 0); // sender id
chatMessage.write(Typesc0_30.STRING, "&cYou are not allowed to place/break this block"); // message
chatMessage.send(Protocolc0_30toc0_30cpe.class);
} else {
block = placeBlock ? blockId : ClassicBlocks.AIR;
level.setBlock(position, block);
}
final PacketWrapper blockChange = PacketWrapper.create(ClientboundPacketsc0_30cpe.BLOCK_CHANGE, wrapper.user());
blockChange.write(Typesc0_30.POSITION, position); // position
blockChange.write(Type.BYTE, (byte) block); // block id
blockChange.send(Protocolc0_30toc0_30cpe.class);
});
}
});
}
@Override
public void register(ViaProviders providers) {
Via.getPlatform().runRepeatingSync(new ClassicPingTask(), 20L);
}
@Override
public void init(UserConnection userConnection) {
userConnection.put(new PreNettySplitter(userConnection, Protocolc0_30toc0_30cpe.class, ClientboundPacketsc0_30cpe::getPacket));
userConnection.put(new ExtensionProtocolMetadataStorage(userConnection));
final ClassicBlockRemapper previousRemapper = userConnection.get(ClassicBlockRemapper.class);
userConnection.put(new ClassicBlockRemapper(userConnection, i -> {
if (ClassicBlocks.MAPPING.containsKey(i)) return previousRemapper.getMapper().get(i);
final ExtensionProtocolMetadataStorage extensionProtocol = userConnection.get(ExtensionProtocolMetadataStorage.class);
if (extensionProtocol.hasServerExtension(ClassicProtocolExtension.CUSTOM_BLOCKS, 1)) {
return ExtendedClassicBlocks.MAPPING.get(i);
}
return new IdAndData(BlockList1_6.stone.blockID, 0);
}, o -> {
if (ClassicBlocks.REVERSE_MAPPING.containsKey(o)) return previousRemapper.getReverseMapper().getInt(o);
final ExtensionProtocolMetadataStorage extensionProtocol = userConnection.get(ExtensionProtocolMetadataStorage.class);
if (extensionProtocol.hasServerExtension(ClassicProtocolExtension.CUSTOM_BLOCKS, 1)) {
return ExtendedClassicBlocks.REVERSE_MAPPING.getInt(o);
}
return ClassicBlocks.STONE;
}));
}
}