Skip to content

Commit 0afefae

Browse files
authored
Merge branch 'master' into fix/skip_av_wine
2 parents 9a0e451 + ceaadd4 commit 0afefae

File tree

24 files changed

+627
-344
lines changed

24 files changed

+627
-344
lines changed

Client/ceflauncher/Main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ struct HINSTANCE__;
2727
using HINSTANCE = HINSTANCE__*;
2828
using LPSTR = char*;
2929

30-
[[nodiscard("InitCEF return value must be used")]] __declspec(dllimport) auto InitCEF() -> int;
30+
extern "C" [[nodiscard("InitCEF return value must be used")]] __declspec(dllimport) int __cdecl InitCEF();
3131

3232
// Users are faced with vague crashes in CEFLauncher.exe, so rather than over-engineering all this is intended
3333
// Do note that CEFLauncher.exe ends up hosting any GPU rendering processes of CEF

Client/ceflauncher_DLL/Main.cpp

Lines changed: 127 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,111 +9,170 @@
99
*
1010
*****************************************************************************/
1111

12+
// #define CEF_ENABLE_SANDBOX
13+
14+
#include <atomic>
15+
1216
#define WIN32_NO_STATUS
1317
#define WIN32_LEAN_AND_MEAN
1418
#include <Windows.h>
19+
1520
#undef WIN32_NO_STATUS
1621
#include <ntstatus.h>
17-
#include <winnt.h>
1822
#include <winternl.h>
1923
#include <delayimp.h>
24+
2025
#include "CCefApp.h"
21-
#include <string>
22-
#include <cef3/cef/include/cef_sandbox_win.h>
23-
#include <SharedUtil.h>
26+
#include "SharedUtil.h"
2427

25-
// #define CEF_ENABLE_SANDBOX
2628
#ifdef CEF_ENABLE_SANDBOX
27-
#pragma comment(lib, "cef_sandbox.lib")
29+
#include <cef3/cef/include/cef_sandbox_win.h>
30+
#pragma comment(lib, "cef_sandbox.lib")
2831
#endif
2932

30-
DWORD WINAPI CheckParentProcessAliveness(LPVOID);
33+
// Return codes
34+
inline constexpr int CEF_INIT_SUCCESS = 0;
35+
inline constexpr int CEF_INIT_ERROR_NO_BASE_DIR = -1;
36+
inline constexpr int CEF_INIT_ERROR_DLL_LOAD_FAILED = -2;
3137

