Make cURL loosely coupled (#739)

This makes it so the project doesn't link against the library. The project must be able to run without the presence of the library.
This commit is contained in:
smallmodel
2025-06-07 20:36:15 +02:00
committed by GitHub
parent 56db5a1845
commit 099a67f1fd
9 changed files with 330 additions and 31 deletions

15
code/curl/CMakeLists.txt Normal file
View File

@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.12)
project(curl)
add_library(curldefs INTERFACE)
target_include_directories(curldefs INTERFACE "./")
find_package(CURL)
if (CURL_FOUND)
target_include_directories(curldefs INTERFACE ${CURL_INCLUDE_DIRS})
target_compile_definitions(curldefs INTERFACE HAS_LIBCURL=1)
else()
message(WARNING "CURL is not available, HTTP-based features will not work.")
endif()

105
code/curl/curldefs.h Normal file
View File

@@ -0,0 +1,105 @@
/*
===========================================================================
Copyright (C) 2025 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#pragma once
#include "../qcommon/q_shared.h"
#ifdef HAS_LIBCURL
# include <stdint.h>
# include <stdlib.h>
# include <curl/curl.h>
//
// This structure is guaranteed to work on cURL 7.88.1
//
typedef struct {
char *(*qcurl_version)(void);
char *(*qcurl_getenv)(const char *variable);
void (*qcurl_free)(void *p);
struct curl_slist *(*qcurl_slist_append)(struct curl_slist *list, const char *data);
void (*qcurl_slist_free_all)(struct curl_slist *list);
struct curl_version_info_data *(*qcurl_version_info)(CURLversion);
const char *(*qcurl_easy_strerror)(CURLcode);
const char *(*qcurl_share_strerror)(CURLSHcode);
CURLcode (*qcurl_easy_pause)(CURL *handle, int bitmask);
char *(*qcurl_easy_escape)(CURL *handle, const char *string, int length);
char *(*qcurl_easy_unescape)(CURL *handle, const char *string, int length, int *outlength);
CURL *(*qcurl_easy_init)(void);
CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...);
CURLcode (*qcurl_easy_perform)(CURL *curl);
void (*qcurl_easy_cleanup)(CURL *curl);
CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...);
CURL *(*qcurl_easy_duphandle)(CURL *curl);
void (*qcurl_easy_reset)(CURL *curl);
CURLcode (*qcurl_easy_recv)(CURL *curl, void *buffer, size_t buflen, size_t *n);
CURLcode (*qcurl_easy_send)(CURL *curl, const void *buffer, size_t buflen, size_t *n);
CURLcode (*qcurl_easy_upkeep)(CURL *curl);
CURLM *(*qcurl_multi_init)(void);
CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, CURL *curl_handle);
CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, CURL *curl_handle);
CURLMcode (*qcurl_multi_fdset)(
CURLM *multi_handle, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd
);
CURLMcode (*qcurl_multi_wait)(
CURLM *multi_handle, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, int *ret
);
CURLMcode (*qcurl_multi_poll)(
CURLM *multi_handle, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, int *ret
);
CURLMcode (*qcurl_multi_wakeup)(CURLM *multi_handle);
CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, int *running_handles);
CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle);
CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle, int *msgs_in_queue);
const char *(*qcurl_multi_strerror)(CURLMcode);
CURLMcode (*qcurl_multi_socket_action)(CURLM *multi_handle, curl_socket_t s, int ev_bitmask, int *running_handles);
CURLMcode (*qcurl_multi_timeout)(CURLM *multi_handle, long *milliseconds);
CURLMcode (*qcurl_multi_setopt)(CURLM *multi_handle, CURLMoption option, ...);
CURLMcode (*qcurl_multi_assign)(CURLM *multi_handle, curl_socket_t sockfd, void *sockp);
// 8.4.0
//CURL **(*qcurl_multi_get_handles)(CURLM *multi_handle);
char *(*qcurl_pushheader_bynum)(struct curl_pushheaders *h, size_t num);
char *(*qcurl_pushheader_byname)(struct curl_pushheaders *h, const char *name);
// 8.8.0
//CURLMcode (*qcurl_multi_waitfds)(CURLM *multi, struct curl_waitfd *ufds, unsigned int size, unsigned int *fd_count);
} curlImport_t;
static inline qboolean Com_IsCurlImportValid(curlImport_t *import)
{
return import && import->qcurl_version != NULL;
}
#else
//
// No cURL at all
//
typedef void *curlImport_t;
static inline qboolean Com_IsCurlImportValid(curlImport_t *import)
{
return qfalse;
}
#endif

View File

@@ -84,6 +84,7 @@ set(SOURCES_COMMON
add_subdirectory("../skeletor" "./skeletor")
add_subdirectory("../tiki" "./tiki")
add_subdirectory("../curl" "./curl")
add_library(qcommon_standalone INTERFACE)
target_sources(qcommon_standalone INTERFACE ${SOURCES_COMMON})

View File

@@ -36,6 +36,7 @@ if (APPLE)
endif()
set(SOURCES_PLATFORM_COMMON
"${CMAKE_SOURCE_DIR}/code/sys/sys_curl.c"
"${CMAKE_SOURCE_DIR}/code/sys/sys_update_checker.cpp"
)
@@ -44,15 +45,7 @@ target_sources(syslib INTERFACE ${SOURCES_PLATFORM_SPECIFIC} ${SOURCES_PLATFORM_
target_compile_features(syslib INTERFACE cxx_nullptr cxx_std_17)
target_compile_features(syslib INTERFACE c_variadic_macros)
target_link_libraries(syslib INTERFACE qcommon)
find_package(CURL)
if (CURL_FOUND)
target_link_libraries(syslib INTERFACE CURL::libcurl)
target_compile_definitions(syslib INTERFACE HAS_LIBCURL=1)
else()
message(WARNING "CURL is not available, update checking will not work")
endif()
target_link_libraries(syslib INTERFACE curldefs)
if(WIN32)
target_link_libraries(syslib INTERFACE wsock32 ws2_32)

View File

@@ -23,6 +23,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "../sys_local.h"
#include "../win_localization.h"
#include "../sys_loadlib.h"
#include "../sys_curl.h"
#include "../sys_update_checker.h"
// a pointer to the last piece of data retrieved from the clipboard is stored here,
@@ -320,12 +321,14 @@ void VM_Forced_Unload_Done(void) {
void Sys_InitEx()
{
Sys_InitLocalization();
Sys_InitCurl();
Sys_UpdateChecker_Init();
}
void Sys_ShutdownEx()
{
Sys_UpdateChecker_Shutdown();
Sys_ShutdownCurl();
}
void Sys_ProcessBackgroundTasks()

142
code/sys/sys_curl.c Normal file
View File

@@ -0,0 +1,142 @@
/*
===========================================================================
Copyright (C) 2025 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "sys_curl.h"
#include "sys_loadlib.h"
#include "../qcommon/qcommon.h"
curlImport_t curlImport;
static void *cURLLib = NULL;
static qboolean cURLValid;
cvar_t *sys_curllib = NULL;
#ifdef WIN32
# define DEFAULT_CURL_LIB "libcurl.dll"
# define ALTERNATE_CURL_LIB "libcurl-3.dll"
#elif defined(__APPLE__)
# define DEFAULT_CURL_LIB "libcurl.4.dylib"
# define ALTERNATE_CURL_LIB "libcurl.3.dylib"
#else
# define DEFAULT_CURL_LIB "libcurl.so.4"
# define ALTERNATE_CURL_LIB "libcurl.so.3"
#endif
/*
=================
GPA
=================
*/
static void *GPA(char *str)
{
void *rv;
rv = Sys_LoadFunction(cURLLib, str);
if (!rv) {
Com_Printf("Can't load symbol %s\n", str);
cURLValid = qfalse;
return NULL;
} else {
Com_DPrintf("Loaded symbol %s (0x%p)\n", str, rv);
return rv;
}
}
void Sys_InitCurl()
{
#ifdef HAS_LIBCURL
if (cURLLib) {
return;
}
sys_curllib = Cvar_Get("sys_curllib", DEFAULT_CURL_LIB, 0);
Com_Printf("Loading \"%s\"...\n", sys_curllib->string);
if (!(cURLLib = Sys_LoadDll(sys_curllib->string, qtrue))) {
#ifdef ALTERNATE_CURL_LIB
// On some linux distributions there is no libcurl.so.3, but only libcurl.so.4. That one works too.
if (!(cURLLib = Sys_LoadDll(ALTERNATE_CURL_LIB, qtrue)))
#endif
return;
}
cURLValid = qtrue;
curlImport.qcurl_version = GPA("curl_version");
curlImport.qcurl_getenv = GPA("curl_getenv");
curlImport.qcurl_free = GPA("curl_free");
curlImport.qcurl_slist_append = GPA("curl_slist_append");
curlImport.qcurl_slist_free_all = GPA("curl_slist_free_all");
curlImport.qcurl_version_info = GPA("curl_version_info");
curlImport.qcurl_easy_strerror = GPA("curl_easy_strerror");
curlImport.qcurl_share_strerror = GPA("curl_share_strerror");
curlImport.qcurl_easy_pause = GPA("curl_easy_pause");
curlImport.qcurl_easy_escape = GPA("curl_easy_escape");
curlImport.qcurl_easy_unescape = GPA("curl_easy_unescape");
curlImport.qcurl_easy_init = GPA("curl_easy_init");
curlImport.qcurl_easy_setopt = GPA("curl_easy_setopt");
curlImport.qcurl_easy_perform = GPA("curl_easy_perform");
curlImport.qcurl_easy_cleanup = GPA("curl_easy_cleanup");
curlImport.qcurl_easy_getinfo = GPA("curl_easy_getinfo");
curlImport.qcurl_easy_duphandle = GPA("curl_easy_duphandle");
curlImport.qcurl_easy_reset = GPA("curl_easy_reset");
curlImport.qcurl_easy_recv = GPA("curl_easy_recv");
curlImport.qcurl_easy_send = GPA("curl_easy_send");
curlImport.qcurl_easy_upkeep = GPA("curl_easy_upkeep");
curlImport.qcurl_multi_init = GPA("curl_multi_init");
curlImport.qcurl_multi_add_handle = GPA("curl_multi_add_handle");
curlImport.qcurl_multi_remove_handle = GPA("curl_multi_remove_handle");
curlImport.qcurl_multi_fdset = GPA("curl_multi_fdset");
curlImport.qcurl_multi_wait = GPA("curl_multi_wait");
curlImport.qcurl_multi_poll = GPA("curl_multi_poll");
curlImport.qcurl_multi_wakeup = GPA("curl_multi_wakeup");
curlImport.qcurl_multi_perform = GPA("curl_multi_perform");
curlImport.qcurl_multi_cleanup = GPA("curl_multi_cleanup");
curlImport.qcurl_multi_info_read = GPA("curl_multi_info_read");
curlImport.qcurl_multi_strerror = GPA("curl_multi_strerror");
curlImport.qcurl_multi_socket_action = GPA("curl_multi_socket_action");
curlImport.qcurl_multi_timeout = GPA("curl_multi_timeout");
curlImport.qcurl_multi_setopt = GPA("curl_multi_setopt");
curlImport.qcurl_multi_assign = GPA("curl_multi_assign");
//curlImport.qcurl_multi_get_handles = GPA("curl_multi_get_handles");
curlImport.qcurl_pushheader_bynum = GPA("curl_pushheader_bynum");
curlImport.qcurl_pushheader_byname = GPA("curl_pushheader_byname");
//curlImport.qcurl_multi_waitfds = GPA("curl_multi_waitfds");
if (!cURLValid) {
Sys_ShutdownCurl();
}
#endif
}
void Sys_ShutdownCurl()
{
if (cURLLib) {
Com_Printf("Unloading cURL...\n");
Sys_UnloadLibrary(cURLLib);
cURLLib = NULL;
cURLValid = qfalse;
memset(&curlImport, 0, sizeof(curlImport));
}
}

