Bläddra i källkod

Merge pull request #105262 from raulsntos/dotnet/android-no-copy

[.NET] Add a preload hook to load .NET assemblies from the APK
Thaddeus Crews 4 månader sedan
förälder
incheckning
fa5dd77566

+ 92 - 18
modules/mono/mono_gd/gd_mono.cpp

@@ -53,6 +53,12 @@
 #include <dlfcn.h>
 #endif
 
+#ifndef TOOLS_ENABLED
+#ifdef ANDROID_ENABLED
+#include "../thirdparty/mono_delegates.h"
+#endif
+#endif
+
 GDMono *GDMono::singleton = nullptr;
 
 namespace {
@@ -67,6 +73,14 @@ typedef int(CORECLR_DELEGATE_CALLTYPE *coreclr_initialize_fn)(const char *exePat
 
 coreclr_create_delegate_fn coreclr_create_delegate = nullptr;
 coreclr_initialize_fn coreclr_initialize = nullptr;
+
+#ifdef ANDROID_ENABLED
+mono_install_assembly_preload_hook_fn mono_install_assembly_preload_hook = nullptr;
+mono_assembly_name_get_name_fn mono_assembly_name_get_name = nullptr;
+mono_assembly_name_get_culture_fn mono_assembly_name_get_culture = nullptr;
+mono_image_open_from_data_with_name_fn mono_image_open_from_data_with_name = nullptr;
+mono_assembly_load_from_full_fn mono_assembly_load_from_full = nullptr;
+#endif
 #endif
 
 #ifdef _WIN32
@@ -276,6 +290,28 @@ bool load_coreclr(void *&r_coreclr_dll_handle) {
 	ERR_FAIL_COND_V(err != OK, false);
 	coreclr_create_delegate = (coreclr_create_delegate_fn)symbol;
 
+#ifdef ANDROID_ENABLED
+	err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_install_assembly_preload_hook", symbol);
+	ERR_FAIL_COND_V(err != OK, false);
+	mono_install_assembly_preload_hook = (mono_install_assembly_preload_hook_fn)symbol;
+
+	err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_assembly_name_get_name", symbol);
+	ERR_FAIL_COND_V(err != OK, false);
+	mono_assembly_name_get_name = (mono_assembly_name_get_name_fn)symbol;
+
+	err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_assembly_name_get_culture", symbol);
+	ERR_FAIL_COND_V(err != OK, false);
+	mono_assembly_name_get_culture = (mono_assembly_name_get_culture_fn)symbol;
+
+	err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_image_open_from_data_with_name", symbol);
+	ERR_FAIL_COND_V(err != OK, false);
+	mono_image_open_from_data_with_name = (mono_image_open_from_data_with_name_fn)symbol;
+
+	err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_assembly_load_from_full", symbol);
+	ERR_FAIL_COND_V(err != OK, false);
+	mono_assembly_load_from_full = (mono_assembly_load_from_full_fn)symbol;
+#endif
+
 	return (coreclr_initialize &&
 			coreclr_create_delegate);
 }
@@ -441,38 +477,76 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle)
 #endif
 
 #ifndef TOOLS_ENABLED
-String make_tpa_list() {
-	String tpa_list;
+#ifdef ANDROID_ENABLED
+MonoAssembly *load_assembly_from_pck(MonoAssemblyName *p_assembly_name, char **p_assemblies_path, void *p_user_data) {
+	constexpr bool ref_only = false;
 
-#if defined(WINDOWS_ENABLED)
-	String separator = ";";
-#else
-	String separator = ":";
-#endif
+	const char *name = mono_assembly_name_get_name(p_assembly_name);
+	const char *culture = mono_assembly_name_get_culture(p_assembly_name);
 
-	String assemblies_dir = GodotSharpDirs::get_api_assemblies_dir();
-	PackedStringArray files = DirAccess::get_files_at(assemblies_dir);
-	for (const String &file : files) {
-		tpa_list += assemblies_dir.path_join(file);
-		tpa_list += separator;
+	String assembly_name;
+	if (culture && strcmp(culture, "")) {
+		assembly_name += culture;
+		assembly_name += "/";
+	}
+	assembly_name += name;
+	if (!assembly_name.ends_with(".dll")) {
+		assembly_name += ".dll";
 	}
 
-	return tpa_list;
+	String path = GodotSharpDirs::get_api_assemblies_dir();
+	path = path.path_join(assembly_name);
+
+	print_verbose(".NET: Loading assembly '" + assembly_name + "' from '" + path + "'.");
+
+	if (!FileAccess::exists(path)) {
+		// We could not find the assembly, return null so another hook may find it.
+		return nullptr;
+	}
+
+	Vector<uint8_t> data = FileAccess::get_file_as_bytes(path);
+	ERR_FAIL_COND_V_MSG(data.is_empty(), nullptr, ".NET: Could not read assembly in '" + path + "'.");
+
+	MonoImageOpenStatus status = MONO_IMAGE_OK;
+
+	MonoImage *image = mono_image_open_from_data_with_name(
+			reinterpret_cast<char *>(data.ptrw()), data.size(),
+			/*need_copy*/ true,
+			&status,
+			ref_only,
+			assembly_name.utf8().get_data());
+
+	ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || image == nullptr, nullptr, ".NET: Failed to open assembly image.");
+
+	status = MONO_IMAGE_OK;
+
+	MonoAssembly *assembly = mono_assembly_load_from_full(
+			image, assembly_name.utf8().get_data(),
+			&status,
+			ref_only);
+
+	ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || assembly == nullptr, nullptr, ".NET: Failed to load assembly from image.");
+
+	return assembly;
 }
+#endif
 
 godot_plugins_initialize_fn initialize_coreclr_and_godot_plugins(bool &r_runtime_initialized) {
 	godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
 
 	String assembly_name = Path::get_csharp_project_name();
 
-	String tpa_list = make_tpa_list();
-	const char *prop_keys[] = { "TRUSTED_PLATFORM_ASSEMBLIES" };
-	const char *prop_values[] = { tpa_list.utf8().get_data() };
-	constexpr int nprops = std::size(prop_keys);
+#ifdef ANDROID_ENABLED
+	// Android requires installing a preload hook to load assemblies from inside the APK,
+	// other platforms can find the assemblies with the default lookup.
+	if (mono_install_assembly_preload_hook != nullptr) {
+		mono_install_assembly_preload_hook(&load_assembly_from_pck, nullptr);
+	}
+#endif
 
 	void *coreclr_handle = nullptr;
 	unsigned int domain_id = 0;
-	int rc = coreclr_initialize(nullptr, nullptr, nprops, (const char **)&prop_keys, (const char **)&prop_values, &coreclr_handle, &domain_id);
+	int rc = coreclr_initialize(nullptr, nullptr, 0, nullptr, nullptr, &coreclr_handle, &domain_id);
 	ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to initialize CoreCLR.");
 
 	r_runtime_initialized = true;

+ 38 - 0
modules/mono/thirdparty/mono_delegates.h

@@ -0,0 +1,38 @@
+// Adapted from monovm.h and assembly-functions.h to match coreclr_delegates.h.
+
+// https://github.com/dotnet/runtime/blob/27a7fe5c4bbe0762c231b2a46162e60ee04f3cde/src/mono/mono/mini/monovm.h
+// https://github.com/dotnet/runtime/blob/27a7fe5c4bbe0762c231b2a46162e60ee04f3cde/src/native/public/mono/metadata/details/assembly-functions.h
+
+#ifndef _MONO_DELEGATES_H_
+#define _MONO_DELEGATES_H_
+
+#include "mono_types.h"
+
+typedef MonoAssembly *(*MonoAssemblyPreLoadFunc)(
+		MonoAssemblyName *aname,
+		char **assemblies_path,
+		void* user_data);
+
+typedef void (*mono_install_assembly_preload_hook_fn)(
+		MonoAssemblyPreLoadFunc func,
+		void *user_data);
+
+typedef const char *(*mono_assembly_name_get_name_fn)(MonoAssemblyName *aname);
+
+typedef const char *(*mono_assembly_name_get_culture_fn)(MonoAssemblyName *aname);
+
+typedef MonoImage *(*mono_image_open_from_data_with_name_fn)(
+		char *data,
+		uint32_t data_len,
+		mono_bool need_copy,
+		/*out*/ MonoImageOpenStatus *status,
+		mono_bool refonly,
+		const char *name);
+
+typedef MonoAssembly *(*mono_assembly_load_from_full_fn)(
+		MonoImage *image,
+		const char *fname,
+		/*out*/ MonoImageOpenStatus *status,
+		mono_bool refonly);
+
+#endif // _MONO_DELEGATES_H_

+ 26 - 0
modules/mono/thirdparty/mono_types.h

@@ -0,0 +1,26 @@
+// Adapted from mono-public-types.h and image-types.h.
+
+// https://github.com/dotnet/runtime/blob/27a7fe5c4bbe0762c231b2a46162e60ee04f3cde/src/native/public/mono/utils/details/mono-publib-types.h
+// https://github.com/dotnet/runtime/blob/27a7fe5c4bbe0762c231b2a46162e60ee04f3cde/src/native/public/mono/metadata/details/image-types.h
+
+#ifndef _MONO_TYPES_H_
+#define _MONO_TYPES_H_
+
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef int32_t mono_bool;
+
+typedef void MonoAssembly;
+typedef void MonoAssemblyName;
+typedef void MonoImage;
+
+typedef enum {
+	MONO_IMAGE_OK,
+	MONO_IMAGE_ERROR_ERRNO,
+	MONO_IMAGE_MISSING_ASSEMBLYREF,
+	MONO_IMAGE_IMAGE_INVALID,
+	MONO_IMAGE_NOT_SUPPORTED, ///< \since net7
+} MonoImageOpenStatus;
+
+#endif // _MONO_TYPES_H_