From 91b189e1bd74ad7e6954fe02d8000a491ea64bb8 Mon Sep 17 00:00:00 2001 From: sylvessa <225480449+sylvessa@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:00:36 -0500 Subject: [PATCH] Add itemmeta, fix some bugs regarding inventory syncing and missing impl (oops) --- Minecraft.Server.FourKit/FourKitHost.cs | 4 +- .../Inventory/ItemStack.cs | 30 +++ .../Inventory/Meta/ItemMeta.cs | 64 ++++++ .../Inventory/PlayerInventory.cs | 145 +++++++++++- Minecraft.Server.FourKit/NativeBridge.cs | 17 +- Minecraft.Server/FourKitBridge.cpp | 214 +++++++++++++++++- 6 files changed, 467 insertions(+), 7 deletions(-) create mode 100644 Minecraft.Server.FourKit/Inventory/Meta/ItemMeta.cs diff --git a/Minecraft.Server.FourKit/FourKitHost.cs b/Minecraft.Server.FourKit/FourKitHost.cs index 2e3190e74..7a4e48771 100644 --- a/Minecraft.Server.FourKit/FourKitHost.cs +++ b/Minecraft.Server.FourKit/FourKitHost.cs @@ -581,11 +581,11 @@ public static class FourKitHost } [UnmanagedCallersOnly] - public static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer) + 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); + NativeBridge.SetInventoryCallbacks(getPlayerInventory, setPlayerInventorySlot, getContainerContents, setContainerSlot, getContainerViewerEntityIds, closeContainer, openVirtualContainer, getItemMeta, setItemMeta, setHeldItemSlot); //ServerLog.Info("fourkit", "Inventory native callbacks registered."); } catch (Exception ex) diff --git a/Minecraft.Server.FourKit/Inventory/ItemStack.cs b/Minecraft.Server.FourKit/Inventory/ItemStack.cs index 394ab637a..5b69ed231 100644 --- a/Minecraft.Server.FourKit/Inventory/ItemStack.cs +++ b/Minecraft.Server.FourKit/Inventory/ItemStack.cs @@ -1,5 +1,7 @@ namespace Minecraft.Server.FourKit.Inventory; +using Minecraft.Server.FourKit.Inventory.Meta; + /// /// Represents a stack of items. /// @@ -8,6 +10,7 @@ public class ItemStack private Material _type; private int _amount; private short _durability; + private ItemMeta? _meta; /// /// Creates a new ItemStack of the specified material with amount 1. @@ -99,4 +102,31 @@ public class ItemStack /// /// Durability of this item. public void setDurability(short durability) => _durability = durability; + + /// + /// Get a copy of this ItemStack's ItemMeta. + /// + /// A copy of the current ItemStack's ItemMeta. + public ItemMeta getItemMeta() => _meta?.clone() ?? new ItemMeta(); + + /// + /// Checks to see if any meta data has been defined. + /// + /// Returns true if some meta data has been set for this item. + public bool hasItemMeta() => _meta != null && !_meta.isEmpty(); + + /// + /// Set the ItemMeta of this ItemStack. + /// + /// New ItemMeta, or null to indicate meta data be cleared. + /// True if successfully applied ItemMeta. + public bool setItemMeta(ItemMeta? itemMeta) + { + _meta = itemMeta?.clone(); + return true; + } + + internal ItemMeta? getItemMetaInternal() => _meta; + + internal void setItemMetaInternal(ItemMeta? meta) => _meta = meta; } diff --git a/Minecraft.Server.FourKit/Inventory/Meta/ItemMeta.cs b/Minecraft.Server.FourKit/Inventory/Meta/ItemMeta.cs new file mode 100644 index 000000000..5da46c5ac --- /dev/null +++ b/Minecraft.Server.FourKit/Inventory/Meta/ItemMeta.cs @@ -0,0 +1,64 @@ +namespace Minecraft.Server.FourKit.Inventory.Meta; + +/// +/// Represents the metadata of an , including display name and lore. +/// +public class ItemMeta +{ + private string? _displayName; + private List? _lore; + + /// + /// Checks for existence of a display name. + /// + /// true if this has a display name. + public bool hasDisplayName() => _displayName != null; + + /// + /// Gets the display name that is set. + /// Plugins should check that hasDisplayName() returns true before calling this method. + /// + /// The display name that is set. + public string getDisplayName() => _displayName ?? string.Empty; + + /// + /// Sets the display name. + /// + /// The name to set. + public void setDisplayName(string? name) => _displayName = name; + + /// + /// Checks for existence of lore. + /// + /// true if this has lore. + public bool hasLore() => _lore != null && _lore.Count > 0; + + /// + /// Gets the lore that is set. + /// Plugins should check if hasLore() returns true before calling this method. + /// + /// A list of lore that is set. + public List getLore() => _lore != null ? new List(_lore) : new List(); + + /// + /// Sets the lore for this item. Removes lore when given null. + /// + /// The lore that will be set. + public void setLore(List? lore) + { + _lore = lore != null ? new List(lore) : null; + } + + public ItemMeta clone() + { + var copy = new ItemMeta(); + copy._displayName = _displayName; + copy._lore = _lore != null ? new List(_lore) : null; + return copy; + } + + internal bool isEmpty() + { + return _displayName == null && (_lore == null || _lore.Count == 0); + } +} diff --git a/Minecraft.Server.FourKit/Inventory/PlayerInventory.cs b/Minecraft.Server.FourKit/Inventory/PlayerInventory.cs index 7a6d950c7..40f095401 100644 --- a/Minecraft.Server.FourKit/Inventory/PlayerInventory.cs +++ b/Minecraft.Server.FourKit/Inventory/PlayerInventory.cs @@ -1,7 +1,9 @@ namespace Minecraft.Server.FourKit.Inventory; using System.Runtime.InteropServices; +using System.Text; using Minecraft.Server.FourKit.Entity; +using Minecraft.Server.FourKit.Inventory.Meta; /// /// Represents a player's inventory, including armor slots and the held item. @@ -43,9 +45,17 @@ public class PlayerInventory : Inventory int count = buf[i * 3 + 1]; int aux = buf[i * 3 + 2]; if (id > 0 && count > 0) - _items[i] = new ItemStack(id, count, (short)aux); + { + var stack = new ItemStack(id, count, (short)aux); + var meta = ReadMetaFromNative(entityId, i); + if (meta != null) + stack.setItemMetaInternal(meta); + _items[i] = stack; + } else + { _items[i] = null; + } } _heldItemSlot = buf[120]; } @@ -62,6 +72,7 @@ public class PlayerInventory : Inventory int count = item?.getAmount() ?? 0; int aux = item?.getDurability() ?? 0; NativeBridge.SetPlayerInventorySlot(_holder.getEntityId(), index, id, count, aux); + WriteMetaToNative(_holder.getEntityId(), index, item?.getItemMetaInternal()); } } @@ -147,7 +158,11 @@ public class PlayerInventory : Inventory /// Sets the item in the player's hand. /// /// The ItemStack to set. - public void setItemInHand(ItemStack? stack) => setItem(_heldItemSlot, stack); + public void setItemInHand(ItemStack? stack) + { + EnsureSynced(); + setItem(_heldItemSlot, stack); + } /// /// Gets the slot number of the currently held item. @@ -168,6 +183,8 @@ public class PlayerInventory : Inventory if (slot < 0 || slot >= QUICKBAR_SIZE) throw new ArgumentException($"Slot must be between 0 and {QUICKBAR_SIZE - 1} inclusive."); _heldItemSlot = slot; + if (_holder != null) + NativeBridge.SetHeldItemSlot?.Invoke(_holder.getEntityId(), slot); } /// @@ -199,4 +216,128 @@ public class PlayerInventory : Inventory /// /// The HumanEntity that owns this inventory. public HumanEntity? getHolder() => _holder; + + private static ItemMeta? ReadMetaFromNative(int entityId, int slot) + { + if (NativeBridge.GetItemMeta == null) + return null; + + byte[] buf = new byte[4096]; + int bytesWritten; + var gh = GCHandle.Alloc(buf, GCHandleType.Pinned); + try + { + bytesWritten = NativeBridge.GetItemMeta(entityId, slot, gh.AddrOfPinnedObject(), buf.Length); + } + finally + { + gh.Free(); + } + + if (bytesWritten <= 0) + return null; + + int offset = 0; + int nameLen = BitConverter.ToInt32(buf, offset); + offset += 4; + + string? displayName = null; + if (nameLen > 0) + { + displayName = Encoding.UTF8.GetString(buf, offset, nameLen); + offset += nameLen; + } + + int loreCount = 0; + if (offset + 4 <= bytesWritten) + { + loreCount = BitConverter.ToInt32(buf, offset); + offset += 4; + } + + List? lore = null; + if (loreCount > 0) + { + lore = new List(loreCount); + for (int i = 0; i < loreCount; i++) + { + if (offset + 4 > bytesWritten) break; + int lineLen = BitConverter.ToInt32(buf, offset); + offset += 4; + if (lineLen > 0 && offset + lineLen <= bytesWritten) + { + lore.Add(Encoding.UTF8.GetString(buf, offset, lineLen)); + offset += lineLen; + } + else + { + lore.Add(string.Empty); + } + } + } + + if (displayName == null && (lore == null || lore.Count == 0)) + return null; + + var meta = new ItemMeta(); + if (displayName != null) + meta.setDisplayName(displayName); + if (lore != null && lore.Count > 0) + meta.setLore(lore); + return meta; + } + + private static void WriteMetaToNative(int entityId, int slot, ItemMeta? meta) + { + if (NativeBridge.SetItemMeta == null) + return; + + if (meta == null || meta.isEmpty()) + { + NativeBridge.SetItemMeta(entityId, slot, IntPtr.Zero, 0); + return; + } + + using var ms = new System.IO.MemoryStream(512); + using var bw = new System.IO.BinaryWriter(ms); + + if (meta.hasDisplayName()) + { + byte[] nameBytes = Encoding.UTF8.GetBytes(meta.getDisplayName()); + bw.Write(nameBytes.Length); + bw.Write(nameBytes); + } + else + { + bw.Write(0); + } + + if (meta.hasLore()) + { + var lore = meta.getLore(); + bw.Write(lore.Count); + foreach (var line in lore) + { + byte[] lineBytes = Encoding.UTF8.GetBytes(line ?? string.Empty); + bw.Write(lineBytes.Length); + bw.Write(lineBytes); + } + } + else + { + bw.Write(0); + } + + bw.Flush(); + byte[] data = ms.ToArray(); + var gh = GCHandle.Alloc(data, GCHandleType.Pinned); + try + { + NativeBridge.SetItemMeta(entityId, slot, gh.AddrOfPinnedObject(), data.Length); + } + finally + { + gh.Free(); + } + } } diff --git a/Minecraft.Server.FourKit/NativeBridge.cs b/Minecraft.Server.FourKit/NativeBridge.cs index 8dee54294..ddad6c2f8 100644 --- a/Minecraft.Server.FourKit/NativeBridge.cs +++ b/Minecraft.Server.FourKit/NativeBridge.cs @@ -108,6 +108,15 @@ internal static class NativeBridge [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void NativeOpenVirtualContainerDelegate(int entityId, int nativeType, IntPtr titleUtf8, int titleByteLen, int slotCount, IntPtr itemsBuf); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int NativeGetItemMetaDelegate(int entityId, int slot, IntPtr outBuf, int bufSize); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void NativeSetItemMetaDelegate(int entityId, int slot, IntPtr inBuf, int bufSize); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void NativeSetHeldItemSlotDelegate(int entityId, int slot); + internal static NativeDamageDelegate? DamagePlayer; internal static NativeSetHealthDelegate? SetPlayerHealth; @@ -144,6 +153,9 @@ internal static class NativeBridge internal static NativeGetContainerViewerEntityIdsDelegate? GetContainerViewerEntityIds; internal static NativeCloseContainerDelegate? CloseContainer; internal static NativeOpenVirtualContainerDelegate? OpenVirtualContainer; + internal static NativeGetItemMetaDelegate? GetItemMeta; + internal static NativeSetItemMetaDelegate? SetItemMeta; + internal static NativeSetHeldItemSlotDelegate? SetHeldItemSlot; internal static void SetCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity) { @@ -184,7 +196,7 @@ internal static class NativeBridge GetPlayerAddress = Marshal.GetDelegateForFunctionPointer(getPlayerAddress); } - internal static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer) + internal static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot) { GetPlayerInventory = Marshal.GetDelegateForFunctionPointer(getPlayerInventory); SetPlayerInventorySlot = Marshal.GetDelegateForFunctionPointer(setPlayerInventorySlot); @@ -193,5 +205,8 @@ internal static class NativeBridge GetContainerViewerEntityIds = Marshal.GetDelegateForFunctionPointer(getContainerViewerEntityIds); CloseContainer = Marshal.GetDelegateForFunctionPointer(closeContainer); OpenVirtualContainer = Marshal.GetDelegateForFunctionPointer(openVirtualContainer); + GetItemMeta = Marshal.GetDelegateForFunctionPointer(getItemMeta); + SetItemMeta = Marshal.GetDelegateForFunctionPointer(setItemMeta); + SetHeldItemSlot = Marshal.GetDelegateForFunctionPointer(setHeldItemSlot); } } diff --git a/Minecraft.Server/FourKitBridge.cpp b/Minecraft.Server/FourKitBridge.cpp index dddecdd0d..34ba09ed6 100644 --- a/Minecraft.Server/FourKitBridge.cpp +++ b/Minecraft.Server/FourKitBridge.cpp @@ -35,6 +35,7 @@ #include "..\Minecraft.World\LightningBolt.h" #include "..\Minecraft.World\Player.h" #include "..\Minecraft.World\PlayerAbilitiesPacket.h" +#include "..\Minecraft.World\SetCarriedItemPacket.h" #include "..\Minecraft.World\SimpleContainer.h" #include "..\Minecraft.World\Slot.h" #include "..\Minecraft.World\Tile.h" @@ -141,7 +142,7 @@ typedef void(__stdcall *fn_set_player_callbacks)(void *kickPlayer, void *banPlay typedef long long(__stdcall *fn_fire_player_drop_item)(int entityId, int itemId, int itemCount, int itemAux, int *outItemId, int *outItemCount, int *outItemAux); -typedef void(__stdcall *fn_set_inventory_callbacks)(void *getPlayerInventory, void *setPlayerInventorySlot, void *getContainerContents, void *setContainerSlot, void *getContainerViewerEntityIds, void *closeContainer, void *openVirtualContainer); +typedef void(__stdcall *fn_set_inventory_callbacks)(void *getPlayerInventory, void *setPlayerInventorySlot, void *getContainerContents, void *setContainerSlot, void *getContainerViewerEntityIds, void *closeContainer, void *openVirtualContainer, void *getItemMeta, void *setItemMeta, void *setHeldItemSlot); typedef int(__stdcall *fn_fire_player_interact)(int entityId, int action, int itemId, int itemCount, int itemAux, int clickedX, int clickedY, int clickedZ, @@ -1069,6 +1070,212 @@ static void __cdecl NativeGetContainerViewerEntityIds(int entityId, int *outIds, *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 std::wstring FindNet10SystemRoot() { // overengineered @@ -1362,7 +1569,10 @@ void Initialize() (void *)&NativeSetContainerSlot, (void *)&NativeGetContainerViewerEntityIds, (void *)&NativeCloseContainer, - (void *)&NativeOpenVirtualContainer); + (void *)&NativeOpenVirtualContainer, + (void *)&NativeGetItemMeta, + (void *)&NativeSetItemMeta, + (void *)&NativeSetHeldItemSlot); LogInfo("fourkit", "FourKit initialized successfully."); }