diff options
author | Ignacio Etcheverry <ignalfonsore@gmail.com> | 2020-03-18 17:40:04 +0100 |
---|---|---|
committer | Ignacio Etcheverry <ignalfonsore@gmail.com> | 2020-03-31 09:37:16 +0200 |
commit | 77dd06134504ce68cc79b879c4a28d76a2c975f8 (patch) | |
tree | a4551344d3914ce93cfc25590f3c7261a27d4ab5 /modules/mono/mono_gd/support | |
parent | fa08437694d09f0d5ee594cb4fbe1ff72b074742 (diff) |
Mono/C#: Add iOS support
Right now, games only work on devices when exported with FullAOT+Interpreter.
There are some issues left that need to addressed for FullAOT alone. Right now,
it's giving issues with the Godot.NativeCalls static constructor.
Diffstat (limited to 'modules/mono/mono_gd/support')
-rwxr-xr-x | modules/mono/mono_gd/support/android_support.cpp | 690 | ||||
-rwxr-xr-x | modules/mono/mono_gd/support/android_support.h | 55 | ||||
-rwxr-xr-x | modules/mono/mono_gd/support/ios_support.h | 51 | ||||
-rwxr-xr-x | modules/mono/mono_gd/support/ios_support.mm | 151 |
4 files changed, 947 insertions, 0 deletions
diff --git a/modules/mono/mono_gd/support/android_support.cpp b/modules/mono/mono_gd/support/android_support.cpp new file mode 100755 index 0000000000..222b50730d --- /dev/null +++ b/modules/mono/mono_gd/support/android_support.cpp @@ -0,0 +1,690 @@ +/*************************************************************************/ +/* android_support.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "android_support.h" + +#if defined(ANDROID_ENABLED) + +#include <dlfcn.h> // dlopen, dlsym +#include <mono/utils/mono-dl-fallback.h> +#include <sys/system_properties.h> +#include <cstddef> + +#if __ANDROID_API__ < 24 +#include "thirdparty/misc/ifaddrs-android.h" +#else +#include <ifaddrs.h> +#endif + +#include "core/os/os.h" +#include "core/ustring.h" +#include "platform/android/java_godot_wrapper.h" +#include "platform/android/os_android.h" +#include "platform/android/thread_jandroid.h" + +#include "../../utils/path_utils.h" +#include "../../utils/string_utils.h" +#include "../gd_mono_cache.h" +#include "../gd_mono_marshal.h" + +// Warning: JNI boilerplate ahead... continue at your own risk + +namespace gdmono { +namespace android { +namespace support { + +template <typename T> +struct ScopedLocalRef { + JNIEnv *env; + T local_ref; + + _FORCE_INLINE_ T get() const { return local_ref; } + _FORCE_INLINE_ operator T() const { return local_ref; } + _FORCE_INLINE_ operator jvalue() const { return (jvalue)local_ref; } + + _FORCE_INLINE_ operator bool() const { return local_ref != NULL; } + + _FORCE_INLINE_ bool operator==(std::nullptr_t) const { + return local_ref == nullptr; + } + + _FORCE_INLINE_ bool operator!=(std::nullptr_t) const { + return local_ref != nullptr; + } + + ScopedLocalRef(const ScopedLocalRef &) = delete; + ScopedLocalRef &operator=(const ScopedLocalRef &) = delete; + + ScopedLocalRef(JNIEnv *p_env, T p_local_ref) : + env(p_env), + local_ref(p_local_ref) { + } + + ~ScopedLocalRef() { + if (local_ref) { + env->DeleteLocalRef(local_ref); + } + } +}; + +bool jni_exception_check(JNIEnv *p_env) { + if (p_env->ExceptionCheck()) { + // Print the exception to logcat + p_env->ExceptionDescribe(); + + p_env->ExceptionClear(); + return true; + } + + return false; +} + +String app_native_lib_dir_cache; + +String determine_app_native_lib_dir() { + JNIEnv *env = ThreadAndroid::get_env(); + + ScopedLocalRef<jclass> activityThreadClass(env, env->FindClass("android/app/ActivityThread")); + jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;"); + ScopedLocalRef<jobject> activityThread(env, env->CallStaticObjectMethod(activityThreadClass, currentActivityThread)); + jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;"); + ScopedLocalRef<jobject> ctx(env, env->CallObjectMethod(activityThread, getApplication)); + + jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); + ScopedLocalRef<jobject> applicationInfo(env, env->CallObjectMethod(ctx, getApplicationInfo)); + jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;"); + ScopedLocalRef<jstring> nativeLibraryDir(env, (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField)); + + String result; + + const char *const nativeLibraryDirUtf8 = env->GetStringUTFChars(nativeLibraryDir, NULL); + if (nativeLibraryDirUtf8) { + result.parse_utf8(nativeLibraryDirUtf8); + env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDirUtf8); + } + + return result; +} + +String get_app_native_lib_dir() { + if (app_native_lib_dir_cache.empty()) + app_native_lib_dir_cache = determine_app_native_lib_dir(); + return app_native_lib_dir_cache; +} + +int gd_mono_convert_dl_flags(int flags) { + // from mono's runtime-bootstrap.c + + int lflags = flags & MONO_DL_LOCAL ? 0 : RTLD_GLOBAL; + + if (flags & MONO_DL_LAZY) + lflags |= RTLD_LAZY; + else + lflags |= RTLD_NOW; + + return lflags; +} + +#ifndef GD_MONO_SO_NAME +#define GD_MONO_SO_NAME "libmonosgen-2.0.so" +#endif + +const char *mono_so_name = GD_MONO_SO_NAME; +const char *godot_so_name = "libgodot_android.so"; + +void *mono_dl_handle = NULL; +void *godot_dl_handle = NULL; + +void *try_dlopen(const String &p_so_path, int p_flags) { + if (!FileAccess::exists(p_so_path)) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Cannot find shared library: '%s'\n", p_so_path.utf8().get_data()); + return NULL; + } + + int lflags = gd_mono_convert_dl_flags(p_flags); + + void *handle = dlopen(p_so_path.utf8().get_data(), lflags); + + if (!handle) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Failed to open shared library: '%s'. Error: '%s'\n", p_so_path.utf8().get_data(), dlerror()); + return NULL; + } + + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Successfully loaded shared library: '%s'\n", p_so_path.utf8().get_data()); + + return handle; +} + +void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void *p_user_data) { + if (p_name == NULL) { + // __Internal + + if (!mono_dl_handle) { + String app_native_lib_dir = get_app_native_lib_dir(); + String so_path = path::join(app_native_lib_dir, mono_so_name); + + mono_dl_handle = try_dlopen(so_path, p_flags); + } + + return mono_dl_handle; + } + + String name = String::utf8(p_name); + + if (name.ends_with(".dll.so") || name.ends_with(".exe.so")) { + String app_native_lib_dir = get_app_native_lib_dir(); + + String orig_so_name = name.get_file(); + String so_name = "lib-aot-" + orig_so_name; + String so_path = path::join(app_native_lib_dir, so_name); + + return try_dlopen(so_path, p_flags); + } + + return NULL; +} + +void *gd_mono_android_dlsym(void *p_handle, const char *p_name, char **r_err, void *p_user_data) { + void *sym_addr = dlsym(p_handle, p_name); + + if (sym_addr) + return sym_addr; + + if (p_handle == mono_dl_handle && godot_dl_handle) { + // Looking up for '__Internal' P/Invoke. We want to search in both the Mono and Godot shared libraries. + // This is needed to resolve the monodroid P/Invoke functions that are defined at the bottom of the file. + sym_addr = dlsym(godot_dl_handle, p_name); + + if (sym_addr) + return sym_addr; + } + + if (r_err) + *r_err = str_format_new("%s\n", dlerror()); + + return NULL; +} + +void *gd_mono_android_dlclose(void *p_handle, void *p_user_data) { + dlclose(p_handle); + + // Not sure if this ever happens. Does Mono close the handle for the main module? + if (p_handle == mono_dl_handle) + mono_dl_handle = NULL; + + return NULL; +} + +int32_t build_version_sdk_int = 0; + +int32_t get_build_version_sdk_int() { + // The JNI code is the equivalent of: + // + // android.os.Build.VERSION.SDK_INT + + if (build_version_sdk_int == 0) { + JNIEnv *env = ThreadAndroid::get_env(); + + jclass versionClass = env->FindClass("android/os/Build$VERSION"); + ERR_FAIL_NULL_V(versionClass, 0); + + jfieldID sdkIntField = env->GetStaticFieldID(versionClass, "SDK_INT", "I"); + ERR_FAIL_NULL_V(sdkIntField, 0); + + build_version_sdk_int = (int32_t)env->GetStaticIntField(versionClass, sdkIntField); + } + + return build_version_sdk_int; +} + +jobject certStore = NULL; // KeyStore + +MonoBoolean _gd_mono_init_cert_store() { + // The JNI code is the equivalent of: + // + // try { + // certStoreLocal = KeyStore.getInstance("AndroidCAStore"); + // certStoreLocal.load(null); + // certStore = certStoreLocal; + // return true; + // } catch (Exception e) { + // return false; + // } + + JNIEnv *env = ThreadAndroid::get_env(); + + ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore")); + + jmethodID getInstance = env->GetStaticMethodID(keyStoreClass, "getInstance", "(Ljava/lang/String;)Ljava/security/KeyStore;"); + jmethodID load = env->GetMethodID(keyStoreClass, "load", "(Ljava/security/KeyStore$LoadStoreParameter;)V"); + + ScopedLocalRef<jstring> androidCAStoreString(env, env->NewStringUTF("AndroidCAStore")); + + ScopedLocalRef<jobject> certStoreLocal(env, env->CallStaticObjectMethod(keyStoreClass, getInstance, androidCAStoreString.get())); + + if (jni_exception_check(env)) + return 0; + + env->CallVoidMethod(certStoreLocal, load, NULL); + + if (jni_exception_check(env)) + return 0; + + certStore = env->NewGlobalRef(certStoreLocal); + + return 1; +} + +MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) { + // The JNI code is the equivalent of: + // + // Certificate certificate = certStore.getCertificate(alias); + // if (certificate == null) + // return null; + // return certificate.getEncoded(); + + MonoError mono_error; + char *alias_utf8 = mono_string_to_utf8_checked(p_alias, &mono_error); + + if (!mono_error_ok(&mono_error)) { + ERR_PRINT(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&mono_error) + "'."); + mono_error_cleanup(&mono_error); + return NULL; + } + + JNIEnv *env = ThreadAndroid::get_env(); + + ScopedLocalRef<jstring> js_alias(env, env->NewStringUTF(alias_utf8)); + mono_free(alias_utf8); + + ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore")); + ERR_FAIL_NULL_V(keyStoreClass, NULL); + ScopedLocalRef<jclass> certificateClass(env, env->FindClass("java/security/cert/Certificate")); + ERR_FAIL_NULL_V(certificateClass, NULL); + + jmethodID getCertificate = env->GetMethodID(keyStoreClass, "getCertificate", "(Ljava/lang/String;)Ljava/security/cert/Certificate;"); + ERR_FAIL_NULL_V(getCertificate, NULL); + + jmethodID getEncoded = env->GetMethodID(certificateClass, "getEncoded", "()[B"); + ERR_FAIL_NULL_V(getEncoded, NULL); + + ScopedLocalRef<jobject> certificate(env, env->CallObjectMethod(certStore, getCertificate, js_alias.get())); + + if (!certificate) + return NULL; + + ScopedLocalRef<jbyteArray> encoded(env, (jbyteArray)env->CallObjectMethod(certificate, getEncoded)); + jsize encodedLength = env->GetArrayLength(encoded); + + MonoArray *encoded_ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), encodedLength); + uint8_t *dest = (uint8_t *)mono_array_addr(encoded_ret, uint8_t, 0); + + env->GetByteArrayRegion(encoded, 0, encodedLength, reinterpret_cast<jbyte *>(dest)); + + return encoded_ret; +} + +void register_internal_calls() { + mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", (void *)_gd_mono_init_cert_store); + mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", (void *)_gd_mono_android_cert_store_lookup); +} + +void initialize() { + // We need to set this environment variable to make the monodroid BCL use btls instead of legacy as the default provider + OS::get_singleton()->set_environment("XA_TLS_PROVIDER", "btls"); + + mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, gd_mono_android_dlclose, NULL); + + String app_native_lib_dir = get_app_native_lib_dir(); + String so_path = path::join(app_native_lib_dir, godot_so_name); + + godot_dl_handle = try_dlopen(so_path, gd_mono_convert_dl_flags(MONO_DL_LAZY)); +} + +void cleanup() { + // This is called after shutting down the Mono runtime + + if (mono_dl_handle) + gd_mono_android_dlclose(mono_dl_handle, NULL); + + if (godot_dl_handle) + gd_mono_android_dlclose(godot_dl_handle, NULL); + + JNIEnv *env = ThreadAndroid::get_env(); + + if (certStore) { + env->DeleteGlobalRef(certStore); + certStore = NULL; + } +} + +} // namespace support +} // namespace android +} // namespace gdmono + +using namespace gdmono::android::support; + +// The following are P/Invoke functions required by the monodroid profile of the BCL. +// These are P/Invoke functions and not internal calls, hence why they use +// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'. + +#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default"))) + +GD_PINVOKE_EXPORT int32_t _monodroid_get_android_api_level() { + return get_build_version_sdk_int(); +} + +GD_PINVOKE_EXPORT void monodroid_free(void *ptr) { + free(ptr); +} + +GD_PINVOKE_EXPORT int32_t monodroid_get_system_property(const char *p_name, char **r_value) { + char prop_value_str[PROP_VALUE_MAX + 1] = { 0 }; + + int len = __system_property_get(p_name, prop_value_str); + + if (r_value) { + if (len >= 0) { + *r_value = (char *)malloc(len + 1); + if (!*r_value) + return -1; + memcpy(*r_value, prop_value_str, len); + (*r_value)[len] = '\0'; + } else { + *r_value = NULL; + } + } + + return len; +} + +GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_up_state(const char *p_ifname, mono_bool *r_is_up) { + // The JNI code is the equivalent of: + // + // NetworkInterface.getByName(p_ifname).isUp() + + if (!r_is_up || !p_ifname || strlen(p_ifname) == 0) + return 0; + + *r_is_up = 0; + + JNIEnv *env = ThreadAndroid::get_env(); + + jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface"); + ERR_FAIL_NULL_V(networkInterfaceClass, 0); + + jmethodID getByName = env->GetStaticMethodID(networkInterfaceClass, "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;"); + ERR_FAIL_NULL_V(getByName, 0); + + jmethodID isUp = env->GetMethodID(networkInterfaceClass, "isUp", "()Z"); + ERR_FAIL_NULL_V(isUp, 0); + + ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname)); + ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get())); + + if (!networkInterface) + return 0; + + *r_is_up = (mono_bool)env->CallBooleanMethod(networkInterface, isUp); + + return 1; +} + +GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_supports_multicast(const char *p_ifname, mono_bool *r_supports_multicast) { + // The JNI code is the equivalent of: + // + // NetworkInterface.getByName(p_ifname).supportsMulticast() + + if (!r_supports_multicast || !p_ifname || strlen(p_ifname) == 0) + return 0; + + *r_supports_multicast = 0; + + JNIEnv *env = ThreadAndroid::get_env(); + + jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface"); + ERR_FAIL_NULL_V(networkInterfaceClass, 0); + + jmethodID getByName = env->GetStaticMethodID(networkInterfaceClass, "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;"); + ERR_FAIL_NULL_V(getByName, 0); + + jmethodID supportsMulticast = env->GetMethodID(networkInterfaceClass, "supportsMulticast", "()Z"); + ERR_FAIL_NULL_V(supportsMulticast, 0); + + ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname)); + ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get())); + + if (!networkInterface) + return 0; + + *r_supports_multicast = (mono_bool)env->CallBooleanMethod(networkInterface, supportsMulticast); + + return 1; +} + +static const int dns_servers_len = 8; + +static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dns_servers_count) { + // The JNI code is the equivalent of: + // + // ConnectivityManager connectivityManager = (ConnectivityManager)getApplicationContext() + // .getSystemService(Context.CONNECTIVITY_SERVICE); + // Network activeNerwork = connectivityManager.getActiveNetwork(); + // LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNerwork); + // List<String> dnsServers = linkProperties.getDnsServers().stream() + // .map(inetAddress -> inetAddress.getHostAddress()).collect(Collectors.toList()); + +#ifdef DEBUG_ENABLED + CRASH_COND(get_build_version_sdk_int() < 23); +#endif + + JNIEnv *env = ThreadAndroid::get_env(); + + GodotJavaWrapper *godot_java = ((OS_Android *)OS::get_singleton())->get_godot_java(); + jobject activity = godot_java->get_activity(); + + ScopedLocalRef<jclass> activityClass(env, env->GetObjectClass(activity)); + ERR_FAIL_NULL(activityClass); + + jmethodID getApplicationContext = env->GetMethodID(activityClass, "getApplicationContext", "()Landroid/content/Context;"); + + ScopedLocalRef<jobject> applicationContext(env, env->CallObjectMethod(activity, getApplicationContext)); + + ScopedLocalRef<jclass> contextClass(env, env->FindClass("android/content/Context")); + ERR_FAIL_NULL(contextClass); + + jfieldID connectivityServiceField = env->GetStaticFieldID(contextClass, "CONNECTIVITY_SERVICE", "Ljava/lang/String;"); + ScopedLocalRef<jstring> connectivityServiceString(env, (jstring)env->GetStaticObjectField(contextClass, connectivityServiceField)); + + jmethodID getSystemService = env->GetMethodID(contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + + ScopedLocalRef<jobject> connectivityManager(env, env->CallObjectMethod(applicationContext, getSystemService, connectivityServiceString.get())); + + if (!connectivityManager) + return; + + ScopedLocalRef<jclass> connectivityManagerClass(env, env->FindClass("android/net/ConnectivityManager")); + ERR_FAIL_NULL(connectivityManagerClass); + + jmethodID getActiveNetwork = env->GetMethodID(connectivityManagerClass, "getActiveNetwork", "()Landroid/net/Network;"); + ERR_FAIL_NULL(getActiveNetwork); + + ScopedLocalRef<jobject> activeNetwork(env, env->CallObjectMethod(connectivityManager, getActiveNetwork)); + + if (!activeNetwork) + return; + + jmethodID getLinkProperties = env->GetMethodID(connectivityManagerClass, + "getLinkProperties", "(Landroid/net/Network;)Landroid/net/LinkProperties;"); + ERR_FAIL_NULL(getLinkProperties); + + ScopedLocalRef<jobject> linkProperties(env, env->CallObjectMethod(connectivityManager, getLinkProperties, activeNetwork.get())); + + if (!linkProperties) + return; + + ScopedLocalRef<jclass> linkPropertiesClass(env, env->FindClass("android/net/LinkProperties")); + ERR_FAIL_NULL(linkPropertiesClass); + + jmethodID getDnsServers = env->GetMethodID(linkPropertiesClass, "getDnsServers", "()Ljava/util/List;"); + ERR_FAIL_NULL(getDnsServers); + + ScopedLocalRef<jobject> dnsServers(env, env->CallObjectMethod(linkProperties, getDnsServers)); + + if (!dnsServers) + return; + + ScopedLocalRef<jclass> listClass(env, env->FindClass("java/util/List")); + ERR_FAIL_NULL(listClass); + + jmethodID listSize = env->GetMethodID(listClass, "size", "()I"); + ERR_FAIL_NULL(listSize); + + int dnsServersCount = env->CallIntMethod(dnsServers, listSize); + + if (dnsServersCount > dns_servers_len) + dnsServersCount = dns_servers_len; + + if (dnsServersCount <= 0) + return; + + jmethodID listGet = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"); + ERR_FAIL_NULL(listGet); + + ScopedLocalRef<jclass> inetAddressClass(env, env->FindClass("java/net/InetAddress")); + ERR_FAIL_NULL(inetAddressClass); + + jmethodID getHostAddress = env->GetMethodID(inetAddressClass, "getHostAddress", "()Ljava/lang/String;"); + ERR_FAIL_NULL(getHostAddress); + + for (int i = 0; i < dnsServersCount; i++) { + ScopedLocalRef<jobject> dnsServer(env, env->CallObjectMethod(dnsServers, listGet, (jint)i)); + if (!dnsServer) + continue; + + ScopedLocalRef<jstring> hostAddress(env, (jstring)env->CallObjectMethod(dnsServer, getHostAddress)); + const char *host_address = env->GetStringUTFChars(hostAddress, 0); + + r_dns_servers[i] = strdup(host_address); // freed by the BCL + (*dns_servers_count)++; + + env->ReleaseStringUTFChars(hostAddress, host_address); + } + + // jesus... +} + +GD_PINVOKE_EXPORT int32_t _monodroid_get_dns_servers(void **r_dns_servers_array) { + if (!r_dns_servers_array) + return -1; + + *r_dns_servers_array = NULL; + + char *dns_servers[dns_servers_len]; + int dns_servers_count = 0; + + if (_monodroid_get_android_api_level() < 26) { + // The 'net.dns*' system properties are no longer available in Android 8.0 (API level 26) and greater: + // https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri + + char prop_name[] = "net.dns*"; + + for (int i = 0; i < dns_servers_len; i++) { + prop_name[7] = (char)(i + 0x31); + char *prop_value; + int32_t len = monodroid_get_system_property(prop_name, &prop_value); + + if (len > 0) { + dns_servers[dns_servers_count] = strndup(prop_value, (size_t)len); // freed by the BCL + dns_servers_count++; + free(prop_value); + } + } + } else { + // Alternative for Oreo and greater + interop_get_active_network_dns_servers(dns_servers, &dns_servers_count); + } + + if (dns_servers_count > 0) { + size_t ret_size = sizeof(char *) * (size_t)dns_servers_count; + *r_dns_servers_array = malloc(ret_size); // freed by the BCL + memcpy(*r_dns_servers_array, dns_servers, ret_size); + } + + return dns_servers_count; +} + +GD_PINVOKE_EXPORT const char *_monodroid_timezone_get_default_id() { + // The JNI code is the equivalent of: + // + // TimeZone.getDefault().getID() + + JNIEnv *env = ThreadAndroid::get_env(); + + ScopedLocalRef<jclass> timeZoneClass(env, env->FindClass("java/util/TimeZone")); + ERR_FAIL_NULL_V(timeZoneClass, NULL); + + jmethodID getDefault = env->GetStaticMethodID(timeZoneClass, "getDefault", "()Ljava/util/TimeZone;"); + ERR_FAIL_NULL_V(getDefault, NULL); + + jmethodID getID = env->GetMethodID(timeZoneClass, "getID", "()Ljava/lang/String;"); + ERR_FAIL_NULL_V(getID, NULL); + + ScopedLocalRef<jobject> defaultTimeZone(env, env->CallStaticObjectMethod(timeZoneClass, getDefault)); + + if (!defaultTimeZone) + return NULL; + + ScopedLocalRef<jstring> defaultTimeZoneID(env, (jstring)env->CallObjectMethod(defaultTimeZone, getID)); + + if (!defaultTimeZoneID) + return NULL; + + const char *default_time_zone_id = env->GetStringUTFChars(defaultTimeZoneID, 0); + + char *result = strdup(default_time_zone_id); // freed by the BCL + + env->ReleaseStringUTFChars(defaultTimeZoneID, default_time_zone_id); + + return result; +} + +GD_PINVOKE_EXPORT int32_t _monodroid_getifaddrs(struct ifaddrs **p_ifap) { + return getifaddrs(p_ifap); +} + +GD_PINVOKE_EXPORT void _monodroid_freeifaddrs(struct ifaddrs *p_ifap) { + freeifaddrs(p_ifap); +} + +#endif diff --git a/modules/mono/mono_gd/support/android_support.h b/modules/mono/mono_gd/support/android_support.h new file mode 100755 index 0000000000..dc2e6c95ed --- /dev/null +++ b/modules/mono/mono_gd/support/android_support.h @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* android_support.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef ANDROID_SUPPORT_H +#define ANDROID_SUPPORT_H + +#if defined(ANDROID_ENABLED) + +#include "core/ustring.h" + +namespace gdmono { +namespace android { +namespace support { + +String get_app_native_lib_dir(); + +void initialize(); +void cleanup(); + +void register_internal_calls(); + +} // namespace support +} // namespace android +} // namespace gdmono + +#endif // ANDROID_ENABLED + +#endif // ANDROID_SUPPORT_H diff --git a/modules/mono/mono_gd/support/ios_support.h b/modules/mono/mono_gd/support/ios_support.h new file mode 100755 index 0000000000..e28af120e3 --- /dev/null +++ b/modules/mono/mono_gd/support/ios_support.h @@ -0,0 +1,51 @@ +/*************************************************************************/ +/* ios_support.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef IOS_SUPPORT_H +#define IOS_SUPPORT_H + +#if defined(IPHONE_ENABLED) + +#include "core/ustring.h" + +namespace gdmono { +namespace ios { +namespace support { + +void initialize(); +void cleanup(); + +} // namespace support +} // namespace ios +} // namespace gdmono + +#endif // IPHONE_ENABLED + +#endif // IOS_SUPPORT_H diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm new file mode 100755 index 0000000000..e3d1a647fd --- /dev/null +++ b/modules/mono/mono_gd/support/ios_support.mm @@ -0,0 +1,151 @@ +/*************************************************************************/ +/* ios_support.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "ios_support.h" + +#if defined(IPHONE_ENABLED) + +#import <Foundation/Foundation.h> +#include <os/log.h> + +#include "core/ustring.h" + +#include "../gd_mono_marshal.h" + +// Implemented mostly following: https://github.com/mono/mono/blob/master/sdks/ios/app/runtime.m + +// Definition generated by the Godot exporter +extern "C" void gd_mono_setup_aot(); + +namespace gdmono { +namespace ios { +namespace support { + +void ios_mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) { + os_log_info(OS_LOG_DEFAULT, "(%s %s) %s", log_domain, log_level, message); + if (fatal) { + os_log_info(OS_LOG_DEFAULT, "Exit code: %d.", 1); + exit(1); + } +} + +void initialize() { + mono_dllmap_insert(NULL, "System.Native", NULL, "__Internal", NULL); + mono_dllmap_insert(NULL, "System.IO.Compression.Native", NULL, "__Internal", NULL); + mono_dllmap_insert(NULL, "System.Security.Cryptography.Native.Apple", NULL, "__Internal", NULL); + +#ifdef IOS_DEVICE + // This function is defined in an auto-generated source file + gd_mono_setup_aot(); +#endif + + mono_set_signal_chaining(true); + mono_set_crash_chaining(true); +} + +void cleanup() { +} + +} // namespace support +} // namespace ios +} // namespace gdmono + +// The following are P/Invoke functions required by the monotouch profile of the BCL. +// These are P/Invoke functions and not internal calls, hence why they use +// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'. + +#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default"))) + +GD_PINVOKE_EXPORT const char *xamarin_get_locale_country_code() { + NSLocale *locale = [NSLocale currentLocale]; + NSString *countryCode = [locale objectForKey:NSLocaleCountryCode]; + if (countryCode == NULL) { + return strdup("US"); + } + return strdup([countryCode UTF8String]); +} + +GD_PINVOKE_EXPORT void xamarin_log(const uint16_t *p_unicode_message) { + int length = 0; + const uint16_t *ptr = p_unicode_message; + while (*ptr++) + length += sizeof(uint16_t); + NSString *msg = [[NSString alloc] initWithBytes:p_unicode_message length:length encoding:NSUTF16LittleEndianStringEncoding]; + + os_log_info(OS_LOG_DEFAULT, "%{public}@", msg); +} + +GD_PINVOKE_EXPORT const char *xamarin_GetFolderPath(int p_folder) { + NSSearchPathDirectory dd = (NSSearchPathDirectory)p_folder; + NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:dd inDomains:NSUserDomainMask] lastObject]; + NSString *path = [url path]; + return strdup([path UTF8String]); +} + +GD_PINVOKE_EXPORT char *xamarin_timezone_get_local_name() { + NSTimeZone *tz = nil; + tz = [NSTimeZone localTimeZone]; + NSString *name = [tz name]; + return (name != nil) ? strdup([name UTF8String]) : strdup("Local"); +} + +GD_PINVOKE_EXPORT char **xamarin_timezone_get_names(uint32_t *p_count) { + NSArray *array = [NSTimeZone knownTimeZoneNames]; + *p_count = array.count; + char **result = (char **)malloc(sizeof(char *) * (*p_count)); + for (uint32_t i = 0; i < *p_count; i++) { + NSString *s = [array objectAtIndex:i]; + result[i] = strdup(s.UTF8String); + } + return result; +} + +GD_PINVOKE_EXPORT void *xamarin_timezone_get_data(const char *p_name, uint32_t *p_size) { // FIXME: uint32_t since Dec 2019, unsigned long before + NSTimeZone *tz = nil; + if (p_name) { + NSString *n = [[NSString alloc] initWithUTF8String:p_name]; + tz = [[[NSTimeZone alloc] initWithName:n] autorelease]; + [n release]; + } else { + tz = [NSTimeZone localTimeZone]; + } + NSData *data = [tz data]; + *p_size = [data length]; + void *result = malloc(*p_size); + memcpy(result, data.bytes, *p_size); + return result; +} + +GD_PINVOKE_EXPORT void xamarin_start_wwan(const char *p_uri) { + // FIXME: What's this for? No idea how to implement. + os_log_error(OS_LOG_DEFAULT, "Not implemented: 'xamarin_start_wwan'"); +} + +#endif // IPHONE_ENABLED |