// todo: split into files for better readability #include "FourKitBridge.h" #include "Common/StringUtils.h" #include "ServerLogger.h" #include "stdafx.h" #include #include #include #include "..\Minecraft.Client\MinecraftServer.h" #include "..\Minecraft.Client\PlayerConnection.h" #include "..\Minecraft.Client\PlayerList.h" #include "..\Minecraft.Client\ServerConnection.h" #include "..\Minecraft.Client\ServerLevel.h" #include "..\Minecraft.Client\ServerPlayer.h" #include "..\Minecraft.Client\ServerPlayerGameMode.h" #include "..\Minecraft.Client\Windows64\Network\WinsockNetLayer.h" #include "..\Minecraft.World\AbstractContainerMenu.h" #include "..\Minecraft.World\AddGlobalEntityPacket.h" #include "..\Minecraft.World\ArrayWithLength.h" #include "..\Minecraft.World\Class.h" #include "..\Minecraft.World\CompoundContainer.h" #include "..\Minecraft.World\Connection.h" #include "..\Minecraft.World\ContainerOpenPacket.h" #include "..\Minecraft.World\DamageSource.h" #include "..\Minecraft.World\EntityDamageSource.h" #include "..\Minecraft.World\Explosion.h" #include "..\Minecraft.World\IndirectEntityDamageSource.h" #include "..\Minecraft.World\ItemEntity.h" #include "..\Minecraft.World\ItemInstance.h" #include "..\Minecraft.World\LevelData.h" #include "..\Minecraft.World\LevelSettings.h" #include "..\Minecraft.World\LightningBolt.h" #include "..\Minecraft.World\Player.h" #include "..\Minecraft.World\PlayerAbilitiesPacket.h" #include "..\Minecraft.World\SetCarriedItemPacket.h" #include "..\Minecraft.World\SimpleContainer.h" #include "..\Minecraft.World\Slot.h" #include "..\Minecraft.World\Tile.h" #include "..\Minecraft.World\net.minecraft.world.entity.player.h" #include "Access\Access.h" #include "Common\NetworkUtils.h" #include "ServerLogManager.h" // gay typedef void *hostfxr_handle; typedef int(__cdecl *hostfxr_initialize_for_runtime_config_fn)( const wchar_t *runtime_config_path, const void *parameters, hostfxr_handle *host_context_handle); enum hostfxr_delegate_type { hdt_com_activation = 0, hdt_load_in_memory_assembly = 1, hdt_winrt_activation = 2, hdt_com_register = 3, hdt_com_unregister = 4, hdt_load_assembly_and_get_function_pointer = 5, hdt_get_function_pointer = 6, }; typedef int(__cdecl *hostfxr_get_runtime_delegate_fn)( const hostfxr_handle host_context_handle, hostfxr_delegate_type type, void **delegate); typedef int(__cdecl *hostfxr_close_fn)(const hostfxr_handle host_context_handle); struct hostfxr_initialize_parameters { size_t size; const wchar_t *host_path; const wchar_t *dotnet_root; }; #define UNMANAGEDCALLERSONLY_METHOD ((const wchar_t *)-1) typedef int(__stdcall *load_assembly_and_get_function_pointer_fn)( const wchar_t *assembly_path, const wchar_t *type_name, const wchar_t *method_name, const wchar_t *delegate_type_name, void *reserved, void **delegate); using ServerRuntime::LogDebugf; using ServerRuntime::LogError; using ServerRuntime::LogInfo; using ServerRuntime::LogInfof; using ServerRuntime::LogWarn; namespace FourKitBridge { static hostfxr_initialize_for_runtime_config_fn s_initFn = nullptr; static hostfxr_get_runtime_delegate_fn s_getDelegateFn = nullptr; static hostfxr_close_fn s_closeFn = nullptr; static std::wstring s_dotnetRoot; typedef void(__stdcall *fn_initialize)(); typedef void(__stdcall *fn_fire_player_join)(int entityId, const char *nameUtf8, int nameByteLen, const char *uuidUtf8, int uuidByteLen); typedef void(__stdcall *fn_fire_player_quit)(int entityId); typedef int(__stdcall *fn_fire_player_kick)(int entityId, int disconnectReason, const char *reasonUtf8, int reasonByteLen, char *outBuf, int outBufSize, int *outLen); typedef void(__stdcall *fn_shutdown)(); typedef int(__stdcall *fn_fire_player_move)(int entityId, double fromX, double fromY, double fromZ, double toX, double toY, double toZ, double *outCoords); typedef void(__stdcall *fn_set_native_callbacks)(void *damage, void *setHealth, void *teleport, void *setGameMode, void *broadcastMessage, void *setFallDistance, void *getPlayerSnapshot, void *sendMessage, void *setWalkSpeed, void *teleportEntity); typedef void(__stdcall *fn_set_world_callbacks)(void *getTileId, void *getTileData, void *setTile, void *setTileData, void *breakBlock, void *getHighestBlockY, void *getWorldInfo, void *setWorldTime, void *setWeather, void *createExplosion, void *strikeLightning, void *setSpawnLocation, void *dropItem); typedef void(__stdcall *fn_update_entity_id)(int oldEntityId, int newEntityId); typedef int(__stdcall *fn_fire_player_chat)(int entityId, const char *msgUtf8, int msgByteLen, char *outBuf, int outBufSize, int *outLen); typedef int(__stdcall *fn_fire_block_place)(int entityId, int dimId, int placedX, int placedY, int placedZ, int againstX, int againstY, int againstZ, int itemId, int itemCount, int canBuild); typedef int(__stdcall *fn_fire_block_break)(int entityId, int dimId, int x, int y, int z, int tileId, int data, int exp); typedef int(__stdcall *fn_fire_entity_damage)(int entityId, int entityTypeId, int dimId, double x, double y, double z, int causeId, double damage, double *outDamage, int damagerEntityId, int damagerEntityTypeId, double damagerX, double damagerY, double damagerZ); typedef int(__stdcall *fn_fire_sign_change)(int entityId, int dimId, int x, int y, int z, const char *line0, int line0Len, const char *line1, int line1Len, const char *line2, int line2Len, const char *line3, int line3Len, char *outBuf, int outBufSize, int *outLens); typedef int(__stdcall *fn_fire_entity_death)(int entityId, int entityTypeId, int dimId, double x, double y, double z, int exp); typedef int(__stdcall *fn_fire_player_death)(int entityId, const char *deathMsgUtf8, int deathMsgByteLen, int exp, char *outMsgBuf, int outMsgBufSize, int *outMsgLen, int *outKeepInventory, int *outNewExp, int *outNewLevel, int *outKeepLevel); typedef void(__stdcall *fn_set_player_callbacks)(void *kickPlayer, void *banPlayer, void *banPlayerIp, void *getPlayerAddress); 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, 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, int blockFace, int dimId, int *outUseItemInHand); typedef int(__stdcall *fn_fire_player_interact_entity)(int playerEntityId, int targetEntityId, int targetEntityTypeId, int dimId, double targetX, double targetY, double targetZ, float targetHealth, float targetMaxHealth, float targetEyeHeight); typedef int(__stdcall *fn_fire_player_pickup_item)(int playerEntityId, int itemEntityId, int dimId, double itemX, double itemY, double itemZ, int itemId, int itemCount, int itemAux, int remaining, int *outItemId, int *outItemCount, int *outItemAux); typedef int(__stdcall *fn_fire_inventory_open)(int entityId, int nativeContainerType, const char *titleUtf8, int titleByteLen, int containerSize); typedef int(__stdcall *fn_handle_player_command)(int entityId, const char *cmdUtf8, int cmdByteLen); typedef int(__stdcall *fn_handle_console_command)(const char *cmdUtf8, int cmdByteLen); typedef int(__stdcall *fn_get_plugin_command_help)(char *outBuf, int outBufSize, int *outLen); typedef int(__stdcall *fn_fire_player_teleport)(int entityId, double fromX, double fromY, double fromZ, int fromDimId, double toX, double toY, double toZ, int toDimId, int cause, double *outCoords); typedef int(__stdcall *fn_fire_player_portal)(int entityId, double fromX, double fromY, double fromZ, int fromDimId, double toX, double toY, double toZ, int toDimId, int cause, double *outCoords); typedef int(__stdcall *fn_fire_inventory_click)(int entityId, int slot, int button, int clickType, int nativeContainerType, int containerSize, const char *titleUtf8, int titleByteLen); typedef int(__stdcall *fn_fire_bed_enter)(int entityId, int dimId, int bedX, int bedY, int bedZ); typedef void(__stdcall *fn_fire_bed_leave)(int entityId, int dimId, int bedX, int bedY, int bedZ); typedef void(__stdcall *fn_set_entity_callbacks)(void *setSneaking, void *setVelocity, void *setAllowFlight); struct OpenContainerInfo { int type; int size; std::wstring title; }; static std::unordered_map s_openContainerInfo; static fn_initialize s_managedInit = nullptr; static fn_fire_player_join s_managedFireJoin = nullptr; static fn_update_entity_id s_managedUpdateEntityId = nullptr; static fn_fire_player_quit s_managedFireQuit = nullptr; static fn_fire_player_kick s_managedFireKick = nullptr; static fn_shutdown s_managedShutdown = nullptr; static fn_fire_player_move s_managedFireMove = nullptr; static fn_set_native_callbacks s_managedSetCallbacks = nullptr; static fn_set_world_callbacks s_managedSetWorldCallbacks = nullptr; static fn_fire_player_chat s_managedFireChat = nullptr; static fn_fire_block_place s_managedFireBlockPlace = nullptr; static fn_fire_block_break s_managedFireBlockBreak = nullptr; static fn_fire_entity_damage s_managedFireEntityDamage = nullptr; static fn_fire_sign_change s_managedFireSignChange = nullptr; static fn_fire_entity_death s_managedFireEntityDeath = nullptr; static fn_fire_player_death s_managedFirePlayerDeath = nullptr; static fn_set_player_callbacks s_managedSetPlayerCallbacks = nullptr; static fn_fire_player_drop_item s_managedFirePlayerDropItem = nullptr; static fn_set_inventory_callbacks s_managedSetInventoryCallbacks = nullptr; static fn_fire_player_interact s_managedFirePlayerInteract = nullptr; static fn_fire_player_interact_entity s_managedFirePlayerInteractEntity = nullptr; static fn_fire_player_pickup_item s_managedFirePlayerPickupItem = nullptr; static fn_fire_inventory_open s_managedFireInventoryOpen = nullptr; static fn_handle_player_command s_managedHandlePlayerCommand = nullptr; static fn_handle_console_command s_managedHandleConsoleCommand = nullptr; static fn_get_plugin_command_help s_managedGetPluginCommandHelp = nullptr; static fn_fire_player_teleport s_managedFirePlayerTeleport = nullptr; static fn_fire_player_portal s_managedFirePlayerPortal = nullptr; static fn_fire_inventory_click s_managedFireInventoryClick = nullptr; static fn_fire_bed_enter s_managedFireBedEnter = nullptr; static fn_fire_bed_leave s_managedFireBedLeave = nullptr; static fn_set_entity_callbacks s_managedSetEntityCallbacks = nullptr; static bool s_initialized = false; static shared_ptr FindPlayer(int entityId) { PlayerList *list = MinecraftServer::getPlayerList(); if (!list) { return nullptr; } for (auto &p : list->players) { if (p && p->entityId == entityId) { return p; } } return nullptr; } static shared_ptr FindEntity(int entityId) { MinecraftServer *srv = MinecraftServer::getInstance(); if (!srv) { return nullptr; } const int dims[] = {0, -1, 1}; for (int dim : dims) { ServerLevel *level = srv->getLevel(dim); if (!level) { continue; } shared_ptr entity = level->getEntity(entityId); if (entity) { return entity; } } return nullptr; } static void __cdecl NativeDamagePlayer(int entityId, float amount) { // todo: move entity hurt to a seperate func (NativeDamageEntity) auto player = FindPlayer(entityId); if (player) { player->hurt(DamageSource::genericSource, amount); return; } auto entity = FindEntity(entityId); if (entity) { entity->hurt(DamageSource::genericSource, amount); } } static void __cdecl NativeSetPlayerHealth(int entityId, float health) { auto player = FindPlayer(entityId); if (player) { player->setHealth(health); } } static void __cdecl NativeTeleportPlayer(int entityId, double x, double y, double z) { auto player = FindPlayer(entityId); if (player && player->connection) { double outX, outY, outZ; bool cancelled = FirePlayerTeleport(entityId, player->x, player->y, player->z, player->dimension, x, y, z, player->dimension, 2 /* PLUGIN */, &outX, &outY, &outZ); if (!cancelled) { player->connection->teleport(outX, outY, outZ, player->yRot, player->xRot); } } } static void __cdecl NativeSetPlayerGameMode(int entityId, int gameMode) { auto player = FindPlayer(entityId); if (player && player->gameMode) { GameType *type = GameType::byId(gameMode); if (type) { player->setGameMode(type); } } } static void __cdecl NativeSetFallDistance(int entityId, float distance) { auto player = FindPlayer(entityId); if (player) { player->fallDistance = distance; } } // double[20] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension, isSleeping, sleepTimer, sneaking, sprinting, onGround, velocityX, velocityY, velocityZ, allowFlight } static void __cdecl NativeGetPlayerSnapshot(int entityId, double *outData) { auto player = FindPlayer(entityId); if (!player) { memset(outData, 0, 20 * sizeof(double)); outData[3] = 20.0; outData[4] = 20.0; outData[7] = 0.1; 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; } static void __cdecl NativeBroadcastMessage(const char *utf8, int len) { if (!utf8 || len <= 0) { return; } std::wstring wide = ServerRuntime::StringUtils::Utf8ToWide(utf8); if (wide.empty()) { return; } PlayerList *list = MinecraftServer::getPlayerList(); if (list) { list->broadcastAll(std::make_shared(wide)); } } static void __cdecl NativeSendMessage(int entityId, const char *utf8, int len) { if (!utf8 || len <= 0) { return; } auto player = FindPlayer(entityId); if (player && player->connection) { std::wstring wide = ServerRuntime::StringUtils::Utf8ToWide(utf8); if (!wide.empty()) { player->connection->send(std::make_shared(wide)); } } } static void __cdecl NativeSetWalkSpeed(int entityId, float speed) { auto player = FindPlayer(entityId); if (player) { player->abilities.setWalkingSpeed(speed); if (player->connection) { player->connection->send(std::make_shared(&player->abilities)); } } } static ServerLevel *GetLevel(int dimId) { MinecraftServer *srv = MinecraftServer::getInstance(); if (!srv) { return nullptr; } return srv->getLevel(dimId); } static void __cdecl NativeTeleportEntity(int entityId, int dimId, double x, double y, double z) { auto player = FindPlayer(entityId); if (player && player->connection) { player->connection->teleport(x, y, z, player->yRot, player->xRot); return; } ServerLevel *level = GetLevel(dimId); if (!level) { return; } shared_ptr entity = level->getEntity(entityId); if (entity) { entity->moveTo(x, y, z, entity->yRot, entity->xRot); } } static int __cdecl NativeGetTileId(int dimId, int x, int y, int z) { ServerLevel *level = GetLevel(dimId); if (!level) { return 0; } return level->getTile(x, y, z); } static void __cdecl NativeSetTile(int dimId, int x, int y, int z, int tileId, int data) { ServerLevel *level = GetLevel(dimId); if (!level) { return; } level->setTileAndData(x, y, z, tileId, data, Tile::UPDATE_ALL); } static int __cdecl NativeGetTileData(int dimId, int x, int y, int z) { ServerLevel *level = GetLevel(dimId); if (!level) { return 0; } return level->getData(x, y, z); } static void __cdecl NativeSetTileData(int dimId, int x, int y, int z, int data) { ServerLevel *level = GetLevel(dimId); if (!level) { return; } level->setData(x, y, z, data, Tile::UPDATE_ALL); } static int __cdecl NativeBreakBlock(int dimId, int x, int y, int z) { ServerLevel *level = GetLevel(dimId); if (!level) { return 0; } if (level->getTile(x, y, z) == 0) { return 0; } return level->destroyTile(x, y, z, true) ? 1 : 0; } static int __cdecl NativeGetHighestBlockY(int dimId, int x, int z) { ServerLevel *level = GetLevel(dimId); if (!level) { return 0; } return level->getHeightmap(x, z); } // double[7] = { spawnX, spawnY, spawnZ, seed, dayTime, isRaining, isThundering } static void __cdecl NativeGetWorldInfo(int dimId, double *outBuf) { ServerLevel *level = GetLevel(dimId); if (!level) { memset(outBuf, 0, 7 * sizeof(double)); return; } LevelData *ld = level->getLevelData(); Pos *spawn = level->getSharedSpawnPos(); outBuf[0] = spawn ? (double)spawn->x : 0.0; outBuf[1] = spawn ? (double)spawn->y : 64.0; outBuf[2] = spawn ? (double)spawn->z : 0.0; outBuf[3] = (double)level->getSeed(); outBuf[4] = (double)level->getDayTime(); outBuf[5] = ld && ld->isRaining() ? 1.0 : 0.0; outBuf[6] = ld && ld->isThundering() ? 1.0 : 0.0; } static void __cdecl NativeSetWorldTime(int dimId, int64_t time) { ServerLevel *level = GetLevel(dimId); if (!level) { return; } level->setDayTime(time); } // note 2 sefl: pass -1 to leave a parameter unchanged static void __cdecl NativeSetWeather(int dimId, int storm, int thundering, int thunderDuration) { ServerLevel *level = GetLevel(dimId); if (!level) { return; } LevelData *ld = level->getLevelData(); if (!ld) { return; } if (storm >= 0) { ld->setRaining(storm != 0); } if (thundering >= 0) { ld->setThundering(thundering != 0); } if (thunderDuration >= 0) { ld->setThunderTime(thunderDuration); } } static int __cdecl NativeCreateExplosion(int dimId, double x, double y, double z, float power, int setFire, int breakBlocks) { ServerLevel *level = GetLevel(dimId); if (!level) { return 0; } Explosion explosion(level, nullptr, x, y, z, power); explosion.fire = (setFire != 0); explosion.destroyBlocks = (breakBlocks != 0); explosion.explode(); explosion.finalizeExplosion(true); return 1; } static int __cdecl NativeStrikeLightning(int dimId, double x, double y, double z, int effectOnly) { ServerLevel *level = GetLevel(dimId); if (!level) { return 0; } std::shared_ptr lightning = std::shared_ptr(new LightningBolt(level, x, y, z)); if (effectOnly != 0) { PlayerList *playerList = MinecraftServer::getPlayerList(); if (playerList == NULL) { return 0; } playerList->broadcast(x, y, z, 512.0, dimId, std::shared_ptr(new AddGlobalEntityPacket(lightning))); level->playSound(x, y, z, eSoundType_AMBIENT_WEATHER_THUNDER, 10000, 0.8f + level->random->nextFloat() * 0.2f); level->playSound(x, y, z, eSoundType_RANDOM_EXPLODE, 2, 0.5f + level->random->nextFloat() * 0.2f); return 1; } return level->addGlobalEntity(lightning) ? 1 : 0; } static int __cdecl NativeSetSpawnLocation(int dimId, int x, int y, int z) { ServerLevel *level = GetLevel(dimId); if (!level) { return 0; } level->setSpawnPos(x, y, z); return 1; } static void __cdecl NativeDropItem(int dimId, double x, double y, double z, int itemId, int count, int auxValue, int naturally) { ServerLevel *level = GetLevel(dimId); if (!level) { return; } if (itemId <= 0 || count <= 0) { return; } auto itemInstance = std::make_shared(itemId, count, auxValue); double spawnX = x, spawnY = y, spawnZ = z; if (naturally) { float s = 0.7f; spawnX += level->random->nextFloat() * s + (1 - s) * 0.5; spawnY += level->random->nextFloat() * s + (1 - s) * 0.5; spawnZ += level->random->nextFloat() * s + (1 - s) * 0.5; } auto item = std::make_shared(level, spawnX, spawnY, spawnZ, itemInstance); item->throwTime = 10; level->addEntity(item); } static void __cdecl NativeKickPlayer(int entityId, int reason) { auto player = FindPlayer(entityId); if (player && player->connection) { DisconnectPacket::eDisconnectReason r = static_cast(reason); player->connection->disconnect(r); } } static int __cdecl NativeBanPlayer(int entityId, const char *reasonUtf8, int reasonByteLen) { if (!ServerRuntime::Access::IsInitialized()) { return 0; } auto player = FindPlayer(entityId); if (!player) { return 0; } std::vector xuids; PlayerUID xuid1 = player->getXuid(); PlayerUID xuid2 = player->getOnlineXuid(); if (xuid1 != INVALID_XUID) { xuids.push_back(xuid1); } if (xuid2 != INVALID_XUID && xuid2 != xuid1) { xuids.push_back(xuid2); } if (xuids.empty()) { return 0; } std::string reason = (reasonUtf8 && reasonByteLen > 0) ? std::string(reasonUtf8, reasonByteLen) : "Banned by plugin."; std::string playerName = ServerRuntime::StringUtils::WideToUtf8(player->getName()); ServerRuntime::Access::BanMetadata metadata = ServerRuntime::Access::BanManager::BuildDefaultMetadata("Plugin"); metadata.reason = reason; for (auto xuid : xuids) { if (!ServerRuntime::Access::IsPlayerBanned(xuid)) { ServerRuntime::Access::AddPlayerBan(xuid, playerName, metadata); } } if (player->connection) { player->connection->disconnect(DisconnectPacket::eDisconnect_Banned); } return 1; } static int __cdecl NativeBanPlayerIp(int entityId, const char *reasonUtf8, int reasonByteLen) { if (!ServerRuntime::Access::IsInitialized()) { return 0; } auto player = FindPlayer(entityId); if (!player || !player->connection || !player->connection->connection || !player->connection->connection->getSocket()) { return 0; } unsigned char smallId = player->connection->connection->getSocket()->getSmallId(); if (smallId == 0) { return 0; } std::string playerIp; if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, &playerIp)) { return 0; } std::string reason = (reasonUtf8 && reasonByteLen > 0) ? std::string(reasonUtf8, reasonByteLen) : "Banned by plugin."; ServerRuntime::Access::BanMetadata metadata = ServerRuntime::Access::BanManager::BuildDefaultMetadata("Plugin"); metadata.reason = reason; std::string normalizedIp = ServerRuntime::NetworkUtils::NormalizeIpToken(playerIp); if (ServerRuntime::Access::IsIpBanned(normalizedIp)) { return 0; } if (!ServerRuntime::Access::AddIpBan(normalizedIp, metadata)) { return 0; } PlayerList *list = MinecraftServer::getPlayerList(); if (list) { std::vector> snapshot = list->players; for (auto &p : snapshot) { if (!p || !p->connection || !p->connection->connection || !p->connection->connection->getSocket()) { continue; } unsigned char sid = p->connection->connection->getSocket()->getSmallId(); if (sid == 0) { continue; } std::string pIp; if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(sid, &pIp)) { continue; } if (ServerRuntime::NetworkUtils::NormalizeIpToken(pIp) == normalizedIp) { if (p->connection) { p->connection->disconnect(DisconnectPacket::eDisconnect_Banned); } } } } return 1; } static int __cdecl NativeGetPlayerAddress(int entityId, char *outIpBuf, int outIpBufSize, int *outPort) { if (outPort) { *outPort = 0; } if (outIpBuf && outIpBufSize > 0) { outIpBuf[0] = '\0'; } auto player = FindPlayer(entityId); if (!player || !player->connection || !player->connection->connection || !player->connection->connection->getSocket()) { return 0; } unsigned char smallId = player->connection->connection->getSocket()->getSmallId(); if (smallId == 0) { return 0; } std::string playerIp; if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, &playerIp)) { SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId); if (sock != INVALID_SOCKET) { sockaddr_in addr; int addrLen = sizeof(addr); if (getpeername(sock, (sockaddr *)&addr, &addrLen) == 0) { char ipBuf[64] = {}; if (inet_ntop(AF_INET, &addr.sin_addr, ipBuf, sizeof(ipBuf))) { playerIp = ipBuf; if (outPort) { *outPort = (int)ntohs(addr.sin_port); } } } } if (playerIp.empty()) { return 0; } } else { SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId); if (sock != INVALID_SOCKET && outPort) { sockaddr_in addr; int addrLen = sizeof(addr); if (getpeername(sock, (sockaddr *)&addr, &addrLen) == 0) { *outPort = (int)ntohs(addr.sin_port); } } } if (outIpBuf && outIpBufSize > 0) { int copyLen = (int)playerIp.size(); if (copyLen >= outIpBufSize) { copyLen = outIpBufSize - 1; } memcpy(outIpBuf, playerIp.c_str(), copyLen); outIpBuf[copyLen] = '\0'; } return 1; } // outData: 40 slots * 3 ints (id, count, aux) + 1 int (selected slot) = 121 ints static void __cdecl NativeGetPlayerInventory(int entityId, int *outData) { memset(outData, 0, 121 * sizeof(int)); auto player = FindPlayer(entityId); if (!player || !player->inventory) { return; } unsigned int size = player->inventory->getContainerSize(); if (size > 40) { size = 40; } for (unsigned int i = 0; i < size; i++) { auto item = player->inventory->getItem(i); if (item) { outData[i * 3] = item->id; outData[i * 3 + 1] = item->count; outData[i * 3 + 2] = item->getAuxValue(); } } outData[120] = player->inventory->selected; } static void __cdecl NativeSetPlayerInventorySlot(int entityId, int slot, int itemId, int count, int aux) { auto player = FindPlayer(entityId); if (!player || !player->inventory) { return; } if (itemId <= 0 || count <= 0) { player->inventory->setItem(slot, nullptr); } else { auto item = std::make_shared(itemId, count, aux); player->inventory->setItem(slot, item); } } static void __cdecl NativeGetContainerContents(int entityId, int *outData, int maxSlots) { memset(outData, 0, maxSlots * 3 * sizeof(int)); auto player = FindPlayer(entityId); if (!player || !player->containerMenu || player->containerMenu == player->inventoryMenu) { return; } auto *menu = player->containerMenu; auto *items = menu->getItems(); int count = (int)items->size(); if (count > maxSlots) { count = maxSlots; } for (int i = 0; i < count; i++) { auto &item = (*items)[i]; if (item) { outData[i * 3] = item->id; outData[i * 3 + 1] = item->count; outData[i * 3 + 2] = item->getAuxValue(); } } delete items; } static void __cdecl NativeSetContainerSlot(int entityId, int slot, int itemId, int count, int aux) { auto player = FindPlayer(entityId); if (!player || !player->containerMenu || player->containerMenu == player->inventoryMenu) { return; } auto *menu = player->containerMenu; if (slot < 0 || slot >= (int)menu->slots.size()) { return; } if (itemId <= 0 || count <= 0) { menu->setItem(slot, nullptr); } else { menu->setItem(slot, std::make_shared(itemId, count, aux)); } menu->broadcastChanges(); } static void __cdecl NativeCloseContainer(int entityId) { auto player = FindPlayer(entityId); if (player) { player->doCloseContainer(); } } class VirtualContainer : public SimpleContainer { int m_containerType; public: VirtualContainer(int containerType, const std::wstring &name, int size) : SimpleContainer(0, name, !name.empty(), size), m_containerType(containerType) { } virtual int getContainerType() override { return m_containerType; } }; static void __cdecl NativeOpenVirtualContainer(int entityId, int nativeType, const char *titleUtf8, int titleByteLen, int slotCount, int *itemsBuf) { auto player = FindPlayer(entityId); if (!player) { return; } if (player->containerMenu != player->inventoryMenu) { player->doCloseContainer(); } std::wstring title = ServerRuntime::StringUtils::Utf8ToWide(std::string(titleUtf8, titleByteLen)); auto container = std::make_shared(nativeType, title, slotCount); for (int i = 0; i < slotCount; i++) { int id = itemsBuf[i * 3]; int count = itemsBuf[i * 3 + 1]; int aux = itemsBuf[i * 3 + 2]; if (id > 0 && count > 0) { container->setItem(i, std::make_shared(id, count, aux)); } } player->openContainer(container); } static void __cdecl NativeGetContainerViewerEntityIds(int entityId, int *outIds, int maxCount, int *outCount) { *outCount = 0; auto player = FindPlayer(entityId); if (!player || !player->containerMenu || player->containerMenu == player->inventoryMenu) { return; } auto *menu = player->containerMenu; if (menu->slots.empty()) { return; } Container *myContainer = menu->slots[0]->container.get(); if (!myContainer) { return; } CompoundContainer *myCompound = dynamic_cast(myContainer); if (myCompound) { myContainer = myCompound->getFirstContainer().get(); } PlayerList *list = MinecraftServer::getPlayerList(); if (!list) { return; } int count = 0; for (auto &p : list->players) { if (!p || !p->containerMenu || p->containerMenu == p->inventoryMenu) { continue; } if (p->containerMenu->slots.empty()) { continue; } Container *theirContainer = p->containerMenu->slots[0]->container.get(); CompoundContainer *theirCompound = dynamic_cast(theirContainer); if (theirCompound) { theirContainer = theirCompound->getFirstContainer().get(); } if (theirContainer == myContainer && count < maxCount) { outIds[count++] = p->entityId; } } *outCount = count; } // [nameLen:int32][nameUTF8:bytes][loreCount:int32][lore0Len:int32][lore0UTF8:bytes] // todo: des muass i no a bissl überarwatn static int __cdecl NativeGetItemMeta(int entityId, int slot, char *outBuf, int bufSize) { auto player = FindPlayer(entityId); if (!player || !player->inventory) { return 0; } unsigned int size = player->inventory->getContainerSize(); if (slot < 0 || slot >= (int)size) { return 0; } auto item = player->inventory->getItem(slot); if (!item || !item->hasTag()) { return 0; } CompoundTag *tag = item->getTag(); if (!tag || !tag->contains(L"display")) { return 0; } CompoundTag *display = tag->getCompound(L"display"); bool hasName = display->contains(L"Name"); bool hasLore = display->contains(L"Lore"); if (!hasName && !hasLore) { return 0; } int offset = 0; if (hasName) { std::wstring wname = display->getString(L"Name"); std::string nameUtf8 = ServerRuntime::StringUtils::WideToUtf8(wname); int nameLen = (int)nameUtf8.size(); if (offset + 4 + nameLen > bufSize) return 0; memcpy(outBuf + offset, &nameLen, 4); offset += 4; memcpy(outBuf + offset, nameUtf8.data(), nameLen); offset += nameLen; } else { int zero = 0; if (offset + 4 > bufSize) return 0; memcpy(outBuf + offset, &zero, 4); offset += 4; } if (hasLore) { ListTag *lore = (ListTag *)display->getList(L"Lore"); int loreCount = lore->size(); if (offset + 4 > bufSize) return 0; memcpy(outBuf + offset, &loreCount, 4); offset += 4; for (int i = 0; i < loreCount; i++) { std::wstring wline = lore->get(i)->data; std::string lineUtf8 = ServerRuntime::StringUtils::WideToUtf8(wline); int lineLen = (int)lineUtf8.size(); if (offset + 4 + lineLen > bufSize) return 0; memcpy(outBuf + offset, &lineLen, 4); offset += 4; memcpy(outBuf + offset, lineUtf8.data(), lineLen); offset += lineLen; } } else { int zero = 0; if (offset + 4 > bufSize) return 0; memcpy(outBuf + offset, &zero, 4); offset += 4; } return offset; } static void __cdecl NativeSetItemMeta(int entityId, int slot, const char *inBuf, int bufSize) { auto player = FindPlayer(entityId); if (!player || !player->inventory) { return; } unsigned int size = player->inventory->getContainerSize(); if (slot < 0 || slot >= (int)size) { return; } auto item = player->inventory->getItem(slot); if (!item) { return; } if (inBuf == nullptr || bufSize <= 0) { item->resetHoverName(); if (item->hasTag()) { CompoundTag *tag = item->getTag(); if (tag && tag->contains(L"display")) { CompoundTag *display = tag->getCompound(L"display"); display->remove(L"Lore"); if (display->isEmpty()) { tag->remove(L"display"); if (tag->isEmpty()) { item->setTag(nullptr); } } } } return; } int offset = 0; if (offset + 4 > bufSize) return; int nameLen = 0; memcpy(&nameLen, inBuf + offset, 4); offset += 4; if (nameLen > 0) { if (offset + nameLen > bufSize) return; std::string nameUtf8(inBuf + offset, nameLen); offset += nameLen; std::wstring wname = ServerRuntime::StringUtils::Utf8ToWide(nameUtf8); item->setHoverName(wname); } else { item->resetHoverName(); } if (offset + 4 > bufSize) return; int loreCount = 0; memcpy(&loreCount, inBuf + offset, 4); offset += 4; if (loreCount > 0) { if (!item->hasTag()) item->setTag(new CompoundTag()); CompoundTag *tag = item->getTag(); if (!tag->contains(L"display")) tag->putCompound(L"display", new CompoundTag()); CompoundTag *display = tag->getCompound(L"display"); auto *loreList = new ListTag(L"Lore"); for (int i = 0; i < loreCount; i++) { if (offset + 4 > bufSize) break; int lineLen = 0; memcpy(&lineLen, inBuf + offset, 4); offset += 4; std::wstring wline; if (lineLen > 0 && offset + lineLen <= bufSize) { std::string lineUtf8(inBuf + offset, lineLen); offset += lineLen; wline = ServerRuntime::StringUtils::Utf8ToWide(lineUtf8); } loreList->add(new StringTag(L"", wline)); } display->put(L"Lore", loreList); } else { if (item->hasTag()) { CompoundTag *tag = item->getTag(); if (tag && tag->contains(L"display")) { tag->getCompound(L"display")->remove(L"Lore"); } } } } static void __cdecl NativeSetHeldItemSlot(int entityId, int slot) { auto player = FindPlayer(entityId); if (!player || !player->inventory) return; if (slot < 0 || slot >= Inventory::getSelectionSize()) return; player->inventory->selected = slot; if (player->connection) player->connection->queueSend(std::make_shared(slot)); } static void __cdecl NativeSetSneaking(int entityId, int sneak) { auto player = FindPlayer(entityId); if (player) { player->setSneaking(sneak != 0); } } static void __cdecl NativeSetVelocity(int entityId, double x, double y, double z) { auto player = FindPlayer(entityId); if (player) { player->xd = x; player->yd = y; player->zd = z; player->hurtMarked = true; return; } auto entity = FindEntity(entityId); if (entity) { entity->xd = x; entity->yd = y; entity->zd = z; entity->hurtMarked = true; } } static void __cdecl NativeSetAllowFlight(int entityId, int allowFlight) { auto player = FindPlayer(entityId); if (player) { player->abilities.mayfly = (allowFlight != 0); if (!player->abilities.mayfly) { player->abilities.flying = false; } if (player->connection) { player->connection->send(std::make_shared(&player->abilities)); } } } static std::wstring FindNet10SystemRoot() { // overengineered // trying to do a lot of safeguards here, setups can be all over the place // fixes an issue aiden had std::vector candidates; wchar_t envRoot[MAX_PATH] = {}; DWORD len = GetEnvironmentVariableW(L"DOTNET_ROOT", envRoot, MAX_PATH); if (len > 0 && len < MAX_PATH) { candidates.push_back(std::wstring(envRoot)); } candidates.push_back(L"C:\\Program Files\\dotnet"); for (const auto &root : candidates) { std::wstring fxrDir = root + L"\\host\\fxr"; WIN32_FIND_DATAW fd; HANDLE h = FindFirstFileW((fxrDir + L"\\*").c_str(), &fd); if (h == INVALID_HANDLE_VALUE) { continue; } bool has10 = false; do { if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && fd.cFileName[0] != L'.') { if (std::wstring(fd.cFileName).substr(0, 3) == L"10.") { has10 = true; } } } while (!has10 && FindNextFileW(h, &fd)); FindClose(h); if (has10) { return root; } } return L"C:\\Program Files\\dotnet"; } static bool TryLoadHostfxrFromPath(const std::wstring &path) { HMODULE lib = LoadLibraryW(path.c_str()); if (!lib) { return false; } s_initFn = (hostfxr_initialize_for_runtime_config_fn)GetProcAddress(lib, "hostfxr_initialize_for_runtime_config"); s_getDelegateFn = (hostfxr_get_runtime_delegate_fn)GetProcAddress(lib, "hostfxr_get_runtime_delegate"); s_closeFn = (hostfxr_close_fn)GetProcAddress(lib, "hostfxr_close"); if (s_initFn && s_getDelegateFn && s_closeFn) { return true; } s_initFn = nullptr; s_getDelegateFn = nullptr; s_closeFn = nullptr; FreeLibrary(lib); return false; } static bool LoadHostfxr() { // hardcoded for windows // no linux support yet so no need wchar_t exePath[MAX_PATH] = {}; GetModuleFileNameW(NULL, exePath, MAX_PATH); std::wstring exeDir(exePath); size_t lastSlash = exeDir.find_last_of(L"\\/"); if (lastSlash != std::wstring::npos) { exeDir = exeDir.substr(0, lastSlash); } if (TryLoadHostfxrFromPath(exeDir + L"\\hostfxr.dll")) { s_dotnetRoot = FindNet10SystemRoot(); return true; } wchar_t dotnetRoot[MAX_PATH] = {}; DWORD len = GetEnvironmentVariableW(L"DOTNET_ROOT", dotnetRoot, MAX_PATH); // sometimes this is set in windows if (len == 0 || len >= MAX_PATH) { wcscpy_s(dotnetRoot, L"C:\\Program Files\\dotnet"); } std::wstring hostfxrDir = std::wstring(dotnetRoot) + L"\\host\\fxr"; WIN32_FIND_DATAW fd; HANDLE hFind = FindFirstFileW((hostfxrDir + L"\\*").c_str(), &fd); if (hFind != INVALID_HANDLE_VALUE) { std::wstring bestVersion; do { if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && fd.cFileName[0] != L'.') { std::wstring ver(fd.cFileName); if (ver.substr(0, 3) == L"10." && ver > bestVersion) { bestVersion = ver; } } } while (FindNextFileW(hFind, &fd)); FindClose(hFind); if (!bestVersion.empty()) { if (TryLoadHostfxrFromPath(hostfxrDir + L"\\" + bestVersion + L"\\hostfxr.dll")) { s_dotnetRoot = std::wstring(dotnetRoot); return true; } } } LogError("fourkit", "hostfxr.dll not found. Install the .NET 10 x64 runtime (https://aka.ms/dotnet/download) or copy hostfxr.dll from C:\\Program Files\\dotnet\\host\\fxr\\10.x.x\\ next to the server executable."); return false; } static bool GetManagedEntryPoint( load_assembly_and_get_function_pointer_fn loadAssembly, const wchar_t *assemblyPath, const wchar_t *typeName, const wchar_t *methodName, void **outFnPtr) { int rc = loadAssembly( assemblyPath, typeName, methodName, UNMANAGEDCALLERSONLY_METHOD, nullptr, outFnPtr); if (rc != 0 || *outFnPtr == nullptr) { char methodNarrow[256]; sprintf_s(methodNarrow, "%S::%S", typeName, methodName); LogError("fourkit", (std::string("Failed to resolve managed entry point: ") + methodNarrow).c_str()); return false; } return true; } void Initialize() { LogInfo("fourkit", "FourKit initializing..."); if (!LoadHostfxr()) { return; } wchar_t exePath[MAX_PATH]; GetModuleFileNameW(NULL, exePath, MAX_PATH); std::wstring exeDir(exePath); size_t lastSlash = exeDir.find_last_of(L"\\/"); if (lastSlash != std::wstring::npos) { exeDir = exeDir.substr(0, lastSlash); } std::wstring runtimeConfigPath = exeDir + L"\\Minecraft.Server.FourKit.runtimeconfig.json"; std::wstring assemblyPath = exeDir + L"\\Minecraft.Server.FourKit.dll"; hostfxr_initialize_parameters initParams = {}; initParams.size = sizeof(hostfxr_initialize_parameters); initParams.host_path = exePath; initParams.dotnet_root = s_dotnetRoot.c_str(); hostfxr_handle ctx = nullptr; int rc = s_initFn(runtimeConfigPath.c_str(), &initParams, &ctx); if (rc != 0 || ctx == nullptr) { char msg[256]; sprintf_s(msg, "hostfxr_initialize_for_runtime_config failed (0x%08X). Check runtimeconfig.json path.", rc); LogError("fourkit", msg); if (ctx) { s_closeFn(ctx); } return; } load_assembly_and_get_function_pointer_fn loadAssembly = nullptr; rc = s_getDelegateFn(ctx, hdt_load_assembly_and_get_function_pointer, (void **)&loadAssembly); s_closeFn(ctx); if (rc != 0 || loadAssembly == nullptr) { LogError("fourkit", "Failed to get load_assembly_and_get_function_pointer delegate."); return; } const wchar_t *typeName = L"Minecraft.Server.FourKit.FourKitHost, Minecraft.Server.FourKit"; bool ok = true; // lol // this is horrid ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"Initialize", (void **)&s_managedInit); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerJoin", (void **)&s_managedFireJoin); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerQuit", (void **)&s_managedFireQuit); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerKick", (void **)&s_managedFireKick); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerMove", (void **)&s_managedFireMove); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"Shutdown", (void **)&s_managedShutdown); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetNativeCallbacks", (void **)&s_managedSetCallbacks); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetWorldCallbacks", (void **)&s_managedSetWorldCallbacks); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"UpdatePlayerEntityId", (void **)&s_managedUpdateEntityId); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerChat", (void **)&s_managedFireChat); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBlockPlace", (void **)&s_managedFireBlockPlace); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBlockBreak", (void **)&s_managedFireBlockBreak); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireEntityDamage", (void **)&s_managedFireEntityDamage); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireSignChange", (void **)&s_managedFireSignChange); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireEntityDeath", (void **)&s_managedFireEntityDeath); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerDeath", (void **)&s_managedFirePlayerDeath); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetPlayerCallbacks", (void **)&s_managedSetPlayerCallbacks); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerDropItem", (void **)&s_managedFirePlayerDropItem); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetInventoryCallbacks", (void **)&s_managedSetInventoryCallbacks); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerInteract", (void **)&s_managedFirePlayerInteract); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerInteractEntity", (void **)&s_managedFirePlayerInteractEntity); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerPickupItem", (void **)&s_managedFirePlayerPickupItem); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireInventoryOpen", (void **)&s_managedFireInventoryOpen); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"HandlePlayerCommand", (void **)&s_managedHandlePlayerCommand); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"HandleConsoleCommand", (void **)&s_managedHandleConsoleCommand); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"GetPluginCommandHelp", (void **)&s_managedGetPluginCommandHelp); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerTeleport", (void **)&s_managedFirePlayerTeleport); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerPortal", (void **)&s_managedFirePlayerPortal); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireInventoryClick", (void **)&s_managedFireInventoryClick); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBedEnter", (void **)&s_managedFireBedEnter); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBedLeave", (void **)&s_managedFireBedLeave); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetEntityCallbacks", (void **)&s_managedSetEntityCallbacks); if (!ok) { LogError("fourkit", "Not all managed entry points resolved. FourKit will be disabled."); return; } s_initialized = true; s_managedInit(); s_managedSetCallbacks( (void *)&NativeDamagePlayer, (void *)&NativeSetPlayerHealth, (void *)&NativeTeleportPlayer, (void *)&NativeSetPlayerGameMode, (void *)&NativeBroadcastMessage, (void *)&NativeSetFallDistance, (void *)&NativeGetPlayerSnapshot, (void *)&NativeSendMessage, (void *)&NativeSetWalkSpeed, (void *)&NativeTeleportEntity); s_managedSetWorldCallbacks( (void *)&NativeGetTileId, (void *)&NativeGetTileData, (void *)&NativeSetTile, (void *)&NativeSetTileData, (void *)&NativeBreakBlock, (void *)&NativeGetHighestBlockY, (void *)&NativeGetWorldInfo, (void *)&NativeSetWorldTime, (void *)&NativeSetWeather, (void *)&NativeCreateExplosion, (void *)&NativeStrikeLightning, (void *)&NativeSetSpawnLocation, (void *)&NativeDropItem); s_managedSetPlayerCallbacks( (void *)&NativeKickPlayer, (void *)&NativeBanPlayer, (void *)&NativeBanPlayerIp, (void *)&NativeGetPlayerAddress); s_managedSetInventoryCallbacks( (void *)&NativeGetPlayerInventory, (void *)&NativeSetPlayerInventorySlot, (void *)&NativeGetContainerContents, (void *)&NativeSetContainerSlot, (void *)&NativeGetContainerViewerEntityIds, (void *)&NativeCloseContainer, (void *)&NativeOpenVirtualContainer, (void *)&NativeGetItemMeta, (void *)&NativeSetItemMeta, (void *)&NativeSetHeldItemSlot); s_managedSetEntityCallbacks( (void *)&NativeSetSneaking, (void *)&NativeSetVelocity, (void *)&NativeSetAllowFlight); LogInfo("fourkit", "FourKit initialized successfully."); } void Shutdown() { if (!s_initialized) { return; } LogInfo("fourkit", "FourKit shutting down..."); s_managedShutdown(); s_initialized = false; LogInfo("fourkit", "FourKit shut down."); } void FirePlayerJoin(int entityId, const std::wstring &name, const std::wstring &uuid) { if (!s_initialized || !s_managedFireJoin) { return; } std::string nameUtf8 = ServerRuntime::StringUtils::WideToUtf8(name); std::string uuidUtf8 = ServerRuntime::StringUtils::WideToUtf8(uuid); s_managedFireJoin(entityId, nameUtf8.empty() ? "" : nameUtf8.data(), (int)nameUtf8.size(), uuidUtf8.empty() ? "" : uuidUtf8.data(), (int)uuidUtf8.size()); LogDebugf("fourkit", "Fired PlayerJoin: entityId=%d", entityId); } bool FirePlayerQuit(int entityId) { s_openContainerInfo.erase(entityId); if (!s_initialized || !s_managedFireQuit) { return false; } s_managedFireQuit(entityId); LogDebugf("fourkit", "Fired PlayerQuit: entityId=%d", entityId); return true; } bool FirePlayerKick(int entityId, int disconnectReason, std::wstring &outLeaveMessage) { if (!s_initialized || !s_managedFireKick) { return false; } std::string reasonUtf8 = std::to_string(disconnectReason); const int kBufSize = 2048; char outBuf[kBufSize] = {}; int outLen = 0; int cancelled = s_managedFireKick(entityId, disconnectReason, reasonUtf8.empty() ? "" : reasonUtf8.data(), (int)reasonUtf8.size(), outBuf, kBufSize, &outLen); if (outLen > 0 && outLen < kBufSize) { std::string resultUtf8(outBuf, outLen); outLeaveMessage = ServerRuntime::StringUtils::Utf8ToWide(resultUtf8); } LogDebugf("fourkit", "Fired PlayerKick: entityId=%d cancelled=%d", entityId, cancelled); return cancelled != 0; } void UpdatePlayerEntityId(int oldEntityId, int newEntityId) { auto it = s_openContainerInfo.find(oldEntityId); if (it != s_openContainerInfo.end()) { s_openContainerInfo[newEntityId] = it->second; s_openContainerInfo.erase(it); } if (!s_initialized || !s_managedUpdateEntityId) { return; } s_managedUpdateEntityId(oldEntityId, newEntityId); LogDebugf("fourkit", "UpdatePlayerEntityId: %d -> %d", oldEntityId, newEntityId); } bool FirePlayerMove(int entityId, double fromX, double fromY, double fromZ, double toX, double toY, double toZ, double *outToX, double *outToY, double *outToZ) { if (!s_initialized || !s_managedFireMove) { return false; } double outCoords[3] = {toX, toY, toZ}; int cancelled = s_managedFireMove(entityId, fromX, fromY, fromZ, toX, toY, toZ, outCoords); *outToX = outCoords[0]; *outToY = outCoords[1]; *outToZ = outCoords[2]; return cancelled != 0; } bool FirePlayerChat(int entityId, const std::wstring &message, std::wstring &outFormatted) { if (!s_initialized || !s_managedFireChat) { return false; } std::string msgUtf8 = ServerRuntime::StringUtils::WideToUtf8(message); const int kBufSize = 2048; char outBuf[kBufSize] = {}; int outLen = 0; int cancelled = s_managedFireChat(entityId, msgUtf8.empty() ? "" : msgUtf8.data(), (int)msgUtf8.size(), outBuf, kBufSize, &outLen); if (cancelled != 0) { return true; } if (outLen > 0 && outLen < kBufSize) { std::string resultUtf8(outBuf, outLen); outFormatted = ServerRuntime::StringUtils::Utf8ToWide(resultUtf8); } return false; } bool FireBlockPlace(int entityId, int dimId, int placedX, int placedY, int placedZ, int againstX, int againstY, int againstZ, int itemId, int itemCount, bool canBuild) { if (!s_initialized || !s_managedFireBlockPlace) { return false; } int cancelled = s_managedFireBlockPlace(entityId, dimId, placedX, placedY, placedZ, againstX, againstY, againstZ, itemId, itemCount, canBuild ? 1 : 0); return cancelled != 0; } int FireBlockBreak(int entityId, int dimId, int x, int y, int z, int tileId, int data, int exp) { if (!s_initialized || !s_managedFireBlockBreak) { return exp; } return s_managedFireBlockBreak(entityId, dimId, x, y, z, tileId, data, exp); } int MapEntityType(int nativeType) { eINSTANCEOF type = (eINSTANCEOF)nativeType; const int ARROW = 0, BAT = 1, BLAZE = 2, BOAT = 3, CAVE_SPIDER = 4; const int CHICKEN = 5, COW = 7, CREEPER = 8, DROPPED_ITEM = 9; const int EGG = 10, ENDER_CRYSTAL = 11, ENDER_DRAGON = 12; const int ENDER_PEARL = 13, ENDER_SIGNAL = 14, ENDERMAN = 15; const int EXPERIENCE_ORB = 16, FALLING_BLOCK = 17, FIREBALL = 18; const int FIREWORK = 19, FISHING_HOOK = 20, GHAST = 21, GIANT = 22; const int HORSE = 23, IRON_GOLEM = 24, ITEM_FRAME = 25; const int LEASH_HITCH = 26, LIGHTNING = 27, MAGMA_CUBE = 28; const int MINECART = 29, MINECART_CHEST = 30, MINECART_FURNACE = 32; const int MINECART_HOPPER = 33, MINECART_MOB_SPAWNER = 34; const int MINECART_TNT = 35, MUSHROOM_COW = 36, OCELOT = 37; const int PAINTING = 38, PIG = 39, PIG_ZOMBIE = 40, PLAYER = 41; const int PRIMED_TNT = 42, SHEEP = 43, SILVERFISH = 44; const int SKELETON = 45, SLIME = 46, SMALL_FIREBALL = 47; const int SNOWBALL = 48, SNOWMAN = 49, SPIDER = 50; const int SPLASH_POTION = 51, SQUID = 52, THROWN_EXP_BOTTLE = 53; const int UNKNOWN = 54, VILLAGER = 55, WITCH = 57; const int WITHER = 58, WITHER_SKULL = 59, WOLF = 60, ZOMBIE = 61; switch (type) { case eTYPE_ARROW: return ARROW; case eTYPE_BAT: return BAT; case eTYPE_BLAZE: return BLAZE; case eTYPE_BOAT: return BOAT; case eTYPE_CAVESPIDER: return CAVE_SPIDER; case eTYPE_CHICKEN: return CHICKEN; case eTYPE_COW: return COW; case eTYPE_CREEPER: return CREEPER; case eTYPE_ITEMENTITY: return DROPPED_ITEM; case eTYPE_THROWNEGG: return EGG; case eTYPE_NETHER_SPHERE: return ENDER_CRYSTAL; case eTYPE_ENDERDRAGON: return ENDER_DRAGON; case eTYPE_THROWNENDERPEARL: return ENDER_PEARL; case eTYPE_EYEOFENDERSIGNAL: return ENDER_SIGNAL; case eTYPE_ENDERMAN: return ENDERMAN; case eTYPE_EXPERIENCEORB: return EXPERIENCE_ORB; case eTYPE_FALLINGTILE: return FALLING_BLOCK; case eTYPE_LARGE_FIREBALL: return FIREBALL; case eTYPE_FIREWORKS_ROCKET: return FIREWORK; case eTYPE_FISHINGHOOK: return FISHING_HOOK; case eTYPE_GHAST: return GHAST; case eTYPE_GIANT: return GIANT; case eTYPE_HORSE: return HORSE; case eTYPE_VILLAGERGOLEM: return IRON_GOLEM; case eTYPE_ITEM_FRAME: return ITEM_FRAME; case eTYPE_LEASHFENCEKNOT: return LEASH_HITCH; case eTYPE_LIGHTNINGBOLT: return LIGHTNING; case eTYPE_LAVASLIME: return MAGMA_CUBE; case eTYPE_MINECART_RIDEABLE: return MINECART; case eTYPE_MINECART_CHEST: return MINECART_CHEST; case eTYPE_MINECART_FURNACE: return MINECART_FURNACE; case eTYPE_MINECART_HOPPER: return MINECART_HOPPER; case eTYPE_MINECART_SPAWNER: return MINECART_MOB_SPAWNER; case eTYPE_MINECART_TNT: return MINECART_TNT; case eTYPE_MUSHROOMCOW: return MUSHROOM_COW; case eTYPE_OCELOT: return OCELOT; case eTYPE_PAINTING: return PAINTING; case eTYPE_PIG: return PIG; case eTYPE_PIGZOMBIE: return PIG_ZOMBIE; case eTYPE_PLAYER: return PLAYER; case eTYPE_SERVERPLAYER: return PLAYER; case eTYPE_REMOTEPLAYER: return PLAYER; case eTYPE_LOCALPLAYER: return PLAYER; case eTYPE_PRIMEDTNT: return PRIMED_TNT; case eTYPE_SHEEP: return SHEEP; case eTYPE_SILVERFISH: return SILVERFISH; case eTYPE_SKELETON: return SKELETON; case eTYPE_SLIME: return SLIME; case eTYPE_SMALL_FIREBALL: return SMALL_FIREBALL; case eTYPE_SNOWBALL: return SNOWBALL; case eTYPE_SNOWMAN: return SNOWMAN; case eTYPE_SPIDER: return SPIDER; case eTYPE_THROWNPOTION: return SPLASH_POTION; case eTYPE_SQUID: return SQUID; case eTYPE_THROWNEXPBOTTLE: return THROWN_EXP_BOTTLE; case eTYPE_VILLAGER: return VILLAGER; case eTYPE_WITCH: return WITCH; case eTYPE_WITHERBOSS: return WITHER; case eTYPE_WITHER_SKULL: return WITHER_SKULL; case eTYPE_WOLF: return WOLF; case eTYPE_ZOMBIE: return ZOMBIE; default: return UNKNOWN; } } int MapDamageCause(void *sourcePtr) { DamageSource *source = (DamageSource *)sourcePtr; const int CONTACT = 1, CUSTOM = 2, DROWNING = 3; const int ENTITY_ATTACK = 4, ENTITY_EXPLOSION = 5; const int FALL = 6, FALLING_BLOCK = 7, FIRE = 8, FIRE_TICK = 9; const int LAVA = 10, MAGIC = 12; const int PROJECTILE = 15, STARVATION = 16, SUFFOCATION = 17; const int CAUSE_VOID = 20, CAUSE_WITHER = 21; if (source == nullptr) { return CUSTOM; } if (source == DamageSource::inFire) { return FIRE; } if (source == DamageSource::onFire) { return FIRE_TICK; } if (source == DamageSource::lava) { return LAVA; } if (source == DamageSource::inWall) { return SUFFOCATION; } if (source == DamageSource::drown) { return DROWNING; } if (source == DamageSource::starve) { return STARVATION; } if (source == DamageSource::cactus) { return CONTACT; } if (source == DamageSource::fall) { return FALL; } if (source == DamageSource::outOfWorld) { return CAUSE_VOID; } if (source == DamageSource::genericSource) { return CUSTOM; } if (source == DamageSource::magic) { return MAGIC; } if (source == DamageSource::wither) { return CAUSE_WITHER; } if (source == DamageSource::anvil) { return FALLING_BLOCK; } if (source == DamageSource::fallingBlock) { return FALLING_BLOCK; } if (source->isExplosion()) { return ENTITY_EXPLOSION; } if (source->isProjectile()) { return PROJECTILE; } if (source->isMagic()) { return MAGIC; } if (dynamic_cast(source) != nullptr) { return ENTITY_ATTACK; } return CUSTOM; } bool FireEntityDamage(int entityId, int entityTypeId, int dimId, double x, double y, double z, int causeId, double damage, double *outDamage, int damagerEntityId, int damagerEntityTypeId, double damagerX, double damagerY, double damagerZ) { if (!s_initialized || !s_managedFireEntityDamage) { *outDamage = damage; return false; } double outDmg = damage; int cancelled = s_managedFireEntityDamage(entityId, entityTypeId, dimId, x, y, z, causeId, damage, &outDmg, damagerEntityId, damagerEntityTypeId, damagerX, damagerY, damagerZ); *outDamage = outDmg; return cancelled != 0; } bool FireSignChange(int entityId, int dimId, int x, int y, int z, const std::wstring &line0, const std::wstring &line1, const std::wstring &line2, const std::wstring &line3, std::wstring outLines[4]) { if (!s_initialized || !s_managedFireSignChange) { return false; } std::string l0 = ServerRuntime::StringUtils::WideToUtf8(line0); std::string l1 = ServerRuntime::StringUtils::WideToUtf8(line1); std::string l2 = ServerRuntime::StringUtils::WideToUtf8(line2); std::string l3 = ServerRuntime::StringUtils::WideToUtf8(line3); const int kBufSize = 512; char outBuf[kBufSize] = {}; int outLens[4] = {}; int cancelled = s_managedFireSignChange(entityId, dimId, x, y, z, l0.empty() ? "" : l0.data(), (int)l0.size(), l1.empty() ? "" : l1.data(), (int)l1.size(), l2.empty() ? "" : l2.data(), (int)l2.size(), l3.empty() ? "" : l3.data(), (int)l3.size(), outBuf, kBufSize, outLens); int offset = 0; for (int i = 0; i < 4; i++) { if (outLens[i] > 0 && offset + outLens[i] <= kBufSize) { std::string s(outBuf + offset, outLens[i]); outLines[i] = ServerRuntime::StringUtils::Utf8ToWide(s); } else { outLines[i] = (i == 0 ? line0 : (i == 1 ? line1 : (i == 2 ? line2 : line3))); } offset += outLens[i]; } return cancelled != 0; } int FireEntityDeath(int entityId, int entityTypeId, int dimId, double x, double y, double z, int exp) { if (!s_initialized || !s_managedFireEntityDeath) { return exp; } return s_managedFireEntityDeath(entityId, entityTypeId, dimId, x, y, z, exp); } int FirePlayerDeath(int entityId, const std::wstring &deathMessage, int exp, std::wstring &outDeathMessage, int *outKeepInventory, int *outNewExp, int *outNewLevel, int *outKeepLevel) { if (!s_initialized || !s_managedFirePlayerDeath) { outDeathMessage = deathMessage; *outKeepInventory = 0; *outNewExp = 0; *outNewLevel = 0; *outKeepLevel = 0; return exp; } std::string msgUtf8 = ServerRuntime::StringUtils::WideToUtf8(deathMessage); const int kBufSize = 2048; char outMsgBuf[kBufSize] = {}; int outMsgLen = 0; int keepInv = 0; int newExp = 0, newLevel = 0, keepLevel = 0; int outExp = s_managedFirePlayerDeath(entityId, msgUtf8.empty() ? "" : msgUtf8.data(), (int)msgUtf8.size(), exp, outMsgBuf, kBufSize, &outMsgLen, &keepInv, &newExp, &newLevel, &keepLevel); if (outMsgLen > 0 && outMsgLen < kBufSize) { std::string resultUtf8(outMsgBuf, outMsgLen); outDeathMessage = ServerRuntime::StringUtils::Utf8ToWide(resultUtf8); } else { outDeathMessage = deathMessage; } *outKeepInventory = keepInv; *outNewExp = newExp; *outNewLevel = newLevel; *outKeepLevel = keepLevel; return outExp; } bool FirePlayerDropItem(int entityId, int itemId, int itemCount, int itemAux, int *outItemId, int *outItemCount, int *outItemAux) { *outItemId = itemId; *outItemCount = itemCount; *outItemAux = itemAux; if (!s_initialized || !s_managedFirePlayerDropItem) { return false; } int newId = itemId, newCount = itemCount, newAux = itemAux; long long result = s_managedFirePlayerDropItem(entityId, itemId, itemCount, itemAux, &newId, &newCount, &newAux); *outItemId = newId; *outItemCount = newCount; *outItemAux = newAux; return result != 0; } bool FirePlayerInteract(int entityId, int action, int itemId, int itemCount, int itemAux, int clickedX, int clickedY, int clickedZ, int blockFace, int dimId, int *outUseItemInHand) { *outUseItemInHand = 1; if (!s_initialized || !s_managedFirePlayerInteract) { return false; } int useItem = 1; int result = s_managedFirePlayerInteract(entityId, action, itemId, itemCount, itemAux, clickedX, clickedY, clickedZ, blockFace, dimId, &useItem); *outUseItemInHand = useItem; return result != 0; } bool FirePlayerInteractEntity(int playerEntityId, int targetEntityId, int targetEntityTypeId, int dimId, double targetX, double targetY, double targetZ, float targetHealth, float targetMaxHealth, float targetEyeHeight) { if (!s_initialized || !s_managedFirePlayerInteractEntity) { return false; } int result = s_managedFirePlayerInteractEntity(playerEntityId, targetEntityId, targetEntityTypeId, dimId, targetX, targetY, targetZ, targetHealth, targetMaxHealth, targetEyeHeight); return result != 0; } bool FirePlayerPickupItem(int playerEntityId, int itemEntityId, int dimId, double itemX, double itemY, double itemZ, int itemId, int itemCount, int itemAux, int remaining, int *outItemId, int *outItemCount, int *outItemAux) { if (!s_initialized || !s_managedFirePlayerPickupItem) { *outItemId = itemId; *outItemCount = itemCount; *outItemAux = itemAux; return false; } *outItemId = itemId; *outItemCount = itemCount; *outItemAux = itemAux; int result = s_managedFirePlayerPickupItem(playerEntityId, itemEntityId, dimId, itemX, itemY, itemZ, itemId, itemCount, itemAux, remaining, outItemId, outItemCount, outItemAux); return result != 0; } bool FireInventoryOpen(int entityId, int nativeContainerType, const std::wstring &title, int containerSize) { if (!s_initialized || !s_managedFireInventoryOpen) { return false; } s_openContainerInfo[entityId] = {nativeContainerType, containerSize, title}; std::string titleUtf8 = ServerRuntime::StringUtils::WideToUtf8(title); int cancelled = s_managedFireInventoryOpen(entityId, nativeContainerType, titleUtf8.empty() ? "" : titleUtf8.data(), (int)titleUtf8.size(), containerSize); return cancelled != 0; } bool HandlePlayerCommand(int entityId, const std::wstring &commandLine) { if (!s_initialized || !s_managedHandlePlayerCommand) { return false; } std::string cmdUtf8 = ServerRuntime::StringUtils::WideToUtf8(commandLine); int handled = s_managedHandlePlayerCommand(entityId, cmdUtf8.empty() ? "" : cmdUtf8.data(), (int)cmdUtf8.size()); return handled != 0; } bool HandleConsoleCommand(const std::string &commandLine) { if (!s_initialized || !s_managedHandleConsoleCommand) { return false; } int handled = s_managedHandleConsoleCommand( commandLine.empty() ? "" : commandLine.data(), (int)commandLine.size()); return handled != 0; } int GetPluginCommandHelp(char *outBuf, int outBufSize, int *outLen) { if (!s_initialized || !s_managedGetPluginCommandHelp) { return 0; } return s_managedGetPluginCommandHelp(outBuf, outBufSize, outLen); } bool FirePlayerTeleport(int entityId, double fromX, double fromY, double fromZ, int fromDimId, double toX, double toY, double toZ, int toDimId, int cause, double *outToX, double *outToY, double *outToZ) { if (!s_initialized || !s_managedFirePlayerTeleport) { return false; } double outCoords[3] = {toX, toY, toZ}; int cancelled = s_managedFirePlayerTeleport(entityId, fromX, fromY, fromZ, fromDimId, toX, toY, toZ, toDimId, cause, outCoords); *outToX = outCoords[0]; *outToY = outCoords[1]; *outToZ = outCoords[2]; return cancelled != 0; } bool FirePlayerPortal(int entityId, double fromX, double fromY, double fromZ, int fromDimId, double toX, double toY, double toZ, int toDimId, int cause, double *outToX, double *outToY, double *outToZ) { if (!s_initialized || !s_managedFirePlayerPortal) { return false; } double outCoords[3] = {toX, toY, toZ}; int cancelled = s_managedFirePlayerPortal(entityId, fromX, fromY, fromZ, fromDimId, toX, toY, toZ, toDimId, cause, outCoords); *outToX = outCoords[0]; *outToY = outCoords[1]; *outToZ = outCoords[2]; return cancelled != 0; } int FireInventoryClick(int entityId, int slot, int button, int clickType) { if (!s_initialized || !s_managedFireInventoryClick) { return 0; } int nativeContainerType = -1; int containerSize = 0; std::string titleUtf8; auto it = s_openContainerInfo.find(entityId); if (it != s_openContainerInfo.end()) { nativeContainerType = it->second.type; containerSize = it->second.size; if (!it->second.title.empty()) { titleUtf8 = ServerRuntime::StringUtils::WideToUtf8(it->second.title); } } return s_managedFireInventoryClick(entityId, slot, button, clickType, nativeContainerType, containerSize, titleUtf8.empty() ? nullptr : titleUtf8.data(), (int)titleUtf8.size()); } bool FireBedEnter(int entityId, int dimId, int bedX, int bedY, int bedZ) { if (!s_initialized || !s_managedFireBedEnter) return false; return s_managedFireBedEnter(entityId, dimId, bedX, bedY, bedZ) != 0; } void FireBedLeave(int entityId, int dimId, int bedX, int bedY, int bedZ) { if (!s_initialized || !s_managedFireBedLeave) return; s_managedFireBedLeave(entityId, dimId, bedX, bedY, bedZ); } } // namespace FourKitBridge