From 7706a2a99f66ac3a00e2fac86fe1cb44ce10ede4 Mon Sep 17 00:00:00 2001 From: Mulambo Date: Fri, 11 Sep 2026 15:11:21 +0200 Subject: [PATCH 1/8] Reject dropping container onto its child container (#1598) Fixes crashing sphereserver, when trying to put container into its child container. This behavior is illegal and cannot be done manually, only by using scripts or raw packets. --- src/game/clients/CClientEvent.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/game/clients/CClientEvent.cpp b/src/game/clients/CClientEvent.cpp index e6b60bf6e..3541c3804 100644 --- a/src/game/clients/CClientEvent.cpp +++ b/src/game/clients/CClientEvent.cpp @@ -406,7 +406,24 @@ void CClient::Event_Item_Drop( CUID uidItem, CPointMap pt, CUID uidOn, uchar gri if ( pContItem != nullptr ) { - // bug with shifting selling list by gold coins + // If player is trying to put container inside its child, it will crash sphere due to circular weight calculation. + auto *const pItemIsContainer = dynamic_cast(pItem); + if (pItemIsContainer && pItemIsContainer->IsItemInside(pContItem)) + { + Event_Item_Drop_Fail(pItem); + // Log the error. + g_Log.EventError("Player '%s' (UID 0%x) trying to drop container '%s' (UID 0%x) inside its child container '%s' (UID 0%x).\n", + m_pChar->GetName(), static_cast(m_pChar->GetUID()), + pItem->GetName(), static_cast(pItem->GetUID()), + pContItem->GetName(), static_cast(pContItem->GetUID()) + ); + // Disconnect character. This behavior cannot be done manually and script might be called indefinitely. + CharDisconnect(); + GetNetState()->markReadClosed(); + return; + } + + // Bug with shifting selling list by gold coins. if ( pContItem->IsType(IT_EQ_VENDOR_BOX) && ( pItem->IsType(IT_GOLD) || pItem->IsType(IT_COIN) )) { From b6c9212882dba3eb984c0584343c991f18cd3106 Mon Sep 17 00:00:00 2001 From: Mulambo Date: Fri, 11 Sep 2026 15:14:00 +0200 Subject: [PATCH 2/8] Fix setting client resolution (#1599) Only calculate height if width was sent by client and height wasn't, otherwise it takes client sent resolution for both dimensions. --- src/network/receive.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network/receive.cpp b/src/network/receive.cpp index 1106e32a0..bcddce6b9 100644 --- a/src/network/receive.cpp +++ b/src/network/receive.cpp @@ -2600,7 +2600,8 @@ bool PacketScreenSize::onReceive(CNetState* net) skip(2); //DEBUG_MSG(("PacketScreenSize::onReceive 0x%hx - 0x%hx (%hu-%hu)\n", x, y, x, y)); - if (net->isClientVersionNumber(MINCLIVER_NEWBOOK)) + // Set default height only if client send width and no height. + if (net->isClientVersionNumber(MINCLIVER_NEWBOOK) && x > 0 && y == 0) { switch (x) { From 64cfe7a616a2813b398d45b756abea5e8057f128 Mon Sep 17 00:00:00 2001 From: Mulambo Date: Mon, 14 Sep 2026 09:41:09 +0200 Subject: [PATCH 3/8] Check if the container owner really exists (#1600) --- src/game/items/CItemContainer.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/game/items/CItemContainer.cpp b/src/game/items/CItemContainer.cpp index 2c13b964a..5914a75de 100644 --- a/src/game/items/CItemContainer.cpp +++ b/src/game/items/CItemContainer.cpp @@ -906,8 +906,12 @@ bool CItemContainer::CanContainerHold( const CItem *pItem, const CChar *pCharMsg int iMaxWeight = m_ModMaxWeight; if ((GetContainedLayer() == LAYER_PACK) && !(g_Cfg.m_iBackpackOverload <= -1)) { - CChar* pCharContainerOwner = static_cast(GetContainer()); // Owner of the container - iMaxWeight += (g_Cfg.Calc_MaxCarryWeight(pCharContainerOwner) + g_Cfg.m_iBackpackOverload); + // Allow more weight based on settings. + auto const *pCharContainerOwner = dynamic_cast(GetContainer()); + if (pCharContainerOwner) + { + iMaxWeight += (g_Cfg.Calc_MaxCarryWeight(pCharContainerOwner) + g_Cfg.m_iBackpackOverload); + } } if (iMaxWeight > 0 && (GetTotalWeight() + pItem->GetWeight() > iMaxWeight)) { From b054d193b8ebece2c22395f5a52f5ae2b7740b47 Mon Sep 17 00:00:00 2001 From: David Kindl Date: Mon, 14 Sep 2026 09:44:35 +0200 Subject: [PATCH 4/8] Move character sleeping status ([Temporary msg] in log) back to debug build --- src/game/chars/CCharAct.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/game/chars/CCharAct.cpp b/src/game/chars/CCharAct.cpp index c8c431fc0..59bd351fb 100644 --- a/src/game/chars/CCharAct.cpp +++ b/src/game/chars/CCharAct.cpp @@ -5974,7 +5974,7 @@ bool CChar::_OnTick() const bool fTickableState = _CanTick(false); const bool fSleeping = _IsSleeping(); -//#ifdef _DEBUG +#ifdef _DEBUG if (!fTickableState || fSleeping) { g_Log.EventDebug("[Temporary msg] Char '%s' (UID=0x%" PRIx32 ") at P=%s is in the ticking list with unusual CanTick=%d, SleepingState=%d.\n", @@ -5983,10 +5983,11 @@ bool CChar::_OnTick() (int)fTickableState, (int)fSleeping ); } -//#endif +#endif if (!fTickableState) { +#ifdef _DEBUG // It can happen that i'm in the ticking list, but for various reasons right now i'm in a non-tickable state. // Among the reasons why i can't tick, though, there cannot be being in a sleeping state: when a char goes into sleeping state // it should also be removed from the list (it happens in _GoSleep()). @@ -5995,15 +5996,16 @@ bool CChar::_OnTick() (int)GetTopSector()->IsSleeping()); } //ASSERT(!fSleeping); +#endif - if (GetTopSector()->IsSleeping() && !g_Rand.Get16ValFast(15)) + if (GetTopSector()->IsSleeping() && !g_Rand.Get16ValFast(15)) { // Do not make the char sleep right when it enters a sleeping sector. Doing this // will lead to an accumulation of npcs at the edge of the new sector. -//#ifdef _DEBUG +#ifdef _DEBUG g_Log.EventDebug("[Temporary msg] Sent CChar to sleep (random), to be awaken alongside its sector.\n"); -//#endif +#endif _SetTimeout(1); //Make it tick after sector's awakening. _GoSleep(); From d9dd9e6ed763b3cd220ee686959b7b410a3d3c50 Mon Sep 17 00:00:00 2001 From: David Kindl Date: Mon, 14 Sep 2026 09:45:23 +0200 Subject: [PATCH 5/8] Fix typo --- src/game/chars/CChar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/chars/CChar.cpp b/src/game/chars/CChar.cpp index cb55c579a..3609940dc 100644 --- a/src/game/chars/CChar.cpp +++ b/src/game/chars/CChar.cpp @@ -589,7 +589,7 @@ void CChar::ClearPlayer() { if (g_Serv.GetServerMode() != ServMode::Exiting) { - g_Log.EventWarn("Character '%s'(UID 0%x) on account '%s' as been deleted.\n", GetName(), (dword)GetUID(), pAccount->GetName()); + g_Log.EventWarn("Character '%s'(UID 0%x) on account '%s' has been deleted.\n", GetName(), (dword)GetUID(), pAccount->GetName()); } pAccount->DetachChar(this); // unlink me from my account. From 5e000b40d149ec7c4bf406b81464e7bd891c3c43 Mon Sep 17 00:00:00 2001 From: David Kindl Date: Mon, 14 Sep 2026 09:51:50 +0200 Subject: [PATCH 6/8] Added new client encryptions (117) --- src/sphereCrypt.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sphereCrypt.ini b/src/sphereCrypt.ini index 45a953521..0a60b5234 100644 --- a/src/sphereCrypt.ini +++ b/src/sphereCrypt.ini @@ -23,6 +23,7 @@ ENC_LOGIN 4 // Rotation cipher used for the Login Server crypt by every client, [SPHERECRYPT] // Classic Clients. +70011700 0366150AD 0AC9E5E7F ENC_TFISH // 7.0.117 70011600 0369A82BD 0AC15127F ENC_TFISH // 7.0.116 70011500 036DCF0CD 0AC2CDE7F ENC_TFISH // 7.0.115 70011400 037062ADD 0ACCA227F ENC_TFISH // 7.0.114 @@ -239,6 +240,7 @@ ENC_LOGIN 4 // Rotation cipher used for the Login Server crypt by every client, 1232700 0D47BD45C 034081CD6 ENC_LOGIN // 1.23.27 // Enhanced Clients. +670011700 0146150AD 0BD9E5E7F ENC_TFISH // 4.0.117 670011600 0149A82BD 0BD15127F ENC_TFISH // 4.0.116 670011500 014DCF0CD 0BD2CDE7F ENC_TFISH // 4.0.115 670011400 015062ADD 0BDCA227F ENC_TFISH // 4.0.114 From cf60700aff6bd5db4d5458d3bd75fa5727b89159 Mon Sep 17 00:00:00 2001 From: David Kindl Date: Mon, 14 Sep 2026 10:12:37 +0200 Subject: [PATCH 7/8] Chance to hit in `CombatDamageEra=0` (Sphere custom) now returns calculated value, instead of random number from that value. --- Changelog.txt | 4 ++++ src/game/CResourceCalc.cpp | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index 3bca801c1..4e3e2732e 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -4144,3 +4144,7 @@ When setting a property like MORE to the a spell or skill defname, trying to rea 23-07-2026, canerksk - Added: New local variable local.Sound to @SpellFail/@Fail triggers (R/W). If you set local.sound = 0, no sound will be played. + +14-09-2026, Mulambo + WARNING! POTENTIALLY BREAKING CHANGE! +- Changed: Chance to hit in `CombatDamageEra=0` (Sphere custom) now returns calculated value, instead of random number from that value. diff --git a/src/game/CResourceCalc.cpp b/src/game/CResourceCalc.cpp index c5b574411..8487aefdd 100644 --- a/src/game/CResourceCalc.cpp +++ b/src/game/CResourceCalc.cpp @@ -193,8 +193,7 @@ int CServerConfig::Calc_CombatChanceToHit(const CChar * pChar, const CChar * pCh if (pCharTarg->IsStatFlag(STATF_SLEEPING | STATF_FREEZE) && iChance < 80) iChance = 80; - // Always need to have some chance (@todo Remove this horrendous random). - return g_Rand.GetVal(iChance); + return iChance; } // Pre-AOS formula. case 1: From dd28a0ad53258adb55b548b1bd4df989065a1fe4 Mon Sep 17 00:00:00 2001 From: Mulambo Date: Fri, 18 Sep 2026 08:16:02 +0200 Subject: [PATCH 8/8] Check for mapId when loading diff only if file exists (#1601) --- src/common/CServerMap.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/common/CServerMap.cpp b/src/common/CServerMap.cpp index 7a34074e5..5920d7f7b 100644 --- a/src/common/CServerMap.cpp +++ b/src/common/CServerMap.cpp @@ -694,16 +694,15 @@ void CServerMapDiffCollection::LoadMapDiffs() if ( !g_MapList.IsMapSupported( m ) ) continue; - const int map = g_MapList.GetMapID(m); - // Load Mapdif Files { - CSFile * pFileMapdif = &(g_Install.m_Mapdif[map]); - CSFile * pFileMapdifl = &(g_Install.m_Mapdifl[map]); + CSFile *pFileMapdif = &g_Install.m_Mapdif[m]; + CSFile *pFileMapdifl = &g_Install.m_Mapdifl[m]; // Check that the relevant dif files are available if ( pFileMapdif->IsFileOpen() && pFileMapdifl->IsFileOpen() ) { + const int map = g_MapList.GetMapID(m); // Make sure that we're at the beginning of the files pFileMapdif->SeekToBegin(); pFileMapdifl->SeekToBegin(); @@ -744,14 +743,15 @@ void CServerMapDiffCollection::LoadMapDiffs() // Load Stadif Files { - CSFile * pFileStadif = &(g_Install.m_Stadif[map]); - CSFile * pFileStadifl = &(g_Install.m_Stadifl[map]); - CSFile * pFileStadifi = &(g_Install.m_Stadifi[map]); + CSFile *pFileStadif = &g_Install.m_Stadif[m]; + CSFile *pFileStadifl = &g_Install.m_Stadifl[m]; + CSFile *pFileStadifi = &g_Install.m_Stadifi[m]; // Check that the relevant dif files are available if ( !pFileStadif->IsFileOpen() || !pFileStadifl->IsFileOpen() || !pFileStadifi->IsFileOpen() ) continue; + const int map = g_MapList.GetMapID(m); // Make sure that we're at the beginning of the files pFileStadif->SeekToBegin(); pFileStadifl->SeekToBegin();