Update to NeoLegacy Latest

This commit is contained in:
2026-05-28 01:23:47 -04:00
parent f4390d5f0b
commit 10f9576b72
201 changed files with 12535 additions and 25235 deletions
+269 -16
View File
@@ -58,14 +58,41 @@
#ifdef _WINDOWS64
#include "Xbox/Network/NetworkPlayerXbox.h"
#include "Common/Network/PlatformNetworkManagerStub.h"
#include "Windows64/Network/WinsockNetLayer.h"
#endif
#include "../Minecraft.World/Recipes.h"
#ifdef _DURANGO
#include "../Minecraft.World/DurangoStats.h"
#include "../Minecraft.World/GenericStats.h"
#endif
namespace
{
char mapIconToFrame(char iconSlot)
{
if (iconSlot >= 8) return iconSlot - 4;
return iconSlot;
}
// Same hash as getRandomPlayerMapIcon in MapItemSavedData.cpp, returning
// the Iggy/SWF frame index (0-7) instead of the raw icon slot.
char computePlayerMapFrame(int entityId, int playerIndex)
{
static const char PLAYER_MAP_ICON_SLOTS[] = { 0, 1, 2, 3, 8, 9, 10, 11 };
unsigned int seed = static_cast<unsigned int>(entityId);
seed ^= static_cast<unsigned int>(playerIndex * 0x9E3779B9u);
seed ^= (seed >> 16);
seed *= 0x7FEB352Du;
seed ^= (seed >> 15);
seed *= 0x846CA68Bu;
seed ^= (seed >> 16);
return mapIconToFrame(PLAYER_MAP_ICON_SLOTS[seed % 8]);
}
}
ClientConnection::ClientConnection(Minecraft *minecraft, const wstring& ip, int port)
{
// 4J Stu - No longer used as we use the socket version below.
@@ -110,6 +137,10 @@ ClientConnection::ClientConnection(Minecraft *minecraft, Socket *socket, int iUs
started = false;
savedDataStorage = new SavedDataStorage(nullptr);
maxPlayers = 20;
m_isForkServer = false;
m_recivedRecipeRegistyUpdate = false;
m_recivedCreativeRegistyUpdate = false;
this->minecraft = minecraft;
@@ -220,6 +251,14 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)
{
if (done) return;
if (!m_recivedRecipeRegistyUpdate) {
Recipes::getInstance()->loadFromLocal();
}
if (!m_recivedCreativeRegistyUpdate) {
IUIScene_CreativeMenu::loadFromLocal();
}
PlayerUID OnlineXuid;
ProfileManager.GetXUID(m_userIndex,&OnlineXuid,true); // online xuid
MOJANG_DATA *pMojangData = nullptr;
@@ -342,7 +381,7 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)
Level *dimensionLevel = minecraft->getLevel( packet->dimension );
if( dimensionLevel == nullptr )
{
level = new MultiPlayerLevel(this, new LevelSettings(packet->seed, GameType::byId(packet->gameType), false, false, packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
level = new MultiPlayerLevel(this, new LevelSettings(packet->seed, GameType::byId(packet->gameType), false, packet->m_isHardcore, packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
// 4J Stu - We want to share the SavedDataStorage between levels
int otherDimensionId = packet->dimension == 0 ? -1 : 0;
@@ -377,6 +416,7 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)
BYTE networkSmallId = getSocket()->getSmallId();
app.UpdatePlayerInfo(networkSmallId, packet->m_playerIndex, packet->m_uiGamePrivileges);
app.SetPlayerMapIcon(minecraft->player->getName().c_str(), computePlayerMapFrame(packet->clientVersion, packet->m_playerIndex));
minecraft->player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_All, packet->m_uiGamePrivileges);
// Assume all privileges are on, so that the first message we see only indicates things that have been turned off
@@ -411,7 +451,7 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)
activeLevel = minecraft->getLevel(otherDimensionId);
}
MultiPlayerLevel *dimensionLevel = new MultiPlayerLevel(this, new LevelSettings(packet->seed, GameType::byId(packet->gameType), false, false, packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
MultiPlayerLevel *dimensionLevel = new MultiPlayerLevel(this, new LevelSettings(packet->seed, GameType::byId(packet->gameType), false, packet->m_isHardcore, packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
dimensionLevel->savedDataStorage = activeLevel->savedDataStorage;
@@ -447,6 +487,7 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)
BYTE networkSmallId = getSocket()->getSmallId();
app.UpdatePlayerInfo(networkSmallId, packet->m_playerIndex, packet->m_uiGamePrivileges);
app.SetPlayerMapIcon(player->getName().c_str(), computePlayerMapFrame(packet->clientVersion, packet->m_playerIndex));
player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_All, packet->m_uiGamePrivileges);
// Assume all privileges are on, so that the first message we see only indicates things that have been turned off
@@ -917,6 +958,36 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> packet)
}
}
// Client-side registration: if we still have no IQNet entry for this remote
// player, create one so they appear in the Tab player list.
// Find the first available IQNet slot (customData == 0, skip slot 0 which
// is the host). We can't use packet->m_playerIndex directly because on
// dedicated servers the game-level player index starts at 0 for real
// players, conflicting with the IQNet host slot.
if (matchedQNetPlayer == nullptr)
{
for (int s = 1; s < MINECRAFT_NET_MAX_PLAYERS; ++s)
{
IQNetPlayer* qp = &IQNet::m_player[s];
if (qp->GetCustomDataValue() == 0 && qp->m_gamertag[0] == 0)
{
BYTE smallId = static_cast<BYTE>(s);
qp->m_smallId = smallId;
qp->m_isRemote = true;
qp->m_isHostPlayer = false;
qp->m_resolvedXuid = pktXuid;
wcsncpy_s(qp->m_gamertag, 32, packet->name.c_str(), _TRUNCATE);
if (smallId >= IQNet::s_playerCount)
IQNet::s_playerCount = smallId + 1;
extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager;
g_pPlatformNetworkManager->NotifyPlayerJoined(qp);
matchedQNetPlayer = qp;
break;
}
}
}
if (matchedQNetPlayer != nullptr)
{
// Store packet-authoritative XUID on this network slot so later lookups by XUID
@@ -946,6 +1017,7 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> packet)
player->setPlayerIndex( packet->m_playerIndex );
player->setCustomSkin( packet->m_skinId );
player->setCustomCape( packet->m_capeId );
app.SetPlayerMapIcon(packet->name.c_str(), computePlayerMapFrame(packet->id, packet->m_playerIndex));
player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_All, packet->m_uiGamePrivileges);
if (!player->customTextureUrl.empty() && player->customTextureUrl.substr(0, 3).compare(L"def") != 0 && !app.IsFileInMemoryTextures(player->customTextureUrl))
@@ -1083,33 +1155,35 @@ void ClientConnection::handleMoveEntitySmall(shared_ptr<MoveEntityPacketSmall> p
void ClientConnection::handleRemoveEntity(shared_ptr<RemoveEntitiesPacket> packet)
{
#ifdef _WINDOWS64
if (!g_NetworkManager.IsHost())
// On fork servers, IQNet cleanup is handled by the MC|ForkPLeave custom
// payload so players stay in Tab regardless of render distance. On
// upstream servers (no MC|ForkHello received), fall back to the old
// behaviour of cleaning up IQNet here.
if (!m_isForkServer && !g_NetworkManager.IsHost())
{
for (int i = 0; i < packet->ids.length; i++)
{
shared_ptr<Entity> entity = getEntity(packet->ids[i]);
if (entity != nullptr && entity->GetType() == eTYPE_PLAYER)
if (entity != nullptr)
{
shared_ptr<Player> player = dynamic_pointer_cast<Player>(entity);
if (player != nullptr)
{
PlayerUID xuid = player->getXuid();
INetworkPlayer* np = g_NetworkManager.GetPlayerByXuid(xuid);
if (np != nullptr)
for (int s = 1; s < MINECRAFT_NET_MAX_PLAYERS; ++s)
{
NetworkPlayerXbox* npx = (NetworkPlayerXbox*)np;
IQNetPlayer* qp = npx->GetQNetPlayer();
if (qp != nullptr)
IQNetPlayer* qp = &IQNet::m_player[s];
if (qp->GetCustomDataValue() != 0 &&
_wcsicmp(qp->m_gamertag, player->getName().c_str()) == 0)
{
extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager;
g_pPlatformNetworkManager->NotifyPlayerLeaving(qp);
qp->m_smallId = 0;
qp->m_isRemote = false;
qp->m_isHostPlayer = false;
// Clear resolved id to avoid stale XUID -> player matches after disconnect.
qp->m_resolvedXuid = INVALID_XUID;
qp->m_gamertag[0] = 0;
qp->SetCustomDataValue(0);
break;
}
}
}
@@ -2883,7 +2957,7 @@ void ClientConnection::handleRespawn(shared_ptr<RespawnPacket> packet)
MultiPlayerLevel *dimensionLevel = (MultiPlayerLevel *)minecraft->getLevel( packet->dimension );
if( dimensionLevel == nullptr )
{
dimensionLevel = new MultiPlayerLevel(this, new LevelSettings(packet->mapSeed, packet->playerGameType, false, minecraft->level->getLevelData()->isHardcore(), packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
dimensionLevel = new MultiPlayerLevel(this, new LevelSettings(packet->mapSeed, packet->playerGameType, false, packet->m_isHardcore, packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
// 4J Stu - We want to shared the savedDataStorage between both levels
//if( dimensionLevel->savedDataStorage != nullptr )
@@ -3328,7 +3402,9 @@ void ClientConnection::handleTileEditorOpen(shared_ptr<TileEditorOpenPacket> pac
void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
{
app.DebugPrintf("ClientConnection::handleSignUpdate - ");
app.DebugPrintf("[SIGN] handleSignUpdate at (%d, %d, %d):\n", packet->x, packet->y, packet->z);
for (int i = 0; i < MAX_SIGN_LINES; i++)
app.DebugPrintf("[SIGN] Line%d: \"%ls\"\n", i+1, packet->lines[i].c_str());
if (minecraft->level->hasChunkAt(packet->x, packet->y, packet->z))
{
shared_ptr<TileEntity> te = minecraft->level->getTileEntity(packet->x, packet->y, packet->z);
@@ -3342,7 +3418,7 @@ void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
ste->SetMessage(i,packet->lines[i]);
}
app.DebugPrintf("verified = %d\tCensored = %d\n",packet->m_bVerified,packet->m_bCensored);
app.DebugPrintf("[SIGN] verified=%d censored=%d\n", packet->m_bVerified, packet->m_bCensored);
ste->SetVerified(packet->m_bVerified);
ste->SetCensored(packet->m_bCensored);
@@ -3350,12 +3426,12 @@ void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
}
else
{
app.DebugPrintf("dynamic_pointer_cast<SignTileEntity>(te) == nullptr\n");
app.DebugPrintf("[SIGN] ERROR: tile entity is not a SignTileEntity\n");
}
}
else
{
app.DebugPrintf("hasChunkAt failed\n");
app.DebugPrintf("[SIGN] ERROR: chunk not loaded at position\n");
}
}
@@ -3742,6 +3818,137 @@ void ClientConnection::handleSoundEvent(shared_ptr<LevelSoundPacket> packet)
void ClientConnection::handleCustomPayload(shared_ptr<CustomPayloadPacket> customPayloadPacket)
{
#ifdef _WINDOWS64
// Build a server-specific identity token file path next to the executable.
// Each server gets its own token file based on a hash of the server address,
// so connecting to multiple secured servers doesn't overwrite tokens.
auto buildIdentityTokenPath = []() -> std::string {
char exePath[MAX_PATH] = {};
DWORD len = GetModuleFileNameA(NULL, exePath, MAX_PATH);
if (len == 0 || len >= MAX_PATH) return std::string();
char *lastSlash = strrchr(exePath, '\\');
if (lastSlash != NULL) *(lastSlash + 1) = 0;
// Hash the server IP:port to create a unique filename per server
char serverAddr[300] = {};
sprintf_s(serverAddr, sizeof(serverAddr), "%s:%d", g_Win64MultiplayerIP, g_Win64MultiplayerPort);
unsigned int hash = 5381;
for (const char *p = serverAddr; *p; ++p)
hash = ((hash << 5) + hash) + static_cast<unsigned char>(*p);
char filename[64] = {};
sprintf_s(filename, sizeof(filename), "identity-token-%08x.dat", hash);
return std::string(exePath) + filename;
};
// Identity token: server issued us a new token - store it locally
if (CustomPayloadPacket::IDENTITY_TOKEN_ISSUE.compare(customPayloadPacket->identifier) == 0)
{
if (customPayloadPacket->data.data != nullptr && customPayloadPacket->length == 32)
{
std::string tokenPath = buildIdentityTokenPath();
if (!tokenPath.empty())
{
FILE *f = nullptr;
fopen_s(&f, tokenPath.c_str(), "wb");
if (f != nullptr)
{
size_t written = fwrite(customPayloadPacket->data.data, 1, 32, f);
fclose(f);
if (written == 32)
{
app.DebugPrintf("Client: Stored identity token to %s\n", tokenPath.c_str());
}
else
{
app.DebugPrintf("Client: Failed to write full identity token (wrote %zu/32)\n", written);
}
}
else
{
app.DebugPrintf("Client: Failed to open %s for writing\n", tokenPath.c_str());
}
}
}
return;
}
// Identity token: server is challenging us to present our stored token
if (CustomPayloadPacket::IDENTITY_TOKEN_CHALLENGE.compare(customPayloadPacket->identifier) == 0)
{
std::string tokenPath = buildIdentityTokenPath();
FILE *f = nullptr;
if (!tokenPath.empty())
fopen_s(&f, tokenPath.c_str(), "rb");
if (f != nullptr)
{
uint8_t token[32] = {};
size_t bytesRead = fread(token, 1, 32, f);
fclose(f);
if (bytesRead == 32)
{
byteArray tokenData(32);
memcpy(tokenData.data, token, 32);
connection->send(std::make_shared<CustomPayloadPacket>(
CustomPayloadPacket::IDENTITY_TOKEN_RESPONSE, tokenData));
app.DebugPrintf("Client: Sent identity token response\n");
}
else
{
app.DebugPrintf("Client: identity-token.dat is invalid (%zu bytes)\n", bytesRead);
connection->send(std::make_shared<CustomPayloadPacket>(
CustomPayloadPacket::IDENTITY_TOKEN_RESPONSE, byteArray()));
}
SecureZeroMemory(token, sizeof(token));
}
else
{
app.DebugPrintf("Client: No identity-token.dat found, sending empty response\n");
connection->send(std::make_shared<CustomPayloadPacket>(
CustomPayloadPacket::IDENTITY_TOKEN_RESPONSE, byteArray()));
}
return;
}
// Fork server identification: enables render-distance-independent player list
if (CustomPayloadPacket::FORK_HELLO_CHANNEL.compare(customPayloadPacket->identifier) == 0)
{
m_isForkServer = true;
app.DebugPrintf("Client: Connected to fork server\n");
return;
}
// Fork server player leave: clean up IQNet slot so player leaves Tab list
if (CustomPayloadPacket::FORK_PLAYER_LEAVE_CHANNEL.compare(customPayloadPacket->identifier) == 0)
{
if (customPayloadPacket->data.data != nullptr && customPayloadPacket->length > 0)
{
int nameLen = customPayloadPacket->length / static_cast<int>(sizeof(wchar_t));
wstring leavingName(reinterpret_cast<const wchar_t*>(customPayloadPacket->data.data), nameLen);
for (int s = 1; s < MINECRAFT_NET_MAX_PLAYERS; ++s)
{
IQNetPlayer* qp = &IQNet::m_player[s];
if (qp->GetCustomDataValue() != 0 &&
_wcsicmp(qp->m_gamertag, leavingName.c_str()) == 0)
{
extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager;
g_pPlatformNetworkManager->NotifyPlayerLeaving(qp);
qp->m_smallId = 0;
qp->m_isRemote = false;
qp->m_isHostPlayer = false;
qp->m_resolvedXuid = INVALID_XUID;
qp->m_gamertag[0] = 0;
qp->SetCustomDataValue(0);
app.DebugPrintf("Client: Player \"%ls\" left fork server, cleared IQNet slot %d\n", leavingName.c_str(), s);
break;
}
}
}
return;
}
#endif
if (CustomPayloadPacket::TRADER_LIST_PACKET.compare(customPayloadPacket->identifier) == 0)
{
ByteArrayInputStream bais(customPayloadPacket->data);
@@ -3774,6 +3981,52 @@ void ClientConnection::handleCustomPayload(shared_ptr<CustomPayloadPacket> custo
trader->overrideOffers(recipeList);
}
}
else if (CustomPayloadPacket::ENCHANTMENT_LIST_PACKET.compare(customPayloadPacket->identifier) == 0) {
ByteArrayInputStream bais(customPayloadPacket->data);
DataInputStream input(&bais);
bool done = false;
int l = 0;
bool firstInGroup = true;
EnchantmentEntry temp;
//int firstAmount = input.readInt();
while (!done) {
int a = input.readInt();
if (a == -1) {
minecraft->localplayers[m_userIndex]->enchantmentEntries[l] = temp;
l++;
firstInGroup = true;
}
else if (a == -2) {
done = true;
}
else if (a == -4) {
for (int i = 0; i < 3; i++) {
minecraft->localplayers[m_userIndex]->enchantmentEntries[i].id = -3;
}
done = true;
}
else {
if (firstInGroup) {
temp.id = a;
temp.level = input.readInt();
firstInGroup = false;
}
else {
input.readInt();
}
}
}
}
else if (CustomPayloadPacket::UPDATE_RECIPE_REGISTRY.compare(customPayloadPacket->identifier) == 0) {
this->m_recivedRecipeRegistyUpdate = true;
Recipes::getInstance()->loadFromPacket(customPayloadPacket->data);
}
else if (CustomPayloadPacket::UPDATE_CREATIVE_REGISTRY.compare(customPayloadPacket->identifier) == 0) {
this->m_recivedCreativeRegistyUpdate = true;
IUIScene_CreativeMenu::loadFromPacket(customPayloadPacket->data);
}
}
Connection *ClientConnection::getConnection()