diff --git a/Minecraft.Client/ServerPlayerGameMode.cpp b/Minecraft.Client/ServerPlayerGameMode.cpp index c1983e9b..1b25f68e 100644 --- a/Minecraft.Client/ServerPlayerGameMode.cpp +++ b/Minecraft.Client/ServerPlayerGameMode.cpp @@ -263,7 +263,7 @@ bool ServerPlayerGameMode::destroyBlock(int x, int y, int z) { if (!EnchantmentHelper::hasSilkTouch(player)) { - // todo: shouldnt we get these values from the actual blocks? + // (SYLV)todo: shouldnt we get these values from the actual blocks? if (t == Tile::coalOre_Id) eventExp = Mth::nextInt(level->random, 0, 2); else if (t == Tile::diamondOre_Id) @@ -286,7 +286,6 @@ bool ServerPlayerGameMode::destroyBlock(int x, int y, int z) int breakResult = FourKitBridge::FireBlockBreak(player->entityId, dimId, x, y, z, t, data, eventExp); if (breakResult < 0) { - // Cancelled: send block correction to client player->connection->send(std::make_shared(x, y, z, level)); return false; } diff --git a/Minecraft.Server.FourKit/FourKitHost.Callbacks.cs b/Minecraft.Server.FourKit/FourKitHost.Callbacks.cs new file mode 100644 index 00000000..ac52ad74 --- /dev/null +++ b/Minecraft.Server.FourKit/FourKitHost.Callbacks.cs @@ -0,0 +1,111 @@ +using System.Runtime.InteropServices; + +namespace Minecraft.Server.FourKit; + +public static partial class FourKitHost +{ + [UnmanagedCallersOnly] + public static void SetNativeCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity) + { + try + { + NativeBridge.SetCallbacks(damage, setHealth, teleport, setGameMode, broadcastMessage, setFallDistance, getPlayerSnapshot, sendMessage, setWalkSpeed, teleportEntity); + ServerLog.Info("fourkit", "Native callbacks registered."); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"SetNativeCallbacks error: {ex}"); + } + } + + [UnmanagedCallersOnly] + public static void SetWorldCallbacks(IntPtr getTileId, IntPtr getTileData, IntPtr setTile, IntPtr setTileData, IntPtr breakBlock, IntPtr getHighestBlockY, IntPtr getWorldInfo, IntPtr setWorldTime, IntPtr setWeather, IntPtr createExplosion, IntPtr strikeLightning, IntPtr setSpawnLocation, IntPtr dropItem) + { + try + { + NativeBridge.SetWorldCallbacks(getTileId, getTileData, setTile, setTileData, breakBlock, getHighestBlockY, getWorldInfo, setWorldTime, setWeather, createExplosion, strikeLightning, setSpawnLocation, dropItem); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"SetWorldCallbacks error: {ex}"); + } + } + + [UnmanagedCallersOnly] + public static void SetPlayerCallbacks(IntPtr kickPlayer, IntPtr banPlayer, IntPtr banPlayerIp, IntPtr getPlayerAddress) + { + try + { + NativeBridge.SetPlayerCallbacks(kickPlayer, banPlayer, banPlayerIp, getPlayerAddress); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"SetPlayerCallbacks error: {ex}"); + } + } + + [UnmanagedCallersOnly] + public static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot) + { + try + { + NativeBridge.SetInventoryCallbacks(getPlayerInventory, setPlayerInventorySlot, getContainerContents, setContainerSlot, getContainerViewerEntityIds, closeContainer, openVirtualContainer, getItemMeta, setItemMeta, setHeldItemSlot); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"SetInventoryCallbacks error: {ex}"); + } + } + + [UnmanagedCallersOnly] + public static void SetEntityCallbacks(IntPtr setSneaking, IntPtr setVelocity, IntPtr setAllowFlight, IntPtr playSound, IntPtr setSleepingIgnored) + { + try + { + NativeBridge.SetEntityCallbacks(setSneaking, setVelocity, setAllowFlight, playSound, setSleepingIgnored); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"SetEntityCallbacks error: {ex}"); + } + } + + [UnmanagedCallersOnly] + public static void SetExperienceCallbacks(IntPtr setLevel, IntPtr setExp, IntPtr giveExp, IntPtr giveExpLevels, IntPtr setFoodLevel, IntPtr setSaturation, IntPtr setExhaustion) + { + try + { + NativeBridge.SetExperienceCallbacks(setLevel, setExp, giveExp, giveExpLevels, setFoodLevel, setSaturation, setExhaustion); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"SetExperienceCallbacks error: {ex}"); + } + } + + [UnmanagedCallersOnly] + public static void SetParticleCallbacks(IntPtr spawnParticle) + { + try + { + NativeBridge.SetParticleCallbacks(spawnParticle); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"SetParticleCallbacks error: {ex}"); + } + } + + [UnmanagedCallersOnly] + public static void SetVehicleCallbacks(IntPtr setPassenger, IntPtr leaveVehicle, IntPtr eject, IntPtr getVehicleId, IntPtr getPassengerId, IntPtr getEntityInfo) + { + try + { + NativeBridge.SetVehicleCallbacks(setPassenger, leaveVehicle, eject, getVehicleId, getPassengerId, getEntityInfo); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"SetVehicleCallbacks error: {ex}"); + } + } +} diff --git a/Minecraft.Server.FourKit/FourKitHost.Events.cs b/Minecraft.Server.FourKit/FourKitHost.Events.cs new file mode 100644 index 00000000..a0dd4fb2 --- /dev/null +++ b/Minecraft.Server.FourKit/FourKitHost.Events.cs @@ -0,0 +1,976 @@ +using System.Runtime.InteropServices; +using Minecraft.Server.FourKit.Block; +using Minecraft.Server.FourKit.Entity; +using Minecraft.Server.FourKit.Event; +using Minecraft.Server.FourKit.Event.Block; +using Minecraft.Server.FourKit.Event.Entity; +using Minecraft.Server.FourKit.Event.Player; +using Minecraft.Server.FourKit.Event.Inventory; +using Minecraft.Server.FourKit.Inventory; + +namespace Minecraft.Server.FourKit; + +public static partial class FourKitHost +{ + [UnmanagedCallersOnly] + public static void FirePlayerJoin(int entityId, IntPtr namePtr, int nameByteLen, IntPtr uuidPtr, int uuidByteLen) + { + try + { + string name = nameByteLen > 0 + ? Marshal.PtrToStringUTF8(namePtr, nameByteLen) ?? string.Empty + : string.Empty; + + string uuidStr = uuidByteLen > 0 + ? Marshal.PtrToStringUTF8(uuidPtr, uuidByteLen) ?? string.Empty + : string.Empty; + + var player = FourKit.TrackPlayer(entityId, name); + player.SetPlayerUniqueIdInternal(ParseOrHashGuid(uuidStr)); + SyncPlayerFromNative(player); + var evt = new PlayerJoinEvent(player); + FourKit.FireEvent(evt); + BroadcastNativeMessage(evt.getJoinMessage()); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerJoin error: {ex}"); + } + } + + [UnmanagedCallersOnly] + public static void FirePlayerQuit(int entityId) + { + try + { + var player = FourKit.UntrackPlayer(entityId); + if (player != null) + { + SyncPlayerFromNative(player); + var evt = new PlayerQuitEvent(player); + FourKit.FireEvent(evt); + BroadcastNativeMessage(evt.getQuitMessage()); + } + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerQuit error: {ex}"); + } + } + + [UnmanagedCallersOnly] + public static int FirePlayerKick(int entityId, int disconnectReason, + IntPtr reasonPtr, int reasonByteLen, + IntPtr outBuf, int outBufSize, IntPtr outLenPtr) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + { + Marshal.WriteInt32(outLenPtr, 0); + return 0; + } + + SyncPlayerFromNative(player); + + var reason = Enum.IsDefined(typeof(DisconnectReason), disconnectReason) + ? (DisconnectReason)disconnectReason + : DisconnectReason.NONE; + + string defaultLeave = $"{player.getName()} was kicked from the game"; + var evt = new PlayerKickEvent(player, reason, defaultLeave); + FourKit.FireEvent(evt); + + if (evt.isCancelled()) + { + Marshal.WriteInt32(outLenPtr, 0); + return 1; + } + + string leaveMessage = evt.getLeaveMessage(); + if (!string.IsNullOrEmpty(leaveMessage)) + { + byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(leaveMessage); + if (utf8Bytes.Length < outBufSize) + { + Marshal.Copy(utf8Bytes, 0, outBuf, utf8Bytes.Length); + Marshal.WriteInt32(outLenPtr, utf8Bytes.Length); + } + else + { + Marshal.WriteInt32(outLenPtr, 0); + } + } + else + { + Marshal.WriteInt32(outLenPtr, 0); + } + + return 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerKick error: {ex}"); + Marshal.WriteInt32(outLenPtr, 0); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FirePlayerMove(int entityId, + double fromX, double fromY, double fromZ, + double toX, double toY, double toZ, + IntPtr outCoords) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + { + Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); + return 0; + } + + SyncPlayerFromNative(player); + + var from = new Location(fromX, fromY, fromZ); + var to = new Location(toX, toY, toZ); + var evt = new PlayerMoveEvent(player, from, to); + FourKit.FireEvent(evt); + + var finalTo = evt.getTo(); + Marshal.Copy(new double[] { finalTo.X, finalTo.Y, finalTo.Z }, 0, outCoords, 3); + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerMove error: {ex}"); + Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); + return 0; + } + } + + [UnmanagedCallersOnly] + public static void UpdatePlayerEntityId(int oldEntityId, int newEntityId) + { + try + { + FourKit.UpdatePlayerEntityId(oldEntityId, newEntityId); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"UpdatePlayerEntityId error: {ex}"); + } + } + + [UnmanagedCallersOnly] + public static int FirePlayerChat(int entityId, IntPtr msgPtr, int msgByteLen, + IntPtr outBuf, int outBufSize, IntPtr outLenPtr) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + { + Marshal.WriteInt32(outLenPtr, 0); + return 0; + } + + SyncPlayerFromNative(player); + + string message = msgByteLen > 0 + ? Marshal.PtrToStringUTF8(msgPtr, msgByteLen) ?? string.Empty + : string.Empty; + + var evt = new PlayerChatEvent(player, message); + FourKit.FireEvent(evt); + + if (evt.isCancelled()) + { + Marshal.WriteInt32(outLenPtr, 0); + return 1; + } + + string formatted = JavaFormat(evt.getFormat(), player.getDisplayName(), evt.getMessage()); + byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(formatted); + if (utf8Bytes.Length < outBufSize) + { + Marshal.Copy(utf8Bytes, 0, outBuf, utf8Bytes.Length); + Marshal.WriteInt32(outLenPtr, utf8Bytes.Length); + } + else + { + Marshal.WriteInt32(outLenPtr, 0); + } + + return 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerChat error: {ex}"); + Marshal.WriteInt32(outLenPtr, 0); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FireBlockPlace(int entityId, int dimId, + int placedX, int placedY, int placedZ, + int againstX, int againstY, int againstZ, + int itemId, int itemCount, int canBuild) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + return 0; + + SyncPlayerFromNative(player); + + var world = FourKit.getWorld(dimId); + var placedBlock = new Block.Block(world, placedX, placedY, placedZ); + var againstBlock = new Block.Block(world, againstX, againstY, againstZ); + + Material mat = Enum.IsDefined(typeof(Material), itemId) ? (Material)itemId : Material.AIR; + var itemInHand = new ItemStack(mat, itemCount); + + var evt = new BlockPlaceEvent(placedBlock, againstBlock, itemInHand, player, canBuild != 0); + FourKit.FireEvent(evt); + + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FireBlockPlace error: {ex}"); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FireBlockBreak(int entityId, int dimId, + int x, int y, int z, int tileId, int data, int exp) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + return exp; + + SyncPlayerFromNative(player); + + var world = FourKit.getWorld(dimId); + var block = new Block.Block(world, x, y, z); + + var evt = new BlockBreakEvent(block, player, exp); + FourKit.FireEvent(evt); + + if (evt.isCancelled()) + return -1; + + return evt.getExpToDrop(); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FireBlockBreak error: {ex}"); + return exp; + } + } + + [UnmanagedCallersOnly] + public static int FireEntityDamage(int entityId, int entityTypeId, int dimId, + double x, double y, double z, int causeId, double damage, IntPtr outDamage, + int damagerEntityId, int damagerEntityTypeId, + double damagerX, double damagerY, double damagerZ) + { + try + { + Entity.Entity? entity = FourKit.GetPlayerByEntityId(entityId); + if (entity is Player player) + { + SyncPlayerFromNative(player); + } + else + { + var entityType = Enum.IsDefined(typeof(EntityType), entityTypeId) + ? (EntityType)entityTypeId + : EntityType.UNKNOWN; + entity = new LivingEntity(entityId, entityType, dimId, x, y, z); + } + + var cause = Enum.IsDefined(typeof(EntityDamageEvent.DamageCause), causeId) + ? (EntityDamageEvent.DamageCause)causeId + : EntityDamageEvent.DamageCause.CUSTOM; + + EntityDamageByEntityEvent? byEntityEvt = null; + if (damagerEntityId >= 0) + { + Entity.Entity? damager = FourKit.GetPlayerByEntityId(damagerEntityId); + if (damager is Player damagerPlayer) + { + SyncPlayerFromNative(damagerPlayer); + } + else + { + var damagerType = Enum.IsDefined(typeof(EntityType), damagerEntityTypeId) + ? (EntityType)damagerEntityTypeId + : EntityType.UNKNOWN; + damager = new LivingEntity(damagerEntityId, damagerType, dimId, damagerX, damagerY, damagerZ); + } + byEntityEvt = new EntityDamageByEntityEvent(damager, entity!, cause, damage); + FourKit.FireEvent(byEntityEvt); + damage = byEntityEvt.getDamage(); + } + + var evt = new EntityDamageEvent(entity!, cause, damage); + if (byEntityEvt != null && byEntityEvt.isCancelled()) + evt.setCancelled(true); + FourKit.FireEvent(evt); + + Marshal.Copy(new double[] { evt.getDamage() }, 0, outDamage, 1); + + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FireEntityDamage error: {ex}"); + Marshal.Copy(new double[] { damage }, 0, outDamage, 1); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FireSignChange(int entityId, int dimId, + int x, int y, int z, + IntPtr line0Ptr, int line0Len, + IntPtr line1Ptr, int line1Len, + IntPtr line2Ptr, int line2Len, + IntPtr line3Ptr, int line3Len, + IntPtr outBuf, int outBufSize, IntPtr outLensPtr) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + { + WriteSignOutLens(outLensPtr, [0, 0, 0, 0]); + return 0; + } + + SyncPlayerFromNative(player); + + string[] lines = + [ + line0Len > 0 ? Marshal.PtrToStringUTF8(line0Ptr, line0Len) ?? string.Empty : string.Empty, + line1Len > 0 ? Marshal.PtrToStringUTF8(line1Ptr, line1Len) ?? string.Empty : string.Empty, + line2Len > 0 ? Marshal.PtrToStringUTF8(line2Ptr, line2Len) ?? string.Empty : string.Empty, + line3Len > 0 ? Marshal.PtrToStringUTF8(line3Ptr, line3Len) ?? string.Empty : string.Empty, + ]; + + var world = FourKit.getWorld(dimId); + var block = new Block.Block(world, x, y, z); + var evt = new Event.Block.SignChangeEvent(block, player, lines); + FourKit.FireEvent(evt); + + int offset = 0; + int[] lens = new int[4]; + for (int i = 0; i < 4; i++) + { + byte[] utf8 = System.Text.Encoding.UTF8.GetBytes(evt.getLine(i)); + if (offset + utf8.Length <= outBufSize) + { + Marshal.Copy(utf8, 0, outBuf + offset, utf8.Length); + lens[i] = utf8.Length; + offset += utf8.Length; + } + } + WriteSignOutLens(outLensPtr, lens); + + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FireSignChange error: {ex}"); + WriteSignOutLens(outLensPtr, [0, 0, 0, 0]); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FireEntityDeath(int entityId, int entityTypeId, int dimId, + double x, double y, double z, int exp) + { + try + { + var entityType = Enum.IsDefined(typeof(EntityType), entityTypeId) + ? (EntityType)entityTypeId + : EntityType.UNKNOWN; + var entity = new LivingEntity(entityId, entityType, dimId, x, y, z); + + var drops = new List(); + var evt = new EntityDeathEvent(entity, drops, exp); + FourKit.FireEvent(evt); + + return evt.getDroppedExp(); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FireEntityDeath error: {ex}"); + return exp; + } + } + + [UnmanagedCallersOnly] + public static int FirePlayerDeath(int entityId, + IntPtr deathMsgPtr, int deathMsgByteLen, int exp, + IntPtr outMsgBuf, int outMsgBufSize, IntPtr outMsgLenPtr, IntPtr outKeepInventoryPtr, + IntPtr outNewExpPtr, IntPtr outNewLevelPtr, IntPtr outKeepLevelPtr) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + { + Marshal.WriteInt32(outMsgLenPtr, 0); + Marshal.WriteInt32(outKeepInventoryPtr, 0); + Marshal.WriteInt32(outNewExpPtr, 0); + Marshal.WriteInt32(outNewLevelPtr, 0); + Marshal.WriteInt32(outKeepLevelPtr, 0); + return exp; + } + + SyncPlayerFromNative(player); + + string deathMessage = deathMsgByteLen > 0 + ? Marshal.PtrToStringUTF8(deathMsgPtr, deathMsgByteLen) ?? string.Empty + : string.Empty; + + var drops = new List(); + + var playerEvt = new PlayerDeathEvent(player, drops, exp, deathMessage); + FourKit.FireEvent(playerEvt); + + var entityEvt = new EntityDeathEvent(player, playerEvt.getDrops(), playerEvt.getDroppedExp()); + FourKit.FireEvent(entityEvt); + + int finalExp = entityEvt.getDroppedExp(); + + string finalMsg = playerEvt.getDeathMessage(); + byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(finalMsg); + if (utf8Bytes.Length < outMsgBufSize) + { + Marshal.Copy(utf8Bytes, 0, outMsgBuf, utf8Bytes.Length); + Marshal.WriteInt32(outMsgLenPtr, utf8Bytes.Length); + } + else + { + Marshal.WriteInt32(outMsgLenPtr, 0); + } + + Marshal.WriteInt32(outKeepInventoryPtr, playerEvt.getKeepInventory() ? 1 : 0); + Marshal.WriteInt32(outNewExpPtr, playerEvt.getNewExp()); + Marshal.WriteInt32(outNewLevelPtr, playerEvt.getNewLevel()); + Marshal.WriteInt32(outKeepLevelPtr, playerEvt.getKeepLevel() ? 1 : 0); + + return finalExp; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerDeath error: {ex}"); + Marshal.WriteInt32(outMsgLenPtr, 0); + Marshal.WriteInt32(outKeepInventoryPtr, 0); + Marshal.WriteInt32(outNewExpPtr, 0); + Marshal.WriteInt32(outNewLevelPtr, 0); + Marshal.WriteInt32(outKeepLevelPtr, 0); + return exp; + } + } + + [UnmanagedCallersOnly] + public static long FirePlayerDropItem(int entityId, int itemId, int itemCount, int itemAux, + IntPtr outItemIdPtr, IntPtr outItemCountPtr, IntPtr outItemAuxPtr) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + { + Marshal.WriteInt32(outItemIdPtr, itemId); + Marshal.WriteInt32(outItemCountPtr, itemCount); + Marshal.WriteInt32(outItemAuxPtr, itemAux); + return 0; + } + + SyncPlayerFromNative(player); + + Material mat = Enum.IsDefined(typeof(Material), itemId) ? (Material)itemId : Material.AIR; + var itemStack = new ItemStack(mat, itemCount, (short)itemAux); + + var evt = new PlayerDropItemEvent(player, itemStack); + FourKit.FireEvent(evt); + + var result = evt.getItemDrop(); + Marshal.WriteInt32(outItemIdPtr, result.getTypeId()); + Marshal.WriteInt32(outItemCountPtr, result.getAmount()); + Marshal.WriteInt32(outItemAuxPtr, result.getDurability()); + + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerDropItem error: {ex}"); + Marshal.WriteInt32(outItemIdPtr, itemId); + Marshal.WriteInt32(outItemCountPtr, itemCount); + Marshal.WriteInt32(outItemAuxPtr, itemAux); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FirePlayerInteract(int entityId, int action, + int itemId, int itemCount, int itemAux, + int clickedX, int clickedY, int clickedZ, + int blockFace, int dimId, + IntPtr outUseItemInHandPtr) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + { + Marshal.WriteInt32(outUseItemInHandPtr, 1); + return 0; + } + + SyncPlayerFromNative(player); + + var actionEnum = Enum.IsDefined(typeof(Block.Action), action) + ? (Block.Action)action + : Block.Action.RIGHT_CLICK_AIR; + + var faceEnum = Enum.IsDefined(typeof(Block.BlockFace), blockFace) + ? (Block.BlockFace)blockFace + : Block.BlockFace.SELF; + + ItemStack? itemStack = null; + if (itemId > 0) + itemStack = new ItemStack(itemId, itemCount, (short)itemAux); + + Block.Block? clickedBlock = null; + bool hasBlock = actionEnum == Block.Action.LEFT_CLICK_BLOCK + || actionEnum == Block.Action.RIGHT_CLICK_BLOCK + || actionEnum == Block.Action.PHYSICAL; + if (hasBlock) + { + var world = FourKit.getWorld(dimId); + if (world != null) + clickedBlock = new Block.Block(world, clickedX, clickedY, clickedZ); + } + + var evt = new PlayerInteractEvent( + player, actionEnum, itemStack, clickedBlock, faceEnum); + FourKit.FireEvent(evt); + + Marshal.WriteInt32(outUseItemInHandPtr, evt.useItemInHand() ? 1 : 0); + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerInteract error: {ex}"); + Marshal.WriteInt32(outUseItemInHandPtr, 1); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FirePlayerInteractEntity(int playerEntityId, + int targetEntityId, int targetEntityTypeId, + int dimId, double targetX, double targetY, double targetZ, + float targetHealth, float targetMaxHealth, float targetEyeHeight) + { + try + { + var player = FourKit.GetPlayerByEntityId(playerEntityId); + if (player == null) + return 0; + + SyncPlayerFromNative(player); + + Entity.Entity? target = FourKit.GetPlayerByEntityId(targetEntityId); + if (target is Player targetPlayer) + { + SyncPlayerFromNative(targetPlayer); + } + else + { + var entityType = Enum.IsDefined(typeof(EntityType), targetEntityTypeId) + ? (EntityType)targetEntityTypeId + : EntityType.UNKNOWN; + var living = new LivingEntity(targetEntityId, entityType, dimId, targetX, targetY, targetZ, + targetHealth, targetMaxHealth); + living.SetEyeHeightInternal(targetEyeHeight); + target = living; + } + + var evt = new PlayerInteractEntityEvent(player, target); + FourKit.FireEvent(evt); + + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerInteractEntity error: {ex}"); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FirePlayerPickupItem(int playerEntityId, + int itemEntityId, int dimId, double itemX, double itemY, double itemZ, + int itemId, int itemCount, int itemAux, int remaining, + IntPtr outItemIdPtr, IntPtr outItemCountPtr, IntPtr outItemAuxPtr) + { + try + { + var player = FourKit.GetPlayerByEntityId(playerEntityId); + if (player == null) + { + Marshal.WriteInt32(outItemIdPtr, itemId); + Marshal.WriteInt32(outItemCountPtr, itemCount); + Marshal.WriteInt32(outItemAuxPtr, itemAux); + // todo: fix + return 1; + } + + SyncPlayerFromNative(player); + + var itemStack = new Inventory.ItemStack(itemId, itemCount, (short)itemAux); + var item = new Entity.Item(itemEntityId, dimId, itemX, itemY, itemZ, itemStack); + var evt = new PlayerPickupItemEvent(player, item, remaining); + FourKit.FireEvent(evt); + + var result = evt.getItem().getItemStack(); + Marshal.WriteInt32(outItemIdPtr, result.getTypeId()); + Marshal.WriteInt32(outItemCountPtr, result.getAmount()); + Marshal.WriteInt32(outItemAuxPtr, result.getDurability()); + + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerPickupItem error: {ex}"); + Marshal.WriteInt32(outItemIdPtr, itemId); + Marshal.WriteInt32(outItemCountPtr, itemCount); + Marshal.WriteInt32(outItemAuxPtr, itemAux); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FireInventoryOpen(int entityId, int nativeContainerType, + IntPtr titlePtr, int titleByteLen, int containerSize) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) return 0; + + SyncPlayerFromNative(player); + + string title = titleByteLen > 0 + ? Marshal.PtrToStringUTF8(titlePtr, titleByteLen) ?? string.Empty + : string.Empty; + + InventoryType invType = MapNativeContainerType(nativeContainerType); + Inventory.Inventory topInv = CreateContainerInventory(invType, nativeContainerType, title, containerSize, entityId); + var bottomInv = player.getInventory(); + + var view = new InventoryView(topInv, bottomInv, player, invType); + var evt = new InventoryOpenEvent(view); + FourKit.FireEvent(evt); + + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FireInventoryOpen error: {ex}"); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FireInventoryClick(int entityId, int slot, int button, int clickType, + int nativeContainerType, int containerSize, IntPtr titleUtf8Ptr, int titleByteLen) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) return 0; + + SyncPlayerFromNative(player); + + ClickType click = MapNativeClickType(clickType, button); + InventoryAction action = DetermineInventoryAction(click, slot); + + InventoryType invType; + Inventory.Inventory topInv; + if (nativeContainerType < 0) + { + invType = InventoryType.PLAYER; + topInv = player.getInventory(); + } + else + { + invType = MapNativeContainerType(nativeContainerType); + int size = containerSize > 0 ? containerSize : invType.getDefaultSize(); + string title = titleByteLen > 0 && titleUtf8Ptr != IntPtr.Zero + ? Marshal.PtrToStringUTF8(titleUtf8Ptr, titleByteLen) ?? invType.getDefaultTitle() + : invType.getDefaultTitle(); + topInv = CreateContainerInventory(invType, nativeContainerType, title, size, entityId); + } + + var bottomInv = player.getInventory(); + var view = new InventoryView(topInv, bottomInv, player, invType); + + SlotType slotType = SlotType.CONTAINER; + if (slot == InventoryView.OUTSIDE) + slotType = SlotType.OUTSIDE; + + Inventory.Inventory._slotModifiedByPlugin = false; + int hotbarKey = click == ClickType.NUMBER_KEY ? button : -1; + var evt = new InventoryClickEvent(view, slotType, slot, click, action, hotbarKey); + FourKit.FireEvent(evt); + + if (evt.isCancelled()) return 1; + if (Inventory.Inventory._slotModifiedByPlugin) return 2; + return 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FireInventoryClick error: {ex}"); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int HandlePlayerCommand(int entityId, IntPtr cmdUtf8, int cmdByteLen) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + return 0; + + SyncPlayerFromNative(player); + + string commandLine = cmdByteLen > 0 + ? Marshal.PtrToStringUTF8(cmdUtf8, cmdByteLen) ?? string.Empty + : string.Empty; + + if (string.IsNullOrEmpty(commandLine)) + return 0; + + return FourKit.DispatchCommand(player, commandLine) ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"HandlePlayerCommand error: {ex}"); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int HandleConsoleCommand(IntPtr cmdUtf8, int cmdByteLen) + { + try + { + string commandLine = cmdByteLen > 0 + ? Marshal.PtrToStringUTF8(cmdUtf8, cmdByteLen) ?? string.Empty + : string.Empty; + + if (string.IsNullOrEmpty(commandLine)) + return 0; + + string trimmed = commandLine.StartsWith('/') ? commandLine[1..] : commandLine; + string[] parts = trimmed.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 0) + return 0; + + if (!FourKit.HasCommand(parts[0])) + return 0; + + FourKit.DispatchCommand(FourKit.getConsoleSender(), commandLine); + return 1; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"HandleConsoleCommand error: {ex}"); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int GetPluginCommandHelp(IntPtr outBuf, int outBufSize, IntPtr outLenPtr) + { + try + { + var entries = FourKit.GetRegisteredCommandHelp(); + if (entries.Count == 0) + { + if (outLenPtr != IntPtr.Zero) + Marshal.WriteInt32(outLenPtr, 0); + return 0; + } + + var sb = new System.Text.StringBuilder(); + foreach (var (usage, description) in entries) + { + sb.Append(usage); + sb.Append('\0'); + sb.Append(description); + sb.Append('\0'); + } + + byte[] utf8 = System.Text.Encoding.UTF8.GetBytes(sb.ToString()); + int copyLen = Math.Min(utf8.Length, outBufSize); + if (outBuf != IntPtr.Zero && copyLen > 0) + Marshal.Copy(utf8, 0, outBuf, copyLen); + if (outLenPtr != IntPtr.Zero) + Marshal.WriteInt32(outLenPtr, copyLen); + + return entries.Count; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"GetPluginCommandHelp error: {ex}"); + if (outLenPtr != IntPtr.Zero) + Marshal.WriteInt32(outLenPtr, 0); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FirePlayerTeleport(int entityId, + double fromX, double fromY, double fromZ, int fromDimId, + double toX, double toY, double toZ, int toDimId, + int cause, IntPtr outCoords) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + { + Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); + return 0; + } + + SyncPlayerFromNative(player); + + var fromWorld = FourKit.getWorld(fromDimId); + var toWorld = FourKit.getWorld(toDimId); + var from = new Location(fromWorld, fromX, fromY, fromZ); + var to = new Location(toWorld, toX, toY, toZ); + var teleportCause = cause >= 0 && cause <= (int)PlayerTeleportEvent.TeleportCause.UNKNOWN + ? (PlayerTeleportEvent.TeleportCause)cause + : PlayerTeleportEvent.TeleportCause.UNKNOWN; + + var evt = new PlayerTeleportEvent(player, from, to, teleportCause); + FourKit.FireEvent(evt); + + var finalTo = evt.getTo(); + Marshal.Copy(new double[] { finalTo.X, finalTo.Y, finalTo.Z }, 0, outCoords, 3); + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerTeleport error: {ex}"); + Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FirePlayerPortal(int entityId, + double fromX, double fromY, double fromZ, int fromDimId, + double toX, double toY, double toZ, int toDimId, + int cause, IntPtr outCoords) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) + { + Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); + return 0; + } + + SyncPlayerFromNative(player); + + var fromWorld = FourKit.getWorld(fromDimId); + var toWorld = FourKit.getWorld(toDimId); + var from = new Location(fromWorld, fromX, fromY, fromZ); + var to = new Location(toWorld, toX, toY, toZ); + var teleportCause = cause >= 0 && cause <= (int)PlayerTeleportEvent.TeleportCause.UNKNOWN + ? (PlayerTeleportEvent.TeleportCause)cause + : PlayerTeleportEvent.TeleportCause.UNKNOWN; + + var evt = new PlayerPortalEvent(player, from, to, teleportCause); + FourKit.FireEvent(evt); + + var finalTo = evt.getTo(); + Marshal.Copy(new double[] { finalTo.X, finalTo.Y, finalTo.Z }, 0, outCoords, 3); + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FirePlayerPortal error: {ex}"); + Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); + return 0; + } + } + + [UnmanagedCallersOnly] + public static int FireBedEnter(int entityId, int dimId, int bedX, int bedY, int bedZ) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) return 0; + + SyncPlayerFromNative(player); + + var world = FourKit.getWorld(dimId); + var bed = new Block.Block(world, bedX, bedY, bedZ); + var evt = new Event.Player.PlayerBedEnterEvent(player, bed); + FourKit.FireEvent(evt); + + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FireBedEnter error: {ex}"); + return 0; + } + } + + [UnmanagedCallersOnly] + public static void FireBedLeave(int entityId, int dimId, int bedX, int bedY, int bedZ) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) return; + + SyncPlayerFromNative(player); + + var world = FourKit.getWorld(dimId); + var bed = new Block.Block(world, bedX, bedY, bedZ); + var evt = new Event.Player.PlayerBedLeaveEvent(player, bed); + FourKit.FireEvent(evt); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FireBedLeave error: {ex}"); + } + } +} diff --git a/Minecraft.Server.FourKit/FourKitHost.cs b/Minecraft.Server.FourKit/FourKitHost.cs index 092ac480..fb086262 100644 --- a/Minecraft.Server.FourKit/FourKitHost.cs +++ b/Minecraft.Server.FourKit/FourKitHost.cs @@ -1,18 +1,11 @@ -using System.Runtime.InteropServices; -using Minecraft.Server.FourKit.Block; +using System.Runtime.InteropServices; using Minecraft.Server.FourKit.Entity; -using Minecraft.Server.FourKit.Event; -using Minecraft.Server.FourKit.Event.Block; -using Minecraft.Server.FourKit.Event.Entity; -using Minecraft.Server.FourKit.Event.Player; using Minecraft.Server.FourKit.Event.Inventory; using Minecraft.Server.FourKit.Inventory; namespace Minecraft.Server.FourKit; -// todo: clean up - -public static class FourKitHost +public static partial class FourKitHost { private static PluginLoader? s_loader; @@ -36,340 +29,6 @@ public static class FourKitHost } } - [UnmanagedCallersOnly] - public static void FirePlayerJoin(int entityId, IntPtr namePtr, int nameByteLen, IntPtr uuidPtr, int uuidByteLen) - { - try - { - string name = nameByteLen > 0 - ? Marshal.PtrToStringUTF8(namePtr, nameByteLen) ?? string.Empty - : string.Empty; - - string uuidStr = uuidByteLen > 0 - ? Marshal.PtrToStringUTF8(uuidPtr, uuidByteLen) ?? string.Empty - : string.Empty; - - var player = FourKit.TrackPlayer(entityId, name); - player.SetPlayerUniqueIdInternal(ParseOrHashGuid(uuidStr)); - SyncPlayerFromNative(player); - var evt = new PlayerJoinEvent(player); - FourKit.FireEvent(evt); - BroadcastNativeMessage(evt.getJoinMessage()); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerJoin error: {ex}"); - } - } - - private static Guid ParseOrHashGuid(string s) - { - if (Guid.TryParse(s, out var g)) return g; - if (string.IsNullOrEmpty(s)) return Guid.NewGuid(); - return new Guid(System.Security.Cryptography.MD5.HashData(System.Text.Encoding.UTF8.GetBytes(s))); - } - - [UnmanagedCallersOnly] - public static void FirePlayerQuit(int entityId) - { - try - { - var player = FourKit.UntrackPlayer(entityId); - if (player != null) - { - SyncPlayerFromNative(player); - var evt = new PlayerQuitEvent(player); - FourKit.FireEvent(evt); - BroadcastNativeMessage(evt.getQuitMessage()); - } - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerQuit error: {ex}"); - } - } - - [UnmanagedCallersOnly] - public static int FirePlayerKick(int entityId, int disconnectReason, - IntPtr reasonPtr, int reasonByteLen, - IntPtr outBuf, int outBufSize, IntPtr outLenPtr) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) - { - Marshal.WriteInt32(outLenPtr, 0); - return 0; - } - - SyncPlayerFromNative(player); - - var reason = Enum.IsDefined(typeof(DisconnectReason), disconnectReason) - ? (DisconnectReason)disconnectReason - : DisconnectReason.NONE; - - string defaultLeave = $"{player.getName()} was kicked from the game"; - var evt = new PlayerKickEvent(player, reason, defaultLeave); - FourKit.FireEvent(evt); - - if (evt.isCancelled()) - { - Marshal.WriteInt32(outLenPtr, 0); - return 1; - } - - string leaveMessage = evt.getLeaveMessage(); - if (!string.IsNullOrEmpty(leaveMessage)) - { - byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(leaveMessage); - if (utf8Bytes.Length < outBufSize) - { - Marshal.Copy(utf8Bytes, 0, outBuf, utf8Bytes.Length); - Marshal.WriteInt32(outLenPtr, utf8Bytes.Length); - } - else - { - Marshal.WriteInt32(outLenPtr, 0); - } - } - else - { - Marshal.WriteInt32(outLenPtr, 0); - } - - return 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerKick error: {ex}"); - Marshal.WriteInt32(outLenPtr, 0); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int FirePlayerMove(int entityId, - double fromX, double fromY, double fromZ, - double toX, double toY, double toZ, - IntPtr outCoords) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) - { - Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); - return 0; - } - - SyncPlayerFromNative(player); - - var from = new Location(fromX, fromY, fromZ); - var to = new Location(toX, toY, toZ); - var evt = new PlayerMoveEvent(player, from, to); - FourKit.FireEvent(evt); - - var finalTo = evt.getTo(); - Marshal.Copy(new double[] { finalTo.X, finalTo.Y, finalTo.Z }, 0, outCoords, 3); - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerMove error: {ex}"); - Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); - return 0; - } - } - - [UnmanagedCallersOnly] - public static void UpdatePlayerEntityId(int oldEntityId, int newEntityId) - { - try - { - FourKit.UpdatePlayerEntityId(oldEntityId, newEntityId); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"UpdatePlayerEntityId error: {ex}"); - } - } - - [UnmanagedCallersOnly] - public static int FirePlayerChat(int entityId, IntPtr msgPtr, int msgByteLen, - IntPtr outBuf, int outBufSize, IntPtr outLenPtr) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) - { - Marshal.WriteInt32(outLenPtr, 0); - return 0; - } - - SyncPlayerFromNative(player); - - string message = msgByteLen > 0 - ? Marshal.PtrToStringUTF8(msgPtr, msgByteLen) ?? string.Empty - : string.Empty; - - var evt = new PlayerChatEvent(player, message); - FourKit.FireEvent(evt); - - if (evt.isCancelled()) - { - Marshal.WriteInt32(outLenPtr, 0); - return 1; - } - - string formatted = JavaFormat(evt.getFormat(), player.getDisplayName(), evt.getMessage()); - byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(formatted); - if (utf8Bytes.Length < outBufSize) - { - Marshal.Copy(utf8Bytes, 0, outBuf, utf8Bytes.Length); - Marshal.WriteInt32(outLenPtr, utf8Bytes.Length); - } - else - { - Marshal.WriteInt32(outLenPtr, 0); - } - - return 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerChat error: {ex}"); - Marshal.WriteInt32(outLenPtr, 0); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int FireBlockPlace(int entityId, int dimId, - int placedX, int placedY, int placedZ, - int againstX, int againstY, int againstZ, - int itemId, int itemCount, int canBuild) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) - return 0; - - SyncPlayerFromNative(player); - - var world = FourKit.getWorld(dimId); - var placedBlock = new Block.Block(world, placedX, placedY, placedZ); - var againstBlock = new Block.Block(world, againstX, againstY, againstZ); - - Material mat = Enum.IsDefined(typeof(Material), itemId) ? (Material)itemId : Material.AIR; - var itemInHand = new ItemStack(mat, itemCount); - - var evt = new BlockPlaceEvent(placedBlock, againstBlock, itemInHand, player, canBuild != 0); - FourKit.FireEvent(evt); - - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FireBlockPlace error: {ex}"); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int FireBlockBreak(int entityId, int dimId, - int x, int y, int z, int tileId, int data, int exp) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) - return exp; - - SyncPlayerFromNative(player); - - var world = FourKit.getWorld(dimId); - var block = new Block.Block(world, x, y, z); - - var evt = new BlockBreakEvent(block, player, exp); - FourKit.FireEvent(evt); - - if (evt.isCancelled()) - return -1; - - return evt.getExpToDrop(); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FireBlockBreak error: {ex}"); - return exp; - } - } - - [UnmanagedCallersOnly] - public static int FireEntityDamage(int entityId, int entityTypeId, int dimId, - double x, double y, double z, int causeId, double damage, IntPtr outDamage, - int damagerEntityId, int damagerEntityTypeId, - double damagerX, double damagerY, double damagerZ) - { - try - { - Entity.Entity? entity = FourKit.GetPlayerByEntityId(entityId); - if (entity is Player player) - { - SyncPlayerFromNative(player); - } - else - { - var entityType = Enum.IsDefined(typeof(EntityType), entityTypeId) - ? (EntityType)entityTypeId - : EntityType.UNKNOWN; - entity = new LivingEntity(entityId, entityType, dimId, x, y, z); - } - - var cause = Enum.IsDefined(typeof(EntityDamageEvent.DamageCause), causeId) - ? (EntityDamageEvent.DamageCause)causeId - : EntityDamageEvent.DamageCause.CUSTOM; - - EntityDamageByEntityEvent? byEntityEvt = null; - if (damagerEntityId >= 0) - { - Entity.Entity? damager = FourKit.GetPlayerByEntityId(damagerEntityId); - if (damager is Player damagerPlayer) - { - SyncPlayerFromNative(damagerPlayer); - } - else - { - var damagerType = Enum.IsDefined(typeof(EntityType), damagerEntityTypeId) - ? (EntityType)damagerEntityTypeId - : EntityType.UNKNOWN; - damager = new LivingEntity(damagerEntityId, damagerType, dimId, damagerX, damagerY, damagerZ); - } - byEntityEvt = new EntityDamageByEntityEvent(damager, entity!, cause, damage); - FourKit.FireEvent(byEntityEvt); - damage = byEntityEvt.getDamage(); - } - - var evt = new EntityDamageEvent(entity!, cause, damage); - if (byEntityEvt != null && byEntityEvt.isCancelled()) - evt.setCancelled(true); - FourKit.FireEvent(evt); - - Marshal.Copy(new double[] { evt.getDamage() }, 0, outDamage, 1); - - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FireEntityDamage error: {ex}"); - Marshal.Copy(new double[] { damage }, 0, outDamage, 1); - return 0; - } - } - [UnmanagedCallersOnly] public static void Shutdown() { @@ -386,627 +45,16 @@ public static class FourKitHost } } - [UnmanagedCallersOnly] - public static int FireSignChange(int entityId, int dimId, - int x, int y, int z, - IntPtr line0Ptr, int line0Len, - IntPtr line1Ptr, int line1Len, - IntPtr line2Ptr, int line2Len, - IntPtr line3Ptr, int line3Len, - IntPtr outBuf, int outBufSize, IntPtr outLensPtr) + + private static Guid ParseOrHashGuid(string s) { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) - { - WriteSignOutLens(outLensPtr, [0, 0, 0, 0]); - return 0; - } - - SyncPlayerFromNative(player); - - string[] lines = - [ - line0Len > 0 ? Marshal.PtrToStringUTF8(line0Ptr, line0Len) ?? string.Empty : string.Empty, - line1Len > 0 ? Marshal.PtrToStringUTF8(line1Ptr, line1Len) ?? string.Empty : string.Empty, - line2Len > 0 ? Marshal.PtrToStringUTF8(line2Ptr, line2Len) ?? string.Empty : string.Empty, - line3Len > 0 ? Marshal.PtrToStringUTF8(line3Ptr, line3Len) ?? string.Empty : string.Empty, - ]; - - var world = FourKit.getWorld(dimId); - var block = new Block.Block(world, x, y, z); - var evt = new Event.Block.SignChangeEvent(block, player, lines); - FourKit.FireEvent(evt); - - int offset = 0; - int[] lens = new int[4]; - for (int i = 0; i < 4; i++) - { - byte[] utf8 = System.Text.Encoding.UTF8.GetBytes(evt.getLine(i)); - if (offset + utf8.Length <= outBufSize) - { - Marshal.Copy(utf8, 0, outBuf + offset, utf8.Length); - lens[i] = utf8.Length; - offset += utf8.Length; - } - } - WriteSignOutLens(outLensPtr, lens); - - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FireSignChange error: {ex}"); - WriteSignOutLens(outLensPtr, [0, 0, 0, 0]); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int FireEntityDeath(int entityId, int entityTypeId, int dimId, - double x, double y, double z, int exp) - { - try - { - var entityType = Enum.IsDefined(typeof(EntityType), entityTypeId) - ? (EntityType)entityTypeId - : EntityType.UNKNOWN; - var entity = new LivingEntity(entityId, entityType, dimId, x, y, z); - - var drops = new List(); - var evt = new EntityDeathEvent(entity, drops, exp); - FourKit.FireEvent(evt); - - return evt.getDroppedExp(); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FireEntityDeath error: {ex}"); - return exp; - } - } - - [UnmanagedCallersOnly] - public static int FirePlayerDeath(int entityId, - IntPtr deathMsgPtr, int deathMsgByteLen, int exp, - IntPtr outMsgBuf, int outMsgBufSize, IntPtr outMsgLenPtr, IntPtr outKeepInventoryPtr, - IntPtr outNewExpPtr, IntPtr outNewLevelPtr, IntPtr outKeepLevelPtr) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) - { - Marshal.WriteInt32(outMsgLenPtr, 0); - Marshal.WriteInt32(outKeepInventoryPtr, 0); - Marshal.WriteInt32(outNewExpPtr, 0); - Marshal.WriteInt32(outNewLevelPtr, 0); - Marshal.WriteInt32(outKeepLevelPtr, 0); - return exp; - } - - SyncPlayerFromNative(player); - - string deathMessage = deathMsgByteLen > 0 - ? Marshal.PtrToStringUTF8(deathMsgPtr, deathMsgByteLen) ?? string.Empty - : string.Empty; - - var drops = new List(); - - var playerEvt = new PlayerDeathEvent(player, drops, exp, deathMessage); - FourKit.FireEvent(playerEvt); - - var entityEvt = new EntityDeathEvent(player, playerEvt.getDrops(), playerEvt.getDroppedExp()); - FourKit.FireEvent(entityEvt); - - int finalExp = entityEvt.getDroppedExp(); - - string finalMsg = playerEvt.getDeathMessage(); - byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(finalMsg); - if (utf8Bytes.Length < outMsgBufSize) - { - Marshal.Copy(utf8Bytes, 0, outMsgBuf, utf8Bytes.Length); - Marshal.WriteInt32(outMsgLenPtr, utf8Bytes.Length); - } - else - { - Marshal.WriteInt32(outMsgLenPtr, 0); - } - - Marshal.WriteInt32(outKeepInventoryPtr, playerEvt.getKeepInventory() ? 1 : 0); - Marshal.WriteInt32(outNewExpPtr, playerEvt.getNewExp()); - Marshal.WriteInt32(outNewLevelPtr, playerEvt.getNewLevel()); - Marshal.WriteInt32(outKeepLevelPtr, playerEvt.getKeepLevel() ? 1 : 0); - - return finalExp; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerDeath error: {ex}"); - Marshal.WriteInt32(outMsgLenPtr, 0); - Marshal.WriteInt32(outKeepInventoryPtr, 0); - Marshal.WriteInt32(outNewExpPtr, 0); - Marshal.WriteInt32(outNewLevelPtr, 0); - Marshal.WriteInt32(outKeepLevelPtr, 0); - return exp; - } - } - - private static void WriteSignOutLens(IntPtr ptr, int[] lens) - { - Marshal.Copy(lens, 0, ptr, 4); - } - - - // todo: rework callback funcs due to it being messy - - [UnmanagedCallersOnly] - public static void SetNativeCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity) - { - try - { - NativeBridge.SetCallbacks(damage, setHealth, teleport, setGameMode, broadcastMessage, setFallDistance, getPlayerSnapshot, sendMessage, setWalkSpeed, teleportEntity); - ServerLog.Info("fourkit", "Native callbacks registered."); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"SetNativeCallbacks error: {ex}"); - } - } - - [UnmanagedCallersOnly] - public static void SetWorldCallbacks(IntPtr getTileId, IntPtr getTileData, IntPtr setTile, IntPtr setTileData, IntPtr breakBlock, IntPtr getHighestBlockY, IntPtr getWorldInfo, IntPtr setWorldTime, IntPtr setWeather, IntPtr createExplosion, IntPtr strikeLightning, IntPtr setSpawnLocation, IntPtr dropItem) - { - try - { - NativeBridge.SetWorldCallbacks(getTileId, getTileData, setTile, setTileData, breakBlock, getHighestBlockY, getWorldInfo, setWorldTime, setWeather, createExplosion, strikeLightning, setSpawnLocation, dropItem); - //ServerLog.Info("fourkit", "World native callbacks registered."); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"SetWorldCallbacks error: {ex}"); - } - } - - [UnmanagedCallersOnly] - public static void SetPlayerCallbacks(IntPtr kickPlayer, IntPtr banPlayer, IntPtr banPlayerIp, IntPtr getPlayerAddress) - { - try - { - NativeBridge.SetPlayerCallbacks(kickPlayer, banPlayer, banPlayerIp, getPlayerAddress); - //ServerLog.Info("fourkit", "Player native callbacks registered."); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"SetPlayerCallbacks error: {ex}"); - } - } - - [UnmanagedCallersOnly] - public static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot) - { - try - { - NativeBridge.SetInventoryCallbacks(getPlayerInventory, setPlayerInventorySlot, getContainerContents, setContainerSlot, getContainerViewerEntityIds, closeContainer, openVirtualContainer, getItemMeta, setItemMeta, setHeldItemSlot); - //ServerLog.Info("fourkit", "Inventory native callbacks registered."); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"SetInventoryCallbacks error: {ex}"); - } - } - - [UnmanagedCallersOnly] - public static void SetEntityCallbacks(IntPtr setSneaking, IntPtr setVelocity, IntPtr setAllowFlight, IntPtr playSound, IntPtr setSleepingIgnored) - { - // setsneaking and setallowflight here cuz i am lazy - // should be under player stuff ill do that later - try - { - NativeBridge.SetEntityCallbacks(setSneaking, setVelocity, setAllowFlight, playSound, setSleepingIgnored); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"SetEntityCallbacks error: {ex}"); - } - } - - [UnmanagedCallersOnly] - public static void SetExperienceCallbacks(IntPtr setLevel, IntPtr setExp, IntPtr giveExp, IntPtr giveExpLevels, IntPtr setFoodLevel, IntPtr setSaturation, IntPtr setExhaustion) - { - try - { - NativeBridge.SetExperienceCallbacks(setLevel, setExp, giveExp, giveExpLevels, setFoodLevel, setSaturation, setExhaustion); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"SetExperienceCallbacks error: {ex}"); - } - } - - [UnmanagedCallersOnly] - public static void SetParticleCallbacks(IntPtr spawnParticle) - { - try - { - NativeBridge.SetParticleCallbacks(spawnParticle); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"SetParticleCallbacks error: {ex}"); - } - } - - [UnmanagedCallersOnly] - public static void SetVehicleCallbacks(IntPtr setPassenger, IntPtr leaveVehicle, IntPtr eject, IntPtr getVehicleId, IntPtr getPassengerId, IntPtr getEntityInfo) - { - try - { - NativeBridge.SetVehicleCallbacks(setPassenger, leaveVehicle, eject, getVehicleId, getPassengerId, getEntityInfo); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"SetVehicleCallbacks error: {ex}"); - } - } - - [UnmanagedCallersOnly] - public static long FirePlayerDropItem(int entityId, int itemId, int itemCount, int itemAux, - IntPtr outItemIdPtr, IntPtr outItemCountPtr, IntPtr outItemAuxPtr) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) - { - Marshal.WriteInt32(outItemIdPtr, itemId); - Marshal.WriteInt32(outItemCountPtr, itemCount); - Marshal.WriteInt32(outItemAuxPtr, itemAux); - return 0; - } - - SyncPlayerFromNative(player); - - Material mat = Enum.IsDefined(typeof(Material), itemId) ? (Material)itemId : Material.AIR; - var itemStack = new ItemStack(mat, itemCount, (short)itemAux); - - var evt = new PlayerDropItemEvent(player, itemStack); - FourKit.FireEvent(evt); - - var result = evt.getItemDrop(); - Marshal.WriteInt32(outItemIdPtr, result.getTypeId()); - Marshal.WriteInt32(outItemCountPtr, result.getAmount()); - Marshal.WriteInt32(outItemAuxPtr, result.getDurability()); - - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerDropItem error: {ex}"); - Marshal.WriteInt32(outItemIdPtr, itemId); - Marshal.WriteInt32(outItemCountPtr, itemCount); - Marshal.WriteInt32(outItemAuxPtr, itemAux); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int FirePlayerInteract(int entityId, int action, - int itemId, int itemCount, int itemAux, - int clickedX, int clickedY, int clickedZ, - int blockFace, int dimId, - IntPtr outUseItemInHandPtr) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) - { - Marshal.WriteInt32(outUseItemInHandPtr, 1); - return 0; - } - - SyncPlayerFromNative(player); - - var actionEnum = Enum.IsDefined(typeof(Block.Action), action) - ? (Block.Action)action - : Block.Action.RIGHT_CLICK_AIR; - - var faceEnum = Enum.IsDefined(typeof(Block.BlockFace), blockFace) - ? (Block.BlockFace)blockFace - : Block.BlockFace.SELF; - - ItemStack? itemStack = null; - if (itemId > 0) - itemStack = new ItemStack(itemId, itemCount, (short)itemAux); - - Block.Block? clickedBlock = null; - bool hasBlock = actionEnum == Block.Action.LEFT_CLICK_BLOCK - || actionEnum == Block.Action.RIGHT_CLICK_BLOCK - || actionEnum == Block.Action.PHYSICAL; - if (hasBlock) - { - var world = FourKit.getWorld(dimId); - if (world != null) - clickedBlock = new Block.Block(world, clickedX, clickedY, clickedZ); - } - - var evt = new PlayerInteractEvent( - player, actionEnum, itemStack, clickedBlock, faceEnum); - FourKit.FireEvent(evt); - - Marshal.WriteInt32(outUseItemInHandPtr, evt.useItemInHand() ? 1 : 0); - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerInteract error: {ex}"); - Marshal.WriteInt32(outUseItemInHandPtr, 1); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int FirePlayerInteractEntity(int playerEntityId, - int targetEntityId, int targetEntityTypeId, - int dimId, double targetX, double targetY, double targetZ, - float targetHealth, float targetMaxHealth, float targetEyeHeight) - { - try - { - var player = FourKit.GetPlayerByEntityId(playerEntityId); - if (player == null) - return 0; - - SyncPlayerFromNative(player); - - Entity.Entity? target = FourKit.GetPlayerByEntityId(targetEntityId); - if (target is Player targetPlayer) - { - SyncPlayerFromNative(targetPlayer); - } - else - { - var entityType = Enum.IsDefined(typeof(EntityType), targetEntityTypeId) - ? (EntityType)targetEntityTypeId - : EntityType.UNKNOWN; - var living = new LivingEntity(targetEntityId, entityType, dimId, targetX, targetY, targetZ, - targetHealth, targetMaxHealth); - living.SetEyeHeightInternal(targetEyeHeight); - target = living; - } - - var evt = new PlayerInteractEntityEvent(player, target); - FourKit.FireEvent(evt); - - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerInteractEntity error: {ex}"); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int FirePlayerPickupItem(int playerEntityId, - int itemEntityId, int dimId, double itemX, double itemY, double itemZ, - int itemId, int itemCount, int itemAux, int remaining, - IntPtr outItemIdPtr, IntPtr outItemCountPtr, IntPtr outItemAuxPtr) - { - try - { - var player = FourKit.GetPlayerByEntityId(playerEntityId); - if (player == null) - { - Marshal.WriteInt32(outItemIdPtr, itemId); - Marshal.WriteInt32(outItemCountPtr, itemCount); - Marshal.WriteInt32(outItemAuxPtr, itemAux); - // todo: fix - return 1; - } - - SyncPlayerFromNative(player); - - var itemStack = new Inventory.ItemStack(itemId, itemCount, (short)itemAux); - var item = new Entity.Item(itemEntityId, dimId, itemX, itemY, itemZ, itemStack); - var evt = new PlayerPickupItemEvent(player, item, remaining); - FourKit.FireEvent(evt); - - var result = evt.getItem().getItemStack(); - Marshal.WriteInt32(outItemIdPtr, result.getTypeId()); - Marshal.WriteInt32(outItemCountPtr, result.getAmount()); - Marshal.WriteInt32(outItemAuxPtr, result.getDurability()); - - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerPickupItem error: {ex}"); - Marshal.WriteInt32(outItemIdPtr, itemId); - Marshal.WriteInt32(outItemCountPtr, itemCount); - Marshal.WriteInt32(outItemAuxPtr, itemAux); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int FireInventoryOpen(int entityId, int nativeContainerType, - IntPtr titlePtr, int titleByteLen, int containerSize) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) return 0; - - SyncPlayerFromNative(player); - - string title = titleByteLen > 0 - ? Marshal.PtrToStringUTF8(titlePtr, titleByteLen) ?? string.Empty - : string.Empty; - - InventoryType invType = MapNativeContainerType(nativeContainerType); - Inventory.Inventory topInv = CreateContainerInventory(invType, nativeContainerType, title, containerSize, entityId); - var bottomInv = player.getInventory(); - - var view = new InventoryView(topInv, bottomInv, player, invType); - var evt = new InventoryOpenEvent(view); - FourKit.FireEvent(evt); - - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FireInventoryOpen error: {ex}"); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int HandlePlayerCommand(int entityId, IntPtr cmdUtf8, int cmdByteLen) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) - return 0; - - SyncPlayerFromNative(player); - - string commandLine = cmdByteLen > 0 - ? Marshal.PtrToStringUTF8(cmdUtf8, cmdByteLen) ?? string.Empty - : string.Empty; - - if (string.IsNullOrEmpty(commandLine)) - return 0; - - return FourKit.DispatchCommand(player, commandLine) ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"HandlePlayerCommand error: {ex}"); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int HandleConsoleCommand(IntPtr cmdUtf8, int cmdByteLen) - { - try - { - string commandLine = cmdByteLen > 0 - ? Marshal.PtrToStringUTF8(cmdUtf8, cmdByteLen) ?? string.Empty - : string.Empty; - - if (string.IsNullOrEmpty(commandLine)) - return 0; - - string trimmed = commandLine.StartsWith('/') ? commandLine[1..] : commandLine; - string[] parts = trimmed.Split(' ', StringSplitOptions.RemoveEmptyEntries); - if (parts.Length == 0) - return 0; - - if (!FourKit.HasCommand(parts[0])) - return 0; - - FourKit.DispatchCommand(FourKit.getConsoleSender(), commandLine); - return 1; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"HandleConsoleCommand error: {ex}"); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int GetPluginCommandHelp(IntPtr outBuf, int outBufSize, IntPtr outLenPtr) - { - try - { - var entries = FourKit.GetRegisteredCommandHelp(); - if (entries.Count == 0) - { - if (outLenPtr != IntPtr.Zero) - Marshal.WriteInt32(outLenPtr, 0); - return 0; - } - - var sb = new System.Text.StringBuilder(); - foreach (var (usage, description) in entries) - { - sb.Append(usage); - sb.Append('\0'); - sb.Append(description); - sb.Append('\0'); - } - - byte[] utf8 = System.Text.Encoding.UTF8.GetBytes(sb.ToString()); - int copyLen = Math.Min(utf8.Length, outBufSize); - if (outBuf != IntPtr.Zero && copyLen > 0) - Marshal.Copy(utf8, 0, outBuf, copyLen); - if (outLenPtr != IntPtr.Zero) - Marshal.WriteInt32(outLenPtr, copyLen); - - return entries.Count; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"GetPluginCommandHelp error: {ex}"); - if (outLenPtr != IntPtr.Zero) - Marshal.WriteInt32(outLenPtr, 0); - return 0; - } - } - - private static InventoryType MapNativeContainerType(int nativeType) - { - return nativeType switch - { - 0 => InventoryType.CHEST, // CONTAINER - 1 => InventoryType.WORKBENCH, // WORKBENCH - 2 => InventoryType.FURNACE, // FURNACE - 3 => InventoryType.DISPENSER, // TRAP (dispenser) - 4 => InventoryType.ENCHANTING, // ENCHANTMENT - 5 => InventoryType.BREWING, // BREWING_STAND - 6 => InventoryType.MERCHANT, // TRADER_NPC - 7 => InventoryType.BEACON, // BEACON - 8 => InventoryType.ANVIL, // REPAIR_TABLE - 9 => InventoryType.HOPPER, // HOPPER - 10 => InventoryType.DROPPER, // DROPPER - 11 => InventoryType.CHEST, // HORSE - 12 => InventoryType.WORKBENCH, // FIREWORKS - 13 => InventoryType.CHEST, // BONUS_CHEST - 14 => InventoryType.CHEST, // LARGE_CHEST - 15 => InventoryType.ENDER_CHEST, // ENDER_CHEST - 16 => InventoryType.CHEST, // MINECART_CHEST - 17 => InventoryType.HOPPER, // MINECART_HOPPER - _ => InventoryType.CHEST, - }; - } - - private static Inventory.Inventory CreateContainerInventory(InventoryType invType, int nativeType, string title, int containerSize, int entityId) - { - string name = string.IsNullOrEmpty(title) ? invType.getDefaultTitle() : title; - int size = containerSize > 0 ? containerSize : invType.getDefaultSize(); - - return invType switch - { - InventoryType.FURNACE => new Inventory.FurnaceInventory(name, size, entityId), - InventoryType.BEACON => new Inventory.BeaconInventory(name, size, entityId), - InventoryType.ENCHANTING => new Inventory.EnchantingInventory(name, size, entityId), - _ => nativeType switch - { - 11 => new Inventory.HorseInventory(name, size, entityId), // HORSE - 14 => new Inventory.DoubleChestInventory(name, size, entityId), // LARGE_CHEST - _ => new Inventory.Inventory(name, invType, size, entityId), - } - }; + if (Guid.TryParse(s, out var g)) return g; + if (string.IsNullOrEmpty(s)) return Guid.NewGuid(); + return new Guid(System.Security.Cryptography.MD5.HashData(System.Text.Encoding.UTF8.GetBytes(s))); } // double[27] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension, isSleeping, sleepTimer, sneaking, sprinting, onGround, velocityX, velocityY, velocityZ, allowFlight, sleepingIgnored, experienceLevel, experienceProgress, totalExperience, foodLevel, saturation, exhaustion } - private static void SyncPlayerFromNative(Player player) + internal static void SyncPlayerFromNative(Player player) { if (NativeBridge.GetPlayerSnapshot == null) return; @@ -1045,7 +93,7 @@ public static class FourKitHost player.SetExhaustionInternal((float)buf[26]); } - private static void BroadcastNativeMessage(string message) + internal static void BroadcastNativeMessage(string message) { if (string.IsNullOrEmpty(message) || NativeBridge.BroadcastMessage == null) return; @@ -1060,9 +108,13 @@ public static class FourKitHost } } + private static void WriteSignOutLens(IntPtr ptr, int[] lens) + { + Marshal.Copy(lens, 0, ptr, 4); + } + private static string JavaFormat(string format, params string[] args) { - // hack to replicate java bukkit format var sb = new System.Text.StringBuilder(format.Length + 64); int seqIndex = 0; @@ -1117,196 +169,63 @@ public static class FourKitHost return sb.ToString(); } - [UnmanagedCallersOnly] - public static int FirePlayerTeleport(int entityId, - double fromX, double fromY, double fromZ, int fromDimId, - double toX, double toY, double toZ, int toDimId, - int cause, IntPtr outCoords) + + private static InventoryType MapNativeContainerType(int nativeType) { - try + return nativeType switch { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) - { - Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); - return 0; - } - - SyncPlayerFromNative(player); - - var fromWorld = FourKit.getWorld(fromDimId); - var toWorld = FourKit.getWorld(toDimId); - var from = new Location(fromWorld, fromX, fromY, fromZ); - var to = new Location(toWorld, toX, toY, toZ); - var teleportCause = cause >= 0 && cause <= (int)PlayerTeleportEvent.TeleportCause.UNKNOWN - ? (PlayerTeleportEvent.TeleportCause)cause - : PlayerTeleportEvent.TeleportCause.UNKNOWN; - - var evt = new PlayerTeleportEvent(player, from, to, teleportCause); - FourKit.FireEvent(evt); - - var finalTo = evt.getTo(); - Marshal.Copy(new double[] { finalTo.X, finalTo.Y, finalTo.Z }, 0, outCoords, 3); - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerTeleport error: {ex}"); - Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); - return 0; - } + 0 => InventoryType.CHEST, // CONTAINER + 1 => InventoryType.WORKBENCH, // WORKBENCH + 2 => InventoryType.FURNACE, // FURNACE + 3 => InventoryType.DISPENSER, // TRAP (dispenser) + 4 => InventoryType.ENCHANTING, // ENCHANTMENT + 5 => InventoryType.BREWING, // BREWING_STAND + 6 => InventoryType.MERCHANT, // TRADER_NPC + 7 => InventoryType.BEACON, // BEACON + 8 => InventoryType.ANVIL, // REPAIR_TABLE + 9 => InventoryType.HOPPER, // HOPPER + 10 => InventoryType.DROPPER, // DROPPER + 11 => InventoryType.CHEST, // HORSE + 12 => InventoryType.WORKBENCH, // FIREWORKS + 13 => InventoryType.CHEST, // BONUS_CHEST + 14 => InventoryType.CHEST, // LARGE_CHEST + 15 => InventoryType.ENDER_CHEST,// ENDER_CHEST + 16 => InventoryType.CHEST, // MINECART_CHEST + 17 => InventoryType.HOPPER, // MINECART_HOPPER + _ => InventoryType.CHEST, + }; } - [UnmanagedCallersOnly] - public static int FirePlayerPortal(int entityId, - double fromX, double fromY, double fromZ, int fromDimId, - double toX, double toY, double toZ, int toDimId, - int cause, IntPtr outCoords) + private static Inventory.Inventory CreateContainerInventory(InventoryType invType, int nativeType, string title, int containerSize, int entityId) { - try + string name = string.IsNullOrEmpty(title) ? invType.getDefaultTitle() : title; + int size = containerSize > 0 ? containerSize : invType.getDefaultSize(); + + return invType switch { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) + InventoryType.FURNACE => new Inventory.FurnaceInventory(name, size, entityId), + InventoryType.BEACON => new Inventory.BeaconInventory(name, size, entityId), + InventoryType.ENCHANTING => new Inventory.EnchantingInventory(name, size, entityId), + _ => nativeType switch { - Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); - return 0; + 11 => new Inventory.HorseInventory(name, size, entityId), // HORSE + 14 => new Inventory.DoubleChestInventory(name, size, entityId), // LARGE_CHEST + _ => new Inventory.Inventory(name, invType, size, entityId), } - - SyncPlayerFromNative(player); - - var fromWorld = FourKit.getWorld(fromDimId); - var toWorld = FourKit.getWorld(toDimId); - var from = new Location(fromWorld, fromX, fromY, fromZ); - var to = new Location(toWorld, toX, toY, toZ); - var teleportCause = cause >= 0 && cause <= (int)PlayerTeleportEvent.TeleportCause.UNKNOWN - ? (PlayerTeleportEvent.TeleportCause)cause - : PlayerTeleportEvent.TeleportCause.UNKNOWN; - - var evt = new PlayerPortalEvent(player, from, to, teleportCause); - FourKit.FireEvent(evt); - - var finalTo = evt.getTo(); - Marshal.Copy(new double[] { finalTo.X, finalTo.Y, finalTo.Z }, 0, outCoords, 3); - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FirePlayerPortal error: {ex}"); - Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int FireInventoryClick(int entityId, int slot, int button, int clickType, - int nativeContainerType, int containerSize, IntPtr titleUtf8Ptr, int titleByteLen) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) return 0; - - SyncPlayerFromNative(player); - - ClickType click = MapNativeClickType(clickType, button); - InventoryAction action = DetermineInventoryAction(click, slot); - - InventoryType invType; - Inventory.Inventory topInv; - if (nativeContainerType < 0) - { - invType = InventoryType.PLAYER; - topInv = player.getInventory(); - } - else - { - invType = MapNativeContainerType(nativeContainerType); - int size = containerSize > 0 ? containerSize : invType.getDefaultSize(); - string title = titleByteLen > 0 && titleUtf8Ptr != IntPtr.Zero - ? Marshal.PtrToStringUTF8(titleUtf8Ptr, titleByteLen) ?? invType.getDefaultTitle() - : invType.getDefaultTitle(); - topInv = CreateContainerInventory(invType, nativeContainerType, title, size, entityId); - } - - var bottomInv = player.getInventory(); - var view = new InventoryView(topInv, bottomInv, player, invType); - - SlotType slotType = SlotType.CONTAINER; - if (slot == InventoryView.OUTSIDE) - slotType = SlotType.OUTSIDE; - - Inventory.Inventory._slotModifiedByPlugin = false; - int hotbarKey = click == ClickType.NUMBER_KEY ? button : -1; - var evt = new InventoryClickEvent(view, slotType, slot, click, action, hotbarKey); - FourKit.FireEvent(evt); - - if (evt.isCancelled()) return 1; - if (Inventory.Inventory._slotModifiedByPlugin) return 2; - return 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FireInventoryClick error: {ex}"); - return 0; - } - } - - [UnmanagedCallersOnly] - public static int FireBedEnter(int entityId, int dimId, int bedX, int bedY, int bedZ) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) return 0; - - SyncPlayerFromNative(player); - - var world = FourKit.getWorld(dimId); - var bed = new Block.Block(world, bedX, bedY, bedZ); - var evt = new Event.Player.PlayerBedEnterEvent(player, bed); - FourKit.FireEvent(evt); - - return evt.isCancelled() ? 1 : 0; - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FireBedEnter error: {ex}"); - return 0; - } - } - - [UnmanagedCallersOnly] - public static void FireBedLeave(int entityId, int dimId, int bedX, int bedY, int bedZ) - { - try - { - var player = FourKit.GetPlayerByEntityId(entityId); - if (player == null) return; - - SyncPlayerFromNative(player); - - var world = FourKit.getWorld(dimId); - var bed = new Block.Block(world, bedX, bedY, bedZ); - var evt = new Event.Player.PlayerBedLeaveEvent(player, bed); - FourKit.FireEvent(evt); - } - catch (Exception ex) - { - ServerLog.Error("fourkit", $"FireBedLeave error: {ex}"); - } + }; } private static ClickType MapNativeClickType(int nativeClickType, int button) { return nativeClickType switch { - 0 => button == 0 ? ClickType.LEFT : ClickType.RIGHT, // CLICK - 1 => button == 0 ? ClickType.SHIFT_LEFT : ClickType.SHIFT_RIGHT, // SHIFT_CLICK - 2 => ClickType.NUMBER_KEY, // NUMBER_KEY - 3 => ClickType.MIDDLE, // MIDDLE_CLICK - 4 => button == 1 ? ClickType.CONTROL_DROP : ClickType.DROP, // DROP - 5 => ClickType.UNKNOWN, // DRAG (does not exist lol but maybe in the future?) - 6 => ClickType.DOUBLE_CLICK, // DOUBLE_CLICK + 0 => button == 0 ? ClickType.LEFT : ClickType.RIGHT, + 1 => button == 0 ? ClickType.SHIFT_LEFT : ClickType.SHIFT_RIGHT, + 2 => ClickType.NUMBER_KEY, + 3 => ClickType.MIDDLE, + 4 => button == 1 ? ClickType.CONTROL_DROP : ClickType.DROP, + 5 => ClickType.UNKNOWN, + 6 => ClickType.DOUBLE_CLICK, _ => ClickType.UNKNOWN, }; } diff --git a/Minecraft.Server/FourKitBridge.cpp b/Minecraft.Server/FourKitBridge.cpp index 02b68c52..fe15b058 100644 --- a/Minecraft.Server/FourKitBridge.cpp +++ b/Minecraft.Server/FourKitBridge.cpp @@ -1,111 +1,20 @@ -// todo: split into files for better readability -// todo: this needs to be made way more neat and less duplicate stuff - #include "FourKitBridge.h" +#include "FourKitNatives.h" +#include "FourKitRuntime.h" #include "Common/StringUtils.h" #include "ServerLogger.h" #include "stdafx.h" #include -#include +#include #include -#include "..\Minecraft.Client\MinecraftServer.h" -#include "..\Minecraft.Client\PlayerConnection.h" -#include "..\Minecraft.Client\PlayerList.h" -#include "..\Minecraft.Client\ServerConnection.h" -#include "..\Minecraft.Client\ServerLevel.h" -#include "..\Minecraft.Client\ServerPlayer.h" -#include "..\Minecraft.Client\ServerPlayerGameMode.h" -#include "..\Minecraft.Client\Windows64\Network\WinsockNetLayer.h" -#include "..\Minecraft.World\AbstractContainerMenu.h" -#include "..\Minecraft.World\AddGlobalEntityPacket.h" -#include "..\Minecraft.World\ArrayWithLength.h" -#include "..\Minecraft.World\Class.h" -#include "..\Minecraft.World\CompoundContainer.h" -#include "..\Minecraft.World\Connection.h" -#include "..\Minecraft.World\ContainerOpenPacket.h" -#include "..\Minecraft.World\DamageSource.h" -#include "..\Minecraft.World\EntityDamageSource.h" -#include "..\Minecraft.World\Explosion.h" -#include "..\Minecraft.World\IndirectEntityDamageSource.h" -#include "..\Minecraft.World\ItemEntity.h" -#include "..\Minecraft.World\ItemInstance.h" -#include "..\Minecraft.World\LevelData.h" -#include "..\Minecraft.World\LevelSettings.h" -#include "..\Minecraft.World\LightningBolt.h" -#include "..\Minecraft.World\Player.h" -#include "..\Minecraft.World\PlayerAbilitiesPacket.h" -#include "..\Minecraft.World\SetCarriedItemPacket.h" -#include "..\Minecraft.World\SetExperiencePacket.h" -#include "..\Minecraft.World\SetHealthPacket.h" -#include "..\Minecraft.World\LevelSoundPacket.h" -#include "..\Minecraft.World\LevelParticlesPacket.h" -#include "..\Minecraft.World\SetEntityLinkPacket.h" -#include "..\Minecraft.World\SimpleContainer.h" -#include "..\Minecraft.World\Slot.h" -#include "..\Minecraft.World\Tile.h" -#include "..\Minecraft.World\net.minecraft.world.entity.player.h" -#include "Access\Access.h" -#include "Common\NetworkUtils.h" -#include "ServerLogManager.h" - -// gay - -typedef void *hostfxr_handle; - -typedef int(__cdecl *hostfxr_initialize_for_runtime_config_fn)( - const wchar_t *runtime_config_path, - const void *parameters, - hostfxr_handle *host_context_handle); - -enum hostfxr_delegate_type -{ - hdt_com_activation = 0, - hdt_load_in_memory_assembly = 1, - hdt_winrt_activation = 2, - hdt_com_register = 3, - hdt_com_unregister = 4, - hdt_load_assembly_and_get_function_pointer = 5, - hdt_get_function_pointer = 6, -}; - -typedef int(__cdecl *hostfxr_get_runtime_delegate_fn)( - const hostfxr_handle host_context_handle, - hostfxr_delegate_type type, - void **delegate); - -typedef int(__cdecl *hostfxr_close_fn)(const hostfxr_handle host_context_handle); - -struct hostfxr_initialize_parameters -{ - size_t size; - const wchar_t *host_path; - const wchar_t *dotnet_root; -}; - -#define UNMANAGEDCALLERSONLY_METHOD ((const wchar_t *)-1) - -typedef int(__stdcall *load_assembly_and_get_function_pointer_fn)( - const wchar_t *assembly_path, - const wchar_t *type_name, - const wchar_t *method_name, - const wchar_t *delegate_type_name, - void *reserved, - void **delegate); - using ServerRuntime::LogDebugf; using ServerRuntime::LogError; using ServerRuntime::LogInfo; -using ServerRuntime::LogInfof; -using ServerRuntime::LogWarn; namespace FourKitBridge { -static hostfxr_initialize_for_runtime_config_fn s_initFn = nullptr; -static hostfxr_get_runtime_delegate_fn s_getDelegateFn = nullptr; -static hostfxr_close_fn s_closeFn = nullptr; -static std::wstring s_dotnetRoot; typedef void(__stdcall *fn_initialize)(); typedef void(__stdcall *fn_fire_player_join)(int entityId, const char *nameUtf8, int nameByteLen, const char *uuidUtf8, int uuidByteLen); @@ -172,10 +81,6 @@ typedef int(__stdcall *fn_fire_player_teleport)(int entityId, double fromX, double fromY, double fromZ, int fromDimId, double toX, double toY, double toZ, int toDimId, int cause, double *outCoords); -typedef int(__stdcall *fn_fire_player_portal)(int entityId, - double fromX, double fromY, double fromZ, int fromDimId, - double toX, double toY, double toZ, int toDimId, - int cause, double *outCoords); typedef int(__stdcall *fn_fire_inventory_click)(int entityId, int slot, int button, int clickType, int nativeContainerType, int containerSize, const char *titleUtf8, int titleByteLen); @@ -221,7 +126,7 @@ static fn_handle_player_command s_managedHandlePlayerCommand = nullptr; static fn_handle_console_command s_managedHandleConsoleCommand = nullptr; static fn_get_plugin_command_help s_managedGetPluginCommandHelp = nullptr; static fn_fire_player_teleport s_managedFirePlayerTeleport = nullptr; -static fn_fire_player_portal s_managedFirePlayerPortal = nullptr; +static fn_fire_player_teleport s_managedFirePlayerPortal = nullptr; static fn_fire_inventory_click s_managedFireInventoryClick = nullptr; static fn_fire_bed_enter s_managedFireBedEnter = nullptr; static fn_fire_bed_leave s_managedFireBedLeave = nullptr; @@ -232,1457 +137,10 @@ static fn_set_vehicle_callbacks s_managedSetVehicleCallbacks = nullptr; static bool s_initialized = false; -static shared_ptr FindPlayer(int entityId) -{ - PlayerList *list = MinecraftServer::getPlayerList(); - if (!list) - { - return nullptr; - } - - for (auto &p : list->players) - { - if (p && p->entityId == entityId) - { - return p; - } - } - return nullptr; -} - -static shared_ptr FindEntity(int entityId) -{ - MinecraftServer *srv = MinecraftServer::getInstance(); - if (!srv) - { - return nullptr; - } - - const int dims[] = {0, -1, 1}; - for (int dim : dims) - { - ServerLevel *level = srv->getLevel(dim); - if (!level) - { - continue; - } - shared_ptr entity = level->getEntity(entityId); - if (entity) - { - return entity; - } - } - return nullptr; -} - -static void __cdecl NativeDamagePlayer(int entityId, float amount) -{ - // todo: move entity hurt to a seperate func (NativeDamageEntity) - auto player = FindPlayer(entityId); - if (player) - { - player->hurt(DamageSource::genericSource, amount); - return; - } - auto entity = FindEntity(entityId); - if (entity) - { - entity->hurt(DamageSource::genericSource, amount); - } -} - -static void __cdecl NativeSetPlayerHealth(int entityId, float health) -{ - auto player = FindPlayer(entityId); - if (player) - { - player->setHealth(health); - } -} - -static void __cdecl NativeTeleportPlayer(int entityId, double x, double y, double z) -{ - auto player = FindPlayer(entityId); - if (player && player->connection) - { - double outX, outY, outZ; - bool cancelled = FirePlayerTeleport(entityId, - player->x, player->y, player->z, player->dimension, - x, y, z, player->dimension, - 2 /* PLUGIN */, - &outX, &outY, &outZ); - if (!cancelled) - { - player->connection->teleport(outX, outY, outZ, player->yRot, player->xRot); - } - } -} - -static void __cdecl NativeSetPlayerGameMode(int entityId, int gameMode) -{ - auto player = FindPlayer(entityId); - if (player && player->gameMode) - { - GameType *type = GameType::byId(gameMode); - if (type) - { - player->setGameMode(type); - } - } -} - -static void __cdecl NativeSetFallDistance(int entityId, float distance) -{ - auto player = FindPlayer(entityId); - if (player) - { - player->fallDistance = distance; - } -} - -// double[27] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension, isSleeping, sleepTimer, sneaking, sprinting, onGround, velocityX, velocityY, velocityZ, allowFlight, sleepingIgnored, experienceLevel, experienceProgress, totalExperience, foodLevel, saturation, exhaustion } -static void __cdecl NativeGetPlayerSnapshot(int entityId, double *outData) -{ - auto player = FindPlayer(entityId); - if (!player) - { - memset(outData, 0, 27 * sizeof(double)); - outData[3] = 20.0; - outData[4] = 20.0; - outData[7] = 0.1; - outData[24] = 20.0; - outData[25] = 5.0; - return; - } - outData[0] = player->x; - outData[1] = player->y; - outData[2] = player->z; - outData[3] = (double)player->getHealth(); - outData[4] = (double)player->getMaxHealth(); - outData[5] = (double)player->fallDistance; - GameType *gm = player->gameMode ? player->gameMode->getGameModeForPlayer() : GameType::SURVIVAL; - outData[6] = (double)(gm ? gm->getId() : 0); - outData[7] = (double)player->abilities.getWalkingSpeed(); - outData[8] = (double)player->yRot; - outData[9] = (double)player->xRot; - outData[10] = (double)player->dimension; - outData[11] = player->isSleeping() ? 1.0 : 0.0; - outData[12] = (double)player->getSleepTimer(); - outData[13] = player->isSneaking() ? 1.0 : 0.0; - outData[14] = player->isSprinting() ? 1.0 : 0.0; - outData[15] = player->onGround ? 1.0 : 0.0; - outData[16] = player->xd; - outData[17] = player->yd; - outData[18] = player->zd; - outData[19] = player->abilities.mayfly ? 1.0 : 0.0; - outData[20] = player->fk_sleepingIgnored ? 1.0 : 0.0; - outData[21] = (double)player->experienceLevel; - outData[22] = (double)player->experienceProgress; - outData[23] = (double)player->totalExperience; - FoodData *fd = player->getFoodData(); - outData[24] = fd ? (double)fd->getFoodLevel() : 20.0; - outData[25] = fd ? (double)fd->getSaturationLevel() : 5.0; - outData[26] = fd ? (double)fd->getExhaustionLevel() : 0.0; -} - -static void __cdecl NativeBroadcastMessage(const char *utf8, int len) -{ - if (!utf8 || len <= 0) - { - return; - } - - std::wstring wide = ServerRuntime::StringUtils::Utf8ToWide(utf8); - if (wide.empty()) - { - return; - } - - PlayerList *list = MinecraftServer::getPlayerList(); - if (list) - { - list->broadcastAll(std::make_shared(wide)); - } -} - -static void __cdecl NativeSendMessage(int entityId, const char *utf8, int len) -{ - if (!utf8 || len <= 0) - { - return; - } - - auto player = FindPlayer(entityId); - if (player && player->connection) - { - std::wstring wide = ServerRuntime::StringUtils::Utf8ToWide(utf8); - if (!wide.empty()) - { - player->connection->send(std::make_shared(wide)); - } - } -} - -static void __cdecl NativeSetWalkSpeed(int entityId, float speed) -{ - auto player = FindPlayer(entityId); - if (player) - { - player->abilities.setWalkingSpeed(speed); - if (player->connection) - { - player->connection->send(std::make_shared(&player->abilities)); - } - } -} - -static ServerLevel *GetLevel(int dimId) -{ - MinecraftServer *srv = MinecraftServer::getInstance(); - if (!srv) - { - return nullptr; - } - return srv->getLevel(dimId); -} - -static void __cdecl NativeTeleportEntity(int entityId, int dimId, double x, double y, double z) -{ - auto player = FindPlayer(entityId); - if (player && player->connection) - { - player->connection->teleport(x, y, z, player->yRot, player->xRot); - return; - } - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return; - } - shared_ptr entity = level->getEntity(entityId); - if (entity) - { - entity->moveTo(x, y, z, entity->yRot, entity->xRot); - } -} - -static int __cdecl NativeGetTileId(int dimId, int x, int y, int z) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return 0; - } - return level->getTile(x, y, z); -} - -static void __cdecl NativeSetTile(int dimId, int x, int y, int z, int tileId, int data) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return; - } - level->setTileAndData(x, y, z, tileId, data, Tile::UPDATE_ALL); -} - -static int __cdecl NativeGetTileData(int dimId, int x, int y, int z) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return 0; - } - return level->getData(x, y, z); -} - -static void __cdecl NativeSetTileData(int dimId, int x, int y, int z, int data) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return; - } - level->setData(x, y, z, data, Tile::UPDATE_ALL); -} - -static int __cdecl NativeBreakBlock(int dimId, int x, int y, int z) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return 0; - } - if (level->getTile(x, y, z) == 0) - { - return 0; - } - return level->destroyTile(x, y, z, true) ? 1 : 0; -} - -static int __cdecl NativeGetHighestBlockY(int dimId, int x, int z) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return 0; - } - return level->getHeightmap(x, z); -} - -// double[7] = { spawnX, spawnY, spawnZ, seed, dayTime, isRaining, isThundering } -static void __cdecl NativeGetWorldInfo(int dimId, double *outBuf) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - memset(outBuf, 0, 7 * sizeof(double)); - return; - } - LevelData *ld = level->getLevelData(); - Pos *spawn = level->getSharedSpawnPos(); - outBuf[0] = spawn ? (double)spawn->x : 0.0; - outBuf[1] = spawn ? (double)spawn->y : 64.0; - outBuf[2] = spawn ? (double)spawn->z : 0.0; - outBuf[3] = (double)level->getSeed(); - outBuf[4] = (double)level->getDayTime(); - outBuf[5] = ld && ld->isRaining() ? 1.0 : 0.0; - outBuf[6] = ld && ld->isThundering() ? 1.0 : 0.0; -} - -static void __cdecl NativeSetWorldTime(int dimId, int64_t time) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return; - } - level->setDayTime(time); -} - -// note 2 sefl: pass -1 to leave a parameter unchanged -static void __cdecl NativeSetWeather(int dimId, int storm, int thundering, int thunderDuration) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return; - } - LevelData *ld = level->getLevelData(); - if (!ld) - { - return; - } - if (storm >= 0) - { - ld->setRaining(storm != 0); - } - if (thundering >= 0) - { - ld->setThundering(thundering != 0); - } - if (thunderDuration >= 0) - { - ld->setThunderTime(thunderDuration); - } -} - -static int __cdecl NativeCreateExplosion(int dimId, double x, double y, double z, float power, int setFire, int breakBlocks) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return 0; - } - Explosion explosion(level, nullptr, x, y, z, power); - explosion.fire = (setFire != 0); - explosion.destroyBlocks = (breakBlocks != 0); - explosion.explode(); - explosion.finalizeExplosion(true); - return 1; -} - -static int __cdecl NativeStrikeLightning(int dimId, double x, double y, double z, int effectOnly) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return 0; - } - - std::shared_ptr lightning = std::shared_ptr(new LightningBolt(level, x, y, z)); - - if (effectOnly != 0) - { - PlayerList *playerList = MinecraftServer::getPlayerList(); - if (playerList == NULL) - { - return 0; - } - - playerList->broadcast(x, y, z, 512.0, dimId, std::shared_ptr(new AddGlobalEntityPacket(lightning))); - level->playSound(x, y, z, eSoundType_AMBIENT_WEATHER_THUNDER, 10000, 0.8f + level->random->nextFloat() * 0.2f); - level->playSound(x, y, z, eSoundType_RANDOM_EXPLODE, 2, 0.5f + level->random->nextFloat() * 0.2f); - return 1; - } - - return level->addGlobalEntity(lightning) ? 1 : 0; -} - -static int __cdecl NativeSetSpawnLocation(int dimId, int x, int y, int z) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return 0; - } - level->setSpawnPos(x, y, z); - return 1; -} - -static void __cdecl NativeDropItem(int dimId, double x, double y, double z, int itemId, int count, int auxValue, int naturally) -{ - ServerLevel *level = GetLevel(dimId); - if (!level) - { - return; - } - if (itemId <= 0 || count <= 0) - { - return; - } - - auto itemInstance = std::make_shared(itemId, count, auxValue); - double spawnX = x, spawnY = y, spawnZ = z; - if (naturally) - { - float s = 0.7f; - spawnX += level->random->nextFloat() * s + (1 - s) * 0.5; - spawnY += level->random->nextFloat() * s + (1 - s) * 0.5; - spawnZ += level->random->nextFloat() * s + (1 - s) * 0.5; - } - - auto item = std::make_shared(level, spawnX, spawnY, spawnZ, itemInstance); - item->throwTime = 10; - level->addEntity(item); -} - -static void __cdecl NativeKickPlayer(int entityId, int reason) -{ - auto player = FindPlayer(entityId); - if (player && player->connection) - { - DisconnectPacket::eDisconnectReason r = static_cast(reason); - player->connection->disconnect(r); - } -} - -static int __cdecl NativeBanPlayer(int entityId, const char *reasonUtf8, int reasonByteLen) -{ - if (!ServerRuntime::Access::IsInitialized()) - { - return 0; - } - - auto player = FindPlayer(entityId); - if (!player) - { - return 0; - } - - std::vector xuids; - PlayerUID xuid1 = player->getXuid(); - PlayerUID xuid2 = player->getOnlineXuid(); - if (xuid1 != INVALID_XUID) - { - xuids.push_back(xuid1); - } - if (xuid2 != INVALID_XUID && xuid2 != xuid1) - { - xuids.push_back(xuid2); - } - - if (xuids.empty()) - { - return 0; - } - - std::string reason = (reasonUtf8 && reasonByteLen > 0) ? std::string(reasonUtf8, reasonByteLen) : "Banned by plugin."; - std::string playerName = ServerRuntime::StringUtils::WideToUtf8(player->getName()); - - ServerRuntime::Access::BanMetadata metadata = ServerRuntime::Access::BanManager::BuildDefaultMetadata("Plugin"); - metadata.reason = reason; - - for (auto xuid : xuids) - { - if (!ServerRuntime::Access::IsPlayerBanned(xuid)) - { - ServerRuntime::Access::AddPlayerBan(xuid, playerName, metadata); - } - } - - if (player->connection) - { - player->connection->disconnect(DisconnectPacket::eDisconnect_Banned); - } - - return 1; -} - -static int __cdecl NativeBanPlayerIp(int entityId, const char *reasonUtf8, int reasonByteLen) -{ - if (!ServerRuntime::Access::IsInitialized()) - { - return 0; - } - - auto player = FindPlayer(entityId); - if (!player || !player->connection || !player->connection->connection || !player->connection->connection->getSocket()) - { - return 0; - } - - unsigned char smallId = player->connection->connection->getSocket()->getSmallId(); - if (smallId == 0) - { - return 0; - } - - std::string playerIp; - if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, &playerIp)) - { - return 0; - } - - std::string reason = (reasonUtf8 && reasonByteLen > 0) ? std::string(reasonUtf8, reasonByteLen) : "Banned by plugin."; - - ServerRuntime::Access::BanMetadata metadata = ServerRuntime::Access::BanManager::BuildDefaultMetadata("Plugin"); - metadata.reason = reason; - - std::string normalizedIp = ServerRuntime::NetworkUtils::NormalizeIpToken(playerIp); - if (ServerRuntime::Access::IsIpBanned(normalizedIp)) - { - return 0; - } - - if (!ServerRuntime::Access::AddIpBan(normalizedIp, metadata)) - { - return 0; - } - - PlayerList *list = MinecraftServer::getPlayerList(); - if (list) - { - std::vector> snapshot = list->players; - for (auto &p : snapshot) - { - if (!p || !p->connection || !p->connection->connection || !p->connection->connection->getSocket()) - { - continue; - } - unsigned char sid = p->connection->connection->getSocket()->getSmallId(); - if (sid == 0) - { - continue; - } - std::string pIp; - if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(sid, &pIp)) - { - continue; - } - if (ServerRuntime::NetworkUtils::NormalizeIpToken(pIp) == normalizedIp) - { - if (p->connection) - { - p->connection->disconnect(DisconnectPacket::eDisconnect_Banned); - } - } - } - } - - return 1; -} - -static int __cdecl NativeGetPlayerAddress(int entityId, char *outIpBuf, int outIpBufSize, int *outPort) -{ - if (outPort) - { - *outPort = 0; - } - if (outIpBuf && outIpBufSize > 0) - { - outIpBuf[0] = '\0'; - } - - auto player = FindPlayer(entityId); - if (!player || !player->connection || !player->connection->connection || !player->connection->connection->getSocket()) - { - return 0; - } - - unsigned char smallId = player->connection->connection->getSocket()->getSmallId(); - if (smallId == 0) - { - return 0; - } - - std::string playerIp; - if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, &playerIp)) - { - SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId); - if (sock != INVALID_SOCKET) - { - sockaddr_in addr; - int addrLen = sizeof(addr); - if (getpeername(sock, (sockaddr *)&addr, &addrLen) == 0) - { - char ipBuf[64] = {}; - if (inet_ntop(AF_INET, &addr.sin_addr, ipBuf, sizeof(ipBuf))) - { - playerIp = ipBuf; - if (outPort) - { - *outPort = (int)ntohs(addr.sin_port); - } - } - } - } - if (playerIp.empty()) - { - return 0; - } - } - else - { - SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId); - if (sock != INVALID_SOCKET && outPort) - { - sockaddr_in addr; - int addrLen = sizeof(addr); - if (getpeername(sock, (sockaddr *)&addr, &addrLen) == 0) - { - *outPort = (int)ntohs(addr.sin_port); - } - } - } - - if (outIpBuf && outIpBufSize > 0) - { - int copyLen = (int)playerIp.size(); - if (copyLen >= outIpBufSize) - { - copyLen = outIpBufSize - 1; - } - memcpy(outIpBuf, playerIp.c_str(), copyLen); - outIpBuf[copyLen] = '\0'; - } - - return 1; -} - -// outData: 40 slots * 3 ints (id, count, aux) + 1 int (selected slot) = 121 ints -static void __cdecl NativeGetPlayerInventory(int entityId, int *outData) -{ - memset(outData, 0, 121 * sizeof(int)); - - auto player = FindPlayer(entityId); - if (!player || !player->inventory) - { - return; - } - - unsigned int size = player->inventory->getContainerSize(); - if (size > 40) - { - size = 40; - } - - for (unsigned int i = 0; i < size; i++) - { - auto item = player->inventory->getItem(i); - if (item) - { - outData[i * 3] = item->id; - outData[i * 3 + 1] = item->count; - outData[i * 3 + 2] = item->getAuxValue(); - } - } - - outData[120] = player->inventory->selected; -} - -static void __cdecl NativeSetPlayerInventorySlot(int entityId, int slot, int itemId, int count, int aux) -{ - auto player = FindPlayer(entityId); - if (!player || !player->inventory) - { - return; - } - - if (itemId <= 0 || count <= 0) - { - player->inventory->setItem(slot, nullptr); - } - else - { - auto item = std::make_shared(itemId, count, aux); - player->inventory->setItem(slot, item); - } -} - -static void __cdecl NativeGetContainerContents(int entityId, int *outData, int maxSlots) -{ - memset(outData, 0, maxSlots * 3 * sizeof(int)); - - auto player = FindPlayer(entityId); - if (!player || !player->containerMenu || player->containerMenu == player->inventoryMenu) - { - return; - } - - auto *menu = player->containerMenu; - auto *items = menu->getItems(); - int count = (int)items->size(); - if (count > maxSlots) - { - count = maxSlots; - } - - for (int i = 0; i < count; i++) - { - auto &item = (*items)[i]; - if (item) - { - outData[i * 3] = item->id; - outData[i * 3 + 1] = item->count; - outData[i * 3 + 2] = item->getAuxValue(); - } - } - delete items; -} - -static void __cdecl NativeSetContainerSlot(int entityId, int slot, int itemId, int count, int aux) -{ - auto player = FindPlayer(entityId); - if (!player || !player->containerMenu || player->containerMenu == player->inventoryMenu) - { - return; - } - - auto *menu = player->containerMenu; - if (slot < 0 || slot >= (int)menu->slots.size()) - { - return; - } - - if (itemId <= 0 || count <= 0) - { - menu->setItem(slot, nullptr); - } - else - { - menu->setItem(slot, std::make_shared(itemId, count, aux)); - } - - menu->broadcastChanges(); -} - -static void __cdecl NativeCloseContainer(int entityId) -{ - auto player = FindPlayer(entityId); - if (player) - { - player->doCloseContainer(); - } -} - -class VirtualContainer : public SimpleContainer -{ - int m_containerType; - - public: - VirtualContainer(int containerType, const std::wstring &name, int size) - : SimpleContainer(0, name, !name.empty(), size), m_containerType(containerType) - { - } - virtual int getContainerType() override - { - return m_containerType; - } -}; - -static void __cdecl NativeOpenVirtualContainer(int entityId, int nativeType, const char *titleUtf8, int titleByteLen, int slotCount, int *itemsBuf) -{ - auto player = FindPlayer(entityId); - if (!player) - { - return; - } - - if (player->containerMenu != player->inventoryMenu) - { - player->doCloseContainer(); - } - - std::wstring title = ServerRuntime::StringUtils::Utf8ToWide(std::string(titleUtf8, titleByteLen)); - - auto container = std::make_shared(nativeType, title, slotCount); - - for (int i = 0; i < slotCount; i++) - { - int id = itemsBuf[i * 3]; - int count = itemsBuf[i * 3 + 1]; - int aux = itemsBuf[i * 3 + 2]; - if (id > 0 && count > 0) - { - container->setItem(i, std::make_shared(id, count, aux)); - } - } - - player->openContainer(container); -} - -static void __cdecl NativeGetContainerViewerEntityIds(int entityId, int *outIds, int maxCount, int *outCount) -{ - *outCount = 0; - - auto player = FindPlayer(entityId); - if (!player || !player->containerMenu || player->containerMenu == player->inventoryMenu) - { - return; - } - - auto *menu = player->containerMenu; - if (menu->slots.empty()) - { - return; - } - - Container *myContainer = menu->slots[0]->container.get(); - if (!myContainer) - { - return; - } - - CompoundContainer *myCompound = dynamic_cast(myContainer); - if (myCompound) - { - myContainer = myCompound->getFirstContainer().get(); - } - - PlayerList *list = MinecraftServer::getPlayerList(); - if (!list) - { - return; - } - - int count = 0; - for (auto &p : list->players) - { - if (!p || !p->containerMenu || p->containerMenu == p->inventoryMenu) - { - continue; - } - if (p->containerMenu->slots.empty()) - { - continue; - } - Container *theirContainer = p->containerMenu->slots[0]->container.get(); - CompoundContainer *theirCompound = dynamic_cast(theirContainer); - if (theirCompound) - { - theirContainer = theirCompound->getFirstContainer().get(); - } - if (theirContainer == myContainer && count < maxCount) - { - outIds[count++] = p->entityId; - } - } - *outCount = count; -} - -// [nameLen:int32][nameUTF8:bytes][loreCount:int32][lore0Len:int32][lore0UTF8:bytes] -// todo: des muass i no a bissl überarwatn -static int __cdecl NativeGetItemMeta(int entityId, int slot, char *outBuf, int bufSize) -{ - auto player = FindPlayer(entityId); - if (!player || !player->inventory) - { - return 0; - } - - unsigned int size = player->inventory->getContainerSize(); - if (slot < 0 || slot >= (int)size) - { - return 0; - } - - auto item = player->inventory->getItem(slot); - if (!item || !item->hasTag()) - { - return 0; - } - - CompoundTag *tag = item->getTag(); - if (!tag || !tag->contains(L"display")) - { - return 0; - } - - CompoundTag *display = tag->getCompound(L"display"); - bool hasName = display->contains(L"Name"); - bool hasLore = display->contains(L"Lore"); - - if (!hasName && !hasLore) - { - return 0; - } - - int offset = 0; - - if (hasName) - { - std::wstring wname = display->getString(L"Name"); - std::string nameUtf8 = ServerRuntime::StringUtils::WideToUtf8(wname); - int nameLen = (int)nameUtf8.size(); - if (offset + 4 + nameLen > bufSize) return 0; - memcpy(outBuf + offset, &nameLen, 4); - offset += 4; - memcpy(outBuf + offset, nameUtf8.data(), nameLen); - offset += nameLen; - } - else - { - int zero = 0; - if (offset + 4 > bufSize) return 0; - memcpy(outBuf + offset, &zero, 4); - offset += 4; - } - - if (hasLore) - { - ListTag *lore = (ListTag *)display->getList(L"Lore"); - int loreCount = lore->size(); - if (offset + 4 > bufSize) return 0; - memcpy(outBuf + offset, &loreCount, 4); - offset += 4; - - for (int i = 0; i < loreCount; i++) - { - std::wstring wline = lore->get(i)->data; - std::string lineUtf8 = ServerRuntime::StringUtils::WideToUtf8(wline); - int lineLen = (int)lineUtf8.size(); - if (offset + 4 + lineLen > bufSize) return 0; - memcpy(outBuf + offset, &lineLen, 4); - offset += 4; - memcpy(outBuf + offset, lineUtf8.data(), lineLen); - offset += lineLen; - } - } - else - { - int zero = 0; - if (offset + 4 > bufSize) return 0; - memcpy(outBuf + offset, &zero, 4); - offset += 4; - } - - return offset; -} - -static void __cdecl NativeSetItemMeta(int entityId, int slot, const char *inBuf, int bufSize) -{ - auto player = FindPlayer(entityId); - if (!player || !player->inventory) - { - return; - } - - unsigned int size = player->inventory->getContainerSize(); - if (slot < 0 || slot >= (int)size) - { - return; - } - - auto item = player->inventory->getItem(slot); - if (!item) - { - return; - } - - if (inBuf == nullptr || bufSize <= 0) - { - item->resetHoverName(); - if (item->hasTag()) - { - CompoundTag *tag = item->getTag(); - if (tag && tag->contains(L"display")) - { - CompoundTag *display = tag->getCompound(L"display"); - display->remove(L"Lore"); - if (display->isEmpty()) - { - tag->remove(L"display"); - if (tag->isEmpty()) - { - item->setTag(nullptr); - } - } - } - } - return; - } - - int offset = 0; - - if (offset + 4 > bufSize) return; - int nameLen = 0; - memcpy(&nameLen, inBuf + offset, 4); - offset += 4; - - if (nameLen > 0) - { - if (offset + nameLen > bufSize) return; - std::string nameUtf8(inBuf + offset, nameLen); - offset += nameLen; - std::wstring wname = ServerRuntime::StringUtils::Utf8ToWide(nameUtf8); - item->setHoverName(wname); - } - else - { - item->resetHoverName(); - } - - if (offset + 4 > bufSize) return; - int loreCount = 0; - memcpy(&loreCount, inBuf + offset, 4); - offset += 4; - - if (loreCount > 0) - { - if (!item->hasTag()) item->setTag(new CompoundTag()); - CompoundTag *tag = item->getTag(); - if (!tag->contains(L"display")) tag->putCompound(L"display", new CompoundTag()); - CompoundTag *display = tag->getCompound(L"display"); - - auto *loreList = new ListTag(L"Lore"); - for (int i = 0; i < loreCount; i++) - { - if (offset + 4 > bufSize) break; - int lineLen = 0; - memcpy(&lineLen, inBuf + offset, 4); - offset += 4; - - std::wstring wline; - if (lineLen > 0 && offset + lineLen <= bufSize) - { - std::string lineUtf8(inBuf + offset, lineLen); - offset += lineLen; - wline = ServerRuntime::StringUtils::Utf8ToWide(lineUtf8); - } - loreList->add(new StringTag(L"", wline)); - } - display->put(L"Lore", loreList); - } - else - { - if (item->hasTag()) - { - CompoundTag *tag = item->getTag(); - if (tag && tag->contains(L"display")) - { - tag->getCompound(L"display")->remove(L"Lore"); - } - } - } -} - -static void __cdecl NativeSetHeldItemSlot(int entityId, int slot) -{ - auto player = FindPlayer(entityId); - if (!player || !player->inventory) return; - if (slot < 0 || slot >= Inventory::getSelectionSize()) return; - player->inventory->selected = slot; - if (player->connection) - player->connection->queueSend(std::make_shared(slot)); -} - -static void __cdecl NativeSetSneaking(int entityId, int sneak) -{ - auto player = FindPlayer(entityId); - if (player) - { - player->setSneaking(sneak != 0); - } -} - -static void __cdecl NativeSetVelocity(int entityId, double x, double y, double z) -{ - auto player = FindPlayer(entityId); - if (player) - { - player->xd = x; - player->yd = y; - player->zd = z; - player->hurtMarked = true; - return; - } - auto entity = FindEntity(entityId); - if (entity) - { - entity->xd = x; - entity->yd = y; - entity->zd = z; - entity->hurtMarked = true; - } -} - -static void __cdecl NativeSetAllowFlight(int entityId, int allowFlight) -{ - auto player = FindPlayer(entityId); - if (player) - { - player->abilities.mayfly = (allowFlight != 0); - if (!player->abilities.mayfly) - { - player->abilities.flying = false; - } - if (player->connection) - { - player->connection->send(std::make_shared(&player->abilities)); - } - } -} - -static void __cdecl NativePlaySound(int entityId, int soundId, double x, double y, double z, float volume, float pitch) -{ - auto player = FindPlayer(entityId); - if (player && player->connection) - { - player->connection->send(std::make_shared(soundId, x, y, z, volume, pitch)); - } -} - -static void __cdecl NativeSetSleepingIgnored(int entityId, int ignored) -{ - auto player = FindPlayer(entityId); - if (player) - { - player->fk_sleepingIgnored = (ignored != 0); - } -} - -static void __cdecl NativeSetLevel(int entityId, int level) -{ - auto player = FindPlayer(entityId); - if (!player) return; - player->experienceLevel = level; - if (player->connection) - player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel)); -} - -static void __cdecl NativeSetExp(int entityId, float exp) -{ - auto player = FindPlayer(entityId); - if (!player) return; - player->experienceProgress = exp; - if (player->connection) - player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel)); -} - -static void __cdecl NativeGiveExp(int entityId, int amount) -{ - auto player = FindPlayer(entityId); - if (!player) return; - player->increaseXp(amount); - if (player->connection) - player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel)); -} - -static void __cdecl NativeGiveExpLevels(int entityId, int amount) -{ - auto player = FindPlayer(entityId); - if (!player) return; - player->giveExperienceLevels(amount); - if (player->connection) - player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel)); -} - -static void __cdecl NativeSetFoodLevel(int entityId, int foodLevel) -{ - auto player = FindPlayer(entityId); - if (!player) return; - FoodData *fd = player->getFoodData(); - if (!fd) return; - fd->setFoodLevel(foodLevel); - if (player->connection) - player->connection->send(std::make_shared(player->getHealth(), fd->getFoodLevel(), fd->getSaturationLevel(), eTelemetryChallenges_Unknown)); -} - -static void __cdecl NativeSetSaturation(int entityId, float saturation) -{ - auto player = FindPlayer(entityId); - if (!player) return; - FoodData *fd = player->getFoodData(); - if (!fd) return; - fd->setSaturation(saturation); - if (player->connection) - player->connection->send(std::make_shared(player->getHealth(), fd->getFoodLevel(), fd->getSaturationLevel(), eTelemetryChallenges_Unknown)); -} - -static void __cdecl NativeSetExhaustion(int entityId, float exhaustion) -{ - auto player = FindPlayer(entityId); - if (!player) return; - FoodData *fd = player->getFoodData(); - if (!fd) return; - fd->setExhaustion(exhaustion); -} - -static void __cdecl NativeSpawnParticle(int entityId, int particleId, float x, float y, float z, float offsetX, float offsetY, float offsetZ, float speed, int count) -{ - // todo(SYLV): i glaub des geht a gscheider - auto player = FindPlayer(entityId); - if (!player || !player->connection) return; - wchar_t buf[32]; - swprintf_s(buf, L"%d", particleId); - player->connection->send(std::make_shared(std::wstring(buf), x, y, z, offsetX, offsetY, offsetZ, speed, count)); -} - -static int __cdecl NativeSetPassenger(int entityId, int passengerEntityId) -{ - auto entity = FindEntity(entityId); - auto passenger = FindEntity(passengerEntityId); - if (!entity || !passenger) return 0; - passenger->ride(entity); - PlayerList *list = MinecraftServer::getPlayerList(); - if (list) - list->broadcastAll(std::make_shared(SetEntityLinkPacket::RIDING, passenger, entity), entity->dimension); - return 1; -} - -static int __cdecl NativeLeaveVehicle(int entityId) -{ - auto entity = FindEntity(entityId); - if (!entity || !entity->riding) return 0; - int dim = entity->riding->dimension; - entity->ride(nullptr); - PlayerList *list = MinecraftServer::getPlayerList(); - if (list) - list->broadcastAll(std::make_shared(SetEntityLinkPacket::RIDING, entity, nullptr), dim); - return 1; -} - -static int __cdecl NativeEject(int entityId) -{ - auto entity = FindEntity(entityId); - if (!entity) return 0; - auto riderPtr = entity->rider.lock(); - if (!riderPtr) return 0; - riderPtr->ride(nullptr); - PlayerList *list = MinecraftServer::getPlayerList(); - if (list) - list->broadcastAll(std::make_shared(SetEntityLinkPacket::RIDING, riderPtr, nullptr), entity->dimension); - return 1; -} - -static int __cdecl NativeGetVehicleId(int entityId) -{ - auto entity = FindEntity(entityId); - if (!entity || !entity->riding) return -1; - return entity->riding->entityId; -} - -static int __cdecl NativeGetPassengerId(int entityId) -{ - auto entity = FindEntity(entityId); - if (!entity) return -1; - auto riderPtr = entity->rider.lock(); - if (!riderPtr) return -1; - return riderPtr->entityId; -} - -static void __cdecl NativeGetEntityInfo(int entityId, double *outData) -{ - // this stinks iof shit and more shit - outData[0] = -1; - outData[1] = 0; - outData[2] = 0; - outData[3] = 0; - outData[4] = 0; - auto entity = FindEntity(entityId); - if (!entity) return; - outData[0] = (double)MapEntityType((int)entity->GetType()); - outData[1] = entity->x; - outData[2] = entity->y; - outData[3] = entity->z; - outData[4] = (double)entity->dimension; -} - -static std::wstring FindNet10SystemRoot() -{ - // overengineered - // trying to do a lot of safeguards here, setups can be all over the place - // fixes an issue aiden had - - std::vector candidates; - wchar_t envRoot[MAX_PATH] = {}; - DWORD len = GetEnvironmentVariableW(L"DOTNET_ROOT", envRoot, MAX_PATH); - if (len > 0 && len < MAX_PATH) - { - candidates.push_back(std::wstring(envRoot)); - } - candidates.push_back(L"C:\\Program Files\\dotnet"); - - for (const auto &root : candidates) - { - std::wstring fxrDir = root + L"\\host\\fxr"; - WIN32_FIND_DATAW fd; - HANDLE h = FindFirstFileW((fxrDir + L"\\*").c_str(), &fd); - if (h == INVALID_HANDLE_VALUE) - { - continue; - } - bool has10 = false; - do - { - if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && fd.cFileName[0] != L'.') - { - if (std::wstring(fd.cFileName).substr(0, 3) == L"10.") - { - has10 = true; - } - } - } while (!has10 && FindNextFileW(h, &fd)); - FindClose(h); - if (has10) - { - return root; - } - } - - return L"C:\\Program Files\\dotnet"; -} - -static bool TryLoadHostfxrFromPath(const std::wstring &path) -{ - HMODULE lib = LoadLibraryW(path.c_str()); - if (!lib) - { - return false; - } - - s_initFn = (hostfxr_initialize_for_runtime_config_fn)GetProcAddress(lib, "hostfxr_initialize_for_runtime_config"); - s_getDelegateFn = (hostfxr_get_runtime_delegate_fn)GetProcAddress(lib, "hostfxr_get_runtime_delegate"); - s_closeFn = (hostfxr_close_fn)GetProcAddress(lib, "hostfxr_close"); - - if (s_initFn && s_getDelegateFn && s_closeFn) - { - return true; - } - - s_initFn = nullptr; - s_getDelegateFn = nullptr; - s_closeFn = nullptr; - FreeLibrary(lib); - return false; -} - -static bool LoadHostfxr() -{ - // hardcoded for windows - // no linux support yet so no need - - wchar_t exePath[MAX_PATH] = {}; - GetModuleFileNameW(NULL, exePath, MAX_PATH); - std::wstring exeDir(exePath); - size_t lastSlash = exeDir.find_last_of(L"\\/"); - if (lastSlash != std::wstring::npos) - { - exeDir = exeDir.substr(0, lastSlash); - } - - if (TryLoadHostfxrFromPath(exeDir + L"\\hostfxr.dll")) - { - s_dotnetRoot = FindNet10SystemRoot(); - return true; - } - - wchar_t dotnetRoot[MAX_PATH] = {}; - DWORD len = GetEnvironmentVariableW(L"DOTNET_ROOT", dotnetRoot, MAX_PATH); // sometimes this is set in windows - if (len == 0 || len >= MAX_PATH) - { - wcscpy_s(dotnetRoot, L"C:\\Program Files\\dotnet"); - } - - std::wstring hostfxrDir = std::wstring(dotnetRoot) + L"\\host\\fxr"; - - WIN32_FIND_DATAW fd; - HANDLE hFind = FindFirstFileW((hostfxrDir + L"\\*").c_str(), &fd); - if (hFind != INVALID_HANDLE_VALUE) - { - std::wstring bestVersion; - do - { - if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && fd.cFileName[0] != L'.') - { - std::wstring ver(fd.cFileName); - if (ver.substr(0, 3) == L"10." && ver > bestVersion) - { - bestVersion = ver; - } - } - } while (FindNextFileW(hFind, &fd)); - FindClose(hFind); - - if (!bestVersion.empty()) - { - if (TryLoadHostfxrFromPath(hostfxrDir + L"\\" + bestVersion + L"\\hostfxr.dll")) - { - s_dotnetRoot = std::wstring(dotnetRoot); - return true; - } - } - } - - LogError("fourkit", "hostfxr.dll not found. Install the .NET 10 x64 runtime (https://aka.ms/dotnet/download) or copy hostfxr.dll from C:\\Program Files\\dotnet\\host\\fxr\\10.x.x\\ next to the server executable."); - return false; -} - -static bool GetManagedEntryPoint( - load_assembly_and_get_function_pointer_fn loadAssembly, - const wchar_t *assemblyPath, - const wchar_t *typeName, - const wchar_t *methodName, - void **outFnPtr) -{ - int rc = loadAssembly( - assemblyPath, - typeName, - methodName, - UNMANAGEDCALLERSONLY_METHOD, - nullptr, - outFnPtr); - - if (rc != 0 || *outFnPtr == nullptr) - { - char methodNarrow[256]; - sprintf_s(methodNarrow, "%S::%S", typeName, methodName); - LogError("fourkit", (std::string("Failed to resolve managed entry point: ") + methodNarrow).c_str()); - return false; - } - return true; -} - void Initialize() { LogInfo("fourkit", "FourKit initializing..."); - if (!LoadHostfxr()) - { - return; - } - wchar_t exePath[MAX_PATH]; GetModuleFileNameW(NULL, exePath, MAX_PATH); @@ -1696,75 +154,58 @@ void Initialize() std::wstring runtimeConfigPath = exeDir + L"\\Minecraft.Server.FourKit.runtimeconfig.json"; std::wstring assemblyPath = exeDir + L"\\Minecraft.Server.FourKit.dll"; - hostfxr_initialize_parameters initParams = {}; - initParams.size = sizeof(hostfxr_initialize_parameters); - initParams.host_path = exePath; - initParams.dotnet_root = s_dotnetRoot.c_str(); - - hostfxr_handle ctx = nullptr; - int rc = s_initFn(runtimeConfigPath.c_str(), &initParams, &ctx); - if (rc != 0 || ctx == nullptr) + load_assembly_fn loadAssembly = nullptr; + if (!LoadManagedRuntime(runtimeConfigPath.c_str(), exePath, &loadAssembly)) { - char msg[256]; - sprintf_s(msg, "hostfxr_initialize_for_runtime_config failed (0x%08X). Check runtimeconfig.json path.", rc); - LogError("fourkit", msg); - if (ctx) - { - s_closeFn(ctx); - } - return; - } - - load_assembly_and_get_function_pointer_fn loadAssembly = nullptr; - rc = s_getDelegateFn(ctx, hdt_load_assembly_and_get_function_pointer, (void **)&loadAssembly); - s_closeFn(ctx); - - if (rc != 0 || loadAssembly == nullptr) - { - LogError("fourkit", "Failed to get load_assembly_and_get_function_pointer delegate."); return; } const wchar_t *typeName = L"Minecraft.Server.FourKit.FourKitHost, Minecraft.Server.FourKit"; + const wchar_t *asmPath = assemblyPath.c_str(); + + struct { const wchar_t *name; void **target; } entries[] = { + {L"Initialize", (void **)&s_managedInit}, + {L"FirePlayerJoin", (void **)&s_managedFireJoin}, + {L"FirePlayerQuit", (void **)&s_managedFireQuit}, + {L"FirePlayerKick", (void **)&s_managedFireKick}, + {L"FirePlayerMove", (void **)&s_managedFireMove}, + {L"Shutdown", (void **)&s_managedShutdown}, + {L"SetNativeCallbacks", (void **)&s_managedSetCallbacks}, + {L"SetWorldCallbacks", (void **)&s_managedSetWorldCallbacks}, + {L"UpdatePlayerEntityId", (void **)&s_managedUpdateEntityId}, + {L"FirePlayerChat", (void **)&s_managedFireChat}, + {L"FireBlockPlace", (void **)&s_managedFireBlockPlace}, + {L"FireBlockBreak", (void **)&s_managedFireBlockBreak}, + {L"FireEntityDamage", (void **)&s_managedFireEntityDamage}, + {L"FireSignChange", (void **)&s_managedFireSignChange}, + {L"FireEntityDeath", (void **)&s_managedFireEntityDeath}, + {L"FirePlayerDeath", (void **)&s_managedFirePlayerDeath}, + {L"SetPlayerCallbacks", (void **)&s_managedSetPlayerCallbacks}, + {L"FirePlayerDropItem", (void **)&s_managedFirePlayerDropItem}, + {L"SetInventoryCallbacks", (void **)&s_managedSetInventoryCallbacks}, + {L"FirePlayerInteract", (void **)&s_managedFirePlayerInteract}, + {L"FirePlayerInteractEntity", (void **)&s_managedFirePlayerInteractEntity}, + {L"FirePlayerPickupItem", (void **)&s_managedFirePlayerPickupItem}, + {L"FireInventoryOpen", (void **)&s_managedFireInventoryOpen}, + {L"HandlePlayerCommand", (void **)&s_managedHandlePlayerCommand}, + {L"HandleConsoleCommand", (void **)&s_managedHandleConsoleCommand}, + {L"GetPluginCommandHelp", (void **)&s_managedGetPluginCommandHelp}, + {L"FirePlayerTeleport", (void **)&s_managedFirePlayerTeleport}, + {L"FirePlayerPortal", (void **)&s_managedFirePlayerPortal}, + {L"FireInventoryClick", (void **)&s_managedFireInventoryClick}, + {L"FireBedEnter", (void **)&s_managedFireBedEnter}, + {L"FireBedLeave", (void **)&s_managedFireBedLeave}, + {L"SetEntityCallbacks", (void **)&s_managedSetEntityCallbacks}, + {L"SetExperienceCallbacks", (void **)&s_managedSetExperienceCallbacks}, + {L"SetParticleCallbacks", (void **)&s_managedSetParticleCallbacks}, + {L"SetVehicleCallbacks", (void **)&s_managedSetVehicleCallbacks}, + }; bool ok = true; - // lol - // this is horrid - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"Initialize", (void **)&s_managedInit); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerJoin", (void **)&s_managedFireJoin); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerQuit", (void **)&s_managedFireQuit); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerKick", (void **)&s_managedFireKick); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerMove", (void **)&s_managedFireMove); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"Shutdown", (void **)&s_managedShutdown); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetNativeCallbacks", (void **)&s_managedSetCallbacks); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetWorldCallbacks", (void **)&s_managedSetWorldCallbacks); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"UpdatePlayerEntityId", (void **)&s_managedUpdateEntityId); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerChat", (void **)&s_managedFireChat); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBlockPlace", (void **)&s_managedFireBlockPlace); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBlockBreak", (void **)&s_managedFireBlockBreak); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireEntityDamage", (void **)&s_managedFireEntityDamage); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireSignChange", (void **)&s_managedFireSignChange); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireEntityDeath", (void **)&s_managedFireEntityDeath); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerDeath", (void **)&s_managedFirePlayerDeath); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetPlayerCallbacks", (void **)&s_managedSetPlayerCallbacks); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerDropItem", (void **)&s_managedFirePlayerDropItem); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetInventoryCallbacks", (void **)&s_managedSetInventoryCallbacks); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerInteract", (void **)&s_managedFirePlayerInteract); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerInteractEntity", (void **)&s_managedFirePlayerInteractEntity); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerPickupItem", (void **)&s_managedFirePlayerPickupItem); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireInventoryOpen", (void **)&s_managedFireInventoryOpen); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"HandlePlayerCommand", (void **)&s_managedHandlePlayerCommand); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"HandleConsoleCommand", (void **)&s_managedHandleConsoleCommand); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"GetPluginCommandHelp", (void **)&s_managedGetPluginCommandHelp); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerTeleport", (void **)&s_managedFirePlayerTeleport); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerPortal", (void **)&s_managedFirePlayerPortal); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireInventoryClick", (void **)&s_managedFireInventoryClick); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBedEnter", (void **)&s_managedFireBedEnter); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBedLeave", (void **)&s_managedFireBedLeave); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetEntityCallbacks", (void **)&s_managedSetEntityCallbacks); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetExperienceCallbacks", (void **)&s_managedSetExperienceCallbacks); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetParticleCallbacks", (void **)&s_managedSetParticleCallbacks); - ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetVehicleCallbacks", (void **)&s_managedSetVehicleCallbacks); + for (const auto &e : entries) + { + ok = ok && GetManagedEntryPoint(loadAssembly, asmPath, typeName, e.name, e.target); + } if (!ok) { @@ -2021,249 +462,6 @@ int FireBlockBreak(int entityId, int dimId, return s_managedFireBlockBreak(entityId, dimId, x, y, z, tileId, data, exp); } -int MapEntityType(int nativeType) -{ - eINSTANCEOF type = (eINSTANCEOF)nativeType; - const int ARROW = 0, BAT = 1, BLAZE = 2, BOAT = 3, CAVE_SPIDER = 4; - const int CHICKEN = 5, COW = 7, CREEPER = 8, DROPPED_ITEM = 9; - const int EGG = 10, ENDER_CRYSTAL = 11, ENDER_DRAGON = 12; - const int ENDER_PEARL = 13, ENDER_SIGNAL = 14, ENDERMAN = 15; - const int EXPERIENCE_ORB = 16, FALLING_BLOCK = 17, FIREBALL = 18; - const int FIREWORK = 19, FISHING_HOOK = 20, GHAST = 21, GIANT = 22; - const int HORSE = 23, IRON_GOLEM = 24, ITEM_FRAME = 25; - const int LEASH_HITCH = 26, LIGHTNING = 27, MAGMA_CUBE = 28; - const int MINECART = 29, MINECART_CHEST = 30, MINECART_FURNACE = 32; - const int MINECART_HOPPER = 33, MINECART_MOB_SPAWNER = 34; - const int MINECART_TNT = 35, MUSHROOM_COW = 36, OCELOT = 37; - const int PAINTING = 38, PIG = 39, PIG_ZOMBIE = 40, PLAYER = 41; - const int PRIMED_TNT = 42, SHEEP = 43, SILVERFISH = 44; - const int SKELETON = 45, SLIME = 46, SMALL_FIREBALL = 47; - const int SNOWBALL = 48, SNOWMAN = 49, SPIDER = 50; - const int SPLASH_POTION = 51, SQUID = 52, THROWN_EXP_BOTTLE = 53; - const int UNKNOWN = 54, VILLAGER = 55, WITCH = 57; - const int WITHER = 58, WITHER_SKULL = 59, WOLF = 60, ZOMBIE = 61; - - switch (type) - { - case eTYPE_ARROW: - return ARROW; - case eTYPE_BAT: - return BAT; - case eTYPE_BLAZE: - return BLAZE; - case eTYPE_BOAT: - return BOAT; - case eTYPE_CAVESPIDER: - return CAVE_SPIDER; - case eTYPE_CHICKEN: - return CHICKEN; - case eTYPE_COW: - return COW; - case eTYPE_CREEPER: - return CREEPER; - case eTYPE_ITEMENTITY: - return DROPPED_ITEM; - case eTYPE_THROWNEGG: - return EGG; - case eTYPE_NETHER_SPHERE: - return ENDER_CRYSTAL; - case eTYPE_ENDERDRAGON: - return ENDER_DRAGON; - case eTYPE_THROWNENDERPEARL: - return ENDER_PEARL; - case eTYPE_EYEOFENDERSIGNAL: - return ENDER_SIGNAL; - case eTYPE_ENDERMAN: - return ENDERMAN; - case eTYPE_EXPERIENCEORB: - return EXPERIENCE_ORB; - case eTYPE_FALLINGTILE: - return FALLING_BLOCK; - case eTYPE_LARGE_FIREBALL: - return FIREBALL; - case eTYPE_FIREWORKS_ROCKET: - return FIREWORK; - case eTYPE_FISHINGHOOK: - return FISHING_HOOK; - case eTYPE_GHAST: - return GHAST; - case eTYPE_GIANT: - return GIANT; - case eTYPE_HORSE: - return HORSE; - case eTYPE_VILLAGERGOLEM: - return IRON_GOLEM; - case eTYPE_ITEM_FRAME: - return ITEM_FRAME; - case eTYPE_LEASHFENCEKNOT: - return LEASH_HITCH; - case eTYPE_LIGHTNINGBOLT: - return LIGHTNING; - case eTYPE_LAVASLIME: - return MAGMA_CUBE; - case eTYPE_MINECART_RIDEABLE: - return MINECART; - case eTYPE_MINECART_CHEST: - return MINECART_CHEST; - case eTYPE_MINECART_FURNACE: - return MINECART_FURNACE; - case eTYPE_MINECART_HOPPER: - return MINECART_HOPPER; - case eTYPE_MINECART_SPAWNER: - return MINECART_MOB_SPAWNER; - case eTYPE_MINECART_TNT: - return MINECART_TNT; - case eTYPE_MUSHROOMCOW: - return MUSHROOM_COW; - case eTYPE_OCELOT: - return OCELOT; - case eTYPE_PAINTING: - return PAINTING; - case eTYPE_PIG: - return PIG; - case eTYPE_PIGZOMBIE: - return PIG_ZOMBIE; - case eTYPE_PLAYER: - return PLAYER; - case eTYPE_SERVERPLAYER: - return PLAYER; - case eTYPE_REMOTEPLAYER: - return PLAYER; - case eTYPE_LOCALPLAYER: - return PLAYER; - case eTYPE_PRIMEDTNT: - return PRIMED_TNT; - case eTYPE_SHEEP: - return SHEEP; - case eTYPE_SILVERFISH: - return SILVERFISH; - case eTYPE_SKELETON: - return SKELETON; - case eTYPE_SLIME: - return SLIME; - case eTYPE_SMALL_FIREBALL: - return SMALL_FIREBALL; - case eTYPE_SNOWBALL: - return SNOWBALL; - case eTYPE_SNOWMAN: - return SNOWMAN; - case eTYPE_SPIDER: - return SPIDER; - case eTYPE_THROWNPOTION: - return SPLASH_POTION; - case eTYPE_SQUID: - return SQUID; - case eTYPE_THROWNEXPBOTTLE: - return THROWN_EXP_BOTTLE; - case eTYPE_VILLAGER: - return VILLAGER; - case eTYPE_WITCH: - return WITCH; - case eTYPE_WITHERBOSS: - return WITHER; - case eTYPE_WITHER_SKULL: - return WITHER_SKULL; - case eTYPE_WOLF: - return WOLF; - case eTYPE_ZOMBIE: - return ZOMBIE; - default: - return UNKNOWN; - } -} - -int MapDamageCause(void *sourcePtr) -{ - DamageSource *source = (DamageSource *)sourcePtr; - const int CONTACT = 1, CUSTOM = 2, DROWNING = 3; - const int ENTITY_ATTACK = 4, ENTITY_EXPLOSION = 5; - const int FALL = 6, FALLING_BLOCK = 7, FIRE = 8, FIRE_TICK = 9; - const int LAVA = 10, MAGIC = 12; - const int PROJECTILE = 15, STARVATION = 16, SUFFOCATION = 17; - const int CAUSE_VOID = 20, CAUSE_WITHER = 21; - - if (source == nullptr) - { - return CUSTOM; - } - if (source == DamageSource::inFire) - { - return FIRE; - } - if (source == DamageSource::onFire) - { - return FIRE_TICK; - } - if (source == DamageSource::lava) - { - return LAVA; - } - if (source == DamageSource::inWall) - { - return SUFFOCATION; - } - if (source == DamageSource::drown) - { - return DROWNING; - } - if (source == DamageSource::starve) - { - return STARVATION; - } - if (source == DamageSource::cactus) - { - return CONTACT; - } - if (source == DamageSource::fall) - { - return FALL; - } - if (source == DamageSource::outOfWorld) - { - return CAUSE_VOID; - } - if (source == DamageSource::genericSource) - { - return CUSTOM; - } - if (source == DamageSource::magic) - { - return MAGIC; - } - if (source == DamageSource::wither) - { - return CAUSE_WITHER; - } - if (source == DamageSource::anvil) - { - return FALLING_BLOCK; - } - if (source == DamageSource::fallingBlock) - { - return FALLING_BLOCK; - } - - if (source->isExplosion()) - { - return ENTITY_EXPLOSION; - } - if (source->isProjectile()) - { - return PROJECTILE; - } - if (source->isMagic()) - { - return MAGIC; - } - - if (dynamic_cast(source) != nullptr) - { - return ENTITY_ATTACK; - } - - return CUSTOM; -} - bool FireEntityDamage(int entityId, int entityTypeId, int dimId, double x, double y, double z, int causeId, double damage, double *outDamage, @@ -2532,27 +730,40 @@ int GetPluginCommandHelp(char *outBuf, int outBufSize, int *outLen) return s_managedGetPluginCommandHelp(outBuf, outBufSize, outLen); } +static bool FireSpatialEvent(fn_fire_player_teleport managedFn, + int entityId, + double fromX, double fromY, double fromZ, int fromDimId, + double toX, double toY, double toZ, int toDimId, + int cause, + double *outToX, double *outToY, double *outToZ) +{ + if (!s_initialized || !managedFn) + { + return false; + } + + double outCoords[3] = {toX, toY, toZ}; + int cancelled = managedFn(entityId, + fromX, fromY, fromZ, fromDimId, + toX, toY, toZ, toDimId, + cause, outCoords); + + *outToX = outCoords[0]; + *outToY = outCoords[1]; + *outToZ = outCoords[2]; + return cancelled != 0; +} + bool FirePlayerTeleport(int entityId, double fromX, double fromY, double fromZ, int fromDimId, double toX, double toY, double toZ, int toDimId, int cause, double *outToX, double *outToY, double *outToZ) { - if (!s_initialized || !s_managedFirePlayerTeleport) - { - return false; - } - - double outCoords[3] = {toX, toY, toZ}; - int cancelled = s_managedFirePlayerTeleport(entityId, - fromX, fromY, fromZ, fromDimId, - toX, toY, toZ, toDimId, - cause, outCoords); - - *outToX = outCoords[0]; - *outToY = outCoords[1]; - *outToZ = outCoords[2]; - return cancelled != 0; + return FireSpatialEvent(s_managedFirePlayerTeleport, entityId, + fromX, fromY, fromZ, fromDimId, + toX, toY, toZ, toDimId, + cause, outToX, outToY, outToZ); } bool FirePlayerPortal(int entityId, @@ -2561,21 +772,10 @@ bool FirePlayerPortal(int entityId, int cause, double *outToX, double *outToY, double *outToZ) { - if (!s_initialized || !s_managedFirePlayerPortal) - { - return false; - } - - double outCoords[3] = {toX, toY, toZ}; - int cancelled = s_managedFirePlayerPortal(entityId, - fromX, fromY, fromZ, fromDimId, - toX, toY, toZ, toDimId, - cause, outCoords); - - *outToX = outCoords[0]; - *outToY = outCoords[1]; - *outToZ = outCoords[2]; - return cancelled != 0; + return FireSpatialEvent(s_managedFirePlayerPortal, entityId, + fromX, fromY, fromZ, fromDimId, + toX, toY, toZ, toDimId, + cause, outToX, outToY, outToZ); } int FireInventoryClick(int entityId, int slot, int button, int clickType) diff --git a/Minecraft.Server/FourKitMappers.cpp b/Minecraft.Server/FourKitMappers.cpp new file mode 100644 index 00000000..6e1803cd --- /dev/null +++ b/Minecraft.Server/FourKitMappers.cpp @@ -0,0 +1,251 @@ +#include "FourKitBridge.h" +#include "stdafx.h" + +#include "..\Minecraft.World\DamageSource.h" +#include "..\Minecraft.World\EntityDamageSource.h" + +namespace FourKitBridge +{ +int MapEntityType(int nativeType) +{ + eINSTANCEOF type = (eINSTANCEOF)nativeType; + const int ARROW = 0, BAT = 1, BLAZE = 2, BOAT = 3, CAVE_SPIDER = 4; + const int CHICKEN = 5, COW = 7, CREEPER = 8, DROPPED_ITEM = 9; + const int EGG = 10, ENDER_CRYSTAL = 11, ENDER_DRAGON = 12; + const int ENDER_PEARL = 13, ENDER_SIGNAL = 14, ENDERMAN = 15; + const int EXPERIENCE_ORB = 16, FALLING_BLOCK = 17, FIREBALL = 18; + const int FIREWORK = 19, FISHING_HOOK = 20, GHAST = 21, GIANT = 22; + const int HORSE = 23, IRON_GOLEM = 24, ITEM_FRAME = 25; + const int LEASH_HITCH = 26, LIGHTNING = 27, MAGMA_CUBE = 28; + const int MINECART = 29, MINECART_CHEST = 30, MINECART_FURNACE = 32; + const int MINECART_HOPPER = 33, MINECART_MOB_SPAWNER = 34; + const int MINECART_TNT = 35, MUSHROOM_COW = 36, OCELOT = 37; + const int PAINTING = 38, PIG = 39, PIG_ZOMBIE = 40, PLAYER = 41; + const int PRIMED_TNT = 42, SHEEP = 43, SILVERFISH = 44; + const int SKELETON = 45, SLIME = 46, SMALL_FIREBALL = 47; + const int SNOWBALL = 48, SNOWMAN = 49, SPIDER = 50; + const int SPLASH_POTION = 51, SQUID = 52, THROWN_EXP_BOTTLE = 53; + const int UNKNOWN = 54, VILLAGER = 55, WITCH = 57; + const int WITHER = 58, WITHER_SKULL = 59, WOLF = 60, ZOMBIE = 61; + + switch (type) + { + case eTYPE_ARROW: + return ARROW; + case eTYPE_BAT: + return BAT; + case eTYPE_BLAZE: + return BLAZE; + case eTYPE_BOAT: + return BOAT; + case eTYPE_CAVESPIDER: + return CAVE_SPIDER; + case eTYPE_CHICKEN: + return CHICKEN; + case eTYPE_COW: + return COW; + case eTYPE_CREEPER: + return CREEPER; + case eTYPE_ITEMENTITY: + return DROPPED_ITEM; + case eTYPE_THROWNEGG: + return EGG; + case eTYPE_NETHER_SPHERE: + return ENDER_CRYSTAL; + case eTYPE_ENDERDRAGON: + return ENDER_DRAGON; + case eTYPE_THROWNENDERPEARL: + return ENDER_PEARL; + case eTYPE_EYEOFENDERSIGNAL: + return ENDER_SIGNAL; + case eTYPE_ENDERMAN: + return ENDERMAN; + case eTYPE_EXPERIENCEORB: + return EXPERIENCE_ORB; + case eTYPE_FALLINGTILE: + return FALLING_BLOCK; + case eTYPE_LARGE_FIREBALL: + return FIREBALL; + case eTYPE_FIREWORKS_ROCKET: + return FIREWORK; + case eTYPE_FISHINGHOOK: + return FISHING_HOOK; + case eTYPE_GHAST: + return GHAST; + case eTYPE_GIANT: + return GIANT; + case eTYPE_HORSE: + return HORSE; + case eTYPE_VILLAGERGOLEM: + return IRON_GOLEM; + case eTYPE_ITEM_FRAME: + return ITEM_FRAME; + case eTYPE_LEASHFENCEKNOT: + return LEASH_HITCH; + case eTYPE_LIGHTNINGBOLT: + return LIGHTNING; + case eTYPE_LAVASLIME: + return MAGMA_CUBE; + case eTYPE_MINECART_RIDEABLE: + return MINECART; + case eTYPE_MINECART_CHEST: + return MINECART_CHEST; + case eTYPE_MINECART_FURNACE: + return MINECART_FURNACE; + case eTYPE_MINECART_HOPPER: + return MINECART_HOPPER; + case eTYPE_MINECART_SPAWNER: + return MINECART_MOB_SPAWNER; + case eTYPE_MINECART_TNT: + return MINECART_TNT; + case eTYPE_MUSHROOMCOW: + return MUSHROOM_COW; + case eTYPE_OCELOT: + return OCELOT; + case eTYPE_PAINTING: + return PAINTING; + case eTYPE_PIG: + return PIG; + case eTYPE_PIGZOMBIE: + return PIG_ZOMBIE; + case eTYPE_PLAYER: + return PLAYER; + case eTYPE_SERVERPLAYER: + return PLAYER; + case eTYPE_REMOTEPLAYER: + return PLAYER; + case eTYPE_LOCALPLAYER: + return PLAYER; + case eTYPE_PRIMEDTNT: + return PRIMED_TNT; + case eTYPE_SHEEP: + return SHEEP; + case eTYPE_SILVERFISH: + return SILVERFISH; + case eTYPE_SKELETON: + return SKELETON; + case eTYPE_SLIME: + return SLIME; + case eTYPE_SMALL_FIREBALL: + return SMALL_FIREBALL; + case eTYPE_SNOWBALL: + return SNOWBALL; + case eTYPE_SNOWMAN: + return SNOWMAN; + case eTYPE_SPIDER: + return SPIDER; + case eTYPE_THROWNPOTION: + return SPLASH_POTION; + case eTYPE_SQUID: + return SQUID; + case eTYPE_THROWNEXPBOTTLE: + return THROWN_EXP_BOTTLE; + case eTYPE_VILLAGER: + return VILLAGER; + case eTYPE_WITCH: + return WITCH; + case eTYPE_WITHERBOSS: + return WITHER; + case eTYPE_WITHER_SKULL: + return WITHER_SKULL; + case eTYPE_WOLF: + return WOLF; + case eTYPE_ZOMBIE: + return ZOMBIE; + default: + return UNKNOWN; + } +} + +int MapDamageCause(void *sourcePtr) +{ + DamageSource *source = (DamageSource *)sourcePtr; + const int CONTACT = 1, CUSTOM = 2, DROWNING = 3; + const int ENTITY_ATTACK = 4, ENTITY_EXPLOSION = 5; + const int FALL = 6, FALLING_BLOCK = 7, FIRE = 8, FIRE_TICK = 9; + const int LAVA = 10, MAGIC = 12; + const int PROJECTILE = 15, STARVATION = 16, SUFFOCATION = 17; + const int CAUSE_VOID = 20, CAUSE_WITHER = 21; + + if (source == nullptr) + { + return CUSTOM; + } + if (source == DamageSource::inFire) + { + return FIRE; + } + if (source == DamageSource::onFire) + { + return FIRE_TICK; + } + if (source == DamageSource::lava) + { + return LAVA; + } + if (source == DamageSource::inWall) + { + return SUFFOCATION; + } + if (source == DamageSource::drown) + { + return DROWNING; + } + if (source == DamageSource::starve) + { + return STARVATION; + } + if (source == DamageSource::cactus) + { + return CONTACT; + } + if (source == DamageSource::fall) + { + return FALL; + } + if (source == DamageSource::outOfWorld) + { + return CAUSE_VOID; + } + if (source == DamageSource::genericSource) + { + return CUSTOM; + } + if (source == DamageSource::magic) + { + return MAGIC; + } + if (source == DamageSource::wither) + { + return CAUSE_WITHER; + } + if (source == DamageSource::anvil) + { + return FALLING_BLOCK; + } + if (source == DamageSource::fallingBlock) + { + return FALLING_BLOCK; + } + + if (source->isExplosion()) + { + return ENTITY_EXPLOSION; + } + if (source->isProjectile()) + { + return PROJECTILE; + } + if (source->isMagic()) + { + return MAGIC; + } + + if (dynamic_cast(source) != nullptr) + { + return ENTITY_ATTACK; + } + + return CUSTOM; +} +} diff --git a/Minecraft.Server/FourKitNatives.cpp b/Minecraft.Server/FourKitNatives.cpp new file mode 100644 index 00000000..00c072da --- /dev/null +++ b/Minecraft.Server/FourKitNatives.cpp @@ -0,0 +1,1140 @@ +#include "FourKitNatives.h" +#include "FourKitBridge.h" +#include "Common/StringUtils.h" +#include "stdafx.h" + +#include +#include + +#include "..\Minecraft.Client\MinecraftServer.h" +#include "..\Minecraft.Client\PlayerConnection.h" +#include "..\Minecraft.Client\PlayerList.h" +#include "..\Minecraft.Client\ServerConnection.h" +#include "..\Minecraft.Client\ServerLevel.h" +#include "..\Minecraft.Client\ServerPlayer.h" +#include "..\Minecraft.Client\ServerPlayerGameMode.h" +#include "..\Minecraft.Client\Windows64\Network\WinsockNetLayer.h" +#include "..\Minecraft.World\AbstractContainerMenu.h" +#include "..\Minecraft.World\AddGlobalEntityPacket.h" +#include "..\Minecraft.World\ArrayWithLength.h" +#include "..\Minecraft.World\Class.h" +#include "..\Minecraft.World\CompoundContainer.h" +#include "..\Minecraft.World\Connection.h" +#include "..\Minecraft.World\DamageSource.h" +#include "..\Minecraft.World\Explosion.h" +#include "..\Minecraft.World\ItemEntity.h" +#include "..\Minecraft.World\ItemInstance.h" +#include "..\Minecraft.World\LevelData.h" +#include "..\Minecraft.World\LightningBolt.h" +#include "..\Minecraft.World\Player.h" +#include "..\Minecraft.World\PlayerAbilitiesPacket.h" +#include "..\Minecraft.World\SetCarriedItemPacket.h" +#include "..\Minecraft.World\SetExperiencePacket.h" +#include "..\Minecraft.World\SetHealthPacket.h" +#include "..\Minecraft.World\LevelSoundPacket.h" +#include "..\Minecraft.World\LevelParticlesPacket.h" +#include "..\Minecraft.World\SetEntityLinkPacket.h" +#include "..\Minecraft.World\SimpleContainer.h" +#include "..\Minecraft.World\Slot.h" +#include "..\Minecraft.World\Tile.h" +#include "..\Minecraft.World\net.minecraft.world.entity.player.h" +#include "Access\Access.h" +#include "Common\NetworkUtils.h" +#include "ServerLogManager.h" + +namespace +{ + +static shared_ptr FindPlayer(int entityId) +{ + PlayerList *list = MinecraftServer::getPlayerList(); + if (!list) + return nullptr; + for (auto &p : list->players) + { + if (p && p->entityId == entityId) + return p; + } + return nullptr; +} + +static shared_ptr FindEntity(int entityId) +{ + MinecraftServer *srv = MinecraftServer::getInstance(); + if (!srv) + return nullptr; + const int dims[] = {0, -1, 1}; + for (int dim : dims) + { + ServerLevel *level = srv->getLevel(dim); + if (!level) + continue; + shared_ptr entity = level->getEntity(entityId); + if (entity) + return entity; + } + return nullptr; +} + +static ServerLevel *GetLevel(int dimId) +{ + MinecraftServer *srv = MinecraftServer::getInstance(); + if (!srv) + return nullptr; + return srv->getLevel(dimId); +} + +class VirtualContainer : public SimpleContainer +{ + int m_containerType; + + public: + VirtualContainer(int containerType, const std::wstring &name, int size) + : SimpleContainer(0, name, !name.empty(), size), m_containerType(containerType) + { + } + virtual int getContainerType() override + { + return m_containerType; + } +}; + +} + +namespace FourKitBridge +{ +void __cdecl NativeDamagePlayer(int entityId, float amount) +{ + auto player = FindPlayer(entityId); + if (player) + { + player->hurt(DamageSource::genericSource, amount); + return; + } + auto entity = FindEntity(entityId); + if (entity) + { + entity->hurt(DamageSource::genericSource, amount); + } +} + +void __cdecl NativeSetPlayerHealth(int entityId, float health) +{ + auto player = FindPlayer(entityId); + if (player) + { + player->setHealth(health); + } +} + +void __cdecl NativeTeleportPlayer(int entityId, double x, double y, double z) +{ + auto player = FindPlayer(entityId); + if (player && player->connection) + { + double outX, outY, outZ; + bool cancelled = FirePlayerTeleport(entityId, + player->x, player->y, player->z, player->dimension, + x, y, z, player->dimension, + 2 /* PLUGIN */, + &outX, &outY, &outZ); + if (!cancelled) + { + player->connection->teleport(outX, outY, outZ, player->yRot, player->xRot); + } + } +} + +void __cdecl NativeSetPlayerGameMode(int entityId, int gameMode) +{ + auto player = FindPlayer(entityId); + if (player && player->gameMode) + { + GameType *type = GameType::byId(gameMode); + if (type) + { + player->setGameMode(type); + } + } +} + +void __cdecl NativeBroadcastMessage(const char *utf8, int len) +{ + if (!utf8 || len <= 0) + return; + std::wstring wide = ServerRuntime::StringUtils::Utf8ToWide(utf8); + if (wide.empty()) + return; + PlayerList *list = MinecraftServer::getPlayerList(); + if (list) + { + list->broadcastAll(std::make_shared(wide)); + } +} + +void __cdecl NativeSetFallDistance(int entityId, float distance) +{ + auto player = FindPlayer(entityId); + if (player) + { + player->fallDistance = distance; + } +} + +// double[27] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension, isSleeping, sleepTimer, sneaking, sprinting, onGround, velocityX, velocityY, velocityZ, allowFlight, sleepingIgnored, experienceLevel, experienceProgress, totalExperience, foodLevel, saturation, exhaustion } +void __cdecl NativeGetPlayerSnapshot(int entityId, double *outData) +{ + auto player = FindPlayer(entityId); + if (!player) + { + memset(outData, 0, 27 * sizeof(double)); + outData[3] = 20.0; + outData[4] = 20.0; + outData[7] = 0.1; + outData[24] = 20.0; + outData[25] = 5.0; + return; + } + outData[0] = player->x; + outData[1] = player->y; + outData[2] = player->z; + outData[3] = (double)player->getHealth(); + outData[4] = (double)player->getMaxHealth(); + outData[5] = (double)player->fallDistance; + GameType *gm = player->gameMode ? player->gameMode->getGameModeForPlayer() : GameType::SURVIVAL; + outData[6] = (double)(gm ? gm->getId() : 0); + outData[7] = (double)player->abilities.getWalkingSpeed(); + outData[8] = (double)player->yRot; + outData[9] = (double)player->xRot; + outData[10] = (double)player->dimension; + outData[11] = player->isSleeping() ? 1.0 : 0.0; + outData[12] = (double)player->getSleepTimer(); + outData[13] = player->isSneaking() ? 1.0 : 0.0; + outData[14] = player->isSprinting() ? 1.0 : 0.0; + outData[15] = player->onGround ? 1.0 : 0.0; + outData[16] = player->xd; + outData[17] = player->yd; + outData[18] = player->zd; + outData[19] = player->abilities.mayfly ? 1.0 : 0.0; + outData[20] = player->fk_sleepingIgnored ? 1.0 : 0.0; + outData[21] = (double)player->experienceLevel; + outData[22] = (double)player->experienceProgress; + outData[23] = (double)player->totalExperience; + FoodData *fd = player->getFoodData(); + outData[24] = fd ? (double)fd->getFoodLevel() : 20.0; + outData[25] = fd ? (double)fd->getSaturationLevel() : 5.0; + outData[26] = fd ? (double)fd->getExhaustionLevel() : 0.0; +} + +void __cdecl NativeSendMessage(int entityId, const char *utf8, int len) +{ + if (!utf8 || len <= 0) + return; + auto player = FindPlayer(entityId); + if (player && player->connection) + { + std::wstring wide = ServerRuntime::StringUtils::Utf8ToWide(utf8); + if (!wide.empty()) + { + player->connection->send(std::make_shared(wide)); + } + } +} + +void __cdecl NativeSetWalkSpeed(int entityId, float speed) +{ + auto player = FindPlayer(entityId); + if (player) + { + player->abilities.setWalkingSpeed(speed); + if (player->connection) + { + player->connection->send(std::make_shared(&player->abilities)); + } + } +} + +void __cdecl NativeTeleportEntity(int entityId, int dimId, double x, double y, double z) +{ + auto player = FindPlayer(entityId); + if (player && player->connection) + { + player->connection->teleport(x, y, z, player->yRot, player->xRot); + return; + } + ServerLevel *level = GetLevel(dimId); + if (!level) + return; + shared_ptr entity = level->getEntity(entityId); + if (entity) + { + entity->moveTo(x, y, z, entity->yRot, entity->xRot); + } +} + +int __cdecl NativeGetTileId(int dimId, int x, int y, int z) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return 0; + return level->getTile(x, y, z); +} + +int __cdecl NativeGetTileData(int dimId, int x, int y, int z) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return 0; + return level->getData(x, y, z); +} + +void __cdecl NativeSetTile(int dimId, int x, int y, int z, int tileId, int data) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return; + level->setTileAndData(x, y, z, tileId, data, Tile::UPDATE_ALL); +} + +void __cdecl NativeSetTileData(int dimId, int x, int y, int z, int data) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return; + level->setData(x, y, z, data, Tile::UPDATE_ALL); +} + +int __cdecl NativeBreakBlock(int dimId, int x, int y, int z) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return 0; + if (level->getTile(x, y, z) == 0) + return 0; + return level->destroyTile(x, y, z, true) ? 1 : 0; +} + +int __cdecl NativeGetHighestBlockY(int dimId, int x, int z) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return 0; + return level->getHeightmap(x, z); +} + +// double[7] = { spawnX, spawnY, spawnZ, seed, dayTime, isRaining, isThundering } +void __cdecl NativeGetWorldInfo(int dimId, double *outBuf) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + { + memset(outBuf, 0, 7 * sizeof(double)); + return; + } + LevelData *ld = level->getLevelData(); + Pos *spawn = level->getSharedSpawnPos(); + outBuf[0] = spawn ? (double)spawn->x : 0.0; + outBuf[1] = spawn ? (double)spawn->y : 64.0; + outBuf[2] = spawn ? (double)spawn->z : 0.0; + outBuf[3] = (double)level->getSeed(); + outBuf[4] = (double)level->getDayTime(); + outBuf[5] = ld && ld->isRaining() ? 1.0 : 0.0; + outBuf[6] = ld && ld->isThundering() ? 1.0 : 0.0; +} + +void __cdecl NativeSetWorldTime(int dimId, int64_t time) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return; + level->setDayTime(time); +} + +void __cdecl NativeSetWeather(int dimId, int storm, int thundering, int thunderDuration) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return; + LevelData *ld = level->getLevelData(); + if (!ld) + return; + if (storm >= 0) + ld->setRaining(storm != 0); + if (thundering >= 0) + ld->setThundering(thundering != 0); + if (thunderDuration >= 0) + ld->setThunderTime(thunderDuration); +} + +int __cdecl NativeCreateExplosion(int dimId, double x, double y, double z, float power, int setFire, int breakBlocks) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return 0; + Explosion explosion(level, nullptr, x, y, z, power); + explosion.fire = (setFire != 0); + explosion.destroyBlocks = (breakBlocks != 0); + explosion.explode(); + explosion.finalizeExplosion(true); + return 1; +} + +int __cdecl NativeStrikeLightning(int dimId, double x, double y, double z, int effectOnly) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return 0; + + std::shared_ptr lightning = std::shared_ptr(new LightningBolt(level, x, y, z)); + + if (effectOnly != 0) + { + PlayerList *playerList = MinecraftServer::getPlayerList(); + if (playerList == NULL) + return 0; + playerList->broadcast(x, y, z, 512.0, dimId, std::shared_ptr(new AddGlobalEntityPacket(lightning))); + level->playSound(x, y, z, eSoundType_AMBIENT_WEATHER_THUNDER, 10000, 0.8f + level->random->nextFloat() * 0.2f); + level->playSound(x, y, z, eSoundType_RANDOM_EXPLODE, 2, 0.5f + level->random->nextFloat() * 0.2f); + return 1; + } + + return level->addGlobalEntity(lightning) ? 1 : 0; +} + +int __cdecl NativeSetSpawnLocation(int dimId, int x, int y, int z) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return 0; + level->setSpawnPos(x, y, z); + return 1; +} + +void __cdecl NativeDropItem(int dimId, double x, double y, double z, int itemId, int count, int auxValue, int naturally) +{ + ServerLevel *level = GetLevel(dimId); + if (!level) + return; + if (itemId <= 0 || count <= 0) + return; + + auto itemInstance = std::make_shared(itemId, count, auxValue); + double spawnX = x, spawnY = y, spawnZ = z; + if (naturally) + { + float s = 0.7f; + spawnX += level->random->nextFloat() * s + (1 - s) * 0.5; + spawnY += level->random->nextFloat() * s + (1 - s) * 0.5; + spawnZ += level->random->nextFloat() * s + (1 - s) * 0.5; + } + + auto item = std::make_shared(level, spawnX, spawnY, spawnZ, itemInstance); + item->throwTime = 10; + level->addEntity(item); +} + +void __cdecl NativeKickPlayer(int entityId, int reason) +{ + auto player = FindPlayer(entityId); + if (player && player->connection) + { + DisconnectPacket::eDisconnectReason r = static_cast(reason); + player->connection->disconnect(r); + } +} + +int __cdecl NativeBanPlayer(int entityId, const char *reasonUtf8, int reasonByteLen) +{ + if (!ServerRuntime::Access::IsInitialized()) + return 0; + + auto player = FindPlayer(entityId); + if (!player) + return 0; + + std::vector xuids; + PlayerUID xuid1 = player->getXuid(); + PlayerUID xuid2 = player->getOnlineXuid(); + if (xuid1 != INVALID_XUID) + xuids.push_back(xuid1); + if (xuid2 != INVALID_XUID && xuid2 != xuid1) + xuids.push_back(xuid2); + + if (xuids.empty()) + return 0; + + std::string reason = (reasonUtf8 && reasonByteLen > 0) ? std::string(reasonUtf8, reasonByteLen) : "Banned by plugin."; + std::string playerName = ServerRuntime::StringUtils::WideToUtf8(player->getName()); + + ServerRuntime::Access::BanMetadata metadata = ServerRuntime::Access::BanManager::BuildDefaultMetadata("Plugin"); + metadata.reason = reason; + + for (auto xuid : xuids) + { + if (!ServerRuntime::Access::IsPlayerBanned(xuid)) + ServerRuntime::Access::AddPlayerBan(xuid, playerName, metadata); + } + + if (player->connection) + player->connection->disconnect(DisconnectPacket::eDisconnect_Banned); + + return 1; +} + +int __cdecl NativeBanPlayerIp(int entityId, const char *reasonUtf8, int reasonByteLen) +{ + if (!ServerRuntime::Access::IsInitialized()) + return 0; + + auto player = FindPlayer(entityId); + if (!player || !player->connection || !player->connection->connection || !player->connection->connection->getSocket()) + return 0; + + unsigned char smallId = player->connection->connection->getSocket()->getSmallId(); + if (smallId == 0) + return 0; + + std::string playerIp; + if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, &playerIp)) + return 0; + + std::string reason = (reasonUtf8 && reasonByteLen > 0) ? std::string(reasonUtf8, reasonByteLen) : "Banned by plugin."; + + ServerRuntime::Access::BanMetadata metadata = ServerRuntime::Access::BanManager::BuildDefaultMetadata("Plugin"); + metadata.reason = reason; + + std::string normalizedIp = ServerRuntime::NetworkUtils::NormalizeIpToken(playerIp); + if (ServerRuntime::Access::IsIpBanned(normalizedIp)) + return 0; + + if (!ServerRuntime::Access::AddIpBan(normalizedIp, metadata)) + return 0; + + PlayerList *list = MinecraftServer::getPlayerList(); + if (list) + { + std::vector> snapshot = list->players; + for (auto &p : snapshot) + { + if (!p || !p->connection || !p->connection->connection || !p->connection->connection->getSocket()) + continue; + unsigned char sid = p->connection->connection->getSocket()->getSmallId(); + if (sid == 0) + continue; + std::string pIp; + if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(sid, &pIp)) + continue; + if (ServerRuntime::NetworkUtils::NormalizeIpToken(pIp) == normalizedIp) + { + if (p->connection) + p->connection->disconnect(DisconnectPacket::eDisconnect_Banned); + } + } + } + + return 1; +} + +int __cdecl NativeGetPlayerAddress(int entityId, char *outIpBuf, int outIpBufSize, int *outPort) +{ + if (outPort) + *outPort = 0; + if (outIpBuf && outIpBufSize > 0) + outIpBuf[0] = '\0'; + + auto player = FindPlayer(entityId); + if (!player || !player->connection || !player->connection->connection || !player->connection->connection->getSocket()) + return 0; + + unsigned char smallId = player->connection->connection->getSocket()->getSmallId(); + if (smallId == 0) + return 0; + + std::string playerIp; + if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, &playerIp)) + { + SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId); + if (sock != INVALID_SOCKET) + { + sockaddr_in addr; + int addrLen = sizeof(addr); + if (getpeername(sock, (sockaddr *)&addr, &addrLen) == 0) + { + char ipBuf[64] = {}; + if (inet_ntop(AF_INET, &addr.sin_addr, ipBuf, sizeof(ipBuf))) + { + playerIp = ipBuf; + if (outPort) + *outPort = (int)ntohs(addr.sin_port); + } + } + } + if (playerIp.empty()) + return 0; + } + else + { + SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId); + if (sock != INVALID_SOCKET && outPort) + { + sockaddr_in addr; + int addrLen = sizeof(addr); + if (getpeername(sock, (sockaddr *)&addr, &addrLen) == 0) + *outPort = (int)ntohs(addr.sin_port); + } + } + + if (outIpBuf && outIpBufSize > 0) + { + int copyLen = (int)playerIp.size(); + if (copyLen >= outIpBufSize) + copyLen = outIpBufSize - 1; + memcpy(outIpBuf, playerIp.c_str(), copyLen); + outIpBuf[copyLen] = '\0'; + } + + return 1; +} + + +void __cdecl NativeGetPlayerInventory(int entityId, int *outData) +{ + memset(outData, 0, 121 * sizeof(int)); + + auto player = FindPlayer(entityId); + if (!player || !player->inventory) + return; + + unsigned int size = player->inventory->getContainerSize(); + if (size > 40) + size = 40; + + for (unsigned int i = 0; i < size; i++) + { + auto item = player->inventory->getItem(i); + if (item) + { + outData[i * 3] = item->id; + outData[i * 3 + 1] = item->count; + outData[i * 3 + 2] = item->getAuxValue(); + } + } + + outData[120] = player->inventory->selected; +} + +void __cdecl NativeSetPlayerInventorySlot(int entityId, int slot, int itemId, int count, int aux) +{ + auto player = FindPlayer(entityId); + if (!player || !player->inventory) + return; + + if (itemId <= 0 || count <= 0) + player->inventory->setItem(slot, nullptr); + else + player->inventory->setItem(slot, std::make_shared(itemId, count, aux)); +} + +void __cdecl NativeGetContainerContents(int entityId, int *outData, int maxSlots) +{ + memset(outData, 0, maxSlots * 3 * sizeof(int)); + + auto player = FindPlayer(entityId); + if (!player || !player->containerMenu || player->containerMenu == player->inventoryMenu) + return; + + auto *menu = player->containerMenu; + auto *items = menu->getItems(); + int count = (int)items->size(); + if (count > maxSlots) + count = maxSlots; + + for (int i = 0; i < count; i++) + { + auto &item = (*items)[i]; + if (item) + { + outData[i * 3] = item->id; + outData[i * 3 + 1] = item->count; + outData[i * 3 + 2] = item->getAuxValue(); + } + } + delete items; +} + +void __cdecl NativeSetContainerSlot(int entityId, int slot, int itemId, int count, int aux) +{ + auto player = FindPlayer(entityId); + if (!player || !player->containerMenu || player->containerMenu == player->inventoryMenu) + return; + + auto *menu = player->containerMenu; + if (slot < 0 || slot >= (int)menu->slots.size()) + return; + + if (itemId <= 0 || count <= 0) + menu->setItem(slot, nullptr); + else + menu->setItem(slot, std::make_shared(itemId, count, aux)); + + menu->broadcastChanges(); +} + +void __cdecl NativeGetContainerViewerEntityIds(int entityId, int *outIds, int maxCount, int *outCount) +{ + *outCount = 0; + + auto player = FindPlayer(entityId); + if (!player || !player->containerMenu || player->containerMenu == player->inventoryMenu) + return; + + auto *menu = player->containerMenu; + if (menu->slots.empty()) + return; + + Container *myContainer = menu->slots[0]->container.get(); + if (!myContainer) + return; + + CompoundContainer *myCompound = dynamic_cast(myContainer); + if (myCompound) + myContainer = myCompound->getFirstContainer().get(); + + PlayerList *list = MinecraftServer::getPlayerList(); + if (!list) + return; + + int count = 0; + for (auto &p : list->players) + { + if (!p || !p->containerMenu || p->containerMenu == p->inventoryMenu) + continue; + if (p->containerMenu->slots.empty()) + continue; + Container *theirContainer = p->containerMenu->slots[0]->container.get(); + CompoundContainer *theirCompound = dynamic_cast(theirContainer); + if (theirCompound) + theirContainer = theirCompound->getFirstContainer().get(); + if (theirContainer == myContainer && count < maxCount) + outIds[count++] = p->entityId; + } + *outCount = count; +} + +void __cdecl NativeCloseContainer(int entityId) +{ + auto player = FindPlayer(entityId); + if (player) + player->doCloseContainer(); +} + +void __cdecl NativeOpenVirtualContainer(int entityId, int nativeType, const char *titleUtf8, int titleByteLen, int slotCount, int *itemsBuf) +{ + auto player = FindPlayer(entityId); + if (!player) + return; + + if (player->containerMenu != player->inventoryMenu) + player->doCloseContainer(); + + std::wstring title = ServerRuntime::StringUtils::Utf8ToWide(std::string(titleUtf8, titleByteLen)); + auto container = std::make_shared(nativeType, title, slotCount); + + for (int i = 0; i < slotCount; i++) + { + int id = itemsBuf[i * 3]; + int count = itemsBuf[i * 3 + 1]; + int aux = itemsBuf[i * 3 + 2]; + if (id > 0 && count > 0) + container->setItem(i, std::make_shared(id, count, aux)); + } + + player->openContainer(container); +} + +// [nameLen:int32][nameUTF8:bytes][loreCount:int32][lore0Len:int32][lore0UTF8:bytes] +int __cdecl NativeGetItemMeta(int entityId, int slot, char *outBuf, int bufSize) +{ + auto player = FindPlayer(entityId); + if (!player || !player->inventory) + return 0; + + unsigned int size = player->inventory->getContainerSize(); + if (slot < 0 || slot >= (int)size) + return 0; + + auto item = player->inventory->getItem(slot); + if (!item || !item->hasTag()) + return 0; + + CompoundTag *tag = item->getTag(); + if (!tag || !tag->contains(L"display")) + return 0; + + CompoundTag *display = tag->getCompound(L"display"); + bool hasName = display->contains(L"Name"); + bool hasLore = display->contains(L"Lore"); + + if (!hasName && !hasLore) + return 0; + + int offset = 0; + + if (hasName) + { + std::wstring wname = display->getString(L"Name"); + std::string nameUtf8 = ServerRuntime::StringUtils::WideToUtf8(wname); + int nameLen = (int)nameUtf8.size(); + if (offset + 4 + nameLen > bufSize) return 0; + memcpy(outBuf + offset, &nameLen, 4); + offset += 4; + memcpy(outBuf + offset, nameUtf8.data(), nameLen); + offset += nameLen; + } + else + { + int zero = 0; + if (offset + 4 > bufSize) return 0; + memcpy(outBuf + offset, &zero, 4); + offset += 4; + } + + if (hasLore) + { + ListTag *lore = (ListTag *)display->getList(L"Lore"); + int loreCount = lore->size(); + if (offset + 4 > bufSize) return 0; + memcpy(outBuf + offset, &loreCount, 4); + offset += 4; + + for (int i = 0; i < loreCount; i++) + { + std::wstring wline = lore->get(i)->data; + std::string lineUtf8 = ServerRuntime::StringUtils::WideToUtf8(wline); + int lineLen = (int)lineUtf8.size(); + if (offset + 4 + lineLen > bufSize) return 0; + memcpy(outBuf + offset, &lineLen, 4); + offset += 4; + memcpy(outBuf + offset, lineUtf8.data(), lineLen); + offset += lineLen; + } + } + else + { + int zero = 0; + if (offset + 4 > bufSize) return 0; + memcpy(outBuf + offset, &zero, 4); + offset += 4; + } + + return offset; +} + +void __cdecl NativeSetItemMeta(int entityId, int slot, const char *inBuf, int bufSize) +{ + auto player = FindPlayer(entityId); + if (!player || !player->inventory) + return; + + unsigned int size = player->inventory->getContainerSize(); + if (slot < 0 || slot >= (int)size) + return; + + auto item = player->inventory->getItem(slot); + if (!item) + return; + + if (inBuf == nullptr || bufSize <= 0) + { + item->resetHoverName(); + if (item->hasTag()) + { + CompoundTag *tag = item->getTag(); + if (tag && tag->contains(L"display")) + { + CompoundTag *display = tag->getCompound(L"display"); + display->remove(L"Lore"); + if (display->isEmpty()) + { + tag->remove(L"display"); + if (tag->isEmpty()) + item->setTag(nullptr); + } + } + } + return; + } + + int offset = 0; + + if (offset + 4 > bufSize) return; + int nameLen = 0; + memcpy(&nameLen, inBuf + offset, 4); + offset += 4; + + if (nameLen > 0) + { + if (offset + nameLen > bufSize) return; + std::string nameUtf8(inBuf + offset, nameLen); + offset += nameLen; + std::wstring wname = ServerRuntime::StringUtils::Utf8ToWide(nameUtf8); + item->setHoverName(wname); + } + else + { + item->resetHoverName(); + } + + if (offset + 4 > bufSize) return; + int loreCount = 0; + memcpy(&loreCount, inBuf + offset, 4); + offset += 4; + + if (loreCount > 0) + { + if (!item->hasTag()) item->setTag(new CompoundTag()); + CompoundTag *tag = item->getTag(); + if (!tag->contains(L"display")) tag->putCompound(L"display", new CompoundTag()); + CompoundTag *display = tag->getCompound(L"display"); + + auto *loreList = new ListTag(L"Lore"); + for (int i = 0; i < loreCount; i++) + { + if (offset + 4 > bufSize) break; + int lineLen = 0; + memcpy(&lineLen, inBuf + offset, 4); + offset += 4; + + std::wstring wline; + if (lineLen > 0 && offset + lineLen <= bufSize) + { + std::string lineUtf8(inBuf + offset, lineLen); + offset += lineLen; + wline = ServerRuntime::StringUtils::Utf8ToWide(lineUtf8); + } + loreList->add(new StringTag(L"", wline)); + } + display->put(L"Lore", loreList); + } + else + { + if (item->hasTag()) + { + CompoundTag *tag = item->getTag(); + if (tag && tag->contains(L"display")) + tag->getCompound(L"display")->remove(L"Lore"); + } + } +} + +void __cdecl NativeSetHeldItemSlot(int entityId, int slot) +{ + auto player = FindPlayer(entityId); + if (!player || !player->inventory) return; + if (slot < 0 || slot >= Inventory::getSelectionSize()) return; + player->inventory->selected = slot; + if (player->connection) + player->connection->queueSend(std::make_shared(slot)); +} + +void __cdecl NativeSetSneaking(int entityId, int sneak) +{ + auto player = FindPlayer(entityId); + if (player) + player->setSneaking(sneak != 0); +} + +void __cdecl NativeSetVelocity(int entityId, double x, double y, double z) +{ + auto player = FindPlayer(entityId); + if (player) + { + player->xd = x; + player->yd = y; + player->zd = z; + player->hurtMarked = true; + return; + } + auto entity = FindEntity(entityId); + if (entity) + { + entity->xd = x; + entity->yd = y; + entity->zd = z; + entity->hurtMarked = true; + } +} + +void __cdecl NativeSetAllowFlight(int entityId, int allowFlight) +{ + auto player = FindPlayer(entityId); + if (player) + { + player->abilities.mayfly = (allowFlight != 0); + if (!player->abilities.mayfly) + player->abilities.flying = false; + if (player->connection) + player->connection->send(std::make_shared(&player->abilities)); + } +} + +void __cdecl NativePlaySound(int entityId, int soundId, double x, double y, double z, float volume, float pitch) +{ + auto player = FindPlayer(entityId); + if (player && player->connection) + player->connection->send(std::make_shared(soundId, x, y, z, volume, pitch)); +} + +void __cdecl NativeSetSleepingIgnored(int entityId, int ignored) +{ + auto player = FindPlayer(entityId); + if (player) + player->fk_sleepingIgnored = (ignored != 0); +} + +void __cdecl NativeSetLevel(int entityId, int level) +{ + auto player = FindPlayer(entityId); + if (!player) return; + player->experienceLevel = level; + if (player->connection) + player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel)); +} + +void __cdecl NativeSetExp(int entityId, float exp) +{ + auto player = FindPlayer(entityId); + if (!player) return; + player->experienceProgress = exp; + if (player->connection) + player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel)); +} + +void __cdecl NativeGiveExp(int entityId, int amount) +{ + auto player = FindPlayer(entityId); + if (!player) return; + player->increaseXp(amount); + if (player->connection) + player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel)); +} + +void __cdecl NativeGiveExpLevels(int entityId, int amount) +{ + auto player = FindPlayer(entityId); + if (!player) return; + player->giveExperienceLevels(amount); + if (player->connection) + player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel)); +} + +void __cdecl NativeSetFoodLevel(int entityId, int foodLevel) +{ + auto player = FindPlayer(entityId); + if (!player) return; + FoodData *fd = player->getFoodData(); + if (!fd) return; + fd->setFoodLevel(foodLevel); + if (player->connection) + player->connection->send(std::make_shared(player->getHealth(), fd->getFoodLevel(), fd->getSaturationLevel(), eTelemetryChallenges_Unknown)); +} + +void __cdecl NativeSetSaturation(int entityId, float saturation) +{ + auto player = FindPlayer(entityId); + if (!player) return; + FoodData *fd = player->getFoodData(); + if (!fd) return; + fd->setSaturation(saturation); + if (player->connection) + player->connection->send(std::make_shared(player->getHealth(), fd->getFoodLevel(), fd->getSaturationLevel(), eTelemetryChallenges_Unknown)); +} + +void __cdecl NativeSetExhaustion(int entityId, float exhaustion) +{ + auto player = FindPlayer(entityId); + if (!player) return; + FoodData *fd = player->getFoodData(); + if (!fd) return; + fd->setExhaustion(exhaustion); +} + +void __cdecl NativeSpawnParticle(int entityId, int particleId, float x, float y, float z, float offsetX, float offsetY, float offsetZ, float speed, int count) +{ + auto player = FindPlayer(entityId); + if (!player || !player->connection) return; + wchar_t buf[32]; + swprintf_s(buf, L"%d", particleId); + player->connection->send(std::make_shared(std::wstring(buf), x, y, z, offsetX, offsetY, offsetZ, speed, count)); +} + +int __cdecl NativeSetPassenger(int entityId, int passengerEntityId) +{ + auto entity = FindEntity(entityId); + auto passenger = FindEntity(passengerEntityId); + if (!entity || !passenger) return 0; + passenger->ride(entity); + PlayerList *list = MinecraftServer::getPlayerList(); + if (list) + list->broadcastAll(std::make_shared(SetEntityLinkPacket::RIDING, passenger, entity), entity->dimension); + return 1; +} + +int __cdecl NativeLeaveVehicle(int entityId) +{ + auto entity = FindEntity(entityId); + if (!entity || !entity->riding) return 0; + int dim = entity->riding->dimension; + entity->ride(nullptr); + PlayerList *list = MinecraftServer::getPlayerList(); + if (list) + list->broadcastAll(std::make_shared(SetEntityLinkPacket::RIDING, entity, nullptr), dim); + return 1; +} + +int __cdecl NativeEject(int entityId) +{ + auto entity = FindEntity(entityId); + if (!entity) return 0; + auto riderPtr = entity->rider.lock(); + if (!riderPtr) return 0; + riderPtr->ride(nullptr); + PlayerList *list = MinecraftServer::getPlayerList(); + if (list) + list->broadcastAll(std::make_shared(SetEntityLinkPacket::RIDING, riderPtr, nullptr), entity->dimension); + return 1; +} + +int __cdecl NativeGetVehicleId(int entityId) +{ + auto entity = FindEntity(entityId); + if (!entity || !entity->riding) return -1; + return entity->riding->entityId; +} + +int __cdecl NativeGetPassengerId(int entityId) +{ + auto entity = FindEntity(entityId); + if (!entity) return -1; + auto riderPtr = entity->rider.lock(); + if (!riderPtr) return -1; + return riderPtr->entityId; +} + +void __cdecl NativeGetEntityInfo(int entityId, double *outData) +{ + outData[0] = -1; + outData[1] = 0; + outData[2] = 0; + outData[3] = 0; + outData[4] = 0; + auto entity = FindEntity(entityId); + if (!entity) return; + outData[0] = (double)MapEntityType((int)entity->GetType()); + outData[1] = entity->x; + outData[2] = entity->y; + outData[3] = entity->z; + outData[4] = (double)entity->dimension; +} + +} // namespace FourKitBridge diff --git a/Minecraft.Server/FourKitNatives.h b/Minecraft.Server/FourKitNatives.h new file mode 100644 index 00000000..a3a00524 --- /dev/null +++ b/Minecraft.Server/FourKitNatives.h @@ -0,0 +1,77 @@ +#pragma once + + +namespace FourKitBridge +{ + // core + void __cdecl NativeDamagePlayer(int entityId, float amount); + void __cdecl NativeSetPlayerHealth(int entityId, float health); + void __cdecl NativeTeleportPlayer(int entityId, double x, double y, double z); + void __cdecl NativeSetPlayerGameMode(int entityId, int gameMode); + void __cdecl NativeBroadcastMessage(const char *utf8, int len); + void __cdecl NativeSetFallDistance(int entityId, float distance); + void __cdecl NativeGetPlayerSnapshot(int entityId, double *outData); + void __cdecl NativeSendMessage(int entityId, const char *utf8, int len); + void __cdecl NativeSetWalkSpeed(int entityId, float speed); + void __cdecl NativeTeleportEntity(int entityId, int dimId, double x, double y, double z); + + // World + int __cdecl NativeGetTileId(int dimId, int x, int y, int z); + int __cdecl NativeGetTileData(int dimId, int x, int y, int z); + void __cdecl NativeSetTile(int dimId, int x, int y, int z, int tileId, int data); + void __cdecl NativeSetTileData(int dimId, int x, int y, int z, int data); + int __cdecl NativeBreakBlock(int dimId, int x, int y, int z); + int __cdecl NativeGetHighestBlockY(int dimId, int x, int z); + void __cdecl NativeGetWorldInfo(int dimId, double *outBuf); + void __cdecl NativeSetWorldTime(int dimId, int64_t time); + void __cdecl NativeSetWeather(int dimId, int storm, int thundering, int thunderDuration); + int __cdecl NativeCreateExplosion(int dimId, double x, double y, double z, float power, int setFire, int breakBlocks); + int __cdecl NativeStrikeLightning(int dimId, double x, double y, double z, int effectOnly); + int __cdecl NativeSetSpawnLocation(int dimId, int x, int y, int z); + void __cdecl NativeDropItem(int dimId, double x, double y, double z, int itemId, int count, int auxValue, int naturally); + + // plr + void __cdecl NativeKickPlayer(int entityId, int reason); + int __cdecl NativeBanPlayer(int entityId, const char *reasonUtf8, int reasonByteLen); + int __cdecl NativeBanPlayerIp(int entityId, const char *reasonUtf8, int reasonByteLen); + int __cdecl NativeGetPlayerAddress(int entityId, char *outIpBuf, int outIpBufSize, int *outPort); + + // inv + void __cdecl NativeGetPlayerInventory(int entityId, int *outData); + void __cdecl NativeSetPlayerInventorySlot(int entityId, int slot, int itemId, int count, int aux); + void __cdecl NativeGetContainerContents(int entityId, int *outData, int maxSlots); + void __cdecl NativeSetContainerSlot(int entityId, int slot, int itemId, int count, int aux); + void __cdecl NativeGetContainerViewerEntityIds(int entityId, int *outIds, int maxCount, int *outCount); + void __cdecl NativeCloseContainer(int entityId); + void __cdecl NativeOpenVirtualContainer(int entityId, int nativeType, const char *titleUtf8, int titleByteLen, int slotCount, int *itemsBuf); + int __cdecl NativeGetItemMeta(int entityId, int slot, char *outBuf, int bufSize); + void __cdecl NativeSetItemMeta(int entityId, int slot, const char *inBuf, int bufSize); + void __cdecl NativeSetHeldItemSlot(int entityId, int slot); + + // ent + void __cdecl NativeSetSneaking(int entityId, int sneak); + void __cdecl NativeSetVelocity(int entityId, double x, double y, double z); + void __cdecl NativeSetAllowFlight(int entityId, int allowFlight); + void __cdecl NativePlaySound(int entityId, int soundId, double x, double y, double z, float volume, float pitch); + void __cdecl NativeSetSleepingIgnored(int entityId, int ignored); + + // x[p&food + void __cdecl NativeSetLevel(int entityId, int level); + void __cdecl NativeSetExp(int entityId, float exp); + void __cdecl NativeGiveExp(int entityId, int amount); + void __cdecl NativeGiveExpLevels(int entityId, int amount); + void __cdecl NativeSetFoodLevel(int entityId, int foodLevel); + void __cdecl NativeSetSaturation(int entityId, float saturation); + void __cdecl NativeSetExhaustion(int entityId, float exhaustion); + + // particle + void __cdecl NativeSpawnParticle(int entityId, int particleId, float x, float y, float z, float offsetX, float offsetY, float offsetZ, float speed, int count); + + // vehicle + int __cdecl NativeSetPassenger(int entityId, int passengerEntityId); + int __cdecl NativeLeaveVehicle(int entityId); + int __cdecl NativeEject(int entityId); + int __cdecl NativeGetVehicleId(int entityId); + int __cdecl NativeGetPassengerId(int entityId); + void __cdecl NativeGetEntityInfo(int entityId, double *outData); +} diff --git a/Minecraft.Server/FourKitRuntime.cpp b/Minecraft.Server/FourKitRuntime.cpp new file mode 100644 index 00000000..b5e2bb14 --- /dev/null +++ b/Minecraft.Server/FourKitRuntime.cpp @@ -0,0 +1,241 @@ +#include "FourKitRuntime.h" +#include "ServerLogger.h" +#include "stdafx.h" + +#include +#include +#include + +using ServerRuntime::LogError; + +typedef void *hostfxr_handle; + +typedef int(__cdecl *hostfxr_initialize_for_runtime_config_fn)( + const wchar_t *runtime_config_path, + const void *parameters, + hostfxr_handle *host_context_handle); + +enum hostfxr_delegate_type +{ + hdt_com_activation = 0, + hdt_load_in_memory_assembly = 1, + hdt_winrt_activation = 2, + hdt_com_register = 3, + hdt_com_unregister = 4, + hdt_load_assembly_and_get_function_pointer = 5, + hdt_get_function_pointer = 6, +}; + +typedef int(__cdecl *hostfxr_get_runtime_delegate_fn)( + const hostfxr_handle host_context_handle, + hostfxr_delegate_type type, + void **delegate); + +typedef int(__cdecl *hostfxr_close_fn)(const hostfxr_handle host_context_handle); + +struct hostfxr_initialize_parameters +{ + size_t size; + const wchar_t *host_path; + const wchar_t *dotnet_root; +}; + +namespace +{ +static hostfxr_initialize_for_runtime_config_fn s_initFn = nullptr; +static hostfxr_get_runtime_delegate_fn s_getDelegateFn = nullptr; +static hostfxr_close_fn s_closeFn = nullptr; +static std::wstring s_dotnetRoot; + +static std::wstring FindNet10SystemRoot() +{ + std::vector candidates; + wchar_t envRoot[MAX_PATH] = {}; + DWORD len = GetEnvironmentVariableW(L"DOTNET_ROOT", envRoot, MAX_PATH); + if (len > 0 && len < MAX_PATH) + { + candidates.push_back(std::wstring(envRoot)); + } + candidates.push_back(L"C:\\Program Files\\dotnet"); + + for (const auto &root : candidates) + { + std::wstring fxrDir = root + L"\\host\\fxr"; + WIN32_FIND_DATAW fd; + HANDLE h = FindFirstFileW((fxrDir + L"\\*").c_str(), &fd); + if (h == INVALID_HANDLE_VALUE) + { + continue; + } + bool has10 = false; + do + { + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && fd.cFileName[0] != L'.') + { + if (std::wstring(fd.cFileName).substr(0, 3) == L"10.") + { + has10 = true; + } + } + } while (!has10 && FindNextFileW(h, &fd)); + FindClose(h); + if (has10) + { + return root; + } + } + + return L"C:\\Program Files\\dotnet"; +} + +static bool TryLoadHostfxrFromPath(const std::wstring &path) +{ + HMODULE lib = LoadLibraryW(path.c_str()); + if (!lib) + { + return false; + } + + s_initFn = (hostfxr_initialize_for_runtime_config_fn)GetProcAddress(lib, "hostfxr_initialize_for_runtime_config"); + s_getDelegateFn = (hostfxr_get_runtime_delegate_fn)GetProcAddress(lib, "hostfxr_get_runtime_delegate"); + s_closeFn = (hostfxr_close_fn)GetProcAddress(lib, "hostfxr_close"); + + if (s_initFn && s_getDelegateFn && s_closeFn) + { + return true; + } + + s_initFn = nullptr; + s_getDelegateFn = nullptr; + s_closeFn = nullptr; + FreeLibrary(lib); + return false; +} + +static bool LoadHostfxr() +{ + wchar_t exePath[MAX_PATH] = {}; + GetModuleFileNameW(NULL, exePath, MAX_PATH); + std::wstring exeDir(exePath); + size_t lastSlash = exeDir.find_last_of(L"\\/"); + if (lastSlash != std::wstring::npos) + { + exeDir = exeDir.substr(0, lastSlash); + } + + if (TryLoadHostfxrFromPath(exeDir + L"\\hostfxr.dll")) + { + s_dotnetRoot = FindNet10SystemRoot(); + return true; + } + + wchar_t dotnetRoot[MAX_PATH] = {}; + DWORD len = GetEnvironmentVariableW(L"DOTNET_ROOT", dotnetRoot, MAX_PATH); + if (len == 0 || len >= MAX_PATH) + { + wcscpy_s(dotnetRoot, L"C:\\Program Files\\dotnet"); + } + + std::wstring hostfxrDir = std::wstring(dotnetRoot) + L"\\host\\fxr"; + + WIN32_FIND_DATAW fd; + HANDLE hFind = FindFirstFileW((hostfxrDir + L"\\*").c_str(), &fd); + if (hFind != INVALID_HANDLE_VALUE) + { + std::wstring bestVersion; + do + { + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && fd.cFileName[0] != L'.') + { + std::wstring ver(fd.cFileName); + if (ver.substr(0, 3) == L"10." && ver > bestVersion) + { + bestVersion = ver; + } + } + } while (FindNextFileW(hFind, &fd)); + FindClose(hFind); + + if (!bestVersion.empty()) + { + if (TryLoadHostfxrFromPath(hostfxrDir + L"\\" + bestVersion + L"\\hostfxr.dll")) + { + s_dotnetRoot = std::wstring(dotnetRoot); + return true; + } + } + } + + LogError("fourkit", "hostfxr.dll not found. Install the .NET 10 x64 runtime (https://aka.ms/dotnet/download) or copy hostfxr.dll from C:\\Program Files\\dotnet\\host\\fxr\\10.x.x\\ next to the server executable."); + return false; +} +} + +namespace FourKitBridge +{ + bool LoadManagedRuntime(const wchar_t *runtimeConfigPath, + const wchar_t *hostPath, + load_assembly_fn *outLoadAssembly) + { + if (!LoadHostfxr()) + { + return false; + } + + hostfxr_initialize_parameters initParams = {}; + initParams.size = sizeof(hostfxr_initialize_parameters); + initParams.host_path = hostPath; + initParams.dotnet_root = s_dotnetRoot.c_str(); + + hostfxr_handle ctx = nullptr; + int rc = s_initFn(runtimeConfigPath, &initParams, &ctx); + if (rc != 0 || ctx == nullptr) + { + char msg[256]; + sprintf_s(msg, "hostfxr_initialize_for_runtime_config failed (0x%08X). Check runtimeconfig.json path.", rc); + LogError("fourkit", msg); + if (ctx) + { + s_closeFn(ctx); + } + return false; + } + + load_assembly_fn loadAssembly = nullptr; + rc = s_getDelegateFn(ctx, hdt_load_assembly_and_get_function_pointer, (void **)&loadAssembly); + s_closeFn(ctx); + + if (rc != 0 || loadAssembly == nullptr) + { + LogError("fourkit", "Failed to get load_assembly_and_get_function_pointer delegate."); + return false; + } + + *outLoadAssembly = loadAssembly; + return true; + } + + bool GetManagedEntryPoint(load_assembly_fn loadAssembly, + const wchar_t *assemblyPath, + const wchar_t *typeName, + const wchar_t *methodName, + void **outFnPtr) + { + int rc = loadAssembly( + assemblyPath, + typeName, + methodName, + UNMANAGEDCALLERSONLY_METHOD, + nullptr, + outFnPtr); + + if (rc != 0 || *outFnPtr == nullptr) + { + char methodNarrow[256]; + sprintf_s(methodNarrow, "%S::%S", typeName, methodName); + LogError("fourkit", (std::string("Failed to resolve managed entry point: ") + methodNarrow).c_str()); + return false; + } + return true; + } +} diff --git a/Minecraft.Server/FourKitRuntime.h b/Minecraft.Server/FourKitRuntime.h new file mode 100644 index 00000000..a7113b0c --- /dev/null +++ b/Minecraft.Server/FourKitRuntime.h @@ -0,0 +1,24 @@ +#pragma once + +#define UNMANAGEDCALLERSONLY_METHOD ((const wchar_t *)-1) + +namespace FourKitBridge +{ +typedef int(__stdcall *load_assembly_fn)( + const wchar_t *assembly_path, + const wchar_t *type_name, + const wchar_t *method_name, + const wchar_t *delegate_type_name, + void *reserved, + void **delegate); + +bool LoadManagedRuntime(const wchar_t *runtimeConfigPath, + const wchar_t *hostPath, + load_assembly_fn *outLoadAssembly); + +bool GetManagedEntryPoint(load_assembly_fn loadAssembly, + const wchar_t *assemblyPath, + const wchar_t *typeName, + const wchar_t *methodName, + void **outFnPtr); +} diff --git a/Minecraft.Server/cmake/sources/Common.cmake b/Minecraft.Server/cmake/sources/Common.cmake index 89736ac7..b6c4577a 100644 --- a/Minecraft.Server/cmake/sources/Common.cmake +++ b/Minecraft.Server/cmake/sources/Common.cmake @@ -522,6 +522,11 @@ set(_MINECRAFT_SERVER_COMMON_SERVER "${CMAKE_CURRENT_SOURCE_DIR}/WorldManager.h" "${CMAKE_CURRENT_SOURCE_DIR}/FourKitBridge.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/FourKitBridge.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FourKitMappers.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FourKitNatives.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FourKitNatives.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FourKitRuntime.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FourKitRuntime.h" ) source_group("Server" FILES ${_MINECRAFT_SERVER_COMMON_SERVER})