32-
int _declspec(dllexport) InitCEF()
33-
{
34-
// Get MTA base directory and set DLL directory to MTA folder
35-
const SString strBaseDir = SharedUtil::GetMTAProcessBaseDir();
36-
37-
if (strBaseDir.empty())
38-
{
39-
// Unable to determine base directory - CEF cannot initialize
40-
return -1;
41-
}
42-
43-
const SString strMTADir = SharedUtil::PathJoin(strBaseDir, "MTA");
44-
45-
SetDllDirectoryW(SharedUtil::FromUTF8(strMTADir));
38+
inline constexpr DWORD CEF_PARENT_CHECK_INTERVAL = 1000;
39+
inline constexpr const char* CEF_DLL_NAME = "libcef.dll";
40+
inline constexpr const char* CEF_MTA_SUBDIR = "MTA";
4641

47-
// Load libcef.dll from the DLL directory
48-
assert(SUCCEEDED(__HrLoadAllImportsForDll("libcef.dll")));
42+
inline constexpr DWORD PARENT_CHECK_ERROR_NO_QUERY_FUNC = 1;
43+
inline constexpr DWORD PARENT_CHECK_ERROR_QUERY_FAILED = 2;
44+
inline constexpr DWORD PARENT_CHECK_ERROR_OPEN_FAILED = 3;
4945

50-
// Load CEF
51-
CefMainArgs mainArgs(GetModuleHandle(NULL));
52-
CefRefPtr<CCefApp> app{new CCefApp};
46+
using NtQueryInformationProcessFunc = NTSTATUS(NTAPI*)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
5347

54-
void* sandboxInfo = nullptr;
55-
#ifdef CEF_ENABLE_SANDBOX
56-
CefScopedSandboxInfo scopedSandbox;
57-
sandboxInfo = scopedSandbox.sandbox_info();
58-
#endif
48+
// Safe parent monitor thread shutdown
49+
std::atomic<bool> g_bShouldTerminateMonitor{false};
50+
std::atomic<HANDLE> g_hMonitorThread{nullptr};
5951

60-
const HANDLE parentCheckThread = CreateThread(nullptr, 0, CheckParentProcessAliveness, nullptr, 0, nullptr);
52+
namespace
53+
{
54+
[[nodiscard]] auto GetNtQueryInformationProcess() noexcept -> NtQueryInformationProcessFunc
55+
{
56+
const auto ntdll = GetModuleHandleW(L"ntdll.dll");
57+
if (!ntdll)
58+
return nullptr;
59+
60+
const auto procAddr = GetProcAddress(ntdll, "NtQueryInformationProcess");
61+
if (!procAddr)
62+
return nullptr;
63+
64+
return reinterpret_cast<NtQueryInformationProcessFunc>(procAddr);
65+
}
6166

62-
const int exitCode = CefExecuteProcess(mainArgs, app, sandboxInfo);
67+
[[nodiscard]] auto GetParentProcessId(NtQueryInformationProcessFunc queryFunc) noexcept -> DWORD
68+
{
69+
PROCESS_BASIC_INFORMATION info{};
70+
ULONG returnLength = 0;
71+
72+
if (const auto status = queryFunc(GetCurrentProcess(), ProcessBasicInformation, &info, sizeof(info), &returnLength);
73+
!NT_SUCCESS(status) || returnLength < sizeof(PROCESS_BASIC_INFORMATION))
74+
{
75+
return 0;
76+
}
6377

64-
if (parentCheckThread != nullptr)
78+
return static_cast<DWORD>(reinterpret_cast<ULONG_PTR>(info.Reserved3));
79+
}
80+
81+
void MonitorParentProcess(HANDLE parentProcess) noexcept
6582
{
66-
TerminateThread(parentCheckThread, 0);
67-
CloseHandle(parentCheckThread);
83+
while (!g_bShouldTerminateMonitor.load(std::memory_order_acquire))
84+
{
85+
const DWORD result = WaitForSingleObject(parentProcess, CEF_PARENT_CHECK_INTERVAL);
86+
87+
if (result == WAIT_OBJECT_0)
88+
{
89+
DWORD exitCode = 0;
90+
if (GetExitCodeProcess(parentProcess, &exitCode))
91+
ExitProcess(exitCode);
92+
else
93+
ExitProcess(0);
94+
}
95+
else if (result == WAIT_FAILED)
96+
{
97+
// Wine/Proton compatibility: Exit thread instead of terminating process
98+
// Wine's handle implementation dont support all wait operations reliably
99+
break;
100+
}
101+
}
68102
}
103+
} // namespace
69104

70-
return exitCode;
71-
}
105+
DWORD WINAPI CheckParentProcessAliveness(LPVOID) noexcept;
72106

73-
static DWORD WINAPI CheckParentProcessAliveness(LPVOID)
107+
BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, [[maybe_unused]] LPVOID lpReserved)
74108
{
75-
NTSTATUS(NTAPI * queryInformation)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG) = nullptr;
76-
77-
if (auto ntdll = GetModuleHandleW(L"ntdll.dll"); ntdll != nullptr)
109+
if (dwReason == DLL_PROCESS_ATTACH)
78110
{
79-
auto procAddr = GetProcAddress(ntdll, "NtQueryInformationProcess");
80-
queryInformation = reinterpret_cast<decltype(queryInformation)>(reinterpret_cast<void*>(procAddr));
111+
DisableThreadLibraryCalls(hModule);
112+
g_bShouldTerminateMonitor.store(false, std::memory_order_relaxed);
113+
g_hMonitorThread.store(nullptr, std::memory_order_relaxed);
81114
}
115+
else if (dwReason == DLL_PROCESS_DETACH)
116+
{
117+
g_bShouldTerminateMonitor.store(true, std::memory_order_release);
118+
}
119+
120+
return TRUE;
121+
}
82122

