aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.Client/Windows64
diff options
context:
space:
mode:
Diffstat (limited to 'Minecraft.Client/Windows64')
-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
7 files changed, 657 insertions, 28 deletions
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.