From 4a2a4fed91417def811ada8cdad6f9749beca285 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Sun, 9 Nov 2025 00:34:08 +0000 Subject: [PATCH 1/8] Resolve CLocalization / CGUI memory leaks --- Client/core/CGUI.cpp | 4 +++ Client/core/CLocalization.cpp | 50 +++++++++++++++++------------------ Client/core/CLocalization.h | 8 +++--- Client/gui/CGUI_Impl.cpp | 33 ++++++++++++++++++++--- Client/gui/CGUI_Impl.h | 6 +++++ Client/sdk/gui/CGUI.h | 2 ++ 6 files changed, 71 insertions(+), 32 deletions(-) diff --git a/Client/core/CGUI.cpp b/Client/core/CGUI.cpp index 663376601f..486afc1e59 100644 --- a/Client/core/CGUI.cpp +++ b/Client/core/CGUI.cpp @@ -134,6 +134,10 @@ void CLocalGUI::ChangeLocale(const char* szName) m_LastLocaleName = strCanonicalLocale; + // Attempt CEGUI cleanup + if (CGUI* pGUI = CCore::GetSingleton().GetGUI()) + pGUI->Cleanup(); + if (guiWasLoaded) { CreateWindows(guiWasLoaded); diff --git a/Client/core/CLocalization.cpp b/Client/core/CLocalization.cpp index 5f2f271de0..1c8053b38b 100644 --- a/Client/core/CLocalization.cpp +++ b/Client/core/CLocalization.cpp @@ -41,10 +41,7 @@ CLocalization::CLocalization(const SString& strLocale, const SString& strLocaleP CLocalization::~CLocalization() { - for (auto iter : m_LanguageMap) - { - delete iter.second; - } + // m_LanguageMap now uses unique_ptr so cleanup is automatic } // @@ -99,29 +96,32 @@ void CLocalization::SetCurrentLanguage(SString strLocale) CLanguage* CLocalization::GetLanguage(SString strLocale) { strLocale = ValidateLocale(strLocale); - CLanguage* pLanguage = MapFindRef(m_LanguageMap, strLocale); - if (!pLanguage) + auto iter = m_LanguageMap.find(strLocale); + if (iter != m_LanguageMap.end()) + { + return iter->second.get(); + } + + Language Lang = Language::from_name(strLocale); + Lang = Lang ? Lang : Language::from_name("en_US"); + + try + { + std::unique_ptr pLanguage = std::make_unique(m_DictManager.get_dictionary(Lang, MTA_LOCALE_TEXTDOMAIN), Lang.str(), Lang.get_name()); + CLanguage* pLanguagePtr = pLanguage.get(); + m_LanguageMap.emplace(strLocale, std::move(pLanguage)); + return pLanguagePtr; + } + catch (const std::exception& ex) + { + WriteDebugEvent(SString("Localization failed to load dictionary for '%s': %s", strLocale.c_str(), ex.what())); + return (strLocale != "en_US") ? GetLanguage("en_US") : nullptr; + } + catch (...) { - Language Lang = Language::from_name(strLocale); - Lang = Lang ? Lang : Language::from_name("en_US"); - - try - { - pLanguage = new CLanguage(m_DictManager.get_dictionary(Lang, MTA_LOCALE_TEXTDOMAIN), Lang.str(), Lang.get_name()); - MapSet(m_LanguageMap, strLocale, pLanguage); - } - catch (const std::exception& ex) - { - WriteDebugEvent(SString("Localization failed to load dictionary for '%s': %s", strLocale.c_str(), ex.what())); - return (strLocale != "en_US") ? GetLanguage("en_US") : nullptr; - } - catch (...) - { - WriteDebugEvent(SString("Localization failed to load dictionary for '%s': unknown error", strLocale.c_str())); - return (strLocale != "en_US") ? GetLanguage("en_US") : nullptr; - } + WriteDebugEvent(SString("Localization failed to load dictionary for '%s': unknown error", strLocale.c_str())); + return (strLocale != "en_US") ? GetLanguage("en_US") : nullptr; } - return pLanguage; } // diff --git a/Client/core/CLocalization.h b/Client/core/CLocalization.h index 58e49b58dc..0d432c1351 100644 --- a/Client/core/CLocalization.h +++ b/Client/core/CLocalization.h @@ -13,7 +13,7 @@ using namespace tinygettext; #include #include "CLanguage.h" -#define MTA_LOCALE_DIR "MTA/locale/" +#define MTA_LOCALE_DIR "MTA/locale/" #pragma once @@ -42,7 +42,7 @@ class CLocalization : public CLocalizationInterface static void LogCallback(const std::string& str); private: - DictionaryManager m_DictManager; - std::map m_LanguageMap; - CLanguage* m_pCurrentLang{}; + DictionaryManager m_DictManager; + std::map> m_LanguageMap; + CLanguage* m_pCurrentLang{}; }; diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index c05853ea4a..03c4d43d7e 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -144,8 +144,17 @@ CGUI_Impl::~CGUI_Impl() // DO NOT delete m_pRenderer - it's already deleted by System destructor } +void CGUI_Impl::CreateRootWindow() +{ + // Create dummy GUI root + m_pTop = reinterpret_cast(m_pWindowManager->createWindow("DefaultWindow", "guiroot")); + m_pSystem->setGUISheet(m_pTop); +} + void CGUI_Impl::SetSkin(const char* szName) { + m_currentSkin = szName; + if (m_HasSchemeLoaded) { CEGUI::GlobalEventSet::getSingletonPtr()->removeAllEvents(); @@ -165,9 +174,8 @@ void CGUI_Impl::SetSkin(const char* szName) // Destroy any windows we already have CEGUI::WindowManager::getSingleton().destroyAllWindows(); - // Create dummy GUI root - m_pTop = reinterpret_cast(m_pWindowManager->createWindow("DefaultWindow", "guiroot")); - m_pSystem->setGUISheet(m_pTop); + // Clean up CEGUI - this also re-creates the root window + Cleanup(); // Disable single click timeouts m_pSystem->setSingleClickTimeout(100000000.0f); @@ -1797,3 +1805,22 @@ CEGUI::Window* CGUI_Impl::GetMasterWindow(CEGUI::Window* wnd) } return wnd; } + +void CGUI_Impl::Cleanup() +{ + try + { + CleanDeadPool(); + + if (m_pWindowManager) + m_pWindowManager->destroyAllWindows(); + + // Clear redraw structures that may reference old elements + m_RedrawQueue.clear(); + m_RedrawRegistry.clear(); + + // Recreate the root window (destroyed above via destroyAllWindows) + CreateRootWindow(); + } + catch (...) {} +} diff --git a/Client/gui/CGUI_Impl.h b/Client/gui/CGUI_Impl.h index 05c30de9f7..76d0335fee 100644 --- a/Client/gui/CGUI_Impl.h +++ b/Client/gui/CGUI_Impl.h @@ -283,6 +283,9 @@ class CGUI_Impl : public CGUI, public CGUITabList CGUIWindow* LoadLayout(CGUIElement* pParent, const SString& strFilename); bool LoadImageset(const SString& strFilename); + // Cleanup CEGUI active resources (dead pool) + void Cleanup(); + private: friend class CGUIElement_Impl; CGUIButton* _CreateButton(CGUIElement_Impl* pParent = NULL, const char* szCaption = ""); @@ -361,4 +364,7 @@ class CGUI_Impl : public CGUI, public CGUITabList bool m_HasSchemeLoaded; SString m_CurrentSchemeName; CElapsedTime m_RenderOkTimer; + const char* m_currentSkin; + + void CreateRootWindow(); }; diff --git a/Client/sdk/gui/CGUI.h b/Client/sdk/gui/CGUI.h index 217fc004c2..1b7f641226 100644 --- a/Client/sdk/gui/CGUI.h +++ b/Client/sdk/gui/CGUI.h @@ -170,4 +170,6 @@ class CGUI virtual CGUIWindow* LoadLayout(CGUIElement* pParent, const SString& strFilename) = 0; virtual bool LoadImageset(const SString& strFilename) = 0; + + virtual void Cleanup() = 0; }; From 9b6001ff88d0dd78ad4b3163386b8f60ef416343 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Sun, 9 Nov 2025 05:00:33 +0000 Subject: [PATCH 2/8] Remove unused var + duplicate call --- Client/gui/CGUI_Impl.cpp | 5 ----- Client/gui/CGUI_Impl.h | 1 - 2 files changed, 6 deletions(-) diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index 03c4d43d7e..914717d524 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -153,8 +153,6 @@ void CGUI_Impl::CreateRootWindow() void CGUI_Impl::SetSkin(const char* szName) { - m_currentSkin = szName; - if (m_HasSchemeLoaded) { CEGUI::GlobalEventSet::getSingletonPtr()->removeAllEvents(); @@ -171,9 +169,6 @@ void CGUI_Impl::SetSkin(const char* szName) CEGUI::System::getSingleton().setDefaultMouseCursor("CGUI-Images", "MouseArrow"); - // Destroy any windows we already have - CEGUI::WindowManager::getSingleton().destroyAllWindows(); - // Clean up CEGUI - this also re-creates the root window Cleanup(); diff --git a/Client/gui/CGUI_Impl.h b/Client/gui/CGUI_Impl.h index 76d0335fee..6fe4de332d 100644 --- a/Client/gui/CGUI_Impl.h +++ b/Client/gui/CGUI_Impl.h @@ -364,7 +364,6 @@ class CGUI_Impl : public CGUI, public CGUITabList bool m_HasSchemeLoaded; SString m_CurrentSchemeName; CElapsedTime m_RenderOkTimer; - const char* m_currentSkin; void CreateRootWindow(); }; From e4b4a5cd827f46a14df8b044eef295f303a0e984 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Sun, 9 Nov 2025 06:12:07 +0000 Subject: [PATCH 3/8] Update CLocalization.cpp --- Client/core/CLocalization.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/core/CLocalization.cpp b/Client/core/CLocalization.cpp index 1c8053b38b..709d49f2dd 100644 --- a/Client/core/CLocalization.cpp +++ b/Client/core/CLocalization.cpp @@ -41,7 +41,7 @@ CLocalization::CLocalization(const SString& strLocale, const SString& strLocaleP CLocalization::~CLocalization() { - // m_LanguageMap now uses unique_ptr so cleanup is automatic + m_pCurrentLang = nullptr; } // From 60a7ab3523139445830c636894f84988f8b930e3 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Sun, 9 Nov 2025 06:12:09 +0000 Subject: [PATCH 4/8] Update CGUI_Impl.cpp --- Client/gui/CGUI_Impl.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index 914717d524..c270d8df98 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -1807,6 +1807,8 @@ void CGUI_Impl::Cleanup() { CleanDeadPool(); + m_pTop = nullptr; + if (m_pWindowManager) m_pWindowManager->destroyAllWindows(); @@ -1817,5 +1819,8 @@ void CGUI_Impl::Cleanup() // Recreate the root window (destroyed above via destroyAllWindows) CreateRootWindow(); } - catch (...) {} + catch (std::exception& e) + { + OutputDebugLine(SString("CGUI_Impl::Cleanup - Exception: %s", e.what())); + } } From 4fcf64ac3602ef067900a930de4202f3343cb777 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Sun, 9 Nov 2025 06:13:35 +0000 Subject: [PATCH 5/8] Use WriteDebugEvent --- Client/gui/CGUI_Impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index c270d8df98..1db64da004 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -1821,6 +1821,6 @@ void CGUI_Impl::Cleanup() } catch (std::exception& e) { - OutputDebugLine(SString("CGUI_Impl::Cleanup - Exception: %s", e.what())); + WriteDebugEvent(SString("CGUI_Impl::Cleanup - Exception: %s", e.what())); } } From 8d366f626042311a4077714fedbea516bdb76dae Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Sun, 9 Nov 2025 06:29:20 +0000 Subject: [PATCH 6/8] m_pTop checks --- Client/gui/CGUI_Impl.cpp | 59 +++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index 1db64da004..1f1303a3ac 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -330,28 +330,31 @@ bool CGUI_Impl::GetGUIInputEnabled() break; case INPUTMODE_NO_BINDS_ON_EDIT: { - CEGUI::Window* pActiveWindow = m_pTop->getActiveChild(); - if (!pActiveWindow || pActiveWindow == m_pTop || !pActiveWindow->isVisible()) - { - return false; - } - if (pActiveWindow->getType() == "CGUI/Editbox") - { - CEGUI::Editbox* pEditBox = reinterpret_cast(pActiveWindow); - return (!pEditBox->isReadOnly() && pEditBox->hasInputFocus()); - } - else if (pActiveWindow->getType() == "CGUI/MultiLineEditbox") + if (m_pTop) { - CEGUI::MultiLineEditbox* pMultiLineEditBox = reinterpret_cast(pActiveWindow); - return (!pMultiLineEditBox->isReadOnly() && pMultiLineEditBox->hasInputFocus()); - } - else if (pActiveWindow->getType() == CGUIWEBBROWSER_NAME) - { - auto pElement = reinterpret_cast(pActiveWindow->getUserData()); - if (pElement->GetType() == CGUI_WEBBROWSER) + CEGUI::Window* pActiveWindow = m_pTop->getActiveChild(); + if (!pActiveWindow || pActiveWindow == m_pTop || !pActiveWindow->isVisible()) + { + return false; + } + if (pActiveWindow->getType() == "CGUI/Editbox") + { + CEGUI::Editbox* pEditBox = reinterpret_cast(pActiveWindow); + return (!pEditBox->isReadOnly() && pEditBox->hasInputFocus()); + } + else if (pActiveWindow->getType() == "CGUI/MultiLineEditbox") + { + CEGUI::MultiLineEditbox* pMultiLineEditBox = reinterpret_cast(pActiveWindow); + return (!pMultiLineEditBox->isReadOnly() && pMultiLineEditBox->hasInputFocus()); + } + else if (pActiveWindow->getType() == CGUIWEBBROWSER_NAME) { - auto pWebBrowser = reinterpret_cast(pElement); - return pWebBrowser->HasInputFocus(); + auto pElement = reinterpret_cast(pActiveWindow->getUserData()); + if (pElement->GetType() == CGUI_WEBBROWSER) + { + auto pWebBrowser = reinterpret_cast(pElement); + return pWebBrowser->HasInputFocus(); + } } } return false; @@ -583,6 +586,9 @@ eCursorType CGUI_Impl::GetCursorType() void CGUI_Impl::AddChild(CGUIElement_Impl* pChild) { + if (!m_pTop) + return; + m_pTop->addChildWindow(pChild->GetWindow()); } @@ -1156,12 +1162,15 @@ bool CGUI_Impl::Event_MouseButtonDown(const CEGUI::EventArgs& Args) pElement->Event_OnMouseButtonDown(); else { - // If there's no element, we're probably dealing with the root element - CEGUI::Window* pActiveWindow = m_pTop->getActiveChild(); - if (m_pTop == wnd && pActiveWindow) + if (m_pTop) { - // Deactivate active window to trigger onClientGUIBlur - pActiveWindow->deactivate(); + // If there's no element, we're probably dealing with the root element + CEGUI::Window* pActiveWindow = m_pTop->getActiveChild(); + if (m_pTop == wnd && pActiveWindow) + { + // Deactivate active window to trigger onClientGUIBlur + pActiveWindow->deactivate(); + } } } From 748b73611782ae1bd8cf3b6906f0d474bbd80d83 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Sun, 9 Nov 2025 06:29:24 +0000 Subject: [PATCH 7/8] Update CLocalization.h --- Client/core/CLocalization.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Client/core/CLocalization.h b/Client/core/CLocalization.h index 0d432c1351..6e5b46b579 100644 --- a/Client/core/CLocalization.h +++ b/Client/core/CLocalization.h @@ -13,6 +13,7 @@ using namespace tinygettext; #include #include "CLanguage.h" +#include #define MTA_LOCALE_DIR "MTA/locale/" #pragma once From 076c914947bfe5d11704e6826da04bcb5d1d20a2 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Sun, 9 Nov 2025 06:48:51 +0000 Subject: [PATCH 8/8] Update exception handling in CGUI_Impl::Cleanup --- Client/gui/CGUI_Impl.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index 1f1303a3ac..a2c981632d 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -146,6 +146,9 @@ CGUI_Impl::~CGUI_Impl() void CGUI_Impl::CreateRootWindow() { + if (!m_pWindowManager || !m_pSystem) + return; + // Create dummy GUI root m_pTop = reinterpret_cast(m_pWindowManager->createWindow("DefaultWindow", "guiroot")); m_pSystem->setGUISheet(m_pTop); @@ -1828,8 +1831,14 @@ void CGUI_Impl::Cleanup() // Recreate the root window (destroyed above via destroyAllWindows) CreateRootWindow(); } - catch (std::exception& e) + catch (const std::exception& e) { WriteDebugEvent(SString("CGUI_Impl::Cleanup - Exception: %s", e.what())); + m_pTop = nullptr; + } + catch (...) + { + WriteDebugEvent("CGUI_Impl::Cleanup() failed with unknown exception"); + m_pTop = nullptr; } }