Merge branch 'smartcmd:main' into main

This commit is contained in:
Racc 2026-03-08 21:31:21 +00:00 committed by GitHub
commit 4476b4df8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 1764 additions and 399 deletions

View file

@ -142,6 +142,13 @@ ClientConnection::ClientConnection(Minecraft *minecraft, Socket *socket, int iUs
deferredEntityLinkPackets = vector<DeferredEntityLinkPacket>();
}
bool ClientConnection::isPrimaryConnection() const
{
// On host, all connections are primary (server is authoritative).
// On non-host, only the primary pad processes shared entity state.
return g_NetworkManager.IsHost() || m_userIndex == ProfileManager.GetPrimaryPad();
}
ClientConnection::~ClientConnection()
{
delete connection;
@ -304,6 +311,10 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)
level->isClientSide = true;
minecraft->setLevel(level);
}
else
{
level = (MultiPlayerLevel *)dimensionLevel;
}
minecraft->player->setPlayerIndex( packet->m_playerIndex );
minecraft->player->setCustomSkin( app.GetPlayerSkinId(m_userIndex) );
@ -705,6 +716,7 @@ void ClientConnection::handleAddExperienceOrb(shared_ptr<AddExperienceOrbPacket>
void ClientConnection::handleAddGlobalEntity(shared_ptr<AddGlobalEntityPacket> packet)
{
if (!isPrimaryConnection()) return;
double x = packet->x / 32.0;
double y = packet->y / 32.0;
double z = packet->z / 32.0;
@ -730,6 +742,13 @@ void ClientConnection::handleAddPainting(shared_ptr<AddPaintingPacket> packet)
void ClientConnection::handleSetEntityMotion(shared_ptr<SetEntityMotionPacket> packet)
{
if (!isPrimaryConnection())
{
// Secondary connection: only accept motion for our own local player (knockback)
if (minecraft->localplayers[m_userIndex] == NULL ||
packet->id != minecraft->localplayers[m_userIndex]->entityId)
return;
}
shared_ptr<Entity> e = getEntity(packet->id);
if (e == NULL) return;
e->lerpMotion(packet->xa / 8000.0, packet->ya / 8000.0, packet->za / 8000.0);
@ -953,6 +972,7 @@ void ClientConnection::handleSetCarriedItem(shared_ptr<SetCarriedItemPacket> pac
void ClientConnection::handleMoveEntity(shared_ptr<MoveEntityPacket> packet)
{
if (!isPrimaryConnection()) return;
shared_ptr<Entity> e = getEntity(packet->id);
if (e == NULL) return;
e->xp += packet->xa;
@ -982,6 +1002,7 @@ void ClientConnection::handleRotateMob(shared_ptr<RotateHeadPacket> packet)
void ClientConnection::handleMoveEntitySmall(shared_ptr<MoveEntityPacketSmall> packet)
{
if (!isPrimaryConnection()) return;
shared_ptr<Entity> e = getEntity(packet->id);
if (e == NULL) return;
e->xp += packet->xa;
@ -1106,6 +1127,7 @@ void ClientConnection::handleMovePlayer(shared_ptr<MovePlayerPacket> packet)
// 4J Added
void ClientConnection::handleChunkVisibilityArea(shared_ptr<ChunkVisibilityAreaPacket> packet)
{
if (level == NULL) return;
for(int z = packet->m_minZ; z <= packet->m_maxZ; ++z)
for(int x = packet->m_minX; x <= packet->m_maxX; ++x)
level->setChunkVisible(x, z, true);
@ -1113,11 +1135,13 @@ void ClientConnection::handleChunkVisibilityArea(shared_ptr<ChunkVisibilityAreaP
void ClientConnection::handleChunkVisibility(shared_ptr<ChunkVisibilityPacket> packet)
{
if (level == NULL) return;
level->setChunkVisible(packet->x, packet->z, packet->visible);
}
void ClientConnection::handleChunkTilesUpdate(shared_ptr<ChunkTilesUpdatePacket> packet)
{
if (!isPrimaryConnection()) return;
// 4J - changed to encode level in packet
MultiPlayerLevel *dimensionLevel = (MultiPlayerLevel *)minecraft->levels[packet->levelIdx];
if( dimensionLevel )
@ -1187,16 +1211,29 @@ void ClientConnection::handleChunkTilesUpdate(shared_ptr<ChunkTilesUpdatePacket>
void ClientConnection::handleBlockRegionUpdate(shared_ptr<BlockRegionUpdatePacket> packet)
{
if (!isPrimaryConnection()) return;
// 4J - changed to encode level in packet
MultiPlayerLevel *dimensionLevel = (MultiPlayerLevel *)minecraft->levels[packet->levelIdx];
if( dimensionLevel )
{
PIXBeginNamedEvent(0,"Handle block region update");
if(packet->bIsFullChunk && packet->ys == 0)
{
app.DebugPrintf("[BRUP-CLIENT] *** EMPTY FULL CHUNK received at (%d,%d)! Buffer length=%d\n",
packet->x>>4, packet->z>>4, packet->buffer.length);
}
int y1 = packet->y + packet->ys;
if(packet->bIsFullChunk)
{
y1 = Level::maxBuildHeight;
// Ensure the chunk exists in the cache before writing data.
// The ChunkVisibilityAreaPacket that creates chunks can arrive AFTER the first BRUP,
// causing getChunk() to return EmptyLevelChunk (whose setBlocksAndData is a no-op).
dimensionLevel->setChunkVisible(packet->x >> 4, packet->z >> 4, true);
if(packet->buffer.length > 0)
{
PIXBeginNamedEvent(0, "Reordering to XZY");
@ -1235,6 +1272,7 @@ void ClientConnection::handleBlockRegionUpdate(shared_ptr<BlockRegionUpdatePacke
void ClientConnection::handleTileUpdate(shared_ptr<TileUpdatePacket> packet)
{
if (!isPrimaryConnection()) return;
// 4J added - using a block of 255 to signify that this is a packet for destroying a tile, where we need to inform the level renderer that we are about to do so.
// This is used in creative mode as the point where a tile is first destroyed at the client end of things. Packets formed like this are potentially sent from
// ServerPlayerGameMode::destroyBlock
@ -1349,6 +1387,7 @@ void ClientConnection::send(shared_ptr<Packet> packet)
void ClientConnection::handleTakeItemEntity(shared_ptr<TakeItemEntityPacket> packet)
{
if (!isPrimaryConnection()) return;
shared_ptr<Entity> from = getEntity(packet->itemId);
shared_ptr<LivingEntity> to = dynamic_pointer_cast<LivingEntity>(getEntity(packet->playerId));
@ -2847,31 +2886,34 @@ void ClientConnection::handleRespawn(shared_ptr<RespawnPacket> packet)
void ClientConnection::handleExplosion(shared_ptr<ExplodePacket> packet)
{
if(!packet->m_bKnockbackOnly)
// World modification (block destruction) must only happen once
if (isPrimaryConnection())
{
//app.DebugPrintf("Received ExplodePacket with explosion data\n");
PIXBeginNamedEvent(0,"Handling explosion");
Explosion *e = new Explosion(minecraft->level, nullptr, packet->x, packet->y, packet->z, packet->r);
PIXBeginNamedEvent(0,"Finalizing");
if(!packet->m_bKnockbackOnly)
{
//app.DebugPrintf("Received ExplodePacket with explosion data\n");
PIXBeginNamedEvent(0,"Handling explosion");
Explosion *e = new Explosion(minecraft->level, nullptr, packet->x, packet->y, packet->z, packet->r);
PIXBeginNamedEvent(0,"Finalizing");
// Fix for #81758 - TCR 006 BAS Non-Interactive Pause: TU9: Performance: Gameplay: After detonating bunch of TNT, game enters unresponsive state for couple of seconds.
// The changes we are making here have been decided by the server, so we don't need to add them to the vector that resets tiles changes made
// on the client as we KNOW that the server is matching these changes
MultiPlayerLevel *mpLevel = (MultiPlayerLevel *)minecraft->level;
mpLevel->enableResetChanges(false);
// 4J - now directly pass a pointer to the toBlow array in the packet rather than copying around
e->finalizeExplosion(true, &packet->toBlow);
mpLevel->enableResetChanges(true);
PIXEndNamedEvent();
PIXEndNamedEvent();
delete e;
}
else
{
//app.DebugPrintf("Received ExplodePacket with knockback only data\n");
// Fix for #81758 - TCR 006 BAS Non-Interactive Pause: TU9: Performance: Gameplay: After detonating bunch of TNT, game enters unresponsive state for couple of seconds.
// The changes we are making here have been decided by the server, so we don't need to add them to the vector that resets tiles changes made
// on the client as we KNOW that the server is matching these changes
MultiPlayerLevel *mpLevel = (MultiPlayerLevel *)minecraft->level;
mpLevel->enableResetChanges(false);
// 4J - now directly pass a pointer to the toBlow array in the packet rather than copying around
e->finalizeExplosion(true, &packet->toBlow);
mpLevel->enableResetChanges(true);
PIXEndNamedEvent();
PIXEndNamedEvent();
delete e;
}
}
// Per-player knockback — each connection applies to its own local player
//app.DebugPrintf("Adding knockback (%f,%f,%f) for player %d\n", packet->getKnockbackX(), packet->getKnockbackY(), packet->getKnockbackZ(), m_userIndex);
if (minecraft->localplayers[m_userIndex] == NULL)
return;
minecraft->localplayers[m_userIndex]->xd += packet->getKnockbackX();
minecraft->localplayers[m_userIndex]->yd += packet->getKnockbackY();
minecraft->localplayers[m_userIndex]->zd += packet->getKnockbackZ();
@ -2881,6 +2923,8 @@ void ClientConnection::handleContainerOpen(shared_ptr<ContainerOpenPacket> packe
{
bool failed = false;
shared_ptr<MultiplayerLocalPlayer> player = minecraft->localplayers[m_userIndex];
if (player == NULL)
return;
switch(packet->type)
{
case ContainerOpenPacket::BONUS_CHEST:
@ -3187,6 +3231,7 @@ void ClientConnection::handleTileEditorOpen(shared_ptr<TileEditorOpenPacket> pac
void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
{
if (!isPrimaryConnection()) return;
app.DebugPrintf("ClientConnection::handleSignUpdate - ");
if (minecraft->level->hasChunkAt(packet->x, packet->y, packet->z))
{
@ -3220,6 +3265,7 @@ void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
void ClientConnection::handleTileEntityData(shared_ptr<TileEntityDataPacket> packet)
{
if (!isPrimaryConnection()) return;
if (minecraft->level->hasChunkAt(packet->x, packet->y, packet->z))
{
shared_ptr<TileEntity> te = minecraft->level->getTileEntity(packet->x, packet->y, packet->z);
@ -3272,6 +3318,7 @@ void ClientConnection::handleContainerClose(shared_ptr<ContainerClosePacket> pac
void ClientConnection::handleTileEvent(shared_ptr<TileEventPacket> packet)
{
if (!isPrimaryConnection()) return;
PIXBeginNamedEvent(0,"Handle tile event\n");
minecraft->level->tileEvent(packet->x, packet->y, packet->z, packet->tile, packet->b0, packet->b1);
PIXEndNamedEvent();
@ -3279,6 +3326,7 @@ void ClientConnection::handleTileEvent(shared_ptr<TileEventPacket> packet)
void ClientConnection::handleTileDestruction(shared_ptr<TileDestructionPacket> packet)
{
if (!isPrimaryConnection()) return;
minecraft->level->destroyTileProgress(packet->getEntityId(), packet->getX(), packet->getY(), packet->getZ(), packet->getState());
}
@ -3360,6 +3408,7 @@ void ClientConnection::handleGameEvent(shared_ptr<GameEventPacket> gameEventPack
void ClientConnection::handleComplexItemData(shared_ptr<ComplexItemDataPacket> packet)
{
if (!isPrimaryConnection()) return;
if (packet->itemType == Item::map->id)
{
MapItem::getSavedData(packet->itemId, minecraft->level)->handleComplexItemData(packet->data);
@ -3374,6 +3423,7 @@ void ClientConnection::handleComplexItemData(shared_ptr<ComplexItemDataPacket> p
void ClientConnection::handleLevelEvent(shared_ptr<LevelEventPacket> packet)
{
if (!isPrimaryConnection()) return;
if (packet->type == LevelEvent::SOUND_DRAGON_DEATH)
{
for(unsigned int i = 0; i < XUSER_MAX_COUNT; ++i)
@ -3597,6 +3647,7 @@ void ClientConnection::handlePlayerAbilities(shared_ptr<PlayerAbilitiesPacket> p
void ClientConnection::handleSoundEvent(shared_ptr<LevelSoundPacket> packet)
{
if (!isPrimaryConnection()) return;
minecraft->level->playLocalSound(packet->getX(), packet->getY(), packet->getZ(), packet->getSound(), packet->getVolume(), packet->getPitch(), false);
}
@ -3909,6 +3960,7 @@ void ClientConnection::handleSetPlayerTeamPacket(shared_ptr<SetPlayerTeamPacket>
void ClientConnection::handleParticleEvent(shared_ptr<LevelParticlesPacket> packet)
{
if (!isPrimaryConnection()) return;
for (int i = 0; i < packet->getCount(); i++)
{
double xVarience = random->nextGaussian() * packet->getXDist();

View file

@ -43,6 +43,7 @@ public:
private:
DWORD m_userIndex; // 4J Added
bool isPrimaryConnection() const;
public:
SavedDataStorage *savedDataStorage;
ClientConnection(Minecraft *minecraft, const wstring& ip, int port);

View file

@ -41,6 +41,11 @@
#include "..\Minecraft.World\DurangoStats.h"
#endif
#ifdef _WINDOWS64
#include "..\..\Windows64\Network\WinsockNetLayer.h"
#include "..\..\Windows64\Windows64_Xuid.h"
#endif
// Global instance
CGameNetworkManager g_NetworkManager;
CPlatformNetworkManager *CGameNetworkManager::s_pPlatformNetworkManager;
@ -1501,6 +1506,45 @@ void CGameNetworkManager::CreateSocket( INetworkPlayer *pNetworkPlayer, bool loc
}
else
{
#ifdef _WINDOWS64
// Non-host split-screen: open a dedicated TCP connection for this pad
if (localPlayer && !g_NetworkManager.IsHost() && g_NetworkManager.IsInGameplay())
{
int padIdx = pNetworkPlayer->GetUserIndex();
BYTE assignedSmallId = 0;
if (!WinsockNetLayer::JoinSplitScreen(padIdx, &assignedSmallId))
{
app.DebugPrintf("Split-screen pad %d: failed to open TCP to host\n", padIdx);
pMinecraft->connectionDisconnected(padIdx, DisconnectPacket::eDisconnect_ConnectionCreationFailed);
return;
}
// Update the local IQNetPlayer (at pad index) with the host-assigned smallId.
// The NetworkPlayerXbox created by NotifyPlayerJoined already points to
// m_player[padIdx], so we just set the smallId for network routing.
IQNet::m_player[padIdx].m_smallId = assignedSmallId;
IQNet::m_player[padIdx].m_resolvedXuid = Win64Xuid::DeriveXuidForPad(Win64Xuid::ResolvePersistentXuid(), padIdx);
// Network socket (not hostLocal) — data goes through TCP via GetLocalSocket
socket = new Socket(pNetworkPlayer, false, false);
pNetworkPlayer->SetSocket(socket);
ClientConnection* connection = new ClientConnection(pMinecraft, socket, padIdx);
if (connection->createdOk)
{
connection->send(shared_ptr<PreLoginPacket>(new PreLoginPacket(pNetworkPlayer->GetOnlineName())));
pMinecraft->addPendingLocalConnection(padIdx, connection);
}
else
{
pMinecraft->connectionDisconnected(padIdx, DisconnectPacket::eDisconnect_ConnectionCreationFailed);
delete connection;
}
return;
}
#endif
socket = new Socket( pNetworkPlayer, g_NetworkManager.IsHost(), g_NetworkManager.IsHost() && localPlayer );
pNetworkPlayer->SetSocket( socket );

View file

@ -243,10 +243,16 @@ void CPlatformNetworkManagerStub::DoWork()
if (IQNet::s_playerCount > 1)
IQNet::s_playerCount--;
}
// Always return smallId to the free pool so it can be reused (game may have already cleared the slot).
WinsockNetLayer::PushFreeSmallId(disconnectedSmallId);
// Clear O(1) socket lookup so GetSocketForSmallId stays fast (s_connections never shrinks).
WinsockNetLayer::ClearSocketForSmallId(disconnectedSmallId);
// NOTE: Do NOT call PushFreeSmallId here. The old PlayerConnection's
// write thread may still be alive (it dies in PlayerList::tick when
// m_smallIdsToClose is processed). If we recycle the smallId now,
// AcceptThread can reuse it for a new connection, and the old write
// thread's getPlayer() lookup will resolve to the NEW player, sending
// stale game packets to the new client's TCP socket — corrupting its
// login handshake (bad packet id crash). PushFreeSmallId and
// ClearSocketForSmallId are called from PlayerList::tick after the
// old Connection threads are dead.
//
// Clear chunk visibility flags for this system so rejoin gets fresh chunk state.
SystemFlagRemoveBySmallId((int)disconnectedSmallId);
}
@ -289,12 +295,40 @@ int CPlatformNetworkManagerStub::GetLocalPlayerMask(int playerIndex)
bool CPlatformNetworkManagerStub::AddLocalPlayerByUserIndex( int userIndex )
{
NotifyPlayerJoined(m_pIQNet->GetLocalPlayerByUserIndex(userIndex));
return ( m_pIQNet->AddLocalPlayerByUserIndex(userIndex) == S_OK );
if ( m_pIQNet->AddLocalPlayerByUserIndex(userIndex) != S_OK )
return false;
// Player is now registered in IQNet — get a pointer and notify the network layer.
// Use the static array directly: GetLocalPlayerByUserIndex checks customData which
// isn't set until addNetworkPlayer runs inside NotifyPlayerJoined.
NotifyPlayerJoined(&IQNet::m_player[userIndex]);
return true;
}
bool CPlatformNetworkManagerStub::RemoveLocalPlayerByUserIndex( int userIndex )
{
#ifdef _WINDOWS64
if (userIndex > 0 && userIndex < XUSER_MAX_COUNT && !m_pIQNet->IsHost())
{
IQNetPlayer* qp = &IQNet::m_player[userIndex];
// Notify the network layer before clearing the slot
if (qp->GetCustomDataValue() != 0)
{
NotifyPlayerLeaving(qp);
}
// Close the split-screen TCP connection and reset WinsockNetLayer state
WinsockNetLayer::CloseSplitScreenConnection(userIndex);
// Clear the IQNet slot so it can be reused on rejoin
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);
}
#endif
return true;
}

View file

@ -481,25 +481,15 @@ void IUIScene_AbstractContainerMenu::onMouseTick()
#endif
#ifdef _WINDOWS64
if (!g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive())
if (iPad == 0 && !g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive())
{
int deltaX = g_KBMInput.GetMouseDeltaX();
int deltaY = g_KBMInput.GetMouseDeltaY();
extern HWND g_hWnd;
RECT rc;
GetClientRect(g_hWnd, &rc);
int winW = rc.right - rc.left;
int winH = rc.bottom - rc.top;
if (winW > 0 && winH > 0)
{
float scaleX = (float)getMovieWidth() / (float)winW;
float scaleY = (float)getMovieHeight() / (float)winH;
vPointerPos.x += (float)deltaX * scaleX;
vPointerPos.y += (float)deltaY * scaleY;
}
float scaleX, scaleY;
getMouseToSWFScale(scaleX, scaleY);
vPointerPos.x += (float)deltaX * scaleX;
vPointerPos.y += (float)deltaY * scaleY;
if (deltaX != 0 || deltaY != 0)
{

View file

@ -277,4 +277,5 @@ public:
virtual int getPad() = 0;
virtual int getMovieWidth() = 0;
virtual int getMovieHeight() = 0;
virtual void getMouseToSWFScale(float &scaleX, float &scaleY) = 0;
};

View file

@ -250,15 +250,22 @@ rrbool UIBitmapFont::GetGlyphBitmap(S32 glyph,F32 pixel_scale,IggyBitmapCharacte
// Choose a reasonable glyph scale.
float glyphScale = 1.0f, truePixelScale = 1.0f / m_cFontData->getFontData()->m_fAdvPerPixel;
F32 targetPixelScale = pixel_scale;
//if(!RenderManager.IsWidescreen())
//{
// // Fix for different scales in 480
// targetPixelScale = pixel_scale*2/3;
//}
while ( (0.5f + glyphScale) * truePixelScale < targetPixelScale)
while ( (0.5f + glyphScale) * truePixelScale < pixel_scale)
glyphScale++;
// Debug: log each unique (font, pixel_scale) pair
{
static std::unordered_set<int> s_loggedScaleKeys;
// Encode font pointer + quantized scale into a key to log each combo once
int scaleKey = (int)(pixel_scale * 100.0f) ^ (int)(uintptr_t)m_cFontData;
if (s_loggedScaleKeys.find(scaleKey) == s_loggedScaleKeys.end() && s_loggedScaleKeys.size() < 50) {
s_loggedScaleKeys.insert(scaleKey);
float tps = truePixelScale;
app.DebugPrintf("[FONT-DBG] GetGlyphBitmap: font=%s glyph=%d pixel_scale=%.3f truePixelScale=%.1f glyphScale=%.0f\n",
m_cFontData->getFontName().c_str(), glyph, pixel_scale, tps, glyphScale);
}
}
// 4J-JEV: Debug code to check which font sizes are being used.
#if (!defined _CONTENT_PACKAGE) && (VERBOSE_FONT_OUTPUT > 0)
@ -303,9 +310,6 @@ rrbool UIBitmapFont::GetGlyphBitmap(S32 glyph,F32 pixel_scale,IggyBitmapCharacte
}
#endif
//app.DebugPrintf("Request glyph_%d (U+%.4X) at %f, converted to %f (%f)\n",
// glyph, GetUnicode(glyph), pixel_scale, targetPixelScale, glyphScale);
// It is not necessary to shrink the glyph width here
// as its already been done in 'GetGlyphMetrics' by:
// > metrics->x1 = m_kerningTable[glyph] * ratio;
@ -324,27 +328,57 @@ rrbool UIBitmapFont::GetGlyphBitmap(S32 glyph,F32 pixel_scale,IggyBitmapCharacte
bitmap->top_left_y = -((S32) m_cFontData->getFontData()->m_uiGlyphHeight) * m_cFontData->getFontData()->m_fAscent;
bitmap->oversample = 0;
bitmap->point_sample = true;
// 4J-JEV:
// pixel_scale == font size chosen in flash.
// bitmap->pixel_scale_correct = (float) m_glyphHeight; // Scales the glyph to desired size.
// bitmap->pixel_scale_correct = pixel_scale; // Always the same size (not desired size).
// bitmap->pixel_scale_correct = pixel_scale * 0.5; // Doubles original size.
// bitmap->pixel_scale_correct = pixel_scale * 2; // Halves original size.
// Actual scale, and possible range of scales.
bitmap->pixel_scale_correct = pixel_scale / glyphScale;
bitmap->pixel_scale_max = 99.0f;
bitmap->pixel_scale_min = 0.0f;
/* 4J-JEV: Some of Sean's code.
int glyphScaleMin = 1;
int glyphScaleMax = 3;
float actualScale = pixel_scale / glyphScale;
bitmap->pixel_scale_correct = actualScale;
bitmap->pixel_scale_min = actualScale * glyphScaleMin * 0.999f;
bitmap->pixel_scale_max = actualScale * glyphScaleMax * 1.001f; */
#ifdef _WINDOWS64
// On Windows64 the window can be any size, producing fractional
// pixel_scale values that don't align to integer multiples of
// truePixelScale. The original console code cached glyphs with a
// broad [truePixelScale, 99] range in the "normal" branch, which
// works on consoles (fixed 1080p — font sizes are exact multiples)
// but causes cache pollution on Windows: the first glyph cached in
// that range sets pixel_scale_correct for ALL subsequent requests,
// so different font sizes get scaled by wrong ratios, producing
// mixed letter sizes on screen.
//
// Fix: always use pixel_scale_correct = truePixelScale so every
// cache entry is consistent. Two ranges: downscale (bilinear for
// smooth reduction) and upscale (point_sample for crisp pixel-art).
bitmap->pixel_scale_correct = truePixelScale;
if (pixel_scale < truePixelScale)
{
bitmap->pixel_scale_min = 0.0f;
bitmap->pixel_scale_max = truePixelScale;
bitmap->point_sample = false;
}
else
{
bitmap->pixel_scale_min = truePixelScale;
bitmap->pixel_scale_max = 99.0f;
bitmap->point_sample = true;
}
#else
if (glyphScale <= 1 && pixel_scale < truePixelScale)
{
// Small display: pixel_scale is less than the native glyph size.
// Report the bitmap at its true native scale so Iggy downscales it
// to match the layout metrics (bilinear for smooth downscaling).
bitmap->pixel_scale_correct = truePixelScale;
bitmap->pixel_scale_min = 0.0f;
bitmap->pixel_scale_max = truePixelScale * 1.001f;
bitmap->point_sample = false;
}
else
{
// Normal/upscale case: integer-multiple scaling for pixel-art look.
// Console-only — fixed resolution means pixel_scale values are exact
// integer multiples of truePixelScale, so cache sharing is safe.
float actualScale = pixel_scale / glyphScale;
bitmap->pixel_scale_correct = actualScale;
bitmap->pixel_scale_min = truePixelScale;
bitmap->pixel_scale_max = 99.0f;
bitmap->point_sample = true;
}
#endif
// 4J-JEV: Nothing to do with glyph placement,
// entirely to do with cropping your glyph out of an archive.

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "UI.h"
#include "UIComponent_Chat.h"
#include "UISplitScreenHelpers.h"
#include "..\..\Minecraft.h"
#include "..\..\Gui.h"
@ -120,6 +121,7 @@ void UIComponent_Chat::render(S32 width, S32 height, C4JRender::eViewportType vi
S32 tileWidth = width;
S32 tileHeight = height;
bool needsYTile = false;
switch( viewport )
{
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
@ -127,25 +129,30 @@ void UIComponent_Chat::render(S32 width, S32 height, C4JRender::eViewportType vi
tileHeight = (S32)(ui.getScreenHeight());
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
}
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight );
F32 scale;
ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart);
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
S32 contentOffX, contentOffY;
ComputeSplitContentOffset(viewport, m_movieWidth, m_movieHeight, scale, tileWidth, tileHeight, tileYStart, contentOffX, contentOffY);
xPos += contentOffX;
yPos += contentOffY;
ui.setupRenderPosition(xPos, yPos);
IggyPlayerDrawTilesStart ( getMovie() );
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
IggyPlayerDrawTile ( getMovie() ,
@ -153,7 +160,7 @@ void UIComponent_Chat::render(S32 width, S32 height, C4JRender::eViewportType vi
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
}
else

View file

@ -68,24 +68,29 @@ void UIComponent_MenuBackground::render(S32 width, S32 height, C4JRender::eViewp
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
tileYStart = (S32)(ui.getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
tileYStart = (S32)(ui.getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2);
tileYStart = (S32)(ui.getScreenHeight() / 2);
break;
}
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight );
F32 scaleW = (F32)(tileXStart + tileWidth) / (F32)m_movieWidth;
F32 scaleH = (F32)(tileYStart + tileHeight) / (F32)m_movieHeight;
F32 scale = (scaleW > scaleH) ? scaleW : scaleH;
if(scale < 1.0f) scale = 1.0f;
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
IggyPlayerDrawTilesStart ( getMovie() );
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
IggyPlayerDrawTile ( getMovie() ,
@ -93,11 +98,15 @@ void UIComponent_MenuBackground::render(S32 width, S32 height, C4JRender::eViewp
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
}
else
{
UIScene::render(width, height, viewport);
if(m_bIsReloading) return;
if(!m_hasTickedOnce || !getMovie()) return;
ui.setupRenderPosition(0, 0);
IggyPlayerSetDisplaySize( getMovie(), (S32)ui.getScreenWidth(), (S32)ui.getScreenHeight() );
IggyPlayerDraw( getMovie() );
}
}

View file

@ -93,38 +93,47 @@ void UIComponent_Panorama::render(S32 width, S32 height, C4JRender::eViewportTyp
}
ui.setupRenderPosition(xPos, yPos);
if((viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT) || (viewport == C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT))
S32 tileXStart = 0;
S32 tileYStart = 0;
S32 tileWidth = width;
S32 tileHeight = height;
if((viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT) || (viewport == C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT))
{
// Need to render at full height, but only the left side of the scene
S32 tileXStart = 0;
S32 tileYStart = 0;
S32 tileWidth = width;
S32 tileHeight = (S32)(ui.getScreenHeight());
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight );
IggyPlayerDrawTilesStart ( getMovie() );
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
IggyPlayerDrawTile ( getMovie() ,
tileXStart ,
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
tileHeight = (S32)(ui.getScreenHeight());
}
else
{
// Need to render at full height, and full width. But compressed into the viewport
IggyPlayerSetDisplaySize( getMovie(), ui.getScreenWidth(), ui.getScreenHeight()/2 );
IggyPlayerDraw( getMovie() );
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(ui.getScreenHeight() / 2);
}
F32 scaleW = (F32)(tileXStart + tileWidth) / (F32)m_movieWidth;
F32 scaleH = (F32)(tileYStart + tileHeight) / (F32)m_movieHeight;
F32 scale = (scaleW > scaleH) ? scaleW : scaleH;
if(scale < 1.0f) scale = 1.0f;
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
IggyPlayerDrawTilesStart ( getMovie() );
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
IggyPlayerDrawTile ( getMovie() ,
tileXStart ,
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
}
else
{
UIScene::render(width, height, viewport);
if(m_bIsReloading) return;
if(!m_hasTickedOnce || !getMovie()) return;
ui.setupRenderPosition(0, 0);
IggyPlayerSetDisplaySize( getMovie(), (S32)ui.getScreenWidth(), (S32)ui.getScreenHeight() );
IggyPlayerDraw( getMovie() );
}
}

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "UI.h"
#include "UIComponent_Tooltips.h"
#include "UISplitScreenHelpers.h"
UIComponent_Tooltips::UIComponent_Tooltips(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer)
{
@ -224,6 +225,7 @@ void UIComponent_Tooltips::render(S32 width, S32 height, C4JRender::eViewportTyp
S32 tileWidth = width;
S32 tileHeight = height;
bool needsYTile = false;
switch( viewport )
{
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
@ -231,25 +233,30 @@ void UIComponent_Tooltips::render(S32 width, S32 height, C4JRender::eViewportTyp
tileHeight = (S32)(ui.getScreenHeight());
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
}
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight );
F32 scale;
ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart);
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
S32 contentOffX, contentOffY;
ComputeSplitContentOffset(viewport, m_movieWidth, m_movieHeight, scale, tileWidth, tileHeight, tileYStart, contentOffX, contentOffY);
xPos += contentOffX;
yPos += contentOffY;
ui.setupRenderPosition(xPos, yPos);
IggyPlayerDrawTilesStart ( getMovie() );
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
IggyPlayerDrawTile ( getMovie() ,
@ -257,7 +264,7 @@ void UIComponent_Tooltips::render(S32 width, S32 height, C4JRender::eViewportTyp
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
}
else

View file

@ -13,6 +13,7 @@
#include "..\..\EnderDragonRenderer.h"
#include "..\..\MultiPlayerLocalPlayer.h"
#include "UIFontData.h"
#include "UISplitScreenHelpers.h"
#ifdef _WINDOWS64
#include "..\..\Windows64\KeyboardMouseInput.h"
#endif
@ -57,6 +58,8 @@ bool UIController::ms_bReloadSkinCSInitialised = false;
DWORD UIController::m_dwTrialTimerLimitSecs=DYNAMIC_CONFIG_DEFAULT_TRIAL_TIME;
// GetViewportRect and Fit16x9 are now in UISplitScreenHelpers.h
#ifdef _WINDOWS64
static UIControl_Slider *FindSliderById(UIScene *pScene, int sliderId)
{
@ -806,13 +809,16 @@ void UIController::tickInput()
eUILayer_Fullscreen,
eUILayer_Scene,
};
for (int l = 0; l < _countof(mouseLayers) && !pScene; ++l)
// Only check the fullscreen group and the primary (KBM) player's group.
// Other splitscreen players use controllers — mouse must not affect them.
const int mouseGroups[] = { (int)eUIGroup_Fullscreen, ProfileManager.GetPrimaryPad() + 1 };
for (int l = 0; l < _countof(mouseLayers) && !pScene; ++l)
{
for (int g = 0; g < _countof(mouseGroups) && !pScene; ++g)
{
for (int grp = 0; grp < eUIGroup_COUNT && !pScene; ++grp)
{
pScene = m_groups[grp]->GetTopScene(mouseLayers[l]);
}
pScene = m_groups[mouseGroups[g]]->GetTopScene(mouseLayers[l]);
}
}
if (pScene && pScene->getMovie())
{
int rawMouseX = g_KBMInput.GetMouseX();
@ -825,7 +831,12 @@ void UIController::tickInput()
m_lastHoverMouseX = rawMouseX;
m_lastHoverMouseY = rawMouseY;
// Convert mouse to scene/movie coordinates
// Convert mouse window-pixel coords to Flash/SWF authoring coords.
// In split-screen the scene is rendered at a tile-origin offset
// and at a smaller display size, so we must:
// 1. Map window pixels -> UIController screen space
// 2. Subtract the viewport tile origin
// 3. Scale from display dimensions to SWF authoring dimensions
F32 sceneMouseX = (F32)rawMouseX;
F32 sceneMouseY = (F32)rawMouseY;
{
@ -837,8 +848,30 @@ void UIController::tickInput()
int winH = rc.bottom - rc.top;
if (winW > 0 && winH > 0)
{
sceneMouseX = sceneMouseX * ((F32)pScene->getRenderWidth() / (F32)winW);
sceneMouseY = sceneMouseY * ((F32)pScene->getRenderHeight() / (F32)winH);
// Step 1: window pixels -> screen space
F32 screenX = sceneMouseX * (getScreenWidth() / (F32)winW);
F32 screenY = sceneMouseY * (getScreenHeight() / (F32)winH);
// Step 2 & 3: account for split-screen viewport
C4JRender::eViewportType vp = pScene->GetParentLayer()->getViewport();
S32 displayW = 0, displayH = 0;
getRenderDimensions(vp, displayW, displayH);
F32 vpOriginX, vpOriginY, vpW, vpH;
GetViewportRect(getScreenWidth(), getScreenHeight(), vp, vpOriginX, vpOriginY, vpW, vpH);
// All viewports use Fit16x9 for menu scenes
S32 fitW, fitH, fitOffsetX, fitOffsetY;
Fit16x9(vpW, vpH, fitW, fitH, fitOffsetX, fitOffsetY);
S32 originX = (S32)vpOriginX + fitOffsetX;
S32 originY = (S32)vpOriginY + fitOffsetY;
displayW = fitW;
displayH = fitH;
if (displayW > 0 && displayH > 0)
{
sceneMouseX = (screenX - originX) * ((F32)pScene->getRenderWidth() / (F32)displayW);
sceneMouseY = (screenY - originY) * ((F32)pScene->getRenderHeight() / (F32)displayH);
}
}
}
}
@ -1566,73 +1599,48 @@ void UIController::renderScenes()
void UIController::getRenderDimensions(C4JRender::eViewportType viewport, S32 &width, S32 &height)
{
switch( viewport )
F32 originX, originY, viewW, viewH;
GetViewportRect(getScreenWidth(), getScreenHeight(), viewport, originX, originY, viewW, viewH);
if(viewport == C4JRender::VIEWPORT_TYPE_FULLSCREEN)
{
case C4JRender::VIEWPORT_TYPE_FULLSCREEN:
width = (S32)(getScreenWidth());
height = (S32)(getScreenHeight());
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
width = (S32)(getScreenWidth() / 2);
height = (S32)(getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
width = (S32)(getScreenWidth() / 2);
height = (S32)(getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
width = (S32)(getScreenWidth() / 2);
height = (S32)(getScreenHeight() / 2);
break;
S32 offsetX, offsetY;
Fit16x9(viewW, viewH, width, height, offsetX, offsetY);
}
else
{
// Split-screen: use raw viewport dims — the SWF tiling code handles non-16:9
width = (S32)viewW;
height = (S32)viewH;
}
}
void UIController::setupRenderPosition(C4JRender::eViewportType viewport)
{
if(m_bCustomRenderPosition || m_currentRenderViewport != viewport)
m_currentRenderViewport = viewport;
m_bCustomRenderPosition = false;
F32 vpOriginX, vpOriginY, vpW, vpH;
GetViewportRect(getScreenWidth(), getScreenHeight(), viewport, vpOriginX, vpOriginY, vpW, vpH);
S32 xPos, yPos;
if(viewport == C4JRender::VIEWPORT_TYPE_FULLSCREEN)
{
m_currentRenderViewport = viewport;
m_bCustomRenderPosition = false;
S32 xPos = 0;
S32 yPos = 0;
switch( viewport )
{
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
xPos = (S32)(getScreenWidth() / 4);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
xPos = (S32)(getScreenWidth() / 4);
yPos = (S32)(getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
yPos = (S32)(getScreenHeight() / 4);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
xPos = (S32)(getScreenWidth() / 2);
yPos = (S32)(getScreenHeight() / 4);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
xPos = (S32)(getScreenWidth() / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
yPos = (S32)(getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
xPos = (S32)(getScreenWidth() / 2);
yPos = (S32)(getScreenHeight() / 2);
break;
}
m_tileOriginX = xPos;
m_tileOriginY = yPos;
setTileOrigin(xPos, yPos);
S32 fitW, fitH, fitOffsetX, fitOffsetY;
Fit16x9(vpW, vpH, fitW, fitH, fitOffsetX, fitOffsetY);
xPos = (S32)vpOriginX + fitOffsetX;
yPos = (S32)vpOriginY + fitOffsetY;
}
else
{
// Split-screen: position at viewport origin, no 16:9 fitting
xPos = (S32)vpOriginX;
yPos = (S32)vpOriginY;
}
m_tileOriginX = xPos;
m_tileOriginY = yPos;
setTileOrigin(xPos, yPos);
}
void UIController::setupRenderPosition(S32 xOrigin, S32 yOrigin)
@ -1840,8 +1848,11 @@ void RADLINK UIController::TextureSubstitutionDestroyCallback ( void * user_call
ui.destroySubstitutionTexture(user_callback_data, handle);
Textures *t = Minecraft::GetInstance()->textures;
t->releaseTexture( id );
Minecraft* mc = Minecraft::GetInstance();
if (mc && mc->textures)
{
mc->textures->releaseTexture( id );
}
}
void UIController::registerSubstitutionTexture(const wstring &textureName, PBYTE pbData, DWORD dwLength)

View file

@ -257,6 +257,7 @@ public:
// RENDERING
float getScreenWidth() { return m_fScreenWidth; }
float getScreenHeight() { return m_fScreenHeight; }
void updateScreenSize(S32 w, S32 h) { m_fScreenWidth = (float)w; m_fScreenHeight = (float)h; app.DebugPrintf("[UI-INIT] updateScreenSize: %d x %d\n", w, h); }
virtual void render() = 0;
void getRenderDimensions(C4JRender::eViewportType viewport, S32 &width, S32 &height);

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "UI.h"
#include "UIScene.h"
#include "UISplitScreenHelpers.h"
#include "..\..\Lighting.h"
#include "..\..\LocalPlayer.h"
@ -285,26 +286,8 @@ void UIScene::loadMovie()
moviePath.append(L"Vita.swf");
m_loadedResolution = eSceneResolution_Vita;
#elif defined _WINDOWS64
if(ui.getScreenHeight() == 720)
{
moviePath.append(L"720.swf");
m_loadedResolution = eSceneResolution_720;
}
else if(ui.getScreenHeight() == 480)
{
moviePath.append(L"480.swf");
m_loadedResolution = eSceneResolution_480;
}
else if(ui.getScreenHeight() < 720)
{
moviePath.append(L"Vita.swf");
m_loadedResolution = eSceneResolution_Vita;
}
else
{
moviePath.append(L"1080.swf");
m_loadedResolution = eSceneResolution_1080;
}
moviePath.append(L"1080.swf");
m_loadedResolution = eSceneResolution_1080;
#else
moviePath.append(L"1080.swf");
m_loadedResolution = eSceneResolution_1080;
@ -332,8 +315,6 @@ void UIScene::loadMovie()
int64_t beforeLoad = ui.iggyAllocCount;
swf = IggyPlayerCreateFromMemory ( baFile.data , baFile.length, NULL);
int64_t afterLoad = ui.iggyAllocCount;
IggyPlayerInitializeAndTickRS ( swf );
int64_t afterTick = ui.iggyAllocCount;
if(!swf)
{
@ -343,17 +324,44 @@ void UIScene::loadMovie()
#endif
app.FatalLoadError();
}
app.DebugPrintf( app.USER_SR, "Loaded iggy movie %ls\n", moviePath.c_str() );
// Read movie dimensions from the SWF header (available immediately after
// CreateFromMemory, no init tick needed).
IggyProperties *properties = IggyPlayerProperties ( swf );
m_movieHeight = properties->movie_height_in_pixels;
m_movieWidth = properties->movie_width_in_pixels;
m_renderWidth = m_movieWidth;
m_renderHeight = m_movieHeight;
S32 width, height;
m_parentLayer->getRenderDimensions(width, height);
IggyPlayerSetDisplaySize( swf, width, height );
// Set display size BEFORE the init tick to match what render() will use.
// InitializeAndTickRS runs ActionScript that creates text fields. If the
// display size here differs from what render() passes to SetDisplaySize,
// Iggy can cache glyph rasterizations at one scale during init and then
// reuse them at a different scale during draw, producing mixed glyph sizes.
#ifdef _WINDOWS64
{
S32 fitW, fitH, fitOffX, fitOffY;
Fit16x9(ui.getScreenWidth(), ui.getScreenHeight(), fitW, fitH, fitOffX, fitOffY);
IggyPlayerSetDisplaySize( swf, fitW, fitH );
}
#else
IggyPlayerSetDisplaySize( swf, m_movieWidth, m_movieHeight );
#endif
IggyPlayerInitializeAndTickRS ( swf );
int64_t afterTick = ui.iggyAllocCount;
#ifdef _WINDOWS64
// Flush Iggy's internal font caches so all glyphs get rasterized fresh
// at the current display scale on the first Draw. Without this, stale
// cache entries from a previous scene (loaded at a different display size)
// cause mixed glyph sizes. ResizeD3D already calls this, which is why
// fonts look correct after a resize but break when a scene reloads
// without one.
IggyFlushInstalledFonts();
#endif
app.DebugPrintf( app.USER_SR, "Loaded iggy movie %ls\n", moviePath.c_str() );
IggyPlayerSetUserdata(swf,this);
@ -685,9 +693,23 @@ void UIScene::render(S32 width, S32 height, C4JRender::eViewportType viewport)
{
if(m_bIsReloading) return;
if(!m_hasTickedOnce || !swf) return;
ui.setupRenderPosition(viewport);
IggyPlayerSetDisplaySize( swf, width, height );
IggyPlayerDraw( swf );
if(viewport != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
{
F32 originX, originY, viewW, viewH;
GetViewportRect(ui.getScreenWidth(), ui.getScreenHeight(), viewport, originX, originY, viewW, viewH);
S32 fitW, fitH, offsetX, offsetY;
Fit16x9(viewW, viewH, fitW, fitH, offsetX, offsetY);
ui.setupRenderPosition((S32)originX + offsetX, (S32)originY + offsetY);
IggyPlayerSetDisplaySize( swf, fitW, fitH );
IggyPlayerDraw( swf );
}
else
{
ui.setupRenderPosition(viewport);
IggyPlayerSetDisplaySize( swf, width, height );
IggyPlayerDraw( swf );
}
}
void UIScene::setOpacity(float percent)
@ -1331,11 +1353,17 @@ bool UIScene::hasRegisteredSubstitutionTexture(const wstring &textureName)
void UIScene::_handleFocusChange(F64 controlId, F64 childId)
{
m_iFocusControl = (int)controlId;
m_iFocusChild = (int)childId;
int newControl = (int)controlId;
int newChild = (int)childId;
handleFocusChange(controlId, childId);
ui.PlayUISFX(eSFX_Focus);
if (newControl != m_iFocusControl || newChild != m_iFocusChild)
{
m_iFocusControl = newControl;
m_iFocusChild = newChild;
handleFocusChange(controlId, childId);
ui.PlayUISFX(eSFX_Focus);
}
}
void UIScene::_handleInitFocus(F64 controlId, F64 childId)

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "UI.h"
#include "UIScene_AbstractContainerMenu.h"
#include "UISplitScreenHelpers.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.inventory.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.item.h"
@ -187,14 +188,19 @@ void UIScene_AbstractContainerMenu::PlatformInitialize(int iPad, int startIndex)
IggyEvent mouseEvent;
S32 width, height;
m_parentLayer->getRenderDimensions(width, height);
C4JRender::eViewportType vp = m_parentLayer->getViewport();
if(vp != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
Fit16x9(width, height);
S32 x = m_pointerPos.x*((float)width/m_movieWidth);
S32 y = m_pointerPos.y*((float)height/m_movieHeight);
S32 y = m_pointerPos.y*((float)height/m_movieHeight);
IggyMakeEventMouseMove( &mouseEvent, x, y);
IggyEventResult result;
IggyPlayerDispatchEventRS ( getMovie() , &mouseEvent , &result );
#ifdef USE_POINTER_ACCEL
#ifdef USE_POINTER_ACCEL
m_fPointerVelX = 0.0f;
m_fPointerVelY = 0.0f;
m_fPointerAccelX = 0.0f;
@ -212,6 +218,10 @@ void UIScene_AbstractContainerMenu::tick()
S32 width, height;
m_parentLayer->getRenderDimensions(width, height);
C4JRender::eViewportType vp = m_parentLayer->getViewport();
if(vp != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
Fit16x9(width, height);
S32 x = (S32)(m_pointerPos.x * ((float)width / m_movieWidth));
S32 y = (S32)(m_pointerPos.y * ((float)height / m_movieHeight));
@ -251,6 +261,27 @@ void UIScene_AbstractContainerMenu::render(S32 width, S32 height, C4JRender::eVi
m_needsCacheRendered = false;
}
void UIScene_AbstractContainerMenu::getMouseToSWFScale(float &scaleX, float &scaleY)
{
extern HWND g_hWnd;
RECT rc;
GetClientRect(g_hWnd, &rc);
int winW = rc.right - rc.left;
int winH = rc.bottom - rc.top;
if(winW <= 0 || winH <= 0) { scaleX = 1.0f; scaleY = 1.0f; return; }
S32 renderW, renderH;
C4JRender::eViewportType vp = GetParentLayer()->getViewport();
ui.getRenderDimensions(vp, renderW, renderH);
if(vp != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
Fit16x9(renderW, renderH);
float screenW = (float)ui.getScreenWidth();
float screenH = (float)ui.getScreenHeight();
scaleX = (float)m_movieWidth * screenW / ((float)renderW * (float)winW);
scaleY = (float)m_movieHeight * screenH / ((float)renderH * (float)winH);
}
void UIScene_AbstractContainerMenu::customDraw(IggyCustomDrawCallbackRegion *region)
{
Minecraft *pMinecraft = Minecraft::GetInstance();

View file

@ -38,6 +38,7 @@ public:
int getPad() { return m_iPad; }
int getMovieWidth() { return m_movieWidth; }
int getMovieHeight() { return m_movieHeight; }
void getMouseToSWFScale(float &scaleX, float &scaleY);
bool getIgnoreInput() { return m_bIgnoreInput; }
void setIgnoreInput(bool bVal) { m_bIgnoreInput=bVal; }

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "UI.h"
#include "UIScene_HUD.h"
#include "UISplitScreenHelpers.h"
#include "BossMobGuiInfo.h"
#include "..\..\Minecraft.h"
#include "..\..\MultiplayerLocalPlayer.h"
@ -266,8 +267,6 @@ void UIScene_HUD::handleReload()
SetDisplayName(ProfileManager.GetDisplayName(m_iPad));
repositionHud();
SetTooltipsEnabled(((ui.GetMenuDisplayed(ProfileManager.GetPrimaryPad())) || (app.GetGameSettings(ProfileManager.GetPrimaryPad(),eGameSetting_Tooltips) != 0)));
}
@ -697,6 +696,7 @@ void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewpor
S32 tileWidth = width;
S32 tileHeight = height;
bool needsYTile = false;
switch( viewport )
{
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
@ -704,23 +704,25 @@ void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewpor
tileHeight = (S32)(ui.getScreenHeight());
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
}
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight );
F32 scale;
ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart);
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
repositionHud(tileWidth, tileHeight, scale);
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
@ -730,7 +732,7 @@ void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewpor
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
}
else
@ -790,34 +792,24 @@ void UIScene_HUD::handleTimerComplete(int id)
//setVisible(anyVisible);
}
void UIScene_HUD::repositionHud()
void UIScene_HUD::repositionHud(S32 tileWidth, S32 tileHeight, F32 scale)
{
if(!m_bSplitscreen) return;
S32 width = 0;
S32 height = 0;
m_parentLayer->getRenderDimensions( width, height );
// Pass the visible tile area in SWF coordinates so ActionScript
// positions elements (crosshair, hotbar, etc.) centered in the
// actually visible region, not the raw viewport.
S32 visibleW = (S32)(tileWidth / scale);
S32 visibleH = (S32)(tileHeight / scale);
switch( m_parentLayer->getViewport() )
{
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
height = (S32)(ui.getScreenHeight());
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
width = (S32)(ui.getScreenWidth());
break;
}
app.DebugPrintf(app.USER_SR, "Reposition HUD with dims %d, %d\n", width, height );
app.DebugPrintf(app.USER_SR, "Reposition HUD: tile %dx%d, scale %.3f, visible SWF %dx%d\n", tileWidth, tileHeight, scale, visibleW, visibleH );
IggyDataValue result;
IggyDataValue value[2];
value[0].type = IGGY_DATATYPE_number;
value[0].number = width;
value[0].number = visibleW;
value[1].type = IGGY_DATATYPE_number;
value[1].number = height;
value[1].number = visibleH;
IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcRepositionHud , 2 , value );
}

View file

@ -176,5 +176,5 @@ protected:
#endif
private:
void repositionHud();
void repositionHud(S32 tileWidth, S32 tileHeight, F32 scale);
};

View file

@ -38,6 +38,7 @@ UIScene_Keyboard::UIScene_Keyboard(int iPad, void *initData, UILayer *parentLaye
}
m_win64TextBuffer = defaultText;
m_iCursorPos = (int)m_win64TextBuffer.length();
m_EnterTextLabel.init(titleText);
m_KeyboardTextInput.init(defaultText, -1);
@ -111,6 +112,9 @@ UIScene_Keyboard::UIScene_Keyboard(int iPad, void *initData, UILayer *parentLaye
if (IggyValuePathMakeNameRef(&keyPath, root, s_keyNames[i]))
IggyValueSetBooleanRS(&keyPath, nameVisible, NULL, false);
}
m_KeyboardTextInput.setCaretVisible(true);
m_KeyboardTextInput.setCaretIndex(m_iCursorPos);
}
#endif
@ -165,9 +169,13 @@ void UIScene_Keyboard::tick()
// Sync our buffer from Flash so we pick up changes made via controller/on-screen buttons.
// Without this, switching between controller and keyboard would use stale text.
const wchar_t* flashText = m_KeyboardTextInput.getLabel();
if (flashText)
m_win64TextBuffer = flashText;
// In PC mode we own the buffer — skip sync to preserve cursor position.
if (!m_bPCMode)
{
const wchar_t* flashText = m_KeyboardTextInput.getLabel();
if (flashText)
m_win64TextBuffer = flashText;
}
// Accumulate physical keyboard chars into our own buffer, then push to Flash via setLabel.
// This bypasses Iggy's focus system (char events only route to the focused element).
@ -178,7 +186,16 @@ void UIScene_Keyboard::tick()
{
if (ch == 0x08) // backspace
{
if (!m_win64TextBuffer.empty())
if (m_bPCMode)
{
if (m_iCursorPos > 0)
{
m_win64TextBuffer.erase(m_iCursorPos - 1, 1);
m_iCursorPos--;
changed = true;
}
}
else if (!m_win64TextBuffer.empty())
{
m_win64TextBuffer.pop_back();
changed = true;
@ -194,13 +211,45 @@ void UIScene_Keyboard::tick()
}
else if ((int)m_win64TextBuffer.length() < m_win64MaxChars)
{
m_win64TextBuffer += ch;
if (m_bPCMode)
{
m_win64TextBuffer.insert(m_iCursorPos, 1, ch);
m_iCursorPos++;
}
else
{
m_win64TextBuffer += ch;
}
changed = true;
}
}
if (m_bPCMode)
{
// Arrow keys, Home, End, Delete for cursor movement
if (g_KBMInput.IsKeyPressed(VK_LEFT) && m_iCursorPos > 0)
m_iCursorPos--;
if (g_KBMInput.IsKeyPressed(VK_RIGHT) && m_iCursorPos < (int)m_win64TextBuffer.length())
m_iCursorPos++;
if (g_KBMInput.IsKeyPressed(VK_HOME))
m_iCursorPos = 0;
if (g_KBMInput.IsKeyPressed(VK_END))
m_iCursorPos = (int)m_win64TextBuffer.length();
if (g_KBMInput.IsKeyPressed(VK_DELETE) && m_iCursorPos < (int)m_win64TextBuffer.length())
{
m_win64TextBuffer.erase(m_iCursorPos, 1);
changed = true;
}
}
if (changed)
m_KeyboardTextInput.setLabel(m_win64TextBuffer.c_str(), true /*instant*/);
if (m_bPCMode)
{
m_KeyboardTextInput.setCaretVisible(true);
m_KeyboardTextInput.setCaretIndex(m_iCursorPos);
}
}
#endif
@ -286,7 +335,10 @@ void UIScene_Keyboard::handleInput(int iPad, int key, bool repeat, bool pressed,
case ACTION_MENU_RIGHT:
case ACTION_MENU_UP:
case ACTION_MENU_DOWN:
sendInputToMovie(key, repeat, pressed, released);
#ifdef _WINDOWS64
if (!m_bPCMode)
#endif
sendInputToMovie(key, repeat, pressed, released);
handled = true;
break;
}

View file

@ -13,6 +13,7 @@ private:
wstring m_win64TextBuffer;
int m_win64MaxChars;
bool m_bPCMode; // Hides on-screen keyboard buttons; physical keyboard only
int m_iCursorPos;
#endif
protected:

View file

@ -0,0 +1,114 @@
#pragma once
// Shared split-screen UI helpers to avoid duplicating viewport math
// across HUD, Chat, Tooltips, and container menus.
// Compute the raw viewport rectangle for a given viewport type.
inline void GetViewportRect(F32 screenW, F32 screenH, C4JRender::eViewportType viewport,
F32 &originX, F32 &originY, F32 &viewW, F32 &viewH)
{
originX = originY = 0;
viewW = screenW;
viewH = screenH;
switch(viewport)
{
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
originY = screenH * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
viewW = screenW * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
originX = screenW * 0.5f; viewW = screenW * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
originX = screenW * 0.5f; viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
originY = screenH * 0.5f; viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
originX = screenW * 0.5f; originY = screenH * 0.5f;
viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
default: break;
}
}
// Fit a 16:9 rectangle inside the given dimensions.
inline void Fit16x9(F32 viewW, F32 viewH, S32 &fitW, S32 &fitH, S32 &offsetX, S32 &offsetY)
{
const F32 kAspect = 16.0f / 9.0f;
if(viewW / viewH > kAspect)
{
fitH = (S32)viewH;
fitW = (S32)(viewH * kAspect);
}
else
{
fitW = (S32)viewW;
fitH = (S32)(viewW / kAspect);
}
offsetX = (S32)((viewW - fitW) * 0.5f);
offsetY = (S32)((viewH - fitH) * 0.5f);
}
// Convenience: just fit 16:9 dimensions, ignore offsets.
inline void Fit16x9(S32 &width, S32 &height)
{
S32 offX, offY;
Fit16x9((F32)width, (F32)height, width, height, offX, offY);
}
// Compute the uniform scale and tileYStart for split-screen tile rendering.
// Used by HUD, Chat, and Tooltips to scale the SWF movie to cover the viewport tile.
inline void ComputeTileScale(S32 tileWidth, S32 tileHeight, S32 movieWidth, S32 movieHeight,
bool needsYTile, F32 &outScale, S32 &outTileYStart)
{
F32 scaleW = (F32)tileWidth / (F32)movieWidth;
F32 scaleH = (F32)tileHeight / (F32)movieHeight;
F32 scale = (scaleW > scaleH) ? scaleW : scaleH;
if(scale < 1.0f) scale = 1.0f;
outTileYStart = 0;
if(needsYTile)
{
S32 dispH = (S32)(movieHeight * scale);
outTileYStart = dispH - tileHeight;
if(outTileYStart < 0) outTileYStart = 0;
scaleH = (F32)(outTileYStart + tileHeight) / (F32)movieHeight;
scale = (scaleW > scaleH) ? scaleW : scaleH;
if(scale < 1.0f) scale = 1.0f;
}
outScale = scale;
}
// Compute the render offset to center split-screen SWF content in the viewport.
// Used by Chat and Tooltips (HUD uses repositionHud instead).
inline void ComputeSplitContentOffset(C4JRender::eViewportType viewport, S32 movieWidth, S32 movieHeight,
F32 scale, S32 tileWidth, S32 tileHeight, S32 tileYStart,
S32 &outXOffset, S32 &outYOffset)
{
S32 contentCenterX, contentCenterY;
if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT || viewport == C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT)
{
contentCenterX = (S32)(movieWidth * scale / 4);
contentCenterY = (S32)(movieHeight * scale / 2);
}
else if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_TOP || viewport == C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM)
{
contentCenterX = (S32)(movieWidth * scale / 2);
contentCenterY = (S32)(movieHeight * scale * 3 / 4);
}
else
{
contentCenterX = (S32)(movieWidth * scale / 4);
contentCenterY = (S32)(movieHeight * scale * 3 / 4);
}
outXOffset = 0;
outYOffset = 0;
if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT)
outXOffset = -(tileWidth / 2 - contentCenterX);
if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_TOP || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT)
outYOffset = -(tileHeight / 2 - (contentCenterY - tileYStart));
}

View file

@ -234,6 +234,7 @@ void EntityRenderDispatcher::prepare(Level *level, Textures *textures, Font *fon
if (pl->ThirdPersonView() == 2)
{
playerRotY += 180;
playerRotX = -playerRotX;
}
xPlayer = player->xOld + (player->x - player->xOld) * a;

View file

@ -192,7 +192,16 @@ void IQNetPlayer::SendData(IQNetPlayer * player, const void* pvData, DWORD dwDat
{
if (WinsockNetLayer::IsActive())
{
WinsockNetLayer::SendToSmallId(player->m_smallId, pvData, dwDataSize);
if (!WinsockNetLayer::IsHosting() && !m_isRemote)
{
SOCKET sock = WinsockNetLayer::GetLocalSocket(m_smallId);
if (sock != INVALID_SOCKET)
WinsockNetLayer::SendOnSocket(sock, pvData, dwDataSize);
}
else
{
WinsockNetLayer::SendToSmallId(player->m_smallId, pvData, dwDataSize);
}
}
}
bool IQNetPlayer::IsSameSystem(IQNetPlayer * player) { return (this == player) || (!m_isRemote && !player->m_isRemote); }
@ -243,7 +252,20 @@ void Win64_SetupRemoteQNetPlayer(IQNetPlayer * player, BYTE smallId, bool isHost
static bool Win64_IsActivePlayer(IQNetPlayer* p, DWORD index);
HRESULT IQNet::AddLocalPlayerByUserIndex(DWORD dwUserIndex) { return S_OK; }
HRESULT IQNet::AddLocalPlayerByUserIndex(DWORD dwUserIndex) {
if (dwUserIndex >= MINECRAFT_NET_MAX_PLAYERS) return E_FAIL;
m_player[dwUserIndex].m_isRemote = false;
m_player[dwUserIndex].m_isHostPlayer = false;
// Give the joining player a distinct gamertag
extern wchar_t g_Win64UsernameW[17];
if (dwUserIndex == 0)
wcscpy_s(m_player[0].m_gamertag, 32, g_Win64UsernameW);
else
swprintf_s(m_player[dwUserIndex].m_gamertag, 32, L"%s(%d)", g_Win64UsernameW, dwUserIndex + 1);
if (dwUserIndex >= s_playerCount)
s_playerCount = dwUserIndex + 1;
return S_OK;
}
IQNetPlayer* IQNet::GetHostPlayer() { return &m_player[0]; }
IQNetPlayer* IQNet::GetLocalPlayerByUserIndex(DWORD dwUserIndex)
{
@ -255,13 +277,31 @@ IQNetPlayer* IQNet::GetLocalPlayerByUserIndex(DWORD dwUserIndex)
return &m_player[dwUserIndex];
return NULL;
}
if (dwUserIndex != 0)
return NULL;
for (DWORD i = 0; i < s_playerCount; i++)
if (dwUserIndex == 0)
{
if (!m_player[i].m_isRemote && Win64_IsActivePlayer(&m_player[i], i))
return &m_player[i];
// Primary pad: use direct index when networking is active (smallId may not be 0)
if (WinsockNetLayer::IsActive())
{
DWORD idx = WinsockNetLayer::GetLocalSmallId();
if (idx < MINECRAFT_NET_MAX_PLAYERS &&
!m_player[idx].m_isRemote &&
Win64_IsActivePlayer(&m_player[idx], idx))
return &m_player[idx];
return NULL;
}
// Offline: scan for first local player
for (DWORD i = 0; i < s_playerCount; i++)
{
if (!m_player[i].m_isRemote && Win64_IsActivePlayer(&m_player[i], i))
return &m_player[i];
}
return NULL;
}
// Split-screen pads 1-3: the player is at m_player[dwUserIndex] with isRemote=false
if (dwUserIndex < MINECRAFT_NET_MAX_PLAYERS &&
!m_player[dwUserIndex].m_isRemote &&
Win64_IsActivePlayer(&m_player[dwUserIndex], dwUserIndex))
return &m_player[dwUserIndex];
return NULL;
}
static bool Win64_IsActivePlayer(IQNetPlayer * p, DWORD index)
@ -582,7 +622,7 @@ void C_4JProfile::SetTrialTextStringTable(CXuiStringTable * pStringTable, int
void C_4JProfile::SetTrialAwardText(eAwardType AwardType, int iTitle, int iText) {}
int C_4JProfile::GetLockedProfile() { return 0; }
void C_4JProfile::SetLockedProfile(int iProf) {}
bool C_4JProfile::IsSignedIn(int iQuadrant) { return (iQuadrant == 0); }
bool C_4JProfile::IsSignedIn(int iQuadrant) { return (iQuadrant == 0) || InputManager.IsPadConnected(iQuadrant); }
bool C_4JProfile::IsSignedInLive(int iProf) { return true; }
bool C_4JProfile::IsGuest(int iQuadrant) { return false; }
UINT C_4JProfile::RequestSignInUI(bool bFromInvite, bool bLocalGame, bool bNoGuestsAllowed, bool bMultiplayerSignIn, bool bAddUser, int(*Func)(LPVOID, const bool, const int iPad), LPVOID lpParam, int iQuadrant) { return 0; }
@ -593,18 +633,10 @@ bool C_4JProfile::QuerySigninStatus(void) { return true; }
void C_4JProfile::GetXUID(int iPad, PlayerUID * pXuid, bool bOnlineXuid)
{
#ifdef _WINDOWS64
if (iPad != 0)
{
*pXuid = INVALID_XUID;
return;
}
// LoginPacket reads this value as client identity:
// - host keeps legacy host XUID for world compatibility
// - non-host uses persistent uid.dat-backed XUID
if (IQNet::s_isHosting)
*pXuid = Win64Xuid::GetLegacyEmbeddedHostXuid();
else
*pXuid = Win64Xuid::ResolvePersistentXuid();
// Each pad gets a unique XUID derived from the persistent uid.dat value.
// Pad 0 uses the base XUID directly. Pads 1-3 get a deterministic hash
// of (base + pad) to produce fully independent IDs with no overlap risk.
*pXuid = Win64Xuid::DeriveXuidForPad(Win64Xuid::ResolvePersistentXuid(), iPad);
#else
* pXuid = 0xe000d45248242f2e + iPad;
#endif
@ -634,8 +666,24 @@ void C_4JProfile::SetPrimaryPad(int iPad) {}
char fakeGamerTag[32] = "PlayerName";
void SetFakeGamertag(char* name) { strcpy_s(fakeGamerTag, name); }
#else
char* C_4JProfile::GetGamertag(int iPad) { extern char g_Win64Username[17]; return g_Win64Username; }
wstring C_4JProfile::GetDisplayName(int iPad) { extern wchar_t g_Win64UsernameW[17]; return g_Win64UsernameW; }
char* C_4JProfile::GetGamertag(int iPad) {
extern char g_Win64Username[17];
if (iPad > 0 && iPad < XUSER_MAX_COUNT && IQNet::m_player[iPad].m_gamertag[0] != 0 &&
!IQNet::m_player[iPad].m_isRemote)
{
static char s_padGamertag[XUSER_MAX_COUNT][17];
WideCharToMultiByte(CP_ACP, 0, IQNet::m_player[iPad].m_gamertag, -1, s_padGamertag[iPad], 17, NULL, NULL);
return s_padGamertag[iPad];
}
return g_Win64Username;
}
wstring C_4JProfile::GetDisplayName(int iPad) {
extern wchar_t g_Win64UsernameW[17];
if (iPad > 0 && iPad < XUSER_MAX_COUNT && IQNet::m_player[iPad].m_gamertag[0] != 0 &&
!IQNet::m_player[iPad].m_isRemote)
return IQNet::m_player[iPad].m_gamertag;
return g_Win64UsernameW;
}
#endif
bool C_4JProfile::IsFullVersion() { return s_bProfileIsFullVersion; }
void C_4JProfile::SetSignInChangeCallback(void (*Func)(LPVOID, bool, unsigned int), LPVOID lpParam) {}

View file

@ -149,7 +149,7 @@ void Font::renderStyleLine(float x0, float y0, float x1, float y1)
void Font::addCharacterQuad(wchar_t c)
{
float xOff = c % m_cols * m_charWidth;
float yOff = c / m_cols * m_charWidth;
float yOff = c / m_cols * m_charHeight; // was m_charWidth — wrong when glyphs aren't square
float width = charWidths[c] - .01f;
float height = m_charHeight - .01f;
float fontWidth = m_cols * m_charWidth;
@ -187,7 +187,7 @@ void Font::addCharacterQuad(wchar_t c)
void Font::renderCharacter(wchar_t c)
{
float xOff = c % m_cols * m_charWidth;
float yOff = c / m_cols * m_charWidth;
float yOff = c / m_cols * m_charHeight; // was m_charWidth — wrong when glyphs aren't square
float width = charWidths[c] - .01f;
float height = m_charHeight - .01f;

View file

@ -593,9 +593,10 @@ void GameRenderer::unZoomRegion()
// 4J added as we have more complex adjustments to make for fov & aspect on account of viewports
void GameRenderer::getFovAndAspect(float& fov, float& aspect, float a, bool applyEffects)
{
// 4J - split out aspect ratio and fov here so we can adjust for viewports - we might need to revisit these as
// they are maybe be too generous for performance.
aspect = mc->width / (float) mc->height;
// Use the real window dimensions so the perspective updates on resize.
extern int g_rScreenWidth;
extern int g_rScreenHeight;
aspect = g_rScreenWidth / static_cast<float>(g_rScreenHeight);
fov = getFov(a, applyEffects);
if( ( mc->player->m_iScreenSection == C4JRender::VIEWPORT_TYPE_SPLIT_TOP ) ||
@ -968,6 +969,10 @@ void GameRenderer::CachePlayerGammas()
bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport) const
{
// Use the actual backbuffer dimensions so viewports adapt to window resize.
extern int g_rScreenWidth;
extern int g_rScreenHeight;
int active = 0;
int indexMap[NUM_LIGHT_TEXTURES] = {-1, -1, -1, -1};
for (int i = 0; i < XUSER_MAX_COUNT && i < NUM_LIGHT_TEXTURES; ++i)
@ -980,8 +985,8 @@ bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport)
{
outViewport.TopLeftX = 0.0f;
outViewport.TopLeftY = 0.0f;
outViewport.Width = static_cast<FLOAT>(mc->width);
outViewport.Height = static_cast<FLOAT>(mc->height);
outViewport.Width = static_cast<FLOAT>(g_rScreenWidth);
outViewport.Height = static_cast<FLOAT>(g_rScreenHeight);
outViewport.MinDepth = 0.0f;
outViewport.MaxDepth = 1.0f;
return true;
@ -997,8 +1002,8 @@ bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport)
if (k < 0)
return false;
const float width = static_cast<float>(mc->width);
const float height = static_cast<float>(mc->height);
const float width = static_cast<float>(g_rScreenWidth);
const float height = static_cast<float>(g_rScreenHeight);
if (active == 2)
{
@ -1171,7 +1176,7 @@ void GameRenderer::render(float a, bool bFirst)
if (mc->noRender) return;
GameRenderer::anaglyph3d = mc->options->anaglyph3d;
glViewport(0, 0, mc->width, mc->height); // 4J - added
glViewport(0, 0, mc->width, mc->height); // 4J - added (no-op on Win64, viewport set by StateSetViewport)
ScreenSizeCalculator ssc(mc->options, mc->width, mc->height);
int screenWidth = ssc.getWidth();
int screenHeight = ssc.getHeight();

View file

@ -895,14 +895,14 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
int zBlockPos = Mth::floor(minecraft->player->z);
// Chunk player is in
int xChunkPos = minecraft->player->xChunk;
int yChunkPos = minecraft->player->yChunk;
int zChunkPos = minecraft->player->zChunk;
int xChunkPos = xBlockPos >> 4;
int yChunkPos = yBlockPos >> 4;
int zChunkPos = zBlockPos >> 4;
// Players offset within the chunk
int xChunkOffset = xBlockPos - xChunkPos * 16;
int yChunkOffset = yBlockPos - yChunkPos * 16;
int zChunkOffset = zBlockPos - zChunkPos * 16;
int xChunkOffset = xBlockPos & 15;
int yChunkOffset = yBlockPos & 15;
int zChunkOffset = zBlockPos & 15;
// Format the position like java with limited decumal places
WCHAR posString[44]; // Allows upto 7 digit positions (+-9_999_999)
@ -951,18 +951,20 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
if (minecraft->level != NULL && minecraft->level->hasChunkAt(xBlockPos, fmod(yBlockPos, 256), zBlockPos))
{
LevelChunk *chunkAt = minecraft->level->getChunkAt(xBlockPos, zBlockPos);
if (chunkAt != NULL)
{
int skyLight = chunkAt->getBrightness(LightLayer::Sky, xChunkOffset, yChunkOffset, zChunkOffset);
int blockLight = chunkAt->getBrightness(LightLayer::Block, xChunkOffset, yChunkOffset, zChunkOffset);
int maxLight = fmax(skyLight, blockLight);
lines.push_back(L"Light: " + std::to_wstring(maxLight) + L" (" + std::to_wstring(skyLight) + L" sky, " + std::to_wstring(blockLight) + L" block)");
int skyLight = chunkAt->getBrightness(LightLayer::Sky, xChunkOffset, yChunkOffset, zChunkOffset);
int blockLight = chunkAt->getBrightness(LightLayer::Block, xChunkOffset, yChunkOffset, zChunkOffset);
int maxLight = fmax(skyLight, blockLight);
lines.push_back(L"Light: " + std::to_wstring(maxLight) + L" (" + std::to_wstring(skyLight) + L" sky, " + std::to_wstring(blockLight) + L" block)");
lines.push_back(L"CH S: " + std::to_wstring(chunkAt->getHeightmap(xChunkOffset, zChunkOffset)));
lines.push_back(L"CH S: " + std::to_wstring(chunkAt->getHeightmap(xChunkOffset, zChunkOffset)));
Biome *biome = chunkAt->getBiome(xChunkOffset, zChunkOffset, minecraft->level->getBiomeSource());
lines.push_back(L"Biome: " + biome->m_name + L" (" + std::to_wstring(biome->id) + L")");
Biome *biome = chunkAt->getBiome(xChunkOffset, zChunkOffset, minecraft->level->getBiomeSource());
lines.push_back(L"Biome: " + biome->m_name + L" (" + std::to_wstring(biome->id) + L")");
lines.push_back(L"Difficulty: " + std::to_wstring(minecraft->level->difficulty) + L" (Day " + std::to_wstring(minecraft->level->getGameTime() / Level::TICKS_PER_DAY) + L")");
lines.push_back(L"Difficulty: " + std::to_wstring(minecraft->level->difficulty) + L" (Day " + std::to_wstring(minecraft->level->getGameTime() / Level::TICKS_PER_DAY) + L")");
}
}

View file

@ -930,6 +930,14 @@ void ItemInHandRenderer::tick()
}
void ItemInHandRenderer::reset()
{
selectedItem = nullptr;
lastSlot = -1;
height = 0.0f;
oHeight = 0.0f;
}
void ItemInHandRenderer::itemPlaced()
{
height = 0;

View file

@ -41,6 +41,7 @@ private:
int lastSlot;
public:
void tick();
void reset();
void itemPlaced();
void itemUsed();
};

View file

@ -1,5 +1,6 @@
#include "stdafx.h"
#include "Minecraft.h"
#include "Common/UI/UIScene.h"
#include "GameMode.h"
#include "Timer.h"
#include "ProgressRenderer.h"
@ -9,6 +10,7 @@
#include "User.h"
#include "Textures.h"
#include "GameRenderer.h"
#include "ItemInHandRenderer.h"
#include "HumanoidModel.h"
#include "Options.h"
#include "TexturePackRepository.h"
@ -216,6 +218,7 @@ Minecraft::Minecraft(Component *mouseComponent, Canvas *parent, MinecraftApplet
m_pendingLocalConnections[i] = NULL;
m_connectionFailed[i] = false;
localgameModes[i]=NULL;
localitemInHandRenderers[i] = NULL;
}
animateTickLevel = NULL; // 4J added
@ -1479,9 +1482,22 @@ void Minecraft::run_middle()
if(g_KBMInput.IsMouseButtonPressed(KeyboardMouseInput::MOUSE_RIGHT))
localplayers[i]->ullButtonsPressed|=1LL<<MINECRAFT_ACTION_USE;
bool isClosableByEitherKey = ui.IsSceneInStack(i, eUIScene_FurnaceMenu) ||
ui.IsSceneInStack(i, eUIScene_ContainerMenu) ||
ui.IsSceneInStack(i, eUIScene_DispenserMenu) ||
ui.IsSceneInStack(i, eUIScene_EnchantingMenu) ||
ui.IsSceneInStack(i, eUIScene_BrewingStandMenu) ||
ui.IsSceneInStack(i, eUIScene_TradingMenu) ||
ui.IsSceneInStack(i, eUIScene_AnvilMenu) ||
ui.IsSceneInStack(i, eUIScene_HopperMenu) ||
ui.IsSceneInStack(i, eUIScene_BeaconMenu) ||
ui.IsSceneInStack(i, eUIScene_InventoryMenu) ||
ui.IsSceneInStack(i, eUIScene_HorseMenu);
bool isEditing = ui.GetTopScene(i) && ui.GetTopScene(i)->isDirectEditBlocking();
if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_INVENTORY))
{
if(ui.IsSceneInStack(i, eUIScene_InventoryMenu))
if(isClosableByEitherKey && !isEditing)
{
ui.CloseUIScenes(i);
}
@ -1496,7 +1512,7 @@ void Minecraft::run_middle()
if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_CRAFTING) || g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_CRAFTING_ALT))
{
if(ui.IsSceneInStack(i, eUIScene_Crafting2x2Menu) || ui.IsSceneInStack(i, eUIScene_Crafting3x3Menu) || ui.IsSceneInStack(i, eUIScene_CreativeMenu))
if((ui.IsSceneInStack(i, eUIScene_Crafting2x2Menu) || ui.IsSceneInStack(i, eUIScene_Crafting3x3Menu) || ui.IsSceneInStack(i, eUIScene_CreativeMenu) || isClosableByEitherKey) && !isEditing)
{
ui.CloseUIScenes(i);
}
@ -2357,16 +2373,21 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures)
}
#ifdef _WINDOWS64
if ((screen != NULL || ui.GetMenuDisplayed(iPad)) && g_KBMInput.IsMouseGrabbed())
// Mouse grab/release only for the primary (KBM) player — splitscreen
// players use controllers and must never fight over the cursor state.
if (iPad == ProfileManager.GetPrimaryPad())
{
g_KBMInput.SetMouseGrabbed(false);
if ((screen != NULL || ui.GetMenuDisplayed(iPad)) && g_KBMInput.IsMouseGrabbed())
{
g_KBMInput.SetMouseGrabbed(false);
}
}
#endif
if (screen == NULL && !ui.GetMenuDisplayed(iPad) )
{
#ifdef _WINDOWS64
if (!g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsWindowFocused())
if (iPad == ProfileManager.GetPrimaryPad() && !g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsWindowFocused())
{
g_KBMInput.SetMouseGrabbed(true);
}
@ -4226,6 +4247,17 @@ void Minecraft::setLevel(MultiPlayerLevel *level, int message /*=-1*/, shared_pt
// 4J - stop update thread from processing this level, which blocks until it is safe to move on - will be re-enabled if we set the level to be non-NULL
gameRenderer->DisableUpdateThread();
if (level == NULL || player == NULL)
{
for (int i = 0; i < XUSER_MAX_COUNT; ++i)
{
if (localitemInHandRenderers[i] != NULL)
{
localitemInHandRenderers[i]->reset();
}
}
}
for(unsigned int i = 0; i < levels.length; ++i)
{
// 4J We only need to save out in multiplayer is we are setting the level to NULL
@ -4642,8 +4674,14 @@ void Minecraft::startAndConnectTo(const wstring& name, const wstring& sid, const
Minecraft *minecraft;
// 4J - was new Minecraft(frame, canvas, NULL, 854, 480, fullScreen);
// Logical width is proportional to the real screen aspect ratio so that
// the ortho projection and HUD layout match the viewport without stretching.
extern int g_iScreenWidth;
extern int g_iScreenHeight;
int logicalH = 720;
int logicalW = logicalH * g_iScreenWidth / g_iScreenHeight;
minecraft = new Minecraft(NULL, NULL, NULL, 1280, 720, fullScreen);
minecraft = new Minecraft(NULL, NULL, NULL, logicalW, logicalH, fullScreen);
/* - 4J - removed
{

View file

@ -2169,12 +2169,16 @@ void MinecraftServer::tick()
}
Entity::tickExtraWandering(); // 4J added
PIXBeginNamedEvent(0,"Connection tick");
connection->tick();
PIXEndNamedEvent();
// Process player disconnect/kick queue BEFORE ticking connections.
// PendingConnection::handleLogin rejects duplicate XUIDs, so the old
// player must be removed from PlayerList before a reconnecting client's
// LoginPacket is processed.
PIXBeginNamedEvent(0,"Players tick");
players->tick();
PIXEndNamedEvent();
PIXBeginNamedEvent(0,"Connection tick");
connection->tick();
PIXEndNamedEvent();
// 4J - removed
#if 0

View file

@ -43,7 +43,7 @@ Dungeon!
Exclusive!
The bee's knees!
Down with O.P.P.!
Closed source!
Closed source xD!
Classy!
Wow!
Not on steam!

View file

@ -190,9 +190,34 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> packet)
}
else if (duplicateXuid)
{
// if same XUID already in use by another player so disconnect this one.
app.DebugPrintf("Rejecting duplicate xuid for name: %ls\n", name.c_str());
disconnect(DisconnectPacket::eDisconnect_Banned);
// The old player is still in PlayerList (disconnect hasn't been
// processed yet). Force-close the stale connection so the
// reconnecting client isn't rejected.
app.DebugPrintf("RECONNECT: Duplicate xuid for name: %ls, forcing old connection closed\n", name.c_str());
shared_ptr<ServerPlayer> stalePlayer = server->getPlayers()->getPlayer(loginXuid);
if (stalePlayer == nullptr && packet->m_onlineXuid != INVALID_XUID)
stalePlayer = server->getPlayers()->getPlayer(packet->m_onlineXuid);
if (stalePlayer != nullptr && stalePlayer->connection != nullptr)
{
BYTE oldSmallId = 0;
if (stalePlayer->connection->connection != nullptr && stalePlayer->connection->connection->getSocket() != nullptr)
oldSmallId = stalePlayer->connection->connection->getSocket()->getSmallId();
app.DebugPrintf("RECONNECT: Force-disconnecting old player smallId=%d\n", oldSmallId);
stalePlayer->connection->disconnect(DisconnectPacket::eDisconnect_Closed);
// Queue the old SmallId for recycling so it's not permanently leaked.
// PlayerList::tick() will call PushFreeSmallId/ClearSocketForSmallId.
if (oldSmallId != 0)
server->getPlayers()->queueSmallIdForRecycle(oldSmallId);
app.DebugPrintf("RECONNECT: Old player force-disconnect complete\n");
}
// Accept the login now that the old entry is removed.
app.DebugPrintf("RECONNECT: Calling handleAcceptedLogin for new connection\n");
handleAcceptedLogin(packet);
app.DebugPrintf("RECONNECT: handleAcceptedLogin complete\n");
}
#ifdef _WINDOWS64
else if (g_bRejectDuplicateNames)

View file

@ -19,6 +19,9 @@
#include "..\Minecraft.World\net.minecraft.network.packet.h"
#include "..\Minecraft.World\net.minecraft.network.h"
#include "Windows64\Windows64_Xuid.h"
#ifdef _WINDOWS64
#include "Windows64\Network\WinsockNetLayer.h"
#endif
#include "..\Minecraft.World\Pos.h"
#include "..\Minecraft.World\ProgressListener.h"
#include "..\Minecraft.World\HellRandomLevelSource.h"
@ -237,6 +240,14 @@ bool PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer>
addPlayerToReceiving( player );
int maxPlayersForPacket = getMaxPlayers() > 255 ? 255 : getMaxPlayers();
BYTE newSmallId = 0;
Socket *sock = connection->getSocket();
INetworkPlayer *np = sock ? sock->getPlayer() : nullptr;
if (np) newSmallId = np->GetSmallId();
app.DebugPrintf("RECONNECT: placeNewPlayer smallId=%d entityId=%d dim=%d\n",
newSmallId, player->entityId, level->dimension->id);
playerConnection->send( shared_ptr<LoginPacket>( new LoginPacket(L"", player->entityId, level->getLevelData()->getGenerator(), level->getSeed(), player->gameMode->getGameModeForPlayer()->getId(),
(byte) level->dimension->id, (byte) level->getMaxBuildHeight(), (byte) maxPlayersForPacket,
level->difficulty, TelemetryManager->GetMultiplayerInstanceID(), (BYTE)playerIndex, level->useNewSeaLevel(), player->getAllPlayerGamePrivileges(),
@ -979,6 +990,14 @@ void PlayerList::tick()
{
player->connection->disconnect( DisconnectPacket::eDisconnect_Closed );
}
#ifdef _WINDOWS64
// The old Connection's read/write threads are now dead (disconnect waits
// for them). Safe to recycle the smallId — no stale write thread can
// resolve getPlayer() to a new connection that reuses this slot.
WinsockNetLayer::PushFreeSmallId(smallId);
WinsockNetLayer::ClearSocketForSmallId(smallId);
#endif
}
LeaveCriticalSection(&m_closePlayersCS);
@ -1618,6 +1637,13 @@ void PlayerList::closePlayerConnectionBySmallId(BYTE networkSmallId)
LeaveCriticalSection(&m_closePlayersCS);
}
void PlayerList::queueSmallIdForRecycle(BYTE smallId)
{
EnterCriticalSection(&m_closePlayersCS);
m_smallIdsToClose.push_back(smallId);
LeaveCriticalSection(&m_closePlayersCS);
}
bool PlayerList::isXuidBanned(PlayerUID xuid)
{
if( xuid == INVALID_XUID ) return false;

View file

@ -133,6 +133,7 @@ public:
// 4J Added
void kickPlayerByShortId(BYTE networkSmallId);
void closePlayerConnectionBySmallId(BYTE networkSmallId);
void queueSmallIdForRecycle(BYTE smallId);
bool isXuidBanned(PlayerUID xuid);
// AP added for Vita so the range can be increased once the level starts
void setViewDistance(int newViewDistance);

View file

@ -46,19 +46,30 @@ void ServerConnection::handleConnection(shared_ptr<PendingConnection> uc)
void ServerConnection::stop()
{
std::vector<shared_ptr<PendingConnection> > pendingSnapshot;
EnterCriticalSection(&pending_cs);
for (unsigned int i = 0; i < pending.size(); i++)
{
shared_ptr<PendingConnection> uc = pending[i];
uc->connection->close(DisconnectPacket::eDisconnect_Closed);
}
pendingSnapshot = pending;
LeaveCriticalSection(&pending_cs);
for (unsigned int i = 0; i < players.size(); i++)
for (unsigned int i = 0; i < pendingSnapshot.size(); i++)
{
shared_ptr<PlayerConnection> player = players[i];
player->connection->close(DisconnectPacket::eDisconnect_Closed);
}
shared_ptr<PendingConnection> uc = pendingSnapshot[i];
if (uc != NULL && !uc->done)
{
uc->disconnect(DisconnectPacket::eDisconnect_Closed);
}
}
// Snapshot to avoid iterator invalidation if disconnect modifies the vector.
std::vector<shared_ptr<PlayerConnection> > playerSnapshot = players;
for (unsigned int i = 0; i < playerSnapshot.size(); i++)
{
shared_ptr<PlayerConnection> player = playerSnapshot[i];
if (player != NULL && !player->done)
{
player->disconnect(DisconnectPacket::eDisconnect_Quitting);
}
}
}
void ServerConnection::tick()
@ -107,7 +118,10 @@ void ServerConnection::tick()
players.erase(players.begin()+i);
i--;
}
player->connection->flush();
else
{
player->connection->flush();
}
}
}

View file

@ -256,7 +256,6 @@ void ServerLevel::tick()
if (time % (saveInterval) == (dimension->id * dimension->id * (saveInterval/2)))
#endif
{
//app.DebugPrintf("Incremental save\n");
PIXBeginNamedEvent(0,"Incremental save");
save(false, NULL);
PIXEndNamedEvent();

View file

@ -426,7 +426,6 @@ void ServerPlayer::doChunkSendingTick(bool dontDelayChunks)
// unloaded on the client and so just gradually build up more and more of the finite set of chunks as the player moves
if( !g_NetworkManager.SystemFlagGet(connection->getNetworkPlayer(),flagIndex) )
{
// app.DebugPrintf("Creating BRUP for %d %d\n",nearest.x, nearest.z);
PIXBeginNamedEvent(0,"Creation BRUP for sending\n");
int64_t before = System::currentTimeMillis();
shared_ptr<BlockRegionUpdatePacket> packet = shared_ptr<BlockRegionUpdatePacket>( new BlockRegionUpdatePacket(nearest.x * 16, 0, nearest.z * 16, 16, Level::maxBuildHeight, 16, level) );

View file

@ -118,9 +118,7 @@ void StringTable::ProcessStringTableData(void)
else
{
app.DebugPrintf("Failed to get language\n");
#ifdef _DEBUG
__debugbreak();
#endif
isStatic = false;
}

View file

@ -997,8 +997,15 @@ void Textures::replaceTextureDirect(shortArray rawPixels, int w, int h, int id)
void Textures::releaseTexture(int id)
{
if (id <= 0) return;
loadedImages.erase(id);
glDeleteTextures(id);
// TextureFree() has no bounds check and crashes on stale IDs (e.g. after
// RenderManager.Initialise() which memsets the texture table to zero).
// TextureGetTexture() IS safe — returns NULL for invalid/unallocated IDs.
if (RenderManager.TextureGetTexture(id) != NULL)
{
glDeleteTextures(id);
}
}
int Textures::loadHttpTexture(const wstring& url, const wstring& backup)

View file

@ -996,6 +996,13 @@ void gdraw_D3D1X_(SetTileOrigin)(ID3D1X(RenderTargetView) *main_rt, ID3D1X(Depth
static void RADLINK gdraw_SetViewSizeAndWorldScale(S32 w, S32 h, F32 scalex, F32 scaley)
{
static S32 s_lastW = 0, s_lastH = 0;
static F32 s_lastSx = 0, s_lastSy = 0;
if (w != s_lastW || h != s_lastH || scalex != s_lastSx || scaley != s_lastSy) {
app.DebugPrintf("[GDRAW] SetViewSize: fw=%d fh=%d scale=%.6f,%.6f frametex=%dx%d vx=%d vy=%d\n",
w, h, scalex, scaley, gdraw->frametex_width, gdraw->frametex_height, gdraw->vx, gdraw->vy);
s_lastW = w; s_lastH = h; s_lastSx = scalex; s_lastSy = scaley;
}
memset(gdraw->frame, 0, sizeof(gdraw->frame));
gdraw->cur = gdraw->frame;
gdraw->fw = w;

View file

@ -26,7 +26,7 @@ bool WinsockNetLayer::s_initialized = false;
BYTE WinsockNetLayer::s_localSmallId = 0;
BYTE WinsockNetLayer::s_hostSmallId = 0;
unsigned int WinsockNetLayer::s_nextSmallId = 1;
unsigned int WinsockNetLayer::s_nextSmallId = XUSER_MAX_COUNT;
CRITICAL_SECTION WinsockNetLayer::s_sendLock;
CRITICAL_SECTION WinsockNetLayer::s_connectionsLock;
@ -54,6 +54,10 @@ std::vector<BYTE> WinsockNetLayer::s_freeSmallIds;
SOCKET WinsockNetLayer::s_smallIdToSocket[256];
CRITICAL_SECTION WinsockNetLayer::s_smallIdToSocketLock;
SOCKET WinsockNetLayer::s_splitScreenSocket[XUSER_MAX_COUNT] = { INVALID_SOCKET, INVALID_SOCKET, INVALID_SOCKET, INVALID_SOCKET };
BYTE WinsockNetLayer::s_splitScreenSmallId[XUSER_MAX_COUNT] = { 0xFF, 0xFF, 0xFF, 0xFF };
HANDLE WinsockNetLayer::s_splitScreenRecvThread[XUSER_MAX_COUNT] = { NULL, NULL, NULL, NULL };
bool g_Win64MultiplayerHost = false;
bool g_Win64MultiplayerJoin = false;
int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
@ -111,6 +115,15 @@ void WinsockNetLayer::Shutdown()
s_hostConnectionSocket = INVALID_SOCKET;
}
// Stop accept loop first so no new RecvThread can be created while shutting down.
if (s_acceptThread != NULL)
{
WaitForSingleObject(s_acceptThread, 2000);
CloseHandle(s_acceptThread);
s_acceptThread = NULL;
}
std::vector<HANDLE> recvThreads;
EnterCriticalSection(&s_connectionsLock);
for (size_t i = 0; i < s_connections.size(); i++)
{
@ -118,18 +131,27 @@ void WinsockNetLayer::Shutdown()
if (s_connections[i].tcpSocket != INVALID_SOCKET)
{
closesocket(s_connections[i].tcpSocket);
s_connections[i].tcpSocket = INVALID_SOCKET;
}
if (s_connections[i].recvThread != NULL)
{
recvThreads.push_back(s_connections[i].recvThread);
s_connections[i].recvThread = NULL;
}
}
s_connections.clear();
LeaveCriticalSection(&s_connectionsLock);
if (s_acceptThread != NULL)
// Wait for all host-side receive threads to exit before destroying state.
for (size_t i = 0; i < recvThreads.size(); i++)
{
WaitForSingleObject(s_acceptThread, 2000);
CloseHandle(s_acceptThread);
s_acceptThread = NULL;
WaitForSingleObject(recvThreads[i], 2000);
CloseHandle(recvThreads[i]);
}
EnterCriticalSection(&s_connectionsLock);
s_connections.clear();
LeaveCriticalSection(&s_connectionsLock);
if (s_clientRecvThread != NULL)
{
WaitForSingleObject(s_clientRecvThread, 2000);
@ -137,16 +159,38 @@ void WinsockNetLayer::Shutdown()
s_clientRecvThread = NULL;
}
for (int i = 0; i < XUSER_MAX_COUNT; i++)
{
if (s_splitScreenSocket[i] != INVALID_SOCKET)
{
closesocket(s_splitScreenSocket[i]);
s_splitScreenSocket[i] = INVALID_SOCKET;
}
if (s_splitScreenRecvThread[i] != NULL)
{
WaitForSingleObject(s_splitScreenRecvThread[i], 2000);
CloseHandle(s_splitScreenRecvThread[i]);
s_splitScreenRecvThread[i] = NULL;
}
s_splitScreenSmallId[i] = 0xFF;
}
if (s_initialized)
{
EnterCriticalSection(&s_disconnectLock);
s_disconnectedSmallIds.clear();
LeaveCriticalSection(&s_disconnectLock);
EnterCriticalSection(&s_freeSmallIdLock);
s_freeSmallIds.clear();
LeaveCriticalSection(&s_freeSmallIdLock);
DeleteCriticalSection(&s_sendLock);
DeleteCriticalSection(&s_connectionsLock);
DeleteCriticalSection(&s_advertiseLock);
DeleteCriticalSection(&s_discoveryLock);
DeleteCriticalSection(&s_disconnectLock);
s_disconnectedSmallIds.clear();
DeleteCriticalSection(&s_freeSmallIdLock);
s_freeSmallIds.clear();
DeleteCriticalSection(&s_smallIdToSocketLock);
WSACleanup();
s_initialized = false;
@ -160,7 +204,7 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp)
s_isHost = true;
s_localSmallId = 0;
s_hostSmallId = 0;
s_nextSmallId = 1;
s_nextSmallId = XUSER_MAX_COUNT;
s_hostGamePort = port;
EnterCriticalSection(&s_freeSmallIdLock);
@ -249,6 +293,17 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
s_hostConnectionSocket = INVALID_SOCKET;
}
// Wait for old client recv thread to fully exit before starting a new connection.
// Without this, the old thread can read from the new socket (s_hostConnectionSocket
// is a global) and steal bytes from the new connection's TCP stream, causing
// packet stream misalignment on reconnect.
if (s_clientRecvThread != NULL)
{
WaitForSingleObject(s_clientRecvThread, 5000);
CloseHandle(s_clientRecvThread);
s_clientRecvThread = NULL;
}
struct addrinfo hints = {};
struct addrinfo* result = NULL;
@ -351,6 +406,13 @@ bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void* data, int dataSize)
{
if (sock == INVALID_SOCKET || dataSize <= 0) return false;
// TODO: s_sendLock is a single global lock for ALL sockets. If one client's
// send() blocks (TCP window full, slow WiFi), every other write thread stalls
// waiting for this lock — no data flows to any player until the slow send
// completes. This scales badly with player count (8+ players = noticeable).
// Fix: replace with per-socket locks indexed by smallId (s_perSocketSendLock[256]).
// The lock only needs to prevent interleaving of header+payload on the SAME socket;
// sends to different sockets are independent and should never block each other.
EnterCriticalSection(&s_sendLock);
BYTE header[4];
@ -450,19 +512,28 @@ void WinsockNetLayer::HandleDataReceived(BYTE fromSmallId, BYTE toSmallId, unsig
INetworkPlayer* pPlayerFrom = g_NetworkManager.GetPlayerBySmallId(fromSmallId);
INetworkPlayer* pPlayerTo = g_NetworkManager.GetPlayerBySmallId(toSmallId);
if (pPlayerFrom == NULL || pPlayerTo == NULL) return;
if (pPlayerFrom == NULL || pPlayerTo == NULL)
{
app.DebugPrintf("NET RECV: DROPPED %u bytes from=%d to=%d (player NULL: from=%p to=%p)\n",
dataSize, fromSmallId, toSmallId, pPlayerFrom, pPlayerTo);
return;
}
if (s_isHost)
{
::Socket* pSocket = pPlayerFrom->GetSocket();
if (pSocket != NULL)
pSocket->pushDataToQueue(data, dataSize, false);
else
app.DebugPrintf("NET RECV: DROPPED %u bytes, host pSocket NULL for from=%d\n", dataSize, fromSmallId);
}
else
{
::Socket* pSocket = pPlayerTo->GetSocket();
if (pSocket != NULL)
pSocket->pushDataToQueue(data, dataSize, true);
else
app.DebugPrintf("NET RECV: DROPPED %u bytes, client pSocket NULL for to=%d\n", dataSize, toSmallId);
}
}
@ -525,6 +596,7 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param)
{
app.DebugPrintf("Failed to send small ID to client\n");
closesocket(clientSocket);
PushFreeSmallId(assignedSmallId);
continue;
}
@ -662,7 +734,16 @@ bool WinsockNetLayer::PopDisconnectedSmallId(BYTE* outSmallId)
void WinsockNetLayer::PushFreeSmallId(BYTE smallId)
{
EnterCriticalSection(&s_freeSmallIdLock);
s_freeSmallIds.push_back(smallId);
// Guard against double-recycle: the reconnect path (queueSmallIdForRecycle) and
// the DoWork disconnect path can both push the same smallId. If we allow duplicates,
// AcceptThread will hand out the same smallId to two different connections.
bool alreadyFree = false;
for (size_t i = 0; i < s_freeSmallIds.size(); i++)
{
if (s_freeSmallIds[i] == smallId) { alreadyFree = true; break; }
}
if (!alreadyFree)
s_freeSmallIds.push_back(smallId);
LeaveCriticalSection(&s_freeSmallIdLock);
}
@ -682,6 +763,171 @@ void WinsockNetLayer::CloseConnectionBySmallId(BYTE smallId)
LeaveCriticalSection(&s_connectionsLock);
}
BYTE WinsockNetLayer::GetSplitScreenSmallId(int padIndex)
{
if (padIndex <= 0 || padIndex >= XUSER_MAX_COUNT) return 0xFF;
return s_splitScreenSmallId[padIndex];
}
SOCKET WinsockNetLayer::GetLocalSocket(BYTE senderSmallId)
{
if (senderSmallId == s_localSmallId)
return s_hostConnectionSocket;
for (int i = 1; i < XUSER_MAX_COUNT; i++)
{
if (s_splitScreenSmallId[i] == senderSmallId && s_splitScreenSocket[i] != INVALID_SOCKET)
return s_splitScreenSocket[i];
}
return INVALID_SOCKET;
}
bool WinsockNetLayer::JoinSplitScreen(int padIndex, BYTE* outSmallId)
{
if (!s_active || s_isHost || padIndex <= 0 || padIndex >= XUSER_MAX_COUNT)
return false;
if (s_splitScreenSocket[padIndex] != INVALID_SOCKET)
{
return false;
}
struct addrinfo hints = {};
struct addrinfo* result = NULL;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
char portStr[16];
sprintf_s(portStr, "%d", g_Win64MultiplayerPort);
if (getaddrinfo(g_Win64MultiplayerIP, portStr, &hints, &result) != 0 || result == NULL)
{
app.DebugPrintf("Win64 LAN: Split-screen getaddrinfo failed for %s:%d\n", g_Win64MultiplayerIP, g_Win64MultiplayerPort);
return false;
}
SOCKET sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (sock == INVALID_SOCKET)
{
freeaddrinfo(result);
return false;
}
int noDelay = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char*)&noDelay, sizeof(noDelay));
if (connect(sock, result->ai_addr, (int)result->ai_addrlen) == SOCKET_ERROR)
{
app.DebugPrintf("Win64 LAN: Split-screen connect() failed: %d\n", WSAGetLastError());
closesocket(sock);
freeaddrinfo(result);
return false;
}
freeaddrinfo(result);
BYTE assignBuf[1];
if (!RecvExact(sock, assignBuf, 1))
{
app.DebugPrintf("Win64 LAN: Split-screen failed to receive smallId\n");
closesocket(sock);
return false;
}
if (assignBuf[0] == WIN64_SMALLID_REJECT)
{
BYTE rejectBuf[5];
RecvExact(sock, rejectBuf, 5);
app.DebugPrintf("Win64 LAN: Split-screen connection rejected\n");
closesocket(sock);
return false;
}
BYTE assignedSmallId = assignBuf[0];
s_splitScreenSocket[padIndex] = sock;
s_splitScreenSmallId[padIndex] = assignedSmallId;
*outSmallId = assignedSmallId;
app.DebugPrintf("Win64 LAN: Split-screen pad %d connected, assigned smallId=%d\n", padIndex, assignedSmallId);
int* threadParam = new int;
*threadParam = padIndex;
s_splitScreenRecvThread[padIndex] = CreateThread(NULL, 0, SplitScreenRecvThreadProc, threadParam, 0, NULL);
if (s_splitScreenRecvThread[padIndex] == NULL)
{
delete threadParam;
closesocket(sock);
s_splitScreenSocket[padIndex] = INVALID_SOCKET;
s_splitScreenSmallId[padIndex] = 0xFF;
app.DebugPrintf("Win64 LAN: CreateThread failed for split-screen pad %d\n", padIndex);
return false;
}
return true;
}
void WinsockNetLayer::CloseSplitScreenConnection(int padIndex)
{
if (padIndex <= 0 || padIndex >= XUSER_MAX_COUNT) return;
if (s_splitScreenSocket[padIndex] != INVALID_SOCKET)
{
closesocket(s_splitScreenSocket[padIndex]);
s_splitScreenSocket[padIndex] = INVALID_SOCKET;
}
s_splitScreenSmallId[padIndex] = 0xFF;
if (s_splitScreenRecvThread[padIndex] != NULL)
{
WaitForSingleObject(s_splitScreenRecvThread[padIndex], 2000);
CloseHandle(s_splitScreenRecvThread[padIndex]);
s_splitScreenRecvThread[padIndex] = NULL;
}
}
DWORD WINAPI WinsockNetLayer::SplitScreenRecvThreadProc(LPVOID param)
{
int padIndex = *(int*)param;
delete (int*)param;
SOCKET sock = s_splitScreenSocket[padIndex];
BYTE localSmallId = s_splitScreenSmallId[padIndex];
std::vector<BYTE> recvBuf;
recvBuf.resize(WIN64_NET_RECV_BUFFER_SIZE);
while (s_active && s_splitScreenSocket[padIndex] != INVALID_SOCKET)
{
BYTE header[4];
if (!RecvExact(sock, header, 4))
{
app.DebugPrintf("Win64 LAN: Split-screen pad %d disconnected from host\n", padIndex);
break;
}
int packetSize = ((uint32_t)header[0] << 24) | ((uint32_t)header[1] << 16) |
((uint32_t)header[2] << 8) | ((uint32_t)header[3]);
if (packetSize <= 0 || packetSize > WIN64_NET_MAX_PACKET_SIZE)
{
app.DebugPrintf("Win64 LAN: Split-screen pad %d invalid packet size %d\n", padIndex, packetSize);
break;
}
if ((int)recvBuf.size() < packetSize)
recvBuf.resize(packetSize);
if (!RecvExact(sock, &recvBuf[0], packetSize))
{
app.DebugPrintf("Win64 LAN: Split-screen pad %d disconnected from host (body)\n", padIndex);
break;
}
HandleDataReceived(s_hostSmallId, localSmallId, &recvBuf[0], packetSize);
}
EnterCriticalSection(&s_disconnectLock);
s_disconnectedSmallIds.push_back(localSmallId);
LeaveCriticalSection(&s_disconnectLock);
return 0;
}
DWORD WINAPI WinsockNetLayer::ClientRecvThreadProc(LPVOID param)
{
std::vector<BYTE> recvBuf;

View file

@ -72,6 +72,12 @@ public:
static bool SendToSmallId(BYTE targetSmallId, const void* data, int dataSize);
static bool SendOnSocket(SOCKET sock, const void* data, int dataSize);
// Non-host split-screen: additional TCP connections to host, one per pad
static bool JoinSplitScreen(int padIndex, BYTE* outSmallId);
static void CloseSplitScreenConnection(int padIndex);
static SOCKET GetLocalSocket(BYTE senderSmallId);
static BYTE GetSplitScreenSmallId(int padIndex);
static bool IsHosting() { return s_isHost; }
static bool IsConnected() { return s_connected; }
static bool IsActive() { return s_active; }
@ -103,6 +109,7 @@ private:
static DWORD WINAPI AcceptThreadProc(LPVOID param);
static DWORD WINAPI RecvThreadProc(LPVOID param);
static DWORD WINAPI ClientRecvThreadProc(LPVOID param);
static DWORD WINAPI SplitScreenRecvThreadProc(LPVOID param);
static DWORD WINAPI AdvertiseThreadProc(LPVOID param);
static DWORD WINAPI DiscoveryThreadProc(LPVOID param);
@ -147,6 +154,11 @@ private:
static SOCKET s_smallIdToSocket[256];
static CRITICAL_SECTION s_smallIdToSocketLock;
// Per-pad split-screen TCP connections (client-side, non-host only)
static SOCKET s_splitScreenSocket[XUSER_MAX_COUNT];
static BYTE s_splitScreenSmallId[XUSER_MAX_COUNT];
static HANDLE s_splitScreenRecvThread[XUSER_MAX_COUNT];
public:
static void ClearSocketForSmallId(BYTE smallId);
};

View file

@ -48,6 +48,11 @@
#include "Network\WinsockNetLayer.h"
#include "Windows64_Xuid.h"
// Forward-declare the internal Renderer class and its global instance from 4J_Render_PC_d.lib.
// C4JRender (RenderManager) is a stateless wrapper — all D3D state lives in InternalRenderManager.
class Renderer;
extern Renderer InternalRenderManager;
#include "Xbox/resource.h"
#ifdef _MSC_VER
@ -91,10 +96,20 @@ DWORD dwProfileSettingsA[NUM_PROFILE_VALUES]=
BOOL g_bWidescreen = TRUE;
// Screen resolution — auto-detected from the monitor at startup.
// The 3D world renders at native resolution; Flash UI is 16:9-fitted and centered
// within each viewport (pillarboxed on ultrawide, letterboxed on tall displays).
// ApplyScreenMode() can still override these for debug/test resolutions via launch args.
int g_iScreenWidth = 1920;
int g_iScreenHeight = 1080;
// Real window dimensions — updated on every WM_SIZE so the 3D perspective
// always matches the current window, even after a resize.
int g_rScreenWidth = 1920;
int g_rScreenHeight = 1080;
float g_iAspectRatio = static_cast<float>(g_iScreenWidth) / g_iScreenHeight;
static bool g_bResizeReady = false;
char g_Win64Username[17] = { 0 };
wchar_t g_Win64UsernameW[17] = { 0 };
@ -103,14 +118,6 @@ wchar_t g_Win64UsernameW[17] = { 0 };
static bool g_isFullscreen = false;
static WINDOWPLACEMENT g_wpPrev = { sizeof(g_wpPrev) };
//--------------------------------------------------------------------------------------
// Update the Aspect Ratio to support Any Aspect Ratio
//--------------------------------------------------------------------------------------
void UpdateAspectRatio(int width, int height)
{
g_iAspectRatio = static_cast<float>(width) / height;
}
struct Win64LaunchOptions
{
int screenMode;
@ -531,6 +538,11 @@ ID3D11Texture2D* g_pDepthStencilBuffer = NULL;
// WM_SIZE - handle resizing logic to support Any Aspect Ratio
//
//
static bool ResizeD3D(int newW, int newH); // forward declaration
static bool g_bInSizeMove = false; // true while the user is dragging the window border
static int g_pendingResizeW = 0; // deferred resize dimensions
static int g_pendingResizeH = 0;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
@ -668,9 +680,40 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
}
}
break;
case WM_ENTERSIZEMOVE:
g_bInSizeMove = true;
break;
case WM_EXITSIZEMOVE:
g_bInSizeMove = false;
if (g_pendingResizeW > 0 && g_pendingResizeH > 0)
{
// g_rScreenWidth/Height updated inside ResizeD3D to backbuffer dims
ResizeD3D(g_pendingResizeW, g_pendingResizeH);
g_pendingResizeW = 0;
g_pendingResizeH = 0;
}
break;
case WM_SIZE:
{
UpdateAspectRatio(LOWORD(lParam), HIWORD(lParam));
int newW = LOWORD(lParam);
int newH = HIWORD(lParam);
if (newW > 0 && newH > 0)
{
if (g_bInSizeMove)
{
// Just store the latest size, resize when dragging ends
g_pendingResizeW = newW;
g_pendingResizeH = newH;
}
else
{
// Immediate resize (maximize, programmatic resize, etc.)
// g_rScreenWidth/Height updated inside ResizeD3D to backbuffer dims
ResizeD3D(newW, newH);
}
}
}
break;
default:
@ -719,7 +762,7 @@ BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
g_hInst = hInstance; // Store instance handle in our global variable
RECT wr = {0, 0, g_iScreenWidth, g_iScreenHeight}; // set the size, but not the position
RECT wr = {0, 0, g_rScreenWidth, g_rScreenHeight}; // set the size, but not the position
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); // adjust the size
g_hWnd = CreateWindow( "MinecraftClass",
@ -805,8 +848,8 @@ HRESULT InitDevice()
UINT width = rc.right - rc.left;
UINT height = rc.bottom - rc.top;
//app.DebugPrintf("width: %d, height: %d\n", width, height);
width = g_iScreenWidth;
height = g_iScreenHeight;
width = g_rScreenWidth;
height = g_rScreenHeight;
//app.DebugPrintf("width: %d, height: %d\n", width, height);
UINT createDeviceFlags = 0;
@ -838,7 +881,7 @@ HRESULT InitDevice()
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
@ -922,6 +965,281 @@ void Render()
g_pSwapChain->Present( 0, 0 );
}
//--------------------------------------------------------------------------------------
// Rebuild D3D11 resources after a window resize
//--------------------------------------------------------------------------------------
static bool ResizeD3D(int newW, int newH)
{
if (newW <= 0 || newH <= 0) return false;
if (!g_pSwapChain) return false;
if (!g_bResizeReady) return false;
int bbW = newW;
int bbH = newH;
// InternalRenderManager member offsets (from decompiled Renderer.h):
// 0x10 m_pDevice (ID3D11Device*)
// 0x18 m_pDeviceContext (ID3D11DeviceContext*)
// 0x20 m_pSwapChain (IDXGISwapChain*)
// 0x28 renderTargetView (ID3D11RenderTargetView*) — backbuffer RTV
// 0x50 renderTargetShaderResourceView (ID3D11ShaderResourceView*)
// 0x98 depthStencilView (ID3D11DepthStencilView*)
// 0x5138 backBufferWidth (DWORD) — used by StartFrame() for viewport
// 0x513C backBufferHeight (DWORD) — used by StartFrame() for viewport
//
// Strategy: destroy old swap chain, create new one, patch Renderer's internal
// pointers directly. This avoids both ResizeBuffers (outstanding ref issues)
// and Initialise() (which wipes the texture table via memset).
// The Renderer's old RTV/SRV/DSV are intentionally NOT released — they become
// orphaned with the old swap chain. Tiny leak, but avoids fighting unknown refs.
char* pRM = (char*)&InternalRenderManager;
ID3D11RenderTargetView** ppRM_RTV = (ID3D11RenderTargetView**)(pRM + 0x28);
ID3D11ShaderResourceView** ppRM_SRV = (ID3D11ShaderResourceView**)(pRM + 0x50);
ID3D11DepthStencilView** ppRM_DSV = (ID3D11DepthStencilView**)(pRM + 0x98);
IDXGISwapChain** ppRM_SC = (IDXGISwapChain**)(pRM + 0x20);
DWORD* pRM_BBWidth = (DWORD*)(pRM + 0x5138);
DWORD* pRM_BBHeight = (DWORD*)(pRM + 0x513C);
// Verify offsets by checking device and swap chain pointers
ID3D11Device** ppRM_Device = (ID3D11Device**)(pRM + 0x10);
if (*ppRM_Device != g_pd3dDevice || *ppRM_SC != g_pSwapChain)
{
app.DebugPrintf("[RESIZE] ERROR: RenderManager offset verification failed! "
"device=%p (expected %p) swapchain=%p (expected %p)\n",
*ppRM_Device, g_pd3dDevice, *ppRM_SC, g_pSwapChain);
return false;
}
// Cross-check backbuffer dimension offsets against swap chain desc
DXGI_SWAP_CHAIN_DESC oldScDesc;
g_pSwapChain->GetDesc(&oldScDesc);
bool bbDimsValid = (*pRM_BBWidth == oldScDesc.BufferDesc.Width &&
*pRM_BBHeight == oldScDesc.BufferDesc.Height);
if (!bbDimsValid)
{
app.DebugPrintf("[RESIZE] WARNING: backBuffer dim offsets wrong: "
"stored=%ux%u, swapchain=%ux%u\n",
*pRM_BBWidth, *pRM_BBHeight, oldScDesc.BufferDesc.Width, oldScDesc.BufferDesc.Height);
}
RenderManager.Suspend();
while (!RenderManager.Suspended()) { Sleep(1); }
PostProcesser::GetInstance().Cleanup();
g_pImmediateContext->ClearState();
g_pImmediateContext->Flush();
// Release OUR views and depth buffer
if (g_pRenderTargetView) { g_pRenderTargetView->Release(); g_pRenderTargetView = NULL; }
if (g_pDepthStencilView) { g_pDepthStencilView->Release(); g_pDepthStencilView = NULL; }
if (g_pDepthStencilBuffer) { g_pDepthStencilBuffer->Release(); g_pDepthStencilBuffer = NULL; }
gdraw_D3D11_PreReset();
// Get IDXGIFactory from the existing device BEFORE destroying the old swap chain.
// If anything fails before we have a new swap chain, we abort without destroying
// the old one — leaving the Renderer in a valid (old-size) state.
IDXGISwapChain* pOldSwapChain = g_pSwapChain;
bool success = false;
HRESULT hr;
IDXGIDevice* dxgiDevice = NULL;
IDXGIAdapter* dxgiAdapter = NULL;
IDXGIFactory* dxgiFactory = NULL;
hr = g_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
if (FAILED(hr)) goto postReset;
hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter);
if (FAILED(hr)) { dxgiDevice->Release(); goto postReset; }
hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory);
dxgiAdapter->Release();
dxgiDevice->Release();
if (FAILED(hr)) goto postReset;
// Create new swap chain at backbuffer size
{
DXGI_SWAP_CHAIN_DESC sd = {};
sd.BufferCount = 1;
sd.BufferDesc.Width = bbW;
sd.BufferDesc.Height = bbH;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
IDXGISwapChain* pNewSwapChain = NULL;
hr = dxgiFactory->CreateSwapChain(g_pd3dDevice, &sd, &pNewSwapChain);
dxgiFactory->Release();
if (FAILED(hr) || pNewSwapChain == NULL)
{
app.DebugPrintf("[RESIZE] CreateSwapChain FAILED hr=0x%08X — keeping old swap chain\n", (unsigned)hr);
goto postReset;
}
// New swap chain created successfully — NOW destroy the old one.
// The Renderer's internal RTV/SRV still reference the old backbuffer —
// those COM objects become orphaned (tiny leak, harmless). We DON'T
// release them because unknown code may also hold refs.
pOldSwapChain->Release();
g_pSwapChain = pNewSwapChain;
}
// Patch Renderer's swap chain pointer
*ppRM_SC = g_pSwapChain;
// Create render target views from new backbuffer
{
ID3D11Texture2D* pBackBuffer = NULL;
hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
if (FAILED(hr)) goto postReset;
// Our RTV
hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView);
if (FAILED(hr)) { pBackBuffer->Release(); goto postReset; }
// Renderer's internal RTV (offset 0x28)
hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, ppRM_RTV);
if (FAILED(hr)) { pBackBuffer->Release(); goto postReset; }
// Renderer's SRV: separate texture matching backbuffer dims (used by CaptureThumbnail)
D3D11_TEXTURE2D_DESC backDesc = {};
pBackBuffer->GetDesc(&backDesc);
pBackBuffer->Release();
D3D11_TEXTURE2D_DESC srvDesc = backDesc;
srvDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
ID3D11Texture2D* srvTexture = NULL;
hr = g_pd3dDevice->CreateTexture2D(&srvDesc, NULL, &srvTexture);
if (SUCCEEDED(hr))
{
hr = g_pd3dDevice->CreateShaderResourceView(srvTexture, NULL, ppRM_SRV);
srvTexture->Release();
}
if (FAILED(hr)) goto postReset;
}
// Recreate depth stencil at backbuffer size
{
D3D11_TEXTURE2D_DESC descDepth = {};
descDepth.Width = bbW;
descDepth.Height = bbH;
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
descDepth.SampleDesc.Count = 1;
descDepth.SampleDesc.Quality = 0;
descDepth.Usage = D3D11_USAGE_DEFAULT;
descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
hr = g_pd3dDevice->CreateTexture2D(&descDepth, NULL, &g_pDepthStencilBuffer);
if (FAILED(hr)) goto postReset;
D3D11_DEPTH_STENCIL_VIEW_DESC descDSView = {};
descDSView.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
descDSView.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
hr = g_pd3dDevice->CreateDepthStencilView(g_pDepthStencilBuffer, &descDSView, &g_pDepthStencilView);
if (FAILED(hr)) goto postReset;
}
// Patch Renderer's DSV (AddRef because both we and the Renderer reference it)
g_pDepthStencilView->AddRef();
*ppRM_DSV = g_pDepthStencilView;
// Update Renderer's cached backbuffer dimensions (StartFrame uses these for viewport)
if (bbDimsValid)
{
*pRM_BBWidth = (DWORD)bbW;
*pRM_BBHeight = (DWORD)bbH;
}
// Rebind render targets and viewport
g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, g_pDepthStencilView);
{
D3D11_VIEWPORT vp = {};
vp.Width = (FLOAT)bbW;
vp.Height = (FLOAT)bbH;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
g_pImmediateContext->RSSetViewports(1, &vp);
}
ui.updateRenderTargets(g_pRenderTargetView, g_pDepthStencilView);
ui.updateScreenSize(bbW, bbH);
// Track actual backbuffer dimensions for the rest of the engine
g_rScreenWidth = bbW;
g_rScreenHeight = bbH;
success = true;
postReset:
if (!success && g_pSwapChain != NULL)
{
// Failure recovery: recreate our views from whatever swap chain survived
// so ui.m_pRenderTargetView / m_pDepthStencilView don't dangle.
DXGI_SWAP_CHAIN_DESC recoveryDesc;
g_pSwapChain->GetDesc(&recoveryDesc);
int recW = (int)recoveryDesc.BufferDesc.Width;
int recH = (int)recoveryDesc.BufferDesc.Height;
if (g_pRenderTargetView == NULL)
{
ID3D11Texture2D* pBB = NULL;
if (SUCCEEDED(g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBB)))
{
g_pd3dDevice->CreateRenderTargetView(pBB, NULL, &g_pRenderTargetView);
pBB->Release();
}
}
if (g_pDepthStencilView == NULL)
{
D3D11_TEXTURE2D_DESC dd = {};
dd.Width = recW; dd.Height = recH; dd.MipLevels = 1; dd.ArraySize = 1;
dd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
dd.SampleDesc.Count = 1; dd.Usage = D3D11_USAGE_DEFAULT;
dd.BindFlags = D3D11_BIND_DEPTH_STENCIL;
if (g_pDepthStencilBuffer == NULL)
g_pd3dDevice->CreateTexture2D(&dd, NULL, &g_pDepthStencilBuffer);
if (g_pDepthStencilBuffer != NULL)
{
D3D11_DEPTH_STENCIL_VIEW_DESC dsvd = {};
dsvd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
dsvd.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
g_pd3dDevice->CreateDepthStencilView(g_pDepthStencilBuffer, &dsvd, &g_pDepthStencilView);
}
}
if (g_pRenderTargetView != NULL)
g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, g_pDepthStencilView);
ui.updateRenderTargets(g_pRenderTargetView, g_pDepthStencilView);
// If the surviving swap chain is the OLD one, dims are unchanged.
// If it's the NEW one (partial failure after swap), update to new dims.
if (g_pSwapChain != pOldSwapChain)
{
g_rScreenWidth = recW;
g_rScreenHeight = recH;
ui.updateScreenSize(recW, recH);
}
app.DebugPrintf("[RESIZE] FAILED but recovered views at %dx%d\n", g_rScreenWidth, g_rScreenHeight);
}
gdraw_D3D11_PostReset();
gdraw_D3D11_SetRendertargetSize(g_rScreenWidth, g_rScreenHeight);
if (success)
IggyFlushInstalledFonts();
RenderManager.Resume();
if (success)
PostProcesser::GetInstance().Init();
return success;
}
//--------------------------------------------------------------------------------------
// Toggle borderless fullscreen
//--------------------------------------------------------------------------------------
@ -963,6 +1281,8 @@ void CleanupDevice()
{
if( g_pImmediateContext ) g_pImmediateContext->ClearState();
if( g_pDepthStencilView ) g_pDepthStencilView->Release();
if( g_pDepthStencilBuffer ) g_pDepthStencilBuffer->Release();
if( g_pRenderTargetView ) g_pRenderTargetView->Release();
if( g_pSwapChain ) g_pSwapChain->Release();
if( g_pImmediateContext ) g_pImmediateContext->Release();
@ -976,7 +1296,7 @@ static Minecraft* InitialiseMinecraftRuntime()
RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
app.loadStringTable();
ui.init(g_pd3dDevice, g_pImmediateContext, g_pRenderTargetView, g_pDepthStencilView, g_iScreenWidth, g_iScreenHeight);
ui.init(g_pd3dDevice, g_pImmediateContext, g_pRenderTargetView, g_pDepthStencilView, g_rScreenWidth, g_rScreenHeight);
InputManager.Initialise(1, 3, MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
g_KBMInput.Init();
@ -1203,8 +1523,12 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
// Declare DPI awareness so GetSystemMetrics returns physical pixels
SetProcessDPIAware();
g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
g_iScreenHeight = GetSystemMetrics(SM_CYSCREEN);
// Use the native monitor resolution for the window and swap chain,
// but keep g_iScreenWidth/Height at 1920x1080 for logical resolution
// (SWF selection, ortho projection, game logic). The real window
// dimensions are tracked by g_rScreenWidth/g_rScreenHeight.
g_rScreenWidth = GetSystemMetrics(SM_CXSCREEN);
g_rScreenHeight = GetSystemMetrics(SM_CYSCREEN);
// Load username from username.txt
char exePath[MAX_PATH] = {};
@ -1411,6 +1735,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
CleanupDevice();
return 1;
}
g_bResizeReady = true;
//app.TemporaryCreateGameStart();
@ -1450,6 +1775,14 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
}
if (msg.message == WM_QUIT) break;
// When the window is minimized (e.g. "Show Desktop"), skip rendering entirely
// to avoid pegging the GPU at 100% presenting to a non-visible swap chain.
if (IsIconic(g_hWnd))
{
Sleep(100);
continue;
}
RenderManager.StartFrame();
#if 0
if(pMinecraft->soundEngine->isStreamingWavebankReady() &&

View file

@ -143,6 +143,12 @@ void ConsoleUIController::endCustomDraw(IggyCustomDrawCallbackRegion *region)
PIXEndNamedEvent();
}
void ConsoleUIController::updateRenderTargets(ID3D11RenderTargetView* rtv, ID3D11DepthStencilView* dsv)
{
m_pRenderTargetView = rtv;
m_pDepthStencilView = dsv;
}
void ConsoleUIController::setTileOrigin(S32 xPos, S32 yPos)
{
gdraw_D3D11_SetTileOrigin( m_pRenderTargetView,

View file

@ -16,10 +16,12 @@ public:
virtual CustomDrawData *calculateCustomDraw(IggyCustomDrawCallbackRegion *region);
virtual void endCustomDraw(IggyCustomDrawCallbackRegion *region);
void updateRenderTargets(ID3D11RenderTargetView* rtv, ID3D11DepthStencilView* dsv);
protected:
virtual void setTileOrigin(S32 xPos, S32 yPos);
public:
public:
GDrawTexture *getSubstitutionTexture(int textureId);
void destroySubstitutionTexture(void *destroyCallBackData, GDrawTexture *handle);

View file

@ -186,6 +186,29 @@ namespace Win64Xuid
return xuid;
}
inline PlayerUID DeriveXuidForPad(PlayerUID baseXuid, int iPad)
{
if (iPad == 0)
return baseXuid;
// Deterministic per-pad XUID: hash the base XUID with the pad number.
// Produces a fully unique 64-bit value with no risk of overlap.
// Suggested by rtm516 to avoid adjacent-integer collisions from the old "+ iPad" approach.
uint64_t raw = Mix64((uint64_t)baseXuid + (uint64_t)iPad);
raw |= 0x8000000000000000ULL; // keep high bit set like all our XUIDs
PlayerUID xuid = (PlayerUID)raw;
if (!IsPersistedUidValid(xuid))
{
raw ^= 0x0100000000000001ULL;
xuid = (PlayerUID)raw;
}
if (!IsPersistedUidValid(xuid))
xuid = (PlayerUID)(0xD15EA5E000000001ULL + iPad);
return xuid;
}
inline PlayerUID ResolvePersistentXuid()
{
// Process-local cache: uid.dat is immutable during runtime and this path is hot.

View file

@ -48,11 +48,12 @@ void glLoadIdentity()
RenderManager.MatrixSetIdentity();
}
// AAR - Use calculated aspect ratio to support dynamic resizing
extern float g_iAspectRatio;
// AAR - Use the aspect ratio passed by the caller. For single-player this
// equals g_iAspectRatio (screen width / height), but for split-screen
// getFovAndAspect adjusts it to match the viewport dimensions.
void gluPerspective(float fovy, float aspect, float zNear, float zFar)
{
RenderManager.MatrixPerspective(fovy, g_iAspectRatio, zNear, zFar);
RenderManager.MatrixPerspective(fovy, aspect, zNear, zFar);
}
void glOrtho(float left,float right,float bottom,float top,float zNear,float zFar)

View file

@ -303,16 +303,20 @@ void Arrow::tick()
damageSource = DamageSource::arrow(dynamic_pointer_cast<Arrow>(shared_from_this()), owner);
}
if(!res->entity->isInvulnerable())
{
if (isOnFire() && res->entity->GetType() != eTYPE_ENDERMAN)
{
res->entity->setOnFire(5);
}
}
if(res->entity->hurt(damageSource, dmg))
{
// Firx for #67839 - Customer Encountered: Bows enchanted with "Flame" still set things on fire if pvp/attack animals is turned off
// 4J Stu - We should not set the entity on fire unless we can cause some damage (this doesn't necessarily mean that the arrow hit lowered their health)
// set targets on fire first because we want cooked
// pork/chicken/steak
if (isOnFire() && res->entity->GetType() != eTYPE_ENDERMAN)
{
res->entity->setOnFire(5);
}
if (res->entity->instanceof(eTYPE_LIVINGENTITY))
{

View file

@ -48,7 +48,7 @@ BlockRegionUpdatePacket::BlockRegionUpdatePacket(int x, int y, int z, int xs, in
// TODO - we should be using compressed data directly here rather than decompressing first and then recompressing...
byteArray rawBuffer;
if( xs == 16 && ys == Level::maxBuildHeight && zs == 16 && ( ( x & 15 ) == 0 ) && ( y == 0 ) && ( ( z & 15 ) == 0 ) )
if( xs == 16 && ys == Level::maxBuildHeight && zs == 16 && ( ( x & 15 ) == 0 ) && ( y == 0 ) && ( ( z & 15 ) == 0 ) )
{
bIsFullChunk = true;
@ -66,6 +66,8 @@ BlockRegionUpdatePacket::BlockRegionUpdatePacket(int x, int y, int z, int xs, in
{
size = 0;
buffer = byteArray();
app.DebugPrintf("[BRUP-SERVER] *** EMPTY BUFFER for chunk(%d,%d) ys=%d bIsFullChunk=%d\n",
x>>4, z>>4, this->ys, bIsFullChunk ? 1 : 0);
}
else
{
@ -96,7 +98,7 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException
zs = dis->read() + 1;
bIsFullChunk = (chunkFlags & BLOCK_REGION_UPDATE_FULLCHUNK) ? true : false;
if(chunkFlags & BLOCK_REGION_UPDATE_ZEROHEIGHT)
if(chunkFlags & BLOCK_REGION_UPDATE_ZEROHEIGHT)
ys = 0;
size = dis->readInt();
@ -106,6 +108,10 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException
if(size == 0)
{
buffer = byteArray();
if(bIsFullChunk)
{
app.DebugPrintf("[BRUP-READ] *** ZERO-SIZE full chunk packet at (%d,%d)!\n", x>>4, z>>4);
}
}
else
{
@ -131,6 +137,11 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException
delete [] compressedBuffer.data;
if(buffer.length != outputSize)
{
app.DebugPrintf("*** BlockRegionUpdatePacket DECOMPRESS MISMATCH: expected=%d got=%d xs=%d ys=%d zs=%d fullChunk=%d compressedSize=%d levelIdx=%d\n",
buffer.length, outputSize, xs, ys, zs, bIsFullChunk ? 1 : 0, size, levelIdx);
}
assert(buffer.length == outputSize);
}
}

View file

@ -146,7 +146,9 @@ CompressedTileStorage::CompressedTileStorage(bool isEmpty)
bool CompressedTileStorage::isRenderChunkEmpty(int y) // y == 0, 16, 32... 112 (representing a 16 byte range)
{
int block;
unsigned short *blockIndices = (unsigned short *)indicesAndData;
unsigned char *localIndicesAndData = indicesAndData;
if(!localIndicesAndData) return true;
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
for( int x = 0; x < 16; x += 4 )
for( int z = 0; z < 16; z += 4 )
@ -533,8 +535,12 @@ void CompressedTileStorage::getData(byteArray retArray, unsigned int retOffset)
// Gets all tile values into an array of length 32768.
void CompressedTileStorage::getData(byteArray retArray, unsigned int retOffset)
{
unsigned short *blockIndices = (unsigned short *)indicesAndData;
unsigned char *data = indicesAndData + 1024;
// Snapshot pointer to avoid race with compress() swapping indicesAndData
unsigned char *localIndicesAndData = indicesAndData;
if(!localIndicesAndData) return;
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
unsigned char *data = localIndicesAndData + 1024;
for( int i = 0; i < 512; i++ )
{
@ -588,10 +594,13 @@ void CompressedTileStorage::getData(byteArray retArray, unsigned int retOffset)
// Get an individual tile value
int CompressedTileStorage::get(int x, int y, int z)
{
if(!indicesAndData) return 0;
// Take a local snapshot of indicesAndData to avoid a race with compress() which swaps the pointer.
// Both blockIndices and data must reference the same buffer, otherwise index offsets won't match.
unsigned char *localIndicesAndData = indicesAndData;
if(!localIndicesAndData) return 0;
unsigned short *blockIndices = (unsigned short *)indicesAndData;
unsigned char *data = indicesAndData + 1024;
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
unsigned char *data = localIndicesAndData + 1024;
int block, tile;
getBlockAndTile( &block, &tile, x, y, z );
@ -1223,7 +1232,10 @@ int CompressedTileStorage::getAllocatedSize(int *count0, int *count1, int *count
*count4 = 0;
*count8 = 0;
unsigned short *blockIndices = (unsigned short *)indicesAndData;
// Volatile read: compress() can swap indicesAndData concurrently outside cs_write
unsigned char *localIndicesAndData = *(unsigned char *volatile *)&indicesAndData;
if(!localIndicesAndData) return 0;
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
for(int i = 0; i < 512; i++ )
{
unsigned short idxType = blockIndices[i] & INDEX_TYPE_MASK;
@ -1256,7 +1268,9 @@ int CompressedTileStorage::getAllocatedSize(int *count0, int *count1, int *count
int CompressedTileStorage::getHighestNonEmptyY()
{
unsigned short *blockIndices = (unsigned short *)indicesAndData;
unsigned char *localIndicesAndData = indicesAndData;
if(!localIndicesAndData) return -1;
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
unsigned int highestYBlock = 0;
bool found = false;
@ -1297,19 +1311,26 @@ int CompressedTileStorage::getHighestNonEmptyY()
// Multiply by the number of vertical tiles in a block, and then add that again to be at the top of the block
highestNonEmptyY = (highestYBlock * 4) + 4;
}
else
{
app.DebugPrintf("[CTS-WARN] getHighestNonEmptyY() returned -1! allocatedSize=%d indicesAndData=%p\n",
allocatedSize, indicesAndData);
}
return highestNonEmptyY;
}
void CompressedTileStorage::write(DataOutputStream *dos)
{
dos->writeInt(allocatedSize);
if(indicesAndData)
// Volatile read: compress() can swap indicesAndData concurrently outside cs_write
unsigned char *localIndicesAndData = *(unsigned char *volatile *)&indicesAndData;
if(localIndicesAndData)
{
if(LOCALSYTEM_ENDIAN == BIGENDIAN)
{
// The first 1024 bytes are an array of shorts, so we need to reverse the endianness
byteArray indicesCopy(1024);
memcpy(indicesCopy.data, indicesAndData, 1024);
memcpy(indicesCopy.data, localIndicesAndData, 1024);
reverseIndices(indicesCopy.data);
dos->write(indicesCopy);
delete [] indicesCopy.data;
@ -1317,13 +1338,13 @@ void CompressedTileStorage::write(DataOutputStream *dos)
// Write the rest of the data
if(allocatedSize > 1024)
{
byteArray dataWrapper(indicesAndData + 1024, allocatedSize - 1024);
byteArray dataWrapper(localIndicesAndData + 1024, allocatedSize - 1024);
dos->write(dataWrapper);
}
}
else
{
byteArray wrapper(indicesAndData, allocatedSize);
byteArray wrapper(localIndicesAndData, allocatedSize);
dos->write(wrapper);
}
}

View file

@ -250,6 +250,14 @@ bool Connection::writeTick()
// Otherwise just buffer the packet with other outgoing packets as the java game did
if(packet->shouldDelay)
{
// Flush any buffered data BEFORE writing directly to the socket.
// bufferedDos and sos->writeWithFlags both write to the same underlying
// socket stream. If bufferedDos has unflushed bytes (from packets written
// via the outgoing queue above), writing directly to sos here would send
// the delayed packet's bytes BEFORE the buffered bytes, desynchronizing
// the TCP stream on the receiving end.
bufferedDos->flush();
Packet::writePacket(packet, byteArrayDos);
// 4J Stu - Changed this so that rather than writing to the network stream through a buffered stream we want to:
@ -341,7 +349,7 @@ bool Connection::readTick()
// printf("Con:0x%x readTick close EOS\n",this);
// 4J Stu - Remove this line
// Fix for #10410 - UI: If the player is removed from a splitscreened hosts game, the next game that player joins will produce a message stating that the host has left.
// Fix for #10410 - UI: If the player is removed from a splitscreened host<EFBFBD>s game, the next game that player joins will produce a message stating that the host has left.
//close(DisconnectPacket::eDisconnect_EndOfStream);
}

View file

@ -4240,6 +4240,10 @@ void Level::setBlocksAndData(int x, int y, int z, int xs, int ys, int zs, byteAr
if (z0 < 0) z0 = 0;
if (z1 > 16) z1 = 16;
LevelChunk *lc = getChunk(xc, zc);
if(lc->isEmpty())
{
app.DebugPrintf("[SETBLOCKS-BUG] getChunk(%d,%d) returned EmptyLevelChunk! Data will be LOST\n", xc, zc);
}
// 4J Stu - Unshare before we make any changes incase the server is already another step ahead of us
// Fix for #7904 - Gameplay: Players can dupe torches by throwing them repeatedly into water.
// This is quite expensive so only actually do it if we are hosting, online, and the update will actually

View file

@ -327,9 +327,30 @@ shared_ptr<Packet> Packet::readPacket(DataInputStream *dis, bool isServer) // th
id = dis->read();
if (id == -1) return nullptr;
// Track last few good packets for diagnosing TCP desync
static thread_local int s_lastIds[8] = {};
static thread_local int s_lastIdPos = 0;
static thread_local int s_packetCount = 0;
if ((isServer && serverReceivedPackets.find(id) == serverReceivedPackets.end()) || (!isServer && clientReceivedPackets.find(id) == clientReceivedPackets.end()))
{
//app.DebugPrintf("Bad packet id %d\n", id);
app.DebugPrintf("*** BAD PACKET ID %d (0x%02X) isServer=%d totalPacketsRead=%d\n", id, id, isServer ? 1 : 0, s_packetCount);
app.DebugPrintf("*** Last %d good packet IDs (oldest first): ", 8);
for (int dbg = 0; dbg < 8; dbg++)
{
int idx = (s_lastIdPos + dbg) % 8;
app.DebugPrintf("%d ", s_lastIds[idx]);
}
app.DebugPrintf("\n");
// Dump the next 32 bytes from the stream to see what follows
app.DebugPrintf("*** Next bytes in stream: ");
for (int dbg = 0; dbg < 32; dbg++)
{
int b = dis->read();
if (b == -1) { app.DebugPrintf("[EOS] "); break; }
app.DebugPrintf("%02X ", b);
}
app.DebugPrintf("\n");
__debugbreak();
assert(false);
// throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id));
@ -338,7 +359,10 @@ shared_ptr<Packet> Packet::readPacket(DataInputStream *dis, bool isServer) // th
packet = getPacket(id);
if (packet == NULL) assert(false);//throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id));
//app.DebugPrintf("%s reading packet %d\n", isServer ? "Server" : "Client", packet->getId());
s_lastIds[s_lastIdPos] = id;
s_lastIdPos = (s_lastIdPos + 1) % 8;
s_packetCount++;
packet->read(dis);
// }
// catch (EOFException e)
@ -372,7 +396,7 @@ shared_ptr<Packet> Packet::readPacket(DataInputStream *dis, bool isServer) // th
void Packet::writePacket(shared_ptr<Packet> packet, DataOutputStream *dos) // throws IOException TODO 4J JEV, should this declare a throws?
{
//app.DebugPrintf("Writing packet %d\n", packet->getId());
//app.DebugPrintf("NET WRITE: packet id=%d (0x%02X) estSize=%d\n", packet->getId(), packet->getId(), packet->getEstimatedSize());
dos->write(packet->getId());
packet->write(dos);
}

View file

@ -137,6 +137,9 @@ void Socket::pushDataToQueue(const BYTE * pbData, DWORD dwDataSize, bool fromHos
return;
}
//app.DebugPrintf("SOCKET PUSH: %u bytes to queue[%d] firstByte=0x%02X smallId=%d\n",
// dwDataSize, queueIdx, dwDataSize > 0 ? pbData[0] : 0, networkPlayerSmallId);
EnterCriticalSection(&m_queueLockNetwork[queueIdx]);
for( unsigned int i = 0; i < dwDataSize; i++ )
{
@ -311,7 +314,8 @@ void Socket::SocketInputStreamLocal::close()
{
m_streamOpen = false;
EnterCriticalSection(&s_hostQueueLock[m_queueIdx]);
s_hostQueue[m_queueIdx].empty();
std::queue<byte> empty;
std::swap(s_hostQueue[m_queueIdx], empty);
LeaveCriticalSection(&s_hostQueueLock[m_queueIdx]);
}
@ -359,7 +363,8 @@ void Socket::SocketOutputStreamLocal::close()
{
m_streamOpen = false;
EnterCriticalSection(&s_hostQueueLock[m_queueIdx]);
s_hostQueue[m_queueIdx].empty();
std::queue<byte> empty;
std::swap(s_hostQueue[m_queueIdx], empty);
LeaveCriticalSection(&s_hostQueueLock[m_queueIdx]);
}

View file

@ -59,9 +59,27 @@ HRESULT Compression::CompressLZXRLE(void *pDestination, unsigned int *pDestSize,
EnterCriticalSection(&rleCompressLock);
//static unsigned char rleBuf[1024*100];
// RLE can expand data (each 0xFF byte becomes 2 bytes), so worst case is 2*SrcSize.
// Use the static buffer when it fits, dynamically allocate otherwise.
static const unsigned int staticCompressSize = 1024*100;
unsigned int rleBufSize = SrcSize * 2;
unsigned char *dynamicRleBuf = NULL;
unsigned char *rleBuf;
if(rleBufSize <= staticCompressSize)
{
rleBuf = rleCompressBuf;
rleBufSize = staticCompressSize;
}
else
{
dynamicRleBuf = new unsigned char[rleBufSize];
rleBuf = dynamicRleBuf;
}
unsigned char *pucIn = (unsigned char *)pSource;
unsigned char *pucEnd = pucIn + SrcSize;
unsigned char *pucOut = (unsigned char *)rleCompressBuf;
unsigned char *pucOut = rleBuf;
// Compress with RLE first:
// 0 - 254 - encodes a single byte
@ -101,12 +119,15 @@ HRESULT Compression::CompressLZXRLE(void *pDestination, unsigned int *pDestSize,
*pucOut++ = thisOne;
}
} while (pucIn != pucEnd);
unsigned int rleSize = (unsigned int)(pucOut - rleCompressBuf);
unsigned int rleSize = (unsigned int)(pucOut - rleBuf);
PIXEndNamedEvent();
PIXBeginNamedEvent(0,"Secondary compression");
Compress(pDestination, pDestSize, rleCompressBuf, rleSize);
Compress(pDestination, pDestSize, rleBuf, rleSize);
PIXEndNamedEvent();
if(dynamicRleBuf != NULL) delete [] dynamicRleBuf;
LeaveCriticalSection(&rleCompressLock);
// printf("Compressed from %d to %d to %d\n",SrcSize,rleSize,*pDestSize);
@ -118,9 +139,26 @@ HRESULT Compression::CompressRLE(void *pDestination, unsigned int *pDestSize, vo
EnterCriticalSection(&rleCompressLock);
//static unsigned char rleBuf[1024*100];
// RLE can expand data (each 0xFF byte becomes 2 bytes), so worst case is 2*SrcSize.
static const unsigned int staticCompressSize = 1024*100;
unsigned int rleBufSize = SrcSize * 2;
unsigned char *dynamicRleBuf = NULL;
unsigned char *rleBuf;
if(rleBufSize <= staticCompressSize)
{
rleBuf = rleCompressBuf;
rleBufSize = staticCompressSize;
}
else
{
dynamicRleBuf = new unsigned char[rleBufSize];
rleBuf = dynamicRleBuf;
}
unsigned char *pucIn = (unsigned char *)pSource;
unsigned char *pucEnd = pucIn + SrcSize;
unsigned char *pucOut = (unsigned char *)rleCompressBuf;
unsigned char *pucOut = rleBuf;
// Compress with RLE first:
// 0 - 254 - encodes a single byte
@ -160,15 +198,14 @@ HRESULT Compression::CompressRLE(void *pDestination, unsigned int *pDestSize, vo
*pucOut++ = thisOne;
}
} while (pucIn != pucEnd);
unsigned int rleSize = (unsigned int)(pucOut - rleCompressBuf);
unsigned int rleSize = (unsigned int)(pucOut - rleBuf);
PIXEndNamedEvent();
LeaveCriticalSection(&rleCompressLock);
// Return
if (rleSize <= *pDestSize)
{
*pDestSize = rleSize;
memcpy(pDestination, rleCompressBuf, *pDestSize);
memcpy(pDestination, rleBuf, *pDestSize);
}
else
{
@ -177,6 +214,9 @@ HRESULT Compression::CompressRLE(void *pDestination, unsigned int *pDestSize, vo
#endif
}
if(dynamicRleBuf != NULL) delete [] dynamicRleBuf;
LeaveCriticalSection(&rleCompressLock);
return S_OK;
}
@ -195,33 +235,47 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz
//static unsigned char rleBuf[staticRleSize];
unsigned int rleSize = staticRleSize;
unsigned char *dynamicRleBuf = NULL;
HRESULT decompressResult;
if(*pDestSize > rleSize)
{
rleSize = *pDestSize;
dynamicRleBuf = new unsigned char[rleSize];
Decompress(dynamicRleBuf, &rleSize, pSource, SrcSize);
decompressResult = Decompress(dynamicRleBuf, &rleSize, pSource, SrcSize);
pucIn = (unsigned char *)dynamicRleBuf;
}
else
{
Decompress(rleDecompressBuf, &rleSize, pSource, SrcSize);
decompressResult = Decompress(rleDecompressBuf, &rleSize, pSource, SrcSize);
pucIn = (unsigned char *)rleDecompressBuf;
}
if(decompressResult != S_OK)
{
app.DebugPrintf("*** DecompressLZXRLE: zlib Decompress FAILED hr=0x%08X srcSize=%u expectedDest=%u rleSize=%u\n",
decompressResult, SrcSize, *pDestSize, rleSize);
if(dynamicRleBuf != NULL) delete [] dynamicRleBuf;
*pDestSize = 0;
LeaveCriticalSection(&rleDecompressLock);
return decompressResult;
}
//unsigned char *pucIn = (unsigned char *)rleDecompressBuf;
unsigned char *pucEnd = pucIn + rleSize;
unsigned char *pucOut = (unsigned char *)pDestination;
unsigned char *pucOutEnd = pucOut + *pDestSize;
while( pucIn != pucEnd )
{
unsigned char thisOne = *pucIn++;
if( thisOne == 255 )
{
if( pucIn >= pucEnd ) break;
unsigned int count = *pucIn++;
if( count < 3 )
{
count++;
if( pucOut + count > pucOutEnd ) break;
for( unsigned int i = 0; i < count; i++ )
{
*pucOut++ = 255;
@ -230,7 +284,9 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz
else
{
count++;
if( pucIn >= pucEnd ) break;
unsigned char data = *pucIn++;
if( pucOut + count > pucOutEnd ) break;
for( unsigned int i = 0; i < count; i++ )
{
*pucOut++ = data;
@ -239,6 +295,7 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz
}
else
{
if( pucOut >= pucOutEnd ) break;
*pucOut++ = thisOne;
}
}
@ -260,16 +317,19 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize,
unsigned char *pucIn = (unsigned char *)pSource;
unsigned char *pucEnd = pucIn + SrcSize;
unsigned char *pucOut = (unsigned char *)pDestination;
unsigned char *pucOutEnd = pucOut + *pDestSize;
while( pucIn != pucEnd )
{
unsigned char thisOne = *pucIn++;
if( thisOne == 255 )
{
if( pucIn >= pucEnd ) break;
unsigned int count = *pucIn++;
if( count < 3 )
{
count++;
if( pucOut + count > pucOutEnd ) break;
for( unsigned int i = 0; i < count; i++ )
{
*pucOut++ = 255;
@ -278,7 +338,9 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize,
else
{
count++;
if( pucIn >= pucEnd ) break;
unsigned char data = *pucIn++;
if( pucOut + count > pucOutEnd ) break;
for( unsigned int i = 0; i < count; i++ )
{
*pucOut++ = data;
@ -287,6 +349,7 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize,
}
else
{
if( pucOut >= pucOutEnd ) break;
*pucOut++ = thisOne;
}
}