Adds the networking layer for non-host split-screen multiplayer, implements
live window resize with swap chain recreation, fixes bitmap font scaling at
small window sizes, and fixes several crash-causing bugs in the multiplayer
stack (compression buffer overflow, TCP stream desync, chunk visibility race,
CompressedTileStorage torn reads, reconnect stability).
== Non-host split-screen multiplayer ==
Each split-screen pad on a non-host client opens its own TCP connection to
the host. From the host's perspective each connection looks like a normal
remote player (gets its own smallId, Socket, PlayerConnection).
WinsockNetLayer: JoinSplitScreen(), CloseSplitScreenConnection(),
SplitScreenRecvThreadProc, per-pad socket/thread/smallId tracking
(s_splitScreenSocket[], s_splitScreenSmallId[], s_splitScreenRecvThread[]).
GetLocalSocket() returns the correct TCP socket for a given local sender's
smallId. GetSplitScreenSmallId() returns the host-assigned smallId for a pad.
GameNetworkManager::CreateSocket: non-host path (localPlayer && !IsHost() &&
IsInGameplay()) calls JoinSplitScreen, sets the IQNet slot's smallId and
resolvedXuid, creates a non-hostLocal Socket + ClientConnection, sends
PreLoginPacket, registers via addPendingLocalConnection.
PlatformNetworkManagerStub::RemoveLocalPlayerByUserIndex: implemented the
formerly-empty stub. Calls NotifyPlayerLeaving, CloseSplitScreenConnection,
and clears the IQNet slot fields so the pad can rejoin cleanly.
SmallId pool: s_nextSmallId starts at XUSER_MAX_COUNT (4), reserving
m_player[0-3] for local pads so remote players never collide.
IQNetPlayer::SendData: non-host local senders now route through
GetLocalSocket(m_smallId) instead of always using SendToSmallId.
IQNet::GetLocalPlayerByUserIndex: rewritten. Pad 0 on non-host uses
GetLocalSmallId() for direct lookup; pads 1-3 check m_player[padIdx].
C_4JProfile::IsSignedIn: pad 0 always returns true (was checking controller
connection, which is unreliable on Win64).
GetGamertag/GetDisplayName: for pads 1-3 with active local players, returns
the pad-specific gamertag from IQNet::m_player instead of always returning
the primary username.
ClientConnection: isPrimaryConnection() (true on host or for the primary pad
on non-host) guards relative-delta and world-modifying handlers to prevent
double-processing of shared state:
- Guarded: handleMoveEntity, handleMoveEntitySmall, handleChunkTilesUpdate,
handleBlockRegionUpdate, handleTileUpdate, handleTakeItemEntity,
handleSignUpdate, handleTileEntityData, handleTileEvent,
handleTileDestruction, handleComplexItemData, handleLevelEvent,
handleSoundEvent, handleParticleEvent, handleAddGlobalEntity.
- handleSetEntityMotion: secondary connections only accept motion targeting
their own local player (knockback).
- handleExplosion: world modification (finalizeExplosion) guarded,
per-player knockback unguarded. Added null check on localplayers[].
- Entity spawn/remove/teleport/data handlers left unguarded (putEntity is
idempotent, absolute value setters).
handleLogin: added else clause to set level when the dimension already exists
(was leaving level NULL on reconnect).
handleChunkVisibilityArea/handleChunkVisibility: added null check on level.
handleContainerOpen: added null check on localplayers[m_userIndex].
== Reconnect stability ==
PendingConnection: duplicate XUID no longer rejects with eDisconnect_Banned.
Instead it force-disconnects the stale old connection via
stalePlayer->connection->disconnect(), queues the old smallId for recycling
via queueSmallIdForRecycle(), then calls handleAcceptedLogin for the new
connection.
MinecraftServer: swapped tick order so players->tick() (disconnect queue)
runs before connection->tick() (new logins). The old player is removed
from PlayerList before the new LoginPacket's XUID check runs.
PlayerList: PushFreeSmallId and ClearSocketForSmallId moved here from
DoWork, called only after PlayerConnection::disconnect() completes and
the read/write threads are dead. New queueSmallIdForRecycle() method lets
PendingConnection push smallIds into m_smallIdsToClose, which PlayerList::tick()
processes through closePlayerConnectionBySmallId() for deferred cleanup.
Prevents a race where the old write thread could resolve getPlayer() to a
recycled smallId's new connection and send stale packets on it.
SocketInputStreamLocal::close() and SocketOutputStreamLocal::close() now
actually clear their queues (std::swap with empty queue instead of calling
.empty() which is a read-only no-op).
ServerConnection::stop(): pending and players vectors are snapshot-copied
before iterating (prevents iterator invalidation). Remote players receive
a DisconnectPacket via disconnect(eDisconnect_Quitting) instead of raw
close(). tick(): added else clause so flush() only runs on live connections.
WinsockNetLayer::Shutdown(): accept thread stopped first (prevents new recv
threads from spawning), then all recv threads are collected and waited on,
then connections are closed and split-screen sockets cleaned up. Clears
disconnect and free-pool vectors before deleting critical sections.
WinsockNetLayer::JoinGame(): waits for old s_clientRecvThread to fully
exit before creating a new TCP connection. Prevents the old recv thread
from reading bytes off the new socket and desynchronizing the stream.
== Compression buffer overflow ==
CompressLZXRLE and CompressRLE wrote RLE intermediate output into a fixed
100KB buffer with no bounds checking. Full chunk columns are ~160KB and
the RLE step can expand 0xFF bytes to 2 bytes each, easily overflowing
into rleDecompressBuf and heap metadata. This caused delayed crashes in
unrelated code (Packet::readPacket, LevelRenderer::updateDirtyChunks) after
the first autosave, since that's when full chunks get compressed.
Fix: dynamic allocation when worst-case RLE output (SrcSize * 2) exceeds
the static buffer. Static buffer still used for small inputs (zero overhead).
CompressRLE: moved LeaveCriticalSection after dynamic buffer cleanup.
DecompressLZXRLE: now checks zlib return value (was completely ignored).
On failure, bails out immediately with *pDestSize = 0. Added RLE input
bounds checking (pucIn >= pucEnd before reading count/data bytes) and
output bounds checking (pucOut + count > pucOutEnd). Same bounds checks
applied to DecompressRLE.
== Stream desync (Connection write thread) ==
The write thread had two output paths to the same TCP socket: bufferedDos
(5KB buffered stream) and direct sos->writeWithFlags(). Chunk data sent
via queueSend() used the direct path with shouldDelay=true, while other
packets used bufferedDos. If bufferedDos had unflushed bytes, the direct
write arrived at the client first, reordering the TCP stream and producing
bad packet ID crashes.
Fix: flush bufferedDos immediately before every direct sos->writeWithFlags().
== Chunk visibility race (empty first chunk after 30s) ==
BlockRegionUpdatePacket (direct socket write via queueSend) could arrive
at the client before ChunkVisibilityAreaPacket (buffered). The client
called getChunk() on a chunk that didn't exist yet in the cache, got
EmptyLevelChunk (whose setBlocksAndData is a no-op), and silently lost
the block data. On superflat this left one invisible chunk; on normal
worlds it crashed the renderer.
Fix: handleBlockRegionUpdate calls dimensionLevel->setChunkVisible() for
full-chunk BRUPs before writing data, making it independent of packet
ordering. Added post-write verification logging.
CompressedTileStorage race: get() reads indicesAndData twice without a
lock. compress() can swap the pointer between reads, producing indices
from the old buffer paired with data from the new buffer. Fix: snapshot
indicesAndData into a local variable before deriving both pointers. Same
snapshot pattern applied to getData() (non-Vita path), isRenderChunkEmpty(),
getHighestNonEmptyY(), getAllocatedSize(), and write(). All methods now
also guard against NULL snapshots.
== Window resize ==
ResizeD3D() destroys the old swap chain, creates a new one at the target
size, then patches InternalRenderManager members directly via memory
offsets (0x20=swap chain, 0x28=RTV, 0x50=SRV, 0x98=DSV, 0x5138/0x513C=
backbuffer width/height). Offset verification cross-checks known pointers
(device at 0x10, swap chain at 0x20) before patching. Old RTV/SRV are
intentionally leaked (orphaned with the old swap chain) to avoid fighting
unknown ref holders in the precompiled RenderManager.
The flow: Suspend RenderManager, ClearState+Flush, release views,
gdraw_D3D11_PreReset, destroy old swap chain, create new swap chain via
IDXGIFactory, patch offsets, recreate RTV/SRV/DSV, rebind render targets,
update UIController (updateRenderTargets + updateScreenSize),
gdraw_D3D11_PostReset + SetRendertargetSize, IggyFlushInstalledFonts,
Resume, PostProcesser::Init.
WM_SIZE handling defers resize during window drag (WM_ENTERSIZEMOVE/
WM_EXITSIZEMOVE). Immediate resizes (maximize, programmatic) call
ResizeD3D directly. Removed the old UpdateAspectRatio() function.
CleanupDevice() was leaking g_pDepthStencilView and g_pDepthStencilBuffer.
InitDevice: swap chain BufferUsage now includes DXGI_USAGE_SHADER_INPUT
(needed for the SRV created from the backbuffer for CaptureThumbnail).
New globals: g_rScreenWidth/g_rScreenHeight (real window dimensions,
updated on resize) vs g_iScreenWidth/g_iScreenHeight (fixed logical
resolution, stays 1920x1080).
ComputeViewportForPlayer and getFovAndAspect now use g_rScreenWidth/
g_rScreenHeight instead of the fixed startup values, so 3D perspective
and split-screen viewports adapt to window size.
Main loop: rendering skipped when window is minimized (IsIconic check)
to avoid 100% GPU usage on a hidden swap chain.
Windows64_UIController: new updateRenderTargets(rtv, dsv) method updates
cached D3D pointers used by gdraw_D3D11_SetTileOrigin every frame.
UIController.h: new inline updateScreenSize(w, h) sets m_fScreenWidth/
m_fScreenHeight so all downstream UI code picks up the new size.
== Bitmap font scaling ==
At small window sizes, dynamic text (scrollable list items, HowToPlay
pages) showed overlapping characters. Static SWF text was unaffected
because it uses embedded vector glyphs.
Root cause in UIBitmapFont.cpp GetGlyphBitmap: when display scale is
smaller than the bitmap's native scale (pixel_scale < truePixelScale,
glyphScale stays at 1), Iggy displayed the glyph at native 1:1 pixel
size but advanced the cursor by the smaller display-scale amount.
At intermediate window sizes (e.g. 1678x756, scale factor ~0.7), a
second bug appeared: some SWF font sizes produced pixel_scale just above
truePixelScale (13 for Mojangles_11) while others fell just below,
splitting glyphs across the small-display and normal cache branches.
The normal branch cached all glyphs in a single [truePixelScale, 99]
range, so the first glyph cached set pixel_scale_correct for every
subsequent request regardless of font size. Different font sizes then
got scaled by wrong ratios (e.g. 18.9/13.3 = 1.42x with point sampling),
producing visibly inconsistent letter sizes. This only happened at
specific window sizes where the display scale put some fonts above and
others below the truePixelScale boundary. Full 1080p and very small
windows were unaffected because all fonts landed in the same branch.
Fix: on _WINDOWS64, always use pixel_scale_correct = truePixelScale so
every cache entry is consistent regardless of which font size creates it
first. Two cache ranges: downscale (pixel_scale < truePixelScale) uses
bilinear for smooth reduction, upscale uses point_sample for crisp
pixel-art rendering. At most two cache entries per glyph. The console
code path (fixed resolution, integer-multiple scaling) is preserved
behind #else.
UIScene.cpp loadMovie: always load 1080.swf on _WINDOWS64 regardless of
window size. The old height-based selection could pick 480 or 720 variants
which either crashed or loaded the wrong skin library (skinHD.swf vs
skin.swf). Display size is now set via Fit16x9 BEFORE the init tick so
Iggy's ActionScript text field creation sees the same scale that render()
will use. IggyFlushInstalledFonts() called after init tick to clear stale
glyph cache entries from previous scenes.
Font.cpp addCharacterQuad/renderCharacter: yOff was computed with
m_charWidth instead of m_charHeight, producing wrong texture coordinates
for non-square glyph cells. This is the world-rendering font (chat, signs,
name tags), not the Iggy UI font.
== XUID generation ==
Split-screen pad XUIDs derived by hashing baseXuid + iPad through Mix64
(DeriveXuidForPad in Windows64_Xuid.h) instead of simple addition. Pad 0
returns the base XUID unchanged for save compatibility. Includes validity
fallbacks if the hash produces an invalid XUID. (Suggested by rtm516)
== Misc ==
Packet::readPacket: thread-local ring buffer tracks last 8 good packet IDs.
On bad packet ID, dumps the history plus next 32 bytes of stream for
diagnosing TCP desynchronization.
PendingConnection/PlayerList: debug logging for the reconnect flow
(duplicate XUID handling, force-disconnect, handleAcceptedLogin,
placeNewPlayer with smallId/entityId/dimension).
ClientConnection::handleBlockRegionUpdate: warning log when a full chunk
arrives with ys==0 (empty full chunk, data loss indicator).
== Known issues / future work ==
SendOnSocket global lock (WinsockNetLayer.cpp): s_sendLock is a single
CriticalSection serializing ALL TCP sends across ALL connections. If one
client's send() blocks (TCP window full, slow network), every other write
thread stalls — no data flows to any player until the slow send completes.
Each PlayerConnection has its own write thread, so with 8+ players one slow
client can cause latency spikes or timeout disconnects for healthy players.
Fix: replace s_sendLock with per-socket locks indexed by smallId. The lock
only needs to prevent header+payload interleaving on the SAME socket; sends
to different sockets are independent. Deferred to a separate PR to keep
this one focused.
Textures::releaseTexture: early return for id <= 0, checks
TextureGetTexture(id) != NULL before calling glDeleteTextures. Prevents
crashes on stale texture IDs after RenderManager reset.
UIController TextureSubstitutionDestroyCallback: null guard on
Minecraft::GetInstance() and mc->textures before calling releaseTexture.
Prevents crash during shutdown.
StringTable: removed __debugbreak() on language load failure in debug builds.
- Skip frustum culling for the entity the player is currently riding, preventing the horse from disappearing when looking up (fixes a bug reported on Discord)
- Fix HorseRenderer::bindTexture() to delegate to base class for single textures (fire atlas, enchant glint), only using bindTextureLayers() for multi-layer horse textures
Co-authored-by: MCbabel <MCbabel@users.noreply.github.com>
This code was not tested and breaks in Release builds, reverting to restore
functionality of the nightly. All in-game menus do not work and generating
a world crashes.
This reverts commit a9be52c41a.
* Fixed boats falling and a TP glitch #266
* Replaced every C-style cast with C++ ones
* Replaced every C-style cast with C++ ones
* Fixed boats falling and a TP glitch #266
* Updated NULL to nullptr and fixing some type issues
* Modernized and fixed a few bugs
- Replaced most instances of `NULL` with `nullptr`.
- Replaced most `shared_ptr(new ...)` with `make_shared`.
- Removed the `nullptr` macro as it was interfering with the actual nullptr keyword in some instances.
* Fixing more conflicts
* Replace int loops with size_t and start work on overrides
* Change F3 rendering and add git version information
* Change position, block, chunk and facing
* Limit position decimal places
* Move LCE unique to the bottom and add more java features
* Fix chunk information disappearing after y256
* Add chunk count information
* Move build number script to prebuild.ps1
* We dont need to specify vector and wstring are from std
* Restore build number to fix multiplayer
* Use short symbolic-ref
* Restore original BuildVer.h
---------
Co-authored-by: Loki <lokio.casebstv@gmail.com>
Co-authored-by: Loki Rautio <lokirautio@gmail.com>
The sounds Guglio was using were missing several sounds (skeleton damage, etc) as well as the "original" sound pack just being wrong. Switches back to the original sounds, though Miles was still louder somehow
Original sounds from Guglio - should be correct audio levels. Current sounds
were too quiet so swapping these for JE sounds will have to be done with
some sort of normalization level sampling thingie with ffmpeg or smthn
All pads now get unique XUIDs derived from the persistent uid.dat value
(base + iPad offset). This gives each split-screen player a globally
unique identity that works for both local play and online multiplayer.
The host legacy XUID override for save compatibility still happens in
Minecraft.cpp after GetXUID is called, so old worlds are unaffected.
* Multiplayer 8 to max byte increase.
Made-with: Cursor
* Server chunk optimizations for large player counts, server full notification fix, added to server.properties.
* FOV option without debug menu
Now located in Graphics section.
Based on the FOV thing from discord idk
* language
* render distance option for graphics menu
* oop
* swf files on media
* revert changes on language selector
* nvm it was actually easy to fix
* forgot this
* Final probably
Fixed visual bug and made the chunk updates depend to your view distance.
Main's XUID refactor returned INVALID_XUID for pad != 0, which breaks
split-screen because each local player needs a distinct identity for
the save system and per-player inventory data.
Now pad 1-3 get unique XUIDs derived from the legacy embedded base
(base + iPad), same as the original console behavior. Only pad 0
uses the persistent uid.dat-backed XUID for networking.
Screen resolution is now auto-detected from the monitor at startup
instead of being hardcoded to 1920x1080. This fixes rendering on
ultrawide (21:9), super-ultrawide (32:9), 16:10, and any other
aspect ratio -- both in singleplayer and split-screen multiplayer.
The 3D world renders at native resolution so the full monitor is used.
Flash UI is 16:9-fitted and centered inside each viewport, pillarboxed
on wide displays and letterboxed on tall ones. Logical game dimensions
(used for ortho projection and HUD layout) are computed proportionally
from the real screen aspect ratio, fixing the stretched world projection
and HUD that the old hardcoded 1280x720 caused on non-16:9 monitors.
GameRenderer::ComputeViewportForPlayer uses the actual backbuffer size
instead of the logical game size, which was causing split-screen
viewports to be sized incorrectly.
UIScene::render fits menus to 16:9 within each split viewport using
GetViewportRect + Fit16x9, keeping inventory/crafting/options screens
at their designed aspect ratio instead of stretching.
Panorama and MenuBackground render at full viewport size with proper
tile scaling so the background fills the entire area without gaps in
vertical split and quadrant layouts.
HUD tile rendering uses ComputeTileScale to uniformly scale the SWF
and show the bottom portion (hotbar, hearts, hunger) in horizontal
and quadrant splits. repositionHud passes visible SWF-space dimensions
to ActionScript for proper element centering within each viewport.
Chat and Tooltips overlays use ComputeTileScale and
ComputeSplitContentOffset to anchor correctly to the bottom of each
player's viewport tile.
Container menus apply Fit16x9 to pointer coordinate mapping so the
cursor tracks correctly in split-screen. getMouseToSWFScale moved out
of the header into the .cpp. Mouse input in onMouseTick is gated to
pad 0 since raw mouse deltas should only drive player 1.
All shared viewport math lives in UISplitScreenHelpers.h:
- GetViewportRect: origin and dimensions for any viewport type
- Fit16x9: aspect-correct fitting with centering offsets
- ComputeTileScale: uniform scale and Y-offset for tile rendering
- ComputeSplitContentOffset: content centering for overlay components
* Fix DLCs textures and crash in 720p
* Revert "Fix DLCs textures and crash in 720p"
This reverts commit 7c38605360.
* Update Durango and Windows64 DLCs Textures also fix crash in 720p
* DLC update for Windows edition
- The windows edition dont use anymore DurangoMedia
- Fixed Bed and missing items in Skyrim
- Fixed Hud in almost all the DLCs (Halo hud in 720p is still the default hud)
- Add 720p support
- Add missing skin packs
- High quality Music for DLCs
* Add Fantasy Texture Pack, Remove LittleBigPlanet DLC
The LittleBigPlanet world crash the game, the texture pack dont support 1080p, and the loc file wasn't working
* Added scaling to zombie
i added scaling to zombie based on if its a baby or not
just setting the size does not work you have to set it in the tick (look at the tick for slimes which is a scalable monster)
* add output dir to gitignore
the build output folder is now added to the git ignore
---------
Co-authored-by: Loki <lokirautio@gmail.com>
* per user config should be .gitignored
* add comment surrounding gitignore update
* make it wildcard
* add fullscreen to launch option struct
* remove redundant forward declaration and use fullscreen launch option in main
* add adventure mode option as well as adjust cycling logic
* Revert deleting *.user from end of .gitignore
* rework comments
* Save FOV value to settings.dat
* Batch font drawing to fix debug overlay FPS
* Revert "Batch font drawing to fix debug overlay FPS"
This reverts commit 7dcecdbd4d.