mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-08-20 09:57:09 +00:00
Split-screen networking, window resize, bitmap font fix, and multiplayer stability
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.
This commit is contained in:
parent
797611f48b
commit
79afded2ff
|
|
@ -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) );
|
||||
|
|
@ -704,6 +715,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;
|
||||
|
|
@ -729,6 +741,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);
|
||||
|
|
@ -952,6 +971,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;
|
||||
|
|
@ -981,6 +1001,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;
|
||||
|
|
@ -1105,6 +1126,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);
|
||||
|
|
@ -1112,11 +1134,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 )
|
||||
|
|
@ -1186,16 +1210,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");
|
||||
|
|
@ -1234,6 +1271,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
|
||||
|
|
@ -1348,6 +1386,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));
|
||||
|
||||
|
|
@ -2846,31 +2885,34 @@ void ClientConnection::handleRespawn(shared_ptr<RespawnPacket> packet)
|
|||
|
||||
void ClientConnection::handleExplosion(shared_ptr<ExplodePacket> packet)
|
||||
{
|
||||
if(!packet->m_bKnockbackOnly)
|
||||
// World modification (block destruction) must only happen once
|
||||
if (isPrimaryConnection())
|
||||
{
|
||||
//app.DebugPrintf("Received ExplodePacket with explosion data\n");
|
||||
PIXBeginNamedEvent(0,"Handling explosion");
|
||||
Explosion *e = new Explosion(minecraft->level, nullptr, packet->x, packet->y, packet->z, packet->r);
|
||||
PIXBeginNamedEvent(0,"Finalizing");
|
||||
if(!packet->m_bKnockbackOnly)
|
||||
{
|
||||
//app.DebugPrintf("Received ExplodePacket with explosion data\n");
|
||||
PIXBeginNamedEvent(0,"Handling explosion");
|
||||
Explosion *e = new Explosion(minecraft->level, nullptr, packet->x, packet->y, packet->z, packet->r);
|
||||
PIXBeginNamedEvent(0,"Finalizing");
|
||||
|
||||
// Fix for #81758 - TCR 006 BAS Non-Interactive Pause: TU9: Performance: Gameplay: After detonating bunch of TNT, game enters unresponsive state for couple of seconds.
|
||||
// The changes we are making here have been decided by the server, so we don't need to add them to the vector that resets tiles changes made
|
||||
// on the client as we KNOW that the server is matching these changes
|
||||
MultiPlayerLevel *mpLevel = (MultiPlayerLevel *)minecraft->level;
|
||||
mpLevel->enableResetChanges(false);
|
||||
// 4J - now directly pass a pointer to the toBlow array in the packet rather than copying around
|
||||
e->finalizeExplosion(true, &packet->toBlow);
|
||||
mpLevel->enableResetChanges(true);
|
||||
PIXEndNamedEvent();
|
||||
PIXEndNamedEvent();
|
||||
delete e;
|
||||
}
|
||||
else
|
||||
{
|
||||
//app.DebugPrintf("Received ExplodePacket with knockback only data\n");
|
||||
// Fix for #81758 - TCR 006 BAS Non-Interactive Pause: TU9: Performance: Gameplay: After detonating bunch of TNT, game enters unresponsive state for couple of seconds.
|
||||
// The changes we are making here have been decided by the server, so we don't need to add them to the vector that resets tiles changes made
|
||||
// on the client as we KNOW that the server is matching these changes
|
||||
MultiPlayerLevel *mpLevel = (MultiPlayerLevel *)minecraft->level;
|
||||
mpLevel->enableResetChanges(false);
|
||||
// 4J - now directly pass a pointer to the toBlow array in the packet rather than copying around
|
||||
e->finalizeExplosion(true, &packet->toBlow);
|
||||
mpLevel->enableResetChanges(true);
|
||||
PIXEndNamedEvent();
|
||||
PIXEndNamedEvent();
|
||||
delete e;
|
||||
}
|
||||
}
|
||||
|
||||
// Per-player knockback — each connection applies to its own local player
|
||||
//app.DebugPrintf("Adding knockback (%f,%f,%f) for player %d\n", packet->getKnockbackX(), packet->getKnockbackY(), packet->getKnockbackZ(), m_userIndex);
|
||||
if (minecraft->localplayers[m_userIndex] == NULL)
|
||||
return;
|
||||
minecraft->localplayers[m_userIndex]->xd += packet->getKnockbackX();
|
||||
minecraft->localplayers[m_userIndex]->yd += packet->getKnockbackY();
|
||||
minecraft->localplayers[m_userIndex]->zd += packet->getKnockbackZ();
|
||||
|
|
@ -2880,6 +2922,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:
|
||||
|
|
@ -3186,6 +3230,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))
|
||||
{
|
||||
|
|
@ -3219,6 +3264,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);
|
||||
|
|
@ -3271,6 +3317,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();
|
||||
|
|
@ -3278,6 +3325,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());
|
||||
}
|
||||
|
||||
|
|
@ -3359,6 +3407,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);
|
||||
|
|
@ -3373,6 +3422,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)
|
||||
|
|
@ -3596,6 +3646,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);
|
||||
}
|
||||
|
||||
|
|
@ -3908,6 +3959,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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -300,6 +306,29 @@ bool CPlatformNetworkManagerStub::AddLocalPlayerByUserIndex( int userIndex )
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1601,6 +1601,7 @@ void UIController::getRenderDimensions(C4JRender::eViewportType viewport, S32 &w
|
|||
{
|
||||
F32 originX, originY, viewW, viewH;
|
||||
GetViewportRect(getScreenWidth(), getScreenHeight(), viewport, originX, originY, viewW, viewH);
|
||||
|
||||
if(viewport == C4JRender::VIEWPORT_TYPE_FULLSCREEN)
|
||||
{
|
||||
S32 offsetX, offsetY;
|
||||
|
|
@ -1847,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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -286,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;
|
||||
|
|
@ -333,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)
|
||||
{
|
||||
|
|
@ -344,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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
|
@ -268,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)
|
||||
|
|
@ -595,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 InputManager.IsPadConnected(iQuadrant); }
|
||||
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; }
|
||||
|
|
@ -606,11 +633,10 @@ bool C_4JProfile::QuerySigninStatus(void) { return true; }
|
|||
void C_4JProfile::GetXUID(int iPad, PlayerUID * pXuid, bool bOnlineXuid)
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
// Each pad gets a unique XUID based on the persistent uid.dat value.
|
||||
// Pad 0 uses the base persistent XUID, pads 1-3 offset from it.
|
||||
// Minecraft.cpp overrides pad 0 with legacy XUID when hosting for
|
||||
// save compatibility, but that's handled there, not here.
|
||||
*pXuid = Win64Xuid::ResolvePersistentXuid() + iPad;
|
||||
// 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
|
||||
|
|
@ -640,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) {}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,9 +969,9 @@ void GameRenderer::CachePlayerGammas()
|
|||
|
||||
bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport) const
|
||||
{
|
||||
// Use the actual backbuffer dimensions, not the game's logical size.
|
||||
extern int g_iScreenWidth;
|
||||
extern int g_iScreenHeight;
|
||||
// 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};
|
||||
|
|
@ -984,8 +985,8 @@ bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport)
|
|||
{
|
||||
outViewport.TopLeftX = 0.0f;
|
||||
outViewport.TopLeftY = 0.0f;
|
||||
outViewport.Width = static_cast<FLOAT>(g_iScreenWidth);
|
||||
outViewport.Height = static_cast<FLOAT>(g_iScreenHeight);
|
||||
outViewport.Width = static_cast<FLOAT>(g_rScreenWidth);
|
||||
outViewport.Height = static_cast<FLOAT>(g_rScreenHeight);
|
||||
outViewport.MinDepth = 0.0f;
|
||||
outViewport.MaxDepth = 1.0f;
|
||||
return true;
|
||||
|
|
@ -1001,8 +1002,8 @@ bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport)
|
|||
if (k < 0)
|
||||
return false;
|
||||
|
||||
const float width = static_cast<float>(g_iScreenWidth);
|
||||
const float height = static_cast<float>(g_iScreenHeight);
|
||||
const float width = static_cast<float>(g_rScreenWidth);
|
||||
const float height = static_cast<float>(g_rScreenHeight);
|
||||
|
||||
if (active == 2)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) );
|
||||
|
|
|
|||
|
|
@ -118,9 +118,7 @@ void StringTable::ProcessStringTableData(void)
|
|||
else
|
||||
{
|
||||
app.DebugPrintf("Failed to get language\n");
|
||||
#ifdef _DEBUG
|
||||
__debugbreak();
|
||||
#endif
|
||||
|
||||
|
||||
isStatic = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,6 +47,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
|
||||
|
|
@ -97,7 +102,13 @@ BOOL g_bWidescreen = TRUE;
|
|||
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 };
|
||||
|
|
@ -106,14 +117,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;
|
||||
|
|
@ -534,6 +537,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;
|
||||
|
|
@ -669,9 +677,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:
|
||||
|
|
@ -720,7 +759,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",
|
||||
|
|
@ -806,8 +845,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;
|
||||
|
|
@ -839,7 +878,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 +962,281 @@ void Render()
|
|||
g_pSwapChain->Present( 0, 0 );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Rebuild D3D11 resources after a window resize
|
||||
//--------------------------------------------------------------------------------------
|
||||
static bool ResizeD3D(int newW, int newH)
|
||||
{
|
||||
if (newW <= 0 || newH <= 0) return false;
|
||||
if (!g_pSwapChain) return false;
|
||||
if (!g_bResizeReady) return false;
|
||||
|
||||
int bbW = newW;
|
||||
int bbH = newH;
|
||||
|
||||
// InternalRenderManager member offsets (from decompiled Renderer.h):
|
||||
// 0x10 m_pDevice (ID3D11Device*)
|
||||
// 0x18 m_pDeviceContext (ID3D11DeviceContext*)
|
||||
// 0x20 m_pSwapChain (IDXGISwapChain*)
|
||||
// 0x28 renderTargetView (ID3D11RenderTargetView*) — backbuffer RTV
|
||||
// 0x50 renderTargetShaderResourceView (ID3D11ShaderResourceView*)
|
||||
// 0x98 depthStencilView (ID3D11DepthStencilView*)
|
||||
// 0x5138 backBufferWidth (DWORD) — used by StartFrame() for viewport
|
||||
// 0x513C backBufferHeight (DWORD) — used by StartFrame() for viewport
|
||||
//
|
||||
// Strategy: destroy old swap chain, create new one, patch Renderer's internal
|
||||
// pointers directly. This avoids both ResizeBuffers (outstanding ref issues)
|
||||
// and Initialise() (which wipes the texture table via memset).
|
||||
// The Renderer's old RTV/SRV/DSV are intentionally NOT released — they become
|
||||
// orphaned with the old swap chain. Tiny leak, but avoids fighting unknown refs.
|
||||
char* pRM = (char*)&InternalRenderManager;
|
||||
ID3D11RenderTargetView** ppRM_RTV = (ID3D11RenderTargetView**)(pRM + 0x28);
|
||||
ID3D11ShaderResourceView** ppRM_SRV = (ID3D11ShaderResourceView**)(pRM + 0x50);
|
||||
ID3D11DepthStencilView** ppRM_DSV = (ID3D11DepthStencilView**)(pRM + 0x98);
|
||||
IDXGISwapChain** ppRM_SC = (IDXGISwapChain**)(pRM + 0x20);
|
||||
DWORD* pRM_BBWidth = (DWORD*)(pRM + 0x5138);
|
||||
DWORD* pRM_BBHeight = (DWORD*)(pRM + 0x513C);
|
||||
|
||||
// Verify offsets by checking device and swap chain pointers
|
||||
ID3D11Device** ppRM_Device = (ID3D11Device**)(pRM + 0x10);
|
||||
if (*ppRM_Device != g_pd3dDevice || *ppRM_SC != g_pSwapChain)
|
||||
{
|
||||
app.DebugPrintf("[RESIZE] ERROR: RenderManager offset verification failed! "
|
||||
"device=%p (expected %p) swapchain=%p (expected %p)\n",
|
||||
*ppRM_Device, g_pd3dDevice, *ppRM_SC, g_pSwapChain);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cross-check backbuffer dimension offsets against swap chain desc
|
||||
DXGI_SWAP_CHAIN_DESC oldScDesc;
|
||||
g_pSwapChain->GetDesc(&oldScDesc);
|
||||
bool bbDimsValid = (*pRM_BBWidth == oldScDesc.BufferDesc.Width &&
|
||||
*pRM_BBHeight == oldScDesc.BufferDesc.Height);
|
||||
if (!bbDimsValid)
|
||||
{
|
||||
app.DebugPrintf("[RESIZE] WARNING: backBuffer dim offsets wrong: "
|
||||
"stored=%ux%u, swapchain=%ux%u\n",
|
||||
*pRM_BBWidth, *pRM_BBHeight, oldScDesc.BufferDesc.Width, oldScDesc.BufferDesc.Height);
|
||||
}
|
||||
|
||||
RenderManager.Suspend();
|
||||
while (!RenderManager.Suspended()) { Sleep(1); }
|
||||
|
||||
PostProcesser::GetInstance().Cleanup();
|
||||
|
||||
g_pImmediateContext->ClearState();
|
||||
g_pImmediateContext->Flush();
|
||||
|
||||
// Release OUR views and depth buffer
|
||||
if (g_pRenderTargetView) { g_pRenderTargetView->Release(); g_pRenderTargetView = NULL; }
|
||||
if (g_pDepthStencilView) { g_pDepthStencilView->Release(); g_pDepthStencilView = NULL; }
|
||||
if (g_pDepthStencilBuffer) { g_pDepthStencilBuffer->Release(); g_pDepthStencilBuffer = NULL; }
|
||||
|
||||
gdraw_D3D11_PreReset();
|
||||
|
||||
// Get IDXGIFactory from the existing device BEFORE destroying the old swap chain.
|
||||
// If anything fails before we have a new swap chain, we abort without destroying
|
||||
// the old one — leaving the Renderer in a valid (old-size) state.
|
||||
IDXGISwapChain* pOldSwapChain = g_pSwapChain;
|
||||
bool success = false;
|
||||
HRESULT hr;
|
||||
|
||||
IDXGIDevice* dxgiDevice = NULL;
|
||||
IDXGIAdapter* dxgiAdapter = NULL;
|
||||
IDXGIFactory* dxgiFactory = NULL;
|
||||
hr = g_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
|
||||
if (FAILED(hr)) goto postReset;
|
||||
hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter);
|
||||
if (FAILED(hr)) { dxgiDevice->Release(); goto postReset; }
|
||||
hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory);
|
||||
dxgiAdapter->Release();
|
||||
dxgiDevice->Release();
|
||||
if (FAILED(hr)) goto postReset;
|
||||
|
||||
// Create new swap chain at backbuffer size
|
||||
{
|
||||
DXGI_SWAP_CHAIN_DESC sd = {};
|
||||
sd.BufferCount = 1;
|
||||
sd.BufferDesc.Width = bbW;
|
||||
sd.BufferDesc.Height = bbH;
|
||||
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
sd.BufferDesc.RefreshRate.Numerator = 60;
|
||||
sd.BufferDesc.RefreshRate.Denominator = 1;
|
||||
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
|
||||
sd.OutputWindow = g_hWnd;
|
||||
sd.SampleDesc.Count = 1;
|
||||
sd.SampleDesc.Quality = 0;
|
||||
sd.Windowed = TRUE;
|
||||
|
||||
IDXGISwapChain* pNewSwapChain = NULL;
|
||||
hr = dxgiFactory->CreateSwapChain(g_pd3dDevice, &sd, &pNewSwapChain);
|
||||
dxgiFactory->Release();
|
||||
if (FAILED(hr) || pNewSwapChain == NULL)
|
||||
{
|
||||
app.DebugPrintf("[RESIZE] CreateSwapChain FAILED hr=0x%08X — keeping old swap chain\n", (unsigned)hr);
|
||||
goto postReset;
|
||||
}
|
||||
|
||||
// New swap chain created successfully — NOW destroy the old one.
|
||||
// The Renderer's internal RTV/SRV still reference the old backbuffer —
|
||||
// those COM objects become orphaned (tiny leak, harmless). We DON'T
|
||||
// release them because unknown code may also hold refs.
|
||||
pOldSwapChain->Release();
|
||||
g_pSwapChain = pNewSwapChain;
|
||||
}
|
||||
|
||||
// Patch Renderer's swap chain pointer
|
||||
*ppRM_SC = g_pSwapChain;
|
||||
|
||||
// Create render target views from new backbuffer
|
||||
{
|
||||
ID3D11Texture2D* pBackBuffer = NULL;
|
||||
hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
|
||||
if (FAILED(hr)) goto postReset;
|
||||
|
||||
// Our RTV
|
||||
hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView);
|
||||
if (FAILED(hr)) { pBackBuffer->Release(); goto postReset; }
|
||||
|
||||
// Renderer's internal RTV (offset 0x28)
|
||||
hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, ppRM_RTV);
|
||||
if (FAILED(hr)) { pBackBuffer->Release(); goto postReset; }
|
||||
|
||||
// Renderer's SRV: separate texture matching backbuffer dims (used by CaptureThumbnail)
|
||||
D3D11_TEXTURE2D_DESC backDesc = {};
|
||||
pBackBuffer->GetDesc(&backDesc);
|
||||
pBackBuffer->Release();
|
||||
|
||||
D3D11_TEXTURE2D_DESC srvDesc = backDesc;
|
||||
srvDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
|
||||
ID3D11Texture2D* srvTexture = NULL;
|
||||
hr = g_pd3dDevice->CreateTexture2D(&srvDesc, NULL, &srvTexture);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = g_pd3dDevice->CreateShaderResourceView(srvTexture, NULL, ppRM_SRV);
|
||||
srvTexture->Release();
|
||||
}
|
||||
if (FAILED(hr)) goto postReset;
|
||||
}
|
||||
|
||||
// Recreate depth stencil at backbuffer size
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC descDepth = {};
|
||||
descDepth.Width = bbW;
|
||||
descDepth.Height = bbH;
|
||||
descDepth.MipLevels = 1;
|
||||
descDepth.ArraySize = 1;
|
||||
descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
|
||||
descDepth.SampleDesc.Count = 1;
|
||||
descDepth.SampleDesc.Quality = 0;
|
||||
descDepth.Usage = D3D11_USAGE_DEFAULT;
|
||||
descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
|
||||
hr = g_pd3dDevice->CreateTexture2D(&descDepth, NULL, &g_pDepthStencilBuffer);
|
||||
if (FAILED(hr)) goto postReset;
|
||||
|
||||
D3D11_DEPTH_STENCIL_VIEW_DESC descDSView = {};
|
||||
descDSView.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
|
||||
descDSView.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
|
||||
hr = g_pd3dDevice->CreateDepthStencilView(g_pDepthStencilBuffer, &descDSView, &g_pDepthStencilView);
|
||||
if (FAILED(hr)) goto postReset;
|
||||
}
|
||||
|
||||
// Patch Renderer's DSV (AddRef because both we and the Renderer reference it)
|
||||
g_pDepthStencilView->AddRef();
|
||||
*ppRM_DSV = g_pDepthStencilView;
|
||||
|
||||
// Update Renderer's cached backbuffer dimensions (StartFrame uses these for viewport)
|
||||
if (bbDimsValid)
|
||||
{
|
||||
*pRM_BBWidth = (DWORD)bbW;
|
||||
*pRM_BBHeight = (DWORD)bbH;
|
||||
}
|
||||
|
||||
// Rebind render targets and viewport
|
||||
g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, g_pDepthStencilView);
|
||||
{
|
||||
D3D11_VIEWPORT vp = {};
|
||||
vp.Width = (FLOAT)bbW;
|
||||
vp.Height = (FLOAT)bbH;
|
||||
vp.MinDepth = 0.0f;
|
||||
vp.MaxDepth = 1.0f;
|
||||
g_pImmediateContext->RSSetViewports(1, &vp);
|
||||
}
|
||||
|
||||
ui.updateRenderTargets(g_pRenderTargetView, g_pDepthStencilView);
|
||||
ui.updateScreenSize(bbW, bbH);
|
||||
|
||||
// Track actual backbuffer dimensions for the rest of the engine
|
||||
g_rScreenWidth = bbW;
|
||||
g_rScreenHeight = bbH;
|
||||
|
||||
success = true;
|
||||
|
||||
postReset:
|
||||
if (!success && g_pSwapChain != NULL)
|
||||
{
|
||||
// Failure recovery: recreate our views from whatever swap chain survived
|
||||
// so ui.m_pRenderTargetView / m_pDepthStencilView don't dangle.
|
||||
DXGI_SWAP_CHAIN_DESC recoveryDesc;
|
||||
g_pSwapChain->GetDesc(&recoveryDesc);
|
||||
int recW = (int)recoveryDesc.BufferDesc.Width;
|
||||
int recH = (int)recoveryDesc.BufferDesc.Height;
|
||||
|
||||
if (g_pRenderTargetView == NULL)
|
||||
{
|
||||
ID3D11Texture2D* pBB = NULL;
|
||||
if (SUCCEEDED(g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBB)))
|
||||
{
|
||||
g_pd3dDevice->CreateRenderTargetView(pBB, NULL, &g_pRenderTargetView);
|
||||
pBB->Release();
|
||||
}
|
||||
}
|
||||
if (g_pDepthStencilView == NULL)
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC dd = {};
|
||||
dd.Width = recW; dd.Height = recH; dd.MipLevels = 1; dd.ArraySize = 1;
|
||||
dd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
|
||||
dd.SampleDesc.Count = 1; dd.Usage = D3D11_USAGE_DEFAULT;
|
||||
dd.BindFlags = D3D11_BIND_DEPTH_STENCIL;
|
||||
if (g_pDepthStencilBuffer == NULL)
|
||||
g_pd3dDevice->CreateTexture2D(&dd, NULL, &g_pDepthStencilBuffer);
|
||||
if (g_pDepthStencilBuffer != NULL)
|
||||
{
|
||||
D3D11_DEPTH_STENCIL_VIEW_DESC dsvd = {};
|
||||
dsvd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
|
||||
dsvd.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
|
||||
g_pd3dDevice->CreateDepthStencilView(g_pDepthStencilBuffer, &dsvd, &g_pDepthStencilView);
|
||||
}
|
||||
}
|
||||
if (g_pRenderTargetView != NULL)
|
||||
g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, g_pDepthStencilView);
|
||||
|
||||
ui.updateRenderTargets(g_pRenderTargetView, g_pDepthStencilView);
|
||||
|
||||
// If the surviving swap chain is the OLD one, dims are unchanged.
|
||||
// If it's the NEW one (partial failure after swap), update to new dims.
|
||||
if (g_pSwapChain != pOldSwapChain)
|
||||
{
|
||||
g_rScreenWidth = recW;
|
||||
g_rScreenHeight = recH;
|
||||
ui.updateScreenSize(recW, recH);
|
||||
}
|
||||
|
||||
app.DebugPrintf("[RESIZE] FAILED but recovered views at %dx%d\n", g_rScreenWidth, g_rScreenHeight);
|
||||
}
|
||||
|
||||
gdraw_D3D11_PostReset();
|
||||
gdraw_D3D11_SetRendertargetSize(g_rScreenWidth, g_rScreenHeight);
|
||||
if (success)
|
||||
IggyFlushInstalledFonts();
|
||||
RenderManager.Resume();
|
||||
|
||||
if (success)
|
||||
PostProcesser::GetInstance().Init();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Toggle borderless fullscreen
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
|
@ -964,6 +1278,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();
|
||||
|
|
@ -977,7 +1293,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();
|
||||
|
|
@ -1204,8 +1520,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] = {};
|
||||
|
|
@ -1346,6 +1666,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
|||
CleanupDevice();
|
||||
return 1;
|
||||
}
|
||||
g_bResizeReady = true;
|
||||
|
||||
//app.TemporaryCreateGameStart();
|
||||
|
||||
|
|
@ -1385,6 +1706,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() &&
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ BlockRegionUpdatePacket::BlockRegionUpdatePacket(int x, int y, int z, int xs, in
|
|||
// TODO - we should be using compressed data directly here rather than decompressing first and then recompressing...
|
||||
byteArray rawBuffer;
|
||||
|
||||
if( xs == 16 && ys == Level::maxBuildHeight && zs == 16 && ( ( x & 15 ) == 0 ) && ( y == 0 ) && ( ( z & 15 ) == 0 ) )
|
||||
if( xs == 16 && ys == Level::maxBuildHeight && zs == 16 && ( ( x & 15 ) == 0 ) && ( y == 0 ) && ( ( z & 15 ) == 0 ) )
|
||||
{
|
||||
bIsFullChunk = true;
|
||||
|
||||
|
|
@ -66,6 +66,8 @@ BlockRegionUpdatePacket::BlockRegionUpdatePacket(int x, int y, int z, int xs, in
|
|||
{
|
||||
size = 0;
|
||||
buffer = byteArray();
|
||||
app.DebugPrintf("[BRUP-SERVER] *** EMPTY BUFFER for chunk(%d,%d) ys=%d bIsFullChunk=%d\n",
|
||||
x>>4, z>>4, this->ys, bIsFullChunk ? 1 : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -96,7 +98,7 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException
|
|||
zs = dis->read() + 1;
|
||||
|
||||
bIsFullChunk = (chunkFlags & BLOCK_REGION_UPDATE_FULLCHUNK) ? true : false;
|
||||
if(chunkFlags & BLOCK_REGION_UPDATE_ZEROHEIGHT)
|
||||
if(chunkFlags & BLOCK_REGION_UPDATE_ZEROHEIGHT)
|
||||
ys = 0;
|
||||
|
||||
size = dis->readInt();
|
||||
|
|
@ -106,6 +108,10 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException
|
|||
if(size == 0)
|
||||
{
|
||||
buffer = byteArray();
|
||||
if(bIsFullChunk)
|
||||
{
|
||||
app.DebugPrintf("[BRUP-READ] *** ZERO-SIZE full chunk packet at (%d,%d)!\n", x>>4, z>>4);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -131,6 +137,11 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException
|
|||
|
||||
|
||||
delete [] compressedBuffer.data;
|
||||
if(buffer.length != outputSize)
|
||||
{
|
||||
app.DebugPrintf("*** BlockRegionUpdatePacket DECOMPRESS MISMATCH: expected=%d got=%d xs=%d ys=%d zs=%d fullChunk=%d compressedSize=%d levelIdx=%d\n",
|
||||
buffer.length, outputSize, xs, ys, zs, bIsFullChunk ? 1 : 0, size, levelIdx);
|
||||
}
|
||||
assert(buffer.length == outputSize);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,7 +146,9 @@ CompressedTileStorage::CompressedTileStorage(bool isEmpty)
|
|||
bool CompressedTileStorage::isRenderChunkEmpty(int y) // y == 0, 16, 32... 112 (representing a 16 byte range)
|
||||
{
|
||||
int block;
|
||||
unsigned short *blockIndices = (unsigned short *)indicesAndData;
|
||||
unsigned char *localIndicesAndData = indicesAndData;
|
||||
if(!localIndicesAndData) return true;
|
||||
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
|
||||
|
||||
for( int x = 0; x < 16; x += 4 )
|
||||
for( int z = 0; z < 16; z += 4 )
|
||||
|
|
@ -533,8 +535,12 @@ void CompressedTileStorage::getData(byteArray retArray, unsigned int retOffset)
|
|||
// Gets all tile values into an array of length 32768.
|
||||
void CompressedTileStorage::getData(byteArray retArray, unsigned int retOffset)
|
||||
{
|
||||
unsigned short *blockIndices = (unsigned short *)indicesAndData;
|
||||
unsigned char *data = indicesAndData + 1024;
|
||||
// Snapshot pointer to avoid race with compress() swapping indicesAndData
|
||||
unsigned char *localIndicesAndData = indicesAndData;
|
||||
if(!localIndicesAndData) return;
|
||||
|
||||
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
|
||||
unsigned char *data = localIndicesAndData + 1024;
|
||||
|
||||
for( int i = 0; i < 512; i++ )
|
||||
{
|
||||
|
|
@ -588,10 +594,13 @@ void CompressedTileStorage::getData(byteArray retArray, unsigned int retOffset)
|
|||
// Get an individual tile value
|
||||
int CompressedTileStorage::get(int x, int y, int z)
|
||||
{
|
||||
if(!indicesAndData) return 0;
|
||||
// Take a local snapshot of indicesAndData to avoid a race with compress() which swaps the pointer.
|
||||
// Both blockIndices and data must reference the same buffer, otherwise index offsets won't match.
|
||||
unsigned char *localIndicesAndData = indicesAndData;
|
||||
if(!localIndicesAndData) return 0;
|
||||
|
||||
unsigned short *blockIndices = (unsigned short *)indicesAndData;
|
||||
unsigned char *data = indicesAndData + 1024;
|
||||
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
|
||||
unsigned char *data = localIndicesAndData + 1024;
|
||||
|
||||
int block, tile;
|
||||
getBlockAndTile( &block, &tile, x, y, z );
|
||||
|
|
@ -1223,7 +1232,10 @@ int CompressedTileStorage::getAllocatedSize(int *count0, int *count1, int *count
|
|||
*count4 = 0;
|
||||
*count8 = 0;
|
||||
|
||||
unsigned short *blockIndices = (unsigned short *)indicesAndData;
|
||||
// Volatile read: compress() can swap indicesAndData concurrently outside cs_write
|
||||
unsigned char *localIndicesAndData = *(unsigned char *volatile *)&indicesAndData;
|
||||
if(!localIndicesAndData) return 0;
|
||||
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
|
||||
for(int i = 0; i < 512; i++ )
|
||||
{
|
||||
unsigned short idxType = blockIndices[i] & INDEX_TYPE_MASK;
|
||||
|
|
@ -1256,7 +1268,9 @@ int CompressedTileStorage::getAllocatedSize(int *count0, int *count1, int *count
|
|||
|
||||
int CompressedTileStorage::getHighestNonEmptyY()
|
||||
{
|
||||
unsigned short *blockIndices = (unsigned short *)indicesAndData;
|
||||
unsigned char *localIndicesAndData = indicesAndData;
|
||||
if(!localIndicesAndData) return -1;
|
||||
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
|
||||
unsigned int highestYBlock = 0;
|
||||
bool found = false;
|
||||
|
||||
|
|
@ -1297,19 +1311,26 @@ int CompressedTileStorage::getHighestNonEmptyY()
|
|||
// Multiply by the number of vertical tiles in a block, and then add that again to be at the top of the block
|
||||
highestNonEmptyY = (highestYBlock * 4) + 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("[CTS-WARN] getHighestNonEmptyY() returned -1! allocatedSize=%d indicesAndData=%p\n",
|
||||
allocatedSize, indicesAndData);
|
||||
}
|
||||
return highestNonEmptyY;
|
||||
}
|
||||
|
||||
void CompressedTileStorage::write(DataOutputStream *dos)
|
||||
{
|
||||
dos->writeInt(allocatedSize);
|
||||
if(indicesAndData)
|
||||
// Volatile read: compress() can swap indicesAndData concurrently outside cs_write
|
||||
unsigned char *localIndicesAndData = *(unsigned char *volatile *)&indicesAndData;
|
||||
if(localIndicesAndData)
|
||||
{
|
||||
if(LOCALSYTEM_ENDIAN == BIGENDIAN)
|
||||
{
|
||||
// The first 1024 bytes are an array of shorts, so we need to reverse the endianness
|
||||
byteArray indicesCopy(1024);
|
||||
memcpy(indicesCopy.data, indicesAndData, 1024);
|
||||
memcpy(indicesCopy.data, localIndicesAndData, 1024);
|
||||
reverseIndices(indicesCopy.data);
|
||||
dos->write(indicesCopy);
|
||||
delete [] indicesCopy.data;
|
||||
|
|
@ -1317,13 +1338,13 @@ void CompressedTileStorage::write(DataOutputStream *dos)
|
|||
// Write the rest of the data
|
||||
if(allocatedSize > 1024)
|
||||
{
|
||||
byteArray dataWrapper(indicesAndData + 1024, allocatedSize - 1024);
|
||||
byteArray dataWrapper(localIndicesAndData + 1024, allocatedSize - 1024);
|
||||
dos->write(dataWrapper);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
byteArray wrapper(indicesAndData, allocatedSize);
|
||||
byteArray wrapper(localIndicesAndData, allocatedSize);
|
||||
dos->write(wrapper);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -250,6 +250,14 @@ bool Connection::writeTick()
|
|||
// Otherwise just buffer the packet with other outgoing packets as the java game did
|
||||
if(packet->shouldDelay)
|
||||
{
|
||||
// Flush any buffered data BEFORE writing directly to the socket.
|
||||
// bufferedDos and sos->writeWithFlags both write to the same underlying
|
||||
// socket stream. If bufferedDos has unflushed bytes (from packets written
|
||||
// via the outgoing queue above), writing directly to sos here would send
|
||||
// the delayed packet's bytes BEFORE the buffered bytes, desynchronizing
|
||||
// the TCP stream on the receiving end.
|
||||
bufferedDos->flush();
|
||||
|
||||
Packet::writePacket(packet, byteArrayDos);
|
||||
|
||||
// 4J Stu - Changed this so that rather than writing to the network stream through a buffered stream we want to:
|
||||
|
|
@ -341,7 +349,7 @@ bool Connection::readTick()
|
|||
// printf("Con:0x%x readTick close EOS\n",this);
|
||||
|
||||
// 4J Stu - Remove this line
|
||||
// Fix for #10410 - UI: If the player is removed from a splitscreened host’s game, the next game that player joins will produce a message stating that the host has left.
|
||||
// Fix for #10410 - UI: If the player is removed from a splitscreened host<EFBFBD>s game, the next game that player joins will produce a message stating that the host has left.
|
||||
//close(DisconnectPacket::eDisconnect_EndOfStream);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4240,6 +4240,10 @@ void Level::setBlocksAndData(int x, int y, int z, int xs, int ys, int zs, byteAr
|
|||
if (z0 < 0) z0 = 0;
|
||||
if (z1 > 16) z1 = 16;
|
||||
LevelChunk *lc = getChunk(xc, zc);
|
||||
if(lc->isEmpty())
|
||||
{
|
||||
app.DebugPrintf("[SETBLOCKS-BUG] getChunk(%d,%d) returned EmptyLevelChunk! Data will be LOST\n", xc, zc);
|
||||
}
|
||||
// 4J Stu - Unshare before we make any changes incase the server is already another step ahead of us
|
||||
// Fix for #7904 - Gameplay: Players can dupe torches by throwing them repeatedly into water.
|
||||
// This is quite expensive so only actually do it if we are hosting, online, and the update will actually
|
||||
|
|
|
|||
|
|
@ -327,9 +327,30 @@ shared_ptr<Packet> Packet::readPacket(DataInputStream *dis, bool isServer) // th
|
|||
id = dis->read();
|
||||
if (id == -1) return nullptr;
|
||||
|
||||
// Track last few good packets for diagnosing TCP desync
|
||||
static thread_local int s_lastIds[8] = {};
|
||||
static thread_local int s_lastIdPos = 0;
|
||||
static thread_local int s_packetCount = 0;
|
||||
|
||||
if ((isServer && serverReceivedPackets.find(id) == serverReceivedPackets.end()) || (!isServer && clientReceivedPackets.find(id) == clientReceivedPackets.end()))
|
||||
{
|
||||
//app.DebugPrintf("Bad packet id %d\n", id);
|
||||
app.DebugPrintf("*** BAD PACKET ID %d (0x%02X) isServer=%d totalPacketsRead=%d\n", id, id, isServer ? 1 : 0, s_packetCount);
|
||||
app.DebugPrintf("*** Last %d good packet IDs (oldest first): ", 8);
|
||||
for (int dbg = 0; dbg < 8; dbg++)
|
||||
{
|
||||
int idx = (s_lastIdPos + dbg) % 8;
|
||||
app.DebugPrintf("%d ", s_lastIds[idx]);
|
||||
}
|
||||
app.DebugPrintf("\n");
|
||||
// Dump the next 32 bytes from the stream to see what follows
|
||||
app.DebugPrintf("*** Next bytes in stream: ");
|
||||
for (int dbg = 0; dbg < 32; dbg++)
|
||||
{
|
||||
int b = dis->read();
|
||||
if (b == -1) { app.DebugPrintf("[EOS] "); break; }
|
||||
app.DebugPrintf("%02X ", b);
|
||||
}
|
||||
app.DebugPrintf("\n");
|
||||
__debugbreak();
|
||||
assert(false);
|
||||
// throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id));
|
||||
|
|
@ -338,7 +359,10 @@ shared_ptr<Packet> Packet::readPacket(DataInputStream *dis, bool isServer) // th
|
|||
packet = getPacket(id);
|
||||
if (packet == NULL) assert(false);//throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id));
|
||||
|
||||
//app.DebugPrintf("%s reading packet %d\n", isServer ? "Server" : "Client", packet->getId());
|
||||
s_lastIds[s_lastIdPos] = id;
|
||||
s_lastIdPos = (s_lastIdPos + 1) % 8;
|
||||
s_packetCount++;
|
||||
|
||||
packet->read(dis);
|
||||
// }
|
||||
// catch (EOFException e)
|
||||
|
|
@ -372,7 +396,7 @@ shared_ptr<Packet> Packet::readPacket(DataInputStream *dis, bool isServer) // th
|
|||
|
||||
void Packet::writePacket(shared_ptr<Packet> packet, DataOutputStream *dos) // throws IOException TODO 4J JEV, should this declare a throws?
|
||||
{
|
||||
//app.DebugPrintf("Writing packet %d\n", packet->getId());
|
||||
//app.DebugPrintf("NET WRITE: packet id=%d (0x%02X) estSize=%d\n", packet->getId(), packet->getId(), packet->getEstimatedSize());
|
||||
dos->write(packet->getId());
|
||||
packet->write(dos);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ void Socket::pushDataToQueue(const BYTE * pbData, DWORD dwDataSize, bool fromHos
|
|||
return;
|
||||
}
|
||||
|
||||
//app.DebugPrintf("SOCKET PUSH: %u bytes to queue[%d] firstByte=0x%02X smallId=%d\n",
|
||||
// dwDataSize, queueIdx, dwDataSize > 0 ? pbData[0] : 0, networkPlayerSmallId);
|
||||
|
||||
EnterCriticalSection(&m_queueLockNetwork[queueIdx]);
|
||||
for( unsigned int i = 0; i < dwDataSize; i++ )
|
||||
{
|
||||
|
|
@ -311,7 +314,8 @@ void Socket::SocketInputStreamLocal::close()
|
|||
{
|
||||
m_streamOpen = false;
|
||||
EnterCriticalSection(&s_hostQueueLock[m_queueIdx]);
|
||||
s_hostQueue[m_queueIdx].empty();
|
||||
std::queue<byte> empty;
|
||||
std::swap(s_hostQueue[m_queueIdx], empty);
|
||||
LeaveCriticalSection(&s_hostQueueLock[m_queueIdx]);
|
||||
}
|
||||
|
||||
|
|
@ -359,7 +363,8 @@ void Socket::SocketOutputStreamLocal::close()
|
|||
{
|
||||
m_streamOpen = false;
|
||||
EnterCriticalSection(&s_hostQueueLock[m_queueIdx]);
|
||||
s_hostQueue[m_queueIdx].empty();
|
||||
std::queue<byte> empty;
|
||||
std::swap(s_hostQueue[m_queueIdx], empty);
|
||||
LeaveCriticalSection(&s_hostQueueLock[m_queueIdx]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,9 +59,27 @@ HRESULT Compression::CompressLZXRLE(void *pDestination, unsigned int *pDestSize,
|
|||
EnterCriticalSection(&rleCompressLock);
|
||||
//static unsigned char rleBuf[1024*100];
|
||||
|
||||
// RLE can expand data (each 0xFF byte becomes 2 bytes), so worst case is 2*SrcSize.
|
||||
// Use the static buffer when it fits, dynamically allocate otherwise.
|
||||
static const unsigned int staticCompressSize = 1024*100;
|
||||
unsigned int rleBufSize = SrcSize * 2;
|
||||
unsigned char *dynamicRleBuf = NULL;
|
||||
unsigned char *rleBuf;
|
||||
|
||||
if(rleBufSize <= staticCompressSize)
|
||||
{
|
||||
rleBuf = rleCompressBuf;
|
||||
rleBufSize = staticCompressSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
dynamicRleBuf = new unsigned char[rleBufSize];
|
||||
rleBuf = dynamicRleBuf;
|
||||
}
|
||||
|
||||
unsigned char *pucIn = (unsigned char *)pSource;
|
||||
unsigned char *pucEnd = pucIn + SrcSize;
|
||||
unsigned char *pucOut = (unsigned char *)rleCompressBuf;
|
||||
unsigned char *pucOut = rleBuf;
|
||||
|
||||
// Compress with RLE first:
|
||||
// 0 - 254 - encodes a single byte
|
||||
|
|
@ -101,12 +119,15 @@ HRESULT Compression::CompressLZXRLE(void *pDestination, unsigned int *pDestSize,
|
|||
*pucOut++ = thisOne;
|
||||
}
|
||||
} while (pucIn != pucEnd);
|
||||
unsigned int rleSize = (unsigned int)(pucOut - rleCompressBuf);
|
||||
unsigned int rleSize = (unsigned int)(pucOut - rleBuf);
|
||||
PIXEndNamedEvent();
|
||||
|
||||
PIXBeginNamedEvent(0,"Secondary compression");
|
||||
Compress(pDestination, pDestSize, rleCompressBuf, rleSize);
|
||||
Compress(pDestination, pDestSize, rleBuf, rleSize);
|
||||
PIXEndNamedEvent();
|
||||
|
||||
if(dynamicRleBuf != NULL) delete [] dynamicRleBuf;
|
||||
|
||||
LeaveCriticalSection(&rleCompressLock);
|
||||
// printf("Compressed from %d to %d to %d\n",SrcSize,rleSize,*pDestSize);
|
||||
|
||||
|
|
@ -118,9 +139,26 @@ HRESULT Compression::CompressRLE(void *pDestination, unsigned int *pDestSize, vo
|
|||
EnterCriticalSection(&rleCompressLock);
|
||||
//static unsigned char rleBuf[1024*100];
|
||||
|
||||
// RLE can expand data (each 0xFF byte becomes 2 bytes), so worst case is 2*SrcSize.
|
||||
static const unsigned int staticCompressSize = 1024*100;
|
||||
unsigned int rleBufSize = SrcSize * 2;
|
||||
unsigned char *dynamicRleBuf = NULL;
|
||||
unsigned char *rleBuf;
|
||||
|
||||
if(rleBufSize <= staticCompressSize)
|
||||
{
|
||||
rleBuf = rleCompressBuf;
|
||||
rleBufSize = staticCompressSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
dynamicRleBuf = new unsigned char[rleBufSize];
|
||||
rleBuf = dynamicRleBuf;
|
||||
}
|
||||
|
||||
unsigned char *pucIn = (unsigned char *)pSource;
|
||||
unsigned char *pucEnd = pucIn + SrcSize;
|
||||
unsigned char *pucOut = (unsigned char *)rleCompressBuf;
|
||||
unsigned char *pucOut = rleBuf;
|
||||
|
||||
// Compress with RLE first:
|
||||
// 0 - 254 - encodes a single byte
|
||||
|
|
@ -160,15 +198,14 @@ HRESULT Compression::CompressRLE(void *pDestination, unsigned int *pDestSize, vo
|
|||
*pucOut++ = thisOne;
|
||||
}
|
||||
} while (pucIn != pucEnd);
|
||||
unsigned int rleSize = (unsigned int)(pucOut - rleCompressBuf);
|
||||
unsigned int rleSize = (unsigned int)(pucOut - rleBuf);
|
||||
PIXEndNamedEvent();
|
||||
LeaveCriticalSection(&rleCompressLock);
|
||||
|
||||
// Return
|
||||
if (rleSize <= *pDestSize)
|
||||
{
|
||||
*pDestSize = rleSize;
|
||||
memcpy(pDestination, rleCompressBuf, *pDestSize);
|
||||
memcpy(pDestination, rleBuf, *pDestSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -177,6 +214,9 @@ HRESULT Compression::CompressRLE(void *pDestination, unsigned int *pDestSize, vo
|
|||
#endif
|
||||
}
|
||||
|
||||
if(dynamicRleBuf != NULL) delete [] dynamicRleBuf;
|
||||
LeaveCriticalSection(&rleCompressLock);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
|
@ -195,33 +235,47 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz
|
|||
//static unsigned char rleBuf[staticRleSize];
|
||||
unsigned int rleSize = staticRleSize;
|
||||
unsigned char *dynamicRleBuf = NULL;
|
||||
HRESULT decompressResult;
|
||||
|
||||
if(*pDestSize > rleSize)
|
||||
{
|
||||
rleSize = *pDestSize;
|
||||
dynamicRleBuf = new unsigned char[rleSize];
|
||||
Decompress(dynamicRleBuf, &rleSize, pSource, SrcSize);
|
||||
decompressResult = Decompress(dynamicRleBuf, &rleSize, pSource, SrcSize);
|
||||
pucIn = (unsigned char *)dynamicRleBuf;
|
||||
}
|
||||
else
|
||||
{
|
||||
Decompress(rleDecompressBuf, &rleSize, pSource, SrcSize);
|
||||
decompressResult = Decompress(rleDecompressBuf, &rleSize, pSource, SrcSize);
|
||||
pucIn = (unsigned char *)rleDecompressBuf;
|
||||
}
|
||||
|
||||
if(decompressResult != S_OK)
|
||||
{
|
||||
app.DebugPrintf("*** DecompressLZXRLE: zlib Decompress FAILED hr=0x%08X srcSize=%u expectedDest=%u rleSize=%u\n",
|
||||
decompressResult, SrcSize, *pDestSize, rleSize);
|
||||
if(dynamicRleBuf != NULL) delete [] dynamicRleBuf;
|
||||
*pDestSize = 0;
|
||||
LeaveCriticalSection(&rleDecompressLock);
|
||||
return decompressResult;
|
||||
}
|
||||
|
||||
//unsigned char *pucIn = (unsigned char *)rleDecompressBuf;
|
||||
unsigned char *pucEnd = pucIn + rleSize;
|
||||
unsigned char *pucOut = (unsigned char *)pDestination;
|
||||
unsigned char *pucOutEnd = pucOut + *pDestSize;
|
||||
|
||||
while( pucIn != pucEnd )
|
||||
{
|
||||
unsigned char thisOne = *pucIn++;
|
||||
if( thisOne == 255 )
|
||||
{
|
||||
if( pucIn >= pucEnd ) break;
|
||||
unsigned int count = *pucIn++;
|
||||
if( count < 3 )
|
||||
{
|
||||
count++;
|
||||
if( pucOut + count > pucOutEnd ) break;
|
||||
for( unsigned int i = 0; i < count; i++ )
|
||||
{
|
||||
*pucOut++ = 255;
|
||||
|
|
@ -230,7 +284,9 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz
|
|||
else
|
||||
{
|
||||
count++;
|
||||
if( pucIn >= pucEnd ) break;
|
||||
unsigned char data = *pucIn++;
|
||||
if( pucOut + count > pucOutEnd ) break;
|
||||
for( unsigned int i = 0; i < count; i++ )
|
||||
{
|
||||
*pucOut++ = data;
|
||||
|
|
@ -239,6 +295,7 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz
|
|||
}
|
||||
else
|
||||
{
|
||||
if( pucOut >= pucOutEnd ) break;
|
||||
*pucOut++ = thisOne;
|
||||
}
|
||||
}
|
||||
|
|
@ -260,16 +317,19 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize,
|
|||
unsigned char *pucIn = (unsigned char *)pSource;
|
||||
unsigned char *pucEnd = pucIn + SrcSize;
|
||||
unsigned char *pucOut = (unsigned char *)pDestination;
|
||||
unsigned char *pucOutEnd = pucOut + *pDestSize;
|
||||
|
||||
while( pucIn != pucEnd )
|
||||
{
|
||||
unsigned char thisOne = *pucIn++;
|
||||
if( thisOne == 255 )
|
||||
{
|
||||
if( pucIn >= pucEnd ) break;
|
||||
unsigned int count = *pucIn++;
|
||||
if( count < 3 )
|
||||
{
|
||||
count++;
|
||||
if( pucOut + count > pucOutEnd ) break;
|
||||
for( unsigned int i = 0; i < count; i++ )
|
||||
{
|
||||
*pucOut++ = 255;
|
||||
|
|
@ -278,7 +338,9 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize,
|
|||
else
|
||||
{
|
||||
count++;
|
||||
if( pucIn >= pucEnd ) break;
|
||||
unsigned char data = *pucIn++;
|
||||
if( pucOut + count > pucOutEnd ) break;
|
||||
for( unsigned int i = 0; i < count; i++ )
|
||||
{
|
||||
*pucOut++ = data;
|
||||
|
|
@ -287,6 +349,7 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize,
|
|||
}
|
||||
else
|
||||
{
|
||||
if( pucOut >= pucOutEnd ) break;
|
||||
*pucOut++ = thisOne;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue