aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.Client
diff options
context:
space:
mode:
Diffstat (limited to 'Minecraft.Client')
-rw-r--r--Minecraft.Client/ClientConnection.cpp94
-rw-r--r--Minecraft.Client/ClientConnection.h1
-rw-r--r--Minecraft.Client/Common/Network/GameNetworkManager.cpp44
-rw-r--r--Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp46
-rw-r--r--Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp20
-rw-r--r--Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h1
-rw-r--r--Minecraft.Client/Common/UI/UIBitmapFont.cpp96
-rw-r--r--Minecraft.Client/Common/UI/UIComponent_Chat.cpp23
-rw-r--r--Minecraft.Client/Common/UI/UIComponent_MenuBackground.cpp23
-rw-r--r--Minecraft.Client/Common/UI/UIComponent_Panorama.cpp57
-rw-r--r--Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp23
-rw-r--r--Minecraft.Client/Common/UI/UIController.cpp149
-rw-r--r--Minecraft.Client/Common/UI/UIController.h1
-rw-r--r--Minecraft.Client/Common/UI/UIScene.cpp82
-rw-r--r--Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp35
-rw-r--r--Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h1
-rw-r--r--Minecraft.Client/Common/UI/UIScene_HUD.cpp50
-rw-r--r--Minecraft.Client/Common/UI/UIScene_HUD.h2
-rw-r--r--Minecraft.Client/Common/UI/UIScene_Keyboard.cpp64
-rw-r--r--Minecraft.Client/Common/UI/UIScene_Keyboard.h1
-rw-r--r--Minecraft.Client/Common/UI/UISplitScreenHelpers.h114
-rw-r--r--Minecraft.Client/Extrax64Stubs.cpp92
-rw-r--r--Minecraft.Client/Font.cpp4
-rw-r--r--Minecraft.Client/GameRenderer.cpp21
-rw-r--r--Minecraft.Client/Minecraft.cpp21
-rw-r--r--Minecraft.Client/MinecraftServer.cpp10
-rw-r--r--Minecraft.Client/PS3/Media/splashes.txt2
-rw-r--r--Minecraft.Client/PendingConnection.cpp31
-rw-r--r--Minecraft.Client/PlayerList.cpp26
-rw-r--r--Minecraft.Client/PlayerList.h1
-rw-r--r--Minecraft.Client/ServerConnection.cpp34
-rw-r--r--Minecraft.Client/ServerLevel.cpp1
-rw-r--r--Minecraft.Client/ServerPlayer.cpp1
-rw-r--r--Minecraft.Client/StringTable.cpp4
-rw-r--r--Minecraft.Client/Textures.cpp9
-rw-r--r--Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl7
-rw-r--r--Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp268
-rw-r--r--Minecraft.Client/Windows64/Network/WinsockNetLayer.h12
-rw-r--r--Minecraft.Client/Windows64/Windows64_Minecraft.cpp365
-rw-r--r--Minecraft.Client/Windows64/Windows64_UIController.cpp6
-rw-r--r--Minecraft.Client/Windows64/Windows64_UIController.h4
-rw-r--r--Minecraft.Client/Windows64/Windows64_Xuid.h23
-rw-r--r--Minecraft.Client/glWrapper.cpp7
43 files changed, 1528 insertions, 348 deletions
diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp
index c9e7bac2..0aa9735d 100644
--- a/Minecraft.Client/ClientConnection.cpp
+++ b/Minecraft.Client/ClientConnection.cpp
@@ -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)
- {
- //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
+ // World modification (block destruction) must only happen once
+ if (isPrimaryConnection())
{
- //app.DebugPrintf("Received ExplodePacket with knockback only data\n");
+ 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;
+ }
}
+ // 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();
diff --git a/Minecraft.Client/ClientConnection.h b/Minecraft.Client/ClientConnection.h
index a80c10f7..f13b93e7 100644
--- a/Minecraft.Client/ClientConnection.h
+++ b/Minecraft.Client/ClientConnection.h
@@ -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);
diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.cpp b/Minecraft.Client/Common/Network/GameNetworkManager.cpp
index 92ea8ad0..88db4911 100644
--- a/Minecraft.Client/Common/Network/GameNetworkManager.cpp
+++ b/Minecraft.Client/Common/Network/GameNetworkManager.cpp
@@ -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 );
diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
index 44ca3c2f..3d088935 100644
--- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
+++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
@@ -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;
}
diff --git a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp
index e55f207d..ce247728 100644
--- a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp
+++ b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp
@@ -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)
{
diff --git a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h
index 4877cfce..718a2d44 100644
--- a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h
+++ b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h
@@ -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;
};
diff --git a/Minecraft.Client/Common/UI/UIBitmapFont.cpp b/Minecraft.Client/Common/UI/UIBitmapFont.cpp
index afc2b139..31eef281 100644
--- a/Minecraft.Client/Common/UI/UIBitmapFont.cpp
+++ b/Minecraft.Client/Common/UI/UIBitmapFont.cpp
@@ -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.
diff --git a/Minecraft.Client/Common/UI/UIComponent_Chat.cpp b/Minecraft.Client/Common/UI/UIComponent_Chat.cpp
index 901b5a77..1d2f3cb0 100644
--- a/Minecraft.Client/Common/UI/UIComponent_Chat.cpp
+++ b/Minecraft.Client/Common/UI/UIComponent_Chat.cpp
@@ -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
diff --git a/Minecraft.Client/Common/UI/UIComponent_MenuBackground.cpp b/Minecraft.Client/Common/UI/UIComponent_MenuBackground.cpp
index d3a4c4c0..60b4a95c 100644
--- a/Minecraft.Client/Common/UI/UIComponent_MenuBackground.cpp
+++ b/Minecraft.Client/Common/UI/UIComponent_MenuBackground.cpp
@@ -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() );
}
} \ No newline at end of file
diff --git a/Minecraft.Client/Common/UI/UIComponent_Panorama.cpp b/Minecraft.Client/Common/UI/UIComponent_Panorama.cpp
index a52ebd72..bd3df101 100644
--- a/Minecraft.Client/Common/UI/UIComponent_Panorama.cpp
+++ b/Minecraft.Client/Common/UI/UIComponent_Panorama.cpp
@@ -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() );
}
}
diff --git a/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp b/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp
index 255740c9..844e928a 100644
--- a/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp
+++ b/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp
@@ -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
diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp
index 840ed389..b0bd7dd3 100644
--- a/Minecraft.Client/Common/UI/UIController.cpp
+++ b/Minecraft.Client/Common/UI/UIController.cpp
@@ -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)
diff --git a/Minecraft.Client/Common/UI/UIController.h b/Minecraft.Client/Common/UI/UIController.h
index 5b897b13..63ae5a19 100644
--- a/Minecraft.Client/Common/UI/UIController.h
+++ b/Minecraft.Client/Common/UI/UIController.h
@@ -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);
diff --git a/Minecraft.Client/Common/UI/UIScene.cpp b/Minecraft.Client/Common/UI/UIScene.cpp
index d01585cb..391a0502 100644
--- a/Minecraft.Client/Common/UI/UIScene.cpp
+++ b/Minecraft.Client/Common/UI/UIScene.cpp
@@ -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)
diff --git a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp
index 6b196c1b..7001ab81 100644
--- a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp
+++ b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp
@@ -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();
diff --git a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h
index 605f5dbd..1a2bfff4 100644
--- a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h
+++ b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h
@@ -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; }
diff --git a/Minecraft.Client/Common/UI/UIScene_HUD.cpp b/Minecraft.Client/Common/UI/UIScene_HUD.cpp
index 5f401c39..a5bd61a4 100644
--- a/Minecraft.Client/Common/UI/UIScene_HUD.cpp
+++ b/Minecraft.Client/Common/UI/UIScene_HUD.cpp
@@ -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 );
-
- 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;
- }
+ // 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);
- 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 );
}
diff --git a/Minecraft.Client/Common/UI/UIScene_HUD.h b/Minecraft.Client/Common/UI/UIScene_HUD.h
index 9d58ba4b..569b5234 100644
--- a/Minecraft.Client/Common/UI/UIScene_HUD.h
+++ b/Minecraft.Client/Common/UI/UIScene_HUD.h
@@ -176,5 +176,5 @@ protected:
#endif
private:
- void repositionHud();
+ void repositionHud(S32 tileWidth, S32 tileHeight, F32 scale);
};
diff --git a/Minecraft.Client/Common/UI/UIScene_Keyboard.cpp b/Minecraft.Client/Common/UI/UIScene_Keyboard.cpp
index 0af343bb..f9d558a0 100644
--- a/Minecraft.Client/Common/UI/UIScene_Keyboard.cpp
+++ b/Minecraft.Client/Common/UI/UIScene_Keyboard.cpp
@@ -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;
}
diff --git a/Minecraft.Client/Common/UI/UIScene_Keyboard.h b/Minecraft.Client/Common/UI/UIScene_Keyboard.h
index 054322f2..146934c1 100644
--- a/Minecraft.Client/Common/UI/UIScene_Keyboard.h
+++ b/Minecraft.Client/Common/UI/UIScene_Keyboard.h
@@ -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:
diff --git a/Minecraft.Client/Common/UI/UISplitScreenHelpers.h b/Minecraft.Client/Common/UI/UISplitScreenHelpers.h
new file mode 100644
index 00000000..e451b3f2
--- /dev/null
+++ b/Minecraft.Client/Common/UI/UISplitScreenHelpers.h
@@ -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));
+}
diff --git a/Minecraft.Client/Extrax64Stubs.cpp b/Minecraft.Client/Extrax64Stubs.cpp
index da9fe12e..b21b1c76 100644
--- a/Minecraft.Client/Extrax64Stubs.cpp
+++ b/Minecraft.Client/Extrax64Stubs.cpp
@@ -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) {}
diff --git a/Minecraft.Client/Font.cpp b/Minecraft.Client/Font.cpp
index ce2275f6..7ab260f6 100644
--- a/Minecraft.Client/Font.cpp
+++ b/Minecraft.Client/Font.cpp
@@ -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;
diff --git a/Minecraft.Client/GameRenderer.cpp b/Minecraft.Client/GameRenderer.cpp
index be389211..b24e8446 100644
--- a/Minecraft.Client/GameRenderer.cpp
+++ b/Minecraft.Client/GameRenderer.cpp
@@ -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();
diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp
index 1fc8bd54..d843ee79 100644
--- a/Minecraft.Client/Minecraft.cpp
+++ b/Minecraft.Client/Minecraft.cpp
@@ -2373,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);
}
@@ -4669,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);
-
- minecraft = new Minecraft(NULL, NULL, NULL, 1280, 720, 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, logicalW, logicalH, fullScreen);
/* - 4J - removed
{
diff --git a/Minecraft.Client/MinecraftServer.cpp b/Minecraft.Client/MinecraftServer.cpp
index 55b02cb9..54be25ff 100644
--- a/Minecraft.Client/MinecraftServer.cpp
+++ b/Minecraft.Client/MinecraftServer.cpp
@@ -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
diff --git a/Minecraft.Client/PS3/Media/splashes.txt b/Minecraft.Client/PS3/Media/splashes.txt
index 7f7835c6..df4a4be1 100644
--- a/Minecraft.Client/PS3/Media/splashes.txt
+++ b/Minecraft.Client/PS3/Media/splashes.txt
@@ -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!
diff --git a/Minecraft.Client/PendingConnection.cpp b/Minecraft.Client/PendingConnection.cpp
index 29ab7c28..839cd550 100644
--- a/Minecraft.Client/PendingConnection.cpp
+++ b/Minecraft.Client/PendingConnection.cpp
@@ -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)
diff --git a/Minecraft.Client/PlayerList.cpp b/Minecraft.Client/PlayerList.cpp
index 1742756e..80fcb112 100644
--- a/Minecraft.Client/PlayerList.cpp
+++ b/Minecraft.Client/PlayerList.cpp
@@ -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;
diff --git a/Minecraft.Client/PlayerList.h b/Minecraft.Client/PlayerList.h
index 03ed2398..a4ae9c5d 100644
--- a/Minecraft.Client/PlayerList.h
+++ b/Minecraft.Client/PlayerList.h
@@ -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);
diff --git a/Minecraft.Client/ServerConnection.cpp b/Minecraft.Client/ServerConnection.cpp
index 07616aa4..27fc6e62 100644
--- a/Minecraft.Client/ServerConnection.cpp
+++ b/Minecraft.Client/ServerConnection.cpp
@@ -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();
+ }
}
}
diff --git a/Minecraft.Client/ServerLevel.cpp b/Minecraft.Client/ServerLevel.cpp
index 242a0df9..74881a6f 100644
--- a/Minecraft.Client/ServerLevel.cpp
+++ b/Minecraft.Client/ServerLevel.cpp
@@ -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();
diff --git a/Minecraft.Client/ServerPlayer.cpp b/Minecraft.Client/ServerPlayer.cpp
index 789b3176..29847a93 100644
--- a/Minecraft.Client/ServerPlayer.cpp
+++ b/Minecraft.Client/ServerPlayer.cpp
@@ -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) );
diff --git a/Minecraft.Client/StringTable.cpp b/Minecraft.Client/StringTable.cpp
index 234a8943..bdee2ca9 100644
--- a/Minecraft.Client/StringTable.cpp
+++ b/Minecraft.Client/StringTable.cpp
@@ -118,9 +118,7 @@ void StringTable::ProcessStringTableData(void)
else
{
app.DebugPrintf("Failed to get language\n");
-#ifdef _DEBUG
- __debugbreak();
-#endif
+
isStatic = false;
}
diff --git a/Minecraft.Client/Textures.cpp b/Minecraft.Client/Textures.cpp
index 9a2e98a6..5349412e 100644
--- a/Minecraft.Client/Textures.cpp
+++ b/Minecraft.Client/Textures.cpp
@@ -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)
diff --git a/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl b/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl
index 7e4db038..9a447833 100644
--- a/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl
+++ b/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl
@@ -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;
diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
index c76bc2fe..ec5634ed 100644
--- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
+++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
@@ -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;
diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h
index f30240d3..66010fe4 100644
--- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h
+++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h
@@ -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);
};
diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
index 678c8d62..e4446136 100644
--- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
+++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
@@ -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;
@@ -923,6 +966,281 @@ void Render()
}
//--------------------------------------------------------------------------------------
+// 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
//--------------------------------------------------------------------------------------
void ToggleFullscreen()
@@ -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() &&
diff --git a/Minecraft.Client/Windows64/Windows64_UIController.cpp b/Minecraft.Client/Windows64/Windows64_UIController.cpp
index 10ae20af..db7fa3dc 100644
--- a/Minecraft.Client/Windows64/Windows64_UIController.cpp
+++ b/Minecraft.Client/Windows64/Windows64_UIController.cpp
@@ -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,
diff --git a/Minecraft.Client/Windows64/Windows64_UIController.h b/Minecraft.Client/Windows64/Windows64_UIController.h
index 2b2ccdba..28a137d3 100644
--- a/Minecraft.Client/Windows64/Windows64_UIController.h
+++ b/Minecraft.Client/Windows64/Windows64_UIController.h
@@ -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);
diff --git a/Minecraft.Client/Windows64/Windows64_Xuid.h b/Minecraft.Client/Windows64/Windows64_Xuid.h
index aa88f296..f5fd62b9 100644
--- a/Minecraft.Client/Windows64/Windows64_Xuid.h
+++ b/Minecraft.Client/Windows64/Windows64_Xuid.h
@@ -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.
diff --git a/Minecraft.Client/glWrapper.cpp b/Minecraft.Client/glWrapper.cpp
index a356e674..65a496fa 100644
--- a/Minecraft.Client/glWrapper.cpp
+++ b/Minecraft.Client/glWrapper.cpp
@@ -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)