From 9814f5f42f54119611d300a711f3f897ec30df18 Mon Sep 17 00:00:00 2001 From: Tim Angus Date: Fri, 18 Jul 2025 12:33:34 +0100 Subject: [PATCH] Implement WinINet based HTTP downloads --- Makefile | 14 ++- code/client/cl_http_windows.c | 162 ++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 code/client/cl_http_windows.c diff --git a/Makefile b/Makefile index 81de153d..715ed1cf 100644 --- a/Makefile +++ b/Makefile @@ -749,7 +749,9 @@ ifdef MINGW FREETYPE_CFLAGS = -Ifreetype2 endif - USE_HTTP=0 + ifeq ($(USE_HTTP),1) + CLIENT_LIBS += -lwininet + endif ifeq ($(ARCH),x86) # build 32bit @@ -1914,8 +1916,6 @@ Q3OBJ = \ $(B)/client/qal.o \ $(B)/client/snd_openal.o \ \ - $(B)/client/cl_http_curl.o \ - \ $(B)/client/sv_bot.o \ $(B)/client/sv_ccmds.o \ $(B)/client/sv_client.o \ @@ -1971,6 +1971,14 @@ Q3OBJ = \ $(B)/client/sys_autoupdater.o \ $(B)/client/sys_main.o +ifdef MINGW + Q3OBJ += \ + $(B)/client/cl_http_windows.o +else + Q3OBJ += \ + $(B)/client/cl_http_curl.o +endif + ifdef MINGW Q3OBJ += \ $(B)/client/con_passive.o diff --git a/code/client/cl_http_windows.c b/code/client/cl_http_windows.c new file mode 100644 index 00000000..d098f9e6 --- /dev/null +++ b/code/client/cl_http_windows.c @@ -0,0 +1,162 @@ +/* +=========================================================================== +Copyright (C) 2025 Tim Angus (tim@ngus.net) + +This file is part of Quake III Arena source code. + +Quake III Arena 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. + +Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifdef USE_HTTP + +#include "client.h" + +#include +#include + +static HINTERNET hInternet = NULL; +static HINTERNET hUrl = NULL; + +static void DropIf(qboolean condition, const char *fmt, ...) +{ + char buffer[1024]; + + if (!condition) + return; + + va_list argptr; + va_start(argptr, fmt); + Q_vsnprintf(buffer, sizeof(buffer), fmt, argptr); + va_end(argptr); + + Com_Error(ERR_DROP, "Download Error: %s URL: %s", buffer, clc.downloadURL); +} + +/* +================= +CL_HTTP_Init +================= +*/ +qboolean CL_HTTP_Init() +{ + OSVERSIONINFO osvi = {sizeof(OSVERSIONINFO)}; + const char *windowsVersion = GetVersionEx(&osvi) ? va("Windows %lu.%lu (build %lu)", + osvi.dwMajorVersion, + osvi.dwMinorVersion, + osvi.dwBuildNumber) + : "Windows"; + + hInternet = InternetOpenA( + va("%s %s", Q3_VERSION, windowsVersion), + INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); + + return hInternet != NULL; +} + +/* +================= +CL_HTTP_Available +================= +*/ +qboolean CL_HTTP_Available() +{ + return hInternet != NULL; +} + +/* +================= +CL_HTTP_Shutdown +================= +*/ +void CL_HTTP_Shutdown(void) +{ + if (hInternet) + { + InternetCloseHandle(hInternet); + hInternet = NULL; + } +} + +/* +================= +CL_HTTP_BeginDownload +================= +*/ +void CL_HTTP_BeginDownload(const char *remoteURL) +{ + DWORD httpCode = 0; + DWORD contentLength = 0; + DWORD len = sizeof(httpCode); + DWORD zero = 0; + BOOL success; + + hUrl = InternetOpenUrlA(hInternet, remoteURL, + va("Referer: ioQ3://%s\r\n", NET_AdrToString(clc.serverAddress)), -1, + INTERNET_FLAG_HYPERLINK | + INTERNET_FLAG_NO_CACHE_WRITE | + INTERNET_FLAG_NO_COOKIES | + INTERNET_FLAG_NO_UI | + INTERNET_FLAG_RESYNCHRONIZE | + INTERNET_FLAG_RELOAD, + 0); + + DropIf(hUrl == NULL, "InternetOpenUrlA failed %lu", GetLastError()); + + success = HttpQueryInfo(hUrl, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &httpCode, &len, &zero); + DropIf(!success, "Get HTTP_QUERY_STATUS_CODE failed %lu", GetLastError()); + + DropIf(httpCode >= 400, "HTTP code %lu", httpCode); + DropIf(httpCode != 200, "Unhandled HTTP code %lu", httpCode); + + success = HttpQueryInfo(hUrl, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &contentLength, &len, &zero); + DropIf(!success, "Get HTTP_QUERY_CONTENT_LENGTH failed %lu", GetLastError()); + + clc.downloadSize = (int)contentLength; + Cvar_SetValue("cl_downloadSize", clc.downloadSize); +} + +/* +================= +CL_HTTP_PerformDownload +================= +*/ +qboolean CL_HTTP_PerformDownload(void) +{ + static BYTE readBuffer[256 * 1024]; + DWORD bytesRead = 0; + BOOL success; + + DropIf(hUrl == NULL, "hUrl is NULL"); + + success = InternetReadFile(hUrl, readBuffer, sizeof(readBuffer), &bytesRead); + DropIf(!success, "InternetReadFile failed %lu", GetLastError()); + + if (bytesRead > 0) + { + clc.downloadCount += bytesRead; + Cvar_SetValue("cl_downloadCount", clc.downloadCount); + + int bytesWritten = FS_Write(readBuffer, bytesRead, clc.download); + DropIf(bytesWritten != bytesRead, "bytesWritten != bytesRead"); + + return qfalse; + } + + InternetCloseHandle(hUrl); + return qtrue; +} + +#endif /* USE_HTTP */