This commit is contained in:
sylvessa 2026-03-24 21:09:25 -05:00
parent 98be92ed0c
commit def50adfcc
11 changed files with 2964 additions and 3021 deletions

View file

@ -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<TileUpdatePacket>(x, y, z, level));
return false;
}

View file

@ -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}");
}
}
}

View file

@ -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<ItemStack>();
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<ItemStack>();
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}");
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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<EntityDamageSource *>(source) != nullptr)
{
return ENTITY_ATTACK;
}
return CUSTOM;
}
}

File diff suppressed because it is too large Load diff

View file

@ -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);
}

View file

@ -0,0 +1,241 @@
#include "FourKitRuntime.h"
#include "ServerLogger.h"
#include "stdafx.h"
#include <string>
#include <vector>
#include <windows.h>
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<std::wstring> 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;
}
}

View file

@ -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);
}

View file

@ -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})