diff --git a/Client/core/CCore.cpp b/Client/core/CCore.cpp index 1128bfc0b6..e7be11bbbd 100644 --- a/Client/core/CCore.cpp +++ b/Client/core/CCore.cpp @@ -2453,3 +2453,107 @@ std::shared_ptr CCore::GetDiscord() { return m_pDiscordRichPresence; } + +// Get File Cache Path from coreconfig.xml or fallback to registry +SString CCore::GetFileCachePath() +{ + // First check coreconfig.xml + CXMLNode* root = GetConfig(); + if (root) + { + CXMLNode* fileCachePath = root->FindSubNode("file_cache_path"); + if (fileCachePath) + { + SString path = fileCachePath->GetTagContent(); + if (!path.empty()) + { + // Check if custom path still exists + if (DirectoryExists(path)) + { + return path; + } + else + { + // Custom path was deleted, remove from config and fallback to registry + root->DeleteSubNode(fileCachePath); + SaveConfig(); + } + } + } + } + + // Fallback to registry + return GetCommonRegistryValue("", "File Cache Path"); +} + +// Set File Cache Path in coreconfig.xml +bool CCore::SetFileCachePath(const SString& path) +{ + CXMLNode* root = GetConfig(); + if (!root) + return false; + + CXMLNode* fileCachePath = root->FindSubNode("file_cache_path"); + if (!fileCachePath) + fileCachePath = root->CreateSubNode("file_cache_path"); + + fileCachePath->SetTagContent(path); + SaveConfig(); + return true; +} + +// Remove File Cache Path from coreconfig.xml to fallback to registry +bool CCore::ResetFileCachePath() +{ + CXMLNode* root = GetConfig(); + if (!root) + return false; + + CXMLNode* fileCachePath = root->FindSubNode("file_cache_path"); + if (fileCachePath) + { + root->DeleteSubNode(fileCachePath); + SaveConfig(); + } + + return true; +} + +// Validate a file cache path +bool CCore::ValidateFileCachePath(const SString& path, SString& error) +{ + if (path.empty()) + { + error = "Path cannot be empty"; + return false; + } + + // Check if directory exists + if (!DirectoryExists(path)) + { + error = "Directory does not exist"; + return false; + } + + // Check if path is not inside MTA directory to avoid conflicts + SString mtaPath = GetMTADataPath(); + SString normalizedPath = PathConform(path); + SString normalizedMTAPath = PathConform(mtaPath); + + if (normalizedPath.BeginsWithI(normalizedMTAPath)) + { + error = "Path cannot be inside MTA installation folder to avoid conflicts"; + return false; + } + + // Check if writable + SString testFile = PathJoin(path, "_test_write.tmp"); + if (!FileSave(testFile, "test")) + { + error = "Directory is not writable"; + return false; + } + FileDelete(testFile); + + return true; +} diff --git a/Client/core/CCore.h b/Client/core/CCore.h index 7b9ee431fd..2bbb80a0b7 100644 --- a/Client/core/CCore.h +++ b/Client/core/CCore.h @@ -113,6 +113,12 @@ class CCore : public CCoreInterface, public CSingleton void SaveConfig(bool bWaitUntilFinished = false); + // File Cache Path management + SString GetFileCachePath(); + bool SetFileCachePath(const SString& path); + bool ResetFileCachePath(); + bool ValidateFileCachePath(const SString& path, SString& error); + // Debug void DebugEcho(const char* szText); void DebugPrintf(const char* szFormat, ...); diff --git a/Client/core/CSettings.cpp b/Client/core/CSettings.cpp index fe73aeaba8..b8915d4e06 100644 --- a/Client/core/CSettings.cpp +++ b/Client/core/CSettings.cpp @@ -307,6 +307,8 @@ void CSettings::ResetGuiPointers() m_pCachePathLabel = NULL; m_pCachePathValue = NULL; m_pCachePathShowButton = NULL; + m_pCachePathBrowseButton = NULL; + m_pCachePathResetButton = NULL; m_pLabelMasterVolume = NULL; m_pLabelRadioVolume = NULL; @@ -1857,15 +1859,27 @@ void CSettings::CreateGUI() m_pCachePathLabel->AutoSize(); m_pCachePathShowButton = reinterpret_cast(pManager->CreateButton(pTabAdvanced, _("Show in Explorer"))); - m_pCachePathShowButton->SetPosition(CVector2D(vecTemp.fX + fIndentX + 1, vecTemp.fY - 1)); - m_pCachePathShowButton->AutoSize(NULL, 20.0f, 8.0f); + m_pCachePathShowButton->SetPosition(CVector2D(vecTemp.fX + fIndentX, vecTemp.fY)); + m_pCachePathShowButton->SetSize(CVector2D(120.0f, 20.0f)); m_pCachePathShowButton->SetClickHandler(GUI_CALLBACK(&CSettings::OnCachePathShowButtonClick, this)); m_pCachePathShowButton->SetZOrderingEnabled(false); - m_pCachePathShowButton->GetSize(vecSize); - SString strFileCachePath = GetCommonRegistryValue("", "File Cache Path"); - m_pCachePathValue = reinterpret_cast(pManager->CreateLabel(pTabAdvanced, strFileCachePath)); - m_pCachePathValue->SetPosition(CVector2D(vecTemp.fX + fIndentX + vecSize.fX + 10, vecTemp.fY + 3)); + m_pCachePathBrowseButton = reinterpret_cast(pManager->CreateButton(pTabAdvanced, _("Change Path"))); + m_pCachePathBrowseButton->SetPosition(CVector2D(vecTemp.fX + fIndentX + 130, vecTemp.fY)); + m_pCachePathBrowseButton->SetSize(CVector2D(120.0f, 20.0f)); + m_pCachePathBrowseButton->SetClickHandler(GUI_CALLBACK(&CSettings::OnCachePathBrowseButtonClick, this)); + m_pCachePathBrowseButton->SetZOrderingEnabled(false); + + m_pCachePathResetButton = reinterpret_cast(pManager->CreateButton(pTabAdvanced, _("Reset Path"))); + m_pCachePathResetButton->SetPosition(CVector2D(vecTemp.fX + fIndentX + 260, vecTemp.fY)); + m_pCachePathResetButton->SetSize(CVector2D(120.0f, 20.0f)); + m_pCachePathResetButton->SetClickHandler(GUI_CALLBACK(&CSettings::OnCachePathResetButtonClick, this)); + m_pCachePathResetButton->SetZOrderingEnabled(false); + vecTemp.fY += fLineHeight; + + SString fileCachePath = CCore::GetSingleton().GetFileCachePath(); + m_pCachePathValue = reinterpret_cast(pManager->CreateLabel(pTabAdvanced, fileCachePath)); + m_pCachePathValue->SetPosition(CVector2D(vecTemp.fX + fIndentX, vecTemp.fY)); m_pCachePathValue->SetFont("default-small"); m_pCachePathValue->AutoSize(); vecTemp.fY += fLineHeight; @@ -5586,9 +5600,189 @@ bool CSettings::OnUpdateButtonClick(CGUIElement* pElement) bool CSettings::OnCachePathShowButtonClick(CGUIElement* pElement) { - SString strFileCachePath = GetCommonRegistryValue("", "File Cache Path"); - if (DirectoryExists(strFileCachePath)) - ShellExecuteNonBlocking("open", strFileCachePath); + SString fileCachePath = CCore::GetSingleton().GetFileCachePath(); + if (DirectoryExists(fileCachePath)) + ShellExecuteNonBlocking("open", fileCachePath); + return true; +} + +struct BrowseFolderThreadData +{ + WString title; + wchar_t result[MAX_PATH]; + bool success; + HWND parentWindow; +}; + +DWORD WINAPI BrowseFolderThread(LPVOID lpParam) +{ + BrowseFolderThreadData* data = (BrowseFolderThreadData*)lpParam; + + CoInitialize(NULL); + ShowCursor(TRUE); + SetCursor(LoadCursor(NULL, IDC_ARROW)); + + BROWSEINFOW bi = {0}; + bi.lpszTitle = data->title; + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; + bi.hwndOwner = data->parentWindow; + + LPITEMIDLIST pidl = SHBrowseForFolderW(&bi); + + if (pidl != NULL) + { + if (SHGetPathFromIDListW(pidl, data->result)) + { + data->success = true; + } + CoTaskMemFree(pidl); + } + + CoUninitialize(); + return 0; +} + +bool CSettings::OnCachePathBrowseButtonClick(CGUIElement* pElement) +{ + SString currentPath = CCore::GetSingleton().GetFileCachePath(); + HWND wnd = CCore::GetSingleton().GetHookedWindow(); + + bool wasFullscreen = !GetVideoModeManager()->IsWindowed(); + if (wasFullscreen) + { + ShowWindow(wnd, SW_MINIMIZE); + } + + CGUI* gui = CCore::GetSingleton().GetGUI(); + bool wasCursorEnabled = gui->IsCursorEnabled(); + gui->SetCursorEnabled(false); + + BrowseFolderThreadData threadData; + threadData.title = FromUTF8(_("Select a folder for Client resource files (must be outside MTA folder)")); + threadData.result[0] = 0; + threadData.success = false; + threadData.parentWindow = NULL; + + EnableWindow(wnd, FALSE); + + HANDLE thread = CreateThread(NULL, 0, BrowseFolderThread, &threadData, 0, NULL); + if (thread) + { + MSG msg; + DWORD waitResult = WaitForSingleObject(thread, INFINITE); + while (waitResult) + { + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + waitResult = WaitForSingleObject(thread, 0); + } + CloseHandle(thread); + } + + EnableWindow(wnd, TRUE); + SetForegroundWindow(wnd); + + if (wasCursorEnabled) + gui->SetCursorEnabled(true); + + if (wasFullscreen) + { + ShowWindow(wnd, SW_RESTORE); + } + + if (threadData.success) + { + SString selectedPath = ToUTF8(threadData.result); + SString defaultPath = GetCommonRegistryValue("", "File Cache Path"); + SString error; + + if (!defaultPath.empty() && PathConform(selectedPath) == PathConform(defaultPath)) + { + if (PathConform(currentPath) != PathConform(defaultPath)) + { + if (CCore::GetSingleton().ResetFileCachePath()) + { + m_pCachePathValue->SetText(defaultPath); + m_pCachePathValue->AutoSize(); + + CCore::GetSingleton().ShowMessageBox(_("File Cache Path Reset"), + _("File cache path has been reset to default. Changes will take effect after restarting MTA."), + MB_BUTTON_OK | MB_ICON_INFO); + + ShowRestartQuestion(); + } + } + else + { + CCore::GetSingleton().ShowMessageBox(_("Info"), _("You are already using the default file cache path."), MB_BUTTON_OK | MB_ICON_INFO); + } + } + else if (CCore::GetSingleton().ValidateFileCachePath(selectedPath, error)) + { + if (PathConform(currentPath) != PathConform(selectedPath)) + { + if (CCore::GetSingleton().SetFileCachePath(selectedPath)) + { + m_pCachePathValue->SetText(selectedPath); + m_pCachePathValue->AutoSize(); + + CCore::GetSingleton().ShowMessageBox(_("File Cache Path Changed"), + _("The file cache path has been successfully changed. Changes will take effect after restarting MTA."), + MB_BUTTON_OK | MB_ICON_INFO); + + ShowRestartQuestion(); + } + else + { + CCore::GetSingleton().ShowMessageBox(_("Error"), _("Failed to save the file cache path to configuration."), MB_BUTTON_OK | MB_ICON_ERROR); + } + } + else + { + CCore::GetSingleton().ShowMessageBox(_("Info"), _("The selected path is the same as the current file cache path."), + MB_BUTTON_OK | MB_ICON_INFO); + } + } + else + { + CCore::GetSingleton().ShowMessageBox(_("Invalid Path"), error, MB_BUTTON_OK | MB_ICON_ERROR); + } + } + + return true; +} + +bool CSettings::OnCachePathResetButtonClick(CGUIElement* pElement) +{ + SString currentPath = CCore::GetSingleton().GetFileCachePath(); + SString defaultPath = GetCommonRegistryValue("", "File Cache Path"); + + if (!defaultPath.empty() && PathConform(currentPath) == PathConform(defaultPath)) + { + CCore::GetSingleton().ShowMessageBox(_("Info"), _("You are already using the default file cache path."), MB_BUTTON_OK | MB_ICON_INFO); + return true; + } + + if (CCore::GetSingleton().ResetFileCachePath()) + { + defaultPath = CCore::GetSingleton().GetFileCachePath(); + m_pCachePathValue->SetText(defaultPath); + m_pCachePathValue->AutoSize(); + + CCore::GetSingleton().ShowMessageBox(_("File Cache Path Reset"), + _("File cache path has been reset to default. Changes will take effect after restarting MTA."), + MB_BUTTON_OK | MB_ICON_INFO); + + ShowRestartQuestion(); + } + else + { + CCore::GetSingleton().ShowMessageBox(_("Error"), _("Failed to reset the file cache path."), MB_BUTTON_OK | MB_ICON_ERROR); + } + return true; } diff --git a/Client/core/CSettings.h b/Client/core/CSettings.h index 3c23e99a25..d8ccc8ee09 100644 --- a/Client/core/CSettings.h +++ b/Client/core/CSettings.h @@ -256,6 +256,8 @@ class CSettings CGUILabel* m_pCachePathLabel; CGUILabel* m_pCachePathValue; CGUIButton* m_pCachePathShowButton; + CGUIButton* m_pCachePathBrowseButton; + CGUIButton* m_pCachePathResetButton; CGUILabel* m_pLabelMasterVolume; CGUILabel* m_pLabelRadioVolume; CGUILabel* m_pLabelSFXVolume; @@ -419,6 +421,8 @@ class CSettings bool OnChatAlphaChanged(CGUIElement* pElement); bool OnUpdateButtonClick(CGUIElement* pElement); bool OnCachePathShowButtonClick(CGUIElement* pElement); + bool OnCachePathBrowseButtonClick(CGUIElement* pElement); + bool OnCachePathResetButtonClick(CGUIElement* pElement); bool OnMouseSensitivityChanged(CGUIElement* pElement); bool OnVerticalAimSensitivityChanged(CGUIElement* pElement); bool OnBrowserBlacklistAdd(CGUIElement* pElement); diff --git a/Client/core/StdInc.h b/Client/core/StdInc.h index ed06205655..b81a7af8cd 100644 --- a/Client/core/StdInc.h +++ b/Client/core/StdInc.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index 47d7461cdd..5b2f236b88 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -6766,21 +6766,21 @@ void CClientGame::SetFileCacheRoot() else { // Get shared directory - SString strFileCachePath = GetCommonRegistryValue("", "File Cache Path"); + SString fileCachePath = g_pCore->GetFileCachePath(); // Check exists - if (!strFileCachePath.empty() && DirectoryExists(strFileCachePath)) + if (!fileCachePath.empty() && DirectoryExists(fileCachePath)) { // Check writable - SString strTestFileName = PathJoin(strFileCachePath, "resources", "_test.tmp"); - if (FileSave(strTestFileName, "x")) + SString testFileName = PathJoin(fileCachePath, "resources", "_test.tmp"); + if (FileSave(testFileName, "x")) { - FileDelete(strTestFileName); - strTestFileName = PathJoin(strFileCachePath, "priv", "_test.tmp"); - if (FileSave(strTestFileName, "x")) + FileDelete(testFileName); + testFileName = PathJoin(fileCachePath, "priv", "_test.tmp"); + if (FileSave(testFileName, "x")) { - FileDelete(strTestFileName); + FileDelete(testFileName); // Use shared directory - m_strFileCacheRoot = strFileCachePath; + m_strFileCacheRoot = fileCachePath; AddReportLog(7411, SString("CClientGame::SetFileCacheRoot - Is shared '%s'", *m_strFileCacheRoot)); return; } @@ -6791,10 +6791,10 @@ void CClientGame::SetFileCacheRoot() m_strFileCacheRoot = GetModRoot(); SetCommonRegistryValue("", "File Cache Path", m_strFileCacheRoot); - if (strFileCachePath.empty()) + if (fileCachePath.empty()) AddReportLog(7412, SString("CClientGame::SetFileCacheRoot - Initial setting '%s'", *m_strFileCacheRoot)); else - AddReportLog(7413, SString("CClientGame::SetFileCacheRoot - Change shared from '%s' to '%s'", *strFileCachePath, *m_strFileCacheRoot)); + AddReportLog(7413, SString("CClientGame::SetFileCacheRoot - Change shared from '%s' to '%s'", *fileCachePath, *m_strFileCacheRoot)); } } diff --git a/Client/sdk/core/CCoreInterface.h b/Client/sdk/core/CCoreInterface.h index 07f3848e61..880b418d03 100644 --- a/Client/sdk/core/CCoreInterface.h +++ b/Client/sdk/core/CCoreInterface.h @@ -135,6 +135,11 @@ class CCoreInterface virtual void SaveConfig(bool bWaitUntilFinished = false) = 0; virtual void UpdateRecentlyPlayed() = 0; + virtual SString GetFileCachePath() = 0; + virtual bool SetFileCachePath(const SString& path) = 0; + virtual bool ResetFileCachePath() = 0; + virtual bool ValidateFileCachePath(const SString& path, SString& error) = 0; + virtual void SwitchRenderWindow(HWND hWnd, HWND hWndInput) = 0; virtual void SetCenterCursor(bool bEnabled) = 0; virtual bool IsTimingCheckpoints() = 0;