diff --git a/iclibs/Nukkit-MOT-SNAPSHOT.jar b/iclibs/Nukkit-MOT-SNAPSHOT.jar index f9357537..4924db84 100644 Binary files a/iclibs/Nukkit-MOT-SNAPSHOT.jar and b/iclibs/Nukkit-MOT-SNAPSHOT.jar differ diff --git a/src/main/java/com/reider745/api/hooks/JarEditor.java b/src/main/java/com/reider745/api/hooks/JarEditor.java index 9ad09ec7..a0e08856 100644 --- a/src/main/java/com/reider745/api/hooks/JarEditor.java +++ b/src/main/java/com/reider745/api/hooks/JarEditor.java @@ -57,7 +57,7 @@ public class JarEditor { public JarEditor registerHooksInitializationForClass(String clazz) throws InstantiationException, IllegalAccessException, NotFoundException, NoSuchMethodException, - SecurityException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException { + SecurityException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException, CannotCompileException { CtClass ctClass = classPool.get(clazz); for (Object annotationClass : ctClass.getAnnotations()) { @@ -85,6 +85,93 @@ public class JarEditor { this.addHookInitialization(className, methodName.isBlank() ? method.getName() : methodName, signature, method, inject.type(), isController(method), inject.argumentMap()); + } else if(annotationMethod instanceof ShadowGetField shadowGetField) { + final String name = shadowGetField.field(); + + String className = shadowGetField.className(); + if(className.isEmpty()) className = classNameDefault; + + final CtClass ctClass1 = classPool.get(className); + final CtField field = ctClass1.getDeclaredField(name); + + int modi = field.getModifiers(); + if((modi & Modifier.PUBLIC) == 0 && (modi & Modifier.PROTECTED) == 0) + field.setModifiers(modi & ~Modifier.PRIVATE | Modifier.PUBLIC); + else if((modi & Modifier.PROTECTED) != 0){ + field.setModifiers(modi & ~Modifier.PROTECTED | Modifier.PUBLIC); + } + + final CtClass[] args = method.getParameterTypes(); + + if(args[0].getName().equals(ctClass1.getName())) { + method.setBody("{return $1." + name + ";}"); + }else + method.setBody("{return "+className+"."+name+";}"); + } else if(annotationMethod instanceof ShadowSetField shadowSetField) { + final String name = shadowSetField.field(); + String className = shadowSetField.className(); + if(className.isEmpty()) className = classNameDefault; + final CtClass ctClass1 = classPool.get(className); + final CtField field = ctClass1.getDeclaredField(name); + + int modi = field.getModifiers(); + if((modi & Modifier.PUBLIC) == 0 && (modi & Modifier.PROTECTED) == 0) + field.setModifiers(modi & ~Modifier.PRIVATE | Modifier.PUBLIC); + else if((modi & Modifier.PROTECTED) != 0){ + field.setModifiers(modi & ~Modifier.PROTECTED | Modifier.PUBLIC); + } + + if((modi & Modifier.FINAL) == 0) + field.setModifiers(modi & ~Modifier.FINAL); + + final CtClass[] args = method.getParameterTypes(); + + if(args[0].getName().equals(ctClass1.getName())) { + method.setBody("{$1." + name + " = $2;}"); + }else + method.setBody("{return "+className+"."+name+" = $1;}"); + } else if(annotationMethod instanceof ShadowMethod shadowMethod) { + String name = shadowMethod.method(); + if(name.isEmpty()) name = method.getName(); + + String className = shadowMethod.className(); + if(className.isEmpty()) className = classNameDefault; + + final CtClass ctClass1 = classPool.get(className); + CtMethod methodOrg = null; + + final CtMethod[] methods = ctClass1.getDeclaredMethods(); + for(CtMethod method_ : methods){ + final String sig = shadowMethod.signature(); + if(method_.getName().equals(name) && (sig.isEmpty() || method_.getSignature().equals(sig))){ + methodOrg = method_; + break; + } + } + + + int modi = methodOrg.getModifiers(); + if((modi & Modifier.PUBLIC) == 0 && (modi & Modifier.PROTECTED) == 0) + methodOrg.setModifiers(modi & ~Modifier.PRIVATE | Modifier.PUBLIC); + else if((modi & Modifier.PROTECTED) != 0){ + methodOrg.setModifiers(modi & ~Modifier.PROTECTED | Modifier.PUBLIC); + } + + final CtClass[] args = method.getParameterTypes(); + final CtClass[] orgArgs = methodOrg.getParameterTypes(); + String arg = ""; + final boolean isStatic = args.length == orgArgs.length; + + for(int i = isStatic ? 1 : 2;i < args.length + 1;i++) { + if (!arg.isEmpty()) + arg += ", "; + arg += "$" + i; + } + + if(isStatic){ + method.setBody("{"+className+"."+name+"("+arg+");}"); + }else + method.setBody("{$1."+name+"("+arg+");}"); } } } diff --git a/src/main/java/com/reider745/api/hooks/annotation/ShadowGetField.java b/src/main/java/com/reider745/api/hooks/annotation/ShadowGetField.java new file mode 100644 index 00000000..73d835c9 --- /dev/null +++ b/src/main/java/com/reider745/api/hooks/annotation/ShadowGetField.java @@ -0,0 +1,13 @@ +package com.reider745.api.hooks.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface ShadowGetField { + String className() default ""; + String field(); +} diff --git a/src/main/java/com/reider745/api/hooks/annotation/ShadowMethod.java b/src/main/java/com/reider745/api/hooks/annotation/ShadowMethod.java new file mode 100644 index 00000000..7c371c02 --- /dev/null +++ b/src/main/java/com/reider745/api/hooks/annotation/ShadowMethod.java @@ -0,0 +1,14 @@ +package com.reider745.api.hooks.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface ShadowMethod { + String className() default ""; + String method() default ""; + String signature() default ""; +} diff --git a/src/main/java/com/reider745/api/hooks/annotation/ShadowSetField.java b/src/main/java/com/reider745/api/hooks/annotation/ShadowSetField.java new file mode 100644 index 00000000..90dd7a76 --- /dev/null +++ b/src/main/java/com/reider745/api/hooks/annotation/ShadowSetField.java @@ -0,0 +1,13 @@ +package com.reider745.api.hooks.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface ShadowSetField { + String className() default ""; + String field(); +} diff --git a/src/main/java/com/reider745/event/EventListener.java b/src/main/java/com/reider745/event/EventListener.java index e55c726d..d005a5e5 100644 --- a/src/main/java/com/reider745/event/EventListener.java +++ b/src/main/java/com/reider745/event/EventListener.java @@ -408,16 +408,10 @@ public class EventListener implements Listener { consumeEvent(event, () -> NativeCallback.onEntityPickUpDrop(entity, dropEntity, count)); } - @EventHandler(priority = EventPriority.HIGHEST) - public void onBlockChange(BlockChangeEvent event){ - final Block block = event.getBlockPrevious(); - final Block newBlock = event.getBlock(); - final Level level = newBlock.getLevel(); - - consumeEvent(event, - () -> NativeCallback.onBlockChanged((int) newBlock.x, (int) newBlock.y, (int) newBlock.z, block.getId(), - block.getDamage(), newBlock.getId(), newBlock.getDamage(), FakeDimensions.getFakeIdForLevel(level), - newBlock.getFullId(), level)); + public static void onBlockChange(Level level, Block block, Block newBlock){ + NativeCallback.onBlockChanged((int) newBlock.x, (int) newBlock.y, (int) newBlock.z, block.getId(), + block.getDamage(), newBlock.getId(), newBlock.getDamage(), FakeDimensions.getFakeIdForLevel(level), + newBlock.getFullId(), level); } @EventHandler(priority = EventPriority.HIGHEST) diff --git a/src/main/java/com/reider745/hooks/LevelHooks.java b/src/main/java/com/reider745/hooks/LevelHooks.java index b24e3d6a..4bd741ad 100644 --- a/src/main/java/com/reider745/hooks/LevelHooks.java +++ b/src/main/java/com/reider745/hooks/LevelHooks.java @@ -31,22 +31,16 @@ import cn.nukkit.network.protocol.UpdateBlockPacket; import cn.nukkit.potion.Effect; import cn.nukkit.utils.TextFormat; import cn.nukkit.utils.Utils; -import com.reider745.api.ReflectHelper; import com.reider745.api.hooks.HookClass; -import com.reider745.api.hooks.TypeHook; import com.reider745.api.hooks.annotation.Hooks; import com.reider745.api.hooks.annotation.Inject; +import com.reider745.api.hooks.annotation.ShadowGetField; +import com.reider745.api.hooks.annotation.ShadowMethod; import com.reider745.event.EventListener; -import com.reider745.world.FakeDimensions; import com.reider745.world.dimensions.DimensionsMethods; -import com.zhekasmirnov.innercore.api.NativeCallback; import com.zhekasmirnov.innercore.api.dimensions.CustomDimension; import com.zhekasmirnov.innercore.api.unlimited.IDRegistry; - import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import javassist.CtClass; -import javassist.CtField; -import javassist.Modifier; import java.util.HashMap; import java.util.Map; @@ -349,4 +343,72 @@ public class LevelHooks implements HookClass { } }; } + + @ShadowGetField(field = "callbackBlockSet") + public static Int2ObjectMap> getCallbackBlockSet(Level self){ + return null; + } + + @ShadowMethod(signature = "(JIII)V") + public static void addBlockChange(Level self, long index, int x, int y, int z){} + + @Inject + public static boolean setBlock(Level self, int x, int y, int z, int layer, Block block, boolean direct, boolean update) { + if (!self.isYInRange(y) || layer < 0 || layer > self.requireProvider().getMaximumLayer()) { + return false; + } + BaseFullChunk chunk = self.getChunk(x >> 4, z >> 4, true); + Block blockPrevious; + blockPrevious = chunk.getAndSetBlock(x & 0xF, y, z & 0xF, layer, block); + if (blockPrevious.getFullId() == block.getFullId()) { + return false; + } + block.x = x; + block.y = y; + block.z = z; + block.level = self; + block.layer = layer; + + try { + for (Consumer callback : getCallbackBlockSet(self).values()) { + callback.accept(block); + } + } catch (Exception e) { + Server.getInstance().getLogger().error("Error while calling block set callback", e); + } + + int cx = x >> 4; + int cz = z >> 4; + + if (direct) { + self.sendBlocks(self.getChunkPlayers(cx, cz).values().toArray(Player.EMPTY_ARRAY), new Block[]{block}, UpdateBlockPacket.FLAG_ALL_PRIORITY, block.layer); + } else { + addBlockChange(self, Level.chunkHash(cx, cz), x, y, z); + } + + for (ChunkLoader loader : self.getChunkLoaders(cx, cz)) { + loader.onBlockChanged(block); + } + if (update) { + if (blockPrevious.isTransparent() != block.isTransparent() || blockPrevious.getLightLevel() != block.getLightLevel()) { + self.addLightUpdate(x, y, z); + } + BlockUpdateEvent ev = new BlockUpdateEvent(block); + self.getServer().getPluginManager().callEvent(ev); + EventListener.onBlockChange(self, blockPrevious, block); + if (!ev.isCancelled()) { + for (Entity entity : self.getNearbyEntities(new SimpleAxisAlignedBB(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1))) { + entity.scheduleUpdate(); + } + block = ev.getBlock(); + block.onUpdate(Level.BLOCK_UPDATE_NORMAL); + block.getLevelBlockAtLayer(layer == 0 ? 1 : 0).onUpdate(Level.BLOCK_UPDATE_NORMAL); + if (block.isTransparent()) { + self.antiXrayOnBlockChange(null, block, 1); + } + self.updateAround(new Vector3(x, y, z)); + } + } + return true; + } } diff --git a/src/main/java/com/reider745/hooks/NetworkHooks.java b/src/main/java/com/reider745/hooks/NetworkHooks.java index 6c5aac4f..b67100b7 100644 --- a/src/main/java/com/reider745/hooks/NetworkHooks.java +++ b/src/main/java/com/reider745/hooks/NetworkHooks.java @@ -14,21 +14,20 @@ import com.reider745.api.hooks.HookClass; import com.reider745.api.hooks.TypeHook; import com.reider745.api.hooks.annotation.Inject; import com.reider745.api.hooks.annotation.Hooks; +import com.reider745.api.hooks.annotation.ShadowGetField; import com.reider745.network.BasePacket; import com.reider745.network.InnerCorePacket; import com.reider745.network.LoginPacketFix; import com.reider745.world.BiomesMethods; import com.zhekasmirnov.apparatus.mcpe.NativeNetworking; import com.zhekasmirnov.horizon.runtime.logger.Logger; -import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import org.cloudburstmc.netty.channel.raknet.packet.RakMessage; import java.io.ByteArrayInputStream; import java.net.ProtocolException; -import java.nio.ByteBuffer; -import java.util.Collection; -import java.util.LinkedList; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; @Hooks(className = "cn.nukkit.network.Network") public class NetworkHooks implements HookClass { @@ -42,16 +41,51 @@ public class NetworkHooks implements HookClass { } } - @Inject(signature = "([BLjava/util/Collection;Lcn/nukkit/network/CompressionProvider;ILcn/nukkit/Player;Lcn/nukkit/network/session/RakNetPlayerSession;)V", type = TypeHook.BEFORE_REPLACE) + @ShadowGetField(className = "cn.nukkit.network.session.RakNetPlayerSession", field = "outbound") + public static Queue getOutbound(RakNetPlayerSession session){ + return null; + } + + @ShadowGetField(className = "cn.nukkit.network.session.RakNetPlayerSession", field = "inbound") + public static Queue getInbound(RakNetPlayerSession session){ + return null; + } + + public static void sendPacketNotPlayer(RakNetPlayerSession session, BasePacket packet){ + if (!session.getChannel().isActive()) { + return; + } + + if (packet.packetId() != ProtocolInfo.toNewProtocolID(ProtocolInfo.BATCH_PACKET)) { + packet.tryEncode(); + } + getOutbound(session).offer(packet); + } + + // TODO: Костыль для получения RakNetPlayerSession в стоковом Nukkit-Mot + private static final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + + @Inject(className = "cn.nukkit.network.session.RakNetPlayerSession") + public static void channelRead0(RakNetPlayerSession self, ChannelHandlerContext channelHandlerContext, RakMessage msg){ + sessions.put(getInbound(self), self); + } + + @Inject(className = "cn.nukkit.network.session.RakNetPlayerSession") + public static void disconnect(RakNetPlayerSession self, String reason) { + sessions.remove(getInbound(self)); + } + + @Inject(signature = "([BLjava/util/Collection;Lcn/nukkit/network/CompressionProvider;ILcn/nukkit/Player;)V", type = TypeHook.BEFORE_REPLACE) public static void processBatch(Network network, byte[] payload, Collection packets, - CompressionProvider compression, int raknetProtocol, Player player, RakNetPlayerSession session) { + CompressionProvider compression, int raknetProtocol, Player player) { + final RakNetPlayerSession session = sessions.get(packets); final String id = NativeNetworking.getId(session); if (!canSendInfo.contains(id)) { canSendInfo.add(id); - session.sendPacketNotPlayer(InnerCorePacket.sendInfo); + sendPacketNotPlayer(session, InnerCorePacket.sendInfo); } - MainLogger log = Server.getInstance().getLogger(); + final MainLogger log = Server.getInstance().getLogger(); int maxSize = 3145728; // 3 * 1024 * 1024 if (player != null && player.getSkin() == null) { diff --git a/src/main/java/com/reider745/network/InnerCorePacket.java b/src/main/java/com/reider745/network/InnerCorePacket.java index ea602d8f..d03c3f22 100644 --- a/src/main/java/com/reider745/network/InnerCorePacket.java +++ b/src/main/java/com/reider745/network/InnerCorePacket.java @@ -6,6 +6,7 @@ import cn.nukkit.network.protocol.DataPacket; import cn.nukkit.network.session.RakNetPlayerSession; import com.reider745.InnerCoreServer; import com.reider745.api.ClientOnly; +import com.reider745.hooks.NetworkHooks; import com.zhekasmirnov.apparatus.mcpe.NativeNetworking; import java.util.*; @@ -71,6 +72,6 @@ public class InnerCorePacket extends BasePacket { packet.bytes_length = data.length; packet.bytes = data; - session.sendPacketNotPlayer(packet); + NetworkHooks.sendPacketNotPlayer(session, packet); } }