83-
if (queryInformation == nullptr)
84-
return 1;
123+
extern "C" [[nodiscard]] __declspec(dllexport) auto InitCEF() noexcept -> int
124+
{
125+
const auto baseDir = SharedUtil::GetMTAProcessBaseDir();
126+
if (baseDir.empty())
127+
return CEF_INIT_ERROR_NO_BASE_DIR;
128+
129+
const auto mtaDir = SharedUtil::PathJoin(baseDir, CEF_MTA_SUBDIR);
130+
SetDllDirectoryW(SharedUtil::FromUTF8(mtaDir));
85131

86-
PROCESS_BASIC_INFORMATION info{};
132+
if (FAILED(__HrLoadAllImportsForDll(CEF_DLL_NAME)))
133+
return CEF_INIT_ERROR_DLL_LOAD_FAILED;
87134

88-
ULONG returnLength = 0;
89-
NTSTATUS status = queryInformation(GetCurrentProcess(), ProcessBasicInformation, &info, sizeof(info), &returnLength);
135+
const CefMainArgs mainArgs(GetModuleHandleW(nullptr));
136+
const CefRefPtr<CCefApp> app{new CCefApp};
90137

91-
if (!NT_SUCCESS(status) || returnLength < sizeof(PROCESS_BASIC_INFORMATION))
92-
return 2;
138+
void* sandboxInfo = nullptr;
139+
#ifdef CEF_ENABLE_SANDBOX
140+
const CefScopedSandboxInfo scopedSandbox;
141+
sandboxInfo = scopedSandbox.sandbox_info();
142+
#endif
93143

94-
const auto parentProcessId = static_cast<DWORD>(reinterpret_cast<ULONG_PTR>(info.Reserved3));
95-
const HANDLE parentProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, parentProcessId);
144+
const auto hThread = CreateThread(nullptr, 0, CheckParentProcessAliveness, nullptr, 0, nullptr);
145+
if (hThread)
146+
g_hMonitorThread.store(hThread, std::memory_order_release);
96147

97-
if (parentProcess == nullptr)
98-
{
99-
if (GetLastError() == ERROR_INVALID_PARAMETER)
100-
ExitProcess(0);
148+
return CefExecuteProcess(mainArgs, app, sandboxInfo);
149+
}
101150

102-
return 3;
103-
}
151+
static auto WINAPI CheckParentProcessAliveness([[maybe_unused]] LPVOID) noexcept -> DWORD
152+
{
153+
const auto queryFunc = GetNtQueryInformationProcess();
154+
if (!queryFunc)
155+
return PARENT_CHECK_ERROR_NO_QUERY_FUNC;
104156

105-
while (true)
106-
{
107-
DWORD exitCode{};
157+
const auto parentProcessId = GetParentProcessId(queryFunc);
158+
if (parentProcessId == 0)
159+
return PARENT_CHECK_ERROR_QUERY_FAILED;
108160

109-
if (!GetExitCodeProcess(parentProcess, &exitCode) || exitCode != STILL_ACTIVE)
161+
const auto parentProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, false, parentProcessId);
162+
if (!parentProcess)
163+
{
164+
// Wine/Proton fallback: PROCESS_QUERY_LIMITED_INFORMATION may not be implemented
165+
const auto parentProcessFallback = OpenProcess(SYNCHRONIZE, false, parentProcessId);
166+
if (parentProcessFallback)
110167
{
111-
CloseHandle(parentProcess);
112-
ExitProcess(exitCode);
168+
MonitorParentProcess(parentProcessFallback);
169+
CloseHandle(parentProcessFallback);
170+
return CEF_INIT_SUCCESS;
113171
}
114-
115-
Sleep(1000);
172+
return PARENT_CHECK_ERROR_OPEN_FAILED;
116173
}
117174

118-
return 0;
175+
MonitorParentProcess(parentProcess);
176+
CloseHandle(parentProcess);
177+
return CEF_INIT_SUCCESS;
119178
}

Client/cefweb/CAjaxResourceHandler.cpp

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,47 @@
22
*
33
* PROJECT: Multi Theft Auto v1.0
44
* LICENSE: See LICENSE in the top level directory
5-
* FILE: core/AjaxResourceHandler.cpp
5+
* FILE: cefweb/CAjaxResourceHandler.cpp
66
* PURPOSE: CEF Handler for Ajax Requests with delayed results
77
*
8+
* Multi Theft Auto is available from https://www.multitheftauto.com/
9+
*
810
*****************************************************************************/
911

1012
#include "StdInc.h"
11-
#include "CWebCore.h"
12-
#include "CWebView.h"
1313
#include "CAjaxResourceHandler.h"
1414
#undef min
1515

1616
CAjaxResourceHandler::CAjaxResourceHandler(std::vector<SString>& vecGet, std::vector<SString>& vecPost, const CefString& strMime)
17-
: m_vecGetData(vecGet), m_vecPostData(vecPost), m_strMime(strMime)
17+
: m_vecGetData(std::move(vecGet)), m_vecPostData(std::move(vecPost)), m_strMime(strMime)
1818
{
1919
}
2020

