forked from SteamWar/SteamWar
Implement basic techhider
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
steamwar.java
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":CommonCore", "default"))
|
||||
compileOnly(project(":HiderCore:HiderCore_TechHider", "default"))
|
||||
compileOnly(project(":SpigotCore:SpigotCore_Main", "default"))
|
||||
|
||||
compileOnly(libs.nms18)
|
||||
compileOnly(libs.netty)
|
||||
compileOnly(libs.spigotapi)
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
This file is a part of the SteamWar software.
|
||||
|
||||
Copyright (C) 2021 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 de.steamwar.core.Core;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.core.IRegistry;
|
||||
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
|
||||
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
||||
import net.minecraft.resources.MinecraftKey;
|
||||
import net.minecraft.util.SimpleBitStorage;
|
||||
import net.minecraft.world.level.block.entity.TileEntityTypes;
|
||||
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 ChunkHider18 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);
|
||||
}
|
||||
out.writeBytes(in); // MC appends a 0 byte at the end if there is a full chunk, idk why
|
||||
|
||||
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<TileEntityTypes> entityType = Reflection.getField(tileEntity, TileEntityTypes.class, 0);
|
||||
private static final IRegistry<?> tileEntityTypes = Reflection.getField(Core.getVersion() > 18 ? Reflection.getClass("net.minecraft.core.registries.BuiltInRegistries") : IRegistry.class, IRegistry.class, 0, TileEntityTypes.class).get(null);
|
||||
private static final Reflection.Method getKey = Reflection.getTypedMethod(IRegistry.class, null, MinecraftKey.class, Object.class);
|
||||
private static final Reflection.Method getName = Reflection.getTypedMethod(MinecraftKey.class, null, String.class);
|
||||
protected boolean tileEntityVisible(Set<String> hiddenBlockEntities, Object tile) {
|
||||
return !hiddenBlockEntities.contains((String) getName.invoke(getKey.invoke(tileEntityTypes, entityType.get(tile))));
|
||||
}
|
||||
|
||||
private void blocks(SectionHider section) {
|
||||
section.copyBitsPerBlock();
|
||||
|
||||
boolean singletonPalette = section.getBitsPerBlock() == 0;
|
||||
if(singletonPalette) {
|
||||
int value = ProtocolUtils.readVarInt(section.getIn());
|
||||
ProtocolUtils.writeVarInt(section.getOut(), !section.isSkipSection() && section.getObfuscate().contains(value) ? section.getTarget() : value);
|
||||
}else if(section.getBitsPerBlock() < 15) {
|
||||
section.processPalette();
|
||||
}
|
||||
|
||||
if(section.isSkipSection() || singletonPalette || (!section.blockPrecise() && section.isPaletted())) {
|
||||
section.skipDataArray();
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleBitStorage values = new SimpleBitStorage(section.getBitsPerBlock(), 4096, section.readDataArray());
|
||||
|
||||
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.a(pos)))
|
||||
break;
|
||||
case HIDE:
|
||||
values.b(pos, section.getTarget());
|
||||
break;
|
||||
case HIDE_AIR:
|
||||
default:
|
||||
values.b(pos, section.getAir());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section.writeDataArray(values.a());
|
||||
}
|
||||
|
||||
private void biomes(SectionHider section) {
|
||||
section.copyBitsPerBlock();
|
||||
boolean singletonPalette = section.getBitsPerBlock() == 0;
|
||||
if(singletonPalette)
|
||||
section.copyVarInt();
|
||||
else if(section.getBitsPerBlock() < 6)
|
||||
section.skipPalette();
|
||||
|
||||
section.skipDataArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
This file is a part of the SteamWar software.
|
||||
|
||||
Copyright (C) 2021 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 net.minecraft.core.SectionPosition;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutBlockBreak;
|
||||
import net.minecraft.world.level.block.entity.TileEntitySign;
|
||||
import net.minecraft.world.level.block.entity.TileEntityTypes;
|
||||
import net.minecraft.world.level.block.state.IBlockData;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class ProtocolWrapper18 implements ProtocolWrapper {
|
||||
|
||||
private static final Reflection.Field<SectionPosition> multiBlockChangeChunk = Reflection.getField(TechHider.multiBlockChangePacket, SectionPosition.class, 0);
|
||||
private static final Reflection.Field<short[]> multiBlockChangePos = Reflection.getField(TechHider.multiBlockChangePacket, short[].class, 0);
|
||||
private static final Reflection.Field<IBlockData[]> multiBlockChangeBlocks = Reflection.getField(TechHider.multiBlockChangePacket, IBlockData[].class, 0);
|
||||
@Override
|
||||
public BiFunction<Player, Object, Object> multiBlockChangeGenerator(TechHider techHider) {
|
||||
return (p, packet) -> {
|
||||
TechHider.LocationEvaluator locationEvaluator = techHider.getLocationEvaluator();
|
||||
Object chunkCoords = multiBlockChangeChunk.get(packet);
|
||||
int chunkX = TechHider.blockPositionX.get(chunkCoords);
|
||||
int chunkY = TechHider.blockPositionY.get(chunkCoords);
|
||||
int chunkZ = TechHider.blockPositionZ.get(chunkCoords);
|
||||
if(locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ))
|
||||
return packet;
|
||||
|
||||
packet = TechHider.multiBlockChangeCloner.apply(packet);
|
||||
final short[] oldPos = multiBlockChangePos.get(packet);
|
||||
final IBlockData[] oldBlocks = multiBlockChangeBlocks.get(packet);
|
||||
ArrayList<Short> poss = new ArrayList<>(oldPos.length);
|
||||
ArrayList<IBlockData> blocks = new ArrayList<>(oldPos.length);
|
||||
for(int i = 0; i < oldPos.length; i++) {
|
||||
short pos = oldPos[i];
|
||||
IBlockData block = oldBlocks[i];
|
||||
switch(locationEvaluator.check(p, 16*chunkX + (pos >> 8 & 0xF), 16*chunkY + (pos & 0xF), 16*chunkZ + (pos >> 4 & 0xF))) {
|
||||
case SKIP:
|
||||
poss.add(pos);
|
||||
blocks.add(block);
|
||||
break;
|
||||
case CHECK:
|
||||
poss.add(pos);
|
||||
blocks.add(techHider.iBlockDataHidden(block) ? (IBlockData) techHider.getObfuscationTarget() : block);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(blocks.isEmpty())
|
||||
return null;
|
||||
|
||||
short[] newPos = new short[poss.size()];
|
||||
for(int i = 0; i < newPos.length; i++)
|
||||
newPos[i] = poss.get(i);
|
||||
|
||||
multiBlockChangePos.set(packet, newPos);
|
||||
multiBlockChangeBlocks.set(packet, blocks.toArray(new IBlockData[0]));
|
||||
return packet;
|
||||
};
|
||||
}
|
||||
|
||||
private static final Reflection.Field<TileEntityTypes> tileEntityType = Reflection.getField(TechHider.tileEntityDataPacket, TileEntityTypes.class, 0);
|
||||
private static final TileEntityTypes<?> signType = Reflection.getField(TileEntityTypes.class, TileEntityTypes.class, 0, TileEntitySign.class).get(null);
|
||||
@Override
|
||||
public boolean unfilteredTileEntityDataAction(Object packet) {
|
||||
return tileEntityType.get(packet) != signType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Player, Object, Object> blockBreakHiderGenerator(Class<?> blockBreakPacket, TechHider techHider) {
|
||||
return (p, packet) -> {
|
||||
PacketPlayOutBlockBreak breakPacket = (PacketPlayOutBlockBreak) packet;
|
||||
switch (techHider.getLocationEvaluator().checkBlockPos(p, breakPacket.b())) {
|
||||
case SKIP:
|
||||
return packet;
|
||||
case CHECK:
|
||||
if(!techHider.iBlockDataHidden(breakPacket.c()))
|
||||
return packet;
|
||||
case HIDE:
|
||||
return new PacketPlayOutBlockBreak(breakPacket.b(), (IBlockData) techHider.getObfuscationTarget(), breakPacket.d(), breakPacket.a());
|
||||
case HIDE_AIR:
|
||||
default:
|
||||
return new PacketPlayOutBlockBreak(breakPacket.b(), (IBlockData) TechHider.AIR, breakPacket.d(), breakPacket.a());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user