From af5d5a06d51daf5b79ea1f657637e4e5888f7707 Mon Sep 17 00:00:00 2001 From: MrTheShy <49885496+MrTheShy@users.noreply.github.com> Date: Mon, 9 Mar 2026 03:02:48 +0100 Subject: [PATCH 1/9] Revert the workaround of loading of hd textures (#999) The previous crash fix forced HD skins at every resolution which made everything blurry at 720p and below because Iggy was downscaling 1080p assets. The real problem was that 4J loaded the HD skin libraries (skinHD.swf, skinHDHud.swf etc) unconditionally on Win64 but the HD platform skin was only registered above 720p. At 720p or lower Iggy couldnt find platformskinHD.swf and crashed. Now on Win64 we load both skin sets at startup, HD and non-HD, each with their own registered name so they dont conflict. loadMovie() picks 1080.swf or 720.swf based on actual screen height, and each scene SWF naturally imports the right skin chain. No crash, no blurriness. 4J already had the eLibraryFallback enum slots for this (including eLibraryFallback_Platform) but it was behind a debug-only guard and the platform skin slot was never actually loaded. Removed the guard and added the missing load. --- Minecraft.Client/Common/UI/UIController.cpp | 84 +++++++++++++-------- Minecraft.Client/Common/UI/UIController.h | 6 +- Minecraft.Client/Common/UI/UIScene.cpp | 12 ++- 3 files changed, 63 insertions(+), 39 deletions(-) diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp index 205a4f0bf..b529b3918 100644 --- a/Minecraft.Client/Common/UI/UIController.cpp +++ b/Minecraft.Client/Common/UI/UIController.cpp @@ -522,33 +522,9 @@ void UIController::tick() void UIController::loadSkins() { - wstring platformSkinPath = L""; - #ifdef __PS3__ - platformSkinPath = L"skinPS3.swf"; -#elif defined __PSVITA__ - platformSkinPath = L"skinVita.swf"; -#elif defined _WINDOWS64 - // Windows64/Durango/Orbis always load HD skin libraries unconditionally, - // which import "platformskinHD.swf" by name. The platform skin must - // therefore always be the HD variant regardless of screen resolution. - platformSkinPath = L"skinHDWin.swf"; -#elif defined _DURANGO - platformSkinPath = L"skinHDDurango.swf"; -#elif defined __ORBIS__ - platformSkinPath = L"skinHDOrbis.swf"; + m_iggyLibraries[eLibrary_Platform] = loadSkin(L"skinPS3.swf", L"platformskin.swf"); -#endif - // Every platform has one of these, so nothing shared. - // On HD platforms (Win64/Durango/Orbis) the skin libraries always import - // "platformskinHD.swf", so we must register under that name at any resolution. -#if defined(_WINDOWS64) || defined(_DURANGO) || defined(__ORBIS__) - m_iggyLibraries[eLibrary_Platform] = loadSkin(platformSkinPath, L"platformskinHD.swf"); -#else - m_iggyLibraries[eLibrary_Platform] = loadSkin(platformSkinPath, L"platformskin.swf"); -#endif - -#if defined(__PS3__) || defined(__PSVITA__) m_iggyLibraries[eLibrary_GraphicsDefault] = loadSkin(L"skinGraphics.swf", L"skinGraphics.swf"); m_iggyLibraries[eLibrary_GraphicsHUD] = loadSkin(L"skinGraphicsHud.swf", L"skinGraphicsHud.swf"); m_iggyLibraries[eLibrary_GraphicsInGame] = loadSkin(L"skinGraphicsInGame.swf", L"skinGraphicsInGame.swf"); @@ -559,13 +535,28 @@ void UIController::loadSkins() m_iggyLibraries[eLibrary_HUD] = loadSkin(L"skinHud.swf", L"skinHud.swf"); m_iggyLibraries[eLibrary_Tooltips] = loadSkin(L"skinTooltips.swf", L"skinTooltips.swf"); m_iggyLibraries[eLibrary_Default] = loadSkin(L"skin.swf", L"skin.swf"); -#endif -#if ( defined(_WINDOWS64) || defined(_DURANGO) || defined(__ORBIS__) ) +#elif defined __PSVITA__ + m_iggyLibraries[eLibrary_Platform] = loadSkin(L"skinVita.swf", L"platformskin.swf"); -#if defined(_WINDOWS64) - // 4J Stu - Load the 720/480 skins so that we have something to fallback on during development -#ifndef _FINAL_BUILD + m_iggyLibraries[eLibrary_GraphicsDefault] = loadSkin(L"skinGraphics.swf", L"skinGraphics.swf"); + m_iggyLibraries[eLibrary_GraphicsHUD] = loadSkin(L"skinGraphicsHud.swf", L"skinGraphicsHud.swf"); + m_iggyLibraries[eLibrary_GraphicsInGame] = loadSkin(L"skinGraphicsInGame.swf", L"skinGraphicsInGame.swf"); + m_iggyLibraries[eLibrary_GraphicsTooltips] = loadSkin(L"skinGraphicsTooltips.swf", L"skinGraphicsTooltips.swf"); + m_iggyLibraries[eLibrary_GraphicsLabels] = loadSkin(L"skinGraphicsLabels.swf", L"skinGraphicsLabels.swf"); + m_iggyLibraries[eLibrary_Labels] = loadSkin(L"skinLabels.swf", L"skinLabels.swf"); + m_iggyLibraries[eLibrary_InGame] = loadSkin(L"skinInGame.swf", L"skinInGame.swf"); + m_iggyLibraries[eLibrary_HUD] = loadSkin(L"skinHud.swf", L"skinHud.swf"); + m_iggyLibraries[eLibrary_Tooltips] = loadSkin(L"skinTooltips.swf", L"skinTooltips.swf"); + m_iggyLibraries[eLibrary_Default] = loadSkin(L"skin.swf", L"skin.swf"); + +#elif defined _WINDOWS64 + // HD platform skin — required by skinHD*.swf (1080p scene SWFs) + m_iggyLibraries[eLibrary_Platform] = loadSkin(L"skinHDWin.swf", L"platformskinHD.swf"); + // Non-HD platform skin — required by skin*.swf (720p/480p scene SWFs) + m_iggyLibraries[eLibraryFallback_Platform] = loadSkin(L"skinWin.swf", L"platformskin.swf"); + + // Non-HD skin set (720p/480p scenes import these) m_iggyLibraries[eLibraryFallback_GraphicsDefault] = loadSkin(L"skinGraphics.swf", L"skinGraphics.swf"); m_iggyLibraries[eLibraryFallback_GraphicsHUD] = loadSkin(L"skinGraphicsHud.swf", L"skinGraphicsHud.swf"); m_iggyLibraries[eLibraryFallback_GraphicsInGame] = loadSkin(L"skinGraphicsInGame.swf", L"skinGraphicsInGame.swf"); @@ -576,8 +567,21 @@ void UIController::loadSkins() m_iggyLibraries[eLibraryFallback_HUD] = loadSkin(L"skinHud.swf", L"skinHud.swf"); m_iggyLibraries[eLibraryFallback_Tooltips] = loadSkin(L"skinTooltips.swf", L"skinTooltips.swf"); m_iggyLibraries[eLibraryFallback_Default] = loadSkin(L"skin.swf", L"skin.swf"); -#endif -#endif + + // HD skin set (1080p scenes import these) + m_iggyLibraries[eLibrary_GraphicsDefault] = loadSkin(L"skinHDGraphics.swf", L"skinHDGraphics.swf"); + m_iggyLibraries[eLibrary_GraphicsHUD] = loadSkin(L"skinHDGraphicsHud.swf", L"skinHDGraphicsHud.swf"); + m_iggyLibraries[eLibrary_GraphicsInGame] = loadSkin(L"skinHDGraphicsInGame.swf", L"skinHDGraphicsInGame.swf"); + m_iggyLibraries[eLibrary_GraphicsTooltips] = loadSkin(L"skinHDGraphicsTooltips.swf", L"skinHDGraphicsTooltips.swf"); + m_iggyLibraries[eLibrary_GraphicsLabels] = loadSkin(L"skinHDGraphicsLabels.swf", L"skinHDGraphicsLabels.swf"); + m_iggyLibraries[eLibrary_Labels] = loadSkin(L"skinHDLabels.swf", L"skinHDLabels.swf"); + m_iggyLibraries[eLibrary_InGame] = loadSkin(L"skinHDInGame.swf", L"skinHDInGame.swf"); + m_iggyLibraries[eLibrary_HUD] = loadSkin(L"skinHDHud.swf", L"skinHDHud.swf"); + m_iggyLibraries[eLibrary_Tooltips] = loadSkin(L"skinHDTooltips.swf", L"skinHDTooltips.swf"); + m_iggyLibraries[eLibrary_Default] = loadSkin(L"skinHD.swf", L"skinHD.swf"); + +#elif defined _DURANGO + m_iggyLibraries[eLibrary_Platform] = loadSkin(L"skinHDDurango.swf", L"platformskinHD.swf"); m_iggyLibraries[eLibrary_GraphicsDefault] = loadSkin(L"skinHDGraphics.swf", L"skinHDGraphics.swf"); m_iggyLibraries[eLibrary_GraphicsHUD] = loadSkin(L"skinHDGraphicsHud.swf", L"skinHDGraphicsHud.swf"); @@ -589,7 +593,21 @@ void UIController::loadSkins() m_iggyLibraries[eLibrary_HUD] = loadSkin(L"skinHDHud.swf", L"skinHDHud.swf"); m_iggyLibraries[eLibrary_Tooltips] = loadSkin(L"skinHDTooltips.swf", L"skinHDTooltips.swf"); m_iggyLibraries[eLibrary_Default] = loadSkin(L"skinHD.swf", L"skinHD.swf"); -#endif // HD platforms + +#elif defined __ORBIS__ + m_iggyLibraries[eLibrary_Platform] = loadSkin(L"skinHDOrbis.swf", L"platformskinHD.swf"); + + m_iggyLibraries[eLibrary_GraphicsDefault] = loadSkin(L"skinHDGraphics.swf", L"skinHDGraphics.swf"); + m_iggyLibraries[eLibrary_GraphicsHUD] = loadSkin(L"skinHDGraphicsHud.swf", L"skinHDGraphicsHud.swf"); + m_iggyLibraries[eLibrary_GraphicsInGame] = loadSkin(L"skinHDGraphicsInGame.swf", L"skinHDGraphicsInGame.swf"); + m_iggyLibraries[eLibrary_GraphicsTooltips] = loadSkin(L"skinHDGraphicsTooltips.swf", L"skinHDGraphicsTooltips.swf"); + m_iggyLibraries[eLibrary_GraphicsLabels] = loadSkin(L"skinHDGraphicsLabels.swf", L"skinHDGraphicsLabels.swf"); + m_iggyLibraries[eLibrary_Labels] = loadSkin(L"skinHDLabels.swf", L"skinHDLabels.swf"); + m_iggyLibraries[eLibrary_InGame] = loadSkin(L"skinHDInGame.swf", L"skinHDInGame.swf"); + m_iggyLibraries[eLibrary_HUD] = loadSkin(L"skinHDHud.swf", L"skinHDHud.swf"); + m_iggyLibraries[eLibrary_Tooltips] = loadSkin(L"skinHDTooltips.swf", L"skinHDTooltips.swf"); + m_iggyLibraries[eLibrary_Default] = loadSkin(L"skinHD.swf", L"skinHD.swf"); +#endif } IggyLibrary UIController::loadSkin(const wstring &skinPath, const wstring &skinName) diff --git a/Minecraft.Client/Common/UI/UIController.h b/Minecraft.Client/Common/UI/UIController.h index 29c278129..08a5ba088 100644 --- a/Minecraft.Client/Common/UI/UIController.h +++ b/Minecraft.Client/Common/UI/UIController.h @@ -101,9 +101,8 @@ private: eLibrary_Tooltips, eLibrary_Default, -#if ( defined(_WINDOWS64) ) - // 4J Stu - Load the 720/480 skins so that we have something to fallback on during development -#ifndef _FINAL_BUILD +#if defined(_WINDOWS64) + // Non-HD skin libraries needed by 720p/480p scene SWFs. eLibraryFallback_Platform, eLibraryFallback_GraphicsDefault, eLibraryFallback_GraphicsHUD, @@ -115,7 +114,6 @@ private: eLibraryFallback_HUD, eLibraryFallback_Tooltips, eLibraryFallback_Default, -#endif #endif eLibrary_Count, diff --git a/Minecraft.Client/Common/UI/UIScene.cpp b/Minecraft.Client/Common/UI/UIScene.cpp index 6918bc5bd..5630e9726 100644 --- a/Minecraft.Client/Common/UI/UIScene.cpp +++ b/Minecraft.Client/Common/UI/UIScene.cpp @@ -286,8 +286,16 @@ void UIScene::loadMovie() moviePath.append(L"Vita.swf"); m_loadedResolution = eSceneResolution_Vita; #elif defined _WINDOWS64 - moviePath.append(L"1080.swf"); - m_loadedResolution = eSceneResolution_1080; + if(ui.getScreenHeight() > 720.0f) + { + moviePath.append(L"1080.swf"); + m_loadedResolution = eSceneResolution_1080; + } + else + { + moviePath.append(L"720.swf"); + m_loadedResolution = eSceneResolution_720; + } #else moviePath.append(L"1080.swf"); m_loadedResolution = eSceneResolution_1080; From 91bf8d44faa6b0397768cb794a021db0e3e5a181 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 9 Mar 2026 02:05:54 +0000 Subject: [PATCH 2/9] Use the correctly sized icons on 720p (#883) * Use the correctly sized icons on 720p * Improve check --- Minecraft.Client/Common/UI/UIController.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp index b529b3918..b12ea5e73 100644 --- a/Minecraft.Client/Common/UI/UIController.cpp +++ b/Minecraft.Client/Common/UI/UIController.cpp @@ -1828,14 +1828,24 @@ GDrawTexture * RADLINK UIController::TextureSubstitutionCreateCallback ( void * // 4J Stu - All our flash controls that allow replacing textures use a special 64x64 symbol // Force this size here so that our images don't get scaled wildly - #if (defined __ORBIS__ || defined _DURANGO || defined _WINDOWS64 ) + #if (defined __ORBIS__ || defined _DURANGO ) *width = 96; *height = 96; #else *width = 64; *height = 64; - #endif + + #if defined _WINDOWS64 + // Only set the size to 96x96 for 1080p on Windows + UIScene *scene = uiController->GetTopScene(0); + if (scene->getSceneResolution() == UIScene::eSceneResolution_1080) + { + *width = 96; + *height = 96; + } + #endif + *destroy_callback_data = (void *)id; app.DebugPrintf("Found substitution texture %ls (%d) - %dx%d\n", static_cast(texture_name), id, image.getWidth(), image.getHeight()); From 42164eeb897bd5a8a409aa7fa8539ab1f2a77b45 Mon Sep 17 00:00:00 2001 From: MozzarellaRat <81330397+MozzarellaRat@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:12:13 -0700 Subject: [PATCH 3/9] Reimplement boat gravity again. (#988) --- Minecraft.World/Boat.cpp | 103 ++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 60 deletions(-) diff --git a/Minecraft.World/Boat.cpp b/Minecraft.World/Boat.cpp index a470936ab..60d0c807e 100644 --- a/Minecraft.World/Boat.cpp +++ b/Minecraft.World/Boat.cpp @@ -27,7 +27,7 @@ void Boat::_init() blocksBuilding = true; setSize(1.5f, 0.6f); - heightOffset = bbHeight / 2.0f; + heightOffset = (bbHeight / 2.0f) + 0.2f; // 4J Stu - This function call had to be moved here from the Entity ctor to ensure that // the derived version of the function is called @@ -194,7 +194,7 @@ void Boat::tick() yo = y; zo = z; - + // Check how much of the boat is in water int steps = 5; double waterPercentage = 0; for (int i = 0; i < steps; i++) @@ -208,33 +208,14 @@ void Boat::tick() } } + // Create particles double lastSpeed = sqrt(xd * xd + zd * zd); - if (lastSpeed > MAX_COLLISION_SPEED) + if (lastSpeed > MAX_COLLISION_SPEED && waterPercentage > 0) { - double xa = cos(yRot * PI / 180); - double za = sin(yRot * PI / 180); - - for (int i = 0; i < 1 + lastSpeed * 60; i++) - { - - double side = (random->nextFloat() * 2 - 1); - - double side2 = (random->nextInt(2) * 2 - 1) * 0.7; - if (random->nextBoolean()) - { - double xx = x - xa * side * 0.8 + za * side2; - double zz = z - za * side * 0.8 - xa * side2; - level->addParticle(eParticleType_splash, xx, y - 2 / 16.0f, zz, +xd, yd, +zd); - } - else - { - double xx = x + xa + za * side * 0.7; - double zz = z + za - xa * side * 0.7; - level->addParticle(eParticleType_splash, xx, y - 2 / 16.0f, zz, +xd, yd, +zd); - } - } + createSplash(lastSpeed); } + // Interpolation if (level->isClientSide && doLerp) { if (lSteps > 0) @@ -254,7 +235,6 @@ void Boat::tick() } else { -#if 1 // Original //double xt = x + xd; //double yt = y + yd; @@ -273,51 +253,25 @@ void Boat::tick() xd *= 0.99f; yd *= 0.95f; zd *= 0.99f; -#else - // 4J Stu - Fix for #8280 - Gameplay : Boats behave erratically when exited next to land. - // The client shouldn't change the position of the boat - double xt = x;// + xd; - double yt = y + yd; - double zt = z;// + zd; - this->setPos(xt, yt, zt); - - // 4J Stu - Fix for #9579 - GAMEPLAY: Boats with a player in them slowly sink under the water over time, and with no player in them they float into the sky. - // Just make the boats bob up and down rather than any other client-side movement when not receiving packets from server - if (waterPercentage > 0) - { - double bob = waterPercentage * 2 - 1; - yd += 0.04f * bob; - } - else - { - if (yd < 0) yd /= 2; - yd += 0.007f; - } - //if (onGround) - //{ - xd *= 0.5f; - yd *= 0.5f; - zd *= 0.5f; - //} - //xd *= 0.99f; - //yd *= 0.95f; - //zd *= 0.99f; -#endif } return; } + // Bob in water if (waterPercentage > 0) { double bob = waterPercentage * 2 - 1; yd += 0.04f * bob; } - else + + // Reimplement gravity again (??) + int tileUnder = level->getTile(Mth::floor(x), Mth::floor(y-0.15), Mth::floor(z)); + if (tileUnder == 0 && !onGround) { - yd = 0; + yd -= 0.04f; } - + // Rider controls if ( rider.lock() != nullptr && rider.lock()->instanceof(eTYPE_LIVINGENTITY) ) { shared_ptr livingRider = dynamic_pointer_cast(rider.lock()); @@ -354,6 +308,7 @@ void Boat::tick() if (acceleration < MIN_ACCELERATION) acceleration = MIN_ACCELERATION; } + // Slow on ground if (onGround) { xd *= 0.5f; @@ -362,6 +317,7 @@ void Boat::tick() } move(xd, yd, zd); + // Break boat on high speed collision if ((horizontalCollision && lastSpeed > 0.20)) { if (!level->isClientSide && !removed) @@ -401,6 +357,7 @@ void Boat::tick() yRot += (float) rotDiff; setRot(yRot, xRot); + // Server code after this if(level->isClientSide) return; vector > *entities = level->getEntities(shared_from_this(), bb->grow(0.2f, 0, 0.2f)); @@ -444,13 +401,38 @@ void Boat::tick() } } +void Boat::createSplash(double particleStrengh) { + double xa = cos(yRot * PI / 180); + double za = sin(yRot * PI / 180); + + for (int i = 0; i < 1 + particleStrengh * 60; i++) + { + double side = (random->nextFloat() * 2 - 1); + + double side2 = (random->nextInt(2) * 2 - 1) * 0.7; + if (random->nextBoolean()) + { + double xx = x - xa * side * 0.8 + za * side2; + double zz = z - za * side * 0.8 - xa * side2; + level->addParticle(eParticleType_splash, xx, y - 2 / 16.0f, zz, +xd, yd, +zd); + } + else + { + double xx = x + xa + za * side * 0.7; + double zz = z + za - xa * side * 0.7; + level->addParticle(eParticleType_splash, xx, y - 2 / 16.0f, zz, +xd, yd, +zd); + } + } +} + void Boat::positionRider() { if (rider.lock() == nullptr) return; double xa = cos(yRot * PI / 180) * 0.4; double za = sin(yRot * PI / 180) * 0.4; - rider.lock()->setPos(x + xa, y + getRideHeight() + rider.lock()->getRidingHeight(), z + za); + // We minus 0.4 to ensure that the player is not hovering above the boat. + rider.lock()->setPos(x + xa, y + getRideHeight() + rider.lock()->getRidingHeight()-0.4, z + za); } @@ -523,3 +505,4 @@ void Boat::setDoLerp(bool doLerp) { this->doLerp = doLerp; } + From e2adaa082c219c4c5f2025ae4898d3711000cab9 Mon Sep 17 00:00:00 2001 From: MrTheShy <49885496+MrTheShy@users.noreply.github.com> Date: Mon, 9 Mar 2026 04:16:58 +0100 Subject: [PATCH 4/9] Fix split-screen UI wrong positioning on window resize (#989) * Fix split-screen UI wrong positioning on window resize In vertical split at window heights below 1080, ComputeTileScale's min-scale clamp (>= 1.0) prevented the SWF from scaling down to fit, cropping the bottom and causing repositionHud to shift HUD elements downward. Chat and Tooltips additionally applied an offset from ComputeSplitContentOffset that only produced correct values at the 1920x1080 design resolution. Override the scale for vertical split so the SWF fits the full window height when it is shorter than the movie. Remove the broken content offset from Chat and Tooltips -- the tile crop already positions the content correctly. * Fix gamma post-process in split-screen The gamma shader sampled the full backbuffer texture (UV 0..1) into each player's viewport, stretching the entire screen into every split region. Extended the shader constant buffer with per-viewport UV offset and scale so each pass samples only its own portion of the backbuffer. ComputeViewportForPlayer was hardcoded to top/bottom for 2 players, ignoring the vertical split setting. Rewrote it to read each player's m_iScreenSection directly, which already accounts for the split orientation preference. Secondary players have no Graphics menu and cannot change gamma. CachePlayerGammas now reads the primary player's setting and applies it uniformly to all viewports. --- Minecraft.Client/Common/PostProcesser.h | 5 +- .../Common/UI/UIComponent_Chat.cpp | 16 +- .../Common/UI/UIComponent_Tooltips.cpp | 16 +- Minecraft.Client/Common/UI/UIScene_HUD.cpp | 1748 +++++++++-------- Minecraft.Client/GameRenderer.cpp | 123 +- Minecraft.Client/Windows64/PostProcesser.cpp | 62 +- 6 files changed, 994 insertions(+), 976 deletions(-) diff --git a/Minecraft.Client/Common/PostProcesser.h b/Minecraft.Client/Common/PostProcesser.h index 35fa50809..966d58bc6 100644 --- a/Minecraft.Client/Common/PostProcesser.h +++ b/Minecraft.Client/Common/PostProcesser.h @@ -51,7 +51,10 @@ private: struct GammaCBData { float gamma; - float pad[3]; + float pad; + float uvOffsetX, uvOffsetY; + float uvScaleX, uvScaleY; + float pad2[2]; }; static const char* g_gammaVSCode; diff --git a/Minecraft.Client/Common/UI/UIComponent_Chat.cpp b/Minecraft.Client/Common/UI/UIComponent_Chat.cpp index 257199810..49c5d5d21 100644 --- a/Minecraft.Client/Common/UI/UIComponent_Chat.cpp +++ b/Minecraft.Client/Common/UI/UIComponent_Chat.cpp @@ -143,13 +143,17 @@ void UIComponent_Chat::render(S32 width, S32 height, C4JRender::eViewportType vi F32 scale; ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart); - IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) ); - S32 contentOffX, contentOffY; - ComputeSplitContentOffset(viewport, m_movieWidth, m_movieHeight, scale, tileWidth, tileHeight, tileYStart, contentOffX, contentOffY); - xPos += contentOffX; - yPos += contentOffY; - ui.setupRenderPosition(xPos, yPos); + // For vertical split, scale down to fit the full SWF height when the + // window is shorter than the movie (same fix as HUD). + if(!needsYTile && m_movieHeight > 0) + { + F32 scaleH = (F32)tileHeight / (F32)m_movieHeight; + if(scaleH < scale) + scale = scaleH; + } + + IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) ); IggyPlayerDrawTilesStart ( getMovie() ); diff --git a/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp b/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp index 566f759d2..418546b70 100644 --- a/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp +++ b/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp @@ -247,13 +247,17 @@ void UIComponent_Tooltips::render(S32 width, S32 height, C4JRender::eViewportTyp F32 scale; ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart); - IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) ); - S32 contentOffX, contentOffY; - ComputeSplitContentOffset(viewport, m_movieWidth, m_movieHeight, scale, tileWidth, tileHeight, tileYStart, contentOffX, contentOffY); - xPos += contentOffX; - yPos += contentOffY; - ui.setupRenderPosition(xPos, yPos); + // For vertical split, scale down to fit the full SWF height when the + // window is shorter than the movie (same fix as HUD). + if(!needsYTile && m_movieHeight > 0) + { + F32 scaleH = (F32)tileHeight / (F32)m_movieHeight; + if(scaleH < scale) + scale = scaleH; + } + + IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) ); IggyPlayerDrawTilesStart ( getMovie() ); diff --git a/Minecraft.Client/Common/UI/UIScene_HUD.cpp b/Minecraft.Client/Common/UI/UIScene_HUD.cpp index 61cec0f09..0d8adcb24 100644 --- a/Minecraft.Client/Common/UI/UIScene_HUD.cpp +++ b/Minecraft.Client/Common/UI/UIScene_HUD.cpp @@ -1,868 +1,882 @@ -#include "stdafx.h" -#include "UI.h" -#include "UIScene_HUD.h" -#include "UISplitScreenHelpers.h" -#include "BossMobGuiInfo.h" -#include "..\..\Minecraft.h" -#include "..\..\MultiplayerLocalPlayer.h" -#include "..\..\..\Minecraft.World\net.minecraft.world.entity.boss.enderdragon.h" -#include "..\..\EnderDragonRenderer.h" -#include "..\..\..\Minecraft.World\net.minecraft.world.inventory.h" -#include "..\..\..\Minecraft.World\StringHelpers.h" - -UIScene_HUD::UIScene_HUD(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer) -{ - m_bSplitscreen = false; - - // Setup all the Iggy references we need for this scene - initialiseMovie(); - - SetDragonLabel( app.GetString( IDS_BOSS_ENDERDRAGON_HEALTH ) ); - SetSelectedLabel(L""); - - for(unsigned int i = 0; i < CHAT_LINES_COUNT; ++i) - { - m_labelChatText[i].init(L""); - } - m_labelJukebox.init(L""); - - addTimer(0, 100); -} - -wstring UIScene_HUD::getMoviePath() -{ - switch( m_parentLayer->getViewport() ) - { - case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: - case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: - case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: - case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT: - case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT: - case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: - case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: - case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: - m_bSplitscreen = true; - return L"HUDSplit"; - break; - case C4JRender::VIEWPORT_TYPE_FULLSCREEN: - default: - m_bSplitscreen = false; - return L"HUD"; - break; - } -} - -void UIScene_HUD::updateSafeZone() -{ - // Distance from edge - F64 safeTop = 0.0; - F64 safeBottom = 0.0; - F64 safeLeft = 0.0; - F64 safeRight = 0.0; - - switch( m_parentLayer->getViewport() ) - { - case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: - safeTop = getSafeZoneHalfHeight(); - safeLeft = getSafeZoneHalfWidth(); - safeRight = getSafeZoneHalfWidth(); - break; - case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: - safeBottom = getSafeZoneHalfHeight(); - safeLeft = getSafeZoneHalfWidth(); - safeRight = getSafeZoneHalfWidth(); - break; - case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: - safeLeft = getSafeZoneHalfWidth(); - safeTop = getSafeZoneHalfHeight(); - safeBottom = getSafeZoneHalfHeight(); - break; - case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT: - safeRight = getSafeZoneHalfWidth(); - safeTop = getSafeZoneHalfHeight(); - safeBottom = getSafeZoneHalfHeight(); - break; - case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT: - safeTop = getSafeZoneHalfHeight(); - safeLeft = getSafeZoneHalfWidth(); - break; - case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: - safeTop = getSafeZoneHalfHeight(); - safeRight = getSafeZoneHalfWidth(); - break; - case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: - safeBottom = getSafeZoneHalfHeight(); - safeLeft = getSafeZoneHalfWidth(); - break; - case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: - safeBottom = getSafeZoneHalfHeight(); - safeRight = getSafeZoneHalfWidth(); - break; - case C4JRender::VIEWPORT_TYPE_FULLSCREEN: - default: - safeTop = getSafeZoneHalfHeight(); - safeBottom = getSafeZoneHalfHeight(); - safeLeft = getSafeZoneHalfWidth(); - safeRight = getSafeZoneHalfWidth(); - break; - } - setSafeZone(safeTop, safeBottom, safeLeft, safeRight); -} - -void UIScene_HUD::tick() -{ - UIScene::tick(); - if(getMovie() && app.GetGameStarted()) - { - Minecraft *pMinecraft = Minecraft::GetInstance(); - if(pMinecraft->localplayers[m_iPad] == nullptr || pMinecraft->localgameModes[m_iPad] == nullptr) - { - return; - } - - // Is boss present? - bool noBoss = BossMobGuiInfo::name.empty() || BossMobGuiInfo::displayTicks <= 0; - if (noBoss) - { - if (m_showDragonHealth) - { - // No boss and health is visible - if(m_ticksWithNoBoss <= 20) - { - ++m_ticksWithNoBoss; - } - else - { - ShowDragonHealth(false); - } - } - } - else - { - BossMobGuiInfo::displayTicks--; - - m_ticksWithNoBoss = 0; - SetDragonHealth(BossMobGuiInfo::healthProgress); - - if (!m_showDragonHealth) - { - SetDragonLabel(BossMobGuiInfo::name); - ShowDragonHealth(true); - } - } - } -} - -void UIScene_HUD::customDraw(IggyCustomDrawCallbackRegion *region) -{ - Minecraft *pMinecraft = Minecraft::GetInstance(); - if(pMinecraft->localplayers[m_iPad] == nullptr || pMinecraft->localgameModes[m_iPad] == nullptr) return; - - int slot = -1; - swscanf(static_cast(region->name),L"slot_%d",&slot); - if (slot == -1) - { - app.DebugPrintf("This is not the control we are looking for\n"); - } - else - { - Slot *invSlot = pMinecraft->localplayers[m_iPad]->inventoryMenu->getSlot(InventoryMenu::USE_ROW_SLOT_START + slot); - shared_ptr item = invSlot->getItem(); - if(item != nullptr) - { - unsigned char ucAlpha=app.GetGameSettings(ProfileManager.GetPrimaryPad(),eGameSetting_InterfaceOpacity); - float fVal; - - if(ucAlpha<80) - { - // check if we have the timer running for the opacity - unsigned int uiOpacityTimer=app.GetOpacityTimer(m_iPad); - if(uiOpacityTimer!=0) - { - if(uiOpacityTimer<10) - { - float fStep=(80.0f-static_cast(ucAlpha))/10.0f; - fVal=0.01f*(80.0f-((10.0f-static_cast(uiOpacityTimer))*fStep)); - } - else - { - fVal=0.01f*80.0f; - } - } - else - { - fVal=0.01f*static_cast(ucAlpha); - } - } - else - { - fVal=0.01f*static_cast(ucAlpha); - } - customDrawSlotControl(region,m_iPad,item,fVal,item->isFoil(),true); - } - } -} - -void UIScene_HUD::handleReload() -{ - m_lastActiveSlot = -1; - m_iGuiScale = -1; - m_bToolTipsVisible = true; - m_lastExpProgress = 0.0f; - m_lastExpLevel = 0; - m_iCurrentHealth = 0; - m_lastMaxHealth = 20; - m_lastHealthBlink = false; - m_lastHealthPoison = false; - m_iCurrentFood = -1; - m_lastFoodPoison = false; - m_lastAir = 10; - m_currentExtraAir = 0; - m_lastArmour = 0; - m_showHealth = true; - m_showHorseHealth = true; - m_showFood = true; - m_showAir = false; // get's initialised invisible anyways, by setting it to false we ensure it will remain visible when switching in and out of split screen! - m_showArmour = true; - m_showExpBar = true; - m_bRegenEffectEnabled = false; - m_iFoodSaturation = 0; - m_lastDragonHealth = 0.0f; - m_showDragonHealth = false; - m_ticksWithNoBoss = 0; - m_uiSelectedItemOpacityCountDown = 0; - m_displayName = L""; - m_lastShowDisplayName = true; - m_bRidingHorse = true; - m_horseHealth = 1; - m_lastHealthWither = true; - m_iCurrentHealthAbsorb = -1; - m_horseJumpProgress = 1.0f; - m_iHeartOffsetIndex = -1; - m_bHealthAbsorbActive = false; - m_iHorseMaxHealth = -1; - - m_labelDisplayName.setVisible(m_lastShowDisplayName); - - SetDragonLabel(BossMobGuiInfo::name); - SetSelectedLabel(L""); - - for(unsigned int i = 0; i < CHAT_LINES_COUNT; ++i) - { - m_labelChatText[i].init(L""); - } - m_labelJukebox.init(L""); - - int iGuiScale; - Minecraft *pMinecraft = Minecraft::GetInstance(); - if(pMinecraft->localplayers[m_iPad] == nullptr || pMinecraft->localplayers[m_iPad]->m_iScreenSection == C4JRender::VIEWPORT_TYPE_FULLSCREEN) - { - iGuiScale=app.GetGameSettings(m_iPad,eGameSetting_UISize); - } - else - { - iGuiScale=app.GetGameSettings(m_iPad,eGameSetting_UISizeSplitscreen); - } - SetHudSize(iGuiScale); - - SetDisplayName(ProfileManager.GetDisplayName(m_iPad)); - - SetTooltipsEnabled(((ui.GetMenuDisplayed(ProfileManager.GetPrimaryPad())) || (app.GetGameSettings(ProfileManager.GetPrimaryPad(),eGameSetting_Tooltips) != 0))); -} - -int UIScene_HUD::getPad() -{ - return m_iPad; -} - -void UIScene_HUD::SetOpacity(float opacity) -{ - setOpacity(opacity); -} - -void UIScene_HUD::SetVisible(bool visible) -{ - setVisible(visible); -} - -void UIScene_HUD::SetHudSize(int scale) -{ - if(scale != m_iGuiScale) - { - m_iGuiScale = scale; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = scale; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcLoadHud , 1 , value ); - } -} - -void UIScene_HUD::SetExpBarProgress(float progress, int xpNeededForNextLevel) -{ - if(progress != m_lastExpProgress) - { - m_lastExpProgress = progress; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = progress; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetExpBarProgress , 1 , value ); - } -} - -void UIScene_HUD::SetExpLevel(int level) -{ - if(level != m_lastExpLevel) - { - m_lastExpLevel = level; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = level; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetPlayerLevel , 1 , value ); - } -} - -void UIScene_HUD::SetActiveSlot(int slot) -{ - if(slot != m_lastActiveSlot) - { - m_lastActiveSlot = slot; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = slot; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetActiveSlot , 1 , value ); - } -} - -void UIScene_HUD::SetHealth(int iHealth, int iLastHealth, bool bBlink, bool bPoison, bool bWither) -{ - int maxHealth = max(iHealth, iLastHealth); - if(maxHealth != m_lastMaxHealth || bBlink != m_lastHealthBlink || bPoison != m_lastHealthPoison || bWither != m_lastHealthWither) - { - m_lastMaxHealth = maxHealth; - m_lastHealthBlink = bBlink; - m_lastHealthPoison = bPoison; - m_lastHealthWither = bWither; - - IggyDataValue result; - IggyDataValue value[4]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = maxHealth; - value[1].type = IGGY_DATATYPE_boolean; - value[1].boolval = bBlink; - value[2].type = IGGY_DATATYPE_boolean; - value[2].boolval = bPoison; - value[3].type = IGGY_DATATYPE_boolean; - value[3].boolval = bWither; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetHealth , 4 , value ); - } -} - -void UIScene_HUD::SetFood(int iFood, int iLastFood, bool bPoison) -{ - // Ignore iLastFood as food doesn't flash - int maxFood = iFood; //, iLastFood); - if(maxFood != m_iCurrentFood || bPoison != m_lastFoodPoison) - { - m_iCurrentFood = maxFood; - m_lastFoodPoison = bPoison; - - IggyDataValue result; - IggyDataValue value[2]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = maxFood; - value[1].type = IGGY_DATATYPE_boolean; - value[1].boolval = bPoison; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetFood , 2 , value ); - } -} - -void UIScene_HUD::SetAir(int iAir, int extra) -{ - if(iAir != m_lastAir) - { - app.DebugPrintf("SetAir to %d\n", iAir); - m_lastAir = iAir; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = iAir; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetAir , 1 , value ); - } -} - -void UIScene_HUD::SetArmour(int iArmour) -{ - if(iArmour != m_lastArmour) - { - app.DebugPrintf("SetArmour to %d\n", iArmour); - m_lastArmour = iArmour; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = iArmour; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetArmour , 1 , value ); - } -} - -void UIScene_HUD::ShowHealth(bool show) -{ - if(show != m_showHealth) - { - app.DebugPrintf("ShowHealth to %s\n", show?"TRUE":"FALSE"); - m_showHealth = show; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_boolean; - value[0].boolval = show; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowHealth , 1 , value ); - } -} - -void UIScene_HUD::ShowHorseHealth(bool show) -{ - if(show != m_showHorseHealth) - { - app.DebugPrintf("ShowHorseHealth to %s\n", show?"TRUE":"FALSE"); - m_showHorseHealth = show; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_boolean; - value[0].boolval = show; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowHorseHealth , 1 , value ); - } -} - -void UIScene_HUD::ShowFood(bool show) -{ - if(show != m_showFood) - { - app.DebugPrintf("ShowFood to %s\n", show?"TRUE":"FALSE"); - m_showFood = show; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_boolean; - value[0].boolval = show; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowFood , 1 , value ); - } -} - -void UIScene_HUD::ShowAir(bool show) -{ - if(show != m_showAir) - { - app.DebugPrintf("ShowAir to %s\n", show?"TRUE":"FALSE"); - m_showAir = show; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_boolean; - value[0].boolval = show; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowAir , 1 , value ); - } -} - -void UIScene_HUD::ShowArmour(bool show) -{ - if(show != m_showArmour) - { - app.DebugPrintf("ShowArmour to %s\n", show?"TRUE":"FALSE"); - m_showArmour = show; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_boolean; - value[0].boolval = show; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowArmour , 1 , value ); - } -} - -void UIScene_HUD::ShowExpBar(bool show) -{ - if(show != m_showExpBar) - { - app.DebugPrintf("ShowExpBar to %s\n", show?"TRUE":"FALSE"); - m_showExpBar = show; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_boolean; - value[0].boolval = show; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowExpbar , 1 , value ); - } -} - -void UIScene_HUD::SetRegenerationEffect(bool bEnabled) -{ - if(bEnabled != m_bRegenEffectEnabled) - { - app.DebugPrintf("SetRegenerationEffect to %s\n", bEnabled?"TRUE":"FALSE"); - m_bRegenEffectEnabled = bEnabled; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_boolean; - value[0].boolval = bEnabled; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetRegenerationEffect , 1 , value ); - } -} - -void UIScene_HUD::SetFoodSaturationLevel(int iSaturation) -{ - if(iSaturation != m_iFoodSaturation) - { - app.DebugPrintf("Set saturation to %d\n", iSaturation); - m_iFoodSaturation = iSaturation; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = iSaturation; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetFoodSaturationLevel , 1 , value ); - } -} - -void UIScene_HUD::SetDragonHealth(float health) -{ - if(health != m_lastDragonHealth) - { - app.DebugPrintf("Set dragon health to %f\n", health); - m_lastDragonHealth = health; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = health; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetDragonHealth , 1 , value ); - } -} - -void UIScene_HUD::SetDragonLabel(const wstring &label) -{ - IggyDataValue result; - IggyDataValue value[1]; - IggyStringUTF16 stringVal; - stringVal.string = (IggyUTF16*)label.c_str(); - stringVal.length = label.length(); - value[0].type = IGGY_DATATYPE_string_UTF16; - value[0].string16 = stringVal; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetDragonLabel , 1 , value ); -} - -void UIScene_HUD::ShowDragonHealth(bool show) -{ - if(show != m_showDragonHealth) - { - app.DebugPrintf("ShowDragonHealth to %s\n", show?"TRUE":"FALSE"); - m_showDragonHealth = show; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_boolean; - value[0].boolval = show; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowDragonHealth , 1 , value ); - } -} - -void UIScene_HUD::SetSelectedLabel(const wstring &label) -{ - // 4J Stu - Timing here is kept the same as on Xbox360, even though we do it differently now and do the fade out in Flash rather than directly setting opacity - if(!label.empty()) m_uiSelectedItemOpacityCountDown = SharedConstants::TICKS_PER_SECOND * 3; - - IggyDataValue result; - IggyDataValue value[1]; - IggyStringUTF16 stringVal; - stringVal.string = (IggyUTF16*)label.c_str(); - stringVal.length = label.length(); - value[0].type = IGGY_DATATYPE_string_UTF16; - value[0].string16 = stringVal; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetSelectedLabel , 1 , value ); -} - -void UIScene_HUD::HideSelectedLabel() -{ - IggyDataValue result; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcHideSelectedLabel , 0 , nullptr ); -} - - -void UIScene_HUD::SetRidingHorse(bool ridingHorse, bool bIsJumpable, int maxHorseHealth) -{ - if(m_bRidingHorse != ridingHorse || maxHorseHealth != m_iHorseMaxHealth) - { - app.DebugPrintf("SetRidingHorse to %s\n", ridingHorse?"TRUE":"FALSE"); - m_bRidingHorse = ridingHorse; - m_bIsJumpable = bIsJumpable; - m_iHorseMaxHealth = maxHorseHealth; - - IggyDataValue result; - IggyDataValue value[3]; - value[0].type = IGGY_DATATYPE_boolean; - value[0].boolval = ridingHorse; - value[1].type = IGGY_DATATYPE_boolean; - value[1].boolval = bIsJumpable; - value[2].type = IGGY_DATATYPE_number; - value[2].number = maxHorseHealth; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetRidingHorse , 3 , value ); - } -} - -void UIScene_HUD::SetHorseHealth(int health, bool blink /*= false*/) -{ - if(m_bRidingHorse && m_horseHealth != health) - { - app.DebugPrintf("SetHorseHealth to %d\n", health); - m_horseHealth = health; - - IggyDataValue result; - IggyDataValue value[2]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = health; - value[1].type = IGGY_DATATYPE_boolean; - value[1].boolval = blink; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetHorseHealth , 2 , value ); - } -} - -void UIScene_HUD::SetHorseJumpBarProgress(float progress) -{ - if(m_bRidingHorse && m_horseJumpProgress != progress) - { - app.DebugPrintf("SetHorseJumpBarProgress to %f\n", progress); - m_horseJumpProgress = progress; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = progress; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetHorseJumpBarProgress , 1 , value ); - } -} - -void UIScene_HUD::SetHealthAbsorb(int healthAbsorb) -{ - if(m_iCurrentHealthAbsorb != healthAbsorb) - { - app.DebugPrintf("SetHealthAbsorb to %d\n", healthAbsorb); - m_iCurrentHealthAbsorb = healthAbsorb; - - IggyDataValue result; - IggyDataValue value[2]; - value[0].type = IGGY_DATATYPE_boolean; - value[0].boolval = healthAbsorb > 0; - value[1].type = IGGY_DATATYPE_number; - value[1].number = healthAbsorb; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetHealthAbsorb , 2 , value ); - } -} - -void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewport) -{ - if(m_bSplitscreen) - { - S32 xPos = 0; - S32 yPos = 0; - switch( viewport ) - { - case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: - case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: - yPos = static_cast(ui.getScreenHeight() / 2); - break; - case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT: - case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: - xPos = static_cast(ui.getScreenWidth() / 2); - break; - case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: - xPos = static_cast(ui.getScreenWidth() / 2); - yPos = static_cast(ui.getScreenHeight() / 2); - break; - } - ui.setupRenderPosition(xPos, yPos); - - S32 tileXStart = 0; - S32 tileYStart = 0; - S32 tileWidth = width; - S32 tileHeight = height; - - bool needsYTile = false; - switch( viewport ) - { - case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: - case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT: - tileHeight = static_cast(ui.getScreenHeight()); - break; - case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: - tileWidth = static_cast(ui.getScreenWidth()); - needsYTile = true; - break; - case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT: - case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: - case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: - case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: - needsYTile = true; - break; - } - - F32 scale; - ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart); - - IggyPlayerSetDisplaySize( getMovie(), static_cast(m_movieWidth * scale), static_cast(m_movieHeight * scale) ); - - repositionHud(tileWidth, tileHeight, scale); - - m_renderWidth = tileWidth; - m_renderHeight = tileHeight; - - IggyPlayerDrawTilesStart ( getMovie() ); - IggyPlayerDrawTile ( getMovie() , - tileXStart , - tileYStart , - tileXStart + tileWidth , - tileYStart + tileHeight , - 0 ); - IggyPlayerDrawTilesEnd ( getMovie() ); - } - else - { - UIScene::render(width, height, viewport); - } -} - -void UIScene_HUD::handleTimerComplete(int id) -{ - Minecraft *pMinecraft = Minecraft::GetInstance(); - - bool anyVisible = false; - if(pMinecraft->localplayers[m_iPad]!= nullptr) - { - Gui *pGui = pMinecraft->gui; - //DWORD messagesToDisplay = min( CHAT_LINES_COUNT, pGui->getMessagesCount(m_iPad) ); - for( unsigned int i = 0; i < CHAT_LINES_COUNT; ++i ) - { - float opacity = pGui->getOpacity(m_iPad, i); - if( opacity > 0 ) - { -#if 0 // def _WINDOWS64 // Use Iggy chat until Gui::render has visual parity - // Chat drawn by Gui::render with color codes. Hides Iggy chat to avoid double chats. - m_controlLabelBackground[i].setOpacity(0); - m_labelChatText[i].setOpacity(0); - m_labelChatText[i].setLabel(L""); -#else - m_controlLabelBackground[i].setOpacity(opacity); - m_labelChatText[i].setOpacity(opacity); - m_labelChatText[i].setLabel( pGui->getMessagesCount(m_iPad) ? pGui->getMessage(m_iPad,i) : L"" ); -#endif - anyVisible = true; - } - else - { - m_controlLabelBackground[i].setOpacity(0); - m_labelChatText[i].setOpacity(0); - m_labelChatText[i].setLabel(L""); - } - } - if(pGui->getJukeboxOpacity(m_iPad) > 0) anyVisible = true; - m_labelJukebox.setOpacity( pGui->getJukeboxOpacity(m_iPad) ); - m_labelJukebox.setLabel( pGui->getJukeboxMessage(m_iPad) ); - } - else - { - for( unsigned int i = 0; i < CHAT_LINES_COUNT; ++i ) - { - m_controlLabelBackground[i].setOpacity(0); - m_labelChatText[i].setOpacity(0); - m_labelChatText[i].setLabel(L""); - } - m_labelJukebox.setOpacity( 0 ); - } - - //setVisible(anyVisible); -} - -void UIScene_HUD::repositionHud(S32 tileWidth, S32 tileHeight, F32 scale) -{ - if(!m_bSplitscreen) return; - - // Pass the visible tile area in SWF coordinates so ActionScript - // positions elements (crosshair, hotbar, etc.) centered in the - // actually visible region, not the raw viewport. - S32 visibleW = static_cast(tileWidth / scale); - S32 visibleH = static_cast(tileHeight / scale); - - app.DebugPrintf(app.USER_SR, "Reposition HUD: tile %dx%d, scale %.3f, visible SWF %dx%d\n", tileWidth, tileHeight, scale, visibleW, visibleH ); - - IggyDataValue result; - IggyDataValue value[2]; - value[0].type = IGGY_DATATYPE_number; - value[0].number = visibleW; - value[1].type = IGGY_DATATYPE_number; - value[1].number = visibleH; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcRepositionHud , 2 , value ); -} - -void UIScene_HUD::ShowDisplayName(bool show) -{ - m_lastShowDisplayName = show; - m_labelDisplayName.setVisible(show); -} - -void UIScene_HUD::SetDisplayName(const wstring &displayName) -{ - if(displayName.compare(m_displayName) != 0) - { - m_displayName = displayName; - - IggyDataValue result; - IggyDataValue value[1]; - IggyStringUTF16 stringVal; - stringVal.string = (IggyUTF16*)displayName.c_str(); - stringVal.length = displayName.length(); - value[0].type = IGGY_DATATYPE_string_UTF16; - value[0].string16 = stringVal; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetDisplayName , 1 , value ); - - m_labelDisplayName.setVisible(m_lastShowDisplayName); - } -} - -void UIScene_HUD::SetTooltipsEnabled(bool bEnabled) -{ - if(m_bToolTipsVisible != bEnabled) - { - m_bToolTipsVisible = bEnabled; - - IggyDataValue result; - IggyDataValue value[1]; - value[0].type = IGGY_DATATYPE_boolean; - value[0].boolval = bEnabled; - IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetTooltipsEnabled , 1 , value ); - } -} - -void UIScene_HUD::handleGameTick() -{ - if(getMovie() && app.GetGameStarted()) - { - Minecraft *pMinecraft = Minecraft::GetInstance(); - if(pMinecraft->localplayers[m_iPad] == nullptr || pMinecraft->localgameModes[m_iPad] == nullptr) - { - m_parentLayer->showComponent(m_iPad, eUIScene_HUD,false); - return; - } - m_parentLayer->showComponent(m_iPad, eUIScene_HUD,true); - - updateFrameTick(); - } +#include "stdafx.h" +#include "UI.h" +#include "UIScene_HUD.h" +#include "UISplitScreenHelpers.h" +#include "BossMobGuiInfo.h" +#include "..\..\Minecraft.h" +#include "..\..\MultiplayerLocalPlayer.h" +#include "..\..\..\Minecraft.World\net.minecraft.world.entity.boss.enderdragon.h" +#include "..\..\EnderDragonRenderer.h" +#include "..\..\..\Minecraft.World\net.minecraft.world.inventory.h" +#include "..\..\..\Minecraft.World\StringHelpers.h" + +UIScene_HUD::UIScene_HUD(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer) +{ + m_bSplitscreen = false; + + // Setup all the Iggy references we need for this scene + initialiseMovie(); + + SetDragonLabel( app.GetString( IDS_BOSS_ENDERDRAGON_HEALTH ) ); + SetSelectedLabel(L""); + + for(unsigned int i = 0; i < CHAT_LINES_COUNT; ++i) + { + m_labelChatText[i].init(L""); + } + m_labelJukebox.init(L""); + + addTimer(0, 100); +} + +wstring UIScene_HUD::getMoviePath() +{ + switch( m_parentLayer->getViewport() ) + { + case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: + case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: + case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: + case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT: + case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT: + case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: + m_bSplitscreen = true; + return L"HUDSplit"; + break; + case C4JRender::VIEWPORT_TYPE_FULLSCREEN: + default: + m_bSplitscreen = false; + return L"HUD"; + break; + } +} + +void UIScene_HUD::updateSafeZone() +{ + // Distance from edge + F64 safeTop = 0.0; + F64 safeBottom = 0.0; + F64 safeLeft = 0.0; + F64 safeRight = 0.0; + + switch( m_parentLayer->getViewport() ) + { + case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: + safeTop = getSafeZoneHalfHeight(); + safeLeft = getSafeZoneHalfWidth(); + safeRight = getSafeZoneHalfWidth(); + break; + case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: + safeBottom = getSafeZoneHalfHeight(); + safeLeft = getSafeZoneHalfWidth(); + safeRight = getSafeZoneHalfWidth(); + break; + case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: + safeLeft = getSafeZoneHalfWidth(); + safeTop = getSafeZoneHalfHeight(); + safeBottom = getSafeZoneHalfHeight(); + break; + case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT: + safeRight = getSafeZoneHalfWidth(); + safeTop = getSafeZoneHalfHeight(); + safeBottom = getSafeZoneHalfHeight(); + break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT: + safeTop = getSafeZoneHalfHeight(); + safeLeft = getSafeZoneHalfWidth(); + break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: + safeTop = getSafeZoneHalfHeight(); + safeRight = getSafeZoneHalfWidth(); + break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: + safeBottom = getSafeZoneHalfHeight(); + safeLeft = getSafeZoneHalfWidth(); + break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: + safeBottom = getSafeZoneHalfHeight(); + safeRight = getSafeZoneHalfWidth(); + break; + case C4JRender::VIEWPORT_TYPE_FULLSCREEN: + default: + safeTop = getSafeZoneHalfHeight(); + safeBottom = getSafeZoneHalfHeight(); + safeLeft = getSafeZoneHalfWidth(); + safeRight = getSafeZoneHalfWidth(); + break; + } + setSafeZone(safeTop, safeBottom, safeLeft, safeRight); +} + +void UIScene_HUD::tick() +{ + UIScene::tick(); + if(getMovie() && app.GetGameStarted()) + { + Minecraft *pMinecraft = Minecraft::GetInstance(); + if(pMinecraft->localplayers[m_iPad] == nullptr || pMinecraft->localgameModes[m_iPad] == nullptr) + { + return; + } + + // Is boss present? + bool noBoss = BossMobGuiInfo::name.empty() || BossMobGuiInfo::displayTicks <= 0; + if (noBoss) + { + if (m_showDragonHealth) + { + // No boss and health is visible + if(m_ticksWithNoBoss <= 20) + { + ++m_ticksWithNoBoss; + } + else + { + ShowDragonHealth(false); + } + } + } + else + { + BossMobGuiInfo::displayTicks--; + + m_ticksWithNoBoss = 0; + SetDragonHealth(BossMobGuiInfo::healthProgress); + + if (!m_showDragonHealth) + { + SetDragonLabel(BossMobGuiInfo::name); + ShowDragonHealth(true); + } + } + } +} + +void UIScene_HUD::customDraw(IggyCustomDrawCallbackRegion *region) +{ + Minecraft *pMinecraft = Minecraft::GetInstance(); + if(pMinecraft->localplayers[m_iPad] == nullptr || pMinecraft->localgameModes[m_iPad] == nullptr) return; + + int slot = -1; + swscanf(static_cast(region->name),L"slot_%d",&slot); + if (slot == -1) + { + app.DebugPrintf("This is not the control we are looking for\n"); + } + else + { + Slot *invSlot = pMinecraft->localplayers[m_iPad]->inventoryMenu->getSlot(InventoryMenu::USE_ROW_SLOT_START + slot); + shared_ptr item = invSlot->getItem(); + if(item != nullptr) + { + unsigned char ucAlpha=app.GetGameSettings(ProfileManager.GetPrimaryPad(),eGameSetting_InterfaceOpacity); + float fVal; + + if(ucAlpha<80) + { + // check if we have the timer running for the opacity + unsigned int uiOpacityTimer=app.GetOpacityTimer(m_iPad); + if(uiOpacityTimer!=0) + { + if(uiOpacityTimer<10) + { + float fStep=(80.0f-static_cast(ucAlpha))/10.0f; + fVal=0.01f*(80.0f-((10.0f-static_cast(uiOpacityTimer))*fStep)); + } + else + { + fVal=0.01f*80.0f; + } + } + else + { + fVal=0.01f*static_cast(ucAlpha); + } + } + else + { + fVal=0.01f*static_cast(ucAlpha); + } + customDrawSlotControl(region,m_iPad,item,fVal,item->isFoil(),true); + } + } +} + +void UIScene_HUD::handleReload() +{ + m_lastActiveSlot = -1; + m_iGuiScale = -1; + m_bToolTipsVisible = true; + m_lastExpProgress = 0.0f; + m_lastExpLevel = 0; + m_iCurrentHealth = 0; + m_lastMaxHealth = 20; + m_lastHealthBlink = false; + m_lastHealthPoison = false; + m_iCurrentFood = -1; + m_lastFoodPoison = false; + m_lastAir = 10; + m_currentExtraAir = 0; + m_lastArmour = 0; + m_showHealth = true; + m_showHorseHealth = true; + m_showFood = true; + m_showAir = false; // get's initialised invisible anyways, by setting it to false we ensure it will remain visible when switching in and out of split screen! + m_showArmour = true; + m_showExpBar = true; + m_bRegenEffectEnabled = false; + m_iFoodSaturation = 0; + m_lastDragonHealth = 0.0f; + m_showDragonHealth = false; + m_ticksWithNoBoss = 0; + m_uiSelectedItemOpacityCountDown = 0; + m_displayName = L""; + m_lastShowDisplayName = true; + m_bRidingHorse = true; + m_horseHealth = 1; + m_lastHealthWither = true; + m_iCurrentHealthAbsorb = -1; + m_horseJumpProgress = 1.0f; + m_iHeartOffsetIndex = -1; + m_bHealthAbsorbActive = false; + m_iHorseMaxHealth = -1; + + m_labelDisplayName.setVisible(m_lastShowDisplayName); + + SetDragonLabel(BossMobGuiInfo::name); + SetSelectedLabel(L""); + + for(unsigned int i = 0; i < CHAT_LINES_COUNT; ++i) + { + m_labelChatText[i].init(L""); + } + m_labelJukebox.init(L""); + + int iGuiScale; + Minecraft *pMinecraft = Minecraft::GetInstance(); + if(pMinecraft->localplayers[m_iPad] == nullptr || pMinecraft->localplayers[m_iPad]->m_iScreenSection == C4JRender::VIEWPORT_TYPE_FULLSCREEN) + { + iGuiScale=app.GetGameSettings(m_iPad,eGameSetting_UISize); + } + else + { + iGuiScale=app.GetGameSettings(m_iPad,eGameSetting_UISizeSplitscreen); + } + SetHudSize(iGuiScale); + + SetDisplayName(ProfileManager.GetDisplayName(m_iPad)); + + SetTooltipsEnabled(((ui.GetMenuDisplayed(ProfileManager.GetPrimaryPad())) || (app.GetGameSettings(ProfileManager.GetPrimaryPad(),eGameSetting_Tooltips) != 0))); +} + +int UIScene_HUD::getPad() +{ + return m_iPad; +} + +void UIScene_HUD::SetOpacity(float opacity) +{ + setOpacity(opacity); +} + +void UIScene_HUD::SetVisible(bool visible) +{ + setVisible(visible); +} + +void UIScene_HUD::SetHudSize(int scale) +{ + if(scale != m_iGuiScale) + { + m_iGuiScale = scale; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = scale; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcLoadHud , 1 , value ); + } +} + +void UIScene_HUD::SetExpBarProgress(float progress, int xpNeededForNextLevel) +{ + if(progress != m_lastExpProgress) + { + m_lastExpProgress = progress; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = progress; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetExpBarProgress , 1 , value ); + } +} + +void UIScene_HUD::SetExpLevel(int level) +{ + if(level != m_lastExpLevel) + { + m_lastExpLevel = level; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = level; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetPlayerLevel , 1 , value ); + } +} + +void UIScene_HUD::SetActiveSlot(int slot) +{ + if(slot != m_lastActiveSlot) + { + m_lastActiveSlot = slot; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = slot; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetActiveSlot , 1 , value ); + } +} + +void UIScene_HUD::SetHealth(int iHealth, int iLastHealth, bool bBlink, bool bPoison, bool bWither) +{ + int maxHealth = max(iHealth, iLastHealth); + if(maxHealth != m_lastMaxHealth || bBlink != m_lastHealthBlink || bPoison != m_lastHealthPoison || bWither != m_lastHealthWither) + { + m_lastMaxHealth = maxHealth; + m_lastHealthBlink = bBlink; + m_lastHealthPoison = bPoison; + m_lastHealthWither = bWither; + + IggyDataValue result; + IggyDataValue value[4]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = maxHealth; + value[1].type = IGGY_DATATYPE_boolean; + value[1].boolval = bBlink; + value[2].type = IGGY_DATATYPE_boolean; + value[2].boolval = bPoison; + value[3].type = IGGY_DATATYPE_boolean; + value[3].boolval = bWither; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetHealth , 4 , value ); + } +} + +void UIScene_HUD::SetFood(int iFood, int iLastFood, bool bPoison) +{ + // Ignore iLastFood as food doesn't flash + int maxFood = iFood; //, iLastFood); + if(maxFood != m_iCurrentFood || bPoison != m_lastFoodPoison) + { + m_iCurrentFood = maxFood; + m_lastFoodPoison = bPoison; + + IggyDataValue result; + IggyDataValue value[2]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = maxFood; + value[1].type = IGGY_DATATYPE_boolean; + value[1].boolval = bPoison; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetFood , 2 , value ); + } +} + +void UIScene_HUD::SetAir(int iAir, int extra) +{ + if(iAir != m_lastAir) + { + app.DebugPrintf("SetAir to %d\n", iAir); + m_lastAir = iAir; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = iAir; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetAir , 1 , value ); + } +} + +void UIScene_HUD::SetArmour(int iArmour) +{ + if(iArmour != m_lastArmour) + { + app.DebugPrintf("SetArmour to %d\n", iArmour); + m_lastArmour = iArmour; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = iArmour; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetArmour , 1 , value ); + } +} + +void UIScene_HUD::ShowHealth(bool show) +{ + if(show != m_showHealth) + { + app.DebugPrintf("ShowHealth to %s\n", show?"TRUE":"FALSE"); + m_showHealth = show; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_boolean; + value[0].boolval = show; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowHealth , 1 , value ); + } +} + +void UIScene_HUD::ShowHorseHealth(bool show) +{ + if(show != m_showHorseHealth) + { + app.DebugPrintf("ShowHorseHealth to %s\n", show?"TRUE":"FALSE"); + m_showHorseHealth = show; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_boolean; + value[0].boolval = show; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowHorseHealth , 1 , value ); + } +} + +void UIScene_HUD::ShowFood(bool show) +{ + if(show != m_showFood) + { + app.DebugPrintf("ShowFood to %s\n", show?"TRUE":"FALSE"); + m_showFood = show; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_boolean; + value[0].boolval = show; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowFood , 1 , value ); + } +} + +void UIScene_HUD::ShowAir(bool show) +{ + if(show != m_showAir) + { + app.DebugPrintf("ShowAir to %s\n", show?"TRUE":"FALSE"); + m_showAir = show; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_boolean; + value[0].boolval = show; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowAir , 1 , value ); + } +} + +void UIScene_HUD::ShowArmour(bool show) +{ + if(show != m_showArmour) + { + app.DebugPrintf("ShowArmour to %s\n", show?"TRUE":"FALSE"); + m_showArmour = show; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_boolean; + value[0].boolval = show; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowArmour , 1 , value ); + } +} + +void UIScene_HUD::ShowExpBar(bool show) +{ + if(show != m_showExpBar) + { + app.DebugPrintf("ShowExpBar to %s\n", show?"TRUE":"FALSE"); + m_showExpBar = show; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_boolean; + value[0].boolval = show; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowExpbar , 1 , value ); + } +} + +void UIScene_HUD::SetRegenerationEffect(bool bEnabled) +{ + if(bEnabled != m_bRegenEffectEnabled) + { + app.DebugPrintf("SetRegenerationEffect to %s\n", bEnabled?"TRUE":"FALSE"); + m_bRegenEffectEnabled = bEnabled; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_boolean; + value[0].boolval = bEnabled; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetRegenerationEffect , 1 , value ); + } +} + +void UIScene_HUD::SetFoodSaturationLevel(int iSaturation) +{ + if(iSaturation != m_iFoodSaturation) + { + app.DebugPrintf("Set saturation to %d\n", iSaturation); + m_iFoodSaturation = iSaturation; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = iSaturation; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetFoodSaturationLevel , 1 , value ); + } +} + +void UIScene_HUD::SetDragonHealth(float health) +{ + if(health != m_lastDragonHealth) + { + app.DebugPrintf("Set dragon health to %f\n", health); + m_lastDragonHealth = health; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = health; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetDragonHealth , 1 , value ); + } +} + +void UIScene_HUD::SetDragonLabel(const wstring &label) +{ + IggyDataValue result; + IggyDataValue value[1]; + IggyStringUTF16 stringVal; + stringVal.string = (IggyUTF16*)label.c_str(); + stringVal.length = label.length(); + value[0].type = IGGY_DATATYPE_string_UTF16; + value[0].string16 = stringVal; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetDragonLabel , 1 , value ); +} + +void UIScene_HUD::ShowDragonHealth(bool show) +{ + if(show != m_showDragonHealth) + { + app.DebugPrintf("ShowDragonHealth to %s\n", show?"TRUE":"FALSE"); + m_showDragonHealth = show; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_boolean; + value[0].boolval = show; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcShowDragonHealth , 1 , value ); + } +} + +void UIScene_HUD::SetSelectedLabel(const wstring &label) +{ + // 4J Stu - Timing here is kept the same as on Xbox360, even though we do it differently now and do the fade out in Flash rather than directly setting opacity + if(!label.empty()) m_uiSelectedItemOpacityCountDown = SharedConstants::TICKS_PER_SECOND * 3; + + IggyDataValue result; + IggyDataValue value[1]; + IggyStringUTF16 stringVal; + stringVal.string = (IggyUTF16*)label.c_str(); + stringVal.length = label.length(); + value[0].type = IGGY_DATATYPE_string_UTF16; + value[0].string16 = stringVal; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetSelectedLabel , 1 , value ); +} + +void UIScene_HUD::HideSelectedLabel() +{ + IggyDataValue result; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcHideSelectedLabel , 0 , nullptr ); +} + + +void UIScene_HUD::SetRidingHorse(bool ridingHorse, bool bIsJumpable, int maxHorseHealth) +{ + if(m_bRidingHorse != ridingHorse || maxHorseHealth != m_iHorseMaxHealth) + { + app.DebugPrintf("SetRidingHorse to %s\n", ridingHorse?"TRUE":"FALSE"); + m_bRidingHorse = ridingHorse; + m_bIsJumpable = bIsJumpable; + m_iHorseMaxHealth = maxHorseHealth; + + IggyDataValue result; + IggyDataValue value[3]; + value[0].type = IGGY_DATATYPE_boolean; + value[0].boolval = ridingHorse; + value[1].type = IGGY_DATATYPE_boolean; + value[1].boolval = bIsJumpable; + value[2].type = IGGY_DATATYPE_number; + value[2].number = maxHorseHealth; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetRidingHorse , 3 , value ); + } +} + +void UIScene_HUD::SetHorseHealth(int health, bool blink /*= false*/) +{ + if(m_bRidingHorse && m_horseHealth != health) + { + app.DebugPrintf("SetHorseHealth to %d\n", health); + m_horseHealth = health; + + IggyDataValue result; + IggyDataValue value[2]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = health; + value[1].type = IGGY_DATATYPE_boolean; + value[1].boolval = blink; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetHorseHealth , 2 , value ); + } +} + +void UIScene_HUD::SetHorseJumpBarProgress(float progress) +{ + if(m_bRidingHorse && m_horseJumpProgress != progress) + { + app.DebugPrintf("SetHorseJumpBarProgress to %f\n", progress); + m_horseJumpProgress = progress; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = progress; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetHorseJumpBarProgress , 1 , value ); + } +} + +void UIScene_HUD::SetHealthAbsorb(int healthAbsorb) +{ + if(m_iCurrentHealthAbsorb != healthAbsorb) + { + app.DebugPrintf("SetHealthAbsorb to %d\n", healthAbsorb); + m_iCurrentHealthAbsorb = healthAbsorb; + + IggyDataValue result; + IggyDataValue value[2]; + value[0].type = IGGY_DATATYPE_boolean; + value[0].boolval = healthAbsorb > 0; + value[1].type = IGGY_DATATYPE_number; + value[1].number = healthAbsorb; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetHealthAbsorb , 2 , value ); + } +} + +void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewport) +{ + if(m_bSplitscreen) + { + S32 xPos = 0; + S32 yPos = 0; + switch( viewport ) + { + case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: + yPos = static_cast(ui.getScreenHeight() / 2); + break; + case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT: + case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: + xPos = static_cast(ui.getScreenWidth() / 2); + break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: + xPos = static_cast(ui.getScreenWidth() / 2); + yPos = static_cast(ui.getScreenHeight() / 2); + break; + } + ui.setupRenderPosition(xPos, yPos); + + S32 tileXStart = 0; + S32 tileYStart = 0; + S32 tileWidth = width; + S32 tileHeight = height; + + bool needsYTile = false; + switch( viewport ) + { + case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: + case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT: + tileHeight = static_cast(ui.getScreenHeight()); + break; + case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: + tileWidth = static_cast(ui.getScreenWidth()); + needsYTile = true; + break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT: + case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: + needsYTile = true; + break; + } + + F32 scale; + ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart); + + // For vertical split, if the window is shorter than the SWF movie, + // scale the movie down to fit the full height instead of cropping. + // ComputeTileScale clamps scale >= 1.0 (needed for quadrant mode), + // but in vertical split the tile covers the full screen height and + // cropping the bottom pushes RepositionHud's ActionScript to shift + // elements down. Scaling down keeps visibleH == movieHeight in SWF + // space, so ActionScript sees the full height and applies no offset. + if(!needsYTile && m_movieHeight > 0) + { + F32 scaleH = (F32)tileHeight / (F32)m_movieHeight; + if(scaleH < scale) + scale = scaleH; + } + + IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) ); + + repositionHud(tileWidth, tileHeight, scale); + + m_renderWidth = tileWidth; + m_renderHeight = tileHeight; + + IggyPlayerDrawTilesStart ( getMovie() ); + IggyPlayerDrawTile ( getMovie() , + tileXStart , + tileYStart , + tileXStart + tileWidth , + tileYStart + tileHeight , + 0 ); + IggyPlayerDrawTilesEnd ( getMovie() ); + } + else + { + UIScene::render(width, height, viewport); + } +} + +void UIScene_HUD::handleTimerComplete(int id) +{ + Minecraft *pMinecraft = Minecraft::GetInstance(); + + bool anyVisible = false; + if(pMinecraft->localplayers[m_iPad]!= nullptr) + { + Gui *pGui = pMinecraft->gui; + //DWORD messagesToDisplay = min( CHAT_LINES_COUNT, pGui->getMessagesCount(m_iPad) ); + for( unsigned int i = 0; i < CHAT_LINES_COUNT; ++i ) + { + float opacity = pGui->getOpacity(m_iPad, i); + if( opacity > 0 ) + { +#if 0 // def _WINDOWS64 // Use Iggy chat until Gui::render has visual parity + // Chat drawn by Gui::render with color codes. Hides Iggy chat to avoid double chats. + m_controlLabelBackground[i].setOpacity(0); + m_labelChatText[i].setOpacity(0); + m_labelChatText[i].setLabel(L""); +#else + m_controlLabelBackground[i].setOpacity(opacity); + m_labelChatText[i].setOpacity(opacity); + m_labelChatText[i].setLabel( pGui->getMessagesCount(m_iPad) ? pGui->getMessage(m_iPad,i) : L"" ); +#endif + anyVisible = true; + } + else + { + m_controlLabelBackground[i].setOpacity(0); + m_labelChatText[i].setOpacity(0); + m_labelChatText[i].setLabel(L""); + } + } + if(pGui->getJukeboxOpacity(m_iPad) > 0) anyVisible = true; + m_labelJukebox.setOpacity( pGui->getJukeboxOpacity(m_iPad) ); + m_labelJukebox.setLabel( pGui->getJukeboxMessage(m_iPad) ); + } + else + { + for( unsigned int i = 0; i < CHAT_LINES_COUNT; ++i ) + { + m_controlLabelBackground[i].setOpacity(0); + m_labelChatText[i].setOpacity(0); + m_labelChatText[i].setLabel(L""); + } + m_labelJukebox.setOpacity( 0 ); + } + + //setVisible(anyVisible); +} + +void UIScene_HUD::repositionHud(S32 tileWidth, S32 tileHeight, F32 scale) +{ + if(!m_bSplitscreen) return; + + // Pass the visible tile area in SWF coordinates so ActionScript + // positions elements (crosshair, hotbar, etc.) centered in the + // actually visible region, not the raw viewport. + S32 visibleW = static_cast(tileWidth / scale); + S32 visibleH = static_cast(tileHeight / scale); + + app.DebugPrintf(app.USER_SR, "Reposition HUD: tile %dx%d, scale %.3f, visible SWF %dx%d\n", tileWidth, tileHeight, scale, visibleW, visibleH ); + + IggyDataValue result; + IggyDataValue value[2]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = visibleW; + value[1].type = IGGY_DATATYPE_number; + value[1].number = visibleH; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcRepositionHud , 2 , value ); +} + +void UIScene_HUD::ShowDisplayName(bool show) +{ + m_lastShowDisplayName = show; + m_labelDisplayName.setVisible(show); +} + +void UIScene_HUD::SetDisplayName(const wstring &displayName) +{ + if(displayName.compare(m_displayName) != 0) + { + m_displayName = displayName; + + IggyDataValue result; + IggyDataValue value[1]; + IggyStringUTF16 stringVal; + stringVal.string = (IggyUTF16*)displayName.c_str(); + stringVal.length = displayName.length(); + value[0].type = IGGY_DATATYPE_string_UTF16; + value[0].string16 = stringVal; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetDisplayName , 1 , value ); + + m_labelDisplayName.setVisible(m_lastShowDisplayName); + } +} + +void UIScene_HUD::SetTooltipsEnabled(bool bEnabled) +{ + if(m_bToolTipsVisible != bEnabled) + { + m_bToolTipsVisible = bEnabled; + + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_boolean; + value[0].boolval = bEnabled; + IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetTooltipsEnabled , 1 , value ); + } +} + +void UIScene_HUD::handleGameTick() +{ + if(getMovie() && app.GetGameStarted()) + { + Minecraft *pMinecraft = Minecraft::GetInstance(); + if(pMinecraft->localplayers[m_iPad] == nullptr || pMinecraft->localgameModes[m_iPad] == nullptr) + { + m_parentLayer->showComponent(m_iPad, eUIScene_HUD,false); + return; + } + m_parentLayer->showComponent(m_iPad, eUIScene_HUD,true); + + updateFrameTick(); + } } \ No newline at end of file diff --git a/Minecraft.Client/GameRenderer.cpp b/Minecraft.Client/GameRenderer.cpp index 169f1280b..73b39529c 100644 --- a/Minecraft.Client/GameRenderer.cpp +++ b/Minecraft.Client/GameRenderer.cpp @@ -954,91 +954,70 @@ float GameRenderer::ComputeGammaFromSlider(float slider0to100) void GameRenderer::CachePlayerGammas() { - for (int j = 0; j < XUSER_MAX_COUNT && j < NUM_LIGHT_TEXTURES; ++j) - { - std::shared_ptr player = Minecraft::GetInstance()->localplayers[j]; - if (!player) - { - m_cachedGammaPerPlayer[j] = 1.0f; - continue; - } + const float slider = app.GetGameSettings(ProfileManager.GetPrimaryPad(), eGameSetting_Gamma); + const float gamma = ComputeGammaFromSlider(slider); - const float slider = app.GetGameSettings(j, eGameSetting_Gamma); // 0..100 - m_cachedGammaPerPlayer[j] = ComputeGammaFromSlider(slider); - } + for (int j = 0; j < XUSER_MAX_COUNT && j < NUM_LIGHT_TEXTURES; ++j) + m_cachedGammaPerPlayer[j] = gamma; } bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport) const { - // Use the actual backbuffer dimensions so viewports adapt to window resize. extern int g_rScreenWidth; extern int g_rScreenHeight; - int active = 0; - int indexMap[NUM_LIGHT_TEXTURES] = {-1, -1, -1, -1}; - for (int i = 0; i < XUSER_MAX_COUNT && i < NUM_LIGHT_TEXTURES; ++i) - { - if (Minecraft::GetInstance()->localplayers[i]) - indexMap[active++] = i; - } - - if (active <= 1) - { - outViewport.TopLeftX = 0.0f; - outViewport.TopLeftY = 0.0f; - outViewport.Width = static_cast(g_rScreenWidth); - outViewport.Height = static_cast(g_rScreenHeight); - outViewport.MinDepth = 0.0f; - outViewport.MaxDepth = 1.0f; - return true; - } - - int k = -1; - for (int ord = 0; ord < active; ++ord) - if (indexMap[ord] == j) - { - k = ord; - break; - } - if (k < 0) + std::shared_ptr player = Minecraft::GetInstance()->localplayers[j]; + if (!player) return false; - const float width = static_cast(g_rScreenWidth); - const float height = static_cast(g_rScreenHeight); + const float w = static_cast(g_rScreenWidth); + const float h = static_cast(g_rScreenHeight); + const float halfW = w * 0.5f; + const float halfH = h * 0.5f; - if (active == 2) + outViewport.MinDepth = 0.0f; + outViewport.MaxDepth = 1.0f; + + switch (static_cast(player->m_iScreenSection)) { - const float halfH = height * 0.5f; - outViewport.TopLeftX = 0.0f; - outViewport.Width = width; - outViewport.MinDepth = 0.0f; - outViewport.MaxDepth = 1.0f; - if (k == 0) - { - outViewport.TopLeftY = 0.0f; - outViewport.Height = halfH; - } - else - { - outViewport.TopLeftY = halfH; - outViewport.Height = halfH; - } - return true; - } - else - { - const float halfW = width * 0.5f; - const float halfH = height * 0.5f; - const int row = (k >= 2) ? 1 : 0; - const int col = (k % 2); - outViewport.TopLeftX = col ? halfW : 0.0f; - outViewport.TopLeftY = row ? halfH : 0.0f; - outViewport.Width = halfW; - outViewport.Height = halfH; - outViewport.MinDepth = 0.0f; - outViewport.MaxDepth = 1.0f; - return true; + case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: + outViewport.TopLeftX = 0; outViewport.TopLeftY = 0; + outViewport.Width = w; outViewport.Height = halfH; + break; + case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: + outViewport.TopLeftX = 0; outViewport.TopLeftY = halfH; + outViewport.Width = w; outViewport.Height = halfH; + break; + case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: + outViewport.TopLeftX = 0; outViewport.TopLeftY = 0; + outViewport.Width = halfW; outViewport.Height = h; + break; + case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT: + outViewport.TopLeftX = halfW; outViewport.TopLeftY = 0; + outViewport.Width = halfW; outViewport.Height = h; + break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT: + outViewport.TopLeftX = 0; outViewport.TopLeftY = 0; + outViewport.Width = halfW; outViewport.Height = halfH; + break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: + outViewport.TopLeftX = halfW; outViewport.TopLeftY = 0; + outViewport.Width = halfW; outViewport.Height = halfH; + break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: + outViewport.TopLeftX = 0; outViewport.TopLeftY = halfH; + outViewport.Width = halfW; outViewport.Height = halfH; + break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: + outViewport.TopLeftX = halfW; outViewport.TopLeftY = halfH; + outViewport.Width = halfW; outViewport.Height = halfH; + break; + default: + outViewport.TopLeftX = 0; outViewport.TopLeftY = 0; + outViewport.Width = w; outViewport.Height = h; + break; } + return true; } uint32_t GameRenderer::BuildPlayerViewports(D3D11_VIEWPORT *outViewports, float *outGammas, UINT maxCount) const diff --git a/Minecraft.Client/Windows64/PostProcesser.cpp b/Minecraft.Client/Windows64/PostProcesser.cpp index 4c55d3d98..aabb1f679 100644 --- a/Minecraft.Client/Windows64/PostProcesser.cpp +++ b/Minecraft.Client/Windows64/PostProcesser.cpp @@ -21,13 +21,17 @@ const char* PostProcesser::g_gammaPSCode = "cbuffer GammaCB : register(b0)\n" "{\n" " float gamma;\n" - " float3 pad;\n" + " float _pad;\n" + " float2 uvOffset;\n" + " float2 uvScale;\n" + " float2 _pad2;\n" "};\n" "Texture2D sceneTex : register(t0);\n" "SamplerState sceneSampler : register(s0);\n" "float4 main(float4 pos : SV_Position, float2 uv : TEXCOORD0) : SV_Target\n" "{\n" - " float4 color = sceneTex.Sample(sceneSampler, uv);\n" + " float2 texUV = uvOffset + uv * uvScale;\n" + " float4 color = sceneTex.Sample(sceneSampler, texUV);\n" "\n" " color.rgb = max(color.rgb, 0.0);\n" "\n" @@ -158,7 +162,7 @@ void PostProcesser::Init() cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - GammaCBData initData = {1.0f, {0, 0, 0}}; + GammaCBData initData = {1.0f, 0, 0.0f, 0.0f, 1.0f, 1.0f, {0, 0}}; D3D11_SUBRESOURCE_DATA srData; srData.pSysMem = &initData; srData.SysMemPitch = 0; @@ -237,6 +241,10 @@ void PostProcesser::Apply() const { GammaCBData *cb = static_cast(mapped.pData); cb->gamma = m_gamma; + cb->uvOffsetX = 0.0f; + cb->uvOffsetY = 0.0f; + cb->uvScaleX = 1.0f; + cb->uvScaleY = 1.0f; ctx->Unmap(m_pGammaCB, 0); } @@ -333,27 +341,6 @@ void PostProcesser::ApplyFromCopied() const ID3D11DeviceContext* ctx = g_pImmediateContext; - D3D11_MAPPED_SUBRESOURCE mapped; - const D3D11_MAP mapType = m_wineMode ? D3D11_MAP_WRITE_NO_OVERWRITE : D3D11_MAP_WRITE_DISCARD; - const HRESULT hr = ctx->Map(m_pGammaCB, 0, mapType, 0, &mapped); - if (SUCCEEDED(hr)) - { - const auto cb = static_cast(mapped.pData); - cb->gamma = m_gamma; - ctx->Unmap(m_pGammaCB, 0); - } - - ID3D11RenderTargetView* oldRTV = nullptr; - ID3D11DepthStencilView* oldDSV = nullptr; - ctx->OMGetRenderTargets(1, &oldRTV, &oldDSV); - - UINT numViewports = 1; - D3D11_VIEWPORT oldViewport = {}; - ctx->RSGetViewports(&numViewports, &oldViewport); - - ID3D11RenderTargetView* bbRTV = g_pRenderTargetView; - ctx->OMSetRenderTargets(1, &bbRTV, nullptr); - D3D11_VIEWPORT vp; if (m_useCustomViewport) { @@ -369,6 +356,33 @@ void PostProcesser::ApplyFromCopied() const vp.TopLeftY = 0; } + D3D11_MAPPED_SUBRESOURCE mapped; + const D3D11_MAP mapType = m_wineMode ? D3D11_MAP_WRITE_NO_OVERWRITE : D3D11_MAP_WRITE_DISCARD; + const HRESULT hr = ctx->Map(m_pGammaCB, 0, mapType, 0, &mapped); + if (SUCCEEDED(hr)) + { + const auto cb = static_cast(mapped.pData); + cb->gamma = m_gamma; + const float texW = static_cast(m_gammaTexWidth); + const float texH = static_cast(m_gammaTexHeight); + cb->uvOffsetX = vp.TopLeftX / texW; + cb->uvOffsetY = vp.TopLeftY / texH; + cb->uvScaleX = vp.Width / texW; + cb->uvScaleY = vp.Height / texH; + ctx->Unmap(m_pGammaCB, 0); + } + + ID3D11RenderTargetView* oldRTV = nullptr; + ID3D11DepthStencilView* oldDSV = nullptr; + ctx->OMGetRenderTargets(1, &oldRTV, &oldDSV); + + UINT numViewports = 1; + D3D11_VIEWPORT oldViewport = {}; + ctx->RSGetViewports(&numViewports, &oldViewport); + + ID3D11RenderTargetView* bbRTV = g_pRenderTargetView; + ctx->OMSetRenderTargets(1, &bbRTV, nullptr); + ctx->RSSetViewports(1, &vp); ctx->IASetInputLayout(nullptr); From 7a4f57e3e692c621c20e58bc658d321842872de5 Mon Sep 17 00:00:00 2001 From: MrTheShy <49885496+MrTheShy@users.noreply.github.com> Date: Mon, 9 Mar 2026 05:10:00 +0100 Subject: [PATCH 5/9] Reject duplicate UIDs on login and remove noisy gdraw debug log (#1013) Players joining a server with a UID already in use are now disconnected instead of the existing player being force-kicked. The previous behaviour introduced in #767 allowed two clients with the same UID to coexist, causing invisible players and undefined behaviour. Also removes a per-frame debug printf in gdraw_SetViewSizeAndWorldScale that was left in from earlier resolution-fix work. --- Minecraft.Client/PendingConnection.cpp | 33 +++---------------- .../Iggy/gdraw/gdraw_d3d1x_shared.inl | 6 +--- 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/Minecraft.Client/PendingConnection.cpp b/Minecraft.Client/PendingConnection.cpp index 0a0f3c44b..6d5497f02 100644 --- a/Minecraft.Client/PendingConnection.cpp +++ b/Minecraft.Client/PendingConnection.cpp @@ -190,34 +190,11 @@ void PendingConnection::handleLogin(shared_ptr packet) } else if (duplicateXuid) { - // 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 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"); + // Reject the incoming connection — a player with this UID is already + // on the server. Allowing duplicates causes invisible players and + // other undefined behaviour. + app.DebugPrintf("LOGIN: Rejecting duplicate xuid for name: %ls\n", name.c_str()); + disconnect(DisconnectPacket::eDisconnect_Banned); } #ifdef _WINDOWS64 else if (g_bRejectDuplicateNames) diff --git a/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl b/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl index 0722308ac..62e97e0fe 100644 --- a/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl +++ b/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl @@ -998,11 +998,7 @@ static void RADLINK gdraw_SetViewSizeAndWorldScale(S32 w, S32 h, F32 scalex, F32 { 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; From 0c4f4599045edad935403e4d79d28f6b9aa95833 Mon Sep 17 00:00:00 2001 From: Loki Rautio Date: Mon, 9 Mar 2026 03:25:05 -0500 Subject: [PATCH 6/9] Always show version overlay, add more info --- Minecraft.Client/ClientConstants.cpp | 3 +- Minecraft.Client/ClientConstants.h | 1 + Minecraft.Client/Common/BuildVer.h | 3 +- Minecraft.Client/Gui.cpp | 212 ++++++++++++++------------- Minecraft.Client/prebuild.ps1 | 18 ++- 5 files changed, 130 insertions(+), 107 deletions(-) diff --git a/Minecraft.Client/ClientConstants.cpp b/Minecraft.Client/ClientConstants.cpp index 43da4360b..d1d80aa9e 100644 --- a/Minecraft.Client/ClientConstants.cpp +++ b/Minecraft.Client/ClientConstants.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" #include "ClientConstants.h" -const wstring ClientConstants::VERSION_STRING = wstring(L"Minecraft LCE ") + VER_FILEVERSION_STR_W;//+ SharedConstants::VERSION_STRING; \ No newline at end of file +const wstring ClientConstants::VERSION_STRING = wstring(L"Minecraft LCE ") + VER_FILEVERSION_STR_W;//+ SharedConstants::VERSION_STRING; +const wstring ClientConstants::BRANCH_STRING = VER_BRANCHVERSION_STR_W; diff --git a/Minecraft.Client/ClientConstants.h b/Minecraft.Client/ClientConstants.h index 82bba386f..1476e51bf 100644 --- a/Minecraft.Client/ClientConstants.h +++ b/Minecraft.Client/ClientConstants.h @@ -13,6 +13,7 @@ class ClientConstants // INTERNAL DEVELOPMENT SETTINGS public: static const wstring VERSION_STRING; + static const wstring BRANCH_STRING; static const bool DEADMAU5_CAMERA_CHEATS = false; }; \ No newline at end of file diff --git a/Minecraft.Client/Common/BuildVer.h b/Minecraft.Client/Common/BuildVer.h index babe75660..eaa77d26a 100644 --- a/Minecraft.Client/Common/BuildVer.h +++ b/Minecraft.Client/Common/BuildVer.h @@ -1,6 +1,7 @@ #pragma once #define VER_PRODUCTBUILD 560 -#define VER_PRODUCTVERSION_STR_W L"DEV (unknown)" +#define VER_PRODUCTVERSION_STR_W L"DEV (unknown version)" #define VER_FILEVERSION_STR_W VER_PRODUCTVERSION_STR_W +#define VER_BRANCHVERSION_STR_W L"UNKNOWN BRANCH" #define VER_NETWORK VER_PRODUCTBUILD diff --git a/Minecraft.Client/Gui.cpp b/Minecraft.Client/Gui.cpp index d3bcda9bc..43b41998b 100644 --- a/Minecraft.Client/Gui.cpp +++ b/Minecraft.Client/Gui.cpp @@ -851,7 +851,10 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) #ifndef _FINAL_BUILD MemSect(31); - if (minecraft->options->renderDebug && minecraft->player != nullptr && minecraft->level != nullptr) + + // temporarily render overlay at all times so version is more obvious in bug reports + // we can turn this off once things stabilize + if (true)// minecraft->options->renderDebug && minecraft->player != nullptr && minecraft->level != nullptr) { const int debugLeft = 1; const int debugTop = 1; @@ -870,117 +873,120 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) vector lines; lines.push_back(ClientConstants::VERSION_STRING); - lines.push_back(minecraft->fpsString); - lines.push_back(L"E: " + std::to_wstring(minecraft->level->getAllEntities().size())); // Could maybe use entity::shouldRender to work out how many are rendered but thats like expensive - // TODO Add server information with packet counts - once multiplayer is more stable - int renderDistance = app.GetGameSettings(iPad, eGameSetting_RenderDistance); - // Calculate the chunk sections using 16 * (2n + 1)^2 - lines.push_back(L"C: " + std::to_wstring(16 * (2 * renderDistance + 1) * (2 * renderDistance + 1)) + L" D: " + std::to_wstring(renderDistance)); - lines.push_back(minecraft->gatherStats4()); // Chunk Cache - - // Dimension - wstring dimension = L"unknown"; - switch (minecraft->player->dimension) + lines.push_back(ClientConstants::BRANCH_STRING); + if (minecraft->options->renderDebug && minecraft->player != nullptr && minecraft->level != nullptr) { - case -1: - dimension = L"minecraft:the_nether"; - break; - case 0: - dimension = L"minecraft:overworld"; - break; - case 1: - dimension = L"minecraft:the_end"; - break; - } - lines.push_back(dimension); + lines.push_back(minecraft->fpsString); + lines.push_back(L"E: " + std::to_wstring(minecraft->level->getAllEntities().size())); // Could maybe use entity::shouldRender to work out how many are rendered but thats like expensive + // TODO Add server information with packet counts - once multiplayer is more stable + int renderDistance = app.GetGameSettings(iPad, eGameSetting_RenderDistance); + // Calculate the chunk sections using 16 * (2n + 1)^2 + lines.push_back(L"C: " + std::to_wstring(16 * (2 * renderDistance + 1) * (2 * renderDistance + 1)) + L" D: " + std::to_wstring(renderDistance)); + lines.push_back(minecraft->gatherStats4()); // Chunk Cache - lines.push_back(L""); // Spacer + // Dimension + wstring dimension = L"unknown"; + switch (minecraft->player->dimension) + { + case -1: + dimension = L"minecraft:the_nether"; + break; + case 0: + dimension = L"minecraft:overworld"; + break; + case 1: + dimension = L"minecraft:the_end"; + break; + } + lines.push_back(dimension); - // Players block pos - int xBlockPos = Mth::floor(minecraft->player->x); - int yBlockPos = Mth::floor(minecraft->player->y); - int zBlockPos = Mth::floor(minecraft->player->z); + lines.push_back(L""); // Spacer - // Chunk player is in - int xChunkPos = xBlockPos >> 4; - int yChunkPos = yBlockPos >> 4; - int zChunkPos = zBlockPos >> 4; + // Players block pos + int xBlockPos = Mth::floor(minecraft->player->x); + int yBlockPos = Mth::floor(minecraft->player->y); + int zBlockPos = Mth::floor(minecraft->player->z); - // Players offset within the chunk - int xChunkOffset = xBlockPos & 15; - int yChunkOffset = yBlockPos & 15; - int zChunkOffset = zBlockPos & 15; + // Chunk player is in + int xChunkPos = xBlockPos >> 4; + int yChunkPos = yBlockPos >> 4; + int zChunkPos = zBlockPos >> 4; - // Format the position like java with limited decumal places - WCHAR posString[44]; // Allows upto 7 digit positions (+-9_999_999) - swprintf(posString, 44, L"%.3f / %.5f / %.3f", minecraft->player->x, minecraft->player->y, minecraft->player->z); + // Players offset within the chunk + int xChunkOffset = xBlockPos & 15; + int yChunkOffset = yBlockPos & 15; + int zChunkOffset = zBlockPos & 15; - lines.push_back(L"XYZ: " + std::wstring(posString)); - lines.push_back(L"Block: " + std::to_wstring(static_cast(xBlockPos)) + L" " + std::to_wstring(static_cast(yBlockPos)) + L" " + std::to_wstring(static_cast(zBlockPos))); - lines.push_back(L"Chunk: " + std::to_wstring(xChunkOffset) + L" " + std::to_wstring(yChunkOffset) + L" " + std::to_wstring(zChunkOffset) + L" in " + std::to_wstring(xChunkPos) + L" " + std::to_wstring(yChunkPos) + L" " + std::to_wstring(zChunkPos)); + // Format the position like java with limited decumal places + WCHAR posString[44]; // Allows upto 7 digit positions (+-9_999_999) + swprintf(posString, 44, L"%.3f / %.5f / %.3f", minecraft->player->x, minecraft->player->y, minecraft->player->z); - // Wrap the yRot to 360 then adjust to (-180 to 180) range to match java - float yRotDisplay = fmod(minecraft->player->yRot, 360.0f); - if (yRotDisplay > 180.0f) - { - yRotDisplay -= 360.0f; + lines.push_back(L"XYZ: " + std::wstring(posString)); + lines.push_back(L"Block: " + std::to_wstring(static_cast(xBlockPos)) + L" " + std::to_wstring(static_cast(yBlockPos)) + L" " + std::to_wstring(static_cast(zBlockPos))); + lines.push_back(L"Chunk: " + std::to_wstring(xChunkOffset) + L" " + std::to_wstring(yChunkOffset) + L" " + std::to_wstring(zChunkOffset) + L" in " + std::to_wstring(xChunkPos) + L" " + std::to_wstring(yChunkPos) + L" " + std::to_wstring(zChunkPos)); + + // Wrap the yRot to 360 then adjust to (-180 to 180) range to match java + float yRotDisplay = fmod(minecraft->player->yRot, 360.0f); + if (yRotDisplay > 180.0f) + { + yRotDisplay -= 360.0f; + } + if (yRotDisplay < -180.0f) + { + yRotDisplay += 360.0f; + } + // Generate the angle string in the format "yRot / xRot" with one decimal place, similar to java edition + WCHAR angleString[16]; + swprintf(angleString, 16, L"%.1f / %.1f", yRotDisplay, minecraft->player->xRot); + + // Work out the named direction + int direction = Mth::floor(minecraft->player->yRot * 4.0f / 360.0f + 0.5) & 0x3; + wstring cardinalDirection; + switch (direction) + { + case 0: + cardinalDirection = L"south"; + break; + case 1: + cardinalDirection = L"west"; + break; + case 2: + cardinalDirection = L"north"; + break; + case 3: + cardinalDirection = L"east"; + break; + } + + lines.push_back(L"Facing: " + cardinalDirection + L" (" + angleString + L")"); + + // We have to limit y to 256 as we don't get any information past that + if (minecraft->level != NULL && minecraft->level->hasChunkAt(xBlockPos, fmod(yBlockPos, 256), zBlockPos)) + { + LevelChunk *chunkAt = minecraft->level->getChunkAt(xBlockPos, zBlockPos); + if (chunkAt != NULL) + { + int skyLight = chunkAt->getBrightness(LightLayer::Sky, xChunkOffset, yChunkOffset, zChunkOffset); + int blockLight = chunkAt->getBrightness(LightLayer::Block, xChunkOffset, yChunkOffset, zChunkOffset); + int maxLight = fmax(skyLight, blockLight); + lines.push_back(L"Light: " + std::to_wstring(maxLight) + L" (" + std::to_wstring(skyLight) + L" sky, " + std::to_wstring(blockLight) + L" block)"); + + lines.push_back(L"CH S: " + std::to_wstring(chunkAt->getHeightmap(xChunkOffset, zChunkOffset))); + + Biome *biome = chunkAt->getBiome(xChunkOffset, zChunkOffset, minecraft->level->getBiomeSource()); + lines.push_back(L"Biome: " + biome->m_name + L" (" + std::to_wstring(biome->id) + L")"); + + lines.push_back(L"Difficulty: " + std::to_wstring(minecraft->level->difficulty) + L" (Day " + std::to_wstring(minecraft->level->getGameTime() / Level::TICKS_PER_DAY) + L")"); + } + } + + // This is all LCE only stuff, it was never on java + lines.push_back(L""); // Spacer + lines.push_back(L"Seed: " + std::to_wstring(minecraft->level->getLevelData()->getSeed())); + lines.push_back(minecraft->gatherStats1()); // Time to autosave + lines.push_back(minecraft->gatherStats2()); // Empty currently - CPlatformNetworkManagerStub::GatherStats() + lines.push_back(minecraft->gatherStats3()); // RTT } - if (yRotDisplay < -180.0f) - { - yRotDisplay += 360.0f; - } - // Generate the angle string in the format "yRot / xRot" with one decimal place, similar to java edition - WCHAR angleString[16]; - swprintf(angleString, 16, L"%.1f / %.1f", yRotDisplay, minecraft->player->xRot); - - // Work out the named direction - int direction = Mth::floor(minecraft->player->yRot * 4.0f / 360.0f + 0.5) & 0x3; - wstring cardinalDirection; - switch (direction) - { - case 0: - cardinalDirection = L"south"; - break; - case 1: - cardinalDirection = L"west"; - break; - case 2: - cardinalDirection = L"north"; - break; - case 3: - cardinalDirection = L"east"; - break; - } - - lines.push_back(L"Facing: " + cardinalDirection + L" (" + angleString + L")"); - - // We have to limit y to 256 as we don't get any information past that - if (minecraft->level != NULL && minecraft->level->hasChunkAt(xBlockPos, fmod(yBlockPos, 256), zBlockPos)) - { - LevelChunk *chunkAt = minecraft->level->getChunkAt(xBlockPos, zBlockPos); - if (chunkAt != NULL) - { - int skyLight = chunkAt->getBrightness(LightLayer::Sky, xChunkOffset, yChunkOffset, zChunkOffset); - int blockLight = chunkAt->getBrightness(LightLayer::Block, xChunkOffset, yChunkOffset, zChunkOffset); - int maxLight = fmax(skyLight, blockLight); - lines.push_back(L"Light: " + std::to_wstring(maxLight) + L" (" + std::to_wstring(skyLight) + L" sky, " + std::to_wstring(blockLight) + L" block)"); - - lines.push_back(L"CH S: " + std::to_wstring(chunkAt->getHeightmap(xChunkOffset, zChunkOffset))); - - Biome *biome = chunkAt->getBiome(xChunkOffset, zChunkOffset, minecraft->level->getBiomeSource()); - lines.push_back(L"Biome: " + biome->m_name + L" (" + std::to_wstring(biome->id) + L")"); - - lines.push_back(L"Difficulty: " + std::to_wstring(minecraft->level->difficulty) + L" (Day " + std::to_wstring(minecraft->level->getGameTime() / Level::TICKS_PER_DAY) + L")"); - } - } - - - // This is all LCE only stuff, it was never on java - lines.push_back(L""); // Spacer - lines.push_back(L"Seed: " + std::to_wstring(minecraft->level->getLevelData()->getSeed())); - lines.push_back(minecraft->gatherStats1()); // Time to autosave - lines.push_back(minecraft->gatherStats2()); // Empty currently - CPlatformNetworkManagerStub::GatherStats() - lines.push_back(minecraft->gatherStats3()); // RTT #ifdef _DEBUG // Only show terrain features in debug builds not release // TERRAIN FEATURES diff --git a/Minecraft.Client/prebuild.ps1 b/Minecraft.Client/prebuild.ps1 index fa2c487c5..0acbf023b 100644 --- a/Minecraft.Client/prebuild.ps1 +++ b/Minecraft.Client/prebuild.ps1 @@ -1,5 +1,18 @@ $sha = (git rev-parse --short=7 HEAD) -$ref = (git symbolic-ref --short HEAD) + +if ($env:GITHUB_REPOSITORY) { + $ref = "$env:GITHUB_REPOSITORY/$(git symbolic-ref --short HEAD)" +} else { + $remoteUrl = (git remote get-url origin) + # handle github urls only, can't predict other origins behavior + if ($remoteUrl -match '(?:github\.com[:/])([^/:]+/[^/]+?)(?:\.git)?$') { + $ref = "$($matches[1])/$(git symbolic-ref --short HEAD)" + }else{ + # fallback to just symbolic ref in case remote isnt what we expect + $ref = "UNKNOWN/$(git symbolic-ref --short HEAD)" + } +} + $build = 560 # Note: Build/network has to stay static for now, as without it builds wont be able to play together. We can change it later when we have a better versioning scheme in place. $suffix = "" @@ -18,7 +31,8 @@ if (git status --porcelain) { #pragma once #define VER_PRODUCTBUILD $build -#define VER_PRODUCTVERSION_STR_W L"$sha$suffix ($ref)" +#define VER_PRODUCTVERSION_STR_W L"$sha$suffix" #define VER_FILEVERSION_STR_W VER_PRODUCTVERSION_STR_W +#define VER_BRANCHVERSION_STR_W L"$ref" #define VER_NETWORK VER_PRODUCTBUILD "@ | Set-Content "Common/BuildVer.h" From d557ca2dfba5ffcca99ceb41b07d149f871964b5 Mon Sep 17 00:00:00 2001 From: Loki Rautio Date: Mon, 9 Mar 2026 04:45:14 -0500 Subject: [PATCH 7/9] LCEMP RCE fixes Based on commit d017bfc30a68888bf5c79b23cf5c4f607cf828bf --- Minecraft.World/AwardStatPacket.cpp | 2 +- Minecraft.World/BlockRegionUpdatePacket.cpp | 6 +++ Minecraft.World/ByteArrayInputStream.cpp | 13 ++++- Minecraft.World/ByteArrayOutputStream.cpp | 18 ++++++- Minecraft.World/ByteArrayTag.h | 1 + Minecraft.World/ComplexItemDataPacket.cpp | 7 ++- Minecraft.World/CompoundTag.h | 14 ++++-- Minecraft.World/Connection.cpp | 4 +- Minecraft.World/ContainerSetContentPacket.cpp | 3 ++ Minecraft.World/ContainerSetSlotPacket.cpp | 2 +- Minecraft.World/CustomPayloadPacket.cpp | 2 +- Minecraft.World/DataInputStream.cpp | 4 ++ Minecraft.World/ExplodePacket.cpp | 2 + Minecraft.World/GameCommandPacket.cpp | 2 +- Minecraft.World/IntArrayTag.h | 2 + Minecraft.World/ListTag.h | 11 ++-- Minecraft.World/Packet.cpp | 50 ++++++++----------- Minecraft.World/PreLoginPacket.cpp | 1 + Minecraft.World/RemoveEntitiesPacket.cpp | 4 +- Minecraft.World/Socket.cpp | 5 ++ Minecraft.World/SynchedEntityData.cpp | 6 ++- Minecraft.World/Tag.cpp | 39 +++++++++++++-- Minecraft.World/TextureAndGeometryPacket.cpp | 29 ++++++++++- Minecraft.World/TexturePacket.cpp | 25 +++++++--- Minecraft.World/ThreadName.cpp | 6 +-- .../UpdateGameRuleProgressPacket.cpp | 2 +- Minecraft.World/compression.cpp | 42 +++++++++++++--- 27 files changed, 222 insertions(+), 80 deletions(-) diff --git a/Minecraft.World/AwardStatPacket.cpp b/Minecraft.World/AwardStatPacket.cpp index 07888541f..b2950a0ef 100644 --- a/Minecraft.World/AwardStatPacket.cpp +++ b/Minecraft.World/AwardStatPacket.cpp @@ -47,7 +47,7 @@ void AwardStatPacket::read(DataInputStream *dis) //throws IOException // Read parameter blob. int length = dis->readInt(); - if(length > 0) + if (length > 0 && length <= 65536) { m_paramData = byteArray(length); dis->readFully(m_paramData); diff --git a/Minecraft.World/BlockRegionUpdatePacket.cpp b/Minecraft.World/BlockRegionUpdatePacket.cpp index 730ce3edc..b91cf4f82 100644 --- a/Minecraft.World/BlockRegionUpdatePacket.cpp +++ b/Minecraft.World/BlockRegionUpdatePacket.cpp @@ -105,6 +105,12 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException levelIdx = ( size >> 30 ) & 3; size &= 0x3fffffff; + const int MAX_COMPRESSED_CHUNK_SIZE = 5 * 1024 * 1024; + if (size < 0 || size > MAX_COMPRESSED_CHUNK_SIZE) + { + size = 0; + } + if(size == 0) { buffer = byteArray(); diff --git a/Minecraft.World/ByteArrayInputStream.cpp b/Minecraft.World/ByteArrayInputStream.cpp index d79eff362..9509206d4 100644 --- a/Minecraft.World/ByteArrayInputStream.cpp +++ b/Minecraft.World/ByteArrayInputStream.cpp @@ -10,8 +10,19 @@ //offset - the offset in the buffer of the first byte to read. //length - the maximum number of bytes to read from the buffer. ByteArrayInputStream::ByteArrayInputStream(byteArray buf, unsigned int offset, unsigned int length) - : pos( offset ), count( min( offset+length, buf.length ) ), mark( offset ) + : pos(offset), mark(offset) { + if (offset > buf.length) + { + count = buf.length; + } + else if (length > buf.length - offset) + { + count = buf.length; + } + else + { + count = offset + length; this->buf = buf; } diff --git a/Minecraft.World/ByteArrayOutputStream.cpp b/Minecraft.World/ByteArrayOutputStream.cpp index a9f36e04a..a6fdad8f6 100644 --- a/Minecraft.World/ByteArrayOutputStream.cpp +++ b/Minecraft.World/ByteArrayOutputStream.cpp @@ -53,9 +53,25 @@ void ByteArrayOutputStream::write(byteArray b, unsigned int offset, unsigned int { assert( b.length >= offset + length ); + if (offset > b.length || length > b.length - offset) + { + return; + } + + if (length > 0xFFFFFFFF - count) + { + return; + // If we will fill the buffer we need to make it bigger if( count + length >= buf.length ) - buf.resize( max( count + length + 1, buf.length * 2 ) ); + { + unsigned int newSize = (std::max)(count + length + 1, buf.length * 2); + if (newSize <= buf.length) + { + return; + } + buf.resize(newSize); + } XMemCpy( &buf[count], &b[offset], length ); //std::copy( b->data+offset, b->data+offset+length, buf->data + count ); // Or this instead? diff --git a/Minecraft.World/ByteArrayTag.h b/Minecraft.World/ByteArrayTag.h index 1d3e38755..4d53c63ea 100644 --- a/Minecraft.World/ByteArrayTag.h +++ b/Minecraft.World/ByteArrayTag.h @@ -21,6 +21,7 @@ public: void load(DataInput *dis, int tagDepth) { int length = dis->readInt(); + if (length < 0 || length > 2 * 1024 * 1024) length = 0; if ( data.data ) delete[] data.data; data = byteArray(length); diff --git a/Minecraft.World/ComplexItemDataPacket.cpp b/Minecraft.World/ComplexItemDataPacket.cpp index 38c39cb65..98eeb9dee 100644 --- a/Minecraft.World/ComplexItemDataPacket.cpp +++ b/Minecraft.World/ComplexItemDataPacket.cpp @@ -32,7 +32,12 @@ void ComplexItemDataPacket::read(DataInputStream *dis) //throws IOException itemType = dis->readShort(); itemId = dis->readShort(); - data = charArray(dis->readUnsignedShort() & 0xffff); + int dataLength = dis->readShort() & 0xffff; + if (dataLength > 32767) + { + dataLength = 0; + } + data = charArray(dataLength); dis->readFully(data); } diff --git a/Minecraft.World/CompoundTag.h b/Minecraft.World/CompoundTag.h index 5eaa2a05d..8f4d045df 100644 --- a/Minecraft.World/CompoundTag.h +++ b/Minecraft.World/CompoundTag.h @@ -42,10 +42,16 @@ public: } tags.clear(); Tag *tag; - while ((tag = Tag::readNamedTag(dis))->getId() != Tag::TAG_End) - { - tags[tag->getName()] = tag; - } + int tagCount = 0; + const int MAX_COMPOUND_TAGS = 10000; + while ((tag = Tag::readNamedTag(dis))->getId() != Tag::TAG_End) + { + tags[tag->getName()] = tag; + if (++tagCount >= MAX_COMPOUND_TAGS) + { + break; + } + } delete tag; } diff --git a/Minecraft.World/Connection.cpp b/Minecraft.World/Connection.cpp index 7f10dc4a7..d44502667 100644 --- a/Minecraft.World/Connection.cpp +++ b/Minecraft.World/Connection.cpp @@ -108,8 +108,8 @@ Connection::Connection(Socket *socket, const wstring& id, PacketListener *packet const char *szId = wstringtofilename(id); char readThreadName[256]; char writeThreadName[256]; - sprintf(readThreadName,"%s read\n",szId); - sprintf(writeThreadName,"%s write\n",szId); + sprintf_s(readThreadName, sizeof(readThreadName), "%.240s read\n", szId); + sprintf_s(writeThreadName, sizeof(writeThreadName), "%.240s write\n", szId); readThread = new C4JThread(runRead, static_cast(this), readThreadName, READ_STACK_SIZE); writeThread = new C4JThread(runWrite, this, writeThreadName, WRITE_STACK_SIZE); diff --git a/Minecraft.World/ContainerSetContentPacket.cpp b/Minecraft.World/ContainerSetContentPacket.cpp index ae76e0f05..28ce64e9c 100644 --- a/Minecraft.World/ContainerSetContentPacket.cpp +++ b/Minecraft.World/ContainerSetContentPacket.cpp @@ -32,6 +32,9 @@ void ContainerSetContentPacket::read(DataInputStream *dis) //throws IOException { containerId = dis->readByte(); int count = dis->readShort(); + + if (count < 0 || count > 256) count = 0; + items = ItemInstanceArray(count); for (int i = 0; i < count; i++) { diff --git a/Minecraft.World/ContainerSetSlotPacket.cpp b/Minecraft.World/ContainerSetSlotPacket.cpp index 6a0382442..de8d26804 100644 --- a/Minecraft.World/ContainerSetSlotPacket.cpp +++ b/Minecraft.World/ContainerSetSlotPacket.cpp @@ -35,7 +35,7 @@ void ContainerSetSlotPacket::read(DataInputStream *dis) //throws IOException // 4J Stu - TU-1 hotfix // Fix for #13142 - Holding down the A button on the furnace ingredient slot causes the UI to display incorrect item counts BYTE byteId = dis->readByte(); - containerId = *(char *)&byteId; + containerId = (char)(signed char)byteId; slot = dis->readShort(); item = readItem(dis); } diff --git a/Minecraft.World/CustomPayloadPacket.cpp b/Minecraft.World/CustomPayloadPacket.cpp index 46fde5aaf..e86f01de0 100644 --- a/Minecraft.World/CustomPayloadPacket.cpp +++ b/Minecraft.World/CustomPayloadPacket.cpp @@ -43,7 +43,7 @@ void CustomPayloadPacket::read(DataInputStream *dis) identifier = readUtf(dis, 20); length = dis->readShort(); - if (length > 0 && length < Short::MAX_VALUE) + if (length > 0 && length <= Short::MAX_VALUE) { if(data.data != nullptr) { diff --git a/Minecraft.World/DataInputStream.cpp b/Minecraft.World/DataInputStream.cpp index 4e4f5cd1b..0cd21a7e9 100644 --- a/Minecraft.World/DataInputStream.cpp +++ b/Minecraft.World/DataInputStream.cpp @@ -303,6 +303,10 @@ wstring DataInputStream::readUTF() int b = stream->read(); unsigned short UTFLength = static_cast(((a & 0xff) << 8) | (b & 0xff)); + const unsigned short MAX_UTF_LENGTH = 32767; + if (UTFLength > MAX_UTF_LENGTH) + return outputString; + //// 4J Stu - I decided while writing DataOutputStream that we didn't need to bother using the UTF8 format //// used in the java libs, and just write in/out as wchar_t all the time diff --git a/Minecraft.World/ExplodePacket.cpp b/Minecraft.World/ExplodePacket.cpp index 07626161f..88bf00c69 100644 --- a/Minecraft.World/ExplodePacket.cpp +++ b/Minecraft.World/ExplodePacket.cpp @@ -52,6 +52,8 @@ void ExplodePacket::read(DataInputStream *dis) //throws IOException r = dis->readFloat(); int count = dis->readInt(); + if (count < 0 || count > 32768) count = 0; + int xp = static_cast(x); int yp = static_cast(y); int zp = static_cast(z); diff --git a/Minecraft.World/GameCommandPacket.cpp b/Minecraft.World/GameCommandPacket.cpp index 17c5316af..ada5b04cd 100644 --- a/Minecraft.World/GameCommandPacket.cpp +++ b/Minecraft.World/GameCommandPacket.cpp @@ -40,7 +40,7 @@ void GameCommandPacket::read(DataInputStream *dis) command = static_cast(dis->readInt()); length = dis->readShort(); - if (length > 0 && length < Short::MAX_VALUE) + if (length > 0 && length <= Short::MAX_VALUE) { if(data.data != nullptr) { diff --git a/Minecraft.World/IntArrayTag.h b/Minecraft.World/IntArrayTag.h index 61ca59d09..64a4d004c 100644 --- a/Minecraft.World/IntArrayTag.h +++ b/Minecraft.World/IntArrayTag.h @@ -35,6 +35,8 @@ public: void load(DataInput *dis, int tagDepth) { int length = dis->readInt(); + if (length < 0 || length > 65536) + length = 0; if ( data.data ) delete[] data.data; data = intArray(length); diff --git a/Minecraft.World/ListTag.h b/Minecraft.World/ListTag.h index 2a0fdebc3..f6e1c6a0a 100644 --- a/Minecraft.World/ListTag.h +++ b/Minecraft.World/ListTag.h @@ -26,21 +26,16 @@ public: void load(DataInput *dis, int tagDepth) { - if (tagDepth > MAX_DEPTH) - { -#ifndef _CONTENT_PACKAGE - printf("Tried to read NBT tag with too high complexity, depth > %d", MAX_DEPTH); - __debugbreak(); -#endif - return; - } type = dis->readByte(); int size = dis->readInt(); + if (size < 0 || size > MAX_DEPTH) + size = 0; list.clear(); for (int i = 0; i < size; i++) { Tag *tag = Tag::newTag(type, L""); + if (tag == nullptr) break; tag->load(dis, tagDepth); list.push_back(tag); } diff --git a/Minecraft.World/Packet.cpp b/Minecraft.World/Packet.cpp index 129024b77..05bf932dc 100644 --- a/Minecraft.World/Packet.cpp +++ b/Minecraft.World/Packet.cpp @@ -267,8 +267,12 @@ void Packet::updatePacketStatsPIX() shared_ptr Packet::getPacket(int id) { - // 4J: Removed try/catch - return idToCreateMap[id](); + auto it = idToCreateMap.find(id); + if (it == idToCreateMap.end()) + { + return nullptr; + } + return it->second(); } void Packet::writeBytes(DataOutputStream *dataoutputstream, byteArray bytes) @@ -334,30 +338,11 @@ shared_ptr Packet::readPacket(DataInputStream *dis, bool isServer) // th if ((isServer && serverReceivedPackets.find(id) == serverReceivedPackets.end()) || (!isServer && clientReceivedPackets.find(id) == clientReceivedPackets.end())) { - 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)); + return nullptr; } packet = getPacket(id); - if (packet == nullptr) assert(false);//throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id)); + if (packet == nullptr) return nullptr;//throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id)); s_lastIds[s_lastIdPos] = id; s_lastIdPos = (s_lastIdPos + 1) % 8; @@ -418,11 +403,9 @@ wstring Packet::readUtf(DataInputStream *dis, int maxLength) // throws IOExcepti { short stringLength = dis->readShort(); - if (stringLength > maxLength) + if (stringLength > maxLength || stringLength <= 0) { - wstringstream stream; - stream << L"Received string length longer than maximum allowed (" << stringLength << " > " << maxLength << ")"; - assert(false); + return L""; // throw new IOException( stream.str() ); } if (stringLength < 0) @@ -531,7 +514,7 @@ shared_ptr Packet::readItem(DataInputStream *dis) { shared_ptr item = nullptr; int id = dis->readShort(); - if (id >= 0) + if (id >= 0 && id < 32000) // todo: should turn Item::ITEM_NUM_COUNT into a global define { int count = dis->readByte(); int damage = dis->readShort(); @@ -569,9 +552,16 @@ void Packet::writeItem(shared_ptr item, DataOutputStream *dos) CompoundTag *Packet::readNbt(DataInputStream *dis) { int size = dis->readShort(); - if (size < 0) return nullptr; + if (size <= 0) return nullptr; + + const int MAX_NBT_SIZE = 32767; + if (size > MAX_NBT_SIZE) return nullptr; byteArray buff(size); - dis->readFully(buff); + if (!dis->readFully(buff)) + { + delete [] buff.data; + return nullptr; + } CompoundTag *result = (CompoundTag *) NbtIo::decompress(buff); delete [] buff.data; return result; diff --git a/Minecraft.World/PreLoginPacket.cpp b/Minecraft.World/PreLoginPacket.cpp index aa6832cad..a88b96412 100644 --- a/Minecraft.World/PreLoginPacket.cpp +++ b/Minecraft.World/PreLoginPacket.cpp @@ -62,6 +62,7 @@ void PreLoginPacket::read(DataInputStream *dis) //throws IOException m_friendsOnlyBits = dis->readByte(); m_ugcPlayersVersion = dis->readInt(); m_dwPlayerCount = dis->readByte(); + if( m_dwPlayerCount > MINECRAFT_NET_MAX_PLAYERS ) m_dwPlayerCount = MINECRAFT_NET_MAX_PLAYERS; if( m_dwPlayerCount > 0 ) { m_playerXuids = new PlayerUID[m_dwPlayerCount]; diff --git a/Minecraft.World/RemoveEntitiesPacket.cpp b/Minecraft.World/RemoveEntitiesPacket.cpp index 609cfa776..4c5e33131 100644 --- a/Minecraft.World/RemoveEntitiesPacket.cpp +++ b/Minecraft.World/RemoveEntitiesPacket.cpp @@ -21,7 +21,9 @@ RemoveEntitiesPacket::~RemoveEntitiesPacket() void RemoveEntitiesPacket::read(DataInputStream *dis) //throws IOException { - ids = intArray(dis->readByte()); + int count = dis->readByte(); + if(count < 0) count = 0; + ids = intArray(count); for(unsigned int i = 0; i < ids.length; ++i) { ids[i] = dis->readInt(); diff --git a/Minecraft.World/Socket.cpp b/Minecraft.World/Socket.cpp index 2d5b257d0..41ac3702f 100644 --- a/Minecraft.World/Socket.cpp +++ b/Minecraft.World/Socket.cpp @@ -141,6 +141,11 @@ void Socket::pushDataToQueue(const BYTE * pbData, DWORD dwDataSize, bool fromHos // dwDataSize, queueIdx, dwDataSize > 0 ? pbData[0] : 0, networkPlayerSmallId); EnterCriticalSection(&m_queueLockNetwork[queueIdx]); + if (m_queueNetwork[queueIdx].size() + dwDataSize > 2 * 1024 * 1024) + { + LeaveCriticalSection(&m_queueLockNetwork[queueIdx]); + return; + } for( unsigned int i = 0; i < dwDataSize; i++ ) { m_queueNetwork[queueIdx].push(*pbData++); diff --git a/Minecraft.World/SynchedEntityData.cpp b/Minecraft.World/SynchedEntityData.cpp index 69d419b0d..67b9597bb 100644 --- a/Minecraft.World/SynchedEntityData.cpp +++ b/Minecraft.World/SynchedEntityData.cpp @@ -342,7 +342,10 @@ vector > *SynchedEntityData::unpack(Data int currentHeader = input->readByte(); - while (currentHeader != EOF_MARKER) + int itemCount = 0; + const int MAX_ENTITY_DATA_ITEMS = 256; + + while (currentHeader != EOF_MARKER && itemCount < MAX_ENTITY_DATA_ITEMS) { if (result == nullptr) @@ -397,6 +400,7 @@ vector > *SynchedEntityData::unpack(Data break; } result->push_back(item); + itemCount++; currentHeader = input->readByte(); } diff --git a/Minecraft.World/Tag.cpp b/Minecraft.World/Tag.cpp index b594bb0e2..eafdc9e87 100644 --- a/Minecraft.World/Tag.cpp +++ b/Minecraft.World/Tag.cpp @@ -84,27 +84,56 @@ Tag *Tag::readNamedTag(DataInput *dis) Tag *Tag::readNamedTag(DataInput *dis, int tagDepth) { + static __declspec(thread) int depth = 0; + static __declspec(thread) int totalTagCount = 0; + + if (depth == 0) + { + totalTagCount = 0; + } + + depth++; + + if (depth > 256) + { + depth--; + return new EndTag(); + } + + totalTagCount++; + const int MAX_TOTAL_TAGS = 32768; + if (totalTagCount > MAX_TOTAL_TAGS) + { + depth--; + return new EndTag(); + } + byte type = dis->readByte(); - if (type == 0) return new EndTag(); + if (type == 0) { + depth--; + return new EndTag(); + } // 4J Stu - readByte can return -1, so if it's that then also mark as the end tag if(type == 255) { - app.DebugPrintf("readNamedTag read a type of 255\n"); -#ifndef _CONTENT_PACKAGE - __debugbreak(); -#endif + depth--; return new EndTag(); } wstring name = dis->readUTF();//new String(bytes, "UTF-8"); Tag *tag = newTag(type, name); + if (tag == nullptr) { + depth--; + return new EndTag(); + } // short length = dis.readShort(); // byte[] bytes = new byte[length]; // dis.readFully(bytes); tag->load(dis, tagDepth); + depth--; return tag; } diff --git a/Minecraft.World/TextureAndGeometryPacket.cpp b/Minecraft.World/TextureAndGeometryPacket.cpp index bf5eccdbb..1c920ee7a 100644 --- a/Minecraft.World/TextureAndGeometryPacket.cpp +++ b/Minecraft.World/TextureAndGeometryPacket.cpp @@ -121,7 +121,20 @@ void TextureAndGeometryPacket::read(DataInputStream *dis) //throws IOException { textureName = dis->readUTF(); dwSkinID = static_cast(dis->readInt()); - dwTextureBytes = static_cast(dis->readShort()); + + short rawTextureBytes = dis->readShort(); + if (rawTextureBytes <= 0) + { + dwTextureBytes = 0; + } + else + { + dwTextureBytes = (DWORD)(unsigned short)rawTextureBytes; + if (dwTextureBytes > 65536) + { + dwTextureBytes = 0; + } + } if(dwTextureBytes>0) { @@ -134,7 +147,19 @@ void TextureAndGeometryPacket::read(DataInputStream *dis) //throws IOException } uiAnimOverrideBitmask = dis->readInt(); - dwBoxC = static_cast(dis->readShort()); + short rawBoxC = dis->readShort(); + if (rawBoxC <= 0) + { + dwBoxC = 0; + } + else + { + dwBoxC = (DWORD)(unsigned short)rawBoxC; + if (dwBoxC > 256) + { + dwBoxC = 0; // sane limit for skin boxes + } + } if(dwBoxC>0) { diff --git a/Minecraft.World/TexturePacket.cpp b/Minecraft.World/TexturePacket.cpp index eadcb3edb..94c195a05 100644 --- a/Minecraft.World/TexturePacket.cpp +++ b/Minecraft.World/TexturePacket.cpp @@ -37,17 +37,26 @@ void TexturePacket::handle(PacketListener *listener) void TexturePacket::read(DataInputStream *dis) //throws IOException { textureName = dis->readUTF(); - dwBytes = static_cast(dis->readShort()); + short rawBytes = dis->readShort(); + if (rawBytes <= 0) + { + dwBytes = 0; + return; + } + dwBytes = (DWORD)(unsigned short)rawBytes; + if (dwBytes > 65536) + { + dwBytes = 0; + return; + } + + this->pbData= new BYTE [dwBytes]; - if(dwBytes>0) + for(DWORD i=0;ipbData= new BYTE [dwBytes]; - - for(DWORD i=0;ipbData[i] = dis->readByte(); - } + this->pbData[i] = dis->readByte(); } + } void TexturePacket::write(DataOutputStream *dos) //throws IOException diff --git a/Minecraft.World/ThreadName.cpp b/Minecraft.World/ThreadName.cpp index f41beb61d..1f63aaf00 100644 --- a/Minecraft.World/ThreadName.cpp +++ b/Minecraft.World/ThreadName.cpp @@ -22,9 +22,9 @@ void SetThreadName( DWORD dwThreadID, LPCSTR szThreadName ) #if ( defined _WINDOWS64 | defined _DURANGO ) __try { - RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR *)&info ); - } - __except( GetExceptionCode()==0x406D1388 ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER ) + RaiseException(0x406D1388, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR *)&info); + } + __except (EXCEPTION_EXECUTE_HANDLER) { } #endif diff --git a/Minecraft.World/UpdateGameRuleProgressPacket.cpp b/Minecraft.World/UpdateGameRuleProgressPacket.cpp index d88560114..5bb1b3012 100644 --- a/Minecraft.World/UpdateGameRuleProgressPacket.cpp +++ b/Minecraft.World/UpdateGameRuleProgressPacket.cpp @@ -18,7 +18,7 @@ UpdateGameRuleProgressPacket::UpdateGameRuleProgressPacket(ConsoleGameRules::EGa m_auxValue = auxValue; m_dataTag = dataTag; - if(dataLength > 0) + if (dataLength > 0 && dataLength <= 65536) { m_data = byteArray(dataLength); memcpy(m_data.data,data,dataLength); diff --git a/Minecraft.World/compression.cpp b/Minecraft.World/compression.cpp index 8c2e51c08..4122922b9 100644 --- a/Minecraft.World/compression.cpp +++ b/Minecraft.World/compression.cpp @@ -237,9 +237,19 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz unsigned char *dynamicRleBuf = nullptr; HRESULT decompressResult; - if(*pDestSize > rleSize) + unsigned int safeRleSize = max(rleSize, *pDestSize); + + const unsigned int MAX_RLE_ALLOC = 16 * 1024 * 1024; // 16 MB + if (safeRleSize > MAX_RLE_ALLOC) + { + LeaveCriticalSection(&rleDecompressLock); + *pDestSize = 0; + return E_FAIL; + } + + if (safeRleSize > staticRleSize) { - rleSize = *pDestSize; + rleSize = safeRleSize; dynamicRleBuf = new unsigned char[rleSize]; decompressResult = Decompress(dynamicRleBuf, &rleSize, pSource, SrcSize); pucIn = (unsigned char *)dynamicRleBuf; @@ -263,7 +273,7 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz //unsigned char *pucIn = (unsigned char *)rleDecompressBuf; const unsigned char *pucEnd = pucIn + rleSize; unsigned char *pucOut = static_cast(pDestination); - const unsigned char *pucOutEnd = pucOut + *pDestSize; + unsigned char *pucOutEnd = pucOut + *pDestSize; while( pucIn != pucEnd ) { @@ -275,7 +285,11 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz if( count < 3 ) { count++; - if( pucOut + count > pucOutEnd ) break; + if (pucOut + count > pucOutEnd) + { + pucOut = pucOutEnd; + break; + } for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = 255; @@ -286,7 +300,11 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz count++; if( pucIn >= pucEnd ) break; const unsigned char data = *pucIn++; - if( pucOut + count > pucOutEnd ) break; + if (pucOut + count > pucOutEnd) + { + pucOut = pucOutEnd; + break; + } for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = data; @@ -317,7 +335,7 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize, unsigned char *pucIn = static_cast(pSource); const unsigned char *pucEnd = pucIn + SrcSize; unsigned char *pucOut = static_cast(pDestination); - const unsigned char *pucOutEnd = pucOut + *pDestSize; + unsigned char *pucOutEnd = pucOut + *pDestSize; while( pucIn != pucEnd ) { @@ -329,7 +347,11 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize, if( count < 3 ) { count++; - if( pucOut + count > pucOutEnd ) break; + if (pucOut + count > pucOutEnd) + { + pucOut = pucOutEnd; + break; + } for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = 255; @@ -340,7 +362,11 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize, count++; if( pucIn >= pucEnd ) break; const unsigned char data = *pucIn++; - if( pucOut + count > pucOutEnd ) break; + if (pucOut + count > pucOutEnd) + { + pucOut = pucOutEnd; + break; + } for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = data; From a358a3caaee2a4781f910cfb440bd822ae73a7e5 Mon Sep 17 00:00:00 2001 From: Loki Rautio Date: Mon, 9 Mar 2026 04:46:56 -0500 Subject: [PATCH 8/9] Revert accidentally pushed "LCEMP RCE fixes" This reverts commit d557ca2dfba5ffcca99ceb41b07d149f871964b5. --- Minecraft.World/AwardStatPacket.cpp | 2 +- Minecraft.World/BlockRegionUpdatePacket.cpp | 6 --- Minecraft.World/ByteArrayInputStream.cpp | 13 +---- Minecraft.World/ByteArrayOutputStream.cpp | 18 +------ Minecraft.World/ByteArrayTag.h | 1 - Minecraft.World/ComplexItemDataPacket.cpp | 7 +-- Minecraft.World/CompoundTag.h | 14 ++---- Minecraft.World/Connection.cpp | 4 +- Minecraft.World/ContainerSetContentPacket.cpp | 3 -- Minecraft.World/ContainerSetSlotPacket.cpp | 2 +- Minecraft.World/CustomPayloadPacket.cpp | 2 +- Minecraft.World/DataInputStream.cpp | 4 -- Minecraft.World/ExplodePacket.cpp | 2 - Minecraft.World/GameCommandPacket.cpp | 2 +- Minecraft.World/IntArrayTag.h | 2 - Minecraft.World/ListTag.h | 11 ++-- Minecraft.World/Packet.cpp | 50 +++++++++++-------- Minecraft.World/PreLoginPacket.cpp | 1 - Minecraft.World/RemoveEntitiesPacket.cpp | 4 +- Minecraft.World/Socket.cpp | 5 -- Minecraft.World/SynchedEntityData.cpp | 6 +-- Minecraft.World/Tag.cpp | 39 ++------------- Minecraft.World/TextureAndGeometryPacket.cpp | 29 +---------- Minecraft.World/TexturePacket.cpp | 25 +++------- Minecraft.World/ThreadName.cpp | 6 +-- .../UpdateGameRuleProgressPacket.cpp | 2 +- Minecraft.World/compression.cpp | 42 +++------------- 27 files changed, 80 insertions(+), 222 deletions(-) diff --git a/Minecraft.World/AwardStatPacket.cpp b/Minecraft.World/AwardStatPacket.cpp index b2950a0ef..07888541f 100644 --- a/Minecraft.World/AwardStatPacket.cpp +++ b/Minecraft.World/AwardStatPacket.cpp @@ -47,7 +47,7 @@ void AwardStatPacket::read(DataInputStream *dis) //throws IOException // Read parameter blob. int length = dis->readInt(); - if (length > 0 && length <= 65536) + if(length > 0) { m_paramData = byteArray(length); dis->readFully(m_paramData); diff --git a/Minecraft.World/BlockRegionUpdatePacket.cpp b/Minecraft.World/BlockRegionUpdatePacket.cpp index b91cf4f82..730ce3edc 100644 --- a/Minecraft.World/BlockRegionUpdatePacket.cpp +++ b/Minecraft.World/BlockRegionUpdatePacket.cpp @@ -105,12 +105,6 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException levelIdx = ( size >> 30 ) & 3; size &= 0x3fffffff; - const int MAX_COMPRESSED_CHUNK_SIZE = 5 * 1024 * 1024; - if (size < 0 || size > MAX_COMPRESSED_CHUNK_SIZE) - { - size = 0; - } - if(size == 0) { buffer = byteArray(); diff --git a/Minecraft.World/ByteArrayInputStream.cpp b/Minecraft.World/ByteArrayInputStream.cpp index 9509206d4..d79eff362 100644 --- a/Minecraft.World/ByteArrayInputStream.cpp +++ b/Minecraft.World/ByteArrayInputStream.cpp @@ -10,19 +10,8 @@ //offset - the offset in the buffer of the first byte to read. //length - the maximum number of bytes to read from the buffer. ByteArrayInputStream::ByteArrayInputStream(byteArray buf, unsigned int offset, unsigned int length) - : pos(offset), mark(offset) + : pos( offset ), count( min( offset+length, buf.length ) ), mark( offset ) { - if (offset > buf.length) - { - count = buf.length; - } - else if (length > buf.length - offset) - { - count = buf.length; - } - else - { - count = offset + length; this->buf = buf; } diff --git a/Minecraft.World/ByteArrayOutputStream.cpp b/Minecraft.World/ByteArrayOutputStream.cpp index a6fdad8f6..a9f36e04a 100644 --- a/Minecraft.World/ByteArrayOutputStream.cpp +++ b/Minecraft.World/ByteArrayOutputStream.cpp @@ -53,25 +53,9 @@ void ByteArrayOutputStream::write(byteArray b, unsigned int offset, unsigned int { assert( b.length >= offset + length ); - if (offset > b.length || length > b.length - offset) - { - return; - } - - if (length > 0xFFFFFFFF - count) - { - return; - // If we will fill the buffer we need to make it bigger if( count + length >= buf.length ) - { - unsigned int newSize = (std::max)(count + length + 1, buf.length * 2); - if (newSize <= buf.length) - { - return; - } - buf.resize(newSize); - } + buf.resize( max( count + length + 1, buf.length * 2 ) ); XMemCpy( &buf[count], &b[offset], length ); //std::copy( b->data+offset, b->data+offset+length, buf->data + count ); // Or this instead? diff --git a/Minecraft.World/ByteArrayTag.h b/Minecraft.World/ByteArrayTag.h index 4d53c63ea..1d3e38755 100644 --- a/Minecraft.World/ByteArrayTag.h +++ b/Minecraft.World/ByteArrayTag.h @@ -21,7 +21,6 @@ public: void load(DataInput *dis, int tagDepth) { int length = dis->readInt(); - if (length < 0 || length > 2 * 1024 * 1024) length = 0; if ( data.data ) delete[] data.data; data = byteArray(length); diff --git a/Minecraft.World/ComplexItemDataPacket.cpp b/Minecraft.World/ComplexItemDataPacket.cpp index 98eeb9dee..38c39cb65 100644 --- a/Minecraft.World/ComplexItemDataPacket.cpp +++ b/Minecraft.World/ComplexItemDataPacket.cpp @@ -32,12 +32,7 @@ void ComplexItemDataPacket::read(DataInputStream *dis) //throws IOException itemType = dis->readShort(); itemId = dis->readShort(); - int dataLength = dis->readShort() & 0xffff; - if (dataLength > 32767) - { - dataLength = 0; - } - data = charArray(dataLength); + data = charArray(dis->readUnsignedShort() & 0xffff); dis->readFully(data); } diff --git a/Minecraft.World/CompoundTag.h b/Minecraft.World/CompoundTag.h index 8f4d045df..5eaa2a05d 100644 --- a/Minecraft.World/CompoundTag.h +++ b/Minecraft.World/CompoundTag.h @@ -42,16 +42,10 @@ public: } tags.clear(); Tag *tag; - int tagCount = 0; - const int MAX_COMPOUND_TAGS = 10000; - while ((tag = Tag::readNamedTag(dis))->getId() != Tag::TAG_End) - { - tags[tag->getName()] = tag; - if (++tagCount >= MAX_COMPOUND_TAGS) - { - break; - } - } + while ((tag = Tag::readNamedTag(dis))->getId() != Tag::TAG_End) + { + tags[tag->getName()] = tag; + } delete tag; } diff --git a/Minecraft.World/Connection.cpp b/Minecraft.World/Connection.cpp index d44502667..7f10dc4a7 100644 --- a/Minecraft.World/Connection.cpp +++ b/Minecraft.World/Connection.cpp @@ -108,8 +108,8 @@ Connection::Connection(Socket *socket, const wstring& id, PacketListener *packet const char *szId = wstringtofilename(id); char readThreadName[256]; char writeThreadName[256]; - sprintf_s(readThreadName, sizeof(readThreadName), "%.240s read\n", szId); - sprintf_s(writeThreadName, sizeof(writeThreadName), "%.240s write\n", szId); + sprintf(readThreadName,"%s read\n",szId); + sprintf(writeThreadName,"%s write\n",szId); readThread = new C4JThread(runRead, static_cast(this), readThreadName, READ_STACK_SIZE); writeThread = new C4JThread(runWrite, this, writeThreadName, WRITE_STACK_SIZE); diff --git a/Minecraft.World/ContainerSetContentPacket.cpp b/Minecraft.World/ContainerSetContentPacket.cpp index 28ce64e9c..ae76e0f05 100644 --- a/Minecraft.World/ContainerSetContentPacket.cpp +++ b/Minecraft.World/ContainerSetContentPacket.cpp @@ -32,9 +32,6 @@ void ContainerSetContentPacket::read(DataInputStream *dis) //throws IOException { containerId = dis->readByte(); int count = dis->readShort(); - - if (count < 0 || count > 256) count = 0; - items = ItemInstanceArray(count); for (int i = 0; i < count; i++) { diff --git a/Minecraft.World/ContainerSetSlotPacket.cpp b/Minecraft.World/ContainerSetSlotPacket.cpp index de8d26804..6a0382442 100644 --- a/Minecraft.World/ContainerSetSlotPacket.cpp +++ b/Minecraft.World/ContainerSetSlotPacket.cpp @@ -35,7 +35,7 @@ void ContainerSetSlotPacket::read(DataInputStream *dis) //throws IOException // 4J Stu - TU-1 hotfix // Fix for #13142 - Holding down the A button on the furnace ingredient slot causes the UI to display incorrect item counts BYTE byteId = dis->readByte(); - containerId = (char)(signed char)byteId; + containerId = *(char *)&byteId; slot = dis->readShort(); item = readItem(dis); } diff --git a/Minecraft.World/CustomPayloadPacket.cpp b/Minecraft.World/CustomPayloadPacket.cpp index e86f01de0..46fde5aaf 100644 --- a/Minecraft.World/CustomPayloadPacket.cpp +++ b/Minecraft.World/CustomPayloadPacket.cpp @@ -43,7 +43,7 @@ void CustomPayloadPacket::read(DataInputStream *dis) identifier = readUtf(dis, 20); length = dis->readShort(); - if (length > 0 && length <= Short::MAX_VALUE) + if (length > 0 && length < Short::MAX_VALUE) { if(data.data != nullptr) { diff --git a/Minecraft.World/DataInputStream.cpp b/Minecraft.World/DataInputStream.cpp index 0cd21a7e9..4e4f5cd1b 100644 --- a/Minecraft.World/DataInputStream.cpp +++ b/Minecraft.World/DataInputStream.cpp @@ -303,10 +303,6 @@ wstring DataInputStream::readUTF() int b = stream->read(); unsigned short UTFLength = static_cast(((a & 0xff) << 8) | (b & 0xff)); - const unsigned short MAX_UTF_LENGTH = 32767; - if (UTFLength > MAX_UTF_LENGTH) - return outputString; - //// 4J Stu - I decided while writing DataOutputStream that we didn't need to bother using the UTF8 format //// used in the java libs, and just write in/out as wchar_t all the time diff --git a/Minecraft.World/ExplodePacket.cpp b/Minecraft.World/ExplodePacket.cpp index 88bf00c69..07626161f 100644 --- a/Minecraft.World/ExplodePacket.cpp +++ b/Minecraft.World/ExplodePacket.cpp @@ -52,8 +52,6 @@ void ExplodePacket::read(DataInputStream *dis) //throws IOException r = dis->readFloat(); int count = dis->readInt(); - if (count < 0 || count > 32768) count = 0; - int xp = static_cast(x); int yp = static_cast(y); int zp = static_cast(z); diff --git a/Minecraft.World/GameCommandPacket.cpp b/Minecraft.World/GameCommandPacket.cpp index ada5b04cd..17c5316af 100644 --- a/Minecraft.World/GameCommandPacket.cpp +++ b/Minecraft.World/GameCommandPacket.cpp @@ -40,7 +40,7 @@ void GameCommandPacket::read(DataInputStream *dis) command = static_cast(dis->readInt()); length = dis->readShort(); - if (length > 0 && length <= Short::MAX_VALUE) + if (length > 0 && length < Short::MAX_VALUE) { if(data.data != nullptr) { diff --git a/Minecraft.World/IntArrayTag.h b/Minecraft.World/IntArrayTag.h index 64a4d004c..61ca59d09 100644 --- a/Minecraft.World/IntArrayTag.h +++ b/Minecraft.World/IntArrayTag.h @@ -35,8 +35,6 @@ public: void load(DataInput *dis, int tagDepth) { int length = dis->readInt(); - if (length < 0 || length > 65536) - length = 0; if ( data.data ) delete[] data.data; data = intArray(length); diff --git a/Minecraft.World/ListTag.h b/Minecraft.World/ListTag.h index f6e1c6a0a..2a0fdebc3 100644 --- a/Minecraft.World/ListTag.h +++ b/Minecraft.World/ListTag.h @@ -26,16 +26,21 @@ public: void load(DataInput *dis, int tagDepth) { + if (tagDepth > MAX_DEPTH) + { +#ifndef _CONTENT_PACKAGE + printf("Tried to read NBT tag with too high complexity, depth > %d", MAX_DEPTH); + __debugbreak(); +#endif + return; + } type = dis->readByte(); int size = dis->readInt(); - if (size < 0 || size > MAX_DEPTH) - size = 0; list.clear(); for (int i = 0; i < size; i++) { Tag *tag = Tag::newTag(type, L""); - if (tag == nullptr) break; tag->load(dis, tagDepth); list.push_back(tag); } diff --git a/Minecraft.World/Packet.cpp b/Minecraft.World/Packet.cpp index 05bf932dc..129024b77 100644 --- a/Minecraft.World/Packet.cpp +++ b/Minecraft.World/Packet.cpp @@ -267,12 +267,8 @@ void Packet::updatePacketStatsPIX() shared_ptr Packet::getPacket(int id) { - auto it = idToCreateMap.find(id); - if (it == idToCreateMap.end()) - { - return nullptr; - } - return it->second(); + // 4J: Removed try/catch + return idToCreateMap[id](); } void Packet::writeBytes(DataOutputStream *dataoutputstream, byteArray bytes) @@ -338,11 +334,30 @@ shared_ptr Packet::readPacket(DataInputStream *dis, bool isServer) // th if ((isServer && serverReceivedPackets.find(id) == serverReceivedPackets.end()) || (!isServer && clientReceivedPackets.find(id) == clientReceivedPackets.end())) { - return nullptr; + 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)); } packet = getPacket(id); - if (packet == nullptr) return nullptr;//throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id)); + if (packet == nullptr) assert(false);//throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id)); s_lastIds[s_lastIdPos] = id; s_lastIdPos = (s_lastIdPos + 1) % 8; @@ -403,9 +418,11 @@ wstring Packet::readUtf(DataInputStream *dis, int maxLength) // throws IOExcepti { short stringLength = dis->readShort(); - if (stringLength > maxLength || stringLength <= 0) + if (stringLength > maxLength) { - return L""; + wstringstream stream; + stream << L"Received string length longer than maximum allowed (" << stringLength << " > " << maxLength << ")"; + assert(false); // throw new IOException( stream.str() ); } if (stringLength < 0) @@ -514,7 +531,7 @@ shared_ptr Packet::readItem(DataInputStream *dis) { shared_ptr item = nullptr; int id = dis->readShort(); - if (id >= 0 && id < 32000) // todo: should turn Item::ITEM_NUM_COUNT into a global define + if (id >= 0) { int count = dis->readByte(); int damage = dis->readShort(); @@ -552,16 +569,9 @@ void Packet::writeItem(shared_ptr item, DataOutputStream *dos) CompoundTag *Packet::readNbt(DataInputStream *dis) { int size = dis->readShort(); - if (size <= 0) return nullptr; - - const int MAX_NBT_SIZE = 32767; - if (size > MAX_NBT_SIZE) return nullptr; + if (size < 0) return nullptr; byteArray buff(size); - if (!dis->readFully(buff)) - { - delete [] buff.data; - return nullptr; - } + dis->readFully(buff); CompoundTag *result = (CompoundTag *) NbtIo::decompress(buff); delete [] buff.data; return result; diff --git a/Minecraft.World/PreLoginPacket.cpp b/Minecraft.World/PreLoginPacket.cpp index a88b96412..aa6832cad 100644 --- a/Minecraft.World/PreLoginPacket.cpp +++ b/Minecraft.World/PreLoginPacket.cpp @@ -62,7 +62,6 @@ void PreLoginPacket::read(DataInputStream *dis) //throws IOException m_friendsOnlyBits = dis->readByte(); m_ugcPlayersVersion = dis->readInt(); m_dwPlayerCount = dis->readByte(); - if( m_dwPlayerCount > MINECRAFT_NET_MAX_PLAYERS ) m_dwPlayerCount = MINECRAFT_NET_MAX_PLAYERS; if( m_dwPlayerCount > 0 ) { m_playerXuids = new PlayerUID[m_dwPlayerCount]; diff --git a/Minecraft.World/RemoveEntitiesPacket.cpp b/Minecraft.World/RemoveEntitiesPacket.cpp index 4c5e33131..609cfa776 100644 --- a/Minecraft.World/RemoveEntitiesPacket.cpp +++ b/Minecraft.World/RemoveEntitiesPacket.cpp @@ -21,9 +21,7 @@ RemoveEntitiesPacket::~RemoveEntitiesPacket() void RemoveEntitiesPacket::read(DataInputStream *dis) //throws IOException { - int count = dis->readByte(); - if(count < 0) count = 0; - ids = intArray(count); + ids = intArray(dis->readByte()); for(unsigned int i = 0; i < ids.length; ++i) { ids[i] = dis->readInt(); diff --git a/Minecraft.World/Socket.cpp b/Minecraft.World/Socket.cpp index 41ac3702f..2d5b257d0 100644 --- a/Minecraft.World/Socket.cpp +++ b/Minecraft.World/Socket.cpp @@ -141,11 +141,6 @@ void Socket::pushDataToQueue(const BYTE * pbData, DWORD dwDataSize, bool fromHos // dwDataSize, queueIdx, dwDataSize > 0 ? pbData[0] : 0, networkPlayerSmallId); EnterCriticalSection(&m_queueLockNetwork[queueIdx]); - if (m_queueNetwork[queueIdx].size() + dwDataSize > 2 * 1024 * 1024) - { - LeaveCriticalSection(&m_queueLockNetwork[queueIdx]); - return; - } for( unsigned int i = 0; i < dwDataSize; i++ ) { m_queueNetwork[queueIdx].push(*pbData++); diff --git a/Minecraft.World/SynchedEntityData.cpp b/Minecraft.World/SynchedEntityData.cpp index 67b9597bb..69d419b0d 100644 --- a/Minecraft.World/SynchedEntityData.cpp +++ b/Minecraft.World/SynchedEntityData.cpp @@ -342,10 +342,7 @@ vector > *SynchedEntityData::unpack(Data int currentHeader = input->readByte(); - int itemCount = 0; - const int MAX_ENTITY_DATA_ITEMS = 256; - - while (currentHeader != EOF_MARKER && itemCount < MAX_ENTITY_DATA_ITEMS) + while (currentHeader != EOF_MARKER) { if (result == nullptr) @@ -400,7 +397,6 @@ vector > *SynchedEntityData::unpack(Data break; } result->push_back(item); - itemCount++; currentHeader = input->readByte(); } diff --git a/Minecraft.World/Tag.cpp b/Minecraft.World/Tag.cpp index eafdc9e87..b594bb0e2 100644 --- a/Minecraft.World/Tag.cpp +++ b/Minecraft.World/Tag.cpp @@ -84,56 +84,27 @@ Tag *Tag::readNamedTag(DataInput *dis) Tag *Tag::readNamedTag(DataInput *dis, int tagDepth) { - static __declspec(thread) int depth = 0; - static __declspec(thread) int totalTagCount = 0; - - if (depth == 0) - { - totalTagCount = 0; - } - - depth++; - - if (depth > 256) - { - depth--; - return new EndTag(); - } - - totalTagCount++; - const int MAX_TOTAL_TAGS = 32768; - if (totalTagCount > MAX_TOTAL_TAGS) - { - depth--; - return new EndTag(); - } - byte type = dis->readByte(); - if (type == 0) { - depth--; - return new EndTag(); - } + if (type == 0) return new EndTag(); // 4J Stu - readByte can return -1, so if it's that then also mark as the end tag if(type == 255) { - depth--; + app.DebugPrintf("readNamedTag read a type of 255\n"); +#ifndef _CONTENT_PACKAGE + __debugbreak(); +#endif return new EndTag(); } wstring name = dis->readUTF();//new String(bytes, "UTF-8"); Tag *tag = newTag(type, name); - if (tag == nullptr) { - depth--; - return new EndTag(); - } // short length = dis.readShort(); // byte[] bytes = new byte[length]; // dis.readFully(bytes); tag->load(dis, tagDepth); - depth--; return tag; } diff --git a/Minecraft.World/TextureAndGeometryPacket.cpp b/Minecraft.World/TextureAndGeometryPacket.cpp index 1c920ee7a..bf5eccdbb 100644 --- a/Minecraft.World/TextureAndGeometryPacket.cpp +++ b/Minecraft.World/TextureAndGeometryPacket.cpp @@ -121,20 +121,7 @@ void TextureAndGeometryPacket::read(DataInputStream *dis) //throws IOException { textureName = dis->readUTF(); dwSkinID = static_cast(dis->readInt()); - - short rawTextureBytes = dis->readShort(); - if (rawTextureBytes <= 0) - { - dwTextureBytes = 0; - } - else - { - dwTextureBytes = (DWORD)(unsigned short)rawTextureBytes; - if (dwTextureBytes > 65536) - { - dwTextureBytes = 0; - } - } + dwTextureBytes = static_cast(dis->readShort()); if(dwTextureBytes>0) { @@ -147,19 +134,7 @@ void TextureAndGeometryPacket::read(DataInputStream *dis) //throws IOException } uiAnimOverrideBitmask = dis->readInt(); - short rawBoxC = dis->readShort(); - if (rawBoxC <= 0) - { - dwBoxC = 0; - } - else - { - dwBoxC = (DWORD)(unsigned short)rawBoxC; - if (dwBoxC > 256) - { - dwBoxC = 0; // sane limit for skin boxes - } - } + dwBoxC = static_cast(dis->readShort()); if(dwBoxC>0) { diff --git a/Minecraft.World/TexturePacket.cpp b/Minecraft.World/TexturePacket.cpp index 94c195a05..eadcb3edb 100644 --- a/Minecraft.World/TexturePacket.cpp +++ b/Minecraft.World/TexturePacket.cpp @@ -37,26 +37,17 @@ void TexturePacket::handle(PacketListener *listener) void TexturePacket::read(DataInputStream *dis) //throws IOException { textureName = dis->readUTF(); - short rawBytes = dis->readShort(); - if (rawBytes <= 0) - { - dwBytes = 0; - return; - } - dwBytes = (DWORD)(unsigned short)rawBytes; - if (dwBytes > 65536) - { - dwBytes = 0; - return; - } - - this->pbData= new BYTE [dwBytes]; + dwBytes = static_cast(dis->readShort()); - for(DWORD i=0;i0) { - this->pbData[i] = dis->readByte(); + this->pbData= new BYTE [dwBytes]; + + for(DWORD i=0;ipbData[i] = dis->readByte(); + } } - } void TexturePacket::write(DataOutputStream *dos) //throws IOException diff --git a/Minecraft.World/ThreadName.cpp b/Minecraft.World/ThreadName.cpp index 1f63aaf00..f41beb61d 100644 --- a/Minecraft.World/ThreadName.cpp +++ b/Minecraft.World/ThreadName.cpp @@ -22,9 +22,9 @@ void SetThreadName( DWORD dwThreadID, LPCSTR szThreadName ) #if ( defined _WINDOWS64 | defined _DURANGO ) __try { - RaiseException(0x406D1388, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR *)&info); - } - __except (EXCEPTION_EXECUTE_HANDLER) + RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR *)&info ); + } + __except( GetExceptionCode()==0x406D1388 ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER ) { } #endif diff --git a/Minecraft.World/UpdateGameRuleProgressPacket.cpp b/Minecraft.World/UpdateGameRuleProgressPacket.cpp index 5bb1b3012..d88560114 100644 --- a/Minecraft.World/UpdateGameRuleProgressPacket.cpp +++ b/Minecraft.World/UpdateGameRuleProgressPacket.cpp @@ -18,7 +18,7 @@ UpdateGameRuleProgressPacket::UpdateGameRuleProgressPacket(ConsoleGameRules::EGa m_auxValue = auxValue; m_dataTag = dataTag; - if (dataLength > 0 && dataLength <= 65536) + if(dataLength > 0) { m_data = byteArray(dataLength); memcpy(m_data.data,data,dataLength); diff --git a/Minecraft.World/compression.cpp b/Minecraft.World/compression.cpp index 4122922b9..8c2e51c08 100644 --- a/Minecraft.World/compression.cpp +++ b/Minecraft.World/compression.cpp @@ -237,19 +237,9 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz unsigned char *dynamicRleBuf = nullptr; HRESULT decompressResult; - unsigned int safeRleSize = max(rleSize, *pDestSize); - - const unsigned int MAX_RLE_ALLOC = 16 * 1024 * 1024; // 16 MB - if (safeRleSize > MAX_RLE_ALLOC) - { - LeaveCriticalSection(&rleDecompressLock); - *pDestSize = 0; - return E_FAIL; - } - - if (safeRleSize > staticRleSize) + if(*pDestSize > rleSize) { - rleSize = safeRleSize; + rleSize = *pDestSize; dynamicRleBuf = new unsigned char[rleSize]; decompressResult = Decompress(dynamicRleBuf, &rleSize, pSource, SrcSize); pucIn = (unsigned char *)dynamicRleBuf; @@ -273,7 +263,7 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz //unsigned char *pucIn = (unsigned char *)rleDecompressBuf; const unsigned char *pucEnd = pucIn + rleSize; unsigned char *pucOut = static_cast(pDestination); - unsigned char *pucOutEnd = pucOut + *pDestSize; + const unsigned char *pucOutEnd = pucOut + *pDestSize; while( pucIn != pucEnd ) { @@ -285,11 +275,7 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz if( count < 3 ) { count++; - if (pucOut + count > pucOutEnd) - { - pucOut = pucOutEnd; - break; - } + if( pucOut + count > pucOutEnd ) break; for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = 255; @@ -300,11 +286,7 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz count++; if( pucIn >= pucEnd ) break; const unsigned char data = *pucIn++; - if (pucOut + count > pucOutEnd) - { - pucOut = pucOutEnd; - break; - } + if( pucOut + count > pucOutEnd ) break; for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = data; @@ -335,7 +317,7 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize, unsigned char *pucIn = static_cast(pSource); const unsigned char *pucEnd = pucIn + SrcSize; unsigned char *pucOut = static_cast(pDestination); - unsigned char *pucOutEnd = pucOut + *pDestSize; + const unsigned char *pucOutEnd = pucOut + *pDestSize; while( pucIn != pucEnd ) { @@ -347,11 +329,7 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize, if( count < 3 ) { count++; - if (pucOut + count > pucOutEnd) - { - pucOut = pucOutEnd; - break; - } + if( pucOut + count > pucOutEnd ) break; for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = 255; @@ -362,11 +340,7 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize, count++; if( pucIn >= pucEnd ) break; const unsigned char data = *pucIn++; - if (pucOut + count > pucOutEnd) - { - pucOut = pucOutEnd; - break; - } + if( pucOut + count > pucOutEnd ) break; for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = data; From bda3b1078ac357b805156a8802a0649f7021716e Mon Sep 17 00:00:00 2001 From: Loki Date: Mon, 9 Mar 2026 06:53:08 -0500 Subject: [PATCH 9/9] Port over RCE Patches from LCEMP (#1023) * LCEMP RCE Fixes WIP Based on https://github.com/LCEMP/LCEMP/commit/d017bfc30a68888bf5c79b23cf5c4f607cf828bf * Update to LCEMP's ByteArrayIO version Fixes compilation since ours was missing some revisions from LCEMP * Add additional safety checks missed in first pass * Remove duplicate recipe count check --- Minecraft.Client/Chunk.cpp | 5 +- Minecraft.Client/PlayerConnection.cpp | 8 +- Minecraft.Client/ServerLevel.cpp | 7 +- .../Windows64/Network/WinsockNetLayer.cpp | 2 +- Minecraft.World/AbstractContainerMenu.cpp | 6 +- Minecraft.World/AwardStatPacket.cpp | 2 +- Minecraft.World/BlockRegionUpdatePacket.cpp | 6 + Minecraft.World/ByteArrayInputStream.cpp | 176 ++++++++++-------- Minecraft.World/ByteArrayOutputStream.cpp | 99 ++++++---- Minecraft.World/ByteArrayTag.h | 1 + Minecraft.World/ComplexItemDataPacket.cpp | 7 +- Minecraft.World/CompoundTag.h | 14 +- Minecraft.World/Connection.cpp | 4 +- Minecraft.World/ContainerSetContentPacket.cpp | 3 + Minecraft.World/ContainerSetSlotPacket.cpp | 2 +- Minecraft.World/CustomPayloadPacket.cpp | 2 +- Minecraft.World/DataInputStream.cpp | 4 + Minecraft.World/ExplodePacket.cpp | 2 + Minecraft.World/GameCommandPacket.cpp | 2 +- Minecraft.World/IntArrayTag.h | 2 + Minecraft.World/ListTag.h | 11 +- Minecraft.World/Packet.cpp | 50 ++--- Minecraft.World/PreLoginPacket.cpp | 2 + Minecraft.World/RemoveEntitiesPacket.cpp | 4 +- Minecraft.World/Socket.cpp | 5 + Minecraft.World/SynchedEntityData.cpp | 6 +- Minecraft.World/Tag.cpp | 39 +++- Minecraft.World/TextureAndGeometryPacket.cpp | 29 ++- Minecraft.World/TexturePacket.cpp | 25 ++- Minecraft.World/ThreadName.cpp | 6 +- .../UpdateGameRuleProgressPacket.cpp | 2 +- Minecraft.World/compression.cpp | 42 ++++- 32 files changed, 372 insertions(+), 203 deletions(-) diff --git a/Minecraft.Client/Chunk.cpp b/Minecraft.Client/Chunk.cpp index 6f0ad7366..0a63b8747 100644 --- a/Minecraft.Client/Chunk.cpp +++ b/Minecraft.Client/Chunk.cpp @@ -739,9 +739,9 @@ void Chunk::rebuild_SPU() { // 4J - get tile from those copied into our local array in earlier optimisation unsigned char tileId = pOutData->getTile(x,y,z); - if (tileId > 0) + if (tileId > 0 && tileId != 0xff) { - if (currentLayer == 0 && Tile::tiles[tileId]->isEntityTile()) + if (currentLayer == 0 && Tile::tiles[tileId] && Tile::tiles[tileId]->isEntityTile()) { shared_ptr et = region.getTileEntity(x, y, z); if (TileEntityRenderDispatcher::instance->hasRenderer(et)) @@ -754,6 +754,7 @@ void Chunk::rebuild_SPU() { Tile *tile = Tile::tiles[tileId]; + if (!tile) continue; int renderLayer = tile->getRenderLayer(); if (renderLayer != currentLayer) diff --git a/Minecraft.Client/PlayerConnection.cpp b/Minecraft.Client/PlayerConnection.cpp index 8e056cd81..d9915cf61 100644 --- a/Minecraft.Client/PlayerConnection.cpp +++ b/Minecraft.Client/PlayerConnection.cpp @@ -1588,13 +1588,13 @@ void PlayerConnection::handleCraftItem(shared_ptr packet) if(iRecipe == -1) return; + int recipeCount = (int)Recipes::getInstance()->getRecipies()->size(); + if(iRecipe < 0 || iRecipe >= recipeCount) + return; + Recipy::INGREDIENTS_REQUIRED *pRecipeIngredientsRequired=Recipes::getInstance()->getRecipeIngredientsArray(); shared_ptr pTempItemInst=pRecipeIngredientsRequired[iRecipe].pRecipy->assemble(nullptr); - size_t recipeCount = Recipes::getInstance()->getRecipies()->size(); - if (iRecipe < 0 || iRecipe >= (int)recipeCount) - return; - if(app.DebugSettingsOn() && (player->GetDebugOptions()&(1L<onCraftedBy(player->level, dynamic_pointer_cast( player->shared_from_this() ), pTempItemInst->count ); diff --git a/Minecraft.Client/ServerLevel.cpp b/Minecraft.Client/ServerLevel.cpp index d322d9b6d..a2596911d 100644 --- a/Minecraft.Client/ServerLevel.cpp +++ b/Minecraft.Client/ServerLevel.cpp @@ -1077,7 +1077,12 @@ void ServerLevel::entityRemoved(shared_ptr e) shared_ptr ServerLevel::getEntity(int id) { - return entitiesById[id]; + auto it = entitiesById.find(id); + if (it != entitiesById.end()) + { + return it->second; + } + return nullptr; } bool ServerLevel::addGlobalEntity(shared_ptr e) diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index e82118cd0..28d295049 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -404,7 +404,7 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port) bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void* data, int dataSize) { - if (sock == INVALID_SOCKET || dataSize <= 0) return false; + if (sock == INVALID_SOCKET || dataSize <= 0 || dataSize > WIN64_NET_MAX_PACKET_SIZE) 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 diff --git a/Minecraft.World/AbstractContainerMenu.cpp b/Minecraft.World/AbstractContainerMenu.cpp index 10d8afdcc..c98fc22c5 100644 --- a/Minecraft.World/AbstractContainerMenu.cpp +++ b/Minecraft.World/AbstractContainerMenu.cpp @@ -157,6 +157,9 @@ shared_ptr AbstractContainerMenu::clicked(int slotIndex, int butto shared_ptr clickedEntity = nullptr; shared_ptr inventory = player->inventory; + if (slotIndex < 0 || slotIndex >= (int)slots.size()) + return nullptr; + if (clickType == CLICK_QUICK_CRAFT) { int expectedStatus = quickcraftStatus; @@ -558,12 +561,13 @@ bool AbstractContainerMenu::isPauseScreen() void AbstractContainerMenu::setItem(unsigned int slot, shared_ptr item) { + if (slot >= slots.size()) return; getSlot(slot)->set(item); } void AbstractContainerMenu::setAll(ItemInstanceArray *items) { - for (unsigned int i = 0; i < items->length; i++) + for (unsigned int i = 0; i < items->length && i < slots.size(); i++) { getSlot(i)->set( (*items)[i] ); } diff --git a/Minecraft.World/AwardStatPacket.cpp b/Minecraft.World/AwardStatPacket.cpp index 07888541f..b2950a0ef 100644 --- a/Minecraft.World/AwardStatPacket.cpp +++ b/Minecraft.World/AwardStatPacket.cpp @@ -47,7 +47,7 @@ void AwardStatPacket::read(DataInputStream *dis) //throws IOException // Read parameter blob. int length = dis->readInt(); - if(length > 0) + if (length > 0 && length <= 65536) { m_paramData = byteArray(length); dis->readFully(m_paramData); diff --git a/Minecraft.World/BlockRegionUpdatePacket.cpp b/Minecraft.World/BlockRegionUpdatePacket.cpp index 730ce3edc..b91cf4f82 100644 --- a/Minecraft.World/BlockRegionUpdatePacket.cpp +++ b/Minecraft.World/BlockRegionUpdatePacket.cpp @@ -105,6 +105,12 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException levelIdx = ( size >> 30 ) & 3; size &= 0x3fffffff; + const int MAX_COMPRESSED_CHUNK_SIZE = 5 * 1024 * 1024; + if (size < 0 || size > MAX_COMPRESSED_CHUNK_SIZE) + { + size = 0; + } + if(size == 0) { buffer = byteArray(); diff --git a/Minecraft.World/ByteArrayInputStream.cpp b/Minecraft.World/ByteArrayInputStream.cpp index d79eff362..29f6fc262 100644 --- a/Minecraft.World/ByteArrayInputStream.cpp +++ b/Minecraft.World/ByteArrayInputStream.cpp @@ -2,117 +2,141 @@ #include "InputOutputStream.h" -//Creates ByteArrayInputStream that uses buf as its buffer array. The initial value of pos is offset and -//the initial value of count is the minimum of offset+length and buf.length. The buffer array is not copied. -//The buffer's mark is set to the specified offset. -//Parameters: -//buf - the input buffer. -//offset - the offset in the buffer of the first byte to read. -//length - the maximum number of bytes to read from the buffer. +// Creates ByteArrayInputStream that uses buf as its buffer array. The initial value of pos is offset and +// the initial value of count is the minimum of offset+length and buf.length. The buffer array is not copied. +// The buffer's mark is set to the specified offset. +// Parameters: +// buf - the input buffer. +// offset - the offset in the buffer of the first byte to read. +// length - the maximum number of bytes to read from the buffer. ByteArrayInputStream::ByteArrayInputStream(byteArray buf, unsigned int offset, unsigned int length) - : pos( offset ), count( min( offset+length, buf.length ) ), mark( offset ) + : pos(offset), mark(offset) { - this->buf = buf; + if (offset > buf.length) + { + count = buf.length; + } + else if (length > buf.length - offset) + { + count = buf.length; + } + else + { + count = offset + length; + } + this->buf = buf; } -//Creates a ByteArrayInputStream so that it uses buf as its buffer array. The buffer array is not copied. -//The initial value of pos is 0 and the initial value of count is the length of buf. -//Parameters: -//buf - the input buffer. +// Creates a ByteArrayInputStream so that it uses buf as its buffer array. The buffer array is not copied. +// The initial value of pos is 0 and the initial value of count is the length of buf. +// Parameters: +// buf - the input buffer. ByteArrayInputStream::ByteArrayInputStream(byteArray buf) - : pos( 0 ), count( buf.length ), mark( 0 ) + : pos(0), count(buf.length), mark(0) { - this->buf = buf; + this->buf = buf; } -//Reads the next byte of data from this input stream. The value byte is returned as an int in the range 0 to 255. -//If no byte is available because the end of the stream has been reached, the value -1 is returned. -//This read method cannot block. -//Returns: -//the next byte of data, or -1 if the end of the stream has been reached. +// Reads the next byte of data from this input stream. The value byte is returned as an int in the range 0 to 255. +// If no byte is available because the end of the stream has been reached, the value -1 is returned. +// This read method cannot block. +// Returns: +// the next byte of data, or -1 if the end of the stream has been reached. int ByteArrayInputStream::read() { - if( pos >= count ) - return -1; - else - return buf[pos++]; + if (pos >= count) + { + return -1; + } + else + { + return buf[pos++]; + } } -//Reads some number of bytes from the input stream and stores them into the buffer array b. -//The number of bytes actually read is returned as an integer. This method blocks until input data is available, -//end of file is detected, or an exception is thrown. -//If the length of b is zero, then no bytes are read and 0 is returned; otherwise, there is an attempt to read at least one byte. -//If no byte is available because the stream is at the end of the file, the value -1 is returned; otherwise, -//at least one byte is read and stored into b. +// Reads some number of bytes from the input stream and stores them into the buffer array b. +// The number of bytes actually read is returned as an integer. This method blocks until input data is available, +// end of file is detected, or an exception is thrown. +// If the length of b is zero, then no bytes are read and 0 is returned; otherwise, there is an attempt to read at least one byte. +// If no byte is available because the stream is at the end of the file, the value -1 is returned; otherwise, +// at least one byte is read and stored into b. // -//The first byte read is stored into element b[0], the next one into b[1], and so on. The number of bytes read is, -//at most, equal to the length of b. Let k be the number of bytes actually read; these bytes will be stored in elements b[0] through b[k-1], -//leaving elements b[k] through b[b.length-1] unaffected. +// The first byte read is stored into element b[0], the next one into b[1], and so on. The number of bytes read is, +// at most, equal to the length of b. Let k be the number of bytes actually read; these bytes will be stored in elements b[0] through b[k-1], +// leaving elements b[k] through b[b.length-1] unaffected. // -//The read(b) method for class InputStream has the same effect as: +// The read(b) method for class InputStream has the same effect as: // -// read(b, 0, b.length) -//Parameters: -//b - the buffer into which the data is read. -//Returns: -//the total number of bytes read into the buffer, or -1 is there is no more data because the end of the stream has been reached. +// read(b, 0, b.length) +// Parameters: +// b - the buffer into which the data is read. +// Returns: +// the total number of bytes read into the buffer, or -1 is there is no more data because the end of the stream has been reached. int ByteArrayInputStream::read(byteArray b) { - return read( b, 0, b.length ); + return read(b, 0, b.length); } -//Reads up to len bytes of data into an array of bytes from this input stream. If pos equals count, -//then -1 is returned to indicate end of file. Otherwise, the number k of bytes read is equal to the smaller of len and count-pos. -//If k is positive, then bytes buf[pos] through buf[pos+k-1] are copied into b[off] through b[off+k-1] in the manner -//performed by System.arraycopy. The value k is added into pos and k is returned. -//This read method cannot block. -//Parameters: -//b - the buffer into which the data is read. -//off - the start offset in the destination array b -//len - the maximum number of bytes read. -//Returns: -//the total number of bytes read into the buffer, or -1 if there is no more data because the end of the stream has been reached. +// Reads up to len bytes of data into an array of bytes from this input stream. If pos equals count, +// then -1 is returned to indicate end of file. Otherwise, the number k of bytes read is equal to the smaller of len and count-pos. +// If k is positive, then bytes buf[pos] through buf[pos+k-1] are copied into b[off] through b[off+k-1] in the manner +// performed by System.arraycopy. The value k is added into pos and k is returned. +// This read method cannot block. +// Parameters: +// b - the buffer into which the data is read. +// off - the start offset in the destination array b +// len - the maximum number of bytes read. +// Returns: +// the total number of bytes read into the buffer, or -1 if there is no more data because the end of the stream has been reached. int ByteArrayInputStream::read(byteArray b, unsigned int offset, unsigned int length) { - if( pos == count ) - return -1; + if (pos == count) + { + return -1; + } - int k = min( length, count-pos ); - XMemCpy( &b[offset], &buf[pos], k ); - //std::copy( buf->data+pos, buf->data+pos+k, b->data + offset ); // Or this instead? + int k = min(length, count - pos); + XMemCpy(&b[offset], &buf[pos], k); + // std::copy( buf->data+pos, buf->data+pos+k, b->data + offset ); // Or this instead? - pos += k; + pos += k; - return k; + return k; } -//Closing a ByteArrayInputStream has no effect. -//The methods in this class can be called after the stream has been closed without generating an IOException. +// Closing a ByteArrayInputStream has no effect. +// The methods in this class can be called after the stream has been closed without generating an IOException. void ByteArrayInputStream::close() { - return; + return; } -//Skips n bytes of input from this input stream. Fewer bytes might be skipped if the end of the input stream is reached. The actual number k of bytes to be skipped is equal to the smaller of n and count-pos. The value k is added into pos and k is returned. -//Overrides: -//skip in class InputStream -//Parameters: -//n - the number of bytes to be skipped. -//Returns: -//the actual number of bytes skipped. -int64_t ByteArrayInputStream::skip(int64_t n) +// Skips n bytes of input from this input stream. Fewer bytes might be skipped if the end of the input stream is reached. The actual number k of bytes to be skipped is equal to the smaller of n and count-pos. The value k is added into pos and k is returned. +// Overrides: +// skip in class InputStream +// Parameters: +// n - the number of bytes to be skipped. +// Returns: +// the actual number of bytes skipped. +__int64 ByteArrayInputStream::skip(__int64 n) { - int newPos = pos + n; + int newPos = pos + n; - if(newPos > count) newPos = count; + if (newPos > count) + { + newPos = count; + } - int k = newPos - pos; - pos = newPos; + int k = newPos - pos; + pos = newPos; - return k; + return k; } ByteArrayInputStream::~ByteArrayInputStream() { - if(buf.data != nullptr) delete [] buf.data; -} \ No newline at end of file + if (buf.data != NULL) + { + delete[] buf.data; + } +} diff --git a/Minecraft.World/ByteArrayOutputStream.cpp b/Minecraft.World/ByteArrayOutputStream.cpp index a9f36e04a..9173941fc 100644 --- a/Minecraft.World/ByteArrayOutputStream.cpp +++ b/Minecraft.World/ByteArrayOutputStream.cpp @@ -5,76 +5,95 @@ // Creates a new byte array output stream. The buffer capacity is initially 32 bytes, though its size increases if necessary. ByteArrayOutputStream::ByteArrayOutputStream() { - count = 0; - buf = byteArray( 32 ); + count = 0; + buf = byteArray(32); } -//Creates a new byte array output stream, with a buffer capacity of the specified size, in bytes. -//Parameters: -//size - the initial size. +// Creates a new byte array output stream, with a buffer capacity of the specified size, in bytes. +// Parameters: +// size - the initial size. ByteArrayOutputStream::ByteArrayOutputStream(unsigned int size) { - count = 0; - buf = byteArray( size ); + count = 0; + buf = byteArray(size); } ByteArrayOutputStream::~ByteArrayOutputStream() { - if (buf.data != nullptr) - delete[] buf.data; + if (buf.data != NULL) + { + delete[] buf.data; + } } -//Writes the specified byte to this byte array output stream. -//Parameters: -//b - the byte to be written. +// Writes the specified byte to this byte array output stream. +// Parameters: +// b - the byte to be written. void ByteArrayOutputStream::write(unsigned int b) { - // If we will fill the buffer we need to make it bigger - if( count + 1 >= buf.length ) - buf.resize( buf.length * 2 ); + // If we will fill the buffer we need to make it bigger + if (count + 1 >= buf.length) + { + buf.resize(buf.length * 2); + } - buf[count] = static_cast(b); - count++; + buf[count] = (byte)b; + count++; } // Writes b.length bytes from the specified byte array to this output stream. -//The general contract for write(b) is that it should have exactly the same effect as the call write(b, 0, b.length). +// The general contract for write(b) is that it should have exactly the same effect as the call write(b, 0, b.length). void ByteArrayOutputStream::write(byteArray b) { - write(b, 0, b.length); + write(b, 0, b.length); } -//Writes len bytes from the specified byte array starting at offset off to this byte array output stream. -//Parameters: -//b - the data. -//off - the start offset in the data. -//len - the number of bytes to write. +// Writes len bytes from the specified byte array starting at offset off to this byte array output stream. +// Parameters: +// b - the data. +// off - the start offset in the data. +// len - the number of bytes to write. void ByteArrayOutputStream::write(byteArray b, unsigned int offset, unsigned int length) { - assert( b.length >= offset + length ); - // If we will fill the buffer we need to make it bigger - if( count + length >= buf.length ) - buf.resize( max( count + length + 1, buf.length * 2 ) ); + if (offset > b.length || length > b.length - offset) + { + return; + } - XMemCpy( &buf[count], &b[offset], length ); - //std::copy( b->data+offset, b->data+offset+length, buf->data + count ); // Or this instead? + if (length > 0xFFFFFFFF - count) + { + return; + } - count += length; + // If we will fill the buffer we need to make it bigger + if (count + length >= buf.length) + { + unsigned int newSize = (std::max)(count + length + 1, buf.length * 2); + if (newSize <= buf.length) + { + return; + } + buf.resize(newSize); + } + + XMemCpy(&buf[count], &b[offset], length); + + count += length; } -//Closing a ByteArrayOutputStream has no effect. -//The methods in this class can be called after the stream has been closed without generating an IOException. +// Closing a ByteArrayOutputStream has no effect. +// The methods in this class can be called after the stream has been closed without generating an IOException. void ByteArrayOutputStream::close() { } -//Creates a newly allocated byte array. Its size is the current size of this output stream and the valid contents of the buffer have been copied into it. -//Returns: -//the current contents of this output stream, as a byte array. +// Creates a newly allocated byte array. Its size is the current size of this output stream and the valid contents of the buffer have been copied into it. +// Returns: +// the current contents of this output stream, as a byte array. byteArray ByteArrayOutputStream::toByteArray() { - byteArray out(count); - memcpy(out.data,buf.data,count); - return out; -} \ No newline at end of file + byteArray out(count); + memcpy(out.data, buf.data, count); + return out; +} diff --git a/Minecraft.World/ByteArrayTag.h b/Minecraft.World/ByteArrayTag.h index 1d3e38755..4d53c63ea 100644 --- a/Minecraft.World/ByteArrayTag.h +++ b/Minecraft.World/ByteArrayTag.h @@ -21,6 +21,7 @@ public: void load(DataInput *dis, int tagDepth) { int length = dis->readInt(); + if (length < 0 || length > 2 * 1024 * 1024) length = 0; if ( data.data ) delete[] data.data; data = byteArray(length); diff --git a/Minecraft.World/ComplexItemDataPacket.cpp b/Minecraft.World/ComplexItemDataPacket.cpp index 38c39cb65..98eeb9dee 100644 --- a/Minecraft.World/ComplexItemDataPacket.cpp +++ b/Minecraft.World/ComplexItemDataPacket.cpp @@ -32,7 +32,12 @@ void ComplexItemDataPacket::read(DataInputStream *dis) //throws IOException itemType = dis->readShort(); itemId = dis->readShort(); - data = charArray(dis->readUnsignedShort() & 0xffff); + int dataLength = dis->readShort() & 0xffff; + if (dataLength > 32767) + { + dataLength = 0; + } + data = charArray(dataLength); dis->readFully(data); } diff --git a/Minecraft.World/CompoundTag.h b/Minecraft.World/CompoundTag.h index 5eaa2a05d..8f4d045df 100644 --- a/Minecraft.World/CompoundTag.h +++ b/Minecraft.World/CompoundTag.h @@ -42,10 +42,16 @@ public: } tags.clear(); Tag *tag; - while ((tag = Tag::readNamedTag(dis))->getId() != Tag::TAG_End) - { - tags[tag->getName()] = tag; - } + int tagCount = 0; + const int MAX_COMPOUND_TAGS = 10000; + while ((tag = Tag::readNamedTag(dis))->getId() != Tag::TAG_End) + { + tags[tag->getName()] = tag; + if (++tagCount >= MAX_COMPOUND_TAGS) + { + break; + } + } delete tag; } diff --git a/Minecraft.World/Connection.cpp b/Minecraft.World/Connection.cpp index 7f10dc4a7..d44502667 100644 --- a/Minecraft.World/Connection.cpp +++ b/Minecraft.World/Connection.cpp @@ -108,8 +108,8 @@ Connection::Connection(Socket *socket, const wstring& id, PacketListener *packet const char *szId = wstringtofilename(id); char readThreadName[256]; char writeThreadName[256]; - sprintf(readThreadName,"%s read\n",szId); - sprintf(writeThreadName,"%s write\n",szId); + sprintf_s(readThreadName, sizeof(readThreadName), "%.240s read\n", szId); + sprintf_s(writeThreadName, sizeof(writeThreadName), "%.240s write\n", szId); readThread = new C4JThread(runRead, static_cast(this), readThreadName, READ_STACK_SIZE); writeThread = new C4JThread(runWrite, this, writeThreadName, WRITE_STACK_SIZE); diff --git a/Minecraft.World/ContainerSetContentPacket.cpp b/Minecraft.World/ContainerSetContentPacket.cpp index ae76e0f05..28ce64e9c 100644 --- a/Minecraft.World/ContainerSetContentPacket.cpp +++ b/Minecraft.World/ContainerSetContentPacket.cpp @@ -32,6 +32,9 @@ void ContainerSetContentPacket::read(DataInputStream *dis) //throws IOException { containerId = dis->readByte(); int count = dis->readShort(); + + if (count < 0 || count > 256) count = 0; + items = ItemInstanceArray(count); for (int i = 0; i < count; i++) { diff --git a/Minecraft.World/ContainerSetSlotPacket.cpp b/Minecraft.World/ContainerSetSlotPacket.cpp index 6a0382442..de8d26804 100644 --- a/Minecraft.World/ContainerSetSlotPacket.cpp +++ b/Minecraft.World/ContainerSetSlotPacket.cpp @@ -35,7 +35,7 @@ void ContainerSetSlotPacket::read(DataInputStream *dis) //throws IOException // 4J Stu - TU-1 hotfix // Fix for #13142 - Holding down the A button on the furnace ingredient slot causes the UI to display incorrect item counts BYTE byteId = dis->readByte(); - containerId = *(char *)&byteId; + containerId = (char)(signed char)byteId; slot = dis->readShort(); item = readItem(dis); } diff --git a/Minecraft.World/CustomPayloadPacket.cpp b/Minecraft.World/CustomPayloadPacket.cpp index 46fde5aaf..e86f01de0 100644 --- a/Minecraft.World/CustomPayloadPacket.cpp +++ b/Minecraft.World/CustomPayloadPacket.cpp @@ -43,7 +43,7 @@ void CustomPayloadPacket::read(DataInputStream *dis) identifier = readUtf(dis, 20); length = dis->readShort(); - if (length > 0 && length < Short::MAX_VALUE) + if (length > 0 && length <= Short::MAX_VALUE) { if(data.data != nullptr) { diff --git a/Minecraft.World/DataInputStream.cpp b/Minecraft.World/DataInputStream.cpp index 4e4f5cd1b..0cd21a7e9 100644 --- a/Minecraft.World/DataInputStream.cpp +++ b/Minecraft.World/DataInputStream.cpp @@ -303,6 +303,10 @@ wstring DataInputStream::readUTF() int b = stream->read(); unsigned short UTFLength = static_cast(((a & 0xff) << 8) | (b & 0xff)); + const unsigned short MAX_UTF_LENGTH = 32767; + if (UTFLength > MAX_UTF_LENGTH) + return outputString; + //// 4J Stu - I decided while writing DataOutputStream that we didn't need to bother using the UTF8 format //// used in the java libs, and just write in/out as wchar_t all the time diff --git a/Minecraft.World/ExplodePacket.cpp b/Minecraft.World/ExplodePacket.cpp index 07626161f..88bf00c69 100644 --- a/Minecraft.World/ExplodePacket.cpp +++ b/Minecraft.World/ExplodePacket.cpp @@ -52,6 +52,8 @@ void ExplodePacket::read(DataInputStream *dis) //throws IOException r = dis->readFloat(); int count = dis->readInt(); + if (count < 0 || count > 32768) count = 0; + int xp = static_cast(x); int yp = static_cast(y); int zp = static_cast(z); diff --git a/Minecraft.World/GameCommandPacket.cpp b/Minecraft.World/GameCommandPacket.cpp index 17c5316af..ada5b04cd 100644 --- a/Minecraft.World/GameCommandPacket.cpp +++ b/Minecraft.World/GameCommandPacket.cpp @@ -40,7 +40,7 @@ void GameCommandPacket::read(DataInputStream *dis) command = static_cast(dis->readInt()); length = dis->readShort(); - if (length > 0 && length < Short::MAX_VALUE) + if (length > 0 && length <= Short::MAX_VALUE) { if(data.data != nullptr) { diff --git a/Minecraft.World/IntArrayTag.h b/Minecraft.World/IntArrayTag.h index 61ca59d09..64a4d004c 100644 --- a/Minecraft.World/IntArrayTag.h +++ b/Minecraft.World/IntArrayTag.h @@ -35,6 +35,8 @@ public: void load(DataInput *dis, int tagDepth) { int length = dis->readInt(); + if (length < 0 || length > 65536) + length = 0; if ( data.data ) delete[] data.data; data = intArray(length); diff --git a/Minecraft.World/ListTag.h b/Minecraft.World/ListTag.h index 2a0fdebc3..f6e1c6a0a 100644 --- a/Minecraft.World/ListTag.h +++ b/Minecraft.World/ListTag.h @@ -26,21 +26,16 @@ public: void load(DataInput *dis, int tagDepth) { - if (tagDepth > MAX_DEPTH) - { -#ifndef _CONTENT_PACKAGE - printf("Tried to read NBT tag with too high complexity, depth > %d", MAX_DEPTH); - __debugbreak(); -#endif - return; - } type = dis->readByte(); int size = dis->readInt(); + if (size < 0 || size > MAX_DEPTH) + size = 0; list.clear(); for (int i = 0; i < size; i++) { Tag *tag = Tag::newTag(type, L""); + if (tag == nullptr) break; tag->load(dis, tagDepth); list.push_back(tag); } diff --git a/Minecraft.World/Packet.cpp b/Minecraft.World/Packet.cpp index 129024b77..05bf932dc 100644 --- a/Minecraft.World/Packet.cpp +++ b/Minecraft.World/Packet.cpp @@ -267,8 +267,12 @@ void Packet::updatePacketStatsPIX() shared_ptr Packet::getPacket(int id) { - // 4J: Removed try/catch - return idToCreateMap[id](); + auto it = idToCreateMap.find(id); + if (it == idToCreateMap.end()) + { + return nullptr; + } + return it->second(); } void Packet::writeBytes(DataOutputStream *dataoutputstream, byteArray bytes) @@ -334,30 +338,11 @@ shared_ptr Packet::readPacket(DataInputStream *dis, bool isServer) // th if ((isServer && serverReceivedPackets.find(id) == serverReceivedPackets.end()) || (!isServer && clientReceivedPackets.find(id) == clientReceivedPackets.end())) { - 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)); + return nullptr; } packet = getPacket(id); - if (packet == nullptr) assert(false);//throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id)); + if (packet == nullptr) return nullptr;//throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id)); s_lastIds[s_lastIdPos] = id; s_lastIdPos = (s_lastIdPos + 1) % 8; @@ -418,11 +403,9 @@ wstring Packet::readUtf(DataInputStream *dis, int maxLength) // throws IOExcepti { short stringLength = dis->readShort(); - if (stringLength > maxLength) + if (stringLength > maxLength || stringLength <= 0) { - wstringstream stream; - stream << L"Received string length longer than maximum allowed (" << stringLength << " > " << maxLength << ")"; - assert(false); + return L""; // throw new IOException( stream.str() ); } if (stringLength < 0) @@ -531,7 +514,7 @@ shared_ptr Packet::readItem(DataInputStream *dis) { shared_ptr item = nullptr; int id = dis->readShort(); - if (id >= 0) + if (id >= 0 && id < 32000) // todo: should turn Item::ITEM_NUM_COUNT into a global define { int count = dis->readByte(); int damage = dis->readShort(); @@ -569,9 +552,16 @@ void Packet::writeItem(shared_ptr item, DataOutputStream *dos) CompoundTag *Packet::readNbt(DataInputStream *dis) { int size = dis->readShort(); - if (size < 0) return nullptr; + if (size <= 0) return nullptr; + + const int MAX_NBT_SIZE = 32767; + if (size > MAX_NBT_SIZE) return nullptr; byteArray buff(size); - dis->readFully(buff); + if (!dis->readFully(buff)) + { + delete [] buff.data; + return nullptr; + } CompoundTag *result = (CompoundTag *) NbtIo::decompress(buff); delete [] buff.data; return result; diff --git a/Minecraft.World/PreLoginPacket.cpp b/Minecraft.World/PreLoginPacket.cpp index aa6832cad..ddcfe1973 100644 --- a/Minecraft.World/PreLoginPacket.cpp +++ b/Minecraft.World/PreLoginPacket.cpp @@ -62,6 +62,7 @@ void PreLoginPacket::read(DataInputStream *dis) //throws IOException m_friendsOnlyBits = dis->readByte(); m_ugcPlayersVersion = dis->readInt(); m_dwPlayerCount = dis->readByte(); + if( m_dwPlayerCount > MINECRAFT_NET_MAX_PLAYERS ) m_dwPlayerCount = MINECRAFT_NET_MAX_PLAYERS; if( m_dwPlayerCount > 0 ) { m_playerXuids = new PlayerUID[m_dwPlayerCount]; @@ -74,6 +75,7 @@ void PreLoginPacket::read(DataInputStream *dis) //throws IOException { m_szUniqueSaveName[i]=dis->readByte(); } + // m_szUniqueSaveName[m_iSaveNameLen - 1] = 0; // LCEMP does this but I have no idea why, TODO: why? m_serverSettings = dis->readInt(); m_hostIndex = dis->readByte(); diff --git a/Minecraft.World/RemoveEntitiesPacket.cpp b/Minecraft.World/RemoveEntitiesPacket.cpp index 609cfa776..4c5e33131 100644 --- a/Minecraft.World/RemoveEntitiesPacket.cpp +++ b/Minecraft.World/RemoveEntitiesPacket.cpp @@ -21,7 +21,9 @@ RemoveEntitiesPacket::~RemoveEntitiesPacket() void RemoveEntitiesPacket::read(DataInputStream *dis) //throws IOException { - ids = intArray(dis->readByte()); + int count = dis->readByte(); + if(count < 0) count = 0; + ids = intArray(count); for(unsigned int i = 0; i < ids.length; ++i) { ids[i] = dis->readInt(); diff --git a/Minecraft.World/Socket.cpp b/Minecraft.World/Socket.cpp index 2d5b257d0..41ac3702f 100644 --- a/Minecraft.World/Socket.cpp +++ b/Minecraft.World/Socket.cpp @@ -141,6 +141,11 @@ void Socket::pushDataToQueue(const BYTE * pbData, DWORD dwDataSize, bool fromHos // dwDataSize, queueIdx, dwDataSize > 0 ? pbData[0] : 0, networkPlayerSmallId); EnterCriticalSection(&m_queueLockNetwork[queueIdx]); + if (m_queueNetwork[queueIdx].size() + dwDataSize > 2 * 1024 * 1024) + { + LeaveCriticalSection(&m_queueLockNetwork[queueIdx]); + return; + } for( unsigned int i = 0; i < dwDataSize; i++ ) { m_queueNetwork[queueIdx].push(*pbData++); diff --git a/Minecraft.World/SynchedEntityData.cpp b/Minecraft.World/SynchedEntityData.cpp index 69d419b0d..67b9597bb 100644 --- a/Minecraft.World/SynchedEntityData.cpp +++ b/Minecraft.World/SynchedEntityData.cpp @@ -342,7 +342,10 @@ vector > *SynchedEntityData::unpack(Data int currentHeader = input->readByte(); - while (currentHeader != EOF_MARKER) + int itemCount = 0; + const int MAX_ENTITY_DATA_ITEMS = 256; + + while (currentHeader != EOF_MARKER && itemCount < MAX_ENTITY_DATA_ITEMS) { if (result == nullptr) @@ -397,6 +400,7 @@ vector > *SynchedEntityData::unpack(Data break; } result->push_back(item); + itemCount++; currentHeader = input->readByte(); } diff --git a/Minecraft.World/Tag.cpp b/Minecraft.World/Tag.cpp index b594bb0e2..eafdc9e87 100644 --- a/Minecraft.World/Tag.cpp +++ b/Minecraft.World/Tag.cpp @@ -84,27 +84,56 @@ Tag *Tag::readNamedTag(DataInput *dis) Tag *Tag::readNamedTag(DataInput *dis, int tagDepth) { + static __declspec(thread) int depth = 0; + static __declspec(thread) int totalTagCount = 0; + + if (depth == 0) + { + totalTagCount = 0; + } + + depth++; + + if (depth > 256) + { + depth--; + return new EndTag(); + } + + totalTagCount++; + const int MAX_TOTAL_TAGS = 32768; + if (totalTagCount > MAX_TOTAL_TAGS) + { + depth--; + return new EndTag(); + } + byte type = dis->readByte(); - if (type == 0) return new EndTag(); + if (type == 0) { + depth--; + return new EndTag(); + } // 4J Stu - readByte can return -1, so if it's that then also mark as the end tag if(type == 255) { - app.DebugPrintf("readNamedTag read a type of 255\n"); -#ifndef _CONTENT_PACKAGE - __debugbreak(); -#endif + depth--; return new EndTag(); } wstring name = dis->readUTF();//new String(bytes, "UTF-8"); Tag *tag = newTag(type, name); + if (tag == nullptr) { + depth--; + return new EndTag(); + } // short length = dis.readShort(); // byte[] bytes = new byte[length]; // dis.readFully(bytes); tag->load(dis, tagDepth); + depth--; return tag; } diff --git a/Minecraft.World/TextureAndGeometryPacket.cpp b/Minecraft.World/TextureAndGeometryPacket.cpp index bf5eccdbb..1c920ee7a 100644 --- a/Minecraft.World/TextureAndGeometryPacket.cpp +++ b/Minecraft.World/TextureAndGeometryPacket.cpp @@ -121,7 +121,20 @@ void TextureAndGeometryPacket::read(DataInputStream *dis) //throws IOException { textureName = dis->readUTF(); dwSkinID = static_cast(dis->readInt()); - dwTextureBytes = static_cast(dis->readShort()); + + short rawTextureBytes = dis->readShort(); + if (rawTextureBytes <= 0) + { + dwTextureBytes = 0; + } + else + { + dwTextureBytes = (DWORD)(unsigned short)rawTextureBytes; + if (dwTextureBytes > 65536) + { + dwTextureBytes = 0; + } + } if(dwTextureBytes>0) { @@ -134,7 +147,19 @@ void TextureAndGeometryPacket::read(DataInputStream *dis) //throws IOException } uiAnimOverrideBitmask = dis->readInt(); - dwBoxC = static_cast(dis->readShort()); + short rawBoxC = dis->readShort(); + if (rawBoxC <= 0) + { + dwBoxC = 0; + } + else + { + dwBoxC = (DWORD)(unsigned short)rawBoxC; + if (dwBoxC > 256) + { + dwBoxC = 0; // sane limit for skin boxes + } + } if(dwBoxC>0) { diff --git a/Minecraft.World/TexturePacket.cpp b/Minecraft.World/TexturePacket.cpp index eadcb3edb..94c195a05 100644 --- a/Minecraft.World/TexturePacket.cpp +++ b/Minecraft.World/TexturePacket.cpp @@ -37,17 +37,26 @@ void TexturePacket::handle(PacketListener *listener) void TexturePacket::read(DataInputStream *dis) //throws IOException { textureName = dis->readUTF(); - dwBytes = static_cast(dis->readShort()); + short rawBytes = dis->readShort(); + if (rawBytes <= 0) + { + dwBytes = 0; + return; + } + dwBytes = (DWORD)(unsigned short)rawBytes; + if (dwBytes > 65536) + { + dwBytes = 0; + return; + } + + this->pbData= new BYTE [dwBytes]; - if(dwBytes>0) + for(DWORD i=0;ipbData= new BYTE [dwBytes]; - - for(DWORD i=0;ipbData[i] = dis->readByte(); - } + this->pbData[i] = dis->readByte(); } + } void TexturePacket::write(DataOutputStream *dos) //throws IOException diff --git a/Minecraft.World/ThreadName.cpp b/Minecraft.World/ThreadName.cpp index f41beb61d..1f63aaf00 100644 --- a/Minecraft.World/ThreadName.cpp +++ b/Minecraft.World/ThreadName.cpp @@ -22,9 +22,9 @@ void SetThreadName( DWORD dwThreadID, LPCSTR szThreadName ) #if ( defined _WINDOWS64 | defined _DURANGO ) __try { - RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR *)&info ); - } - __except( GetExceptionCode()==0x406D1388 ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER ) + RaiseException(0x406D1388, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR *)&info); + } + __except (EXCEPTION_EXECUTE_HANDLER) { } #endif diff --git a/Minecraft.World/UpdateGameRuleProgressPacket.cpp b/Minecraft.World/UpdateGameRuleProgressPacket.cpp index d88560114..5bb1b3012 100644 --- a/Minecraft.World/UpdateGameRuleProgressPacket.cpp +++ b/Minecraft.World/UpdateGameRuleProgressPacket.cpp @@ -18,7 +18,7 @@ UpdateGameRuleProgressPacket::UpdateGameRuleProgressPacket(ConsoleGameRules::EGa m_auxValue = auxValue; m_dataTag = dataTag; - if(dataLength > 0) + if (dataLength > 0 && dataLength <= 65536) { m_data = byteArray(dataLength); memcpy(m_data.data,data,dataLength); diff --git a/Minecraft.World/compression.cpp b/Minecraft.World/compression.cpp index 8c2e51c08..4122922b9 100644 --- a/Minecraft.World/compression.cpp +++ b/Minecraft.World/compression.cpp @@ -237,9 +237,19 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz unsigned char *dynamicRleBuf = nullptr; HRESULT decompressResult; - if(*pDestSize > rleSize) + unsigned int safeRleSize = max(rleSize, *pDestSize); + + const unsigned int MAX_RLE_ALLOC = 16 * 1024 * 1024; // 16 MB + if (safeRleSize > MAX_RLE_ALLOC) + { + LeaveCriticalSection(&rleDecompressLock); + *pDestSize = 0; + return E_FAIL; + } + + if (safeRleSize > staticRleSize) { - rleSize = *pDestSize; + rleSize = safeRleSize; dynamicRleBuf = new unsigned char[rleSize]; decompressResult = Decompress(dynamicRleBuf, &rleSize, pSource, SrcSize); pucIn = (unsigned char *)dynamicRleBuf; @@ -263,7 +273,7 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz //unsigned char *pucIn = (unsigned char *)rleDecompressBuf; const unsigned char *pucEnd = pucIn + rleSize; unsigned char *pucOut = static_cast(pDestination); - const unsigned char *pucOutEnd = pucOut + *pDestSize; + unsigned char *pucOutEnd = pucOut + *pDestSize; while( pucIn != pucEnd ) { @@ -275,7 +285,11 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz if( count < 3 ) { count++; - if( pucOut + count > pucOutEnd ) break; + if (pucOut + count > pucOutEnd) + { + pucOut = pucOutEnd; + break; + } for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = 255; @@ -286,7 +300,11 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz count++; if( pucIn >= pucEnd ) break; const unsigned char data = *pucIn++; - if( pucOut + count > pucOutEnd ) break; + if (pucOut + count > pucOutEnd) + { + pucOut = pucOutEnd; + break; + } for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = data; @@ -317,7 +335,7 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize, unsigned char *pucIn = static_cast(pSource); const unsigned char *pucEnd = pucIn + SrcSize; unsigned char *pucOut = static_cast(pDestination); - const unsigned char *pucOutEnd = pucOut + *pDestSize; + unsigned char *pucOutEnd = pucOut + *pDestSize; while( pucIn != pucEnd ) { @@ -329,7 +347,11 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize, if( count < 3 ) { count++; - if( pucOut + count > pucOutEnd ) break; + if (pucOut + count > pucOutEnd) + { + pucOut = pucOutEnd; + break; + } for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = 255; @@ -340,7 +362,11 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize, count++; if( pucIn >= pucEnd ) break; const unsigned char data = *pucIn++; - if( pucOut + count > pucOutEnd ) break; + if (pucOut + count > pucOutEnd) + { + pucOut = pucOutEnd; + break; + } for( unsigned int i = 0; i < count; i++ ) { *pucOut++ = data;