2121
CAjaxResourceHandler::~CAjaxResourceHandler()
2222
{
2323
// Ensure callback is released if handler is destroyed before completion
24-
if (m_callback)
25-
{
26-
m_callback = nullptr;
27-
}
28-
}
29-
30-
std::vector<SString>& CAjaxResourceHandler::GetGetData()
31-
{
32-
return m_vecGetData;
33-
}
24+
if (!m_callback)
25+
return;
3426

35-
std::vector<SString>& CAjaxResourceHandler::GetPostData()
36-
{
37-
return m_vecPostData;
27+
m_callback = nullptr;
3828
}
3929

4030
void CAjaxResourceHandler::SetResponse(const SString& data)
4131
{
32+
// Prevent response corruption: ignore subsequent calls after data is set
33+
if (m_bHasData) [[unlikely]]
34+
return;
35+
4236
m_strResponse = data;
4337
m_bHasData = true;
4438

45-
if (m_callback)
46-
{
47-
m_callback->Continue();
48-
// Release callback to prevent memory leak
49-
m_callback = nullptr;
50-
}
39+
if (!m_callback)
40+
return;
41+
42+
// Release callback to prevent memory leak
43+
auto callback = std::exchange(m_callback, nullptr);
44+
if (callback)
45+
callback->Continue();
5146
}
5247

5348
// CefResourceHandler implementation
@@ -59,42 +54,64 @@ void CAjaxResourceHandler::Cancel()
5954

6055
void CAjaxResourceHandler::GetResponseHeaders(CefRefPtr<CefResponse> response, int64& response_length, CefString& redirectUrl)
6156
{
62-
response->SetStatus(200);
57+
if (!response) [[unlikely]]
58+
return;
59+
60+
constexpr int HTTP_OK = 200;
61+
response->SetStatus(HTTP_OK);
6362
response->SetStatusText("OK");
64-
response->SetMimeType(m_strMime);
63+
64+
// Use default MIME type if none provided
65+
if (!m_strMime.empty())
66+
response->SetMimeType(m_strMime);
67+
else
68+
response->SetMimeType("application/octet-stream");
69+
6570
response_length = -1;
6671
}
6772

68-
bool CAjaxResourceHandler::ProcessRequest(CefRefPtr<CefRequest> request, CefRefPtr<CefCallback> callback)
73+
bool CAjaxResourceHandler::ProcessRequest([[maybe_unused]] CefRefPtr<CefRequest> request, CefRefPtr<CefCallback> callback)
6974
{
70-
// Since we have nothing to process yet, continue
71-
callback->Continue();
75+
// Don't call Continue() here - let ReadResponse handle async flow
76+
// Calling Continue() immediately would use the callback before data is ready
7277
return true;
7378
}
7479

7580
bool CAjaxResourceHandler::ReadResponse(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr<CefCallback> callback)
7681
{
82+
// Validate input parameters first
83+
if (!data_out || bytes_to_read <= 0) [[unlikely]]
84+
{
85+
bytes_read = 0;
86+
return false;
87+
}
88+
7789
// If we have no data yet, wait
7890
if (!m_bHasData)
7991
{
8092
bytes_read = 0;
81-
m_callback = callback;
82-
93+
// Store callback only if we don't already have one (prevent overwrite/leak)
94+
if (callback && !m_callback)
95+
m_callback = callback;
8396
return true;
8497
}
8598

86-
// Are we done?
87-
if (m_strResponse.length() - m_DataOffset <= 0) [[unlikely]]
88-
return false;
89-
90-
if (bytes_to_read <= 0) [[unlikely]]
99+
// Are we done or response is empty?
100+
const auto responseLength = m_strResponse.length();
101+
if (m_DataOffset >= responseLength) [[unlikely]]
102+
{
103+
bytes_read = 0;
91104
return false;
105+
}
92106

93-
const size_t copyBytes = std::min(static_cast<size_t>(bytes_to_read), m_strResponse.length() - m_DataOffset);
107+
const auto remainingBytes = responseLength - m_DataOffset;
108+
const auto copyBytes = std::min(static_cast<size_t>(bytes_to_read), remainingBytes);
94109

95110
memcpy(data_out, m_strResponse.c_str() + m_DataOffset, copyBytes);
111+
112+
// copyBytes is bounded by bytes_to_read (an int), so cast is always safe
96113
bytes_read = static_cast<int>(copyBytes);
97-
114+
98115
m_DataOffset += copyBytes;
99116

100117
return true;

0 commit comments

Comments
 (0)