MinecraftConsoles/Minecraft.Server/FourKitNatives.cpp
2026-03-24 21:13:15 -05:00

1141 lines
34 KiB
C++

#include "FourKitNatives.h"
#include "FourKitBridge.h"
#include "Common/StringUtils.h"
#include "stdafx.h"
#include <string>
#include <vector>
#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<ServerPlayer> 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<Entity> 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> 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<ChatPacket>(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<ChatPacket>(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<PlayerAbilitiesPacket>(&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> 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<Entity> lightning = std::shared_ptr<Entity>(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<Packet>(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<ItemInstance>(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<ItemEntity>(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<DisconnectPacket::eDisconnectReason>(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<PlayerUID> 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<std::shared_ptr<ServerPlayer>> 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<ItemInstance>(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<ItemInstance>(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<CompoundContainer *>(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<CompoundContainer *>(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<VirtualContainer>(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<ItemInstance>(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<StringTag> *lore = (ListTag<StringTag> *)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<StringTag>(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<SetCarriedItemPacket>(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<PlayerAbilitiesPacket>(&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<LevelSoundPacket>(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<SetExperiencePacket>(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<SetExperiencePacket>(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<SetExperiencePacket>(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<SetExperiencePacket>(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<SetHealthPacket>(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<SetHealthPacket>(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<LevelParticlesPacket>(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>(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>(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>(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