Introduce support for Minecraft 1.21: Add ReflectionWrapper21, ChunkHider21, and enhance version compatibility across systems.

This commit is contained in:
2025-07-31 10:43:40 +02:00
parent 297aa6151d
commit cf52b50333
19 changed files with 448 additions and 27 deletions
@@ -37,4 +37,7 @@ dependencies {
compileOnly(libs.paperapi21)
compileOnly(libs.nms21)
compileOnly(libs.datafixer)
compileOnly(libs.netty)
compileOnly(libs.authlib)
}
@@ -0,0 +1,155 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.techhider;
import de.steamwar.Reflection;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.util.SimpleBitStorage;
import net.minecraft.world.level.block.entity.BlockEntityType;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
public class ChunkHider21 implements ChunkHider {
@Override
public Class<?> mapChunkPacket() {
return ClientboundLevelChunkWithLightPacket.class;
}
private static final UnaryOperator<Object> chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class);
private static final UnaryOperator<Object> chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class);
private static final Reflection.Field<Integer> chunkXField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 0);
private static final Reflection.Field<Integer> chunkZField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 1);
private static final Reflection.Field<ClientboundLevelChunkPacketData> chunkData = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0);
private static final Reflection.Field<byte[]> dataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0);
private static final Reflection.Field<List> tileEntities = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0);
@Override
public BiFunction<Player, Object, Object> chunkHiderGenerator(TechHider techHider) {
return (p, packet) -> {
int chunkX = chunkXField.get(packet);
int chunkZ = chunkZField.get(packet);
if (techHider.getLocationEvaluator().skipChunk(p, chunkX, chunkZ))
return packet;
packet = chunkPacketCloner.apply(packet);
Object dataWrapper = chunkDataCloner.apply(chunkData.get(packet));
Set<String> hiddenBlockEntities = techHider.getHiddenBlockEntities();
tileEntities.set(dataWrapper, ((List<?>)tileEntities.get(dataWrapper)).stream().filter(te -> tileEntityVisible(hiddenBlockEntities, te)).collect(Collectors.toList()));
ByteBuf in = Unpooled.wrappedBuffer(dataField.get(dataWrapper));
ByteBuf out = Unpooled.buffer(in.readableBytes() + 64);
for(int yOffset = p.getWorld().getMinHeight(); yOffset < p.getWorld().getMaxHeight(); yOffset += 16) {
SectionHider section = new SectionHider(p, techHider, in, out, chunkX, yOffset/16, chunkZ);
section.copyBlockCount();
blocks(section);
biomes(section);
}
if (in.readableBytes() != 0) {
throw new IllegalStateException("ChunkHider21: Incomplete chunk data, " + in.readableBytes() + " bytes left");
}
byte[] data = new byte[out.readableBytes()];
out.readBytes(data);
dataField.set(dataWrapper, data);
chunkData.set(packet, dataWrapper);
return packet;
};
}
public static final Class<?> tileEntity = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo");
protected static final Reflection.Field<BlockEntityType> entityType = Reflection.getField(tileEntity, BlockEntityType.class, 0);
private static final Class<?> builtInRegestries = Reflection.getClass("net.minecraft.core.registries.BuiltInRegistries");
private static final Class<?> registry = Reflection.getClass("net.minecraft.core.Registry");
private static final Reflection.Field<?> nameField = Reflection.getField(builtInRegestries, "BLOCK_ENTITY_TYPE", registry);
private static final Class<?> resourceLocation = Reflection.getClass("net.minecraft.resources.ResourceLocation");
private static final Reflection.Method getKey = Reflection.getTypedMethod(registry, "getKey", resourceLocation, Object.class);
private static final Reflection.Method getName = Reflection.getTypedMethod(resourceLocation, "getPath", String.class);
protected boolean tileEntityVisible(Set<String> hiddenBlockEntities, Object tile) {
return !hiddenBlockEntities.contains(getName.invoke(getKey.invoke(nameField.get(null), entityType.get(tile))));
}
private void blocks(SectionHider section) {
section.copyBitsPerBlock();
boolean singleValued = section.getBitsPerBlock() == 0;
if (singleValued) {
int value = ProtocolUtils.readVarInt(section.getIn());
ProtocolUtils.writeVarInt(section.getOut(), !section.isSkipSection() && section.getObfuscate().contains(value) ? section.getTarget() : value);
return;
} else {
section.processPalette();
}
if (section.isSkipSection() || (!section.blockPrecise() && section.isPaletted())) {
section.skipNewDataArray(4096);
return;
}
SimpleBitStorage values = new SimpleBitStorage(section.getBitsPerBlock(), 4096, section.readNewDataArray(4096));
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
int pos = (((y * 16) + z) * 16) + x;
switch (section.test(x, y, z)) {
case SKIP:
break;
case CHECK:
if (!section.getObfuscate().contains(values.get(pos)))
break;
case HIDE:
values.set(pos, section.getTarget());
break;
case HIDE_AIR:
default:
values.set(pos, section.getAir());
}
}
}
}
section.writeDataArray(values.getRaw());
}
private void biomes(SectionHider section) {
section.copyBitsPerBlock();
if(section.getBitsPerBlock() == 0) {
section.copyVarInt();
} else if(section.getBitsPerBlock() < 6) {
section.skipPalette();
section.skipNewDataArray(64);
}
}
}