38
code/sys/sys_curl.h Normal file
View File

@@ -0,0 +1,38 @@
/*
===========================================================================
Copyright (C) 2025 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#pragma once
#include "../curl/curldefs.h"
#ifdef __cplusplus
extern "C" {
#endif
void Sys_InitCurl();
void Sys_ShutdownCurl();
extern curlImport_t curlImport;
#ifdef __cplusplus
}
#endif

View File

@@ -21,12 +21,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "sys_update_checker.h"
#include "sys_curl.h"
#include "../qcommon/q_version.h"
#ifdef HAS_LIBCURL
# include <curl/curl.h>
#endif
//#include <httplib.h>
#include "../qcommon/json.hpp"
using json = nlohmann::json;
@@ -93,7 +90,7 @@ bool UpdateChecker::CanHaveRequestThread() const
}
#ifdef HAS_LIBCURL
return true;
return Com_IsCurlImportValid(&curlImport) ? true : false;
#else
return false;
#endif
@@ -234,25 +231,30 @@ void UpdateCheckerThread::InitClient()
#ifdef HAS_LIBCURL
CURLcode result;
if (!Com_IsCurlImportValid(&curlImport)) {
Com_DPrintf("Couldn't load cURL.\n");
return;
}
assert(!handle);
handle = curl_easy_init();
handle = curlImport.qcurl_easy_init();
if (!handle) {
Com_DPrintf("Failed to create curl client\n");
return;
}
result = curl_easy_setopt(handle, CURLOPT_URL, "https://api.github.com/repos/openmoh/openmohaa/releases/latest");
result = curlImport.qcurl_easy_setopt(handle, CURLOPT_URL, "https://api.github.com/repos/openmoh/openmohaa/releases/latest");
if (result != CURLE_OK) {
Com_DPrintf("Failed to set curl URL: %s\n", curl_easy_strerror(result));
curl_easy_cleanup(handle);
Com_DPrintf("Failed to set curl URL: %s\n", curlImport.qcurl_easy_strerror(result));
curlImport.qcurl_easy_cleanup(handle);
handle = NULL;
return;
}
curl_easy_setopt(handle, CURLOPT_USERAGENT, "curl");
curl_easy_setopt(handle, CURLOPT_TIMEOUT, 15);
curlImport.qcurl_easy_setopt(handle, CURLOPT_USERAGENT, "curl");
curlImport.qcurl_easy_setopt(handle, CURLOPT_TIMEOUT, 15);
#else
Com_DPrintf("Project was compiled without libcurl, will not check for updates\n");
#endif
@@ -265,7 +267,7 @@ void UpdateCheckerThread::ShutdownClient()
return;
}
curl_easy_cleanup(handle);
curlImport.qcurl_easy_cleanup(handle);
handle = NULL;
#endif
}
@@ -358,18 +360,18 @@ void UpdateCheckerThread::DoRequest()
CURLcode result;
WriteCallbackData callbackData;
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, &WriteCallback);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &callbackData);
curl_easy_setopt(handle, CURLOPT_MAXFILESIZE, MAX_BUFFER_SIZE);
curlImport.qcurl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, &WriteCallback);
curlImport.qcurl_easy_setopt(handle, CURLOPT_WRITEDATA, &callbackData);
curlImport.qcurl_easy_setopt(handle, CURLOPT_MAXFILESIZE, MAX_BUFFER_SIZE);
struct curl_slist *list = NULL;
list = curl_slist_append(list, "Accept: application/vnd.github+json");
list = curl_slist_append(list, "X-GitHub-Api-Version: 2022-11-28");
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, list);
list = curlImport.qcurl_slist_append(list, "Accept: application/vnd.github+json");
list = curlImport.qcurl_slist_append(list, "X-GitHub-Api-Version: 2022-11-28");
curlImport.qcurl_easy_setopt(handle, CURLOPT_HTTPHEADER, list);
result = curl_easy_perform(handle);
curl_slist_free_all(list);
result = curlImport.qcurl_easy_perform(handle);
curlImport.qcurl_slist_free_all(list);
if (result != CURLE_OK) {
return;

View File

@@ -16,7 +16,8 @@ The installation directory can be set to the MOHAA directory with `-DCMAKE_INSTA
Compiling debug binaries will result in a `-dbg` suffix appended to the name of the binaries to avoid mixing debug/release code.
cURL is used to access websites. It is currently used to check for a new release. If the project is compiled without cURL, OpenMoHAA will be unable to check for new updates automatically.
> [!NOTE]
> OpenMoHAA is loosely coupled libcurl, meaning the library is not required for the game to work normally. Currently it only checks for update, but in the future some features may be introduced that require it.
## Compiling for Linux
@@ -24,7 +25,6 @@ These are the tools required on Linux :
- Clang >= 7.0.1 or GCC >= 9.4.0
- libsdl2-dev
- libopenal-dev
- libssl-dev
- libcurl4-openssl-dev
**clang-7** and **gcc-9** has been tested to work on Ubuntu 20.04. Although it's best to use the latest versions.