forked from SteamWar/SteamWar
Implement basic techhider
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.isWarnings = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":CommonCore", "default"))
|
||||
compileOnly(project(":SpigotCore:SpigotCore_Main", "default"))
|
||||
|
||||
compileOnly(libs.spigotapi)
|
||||
compileOnly(libs.netty)
|
||||
compileOnly(libs.fastutil)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 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.core.events;
|
||||
|
||||
import de.steamwar.Reflection;
|
||||
import com.comphenix.tinyprotocol.TinyProtocol;
|
||||
import de.steamwar.core.Core;
|
||||
import de.steamwar.sql.SWException;
|
||||
import de.steamwar.techhider.ProtocolUtils;
|
||||
import de.steamwar.techhider.TechHider;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class AntiNocom implements Listener {
|
||||
|
||||
private final Map<Player, Integer> flags = new ConcurrentHashMap<>();
|
||||
|
||||
public AntiNocom() {
|
||||
Bukkit.getServer().getPluginManager().registerEvents(this, Core.getInstance());
|
||||
TinyProtocol.instance.addFilter(blockDig, this::onDig);
|
||||
|
||||
if(Core.getVersion() > 8) {
|
||||
registerUseItem();
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent e) {
|
||||
flags.remove(e.getPlayer());
|
||||
}
|
||||
|
||||
private void registerUseItem() {
|
||||
Class<?> useItem = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundUseItemOnPacket");
|
||||
|
||||
Function<Object, Object> getPosition;
|
||||
if(Core.getVersion() > 12) {
|
||||
Class<?> movingObjectPositionBlock = Reflection.getClass("net.minecraft.world.phys.BlockHitResult");
|
||||
Reflection.Field<?> useItemPosition = Reflection.getField(useItem, movingObjectPositionBlock, 0);
|
||||
Reflection.Field<?> movingBlockPosition = Reflection.getField(movingObjectPositionBlock, TechHider.blockPosition, 0);
|
||||
|
||||
getPosition = (packet) -> movingBlockPosition.get(useItemPosition.get(packet));
|
||||
} else {
|
||||
getPosition = Reflection.getField(useItem, TechHider.blockPosition, 0)::get;
|
||||
}
|
||||
|
||||
TinyProtocol.instance.addFilter(useItem, (player, packet) -> {
|
||||
Object pos = getPosition.apply(packet);
|
||||
return isValid(player, "UseItem", TechHider.blockPositionX.get(pos), TechHider.blockPositionZ.get(pos)) ? packet : null;
|
||||
});
|
||||
}
|
||||
|
||||
private static final Class<?> blockDig = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundPlayerActionPacket");
|
||||
private static final Reflection.Field<?> digPosition = Reflection.getField(blockDig, TechHider.blockPosition, 0);
|
||||
private Object onDig(Player player, Object packet) {
|
||||
Object pos = digPosition.get(packet);
|
||||
return isValid(player, "Dig", TechHider.blockPositionX.get(pos), TechHider.blockPositionZ.get(pos)) ? packet : null;
|
||||
}
|
||||
|
||||
private boolean isValid(Player player, String type, int x, int z) {
|
||||
if((x == 0 && z == 0) || player.getWorld().isChunkLoaded(ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(z)))
|
||||
return true;
|
||||
|
||||
int amount = flags.compute(player, (p, a) -> a == null ? 1 : a+1);
|
||||
if(amount % 8 == 0) { // Only after 8 and every 8 to reduce false flags and spam
|
||||
if(amount == 8) // Schedule player kick only once
|
||||
Bukkit.getScheduler().runTask(Core.getInstance(), () -> player.kickPlayer(null));
|
||||
|
||||
SWException.log(player.getName() + " kicked for potential NoCom-DOS attack", x + " " + z + " " + type + " " + amount);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2022 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.entity;
|
||||
|
||||
import de.steamwar.core.Core;
|
||||
import de.steamwar.techhider.BlockIds;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
@Getter
|
||||
public class RFallingBlockEntity extends REntity{
|
||||
|
||||
private final Material material;
|
||||
|
||||
public RFallingBlockEntity(REntityServer server, Location location, Material material) {
|
||||
super(server, EntityType.FALLING_BLOCK, location, BlockIds.impl.materialToId(material) >> (Core.getVersion() <= 12 ? 4 : 0));
|
||||
this.material = material;
|
||||
server.addEntity(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
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 de.steamwar.core.VersionDependent;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface BlockIds {
|
||||
BlockIds impl = VersionDependent.getVersionImpl(Core.getInstance());
|
||||
|
||||
Reflection.Method getCombinedId = Reflection.getTypedMethod(TechHider.block, null, int.class, TechHider.iBlockData);
|
||||
|
||||
int getCombinedId(Object iBlockData);
|
||||
int materialToId(Material material);
|
||||
Set<Integer> materialToAllIds(Material material);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
This file is a part of the SteamWar software.
|
||||
|
||||
Copyright (C) 2022 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.core.Core;
|
||||
import de.steamwar.core.VersionDependent;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public interface ChunkHider {
|
||||
ChunkHider impl = VersionDependent.getVersionImpl(Core.getInstance());
|
||||
|
||||
Class<?> mapChunkPacket();
|
||||
BiFunction<Player, Object, Object> chunkHiderGenerator(TechHider techHider);
|
||||
|
||||
@Getter
|
||||
class SectionHider {
|
||||
private final Player player;
|
||||
private final TechHider techHider;
|
||||
private final ByteBuf in;
|
||||
private final ByteBuf out;
|
||||
|
||||
private final int chunkX;
|
||||
private final int chunkY;
|
||||
private final int chunkZ;
|
||||
private final int offsetX;
|
||||
private final int offsetY;
|
||||
private final int offsetZ;
|
||||
|
||||
private final boolean skipSection;
|
||||
|
||||
private boolean paletted;
|
||||
private int bitsPerBlock;
|
||||
private int blockCount;
|
||||
private int air;
|
||||
private int target;
|
||||
private Set<Integer> obfuscate;
|
||||
|
||||
public SectionHider(Player player, TechHider techHider, ByteBuf in, ByteBuf out, int chunkX, int chunkY, int chunkZ) {
|
||||
this.player = player;
|
||||
this.techHider = techHider;
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
this.chunkX = chunkX;
|
||||
this.chunkY = chunkY;
|
||||
this.chunkZ = chunkZ;
|
||||
this.offsetX = 16*chunkX;
|
||||
this.offsetY = 16*chunkY;
|
||||
this.offsetZ = 16*chunkZ;
|
||||
this.skipSection = techHider.getLocationEvaluator().skipChunkSection(player, chunkX, chunkY, chunkZ);
|
||||
|
||||
this.paletted = false;
|
||||
this.bitsPerBlock = 0;
|
||||
this.air = TechHider.AIR_ID;
|
||||
this.target = techHider.getObfuscationTargetId();
|
||||
this.obfuscate = techHider.getObfuscateIds();
|
||||
}
|
||||
|
||||
public boolean blockPrecise() {
|
||||
return techHider.getLocationEvaluator().blockPrecise(player, chunkX, chunkY, chunkZ);
|
||||
}
|
||||
|
||||
public TechHider.State test(int x, int y, int z) {
|
||||
return techHider.getLocationEvaluator().check(player, offsetX+x, offsetY+y, offsetZ+z);
|
||||
}
|
||||
|
||||
public void copyBlockCount() {
|
||||
this.blockCount = in.readShort();
|
||||
out.writeShort(blockCount);
|
||||
}
|
||||
|
||||
public void copyBitsPerBlock() {
|
||||
bitsPerBlock = in.readByte();
|
||||
out.writeByte(bitsPerBlock);
|
||||
}
|
||||
|
||||
public int copyVarInt() {
|
||||
int value = ProtocolUtils.readVarInt(in);
|
||||
ProtocolUtils.writeVarInt(out, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
public void skipPalette() {
|
||||
int paletteLength = copyVarInt();
|
||||
for(int i = 0; i < paletteLength; i++)
|
||||
copyVarInt();
|
||||
}
|
||||
|
||||
public void processPalette() {
|
||||
if(skipSection) {
|
||||
skipPalette();
|
||||
return;
|
||||
}
|
||||
|
||||
int paletteLength = copyVarInt();
|
||||
if(paletteLength == 0)
|
||||
return;
|
||||
|
||||
paletted = true;
|
||||
air = 0;
|
||||
target = 0;
|
||||
|
||||
for(int i = 0; i < paletteLength; i++) {
|
||||
int entry = ProtocolUtils.readVarInt(in);
|
||||
if(obfuscate.contains(entry))
|
||||
entry = techHider.getObfuscationTargetId();
|
||||
|
||||
if(entry == TechHider.AIR_ID)
|
||||
air = i;
|
||||
else if(entry == techHider.getObfuscationTargetId())
|
||||
target = i;
|
||||
|
||||
ProtocolUtils.writeVarInt(out, entry);
|
||||
}
|
||||
obfuscate = Collections.emptySet();
|
||||
}
|
||||
|
||||
public void skipDataArray() {
|
||||
int dataArrayLength = copyVarInt();
|
||||
out.writeBytes(in, dataArrayLength*8);
|
||||
}
|
||||
|
||||
public void skipNewDataArray(int entries) {
|
||||
if (bitsPerBlock == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char valuesPerLong = (char)(64 / bitsPerBlock);
|
||||
int i1 = (entries + valuesPerLong - 1) / valuesPerLong;
|
||||
out.writeBytes(in, i1 * Long.BYTES);
|
||||
}
|
||||
|
||||
public long[] readDataArray() {
|
||||
long[] array = new long[copyVarInt()];
|
||||
for(int i = 0; i < array.length; i++)
|
||||
array[i] = in.readLong();
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public long[] readNewDataArray(int entries) {
|
||||
if (bitsPerBlock == 0) {
|
||||
return new long[entries];
|
||||
}
|
||||
|
||||
char valuesPerLong = (char) (64 / bitsPerBlock);
|
||||
int i1 = (entries + valuesPerLong - 1) / valuesPerLong;
|
||||
long[] array = new long[i1];
|
||||
for(int i = 0; i < i1; i++)
|
||||
array[i] = in.readLong();
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public void writeDataArray(long[] array) {
|
||||
for(long l : array)
|
||||
out.writeLong(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
This file is a part of the SteamWar software.
|
||||
|
||||
Copyright (C) 2022 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 com.google.common.primitives.Bytes;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
public class ProtocolUtils {
|
||||
private ProtocolUtils() {}
|
||||
|
||||
@Deprecated
|
||||
public static BiFunction<Object, UnaryOperator<Object>, Object> arrayCloneGenerator(Class<?> elementClass) {
|
||||
return (array, worker) -> {
|
||||
int length = Array.getLength(array);
|
||||
Object result = Array.newInstance(elementClass, length);
|
||||
|
||||
for(int i = 0; i < length; i++)
|
||||
Array.set(result, i, worker.apply(Array.get(array, i)));
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
public static UnaryOperator<Object> shallowCloneGenerator(Class<?> clazz) {
|
||||
BiConsumer<Object, Object> filler = shallowFill(clazz);
|
||||
|
||||
return source -> {
|
||||
Object clone = Reflection.newInstance(clazz);
|
||||
filler.accept(source, clone);
|
||||
return clone;
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> UnaryOperator<T> shallowTypedCloneGenerator(Class<T> clazz) {
|
||||
BiConsumer<Object, Object> filler = shallowFill(clazz);
|
||||
|
||||
return source -> {
|
||||
Object clone = Reflection.newInstance(clazz);
|
||||
filler.accept(source, clone);
|
||||
return (T) clone;
|
||||
};
|
||||
}
|
||||
|
||||
private static BiConsumer<Object, Object> shallowFill(Class<?> clazz) {
|
||||
if(clazz == null)
|
||||
return (source, clone) -> {};
|
||||
|
||||
BiConsumer<Object, Object> superFiller = shallowFill(clazz.getSuperclass());
|
||||
|
||||
Field[] fds = clazz.getDeclaredFields();
|
||||
List<Field> fields = new ArrayList<>();
|
||||
for(Field field : fds) {
|
||||
if (Modifier.isStatic(field.getModifiers()))
|
||||
continue;
|
||||
|
||||
field.setAccessible(true);
|
||||
fields.add(field);
|
||||
}
|
||||
|
||||
return (source, clone) -> {
|
||||
superFiller.accept(source, clone);
|
||||
try {
|
||||
for(Field field : fields) {
|
||||
field.set(clone, field.get(source));
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Could not set field", e);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public static int posToChunk(int c){
|
||||
int chunk = c / 16;
|
||||
if(c < 0)
|
||||
chunk--;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static int readVarInt(byte[] array, int startPos) {
|
||||
int numRead = 0;
|
||||
int result = 0;
|
||||
byte read;
|
||||
do {
|
||||
read = array[startPos + numRead];
|
||||
int value = (read & 0b01111111);
|
||||
result |= (value << (7 * numRead));
|
||||
|
||||
numRead++;
|
||||
if (numRead > 5) {
|
||||
break;
|
||||
}
|
||||
} while ((read & 0b10000000) != 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int readVarInt(ByteBuf buf) {
|
||||
int numRead = 0;
|
||||
int result = 0;
|
||||
byte read;
|
||||
do {
|
||||
read = buf.readByte();
|
||||
int value = (read & 0b01111111);
|
||||
result |= (value << (7 * numRead));
|
||||
|
||||
if (++numRead > 5)
|
||||
throw new SecurityException("VarInt too long");
|
||||
} while ((read & 0b10000000) != 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void writeVarInt(ByteBuf buf, int value) {
|
||||
do {
|
||||
int temp = value & 0b01111111;
|
||||
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
|
||||
value >>>= 7;
|
||||
if (value != 0) {
|
||||
temp |= 0b10000000;
|
||||
}
|
||||
buf.writeByte(temp);
|
||||
} while (value != 0);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static int readVarIntLength(byte[] array, int startPos) {
|
||||
int numRead = 0;
|
||||
byte read;
|
||||
do {
|
||||
read = array[startPos + numRead];
|
||||
numRead++;
|
||||
if (numRead > 5) {
|
||||
break;
|
||||
}
|
||||
} while ((read & 0b10000000) != 0);
|
||||
|
||||
return numRead;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static byte[] writeVarInt(int value) {
|
||||
List<Byte> buffer = new ArrayList<>(5);
|
||||
do {
|
||||
byte temp = (byte)(value & 0b01111111);
|
||||
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
|
||||
value >>>= 7;
|
||||
if (value != 0) {
|
||||
temp |= 0b10000000;
|
||||
}
|
||||
buffer.add(temp);
|
||||
} while (value != 0);
|
||||
return Bytes.toArray(buffer);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static class ChunkPos{
|
||||
final int x;
|
||||
final int z;
|
||||
|
||||
public ChunkPos(int x, int z){
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public final int x(){
|
||||
return x;
|
||||
}
|
||||
|
||||
public final int z(){
|
||||
return z;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
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.core.Core;
|
||||
import de.steamwar.core.VersionDependent;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public interface ProtocolWrapper {
|
||||
ProtocolWrapper impl = VersionDependent.getVersionImpl(Core.getInstance());
|
||||
|
||||
|
||||
boolean unfilteredTileEntityDataAction(Object packet);
|
||||
|
||||
BiFunction<Player, Object, Object> blockBreakHiderGenerator(Class<?> blockBreakPacket, TechHider techHider);
|
||||
|
||||
BiFunction<Player, Object, Object> multiBlockChangeGenerator(TechHider techHider);
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
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 com.comphenix.tinyprotocol.TinyProtocol;
|
||||
import de.steamwar.Reflection;
|
||||
import de.steamwar.core.Core;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TechHider {
|
||||
|
||||
public static final Class<?> blockPosition = Reflection.getClass("net.minecraft.core.BlockPos");
|
||||
private static final Class<?> baseBlockPosition = Reflection.getClass("net.minecraft.core.Vec3i");
|
||||
public static final Reflection.Field<Integer> blockPositionX = Reflection.getField(baseBlockPosition, int.class, 0);
|
||||
public static final Reflection.Field<Integer> blockPositionY = Reflection.getField(baseBlockPosition, int.class, 1);
|
||||
public static final Reflection.Field<Integer> blockPositionZ = Reflection.getField(baseBlockPosition, int.class, 2);
|
||||
|
||||
public static final Class<?> iBlockData = Reflection.getClass("net.minecraft.world.level.block.state.BlockState");
|
||||
public static final Class<?> block = Reflection.getClass("net.minecraft.world.level.block.Block");
|
||||
private static final Reflection.Method getBlockDataByBlock = Reflection.getTypedMethod(block, null, iBlockData);
|
||||
|
||||
public static final Class<?> craftMagicNumbers = Reflection.getClass("org.bukkit.craftbukkit.util.CraftMagicNumbers");
|
||||
private static final Reflection.Method getBlockByMaterial = Reflection.getTypedMethod(craftMagicNumbers, "getBlock", block, Material.class);
|
||||
|
||||
public boolean iBlockDataHidden(Object iBlockData) {
|
||||
return obfuscateIds.contains(BlockIds.impl.getCombinedId(iBlockData));
|
||||
}
|
||||
|
||||
public static final Object AIR = getBlockDataByBlock.invoke(getBlockByMaterial.invoke(null, Material.AIR));
|
||||
public static final int AIR_ID = BlockIds.impl.materialToId(Material.AIR);
|
||||
|
||||
private final Map<Class<?>, BiFunction<Player, Object, Object>> techhiders = new HashMap<>();
|
||||
@Getter
|
||||
private final LocationEvaluator locationEvaluator;
|
||||
@Getter
|
||||
private final Object obfuscationTarget;
|
||||
@Getter
|
||||
private final int obfuscationTargetId;
|
||||
@Getter
|
||||
private final Set<Integer> obfuscateIds;
|
||||
@Getter
|
||||
private final Set<String> hiddenBlockEntities;
|
||||
|
||||
public TechHider(LocationEvaluator locationEvaluator, Material obfuscationTarget, Set<Material> obfuscate, Set<String> hiddenBlockEntities) {
|
||||
this.locationEvaluator = locationEvaluator;
|
||||
this.obfuscateIds = obfuscate.stream().flatMap(m -> BlockIds.impl.materialToAllIds(m).stream()).collect(Collectors.toSet());
|
||||
this.hiddenBlockEntities = hiddenBlockEntities;
|
||||
this.obfuscationTarget = getBlockDataByBlock.invoke(getBlockByMaterial.invoke(null, obfuscationTarget));
|
||||
this.obfuscationTargetId = BlockIds.impl.materialToId(obfuscationTarget);
|
||||
|
||||
techhiders.put(blockActionPacket, this::blockActionHider);
|
||||
techhiders.put(blockChangePacket, this::blockChangeHider);
|
||||
techhiders.put(tileEntityDataPacket, this::tileEntityDataHider);
|
||||
techhiders.put(multiBlockChangePacket, ProtocolWrapper.impl.multiBlockChangeGenerator(this));
|
||||
techhiders.put(ChunkHider.impl.mapChunkPacket(), ChunkHider.impl.chunkHiderGenerator(this));
|
||||
|
||||
if(Core.getVersion() > 12 && Core.getVersion() < 19) {
|
||||
Class<?> blockBreakClass = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundBlockDestructionPacket");
|
||||
techhiders.put(blockBreakClass, ProtocolWrapper.impl.blockBreakHiderGenerator(blockBreakClass, this));
|
||||
}
|
||||
|
||||
if(Core.getVersion() > 8){
|
||||
techhiders.put(Reflection.getClass("net.minecraft.network.protocol.game.ServerboundUseItemOnPacket"), (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet);
|
||||
}
|
||||
techhiders.put(Reflection.getClass("net.minecraft.network.protocol.game.ServerboundInteractPacket"), (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet);
|
||||
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
techhiders.forEach(TinyProtocol.instance::addFilter);
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
techhiders.forEach(TinyProtocol.instance::removeFilter);
|
||||
}
|
||||
|
||||
public static final Class<?> multiBlockChangePacket = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket");
|
||||
public static final UnaryOperator<Object> multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket);
|
||||
|
||||
private static final Class<?> blockChangePacket = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket");
|
||||
private static final Function<Object, Object> blockChangeCloner = ProtocolUtils.shallowCloneGenerator(blockChangePacket);
|
||||
private static final Reflection.Field<?> blockChangePosition = Reflection.getField(blockChangePacket, blockPosition, 0);
|
||||
private static final Reflection.Field<?> blockChangeBlockData = Reflection.getField(blockChangePacket, iBlockData, 0);
|
||||
private Object blockChangeHider(Player p, Object packet) {
|
||||
switch (locationEvaluator.checkBlockPos(p, blockChangePosition.get(packet))) {
|
||||
case SKIP:
|
||||
return packet;
|
||||
case CHECK:
|
||||
if(!iBlockDataHidden(blockChangeBlockData.get(packet)))
|
||||
return packet;
|
||||
case HIDE:
|
||||
packet = blockChangeCloner.apply(packet);
|
||||
blockChangeBlockData.set(packet, obfuscationTarget);
|
||||
return packet;
|
||||
case HIDE_AIR:
|
||||
default:
|
||||
packet = blockChangeCloner.apply(packet);
|
||||
blockChangeBlockData.set(packet, AIR);
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Class<?> blockActionPacket = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundBlockEventPacket");
|
||||
private static final Reflection.Field<?> blockActionPosition = Reflection.getField(blockActionPacket, blockPosition, 0);
|
||||
private Object blockActionHider(Player p, Object packet) {
|
||||
if (locationEvaluator.checkBlockPos(p, blockActionPosition.get(packet)) == State.SKIP)
|
||||
return packet;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final Class<?> tileEntityDataPacket = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket");
|
||||
private static final Reflection.Field<?> tileEntityDataPosition = Reflection.getField(tileEntityDataPacket, blockPosition, 0);
|
||||
private Object tileEntityDataHider(Player p, Object packet) {
|
||||
switch (locationEvaluator.checkBlockPos(p, tileEntityDataPosition.get(packet))) {
|
||||
case SKIP:
|
||||
return packet;
|
||||
case CHECK:
|
||||
if(ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet))
|
||||
return packet;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public enum State {
|
||||
SKIP,
|
||||
CHECK,
|
||||
HIDE,
|
||||
HIDE_AIR
|
||||
}
|
||||
|
||||
public interface LocationEvaluator {
|
||||
default boolean suppressInteractions(Player player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean skipChunk(Player player, int x, int z);
|
||||
default boolean skipChunkSection(Player player, int x, int y, int z) {
|
||||
return skipChunk(player, x, z);
|
||||
}
|
||||
default State check(Player player, int x, int y, int z) {
|
||||
return skipChunkSection(player, ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(y), ProtocolUtils.posToChunk(z)) ? State.SKIP : State.CHECK;
|
||||
}
|
||||
|
||||
default State checkBlockPos(Player player, Object pos) {
|
||||
return check(player, blockPositionX.get(pos), blockPositionY.get(pos), blockPositionZ.get(pos));
|
||||
}
|
||||
|
||||
default boolean blockPrecise(Player player, int x, int y, int z) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user