[Scummvm-git-logs] scummvm master -> 423f194cfea0f3badd5cb60d2bd375eea8f10d7f
lephilousophe
noreply at scummvm.org
Sun Nov 2 08:12:30 UTC 2025
This automated email contains information about 7 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
d3ec7c959c NETWORKING: Don't expose raw headers string
ad028cc45a NETWORKING: Remove useless virtual functions
8b55c7660c NETWORKING: ENET: Move ENet classes in their own namespace
14a78e4eaf NETWORKING: Abstract CurlSocket and CurlURL
b5dc393669 TESTBED: Add tests for basic network features
980fd5b54d ANDROID: Implement platform specific networking features
423f194cfe ANDROID: Add support for older Android
Commit: d3ec7c959c9030c70086b0bca7b4a3a51c3a5b8f
https://github.com/scummvm/scummvm/commit/d3ec7c959c9030c70086b0bca7b4a3a51c3a5b8f
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2025-11-02T09:12:23+01:00
Commit Message:
NETWORKING: Don't expose raw headers string
It's only for internal use and only the headers map is used externally
Changed paths:
backends/networking/http/emscripten/networkreadstream-emscripten.cpp
backends/networking/http/networkreadstream.h
diff --git a/backends/networking/http/emscripten/networkreadstream-emscripten.cpp b/backends/networking/http/emscripten/networkreadstream-emscripten.cpp
index 9dede63d44d..f67113545bb 100644
--- a/backends/networking/http/emscripten/networkreadstream-emscripten.cpp
+++ b/backends/networking/http/emscripten/networkreadstream-emscripten.cpp
@@ -288,7 +288,7 @@ Common::HashMap<Common::String, Common::String> NetworkReadStreamEmscripten::res
Common::HashMap<Common::String, Common::String> headers;
- const char *headerString = responseHeaders().c_str();
+ const char *headerString = _responseHeaders.c_str();
char **responseHeaders = emscripten_fetch_unpack_response_headers(headerString);
assert(responseHeaders);
diff --git a/backends/networking/http/networkreadstream.h b/backends/networking/http/networkreadstream.h
index 54c15e28559..a2994699470 100644
--- a/backends/networking/http/networkreadstream.h
+++ b/backends/networking/http/networkreadstream.h
@@ -148,15 +148,6 @@ public:
*/
virtual Common::String currentLocation() const = 0;
- /**
- * Return response headers.
- *
- * @note This method should be called when eos() == true.
- */
- Common::String responseHeaders() const {
- return _responseHeaders;
- }
-
/**
* Return response headers as HashMap. All header names in
* it are lowercase.
Commit: ad028cc45a75c30bed84caae95d9ac28f6d89c32
https://github.com/scummvm/scummvm/commit/ad028cc45a75c30bed84caae95d9ac28f6d89c32
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2025-11-02T09:12:23+01:00
Commit Message:
NETWORKING: Remove useless virtual functions
These functions are only called from each implementation specific code
and never from generic places.
This allows to lighten the interface for other implementations where
these functions would not be needed.
Changed paths:
backends/networking/http/curl/networkreadstream-curl.h
backends/networking/http/emscripten/networkreadstream-emscripten.h
backends/networking/http/networkreadstream.h
diff --git a/backends/networking/http/curl/networkreadstream-curl.h b/backends/networking/http/curl/networkreadstream-curl.h
index 6473b4699f4..ec280092f0a 100644
--- a/backends/networking/http/curl/networkreadstream-curl.h
+++ b/backends/networking/http/curl/networkreadstream-curl.h
@@ -45,8 +45,6 @@ private:
byte *_bufferCopy; // To use with old curl version where CURLOPT_COPYPOSTFIELDS is not available
void initCurl(const char *url, RequestHeaders *headersList);
bool reuseCurl(const char *url, RequestHeaders *headersList);
- void setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) override;
- void setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) override;
static struct curl_slist *requestHeadersToSlist(const RequestHeaders *headersList);
static size_t curlDataCallback(char *d, size_t n, size_t l, void *p);
@@ -56,6 +54,9 @@ private:
// CURL-specific methods
CURL *getEasyHandle() const { return _easy; }
+ void resetStream();
+ void setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post);
+ void setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles);
public:
NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
@@ -77,8 +78,6 @@ public:
/** Send <buffer>, using POST by default. */
bool reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true) override;
- void resetStream() override;
-
long httpResponseCode() const override;
Common::String currentLocation() const override;
/**
diff --git a/backends/networking/http/emscripten/networkreadstream-emscripten.h b/backends/networking/http/emscripten/networkreadstream-emscripten.h
index 61693a3cfe9..7f52a98be6c 100644
--- a/backends/networking/http/emscripten/networkreadstream-emscripten.h
+++ b/backends/networking/http/emscripten/networkreadstream-emscripten.h
@@ -40,6 +40,9 @@ private:
bool _success;
char *_errorBuffer;
+ void resetStream();
+ void setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post);
+ void setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles);
public:
NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
@@ -54,8 +57,6 @@ public:
bool reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading = false, bool usingPatch = false) override { return false; } // no reuse for Emscripten
bool reuse(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) override { return false; } // no reuse for Emscripten
bool reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = false) override { return false; } // no reuse for Emscripten
- void setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) override;
- void setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) override;
bool hasError() const override;
const char *getError() const override;
@@ -63,7 +64,6 @@ public:
long httpResponseCode() const override;
Common::String currentLocation() const override;
Common::HashMap<Common::String, Common::String> responseHeadersMap() const override;
- void resetStream() override;
uint32 read(void *dataPtr, uint32 dataSize) override;
diff --git a/backends/networking/http/networkreadstream.h b/backends/networking/http/networkreadstream.h
index a2994699470..3ae028cbde4 100644
--- a/backends/networking/http/networkreadstream.h
+++ b/backends/networking/http/networkreadstream.h
@@ -46,17 +46,6 @@ protected:
Common::String _responseHeaders;
uint64 _progressDownloaded, _progressTotal;
- /**
- * This method is called by ConnectionManager to indicate
- * that transfer is finished.
- *
- * @note It's called on failure too.
- */
- virtual void resetStream() = 0;
-
- virtual void setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) = 0;
- virtual void setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) = 0;
-
/**
* Fills the passed buffer with _sendingContentsBuffer contents.
* It works similarly to read(), expect it's not for reading
Commit: 8b55c7660c4c127842c861360006a8f449d2db0c
https://github.com/scummvm/scummvm/commit/8b55c7660c4c127842c861360006a8f449d2db0c
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2025-11-02T09:12:23+01:00
Commit Message:
NETWORKING: ENET: Move ENet classes in their own namespace
This avoids clashing with generic terms like Socket and Host.
Changed paths:
backends/networking/enet/enet.cpp
backends/networking/enet/enet.h
backends/networking/enet/host.cpp
backends/networking/enet/host.h
backends/networking/enet/socket.cpp
backends/networking/enet/socket.h
engines/scumm/he/net/net_main.cpp
engines/scumm/he/net/net_main.h
diff --git a/backends/networking/enet/enet.cpp b/backends/networking/enet/enet.cpp
index dd7c83a617e..bb3b59af37b 100644
--- a/backends/networking/enet/enet.cpp
+++ b/backends/networking/enet/enet.cpp
@@ -28,6 +28,7 @@
#include "common/debug.h"
namespace Networking {
+namespace ENet {
ENet::ENet() {
_initialized = false;
@@ -139,4 +140,5 @@ Socket *ENet::createSocket(const Common::String &address, int port) {
return new Socket(enetSocket);
}
+} // End of namespace ENet
} // End of namespace Networking
diff --git a/backends/networking/enet/enet.h b/backends/networking/enet/enet.h
index b164e04b2ca..8cfb047bde4 100644
--- a/backends/networking/enet/enet.h
+++ b/backends/networking/enet/enet.h
@@ -25,6 +25,7 @@
#include "common/str.h"
namespace Networking {
+namespace ENet {
class Host;
class Socket;
@@ -89,6 +90,7 @@ private:
bool _initialized;
};
+} // End of namespace ENet
} // End of namespace Networking
diff --git a/backends/networking/enet/host.cpp b/backends/networking/enet/host.cpp
index d5e83edc60f..62c5a41f570 100644
--- a/backends/networking/enet/host.cpp
+++ b/backends/networking/enet/host.cpp
@@ -25,6 +25,7 @@
#include "backends/networking/enet/host.h"
namespace Networking {
+namespace ENet {
Host::Host(ENetHost *host) {
_host = host;
@@ -187,4 +188,5 @@ bool Host::sendRawData(const Common::String &address, int port, const char *data
}
+} // End of namespace ENet
} // End of namespace Networking
diff --git a/backends/networking/enet/host.h b/backends/networking/enet/host.h
index 439bbb11e31..524356cfdfa 100644
--- a/backends/networking/enet/host.h
+++ b/backends/networking/enet/host.h
@@ -40,6 +40,7 @@ typedef struct _ENetPacket ENetPacket;
#include "common/debug.h"
namespace Networking {
+namespace ENet {
class Host {
public:
@@ -170,6 +171,7 @@ private:
ENetPacket *_recentPacket;
};
+} // End of namespace ENet
} // End of namespace Networking
#endif
diff --git a/backends/networking/enet/socket.cpp b/backends/networking/enet/socket.cpp
index 20d394cf781..b326421d1fe 100644
--- a/backends/networking/enet/socket.cpp
+++ b/backends/networking/enet/socket.cpp
@@ -27,6 +27,7 @@
#include "common/textconsole.h"
namespace Networking {
+namespace ENet {
Socket::Socket(ENetSocket socket) {
_socket = socket;
@@ -101,4 +102,5 @@ int Socket::getPort() {
return _recentPort;
}
+} // End of namespace ENet
} // End of namespace Networking
diff --git a/backends/networking/enet/socket.h b/backends/networking/enet/socket.h
index 43b13c008e7..84fe8e1fb6a 100644
--- a/backends/networking/enet/socket.h
+++ b/backends/networking/enet/socket.h
@@ -36,6 +36,7 @@ typedef int ENetSocket;
#include "common/str.h"
namespace Networking {
+namespace ENet {
class Socket {
public:
@@ -103,6 +104,7 @@ private:
int _recentPort;
};
+} // End of namespace ENet
} // End of namespace Networking
#endif
diff --git a/engines/scumm/he/net/net_main.cpp b/engines/scumm/he/net/net_main.cpp
index 35a024bbbf3..08401af42a0 100644
--- a/engines/scumm/he/net/net_main.cpp
+++ b/engines/scumm/he/net/net_main.cpp
@@ -766,7 +766,7 @@ bool Net::initProvider() {
// Create a new ENet instance and initialize the library.
if (_enet)
return true;
- _enet = new Networking::ENet();
+ _enet = new Networking::ENet::ENet();
if (!_enet->initialize()) {
_vm->displayMessage(0, "Unable to initialize ENet library.");
Net::closeProvider();
diff --git a/engines/scumm/he/net/net_main.h b/engines/scumm/he/net/net_main.h
index a3fb800bb36..a7a78e673db 100644
--- a/engines/scumm/he/net/net_main.h
+++ b/engines/scumm/he/net/net_main.h
@@ -539,7 +539,7 @@ private:
Common::String _gameName;
Common::String _gameVersion;
- Networking::ENet *_enet;
+ Networking::ENet::ENet *_enet;
byte *_tmpbuffer;
@@ -553,7 +553,7 @@ private:
Common::HashMap<Common::String, int> _addressToUserId;
Common::String _sessionName;
- Networking::Host *_sessionHost;
+ Networking::ENet::Host *_sessionHost;
// For Moonbase map generation:
uint8 _mapGenerator;
@@ -574,10 +574,10 @@ private:
int _hostPort;
// For broadcasting our game session over LAN.
- Networking::Socket *_broadcastSocket;
+ Networking::ENet::Socket *_broadcastSocket;
// For creating/joining sessions over the Internet.
- Networking::Host *_sessionServerHost;
+ Networking::ENet::Host *_sessionServerHost;
Address _sessionServerAddress;
bool _forcedAddress;
bool _gotSessions;
Commit: 14a78e4eafaa473ead7a94f1b5a27464ee91e775
https://github.com/scummvm/scummvm/commit/14a78e4eafaa473ead7a94f1b5a27464ee91e775
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2025-11-02T09:12:23+01:00
Commit Message:
NETWORKING: Abstract CurlSocket and CurlURL
This will allow alternative providers.
Changed paths:
A backends/networking/basic/curl/cacert.cpp
A backends/networking/basic/curl/cacert.h
A backends/networking/basic/curl/socket.cpp
A backends/networking/basic/curl/socket.h
A backends/networking/basic/curl/url.cpp
A backends/networking/basic/curl/url.h
A backends/networking/basic/socket.h
A backends/networking/basic/url.h
R backends/networking/curl/cacert.cpp
R backends/networking/curl/cacert.h
R backends/networking/curl/socket.cpp
R backends/networking/curl/socket.h
R backends/networking/curl/url.cpp
R backends/networking/curl/url.h
backends/module.mk
backends/networking/http/curl/networkreadstream-curl.cpp
configure
devtools/create_project/create_project.cpp
engines/scumm/dialogs.cpp
engines/scumm/dialogs.h
engines/scumm/he/intern_he.h
engines/scumm/he/logic/baseball2001.cpp
engines/scumm/he/logic/football.cpp
engines/scumm/he/net/net_lobby.cpp
engines/scumm/he/net/net_lobby.h
engines/scumm/he/script_v72he.cpp
engines/scumm/module.mk
engines/scumm/script.cpp
engines/scumm/script_v6.cpp
engines/scumm/scumm.cpp
engines/scumm/vars.cpp
diff --git a/backends/module.mk b/backends/module.mk
index af68814b790..ae41dea4101 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -89,9 +89,9 @@ endif
ifdef USE_LIBCURL
MODULE_OBJS += \
- networking/curl/cacert.o \
- networking/curl/socket.o \
- networking/curl/url.o
+ networking/basic/curl/cacert.o \
+ networking/basic/curl/socket.o \
+ networking/basic/curl/url.o
ifdef USE_HTTP
MODULE_OBJS += \
networking/http/curl/connectionmanager-curl.o \
diff --git a/backends/networking/curl/cacert.cpp b/backends/networking/basic/curl/cacert.cpp
similarity index 97%
rename from backends/networking/curl/cacert.cpp
rename to backends/networking/basic/curl/cacert.cpp
index 313cfb483c1..1d80c88ead5 100644
--- a/backends/networking/curl/cacert.cpp
+++ b/backends/networking/basic/curl/cacert.cpp
@@ -23,7 +23,7 @@
#include "backends/platform/android/jni-android.h"
#endif
-#include "backends/networking/curl/cacert.h"
+#include "backends/networking/basic/curl/cacert.h"
#include "common/fs.h"
diff --git a/backends/networking/curl/cacert.h b/backends/networking/basic/curl/cacert.h
similarity index 91%
rename from backends/networking/curl/cacert.h
rename to backends/networking/basic/curl/cacert.h
index 6f264b9c989..908eae6e702 100644
--- a/backends/networking/curl/cacert.h
+++ b/backends/networking/basic/curl/cacert.h
@@ -18,8 +18,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-#ifndef BACKENDS_NETWORKING_CURL_CACERT_H
-#define BACKENDS_NETWORKING_CURL_CACERT_H
+#ifndef BACKENDS_NETWORKING_BASIC_CURL_CACERT_H
+#define BACKENDS_NETWORKING_BASIC_CURL_CACERT_H
#include "common/str.h"
diff --git a/backends/networking/curl/socket.cpp b/backends/networking/basic/curl/socket.cpp
similarity index 63%
rename from backends/networking/curl/socket.cpp
rename to backends/networking/basic/curl/socket.cpp
index 5856724e0ec..4e57d664820 100644
--- a/backends/networking/curl/socket.cpp
+++ b/backends/networking/basic/curl/socket.cpp
@@ -22,8 +22,8 @@
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include <curl/curl.h>
-#include "backends/networking/curl/socket.h"
-#include "backends/networking/curl/cacert.h"
+#include "backends/networking/basic/curl/socket.h"
+#include "backends/networking/basic/curl/cacert.h"
#include "common/debug.h"
#include "common/system.h"
@@ -57,77 +57,75 @@ static int waitOnSocket(curl_socket_t sockfd, int for_recv, long timeout_ms) {
namespace Networking {
-CurlSocket::CurlSocket() {
- _easy = nullptr;
- _socket = 0;
-}
+Socket *Socket::connect(const Common::String &url) {
+ CURL *easy = curl_easy_init();
+ if (!easy) {
+ return nullptr;
+ }
-CurlSocket::~CurlSocket() {
- // Always clean up.
- curl_easy_cleanup(_easy);
-}
+ curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
+ // Just connect to the host, do not do any transfers.
+ curl_easy_setopt(easy, CURLOPT_CONNECT_ONLY, 1L);
-int CurlSocket::ready() {
- return waitOnSocket(_socket, 1, 0);
-}
-
-bool CurlSocket::connect(const Common::String &url) {
- _easy = curl_easy_init();
- if (_easy) {
- curl_easy_setopt(_easy, CURLOPT_URL, url.c_str());
- // Just connect to the host, do not do any transfers.
- curl_easy_setopt(_easy, CURLOPT_CONNECT_ONLY, 1L);
-
- // libcurl won't connect to SSL connections
- // with VERIFYPEER enabled because we do not ship
- // with a CA bundle in these platforms.
- // So let's disable it.
+ // libcurl won't connect to SSL connections
+ // with VERIFYPEER enabled because we do not ship
+ // with a CA bundle in these platforms.
+ // So let's disable it.
#if defined NINTENDO_SWITCH || defined PSP2
- curl_easy_setopt(_easy, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 0);
#endif
- Common::String caCertPath = getCaCertPath();
- if (!caCertPath.empty()) {
- curl_easy_setopt(_easy, CURLOPT_CAINFO, caCertPath.c_str());
- }
+ Common::String caCertPath = getCaCertPath();
+ if (!caCertPath.empty()) {
+ curl_easy_setopt(easy, CURLOPT_CAINFO, caCertPath.c_str());
+ }
- CURLcode res = curl_easy_perform(_easy);
- if (res != CURLE_OK) {
- warning("libcurl: Failed to connect: %s", curl_easy_strerror(res));
- return false;
- }
+ CURLcode res = curl_easy_perform(easy);
+ if (res != CURLE_OK) {
+ warning("libcurl: Failed to connect: %s", curl_easy_strerror(res));
+ curl_easy_cleanup(easy);
+ return nullptr;
+ }
- // Get the socket, we'll need it for waiting.
- // NB: Do not use CURL_AT_LEAST_VERSION(x,y,z) or CURL_VERSION_BITS(x,y,z) for version check
- // These were only added around v7.45.0 release so break build with earlier libcurl versions
+ // Get the socket, we'll need it for waiting.
+ // NB: Do not use CURL_AT_LEAST_VERSION(x,y,z) or CURL_VERSION_BITS(x,y,z) for version check
+ // These were only added around v7.45.0 release so break build with earlier libcurl versions
#if LIBCURL_VERSION_NUM >= 0x072d00 // 7.45.0
- // Use new CURLINFO_ACTIVESOCKET API for libcurl v7.45.0 or greater
- res = curl_easy_getinfo(_easy, CURLINFO_ACTIVESOCKET, &_socket);
- if (res == CURLE_OK) {
- return true;
- }
+ // Use new CURLINFO_ACTIVESOCKET API for libcurl v7.45.0 or greater
+ curl_socket_t socket;
+ res = curl_easy_getinfo(easy, CURLINFO_ACTIVESOCKET, &socket);
+ if (res == CURLE_OK) {
+ return new CurlSocket(easy, socket);
+ }
#else
- // Fallback on old deprecated CURLINFO_LASTSOCKET API for libcurl older than v7.45.0 (October 2015)
- long socket;
- res = curl_easy_getinfo(_easy, CURLINFO_LASTSOCKET, &socket);
- if (res == CURLE_OK) {
- // curl_socket_t is an int or a SOCKET (Win32) which is a UINT_PTR
- // A cast should be safe enough as long fits in it
- _socket = (curl_socket_t)socket;
- return true;
- }
+ // Fallback on old deprecated CURLINFO_LASTSOCKET API for libcurl older than v7.45.0 (October 2015)
+ long socket;
+ res = curl_easy_getinfo(easy, CURLINFO_LASTSOCKET, &socketl);
+ if (res == CURLE_OK) {
+ // curl_socket_t is an int or a SOCKET (Win32) which is a UINT_PTR
+ // A cast should be safe enough as long fits in it
+ return new CurlSocket(easy, (curl_socket_t)socket);
+ }
#endif
- warning("libcurl: Failed to extract socket: %s", curl_easy_strerror(res));
- return false;
- }
- return false;
+ warning("libcurl: Failed to extract socket: %s", curl_easy_strerror(res));
+ curl_easy_cleanup(easy);
+ return nullptr;
}
-size_t CurlSocket::send(const char *data, int len) {
- if (!_socket)
- return (size_t)-1;
+CurlSocket::CurlSocket(CURL *easy, curl_socket_t socket) : _easy(easy), _socket(socket) {
+}
+
+CurlSocket::~CurlSocket() {
+ // Always clean up.
+ curl_easy_cleanup(_easy);
+}
+
+int CurlSocket::ready() {
+ return waitOnSocket(_socket, 1, 0);
+}
+size_t CurlSocket::send(const char *data, int len) {
size_t nsent_total = 0, left = len;
CURLcode res = CURLE_AGAIN;
diff --git a/backends/networking/curl/socket.h b/backends/networking/basic/curl/socket.h
similarity index 77%
rename from backends/networking/curl/socket.h
rename to backends/networking/basic/curl/socket.h
index 3f355787430..e514fcebcf2 100644
--- a/backends/networking/curl/socket.h
+++ b/backends/networking/basic/curl/socket.h
@@ -18,8 +18,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-#ifndef BACKENDS_NETWORKING_CURL_SOCKET_H
-#define BACKENDS_NETWORKING_CURL_SOCKET_H
+#ifndef BACKENDS_NETWORKING_BASIC_CURL_SOCKET_H
+#define BACKENDS_NETWORKING_BASIC_CURL_SOCKET_H
typedef void CURL;
#ifdef WIN32
@@ -33,21 +33,19 @@ typedef SOCKET curl_socket_t;
typedef int curl_socket_t;
#endif
-#include "common/str.h"
+#include "backends/networking/basic/socket.h"
namespace Networking {
-class CurlSocket {
+class CurlSocket : public Socket {
public:
- CurlSocket();
- ~CurlSocket();
+ CurlSocket(CURL *easy, curl_socket_t socket);
+ ~CurlSocket() override;
- bool connect(const Common::String &url);
+ int ready() override;
- int ready();
-
- size_t send(const char *data, int len);
- size_t recv(void *data, int maxLen);
+ size_t send(const char *data, int len) override;
+ size_t recv(void *data, int maxLen) override;
private:
CURL *_easy;
curl_socket_t _socket;
diff --git a/backends/networking/curl/url.cpp b/backends/networking/basic/curl/url.cpp
similarity index 72%
rename from backends/networking/curl/url.cpp
rename to backends/networking/basic/curl/url.cpp
index ee33de51964..88372670e2f 100644
--- a/backends/networking/curl/url.cpp
+++ b/backends/networking/basic/curl/url.cpp
@@ -22,69 +22,47 @@
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include <curl/curl.h>
-#include "backends/networking/curl/url.h"
+#include "backends/networking/basic/curl/url.h"
#include "common/debug.h"
#include "common/textconsole.h"
namespace Networking {
-CurlURL::CurlURL() {
- _url = nullptr;
-}
-
// This requires libcurl version 7.62.0, If we're using
// a lower version, stub all of this.
#if LIBCURL_VERSION_NUM < 0x073E00
-CurlURL::~CurlURL() {
-}
-
-Common::String CurlURL::getScheme() {
- return "";
-}
-Common::String CurlURL::getHost() {
- return "";
-}
-
-int CurlURL::getPort(bool defaultPort) {
- return -1;
-}
-
-bool CurlURL::parseURL(const Common::String &url) {
+URL *URL::parseURL(const Common::String &url) {
warning("libcurl: curl_url requires curl 7.62.0 or later");
- return false;
+ return nullptr;
}
#else
-CurlURL::~CurlURL() {
- if (_url) {
- curl_url_cleanup(_url);
- _url = nullptr;
- }
-}
-
-bool CurlURL::parseURL(const Common::String &url) {
- if (_url)
- curl_url_cleanup(_url);
-
- _url = curl_url();
- if (!_url) {
+URL *URL::parseURL(const Common::String &url) {
+ CURLU *curlu = curl_url();
+ if (!curlu) {
warning("libcurl: Could not create curl_url handle");
- return false;
+ return nullptr;
}
- CURLUcode rc = curl_url_set(_url, CURLUPART_URL, url.c_str(), 0);
+ CURLUcode rc = curl_url_set(curlu, CURLUPART_URL, url.c_str(), 0);
if (rc) {
warning("libcurl: Unable to parse URL: \"%s\"", url.c_str());
- return false;
+ curl_url_cleanup(curlu);
+ return nullptr;
}
- return true;
+ return new CurlURL(curlu);
}
-Common::String CurlURL::getScheme() {
- if (!_url)
- return "";
+CurlURL::CurlURL(CURLU *curlu) : _url(curlu) {
+}
+
+CurlURL::~CurlURL() {
+ curl_url_cleanup(_url);
+}
+
+Common::String CurlURL::getScheme() const {
char *scheme;
CURLUcode rc = curl_url_get(_url, CURLUPART_SCHEME, &scheme, 0);
if (rc) {
@@ -96,9 +74,7 @@ Common::String CurlURL::getScheme() {
return schemeString;
}
-Common::String CurlURL::getHost() {
- if (!_url)
- return "";
+Common::String CurlURL::getHost() const {
char *host;
CURLUcode rc = curl_url_get(_url, CURLUPART_HOST, &host, 0);
if (rc) {
@@ -110,10 +86,7 @@ Common::String CurlURL::getHost() {
return hostString;
}
-int CurlURL::getPort(bool defaultPort) {
- if (!_url)
- return -1;
-
+int CurlURL::getPort(bool defaultPort) const {
char *portChr;
CURLUcode rc = curl_url_get(_url, CURLUPART_PORT, &portChr, (defaultPort) ? CURLU_DEFAULT_PORT : CURLU_NO_DEFAULT_PORT);
if (rc) {
diff --git a/backends/networking/basic/curl/url.h b/backends/networking/basic/curl/url.h
new file mode 100644
index 00000000000..76870db70af
--- /dev/null
+++ b/backends/networking/basic/curl/url.h
@@ -0,0 +1,45 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef BACKENDS_NETWORKING_BASIC_CURL_URL_H
+#define BACKENDS_NETWORKING_BASIC_CURL_URL_H
+
+typedef struct Curl_URL CURLU;
+
+#include "backends/networking/basic/url.h"
+
+namespace Networking {
+
+class CurlURL : public URL {
+public:
+ CurlURL(CURLU *curlu);
+
+ ~CurlURL() override;
+
+ Common::String getScheme() const override;
+ Common::String getHost() const override;
+ int getPort(bool returnDefault = false) const override;
+private:
+ CURLU *_url;
+};
+
+} // End of Namespace Networking
+
+#endif
diff --git a/backends/networking/basic/socket.h b/backends/networking/basic/socket.h
new file mode 100644
index 00000000000..5d59ccb4cb8
--- /dev/null
+++ b/backends/networking/basic/socket.h
@@ -0,0 +1,42 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef BACKENDS_NETWORKING_BASIC_SOCKET_H
+#define BACKENDS_NETWORKING_BASIC_SOCKET_H
+
+#include "common/str.h"
+
+namespace Networking {
+
+class Socket {
+public:
+ static Socket *connect(const Common::String &url);
+
+ virtual ~Socket() {}
+
+ virtual int ready() = 0;
+ virtual size_t send(const char *data, int len) = 0;
+ virtual size_t recv(void *data, int maxLen) = 0;
+};
+
+} // End of namespace Networking
+
+#endif
+
diff --git a/backends/networking/curl/url.h b/backends/networking/basic/url.h
similarity index 77%
rename from backends/networking/curl/url.h
rename to backends/networking/basic/url.h
index abd2c6d56b3..83e6b4c9c34 100644
--- a/backends/networking/curl/url.h
+++ b/backends/networking/basic/url.h
@@ -18,40 +18,38 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-#ifndef BACKENDS_NETWORKING_CURL_URL_H
-#define BACKENDS_NETWORKING_CURL_URL_H
-
-typedef struct Curl_URL CURLU;
+#ifndef BACKENDS_NETWORKING_BASIC_URL_H
+#define BACKENDS_NETWORKING_BASIC_URL_H
#include "common/str.h"
namespace Networking {
-class CurlURL {
+class URL {
public:
- CurlURL();
- ~CurlURL();
-
/**
- * Parses an URL string by calling curl_url_set Must be used before using other methods.
+ * Parses an URL string and creates a new URL object.
* @param url is a string containing the URL. e.g. "https://scummvm.org".
- * @retval true if successful.
- * @retval false on failure or if using an older version of libcurl.
+ * @retval An URL object from the url provided
*/
- bool parseURL(const Common::String &url);
+ static URL *parseURL(const Common::String &url);
+
+ virtual ~URL() {}
/**
* Extracts the scheme of an URL parsed previously by parseURL.
* @retval String of the URL's scheme. e.g. "https".
* @retval Empty string on failure.
*/
- Common::String getScheme();
+ virtual Common::String getScheme() const = 0;
+
/**
* Extracts the host name of an URL parsed previously by parseURL.
* @retval String of the URL's host name. e.g. "scummvm.org".
* @retval Empty string on failure.
*/
- Common::String getHost();
+ virtual Common::String getHost() const = 0;
+
/**
* Extracts the port of an URL parsed previously by parseURL.
* @param returnDefault tells libcurl to return the default port according to the URL's scheme if not explicitly defined
@@ -60,11 +58,10 @@ public:
* @retval default port if returnDefault is true.
* @retval -1 on failure.
*/
- int getPort(bool returnDefault = false);
-private:
- CURLU *_url;
+ virtual int getPort(bool returnDefault = false) const = 0;
};
} // End of Namespace Networking
#endif
+
diff --git a/backends/networking/http/curl/networkreadstream-curl.cpp b/backends/networking/http/curl/networkreadstream-curl.cpp
index c1d4137b489..f8c77f9ee9b 100644
--- a/backends/networking/http/curl/networkreadstream-curl.cpp
+++ b/backends/networking/http/curl/networkreadstream-curl.cpp
@@ -22,7 +22,7 @@
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#define CURL_DISABLE_DEPRECATION
-#include "backends/networking/curl/cacert.h"
+#include "backends/networking/basic/curl/cacert.h"
#include "backends/networking/http/curl/networkreadstream-curl.h"
#include "backends/networking/http/curl/connectionmanager-curl.h"
#include "base/version.h"
diff --git a/configure b/configure
index 10def3dd06c..fc7604d0d94 100755
--- a/configure
+++ b/configure
@@ -5996,18 +5996,22 @@ echo "$_libcurl"
define_in_config_if_yes "$_libcurl" "USE_LIBCURL"
-echocheck "HTTP client"
-if test "$_host_os" = "emscripten" && test "$_cloud" = "yes"; then
+echocheck "Network client"
+if test "$_backend" = "emscripten" && test "$_cloud" = "yes"; then
append_var LDFLAGS "-sFETCH"
+ _basicnet=no
_http=yes
- echo "Emscripten"
+ echo "Emscripten (HTTP only)"
elif test "$_libcurl" = "yes"; then
+ _basicnet=yes
_http=yes
echo "libcurl"
else
+ _basicnet=no
_http=no
echo "no"
fi
+define_in_config_if_yes $_basicnet 'USE_BASIC_NET'
define_in_config_if_yes $_http 'USE_HTTP'
#
diff --git a/devtools/create_project/create_project.cpp b/devtools/create_project/create_project.cpp
index 6a030c15506..21352d182b4 100644
--- a/devtools/create_project/create_project.cpp
+++ b/devtools/create_project/create_project.cpp
@@ -1187,6 +1187,7 @@ const Feature s_features[] = {
{ "tinygl", "USE_TINYGL", false, true, "TinyGL support (software) in 3d games" },
{ "taskbar", "USE_TASKBAR", false, true, "Taskbar integration support" },
{ "http", "USE_HTTP", false, true, "HTTP client support" },
+ { "basic-net", "USE_BASIC_NET", false, true, "Basic network support" },
{ "cloud", "USE_CLOUD", false, true, "Cloud integration support" },
{ "enet", "USE_ENET", false, true, "ENet networking support" },
{ "translation", "USE_TRANSLATION", false, true, "Translation support" },
@@ -1299,9 +1300,10 @@ static void fixupFeatures(ProjectType projectType, BuildSetup &setup) {
setFeatureBuildState("mikmod", setup.features, false);
}
- // Only libcurl provides an HTTP client for now
- // (or Emscripten but it's not supported by create_project)
+ // Only libcurl provides basic network and HTTP client for now
+ // (or Emscripten and Android but they are not supported by create_project)
if (!getFeatureBuildState("libcurl", setup.features)) {
+ setFeatureBuildState("basic-net", setup.features, false);
setFeatureBuildState("http", setup.features, false);
}
diff --git a/engines/scumm/dialogs.cpp b/engines/scumm/dialogs.cpp
index 590ab2d12ff..9aaf262b423 100644
--- a/engines/scumm/dialogs.cpp
+++ b/engines/scumm/dialogs.cpp
@@ -1841,7 +1841,7 @@ HENetworkGameOptionsWidget::HENetworkGameOptionsWidget(GuiObject *boss, const Co
if (_gameid == "football" || _gameid == "baseball2001") {
// Lobby configuration (Do not include LAN settings)
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
text->setLabel(_("Online Server:"));
_lobbyServerAddr = new GUI::EditTextWidget(widgetsBoss(), "HENetworkGameOptionsDialog.LobbyServerAddress", Common::U32String(""), _("Address of the server to connect to for online play. It must start with either \"https://\" or \"http://\" schemas."));
_serverResetButton = addClearButton(widgetsBoss(), "HENetworkGameOptionsDialog.ServerReset", kResetServersCmd);
@@ -1874,7 +1874,7 @@ void HENetworkGameOptionsWidget::load() {
_audioOverride->setState(audioOverride);
}
if (_gameid == "football" || _gameid == "baseball2001") {
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
Common::String lobbyServerAddr = "https://multiplayer.scummvm.org:9130";
bool enableCompetitiveMods = false;
@@ -1916,7 +1916,7 @@ bool HENetworkGameOptionsWidget::save() {
if (_audioOverride)
ConfMan.setBool("audio_override", _audioOverride->getState(), _domain);
if (_gameid == "football" || _gameid == "baseball2001") {
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
ConfMan.set("lobby_server", _lobbyServerAddr->getEditString(), _domain);
ConfMan.setBool("enable_competitive_mods", _enableCompetitiveMods->getState(), _domain);
#endif
@@ -1932,7 +1932,7 @@ bool HENetworkGameOptionsWidget::save() {
void HENetworkGameOptionsWidget::defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const {
if (_gameid == "football" || _gameid == "baseball2001") {
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
layouts.addDialog(layoutName, overlayedLayout)
.addLayout(GUI::ThemeLayout::kLayoutVertical, 5)
.addPadding(0, 0, 12, 0)
diff --git a/engines/scumm/dialogs.h b/engines/scumm/dialogs.h
index d3842ce13c6..094288a1cf9 100644
--- a/engines/scumm/dialogs.h
+++ b/engines/scumm/dialogs.h
@@ -439,7 +439,7 @@ private:
GUI::EditTextWidget *_lobbyServerAddr = nullptr;
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
GUI::CheckboxWidget *_enableCompetitiveMods = nullptr;
#endif
diff --git a/engines/scumm/he/intern_he.h b/engines/scumm/he/intern_he.h
index fa56c9f1914..94f94b31ae7 100644
--- a/engines/scumm/he/intern_he.h
+++ b/engines/scumm/he/intern_he.h
@@ -278,7 +278,7 @@ protected:
#ifdef USE_ENET
class Net;
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
class Lobby;
#endif
#endif
@@ -585,7 +585,7 @@ class ScummEngine_v90he : public ScummEngine_v80he {
friend class LogicHE;
#ifdef USE_ENET
friend class Net;
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
friend class Lobby;
#endif
#endif
@@ -682,7 +682,7 @@ protected:
#ifdef USE_ENET
public:
Net *_net;
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
Lobby *_lobby;
#endif
#endif
diff --git a/engines/scumm/he/logic/baseball2001.cpp b/engines/scumm/he/logic/baseball2001.cpp
index 10c7f32a4cc..a37eb68a990 100644
--- a/engines/scumm/he/logic/baseball2001.cpp
+++ b/engines/scumm/he/logic/baseball2001.cpp
@@ -22,7 +22,7 @@
#include "scumm/he/intern_he.h"
#ifdef USE_ENET
#include "scumm/he/net/net_main.h"
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
#include "scumm/he/net/net_lobby.h"
#endif
#include "scumm/he/net/net_defines.h"
@@ -78,7 +78,7 @@ int LogicHEbaseball2001::versionID() {
int LogicHEbaseball2001::startOfFrame() {
#ifdef USE_ENET
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
_vm->_lobby->doNetworkOnceAFrame();
#endif
_vm->_net->doNetworkOnceAFrame(15);
@@ -87,7 +87,7 @@ int LogicHEbaseball2001::startOfFrame() {
}
int32 LogicHEbaseball2001::dispatch(int op, int numArgs, int32 *args) {
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
if (op > 2120 && op < 3003 && op != OP_NET_CHECK_INTERNET_STATUS)
return _vm->_lobby->dispatch(op, numArgs, args);
#endif
@@ -114,7 +114,7 @@ case OP_NET_INIT:
break;
case OP_NET_QUERY_SESSIONS:
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
if (_vm->_lobby->_sessionId) {
_vm->_net->querySessions();
// Only proceed if we've found the session
@@ -125,7 +125,7 @@ case OP_NET_INIT:
break;
case OP_NET_JOIN_SESSION:
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
if (_vm->_lobby->_sessionId) {
res = _vm->_net->joinSessionById(_vm->_lobby->_sessionId);
if (res) {
@@ -159,7 +159,7 @@ case OP_NET_INIT:
#endif // USE_ENET
case OP_NET_CHECK_INTERNET_STATUS:
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
// We can only use the lobby system if both
// libcurl (for lobby communication) and
// ENet (for gameplay communication) is enabled.
diff --git a/engines/scumm/he/logic/football.cpp b/engines/scumm/he/logic/football.cpp
index d9194afe8cc..6ef654e8be6 100644
--- a/engines/scumm/he/logic/football.cpp
+++ b/engines/scumm/he/logic/football.cpp
@@ -26,7 +26,7 @@
#ifdef USE_ENET
#include "scumm/he/net/net_main.h"
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
#include "scumm/he/net/net_lobby.h"
#endif
#include "scumm/he/net/net_defines.h"
@@ -91,7 +91,7 @@ int LogicHEfootball::versionID() {
int LogicHEfootball::startOfFrame() {
#ifdef USE_ENET
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
// Football 2002 does not have lobby support, so
// _lobby is not defined.
if (_vm->_lobby)
@@ -104,7 +104,7 @@ int LogicHEfootball::startOfFrame() {
int32 LogicHEfootball::dispatch(int op, int numArgs, int32 *args) {
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
if (op > 2120 && op < 3003 && op != OP_NET_CHECK_INTERNET_STATUS &&
_vm->_lobby)
return _vm->_lobby->dispatch(op, numArgs, args);
@@ -166,7 +166,7 @@ int32 LogicHEfootball::dispatch(int op, int numArgs, int32 *args) {
break;
case OP_NET_QUERY_SESSIONS:
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
if (_vm->_lobby->_sessionId) {
_vm->_net->querySessions();
// Only proceed if we've found the session
@@ -177,7 +177,7 @@ int32 LogicHEfootball::dispatch(int op, int numArgs, int32 *args) {
break;
case OP_NET_JOIN_SESSION:
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
if (_vm->_lobby->_sessionId) {
res = _vm->_net->joinSessionById(_vm->_lobby->_sessionId);
if (res) {
@@ -211,7 +211,7 @@ int32 LogicHEfootball::dispatch(int op, int numArgs, int32 *args) {
#endif // USE_ENET
case OP_NET_CHECK_INTERNET_STATUS:
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
// We can only use the lobby system if both
// libcurl (for lobby communication) and
// ENet (for gameplay communication) is enabled.
diff --git a/engines/scumm/he/net/net_lobby.cpp b/engines/scumm/he/net/net_lobby.cpp
index dc7b0fd6b54..ff4afa7e069 100644
--- a/engines/scumm/he/net/net_lobby.cpp
+++ b/engines/scumm/he/net/net_lobby.cpp
@@ -353,8 +353,6 @@ bool Lobby::connect() {
if (_socket)
return true;
- _socket = new Networking::CurlSocket();
-
// NOTE: Even though the protocol starts with http(s), this is an entirely
// different protocol. This is done so we can achieve communicating over
// TLS/SSL sockets.
@@ -364,9 +362,9 @@ bool Lobby::connect() {
}
// Parse the URL for checks:
- Networking::CurlURL url;
- if (url.parseURL(lobbyUrl)) {
- Common::String scheme = url.getScheme();
+ Networking::URL *url = Networking::URL::parseURL(lobbyUrl);
+ if (url) {
+ Common::String scheme = url->getScheme();
if (!scheme.contains("http")) {
warning("LOBBY: Unsupported scheme in URL: \"%s\"", scheme.c_str());
writeStringArray(109, "Unsupported scheme in server address");
@@ -374,7 +372,7 @@ bool Lobby::connect() {
return false;
}
- int port = url.getPort();
+ int port = url->getPort();
switch (port) {
case -1:
warning("LOBBY: Unable to get port.");
@@ -386,17 +384,18 @@ bool Lobby::connect() {
lobbyUrl += ":9130";
break;
}
+
+ delete url;
} else
warning("LOBBY: Could not parse URL, attempting to connect as is");
debugC(DEBUG_NETWORK, "LOBBY: Connecting to %s", lobbyUrl.c_str());
- if (_socket->connect(lobbyUrl)) {
+ _socket = Networking::Socket::connect(lobbyUrl);
+ if (_socket) {
debugC(DEBUG_NETWORK, "LOBBY: Successfully connected to %s", lobbyUrl.c_str());
return true;
} else {
- delete _socket;
- _socket = nullptr;
writeStringArray(109, "Unable to contact server");
_vm->writeVar(108, -99);
}
diff --git a/engines/scumm/he/net/net_lobby.h b/engines/scumm/he/net/net_lobby.h
index 81105f0893c..0c879f43160 100644
--- a/engines/scumm/he/net/net_lobby.h
+++ b/engines/scumm/he/net/net_lobby.h
@@ -22,8 +22,8 @@
#ifndef SCUMM_HE_NET_LOBBY_H
#define SCUMM_HE_NET_LOBBY_H
-#include "backends/networking/curl/socket.h"
-#include "backends/networking/curl/url.h"
+#include "backends/networking/basic/socket.h"
+#include "backends/networking/basic/url.h"
#include "common/formats/json.h"
#include "scumm/he/net/net_main.h"
@@ -96,7 +96,7 @@ public:
protected:
ScummEngine_v90he *_vm;
Common::String _gameName;
- Networking::CurlSocket *_socket;
+ Networking::Socket *_socket;
Common::String _buffer;
diff --git a/engines/scumm/he/script_v72he.cpp b/engines/scumm/he/script_v72he.cpp
index 3969e7b6725..b2ea13ef1e8 100644
--- a/engines/scumm/he/script_v72he.cpp
+++ b/engines/scumm/he/script_v72he.cpp
@@ -165,7 +165,7 @@ int ScummEngine_v72he::readArray(int array, int idx2, int idx1) {
FROM_LE_32(ah->downMin), FROM_LE_32(ah->downMax));
}
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
if (_enableHECompetitiveOnlineMods) {
// Mod for Backyard Baseball 2001 online competitive play: allow baserunners to be
// turned around while they're jogging to the next base on a pop-up.
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 3b770ae2842..a2cc2b66021 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -216,7 +216,7 @@ MODULE_OBJS += \
dialog-sessionselector.o \
he/net/net_main.o
-ifdef USE_LIBCURL
+ifdef USE_BASIC_NET
MODULE_OBJS += \
he/net/net_lobby.o
endif
diff --git a/engines/scumm/script.cpp b/engines/scumm/script.cpp
index 62df5793891..a4aecc499a0 100644
--- a/engines/scumm/script.cpp
+++ b/engines/scumm/script.cpp
@@ -600,7 +600,7 @@ int ScummEngine::readVar(uint var) {
return 2;
}
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
if (_enableHECompetitiveOnlineMods) {
// HACK: If we're reading var586, competitive mods enabled, playing online,
// successfully fetched custom teams, and we're not in one of the three scripts
@@ -626,7 +626,7 @@ int ScummEngine::readVar(uint var) {
var &= 0xFFF;
assertRange(0, var, _numRoomVariables - 1, "room variable (reading)");
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
if (_enableHECompetitiveOnlineMods) {
// Mod for Backyard Baseball 2001 online competitive play: don't give powerups for double plays
// Return true for this variable, which dictates whether powerups are disabled, but only in this script
@@ -677,7 +677,7 @@ int ScummEngine::readVar(uint var) {
assertRange(0, var, 25, "local variable (reading)");
else
assertRange(0, var, 20, "local variable (reading)");
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
// Mod for Backyard Baseball 2001 online competitive play: change impact of
// batter's power stat on hit power
if (_enableHECompetitiveOnlineMods) {
diff --git a/engines/scumm/script_v6.cpp b/engines/scumm/script_v6.cpp
index fdd3d189a05..5078badd556 100644
--- a/engines/scumm/script_v6.cpp
+++ b/engines/scumm/script_v6.cpp
@@ -500,7 +500,7 @@ void ScummEngine_v6::o6_pushByteVar() {
void ScummEngine_v6::o6_pushWordVar() {
// BACKYARD BASEBALL 2001 ONLINE CHANGES
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
if (_enableHECompetitiveOnlineMods) {
// Sprinting in competitive Backyard Baseball is considered too weak in its current state. This will increase how effective
// it is, limiting the highest speed characters enough to where they cannot go TOO fast.
@@ -585,7 +585,7 @@ void ScummEngine_v6::o6_byteArrayRead() {
void ScummEngine_v6::o6_wordArrayRead() {
int base = pop();
int array = fetchScriptWord();
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
if (_enableHECompetitiveOnlineMods) {
// If we're pulling from the randomly selected teams for online play
// at Prince Rupert, read from variables 748 and 749 instead
@@ -635,7 +635,7 @@ void ScummEngine_v6::o6_eq() {
int b = pop();
// BACKYARD BASEBALL 2001 ONLINE CHANGES
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
// The player stat adjustments that should get applied in certain conditions (i.e. when two siblings are on the same team)
// don't get applied properly for the away (peer) team in online play. This results in each team's game using a different
// version of players' stats, leading to unfair play and potential desyncs. This hack ensures the away team's game doesn't
@@ -752,7 +752,7 @@ void ScummEngine_v6::o6_eq() {
}
#endif
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
int offset = _scriptPointer - _scriptOrgPointer;
// WORKAROUND: In Backyard Baseball 2001, The special rules of the Mountain Aire and Wilderness neighborhoods
// are incorrect. They were set to "3 innings" and "no swing spot" respectively, while they were supposed to be set to
@@ -856,7 +856,7 @@ void ScummEngine_v6::o6_le() {
void ScummEngine_v6::o6_ge() {
int a = pop();
int b = pop();
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
// Mod for Backyard Baseball 2001 online competitive play: Reduce sprints
// required to reach top speed
if (_enableHECompetitiveOnlineMods && _game.id == GID_BASEBALL2001 &&
@@ -887,7 +887,7 @@ void ScummEngine_v6::o6_div() {
if (a == 0)
error("division by zero");
int b = pop();
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
// Mod for Backyard Baseball 2001 online competitive play: Allow full sprinting while
// running half-speed on a popup
if (_enableHECompetitiveOnlineMods && _game.id == GID_BASEBALL2001 && _currentRoom == 3 &&
@@ -1144,7 +1144,7 @@ void ScummEngine_v6::o6_startScriptQuick2() {
int script;
getStackList(args, ARRAYSIZE(args));
script = pop();
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
// Mod for Backyard Baseball 2001 online competitive play: change effect of
// pitch location on hit quality
if (_enableHECompetitiveOnlineMods && _game.id == GID_BASEBALL2001 && _currentRoom == 4 && script == 2085 && readVar(399) == 1) {
@@ -1715,7 +1715,7 @@ void ScummEngine_v6::o6_getRandomNumberRange() {
int min = pop();
int rnd = _rnd.getRandomNumber(0x7fff);
rnd = min + (rnd % (max - min + 1));
-#if defined(USE_ENET) && defined(USE_LIBCURL)
+#if defined(USE_ENET) && defined(USE_BASIC_NET)
if (_enableHECompetitiveOnlineMods) {
// For using predefined teams in Prince Rupert, instead of choosing player IDs randomly
// let's pull from the variables that contain the teams
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 42adf014649..b65972a479f 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -97,7 +97,7 @@
#include "scumm/he/net/net_main.h"
#include "scumm/dialog-sessionselector.h"
#include "scumm/dialog-createsession.h"
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
#include "scumm/he/net/net_lobby.h"
#endif
#endif
@@ -807,7 +807,7 @@ ScummEngine_v90he::ScummEngine_v90he(OSystem *syst, const DetectorResult &dr)
_game.id == GID_MOONBASE) {
_net = new Net(this);
}
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
_lobby = 0;
if (_game.id == GID_FOOTBALL || _game.id == GID_BASEBALL2001)
_lobby = new Lobby(this);
@@ -830,7 +830,7 @@ ScummEngine_v90he::~ScummEngine_v90he() {
#ifdef USE_ENET
delete _net;
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
delete _lobby;
#endif
#endif
diff --git a/engines/scumm/vars.cpp b/engines/scumm/vars.cpp
index a1a47ced8d1..e5f31017814 100644
--- a/engines/scumm/vars.cpp
+++ b/engines/scumm/vars.cpp
@@ -772,7 +772,7 @@ void ScummEngine_v100he::resetScummVars() {
ScummEngine_v99he::resetScummVars();
if (_game.id == GID_MOONBASE) {
-#ifdef USE_LIBCURL
+#ifdef USE_BASIC_NET
VAR(VAR_NETWORK_AVAILABLE) = 1;
#else
VAR(VAR_NETWORK_AVAILABLE) = 0;
Commit: b5dc393669d5ea504f714300eb1576fb1843a8bd
https://github.com/scummvm/scummvm/commit/b5dc393669d5ea504f714300eb1576fb1843a8bd
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2025-11-02T09:12:23+01:00
Commit Message:
TESTBED: Add tests for basic network features
Changed paths:
engines/testbed/networking.cpp
engines/testbed/networking.h
diff --git a/engines/testbed/networking.cpp b/engines/testbed/networking.cpp
index d03063e9a6c..420e29e1703 100644
--- a/engines/testbed/networking.cpp
+++ b/engines/testbed/networking.cpp
@@ -21,10 +21,19 @@
#include "testbed/networking.h"
+#ifdef USE_BASIC_NET
+#include "backends/networking/basic/socket.h"
+#include "backends/networking/basic/url.h"
+#endif
+
namespace Testbed {
NetworkingTestSuite::NetworkingTestSuite() {
addTest("IsConnectionLimited", Networkingtests::testConnectionLimit, true);
+#ifdef USE_BASIC_NET
+ addTest("URL", Networkingtests::testURL, true);
+ addTest("Socket", Networkingtests::testSocket, true);
+#endif
}
TestExitStatus Networkingtests::testConnectionLimit() {
@@ -51,4 +60,92 @@ TestExitStatus Networkingtests::testConnectionLimit() {
return TestExitStatus();
}
+#ifdef USE_BASIC_NET
+TestExitStatus Networkingtests::testURL() {
+ if (ConfParams.isSessionInteractive()) {
+ if (Testsuite::handleInteractiveInput("Testing the URL API implementation", "Continue", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! URL test skipped by the user.\n");
+ return kTestSkipped;
+ }
+
+ Common::String lobbyUrl = "https://multiplayer.scummvm.org:9130";
+ Networking::URL *url = Networking::URL::parseURL(lobbyUrl);
+ if (!url) {
+ Testsuite::logDetailedPrintf("Error! URL parsing failed\n");
+ return kTestFailed;
+ }
+
+ if (url->getScheme() != "https") {
+ Testsuite::logDetailedPrintf("Error! URL scheme was unexpected\n");
+ return kTestFailed;
+ }
+
+ if (url->getHost() != "multiplayer.scummvm.org") {
+ Testsuite::logDetailedPrintf("Error! URL host was unexpected\n");
+ return kTestFailed;
+ }
+
+ if (url->getPort() != 9130) {
+ Testsuite::logDetailedPrintf("Error! URL port was unexpected\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("URL test worked\n");
+ return kTestPassed;
+ }
+ return TestExitStatus();
+}
+
+TestExitStatus Networkingtests::testSocket() {
+ if (ConfParams.isSessionInteractive()) {
+ if (Testsuite::handleInteractiveInput("Testing the Socket API implementation", "Continue", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Socket test skipped by the user.\n");
+ return kTestSkipped;
+ }
+
+ Common::String lobbyUrl = "https://multiplayer.scummvm.org:9130";
+ Networking::Socket *socket = Networking::Socket::connect(lobbyUrl);
+ if (!socket) {
+ Testsuite::logDetailedPrintf("Error! Socket connection failed\n");
+ return kTestFailed;
+ }
+
+ const char *send = "{\"cmd\": \"get_population\", \"area\": 8}\n";
+ size_t length = strlen(send);
+
+ if (socket->send(send, length) != length) {
+ Testsuite::logDetailedPrintf("Error! Socket send failed\n");
+ return kTestFailed;
+ }
+
+ int i;
+ for (i = 0; i < 10; i++) {
+ if (socket->ready()) {
+ break;
+ }
+ g_system->delayMillis(1000);
+ }
+
+ if (i == 10) {
+ Testsuite::logDetailedPrintf("Error! Socket ready check failed\n");
+ return kTestFailed;
+ }
+
+ char reply[1024];
+ length = socket->recv(reply, sizeof(reply));
+ if (length == 0) {
+ Testsuite::logDetailedPrintf("Error! Socket receive failed\n");
+ return kTestFailed;
+ }
+ reply[length] = 0;
+
+ debug("Socket received: %s", reply);
+
+ Testsuite::logDetailedPrintf("URL test worked\n");
+ return kTestPassed;
+ }
+ return TestExitStatus();
+}
+#endif
+
} // namespace Testbed
diff --git a/engines/testbed/networking.h b/engines/testbed/networking.h
index b2c948bb5ba..2b7138638ea 100644
--- a/engines/testbed/networking.h
+++ b/engines/testbed/networking.h
@@ -33,6 +33,10 @@ namespace Networkingtests {
// Helper functions for Networking tests
TestExitStatus testConnectionLimit();
+#ifdef USE_BASIC_NET
+TestExitStatus testSocket();
+TestExitStatus testURL();
+#endif
} // End of namespace Networkingtests
Commit: 980fd5b54d31513a91d33eca9a4c903ca544bfd3
https://github.com/scummvm/scummvm/commit/980fd5b54d31513a91d33eca9a4c903ca544bfd3
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2025-11-02T09:12:23+01:00
Commit Message:
ANDROID: Implement platform specific networking features
This allows to use system security services instead of us providing curl
and openssl.
HTTP, Socket and URL are all implemented with proxy support.
Changed paths:
A backends/networking/basic/android/jni.cpp
A backends/networking/basic/android/jni.h
A backends/networking/basic/android/socket.cpp
A backends/networking/basic/android/socket.h
A backends/networking/basic/android/url.cpp
A backends/networking/basic/android/url.h
A backends/networking/http/android/connectionmanager-android.cpp
A backends/networking/http/android/connectionmanager-android.h
A backends/networking/http/android/networkreadstream-android.cpp
A backends/networking/http/android/networkreadstream-android.h
A backends/platform/android/org/scummvm/scummvm/net/HTTPManager.java
A backends/platform/android/org/scummvm/scummvm/net/HTTPRequest.java
A backends/platform/android/org/scummvm/scummvm/net/SSocket.java
backends/module.mk
backends/networking/basic/curl/cacert.cpp
backends/platform/android/android.mk
configure
diff --git a/backends/module.mk b/backends/module.mk
index ae41dea4101..2dd0b82f6c5 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -340,7 +340,16 @@ MODULE_OBJS += \
fs/android/android-posix-fs.o \
fs/android/android-saf-fs.o \
graphics/android/android-graphics.o \
- mutex/pthread/pthread-mutex.o
+ mutex/pthread/pthread-mutex.o \
+ networking/basic/android/jni.o \
+ networking/basic/android/socket.o \
+ networking/basic/android/url.o
+
+ifdef USE_HTTP
+MODULE_OBJS += \
+ networking/http/android/connectionmanager-android.o \
+ networking/http/android/networkreadstream-android.o
+endif
endif
ifdef AMIGAOS
diff --git a/backends/networking/basic/android/jni.cpp b/backends/networking/basic/android/jni.cpp
new file mode 100644
index 00000000000..a0d44b62607
--- /dev/null
+++ b/backends/networking/basic/android/jni.cpp
@@ -0,0 +1,173 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Allow use of stuff in <time.h> and abort()
+#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
+#define FORBIDDEN_SYMBOL_EXCEPTION_abort
+
+// Disable printf override in common/forbidden.h to avoid
+// clashes with log.h from the Android SDK.
+// That header file uses
+// __attribute__ ((format(printf, 3, 4)))
+// which gets messed up by our override mechanism; this could
+// be avoided by either changing the Android SDK to use the equally
+// legal and valid
+// __attribute__ ((format(__printf__, 3, 4)))
+// or by refining our printf override to use a varadic macro
+// (which then wouldn't be portable, though).
+// Anyway, for now we just disable the printf override globally
+// for the Android port
+#define FORBIDDEN_SYMBOL_EXCEPTION_printf
+
+#include "backends/platform/android/android.h"
+
+#include "backends/networking/basic/android/jni.h"
+#ifdef USE_HTTP
+#include "backends/networking/http/android/networkreadstream-android.h"
+#endif
+
+namespace Networking {
+
+jclass NetJNI::_CLS_URL = nullptr;
+
+jmethodID NetJNI::_MID_url_init = 0;
+jmethodID NetJNI::_MID_url_getProtocol = 0;
+jmethodID NetJNI::_MID_url_getHost = 0;
+jmethodID NetJNI::_MID_url_getPort = 0;
+jmethodID NetJNI::_MID_url_getDefaultPort = 0;
+
+jclass NetJNI::_CLS_Socket = nullptr;
+
+jmethodID NetJNI::_MID_socket_init = 0;
+jmethodID NetJNI::_MID_socket_ready = 0;
+jmethodID NetJNI::_MID_socket_send = 0;
+jmethodID NetJNI::_MID_socket_recv = 0;
+jmethodID NetJNI::_MID_socket_close = 0;
+
+#ifdef USE_HTTP
+jmethodID NetJNI::_MID_manager_init = 0;
+jmethodID NetJNI::_MID_manager_startRequest = 0;
+jmethodID NetJNI::_MID_manager_poll = 0;
+jfieldID NetJNI::_FID_manager__empty = 0;
+
+jclass NetJNI::_CLS_HTTPRequest = nullptr;
+
+jmethodID NetJNI::_MID_request_bufinit = 0;
+jmethodID NetJNI::_MID_request_forminit = 0;
+jmethodID NetJNI::_MID_request_cancel = 0;
+jmethodID NetJNI::_MID_request_getURL = 0;
+
+const JNINativeMethod NetJNI::_natives_request[] = {
+ { "gotHeaders", "(J[Ljava/lang/String;)V",
+ (void *)NetworkReadStreamAndroid::gotHeaders },
+ { "gotData", "(J[BII)V",
+ (void *)NetworkReadStreamAndroid::gotData },
+ { "finished", "(JILjava/lang/String;)V",
+ (void *)NetworkReadStreamAndroid::finished_ },
+};
+#endif
+
+bool NetJNI::_init = false;
+
+void NetJNI::init(JNIEnv *env) {
+ if (_init) {
+ return;
+ }
+
+ // We can't call error here as the backend is not built yet
+#define FIND_CONSTRUCTOR(prefix, signature) do { \
+ _MID_ ## prefix ## init = env->GetMethodID(cls, "<init>", signature "V");\
+ if (_MID_ ## prefix ## init == 0) { \
+ LOGE("Can't find method ID <init>"); \
+ abort(); \
+ } \
+ } while (0)
+#define FIND_METHOD(prefix, name, signature) do { \
+ _MID_ ## prefix ## name = env->GetMethodID(cls, #name, signature); \
+ if (_MID_ ## prefix ## name == 0) { \
+ LOGE("Can't find method ID " #name); \
+ abort(); \
+ } \
+ } while (0)
+#define FIND_FIELD(prefix, name, signature) do { \
+ _FID_ ## prefix ## name = env->GetFieldID(cls, #name, signature); \
+ if (_FID_ ## prefix ## name == 0) { \
+ LOGE("Can't find field ID " #name); \
+ abort(); \
+ } \
+ } while (0)
+
+ jclass cls = env->FindClass("java/net/URL");
+ _CLS_URL = (jclass)env->NewGlobalRef(cls);
+
+ FIND_CONSTRUCTOR(url_, "(Ljava/lang/String;)");
+ FIND_METHOD(url_, getProtocol, "()Ljava/lang/String;");
+ FIND_METHOD(url_, getHost, "()Ljava/lang/String;");
+ FIND_METHOD(url_, getPort, "()I");
+ FIND_METHOD(url_, getDefaultPort, "()I");
+
+ env->DeleteLocalRef(cls);
+
+ cls = env->FindClass("org/scummvm/scummvm/net/SSocket");
+ _CLS_Socket = (jclass)env->NewGlobalRef(cls);
+
+ FIND_CONSTRUCTOR(socket_, "(Ljava/lang/String;)");
+ FIND_METHOD(socket_, ready, "()I");
+ FIND_METHOD(socket_, send, "([B)I");
+ FIND_METHOD(socket_, recv, "([B)I");
+ FIND_METHOD(socket_, close, "()V");
+
+ env->DeleteLocalRef(cls);
+
+#ifdef USE_HTTP
+ cls = env->FindClass("org/scummvm/scummvm/net/HTTPManager");
+
+ FIND_CONSTRUCTOR(manager_, "()");
+ FIND_METHOD(manager_, startRequest, "(Lorg/scummvm/scummvm/net/HTTPRequest;)V");
+ FIND_METHOD(manager_, poll, "()V");
+ FIND_FIELD(manager_, _empty, "Z");
+
+ env->DeleteLocalRef(cls);
+
+ cls = env->FindClass("org/scummvm/scummvm/net/HTTPRequest");
+ _CLS_HTTPRequest = (jclass)env->NewGlobalRef(cls);
+
+ if (env->RegisterNatives(cls, _natives_request, ARRAYSIZE(_natives_request)) < 0) {
+ LOGE("Can't register natives for org/scummvm/scummvm/net/HTTPRequest");
+ abort();
+ }
+
+ FIND_CONSTRUCTOR(request_buf, "(JLjava/lang/String;[Ljava/lang/String;[BZZZ)");
+ FIND_CONSTRUCTOR(request_form, "(JLjava/lang/String;[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)");
+ FIND_METHOD(request_, cancel, "()V");
+ FIND_METHOD(request_, getURL, "()Ljava/lang/String;");
+
+ env->DeleteLocalRef(cls);
+#endif
+
+#undef FIND_FIELD
+#undef FIND_METHOD
+#undef FIND_CONSTRUCTOR
+
+ _init = true;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/basic/android/jni.h b/backends/networking/basic/android/jni.h
new file mode 100644
index 00000000000..8358341bfe7
--- /dev/null
+++ b/backends/networking/basic/android/jni.h
@@ -0,0 +1,79 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BACKENDS_NETWORKING_BASIC_ANDROID_JNI_H
+#define BACKENDS_NETWORKING_BASIC_ANDROID_JNI_H
+
+#include <jni.h>
+
+namespace Networking {
+
+class NetJNI {
+#ifdef USE_HTTP
+ friend class ConnectionManagerAndroid;
+ friend class NetworkReadStreamAndroid;
+#endif
+ friend class AndroidSocket;
+ friend class AndroidURL;
+
+public:
+ static void init(JNIEnv *env);
+
+private:
+ static jclass _CLS_URL;
+
+ static jmethodID _MID_url_init;
+ static jmethodID _MID_url_getProtocol;
+ static jmethodID _MID_url_getHost;
+ static jmethodID _MID_url_getPort;
+ static jmethodID _MID_url_getDefaultPort;
+
+ static jclass _CLS_Socket;
+
+ static jmethodID _MID_socket_init;
+ static jmethodID _MID_socket_ready;
+ static jmethodID _MID_socket_send;
+ static jmethodID _MID_socket_recv;
+ static jmethodID _MID_socket_close;
+
+#ifdef USE_HTTP
+ static jmethodID _MID_manager_init;
+ static jmethodID _MID_manager_startRequest;
+ static jmethodID _MID_manager_poll;
+ static jfieldID _FID_manager__empty;
+
+ static const JNINativeMethod _natives_request[];
+
+ static jclass _CLS_HTTPRequest;
+
+ static jmethodID _MID_request_bufinit;
+ static jmethodID _MID_request_forminit;
+ static jmethodID _MID_request_cancel;
+ static jmethodID _MID_request_getURL;
+#endif
+
+ static bool _init;
+};
+
+} // End of namespace Networking
+
+#endif
+
diff --git a/backends/networking/basic/android/socket.cpp b/backends/networking/basic/android/socket.cpp
new file mode 100644
index 00000000000..53f1cd44dcf
--- /dev/null
+++ b/backends/networking/basic/android/socket.cpp
@@ -0,0 +1,156 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Allow use of stuff in <time.h> and abort()
+#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
+#define FORBIDDEN_SYMBOL_EXCEPTION_abort
+
+// Disable printf override in common/forbidden.h to avoid
+// clashes with log.h from the Android SDK.
+// That header file uses
+// __attribute__ ((format(printf, 3, 4)))
+// which gets messed up by our override mechanism; this could
+// be avoided by either changing the Android SDK to use the equally
+// legal and valid
+// __attribute__ ((format(__printf__, 3, 4)))
+// or by refining our printf override to use a varadic macro
+// (which then wouldn't be portable, though).
+// Anyway, for now we just disable the printf override globally
+// for the Android port
+#define FORBIDDEN_SYMBOL_EXCEPTION_printf
+
+#include "backends/platform/android/android.h"
+#include "backends/platform/android/jni-android.h"
+
+#include "backends/networking/basic/android/jni.h"
+
+#include "backends/networking/basic/android/socket.h"
+
+#include "common/debug.h"
+#include "common/textconsole.h"
+
+namespace Networking {
+
+Socket *Socket::connect(const Common::String &url) {
+ return AndroidSocket::connect(url);
+}
+
+Socket *AndroidSocket::connect(const Common::String &url) {
+ JNIEnv *env = JNI::getEnv();
+ NetJNI::init(env);
+
+ jstring url_obj = env->NewStringUTF(url.c_str());
+ jobject socket_obj = env->NewObject(NetJNI::_CLS_Socket, NetJNI::_MID_socket_init, url_obj);
+
+ env->DeleteLocalRef(url_obj);
+
+ if (env->ExceptionCheck()) {
+ LOGE("Socket::<init> failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ return nullptr;
+ }
+
+ Socket *socket = new AndroidSocket(env, socket_obj);
+ env->DeleteLocalRef(socket_obj);
+
+ return socket;
+}
+
+AndroidSocket::AndroidSocket(JNIEnv *env, jobject socket) {
+ _socket = env->NewGlobalRef(socket);
+}
+
+AndroidSocket::~AndroidSocket() {
+ JNIEnv *env = JNI::getEnv();
+
+ env->CallVoidMethod(_socket, NetJNI::_MID_socket_close);
+ if (env->ExceptionCheck()) {
+ LOGE("Socket::close failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+
+ env->DeleteGlobalRef(_socket);
+}
+
+int AndroidSocket::ready() {
+ JNIEnv *env = JNI::getEnv();
+
+ jint ret = env->CallIntMethod(_socket, NetJNI::_MID_socket_ready);
+ if (env->ExceptionCheck()) {
+ LOGE("Socket::ready failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ // In doubt make it ready
+ return 1;
+ }
+
+ return ret;
+}
+
+size_t AndroidSocket::send(const char *data, int len) {
+ JNIEnv *env = JNI::getEnv();
+
+ jbyteArray buffer_obj = env->NewByteArray(len);
+ env->SetByteArrayRegion(buffer_obj, 0, len, (const jbyte *)data);
+
+ jint sent = env->CallIntMethod(_socket, NetJNI::_MID_socket_send, buffer_obj);
+
+ env->DeleteLocalRef(buffer_obj);
+
+ if (env->ExceptionCheck()) {
+ LOGE("Socket::send failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ return 0;
+ }
+
+ return sent;
+}
+
+size_t AndroidSocket::recv(void *data, int maxLen) {
+ JNIEnv *env = JNI::getEnv();
+
+ jbyteArray buffer_obj = env->NewByteArray(maxLen);
+
+ jint recvd = env->CallIntMethod(_socket, NetJNI::_MID_socket_recv, buffer_obj);
+ assert(recvd <= maxLen);
+
+ if (env->ExceptionCheck()) {
+ LOGE("Socket::send failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ env->DeleteLocalRef(buffer_obj);
+ return 0;
+ }
+
+ env->GetByteArrayRegion(buffer_obj, 0, recvd, (jbyte *)data);
+
+ return recvd;
+}
+
+} // End of namespace Networking
+
diff --git a/backends/networking/basic/android/socket.h b/backends/networking/basic/android/socket.h
new file mode 100644
index 00000000000..8714a99101c
--- /dev/null
+++ b/backends/networking/basic/android/socket.h
@@ -0,0 +1,47 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef BACKENDS_NETWORKING_BASIC_CURL_SOCKET_H
+#define BACKENDS_NETWORKING_BASIC_CURL_SOCKET_H
+
+#include <jni.h>
+
+#include "backends/networking/basic/socket.h"
+
+namespace Networking {
+
+class AndroidSocket : public Socket {
+public:
+ static Socket *connect(const Common::String &url);
+
+ AndroidSocket(JNIEnv *env, jobject socket);
+ ~AndroidSocket() override;
+
+ int ready() override;
+
+ size_t send(const char *data, int len) override;
+ size_t recv(void *data, int maxLen) override;
+private:
+ jobject _socket;
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/basic/android/url.cpp b/backends/networking/basic/android/url.cpp
new file mode 100644
index 00000000000..92ac010cf4b
--- /dev/null
+++ b/backends/networking/basic/android/url.cpp
@@ -0,0 +1,180 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Allow use of stuff in <time.h> and abort()
+#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
+#define FORBIDDEN_SYMBOL_EXCEPTION_abort
+
+// Disable printf override in common/forbidden.h to avoid
+// clashes with log.h from the Android SDK.
+// That header file uses
+// __attribute__ ((format(printf, 3, 4)))
+// which gets messed up by our override mechanism; this could
+// be avoided by either changing the Android SDK to use the equally
+// legal and valid
+// __attribute__ ((format(__printf__, 3, 4)))
+// or by refining our printf override to use a varadic macro
+// (which then wouldn't be portable, though).
+// Anyway, for now we just disable the printf override globally
+// for the Android port
+#define FORBIDDEN_SYMBOL_EXCEPTION_printf
+
+#include "backends/platform/android/android.h"
+#include "backends/platform/android/jni-android.h"
+
+#include "backends/networking/basic/android/jni.h"
+
+#include "backends/networking/basic/android/url.h"
+
+#include "common/debug.h"
+#include "common/textconsole.h"
+
+namespace Networking {
+
+URL *URL::parseURL(const Common::String &url) {
+ return AndroidURL::parseURL(url);
+}
+
+URL *AndroidURL::parseURL(const Common::String &url) {
+ JNIEnv *env = JNI::getEnv();
+ NetJNI::init(env);
+
+ jstring url_sobj = env->NewStringUTF(url.c_str());
+ jobject url_obj = env->NewObject(NetJNI::_CLS_URL, NetJNI::_MID_url_init, url_sobj);
+
+ env->DeleteLocalRef(url_sobj);
+
+ if (env->ExceptionCheck()) {
+ LOGE("URL::<init> failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ return nullptr;
+ }
+
+ URL *url_ = new AndroidURL(env, url_obj);
+ env->DeleteLocalRef(url_obj);
+
+ return url_;
+}
+
+AndroidURL::AndroidURL(JNIEnv *env, jobject url) {
+ _url = env->NewGlobalRef(url);
+}
+
+AndroidURL::~AndroidURL() {
+ JNIEnv *env = JNI::getEnv();
+ env->DeleteGlobalRef(_url);
+}
+
+Common::String AndroidURL::getScheme() const {
+ JNIEnv *env = JNI::getEnv();
+
+ jstring protocol_obj = (jstring)env->CallObjectMethod(_url, NetJNI::_MID_url_getProtocol);
+ if (env->ExceptionCheck()) {
+ LOGE("URL::getProtocol failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ return Common::String();
+ }
+
+ uint length = env->GetStringLength(protocol_obj);
+ if (!length) {
+ env->DeleteLocalRef(protocol_obj);
+ return Common::String();
+ }
+
+ const char *protocol_ptr = env->GetStringUTFChars(protocol_obj, 0);
+ if (!protocol_ptr) {
+ env->DeleteLocalRef(protocol_obj);
+ return Common::String();
+ }
+
+ Common::String result(protocol_ptr, length);
+
+ env->ReleaseStringUTFChars(protocol_obj, protocol_ptr);
+ env->DeleteLocalRef(protocol_obj);
+
+ return result;
+}
+
+Common::String AndroidURL::getHost() const {
+ JNIEnv *env = JNI::getEnv();
+
+ jstring protocol_obj = (jstring)env->CallObjectMethod(_url, NetJNI::_MID_url_getHost);
+ if (env->ExceptionCheck()) {
+ LOGE("URL::getHost failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ return Common::String();
+ }
+
+ uint length = env->GetStringLength(protocol_obj);
+ if (!length) {
+ env->DeleteLocalRef(protocol_obj);
+ return Common::String();
+ }
+
+ const char *protocol_ptr = env->GetStringUTFChars(protocol_obj, 0);
+ if (!protocol_ptr) {
+ env->DeleteLocalRef(protocol_obj);
+ return Common::String();
+ }
+
+ Common::String result(protocol_ptr, length);
+
+ env->ReleaseStringUTFChars(protocol_obj, protocol_ptr);
+ env->DeleteLocalRef(protocol_obj);
+
+ return result;
+}
+
+int AndroidURL::getPort(bool defaultPort) const {
+ JNIEnv *env = JNI::getEnv();
+
+ jint port = env->CallIntMethod(_url, NetJNI::_MID_url_getPort);
+ if (env->ExceptionCheck()) {
+ LOGE("URL::getPort failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ return -1;
+ }
+ if (port == -1 && defaultPort) {
+ port = env->CallIntMethod(_url, NetJNI::_MID_url_getDefaultPort);
+ if (env->ExceptionCheck()) {
+ LOGE("URL::getDefaultPort failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ return -1;
+ }
+ }
+ if (port == -1) {
+ port = 0;
+ }
+
+ return port;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/basic/android/url.h b/backends/networking/basic/android/url.h
new file mode 100644
index 00000000000..a1b55857882
--- /dev/null
+++ b/backends/networking/basic/android/url.h
@@ -0,0 +1,47 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef BACKENDS_NETWORKING_BASIC_ANDROID_URL_H
+#define BACKENDS_NETWORKING_BASIC_ANDROID_URL_H
+
+#include <jni.h>
+
+#include "backends/networking/basic/url.h"
+
+namespace Networking {
+
+class AndroidURL : public URL {
+public:
+ static URL *parseURL(const Common::String &url);
+
+ AndroidURL(JNIEnv *env, jobject url);
+
+ ~AndroidURL() override;
+
+ Common::String getScheme() const override;
+ Common::String getHost() const override;
+ int getPort(bool returnDefault = false) const override;
+private:
+ jobject _url;
+};
+
+} // End of Namespace Networking
+
+#endif
diff --git a/backends/networking/basic/curl/cacert.cpp b/backends/networking/basic/curl/cacert.cpp
index 1d80c88ead5..24ced7fae4a 100644
--- a/backends/networking/basic/curl/cacert.cpp
+++ b/backends/networking/basic/curl/cacert.cpp
@@ -19,10 +19,6 @@
*
*/
-#if defined(ANDROID_BACKEND)
-#include "backends/platform/android/jni-android.h"
-#endif
-
#include "backends/networking/basic/curl/cacert.h"
#include "common/fs.h"
@@ -30,12 +26,7 @@
namespace Networking {
Common::String getCaCertPath() {
-#if defined(ANDROID_BACKEND)
- // cacert path must exist on filesystem and be reachable by standard open syscall
- // Lets use ScummVM internal directory
- Common::String assetsPath = JNI::getScummVMAssetsPath();
- return assetsPath + "/cacert.pem";
-#elif defined(DATA_PATH)
+#if defined(DATA_PATH)
static enum {
kNotInitialized,
kFileNotFound,
diff --git a/backends/networking/http/android/connectionmanager-android.cpp b/backends/networking/http/android/connectionmanager-android.cpp
new file mode 100644
index 00000000000..fbef9672b5b
--- /dev/null
+++ b/backends/networking/http/android/connectionmanager-android.cpp
@@ -0,0 +1,113 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Allow use of stuff in <time.h> and abort()
+#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
+#define FORBIDDEN_SYMBOL_EXCEPTION_abort
+
+// Disable printf override in common/forbidden.h to avoid
+// clashes with log.h from the Android SDK.
+// That header file uses
+// __attribute__ ((format(printf, 3, 4)))
+// which gets messed up by our override mechanism; this could
+// be avoided by either changing the Android SDK to use the equally
+// legal and valid
+// __attribute__ ((format(__printf__, 3, 4)))
+// or by refining our printf override to use a varadic macro
+// (which then wouldn't be portable, though).
+// Anyway, for now we just disable the printf override globally
+// for the Android port
+#define FORBIDDEN_SYMBOL_EXCEPTION_printf
+
+#include "backends/platform/android/android.h"
+#include "backends/platform/android/jni-android.h"
+
+#include "backends/networking/basic/android/jni.h"
+
+#include "backends/networking/http/android/connectionmanager-android.h"
+#include "backends/networking/http/android/networkreadstream-android.h"
+#include "common/debug.h"
+
+namespace Common {
+
+template<>
+Networking::ConnectionManager *Singleton<Networking::ConnectionManager>::makeInstance() {
+ return new Networking::ConnectionManagerAndroid();
+}
+
+} // namespace Common
+
+namespace Networking {
+
+ConnectionManagerAndroid::ConnectionManagerAndroid() : ConnectionManager(), _manager(0) {
+ JNIEnv *env = JNI::getEnv();
+
+ NetJNI::init(env);
+
+ // As we are called once, don't bother storing a global reference in JNI init
+ jclass cls = env->FindClass("org/scummvm/scummvm/net/HTTPManager");
+ jobject obj = env->NewObject(cls, NetJNI::_MID_manager_init);
+ if (env->ExceptionCheck()) {
+ LOGE("HTTPManager::<init> failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ abort();
+ }
+ _manager = env->NewGlobalRef(obj);
+ env->DeleteLocalRef(obj);
+}
+
+ConnectionManagerAndroid::~ConnectionManagerAndroid() {
+ JNIEnv *env = JNI::getEnv();
+ env->DeleteGlobalRef(_manager);
+}
+
+void ConnectionManagerAndroid::registerRequest(JNIEnv *env, jobject request) const {
+ env->CallVoidMethod(_manager, NetJNI::_MID_manager_startRequest, request);
+ if (env->ExceptionCheck()) {
+ LOGE("HTTPManager::startRequest failed");
+
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+}
+
+// private goes here:
+void ConnectionManagerAndroid::processTransfers() {
+ JNIEnv *env = JNI::getEnv();
+
+ // Don't call Java is there is no need
+ // This is not perfect as the worker threads can update it and we are not
+ // synchronized but that should do the job
+ if (env->GetBooleanField(_manager, NetJNI::_FID_manager__empty)) {
+ return;
+ }
+
+ env->CallVoidMethod(_manager, NetJNI::_MID_manager_poll);
+ if (env->ExceptionCheck()) {
+ LOGE("HTTPManager::poll failed");
+
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/http/android/connectionmanager-android.h b/backends/networking/http/android/connectionmanager-android.h
new file mode 100644
index 00000000000..dba25b0aff5
--- /dev/null
+++ b/backends/networking/http/android/connectionmanager-android.h
@@ -0,0 +1,46 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BACKENDS_NETWORKING_HTTP_ANDROID_CONNECTIONMANAGER_ANDROID_H
+#define BACKENDS_NETWORKING_HTTP_ANDROID_CONNECTIONMANAGER_ANDROID_H
+
+#include <jni.h>
+
+#include "backends/networking/http/connectionmanager.h"
+
+namespace Networking {
+
+class ConnectionManagerAndroid : public ConnectionManager {
+private:
+ jobject _manager;
+
+ void processTransfers() override;
+
+public:
+ ConnectionManagerAndroid();
+ ~ConnectionManagerAndroid() override;
+
+ void registerRequest(JNIEnv *env, jobject request) const;
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/http/android/networkreadstream-android.cpp b/backends/networking/http/android/networkreadstream-android.cpp
new file mode 100644
index 00000000000..dec0e6549d1
--- /dev/null
+++ b/backends/networking/http/android/networkreadstream-android.cpp
@@ -0,0 +1,408 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Allow use of stuff in <time.h> and abort()
+#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
+#define FORBIDDEN_SYMBOL_EXCEPTION_abort
+
+// Disable printf override in common/forbidden.h to avoid
+// clashes with log.h from the Android SDK.
+// That header file uses
+// __attribute__ ((format(printf, 3, 4)))
+// which gets messed up by our override mechanism; this could
+// be avoided by either changing the Android SDK to use the equally
+// legal and valid
+// __attribute__ ((format(__printf__, 3, 4)))
+// or by refining our printf override to use a varadic macro
+// (which then wouldn't be portable, though).
+// Anyway, for now we just disable the printf override globally
+// for the Android port
+#define FORBIDDEN_SYMBOL_EXCEPTION_printf
+
+#include "backends/platform/android/android.h"
+#include "backends/platform/android/jni-android.h"
+
+#include "backends/networking/basic/android/jni.h"
+
+#include "backends/networking/http/android/networkreadstream-android.h"
+#include "backends/networking/http/android/connectionmanager-android.h"
+#include "common/debug.h"
+
+namespace Networking {
+
+NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
+ return new NetworkReadStreamAndroid(url, headersList, postFields, uploading, usingPatch, keepAlive, keepAliveIdle, keepAliveInterval);
+}
+
+NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
+ return new NetworkReadStreamAndroid(url, headersList, formFields, formFiles, keepAlive, keepAliveIdle, keepAliveInterval);
+}
+
+NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
+ return new NetworkReadStreamAndroid(url, headersList, buffer, bufferSize, uploading, usingPatch, post, keepAlive, keepAliveIdle, keepAliveInterval);
+}
+
+void NetworkReadStreamAndroid::gotHeaders(JNIEnv *env, jobject obj, jlong nativePointer, jobjectArray headers) {
+ NetworkReadStreamAndroid *stream = (NetworkReadStreamAndroid *)nativePointer;
+ if (!stream) {
+ return;
+ }
+
+ jsize size = env->GetArrayLength(headers);
+ assert((size & 1) == 0);
+
+ stream->_responseHeadersMap.clear();
+ for (jsize i = 0; i < size; i += 2) {
+ jstring key_obj = (jstring)env->GetObjectArrayElement(headers, i);
+ jstring value_obj = (jstring)env->GetObjectArrayElement(headers, i + 1);
+ const char *key = env->GetStringUTFChars(key_obj, 0);
+ const char *value = env->GetStringUTFChars(value_obj, 0);
+
+ if (key != nullptr && value != nullptr) {
+ stream->_responseHeadersMap[key] = value;
+ }
+ env->ReleaseStringUTFChars(key_obj, key);
+ env->ReleaseStringUTFChars(value_obj, value);
+
+ env->DeleteLocalRef(key_obj);
+ env->DeleteLocalRef(value_obj);
+ }
+}
+
+void NetworkReadStreamAndroid::gotData(JNIEnv *env, jobject obj, jlong nativePointer, jbyteArray data, jint size, jint totalSize) {
+ NetworkReadStreamAndroid *stream = (NetworkReadStreamAndroid *)nativePointer;
+ if (!stream) {
+ return;
+ }
+
+ jsize arrSize = env->GetArrayLength(data);
+ assert(size >= 0 && (jsize)size <= arrSize);
+
+ if (size > 0) {
+ jbyte *dataP = (jbyte *)env->GetPrimitiveArrayCritical(data, 0);
+ assert(dataP);
+
+ stream->_backingStream.write(dataP, size);
+ stream->_downloaded += size;
+
+ env->ReleasePrimitiveArrayCritical(data, dataP, JNI_ABORT);
+ }
+
+ stream->setProgress(stream->_downloaded, totalSize);
+}
+
+void NetworkReadStreamAndroid::finished_(JNIEnv *env, jobject obj, jlong nativePointer, jint errorCode, jstring errorMsg) {
+ NetworkReadStreamAndroid *stream = (NetworkReadStreamAndroid *)nativePointer;
+ if (!stream) {
+ return;
+ }
+
+ Common::String errorMsgStr;
+ if (errorMsg) {
+ const char *errorMsgP = env->GetStringUTFChars(errorMsg, 0);
+ if (errorMsgP != 0) {
+ errorMsgStr = Common::String(errorMsgP);
+ env->ReleaseStringUTFChars(errorMsg, errorMsgP);
+ }
+ }
+
+ stream->finished(errorCode, errorMsgStr);
+}
+
+static jobjectArray getHeaders(JNIEnv *env, RequestHeaders *headersList) {
+ if (!headersList) {
+ return nullptr;
+ }
+
+ jclass stringClass = env->FindClass("java/lang/String");
+ jobjectArray array = env->NewObjectArray(headersList->size() * 2, stringClass, nullptr);
+ env->DeleteLocalRef(stringClass);
+
+ int i = 0;
+ for (const Common::String &header : *headersList) {
+ // Find the colon separator
+ uint colonPos = header.findFirstOf(':');
+ if (colonPos == Common::String::npos) {
+ warning("NetworkReadStreamAndroid: Malformed header (no colon): %s", header.c_str());
+ continue;
+ }
+
+ // Split into key and value parts
+ Common::String key = header.substr(0, colonPos);
+ Common::String value = header.substr(colonPos + 1);
+
+ // Trim whitespace from key and value
+ key.trim();
+ value.trim();
+
+ jobject key_obj = env->NewStringUTF(key.c_str());
+ jobject value_obj = env->NewStringUTF(value.c_str());
+
+ // Store key and value as separate strings
+ env->SetObjectArrayElement(array, i, key_obj);
+ env->SetObjectArrayElement(array, i + 1, value_obj);
+
+ env->DeleteLocalRef(key_obj);
+ env->DeleteLocalRef(value_obj);
+
+ i += 2;
+ }
+
+ return array;
+}
+
+static jobjectArray getFormFields(JNIEnv *env, const Common::HashMap<Common::String, Common::String> &map) {
+ jclass stringClass = env->FindClass("java/lang/String");
+ jobjectArray array = env->NewObjectArray(map.size() * 2, stringClass, nullptr);
+ env->DeleteLocalRef(stringClass);
+
+ int i = 0;
+ for (const Common::HashMap<Common::String, Common::String>::Node &entry : map) {
+ jobject key_obj = env->NewStringUTF(entry._key.c_str());
+ jobject value_obj = env->NewStringUTF(entry._value.c_str());
+
+ // Store key and value as separate strings
+ env->SetObjectArrayElement(array, i, key_obj);
+ env->SetObjectArrayElement(array, i + 1, value_obj);
+
+ env->DeleteLocalRef(key_obj);
+ env->DeleteLocalRef(value_obj);
+ }
+
+ return array;
+}
+
+static jobjectArray getFormFiles(JNIEnv *env, const Common::HashMap<Common::String, Common::Path> &map) {
+ jclass stringClass = env->FindClass("java/lang/String");
+ jobjectArray array = env->NewObjectArray(map.size() * 2, stringClass, nullptr);
+ env->DeleteLocalRef(stringClass);
+
+ int i = 0;
+ for (const Common::HashMap<Common::String, Common::Path>::Node &entry : map) {
+ jobject key_obj = env->NewStringUTF(entry._key.c_str());
+ jobject value_obj = env->NewStringUTF(entry._value.toString('/').c_str());
+
+ // Store key and value as separate strings
+ env->SetObjectArrayElement(array, i, key_obj);
+ env->SetObjectArrayElement(array, i + 1, value_obj);
+
+ env->DeleteLocalRef(key_obj);
+ env->DeleteLocalRef(value_obj);
+ }
+
+ return array;
+}
+
+NetworkReadStreamAndroid::NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::String &postFields,
+ bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
+ NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval), _request(nullptr) {
+ if (!reuse(url, headersList, postFields, uploading, usingPatch)) {
+ abort();
+ }
+}
+
+NetworkReadStreamAndroid::NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields,
+ const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
+ NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval), _request(nullptr) {
+ if (!reuse(url, headersList, formFields, formFiles)) {
+ abort();
+ }
+}
+
+NetworkReadStreamAndroid::NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize,
+ bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
+ NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval), _request(nullptr) {
+ if (!reuse(url, headersList, buffer, bufferSize, uploading, usingPatch, post)) {
+ abort();
+ }
+}
+
+bool NetworkReadStreamAndroid::reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch) {
+ JNIEnv *env = JNI::getEnv();
+
+ resetStream(env);
+
+ jstring url_obj = env->NewStringUTF(url);
+ jobjectArray headers_obj = getHeaders(env, headersList);
+ jbyteArray uploadBuffer_obj = env->NewByteArray(postFields.size());
+ env->SetByteArrayRegion(uploadBuffer_obj, 0, postFields.size(), (const jbyte *)postFields.c_str());
+
+ jobject obj = env->NewObject(NetJNI::_CLS_HTTPRequest, NetJNI::_MID_request_bufinit, (jlong)this, url_obj, headers_obj, uploadBuffer_obj, uploading, usingPatch, false);
+
+ env->DeleteLocalRef(uploadBuffer_obj);
+ env->DeleteLocalRef(headers_obj);
+ env->DeleteLocalRef(url_obj);
+
+ if (env->ExceptionCheck()) {
+ LOGE("HTTPRequest::<init> failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ return false;
+ }
+ _request = env->NewGlobalRef(obj);
+ env->DeleteLocalRef(obj);
+
+ dynamic_cast<ConnectionManagerAndroid &>(ConnMan).registerRequest(env, _request);
+ return true;
+}
+
+bool NetworkReadStreamAndroid::reuse(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) {
+ JNIEnv *env = JNI::getEnv();
+
+ resetStream(env);
+
+ jstring url_obj = env->NewStringUTF(url);
+ jobjectArray headers_obj = getHeaders(env, headersList);
+ jobjectArray formFields_obj = getFormFields(env, formFields);
+ jobjectArray formFiles_obj = getFormFiles(env, formFiles);
+
+ jobject obj = env->NewObject(NetJNI::_CLS_HTTPRequest, NetJNI::_MID_request_forminit, (jlong)this, url_obj, headers_obj, formFields_obj, formFiles_obj);
+
+ env->DeleteLocalRef(formFiles_obj);
+ env->DeleteLocalRef(formFields_obj);
+ env->DeleteLocalRef(headers_obj);
+ env->DeleteLocalRef(url_obj);
+
+ if (env->ExceptionCheck()) {
+ LOGE("HTTPRequest::<init> failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return false;
+ }
+ _request = env->NewGlobalRef(obj);
+ env->DeleteLocalRef(obj);
+
+ dynamic_cast<ConnectionManagerAndroid &>(ConnMan).registerRequest(env, _request);
+ return true;
+}
+
+bool NetworkReadStreamAndroid::reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
+ JNIEnv *env = JNI::getEnv();
+
+ resetStream(env);
+
+ jstring url_obj = env->NewStringUTF(url);
+ jobjectArray headers_obj = getHeaders(env, headersList);
+ jbyteArray uploadBuffer_obj = env->NewByteArray(bufferSize);
+ env->SetByteArrayRegion(uploadBuffer_obj, 0, bufferSize, (const jbyte *)buffer);
+
+ jobject obj = env->NewObject(NetJNI::_CLS_HTTPRequest, NetJNI::_MID_request_bufinit, (jlong)this, url_obj, headers_obj, uploadBuffer_obj, uploading, usingPatch, false);
+
+ env->DeleteLocalRef(uploadBuffer_obj);
+ env->DeleteLocalRef(headers_obj);
+ env->DeleteLocalRef(url_obj);
+
+ if (env->ExceptionCheck()) {
+ LOGE("HTTPRequest::<init> failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return false;
+ }
+ _request = env->NewGlobalRef(obj);
+ env->DeleteLocalRef(obj);
+
+ dynamic_cast<ConnectionManagerAndroid &>(ConnMan).registerRequest(env, _request);
+ return true;
+}
+
+NetworkReadStreamAndroid::~NetworkReadStreamAndroid() {
+ JNIEnv *env = JNI::getEnv();
+
+ env->CallVoidMethod(_request, NetJNI::_MID_request_cancel);
+ if (env->ExceptionCheck()) {
+ LOGE("HTTPRequest::cancel failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ env->DeleteGlobalRef(_request);
+}
+
+void NetworkReadStreamAndroid::finished(int errorCode, const Common::String &errorMsg) {
+ _requestComplete = true;
+ _errorCode = errorCode;
+ _errorMsg = errorMsg;
+
+ if (_errorCode >= 200 && _errorCode < 300) {
+ debug(9, "NetworkReadStreamAndroid: %s - Request succeeded", currentLocation().c_str());
+ } else {
+ warning("NetworkReadStreamAndroid: %s - Request failed (%d - %s)", currentLocation().c_str(), _errorCode, _errorMsg.c_str());
+ }
+}
+
+void NetworkReadStreamAndroid::resetStream(JNIEnv *env) {
+ _eos = _requestComplete = false;
+ _progressDownloaded = _progressTotal = 0;
+
+ if (_request) {
+ env->CallVoidMethod(_request, NetJNI::_MID_request_cancel);
+ if (env->ExceptionCheck()) {
+ LOGE("HTTPRequest::cancel failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ env->DeleteGlobalRef(_request);
+ _request = nullptr;
+ }
+
+ _responseHeadersMap.clear();
+ _downloaded = 0;
+ _errorCode = 0;
+ _errorMsg.clear();
+}
+
+Common::String NetworkReadStreamAndroid::currentLocation() const {
+ if (!_request) {
+ return Common::String();
+ }
+
+ JNIEnv *env = JNI::getEnv();
+
+ jstring location_obj = (jstring)env->CallObjectMethod(_request, NetJNI::_MID_request_getURL);
+ if (env->ExceptionCheck()) {
+ LOGE("HTTPRequest::getURL failed");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ return Common::String();
+ }
+
+ uint length = env->GetStringLength(location_obj);
+ if (!length) {
+ env->DeleteLocalRef(location_obj);
+ return Common::String();
+ }
+
+ const char *location_ptr = env->GetStringUTFChars(location_obj, 0);
+ if (!location_ptr) {
+ env->DeleteLocalRef(location_obj);
+ return Common::String();
+ }
+
+ Common::String result(location_ptr, length);
+
+ env->ReleaseStringUTFChars(location_obj, location_ptr);
+ env->DeleteLocalRef(location_obj);
+
+ return result;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/http/android/networkreadstream-android.h b/backends/networking/http/android/networkreadstream-android.h
new file mode 100644
index 00000000000..9b8db3f2f61
--- /dev/null
+++ b/backends/networking/http/android/networkreadstream-android.h
@@ -0,0 +1,82 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BACKENDS_NETWORKING_HTTP_ANDROID_NETWORKREADSTREAM_ANDROID_H
+#define BACKENDS_NETWORKING_HTTP_ANDROID_NETWORKREADSTREAM_ANDROID_H
+
+#include <jni.h>
+
+#include "backends/networking/http/networkreadstream.h"
+
+namespace Networking {
+
+class NetworkReadStreamAndroid : public NetworkReadStream {
+ friend class NetJNI;
+private:
+ static void gotHeaders(JNIEnv *env, jobject obj, jlong nativePointer, jobjectArray headers);
+ static void gotData(JNIEnv *env, jobject obj, jlong nativePointer, jbyteArray data, jint size, jint totalSize);
+ static void finished_(JNIEnv *env, jobject obj, jlong nativePointer, jint errorCode, jstring errorMsg);
+
+ void resetStream(JNIEnv *env);
+ void finished(int errorCode, const Common::String &errorMsg);
+
+ jobject _request;
+
+ Common::HashMap<Common::String, Common::String> _responseHeadersMap;
+ uint64 _downloaded;
+ int _errorCode;
+ Common::String _errorMsg;
+public:
+ NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
+
+ NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
+
+ NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
+
+ ~NetworkReadStreamAndroid() override;
+
+ /** Send <postFields>, using POST by default. */
+ bool reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading = false, bool usingPatch = false) override;
+ /** Send <formFields>, <formFiles>, using POST multipart/form. */
+ bool reuse(
+ const char *url, RequestHeaders *headersList,
+ const Common::HashMap<Common::String, Common::String> &formFields,
+ const Common::HashMap<Common::String, Common::Path> &formFiles) override;
+ /** Send <buffer>, using POST by default. */
+ bool reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true) override;
+
+ long httpResponseCode() const override { return _errorCode; }
+ Common::String currentLocation() const override;
+ /**
+ * Return response headers as HashMap. All header names in
+ * it are lowercase.
+ *
+ * @note This method should be called when eos() == true.
+ */
+ Common::HashMap<Common::String, Common::String> responseHeadersMap() const override { return _responseHeadersMap; }
+
+ bool hasError() const override { return _errorCode < 200 || _errorCode >= 300; }
+ const char *getError() const override { return _errorMsg.c_str(); }
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/platform/android/android.mk b/backends/platform/android/android.mk
index af17cb1007d..94353176c9c 100644
--- a/backends/platform/android/android.mk
+++ b/backends/platform/android/android.mk
@@ -60,26 +60,6 @@ endif
$(INSTALL) -c -m 644 $(DIST_FILES_DOCS) $(PATH_BUILD_ASSETS)/doc/
(cd $(PATH_BUILD_ASSETS)/ && find assets doc -type f | sort | xargs md5sum) > $@
-ifdef DIST_ANDROID_CACERT_PEM
-$(PATH_BUILD_ASSETS)/assets/cacert.pem: $(DIST_ANDROID_CACERT_PEM)
- $(INSTALL) -d $(PATH_BUILD_ASSETS)/assets/
- $(INSTALL) -c -m 644 $(DIST_ANDROID_CACERT_PEM) $(PATH_BUILD_ASSETS)/assets/cacert.pem
-$(PATH_BUILD_ASSETS)/MD5SUMS: $(PATH_BUILD_ASSETS)/assets/cacert.pem
-else
-ifdef USE_CURL
-$(PATH_BUILD_ASSETS)/assets/cacert.pem:
- $(INSTALL) -d $(PATH_BUILD_ASSETS)/assets/
- $(QUIET_CURL)$(CURL) -s https://curl.se/ca/cacert.pem --time-cond $(PATH_BUILD_ASSETS)/assets/cacert.pem --output $(PATH_BUILD_ASSETS)/assets/cacert.pem
-androidcacert:
- $(INSTALL) -d $(PATH_BUILD_ASSETS)/assets/
- $(QUIET_CURL)$(CURL) -s https://curl.se/ca/cacert.pem --time-cond $(PATH_BUILD_ASSETS)/assets/cacert.pem --output $(PATH_BUILD_ASSETS)/assets/cacert.pem
-
-.PHONY: androidcacert
-endif
-endif
-# Make MD5SUMS depend on cacert. If it's not here and neither DIST_ANDROID_CACERT_PEM nor USE_CURL are defined, it will error out
-$(PATH_BUILD_ASSETS)/MD5SUMS: $(PATH_BUILD_ASSETS)/assets/cacert.pem
-
$(PATH_BUILD_LIBSCUMMVM): libscummvm.so | $(PATH_BUILD)
$(INSTALL) -d $(PATH_BUILD_LIB)
$(INSTALL) -c -m 644 libscummvm.so $(PATH_BUILD_LIBSCUMMVM)
diff --git a/backends/platform/android/org/scummvm/scummvm/net/HTTPManager.java b/backends/platform/android/org/scummvm/scummvm/net/HTTPManager.java
new file mode 100644
index 00000000000..f7cd9cbb2ed
--- /dev/null
+++ b/backends/platform/android/org/scummvm/scummvm/net/HTTPManager.java
@@ -0,0 +1,57 @@
+package org.scummvm.scummvm.net;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class HTTPManager {
+ protected ExecutorService _executor;
+ protected ArrayBlockingQueue<Runnable> _queue;
+ protected boolean _empty;
+
+ /** @noinspection unused
+ * Called from JNI (main ScummVM thread)
+ */
+ public HTTPManager() {
+ _executor = Executors.newCachedThreadPool();
+ // Use a capacity to make sure the queue is checked on a regular basis
+ _queue = new ArrayBlockingQueue<>(50);
+ _empty = true;
+ }
+
+ /** @noinspection unused
+ * Called from JNI (main ScummVM thread)
+ */
+ public void startRequest(HTTPRequest request) {
+ request._manager = this;
+ _executor.execute(request);
+ }
+
+ /** @noinspection unused
+ * Called from JNI (main ScummVM thread)
+ */
+ public void poll() {
+ Runnable r;
+ while((r = _queue.poll()) != null) {
+ r.run();
+ }
+ // The read is never synchronized but at least we ensure here that we don't miss any event
+ synchronized(this) {
+ _empty = _queue.isEmpty();
+ }
+ }
+
+ // Called from workers
+ void enqueue(Runnable r) {
+ while(true) {
+ try {
+ _queue.put(r);
+ synchronized(this) {
+ _empty = false;
+ }
+ return;
+ } catch (InterruptedException ignored) {
+ }
+ }
+ }
+}
diff --git a/backends/platform/android/org/scummvm/scummvm/net/HTTPRequest.java b/backends/platform/android/org/scummvm/scummvm/net/HTTPRequest.java
new file mode 100644
index 00000000000..e8cb1e96fbe
--- /dev/null
+++ b/backends/platform/android/org/scummvm/scummvm/net/HTTPRequest.java
@@ -0,0 +1,360 @@
+package org.scummvm.scummvm.net;
+
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import org.scummvm.scummvm.SAFFSTree;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.SequenceInputStream;
+import java.math.BigInteger;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.security.SecureRandom;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.Vector;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class HTTPRequest implements Runnable {
+ final static String LOG_TAG = "ScummVM.HTTP";
+ private static final int DEFAULT_BUFFER_SIZE = 16384;
+
+ HTTPManager _manager;
+
+ protected String _method;
+ protected AtomicReference<String> _url;
+ protected TreeMap<String, String> _requestHeaders;
+ protected InputStream _uploadBuffer;
+ protected int _uploadBufferLength;
+
+ protected long _nativePointer;
+
+ protected AtomicBoolean _cancelled;
+
+ protected native void gotHeaders(long nativePointer, String[] headers);
+ protected native void gotData(long nativePointer, byte[] data, int size, int totalSize);
+ protected native void finished(long nativePointer, int errorCode, String errorMsg);
+
+ /** @noinspection unused
+ * Called from JNI
+ */
+ public HTTPRequest(long nativePointer, String url, String[] requestHeaders, byte[] uploadBuffer, boolean uploading, boolean usingPatch, boolean post) {
+ init(nativePointer, url);
+ setupUploadBuffer(uploadBuffer, uploading, usingPatch, post);
+ setupHeaders(requestHeaders);
+ }
+
+ /** @noinspection unused
+ * Called from JNI
+ */
+ public HTTPRequest(long nativePointer, String url, String[] requestHeaders, String[] formFields, String[] formFiles) {
+ init(nativePointer, url);
+ setupMultipartForm(formFields, formFiles);
+ setupHeaders(requestHeaders);
+ }
+
+ private void init(long nativePointer, String url) {
+ _nativePointer = nativePointer;
+ _url = new AtomicReference<>(url);
+ _requestHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ _cancelled = new AtomicBoolean(false);
+ _method = "GET";
+ }
+
+ private void setupUploadBuffer(byte[] uploadBuffer, boolean uploading, boolean usingPatch, boolean post) {
+ _uploadBuffer = null;
+ _uploadBufferLength = 0;
+ if (uploading) {
+ if (uploadBuffer == null) {
+ uploadBuffer = new byte[0];
+ }
+ _uploadBuffer = new ByteArrayInputStream(uploadBuffer);
+ _uploadBufferLength = uploadBuffer.length;
+ _method = "PUT";
+ return;
+ }
+
+ if (usingPatch) {
+ _method = "PATCH";
+ return;
+ }
+
+ if (uploadBuffer != null && uploadBuffer.length > 0) {
+ _uploadBuffer = new ByteArrayInputStream(uploadBuffer);
+ _uploadBufferLength = uploadBuffer.length;
+ post = true;
+ }
+
+ if (post) {
+ _method = "POST";
+ _requestHeaders.put("content-type", "application/x-www-form-urlencoded");
+ }
+ }
+
+ private void setupMultipartForm(String[] formFields, String[] formFiles) {
+ if ((formFields.length & 1) != 0) {
+ throw new IllegalArgumentException("formFields has odd length");
+ }
+ if ((formFiles.length & 1) != 0) {
+ throw new IllegalArgumentException("formFiles has odd length");
+ }
+
+ SecureRandom rnd = new SecureRandom();
+ String boundary = "ScummVM-Boundary-" + (new BigInteger(128, rnd)).toString(10);
+
+ int contentLength = 0;
+ Vector<InputStream> bodyParts = new Vector<>(formFiles.length * 2 + 2);
+
+ _method = "POST";
+ _requestHeaders.put("content-type", String.format("multipart/form-data; boundary=%s", boundary));
+
+ StringBuilder formFieldsContent = new StringBuilder();
+ for (int i = 0; i < formFields.length; i += 2) {
+ formFieldsContent.append(String.format("\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n", boundary, formFields[i]));
+ formFieldsContent.append(formFields[i+1]);
+ }
+ for (int i = 0; i < formFiles.length; i += 2) {
+ File file = new File(formFiles[i+1]);
+ formFieldsContent.append(String.format("\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n\r\n", boundary, formFiles[i], file.getName()));
+
+ byte[] textContent = formFieldsContent.toString().getBytes(Charset.defaultCharset());
+ bodyParts.add(new ByteArrayInputStream(textContent));
+ if (contentLength >= 0) {
+ try {
+ contentLength = Math.addExact(contentLength, textContent.length);
+ } catch (ArithmeticException e) {
+ contentLength = -1;
+ }
+ }
+ formFieldsContent = new StringBuilder();
+
+ try {
+ SAFFSTree.PathResult pr = SAFFSTree.fullPathToNode(null, file.getPath(), false);
+ if (pr == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ bodyParts.add(new FileInputStream(file));
+ long length = file.length();
+ if (!file.isFile() || length >= Integer.MAX_VALUE) {
+ contentLength = -1;
+ } else if (contentLength >= 0) {
+ try {
+ contentLength = Math.addExact(contentLength, (int)length);
+ } catch (ArithmeticException e) {
+ contentLength = -1;
+ }
+ }
+ } else {
+ ParcelFileDescriptor pfd = pr.tree.createFileDescriptor(pr.node, "r");
+ bodyParts.add(new ParcelFileDescriptor.AutoCloseInputStream(pfd));
+ contentLength = -1;
+ }
+ } catch (FileNotFoundException ignored) {
+ // We can't trigger an error now: we will make sure we call finished later with an error
+ bodyParts.add(null);
+ break;
+ }
+ }
+ // Now we only have to close the multipart with an ending boundary
+ formFieldsContent.append(String.format("\r\n--%s--\r\n", boundary));
+ byte[] textContent = formFieldsContent.toString().getBytes(Charset.defaultCharset());
+ bodyParts.add(new ByteArrayInputStream(textContent));
+ if (contentLength >= 0) {
+ try {
+ contentLength = Math.addExact(contentLength, textContent.length);
+ } catch (ArithmeticException e) {
+ contentLength = -1;
+ }
+ }
+ _uploadBuffer = new SequenceInputStream(bodyParts.elements());
+ _uploadBufferLength = contentLength;
+ }
+
+ private void setupHeaders(String[] requestHeaders) {
+ if ((requestHeaders.length & 1) != 0) {
+ throw new IllegalArgumentException("requestHeaders has odd length");
+ }
+ for(int i = 0; i < requestHeaders.length; i += 2) {
+ if (requestHeaders[i] == null) {
+ // If there were invalid headers passed in native code
+ // we end up with null entries at the end of the array
+ return;
+ }
+ _requestHeaders.put(requestHeaders[i], requestHeaders[i+1]);
+ }
+ }
+
+ /** @noinspection unused
+ * Called from JNI
+ */
+ public void cancel() {
+ _cancelled.set(true);
+ // Don't notify the native object if we got cancelled: it may have been reused
+ _nativePointer = 0;
+ }
+
+ public String getURL() {
+ return _url.get();
+ }
+
+ private void cleanup() {
+ if (_uploadBuffer != null) {
+ try {
+ _uploadBuffer.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ // Runs on HTTPManager thread pool
+ @Override
+ public void run() {
+ if (_cancelled.get()) {
+ cleanup();
+ return;
+ }
+
+ URL url;
+ HttpURLConnection urlConnection;
+ try {
+ url = new URL(_url.get());
+ Log.d(LOG_TAG, String.format("Will make HTTP request to %s with method %s", url, _method));
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setRequestMethod(_method);
+ } catch (IOException e) {
+ final String errorMsg = e.getMessage();
+ _manager.enqueue(() -> finished(_nativePointer, -1, errorMsg));
+ cleanup();
+ return;
+ }
+
+ if (_cancelled.get()) {
+ cleanup();
+ return;
+ }
+
+ urlConnection.setInstanceFollowRedirects(true);
+ for (Map.Entry<String, String> e : _requestHeaders.entrySet()) {
+ urlConnection.addRequestProperty(e.getKey(), e.getValue());
+ }
+ if (_uploadBuffer != null) {
+ urlConnection.setDoOutput(true);
+ if (_uploadBufferLength != -1) {
+ urlConnection.setFixedLengthStreamingMode(_uploadBufferLength);
+ }
+ }
+
+ try {
+ urlConnection.connect();
+ if (_cancelled.get()) {
+ urlConnection.disconnect();
+ cleanup();
+ return;
+ }
+
+ if (_uploadBuffer != null) {
+ try (OutputStream out = urlConnection.getOutputStream()) {
+ byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+ int read;
+ while ((read = _uploadBuffer.read(buffer, 0, DEFAULT_BUFFER_SIZE)) >= 0) {
+ out.write(buffer, 0, read);
+ }
+ } catch (NullPointerException e) {
+ // We failed to open some file when building the buffer
+ _manager.enqueue(() -> finished(_nativePointer, -1, "Can't open file"));
+ cleanup();
+ return;
+ }
+ }
+ if (_cancelled.get()) {
+ urlConnection.disconnect();
+ cleanup();
+ return;
+ }
+
+ Map<String, java.util.List<String>> headersField = urlConnection.getHeaderFields();
+ if (_cancelled.get()) {
+ urlConnection.disconnect();
+ cleanup();
+ return;
+ }
+
+ final Vector<String> headers = new Vector<>(headersField.size() * 2);
+ for (Map.Entry<String, java.util.List<String>> e : headersField.entrySet()) {
+ String key = e.getKey();
+ if (key == null) {
+ // The status line is placed in the map with a null key: ignore it
+ continue;
+ }
+ List<String> values = e.getValue();
+ headers.add(key.toLowerCase(Locale.ROOT));
+ headers.add(values.get(values.size() - 1));
+ }
+ _manager.enqueue(() -> gotHeaders(_nativePointer, headers.toArray(new String[0])));
+
+ int contentLength = urlConnection.getContentLength();
+
+ InputStream in = urlConnection.getInputStream();
+ if (_cancelled.get()) {
+ cleanup();
+ return;
+ }
+
+ boolean finished = false;
+ while(!finished) {
+ final byte[] inputData = new byte[DEFAULT_BUFFER_SIZE];
+ int offset = 0;
+ while(offset < DEFAULT_BUFFER_SIZE) {
+ final int size = in.read(inputData, offset, DEFAULT_BUFFER_SIZE - offset);
+ if (size == -1) {
+ finished = true;
+ break;
+ }
+ if (_cancelled.get()) {
+ cleanup();
+ return;
+ }
+ offset += size;
+ }
+
+ final int offset_ = offset;
+ _manager.enqueue(() -> gotData(_nativePointer, inputData, offset_, contentLength));
+ }
+ // Update URL field
+ url = urlConnection.getURL();
+ _url.set(url.toExternalForm());
+
+ final int responseCode = urlConnection.getResponseCode();
+ _manager.enqueue(() -> finished(_nativePointer, responseCode, null));
+ } catch (FileNotFoundException e) {
+ // The server returned an error, return the error code and no data
+ int responseCode = -1;
+ try {
+ responseCode = urlConnection.getResponseCode();
+ } catch (IOException ignored) {
+ }
+
+ final int responseCode_ = responseCode;
+ final String errorMsg = e.getMessage();
+ _manager.enqueue(() -> finished(_nativePointer, responseCode_, errorMsg));
+ cleanup();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Error when making HTTP request", e);
+ final String errorMsg = e.getMessage();
+ _manager.enqueue(() -> finished(_nativePointer, -1, errorMsg));
+ cleanup();
+ } finally {
+ urlConnection.disconnect();
+ }
+ }
+}
diff --git a/backends/platform/android/org/scummvm/scummvm/net/SSocket.java b/backends/platform/android/org/scummvm/scummvm/net/SSocket.java
new file mode 100644
index 00000000000..46fcc69a3d3
--- /dev/null
+++ b/backends/platform/android/org/scummvm/scummvm/net/SSocket.java
@@ -0,0 +1,267 @@
+package org.scummvm.scummvm.net;
+
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import java.util.List;
+import java.util.Locale;
+
+import javax.net.ssl.SSLSocketFactory;
+
+/** @noinspection unused*/
+public class SSocket {
+ final static String LOG_TAG = "ScummVM";
+
+ protected Socket _socket;
+
+ protected int _buffer = -2;
+
+ public SSocket(String url_) {
+ final URL url;
+ try {
+ url = new URL(url_);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+
+ String scheme = url.getProtocol().toLowerCase();
+ if (!scheme.equals("http") && !scheme.equals("https")) {
+ throw new RuntimeException("Unsupported protocol");
+ }
+
+ String host = url.getHost();
+ if (host == null) {
+ throw new RuntimeException("Missing host name");
+ }
+ if (host.contains(":")) {
+ host = String.format("[%s]", host);
+ }
+ int port = url.getPort();
+ if (port == -1) {
+ port = url.getDefaultPort();
+ }
+
+ Socket socket;
+ try {
+ socket = proxyConnect(url, host, port);
+
+ if (scheme.equals("https")) {
+ SSLSocketFactory ssf = (SSLSocketFactory)SSLSocketFactory.getDefault();
+ socket = ssf.createSocket(socket, host, port, true);
+ }
+
+ _socket = socket;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static Socket proxyConnect(URL url, String host, int port) throws IOException {
+ Socket ret;
+
+ final URI uri;
+ try {
+ uri = url.toURI();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ SocketAddress sa = new InetSocketAddress(host, port);
+
+ final ProxySelector proxySelector = ProxySelector.getDefault();
+ final List<Proxy> proxies = proxySelector.select(uri);
+
+ IOException lastExc = null;
+ for (Proxy proxy : proxies) {
+ final Proxy.Type proxyType = proxy.type();
+ try {
+ if (proxyType != Proxy.Type.HTTP) {
+ ret = new Socket(proxy);
+ ret.connect(sa);
+ return ret;
+ }
+
+ // HTTP proxy with Socket is not supported on Android
+ // Let's do it ourselves with a CONNECT method
+
+ // First, resolve the proxy address: it's not resolved in Proxy
+ InetSocketAddress proxyAddress = (InetSocketAddress)proxy.address();
+ InetAddress addr = proxyAddress.getAddress();
+ String proxyHost;
+ if (addr != null) {
+ proxyHost = addr.getHostName();
+ } else {
+ proxyHost = proxyAddress.getHostName();
+ }
+ int proxyPort = proxyAddress.getPort();
+ proxyAddress = new InetSocketAddress(proxyHost, proxyPort);
+
+ ret = new Socket();
+ ret.connect(proxyAddress);
+
+ proxyHTTPConnect(ret, host, port);
+ return ret;
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Got an exception while connecting", e);
+ if (proxy.address() != null) {
+ proxySelector.connectFailed(uri, proxy.address(), e);
+ }
+ lastExc = e;
+ }
+ }
+ if (lastExc == null) {
+ throw new RuntimeException("No proxy specified");
+ }
+ throw lastExc;
+ }
+
+ private static void proxyHTTPConnect(Socket socket, String host, int port) throws IOException {
+ String requestLine = String.format(Locale.ROOT, "CONNECT %s:%d HTTP/1.0\r\n\r\n", host, port);
+ socket.getOutputStream().write(requestLine.getBytes());
+ byte[] buffer = readLine(socket);
+
+ // HTTP/1.x SP 2xx SP
+ if (buffer.length < 13 ||
+ buffer[0] != 'H' ||
+ buffer[1] != 'T' ||
+ buffer[2] != 'T' ||
+ buffer[3] != 'P' ||
+ buffer[4] != '/' ||
+ buffer[5] != '1' ||
+ buffer[6] != '.' ||
+ (buffer[7] != '0' && buffer[7] != '1') ||
+ buffer[8] != ' ' ||
+ buffer[9] != '2' ||
+ !Character.isDigit(buffer[10]) ||
+ !Character.isDigit(buffer[11]) ||
+ buffer[12] != ' ') {
+ throw new IOException("Invalid proxy reply");
+ }
+
+ for (int i = 0; i < 64 && buffer.length > 0; i++) {
+ buffer = readLine(socket);
+ }
+ if (buffer.length > 0) {
+ throw new IOException("Invalid proxy reply: too much headers");
+ }
+ }
+
+ private static byte[] readLine(Socket socket) throws IOException {
+ InputStream is = socket.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int b;
+ while (true) {
+ b = is.read();
+ if (b == -1) {
+ return baos.toByteArray();
+ }
+ if (b == '\r') {
+ continue;
+ }
+ if (b == '\n') {
+ return baos.toByteArray();
+ }
+ baos.write(b);
+ }
+ }
+
+ public int ready() {
+ if (_buffer != -2) {
+ // We have at least one byte or an EOF
+ return 1;
+ }
+ try {
+ // Set receive timeout to something ridiculously low to mimic a non-blocking socket
+ _socket.setSoTimeout(1);
+ _buffer = _socket.getInputStream().read();
+ return 1;
+ } catch (SocketTimeoutException e) {
+ // Nothing was ready to consume
+ return 0;
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Got an exception while checking ready status", e);
+ // Make it like if there was something ready
+ return 1;
+ }
+ }
+
+ public int send(byte[] data) {
+ try {
+ // Setup unlimited read timeout to allow for SSL exchanges to work
+ _socket.setSoTimeout(0);
+ _socket.getOutputStream().write(data);
+ return data.length;
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Got an exception while sending socket data", e);
+ // This likely failed
+ return 0;
+ }
+ }
+
+ public int recv(byte[] data) {
+ if (data.length == 0) {
+ return 0;
+ }
+ if (_buffer == -1) {
+ _buffer = -2;
+ return -1;
+ }
+ int offset = 0;
+ if (_buffer != -2) {
+ data[0] = (byte)_buffer;
+ offset = 1;
+ _buffer = -2;
+ }
+ try {
+ int recvd = 0;
+ long end = System.currentTimeMillis() + 5000;
+ while (true) {
+ try {
+ // Allow for some timeout but not too much
+ _socket.setSoTimeout(500);
+ recvd = _socket.getInputStream().read(data, offset, data.length - offset);
+ break;
+ } catch (SocketTimeoutException e1) {
+ if (System.currentTimeMillis() >= end) {
+ break;
+ }
+ }
+ }
+ if (offset == 0) {
+ // Nothing was buffered
+ return recvd;
+ }
+ if (recvd == -1) {
+ // Buffer the EOF and return the previous buffered data;
+ _buffer = -1;
+ return offset;
+ }
+ return offset + recvd;
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Got an exception while receiving socket data", e);
+ return offset;
+ }
+ }
+
+ public void close() {
+ try {
+ _socket.close();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Got an exception while closing socket", e);
+ }
+ _socket = null;
+ }
+}
diff --git a/configure b/configure
index fc7604d0d94..b094dc041ef 100755
--- a/configure
+++ b/configure
@@ -3648,6 +3648,7 @@ if test -n "$_host"; then
_build_aspect=no
_seq_midi=no
_timidity=no
+ _libcurl=no # Android backend uses Java HTTP client
;;
arm-linux|arm*-linux-gnueabi|arm-*-linux)
;;
@@ -6002,6 +6003,10 @@ if test "$_backend" = "emscripten" && test "$_cloud" = "yes"; then
_basicnet=no
_http=yes
echo "Emscripten (HTTP only)"
+elif test "$_backend" = "android"; then
+ _basicnet=yes
+ _http=yes
+ echo "Android"
elif test "$_libcurl" = "yes"; then
_basicnet=yes
_http=yes
Commit: 423f194cfea0f3badd5cb60d2bd375eea8f10d7f
https://github.com/scummvm/scummvm/commit/423f194cfea0f3badd5cb60d2bd375eea8f10d7f
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2025-11-02T09:12:23+01:00
Commit Message:
ANDROID: Add support for older Android
Android before 4.4 doesn't enable TLS1.1 or 1.2 by default but support
it.
Older devices don't have Let's Encrypt new certificates so add them to
our own store: these are used by the ScummVm infrastructure.
Changed paths:
A backends/platform/android/org/scummvm/scummvm/net/LETrustManager.java
A backends/platform/android/org/scummvm/scummvm/net/TLSSocketFactory.java
backends/platform/android/org/scummvm/scummvm/net/HTTPManager.java
backends/platform/android/org/scummvm/scummvm/net/SSocket.java
diff --git a/backends/platform/android/org/scummvm/scummvm/net/HTTPManager.java b/backends/platform/android/org/scummvm/scummvm/net/HTTPManager.java
index f7cd9cbb2ed..b9987bd701d 100644
--- a/backends/platform/android/org/scummvm/scummvm/net/HTTPManager.java
+++ b/backends/platform/android/org/scummvm/scummvm/net/HTTPManager.java
@@ -13,6 +13,8 @@ public class HTTPManager {
* Called from JNI (main ScummVM thread)
*/
public HTTPManager() {
+ TLSSocketFactory.init();
+
_executor = Executors.newCachedThreadPool();
// Use a capacity to make sure the queue is checked on a regular basis
_queue = new ArrayBlockingQueue<>(50);
diff --git a/backends/platform/android/org/scummvm/scummvm/net/LETrustManager.java b/backends/platform/android/org/scummvm/scummvm/net/LETrustManager.java
new file mode 100644
index 00000000000..879b19b4e4c
--- /dev/null
+++ b/backends/platform/android/org/scummvm/scummvm/net/LETrustManager.java
@@ -0,0 +1,109 @@
+package org.scummvm.scummvm.net;
+
+import android.util.Base64;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Locale;
+
+/*
+ * Inspiration taken from http://blog.novoj.net/2016/02/29/how-to-make-apache-httpclient-trust-lets-encrypt-certificate-authority/
+ */
+
+class LETrustManager implements X509TrustManager {
+ private static final String[] derLECerts = {
+ /* ISRG Root X1 */ "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZLubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=",
+ /* ISRG Root X2 */ "MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn",
+ };
+
+ private static LETrustManager instance;
+
+ private final X509TrustManager _systemTrustManager;
+ private final X509TrustManager _leTrustManager;
+
+ static SSLContext getContext() throws NoSuchAlgorithmException, KeyManagementException {
+ try {
+ if (instance == null) {
+ instance = new LETrustManager();
+ }
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace(System.err);
+ return SSLContext.getDefault();
+ } catch (KeyStoreException e) {
+ e.printStackTrace(System.err);
+ return SSLContext.getDefault();
+ } catch (CertificateException e) {
+ e.printStackTrace(System.err);
+ return SSLContext.getDefault();
+ } catch (IOException e) {
+ e.printStackTrace(System.err);
+ return SSLContext.getDefault();
+ }
+
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, new TrustManager[]{instance}, null);
+ return sslContext;
+ }
+
+ public LETrustManager() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
+ final TrustManagerFactory mainTrustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ mainTrustFactory.init((KeyStore)null);
+ this._systemTrustManager = (X509TrustManager)mainTrustFactory.getTrustManagers()[0];
+
+ KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ ks.load(null);
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ int i = 1;
+ for (String derCert : derLECerts) {
+ ByteArrayInputStream is = new ByteArrayInputStream(Base64.decode(derCert, Base64.DEFAULT));
+ Certificate cert = cf.generateCertificate(is);
+ ks.setCertificateEntry(String.format(Locale.getDefault(), "%d", i), cert);
+ i++;
+ }
+
+ final TrustManagerFactory leTrustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ leTrustFactory.init(ks);
+ this._leTrustManager = (X509TrustManager)leTrustFactory.getTrustManagers()[0];
+ }
+
+ @Override
+ public void checkClientTrusted(final X509Certificate[] x509Certificates, final String authType) throws CertificateException {
+ // LE doesn't issue client certificates
+ _systemTrustManager.checkClientTrusted(x509Certificates, authType);
+ }
+
+ @Override
+ public void checkServerTrusted(final X509Certificate[] x509Certificates, final String authType) throws CertificateException {
+ try {
+ _systemTrustManager.checkServerTrusted(x509Certificates, authType);
+ } catch(CertificateException ignored) {
+ this._leTrustManager.checkServerTrusted(x509Certificates, authType);
+ }
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ X509Certificate[] systemAccepted = this._systemTrustManager.getAcceptedIssuers();
+ X509Certificate[] leAccepted = this._leTrustManager.getAcceptedIssuers();
+
+ X509Certificate[] allAccepted = Arrays.copyOf(systemAccepted, systemAccepted.length + leAccepted.length);
+ System.arraycopy(leAccepted, 0, allAccepted, systemAccepted.length, leAccepted.length);
+
+ return allAccepted;
+ }
+}
diff --git a/backends/platform/android/org/scummvm/scummvm/net/SSocket.java b/backends/platform/android/org/scummvm/scummvm/net/SSocket.java
index 46fcc69a3d3..fb2595dbb4b 100644
--- a/backends/platform/android/org/scummvm/scummvm/net/SSocket.java
+++ b/backends/platform/android/org/scummvm/scummvm/net/SSocket.java
@@ -60,7 +60,7 @@ public class SSocket {
socket = proxyConnect(url, host, port);
if (scheme.equals("https")) {
- SSLSocketFactory ssf = (SSLSocketFactory)SSLSocketFactory.getDefault();
+ SSLSocketFactory ssf = new TLSSocketFactory();
socket = ssf.createSocket(socket, host, port, true);
}
diff --git a/backends/platform/android/org/scummvm/scummvm/net/TLSSocketFactory.java b/backends/platform/android/org/scummvm/scummvm/net/TLSSocketFactory.java
new file mode 100644
index 00000000000..108aadc5854
--- /dev/null
+++ b/backends/platform/android/org/scummvm/scummvm/net/TLSSocketFactory.java
@@ -0,0 +1,122 @@
+package org.scummvm.scummvm.net;
+
+/*
+ * Customized from https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/
+ * Added TLS1.3 support and keep old protocols enabled for maximum compatibility
+ */
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * @author fkrauthan
+ */
+public class TLSSocketFactory extends SSLSocketFactory {
+
+ private final SSLSocketFactory _factory;
+ private static String[] _protocols;
+
+ private static boolean _init;
+
+ public static void init() {
+ if (_init) {
+ return;
+ }
+ try {
+ HttpsURLConnection.setDefaultSSLSocketFactory(new TLSSocketFactory());
+ } catch (RuntimeException ignored) {
+ }
+ _init = true;
+ }
+
+ public TLSSocketFactory() {
+ SSLContext context = null;
+ try {
+ context = LETrustManager.getContext();
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ } catch (KeyManagementException e) {
+ throw new RuntimeException(e);
+ }
+ _factory = context.getSocketFactory();
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return _factory.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return _factory.getSupportedCipherSuites();
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ return enableTLSOnSocket(_factory.createSocket());
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
+ return enableTLSOnSocket(_factory.createSocket(s, host, port, autoClose));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
+ return enableTLSOnSocket(_factory.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
+ return enableTLSOnSocket(_factory.createSocket(host, port, localHost, localPort));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ return enableTLSOnSocket(_factory.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+ return enableTLSOnSocket(_factory.createSocket(address, port, localAddress, localPort));
+ }
+
+ private Socket enableTLSOnSocket(Socket socket) {
+ if(socket instanceof SSLSocket) {
+ SSLSocket sslSocket = (SSLSocket)socket;
+
+ if (_protocols == null) {
+ String[] newProtocols = {"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"};
+
+ // Build the list of protocols to enable
+ Set<String> protocols = new HashSet<>(Arrays.asList(sslSocket.getEnabledProtocols()));
+ Set<String> supported = new HashSet<>(Arrays.asList(sslSocket.getSupportedProtocols()));
+ for (String protocol : newProtocols) {
+ if (protocols.contains(protocol)) {
+ continue;
+ }
+ if (!supported.contains(protocol)) {
+ continue;
+ }
+ protocols.add(protocol);
+ }
+ _protocols = protocols.toArray(new String[]{});
+ }
+
+ sslSocket.setEnabledProtocols(_protocols);
+ }
+ return socket;
+ }
+}
More information about the Scummvm-git-logs
mailing list