Repository: godotengine/godot (language: cpp)

Issue:
(4.4.beta1) Android app crashes when exported with .NET 9
### Tested versions

**Reproducible:**
4.4.beta1
4.4.dev7

**Not Reproducible:**
4.4.dev3

### System information

Godot v4.4.beta1.mono - Windows 10 (build 19045) - OpenGL 3 (Compatibility)

### Issue description

When exporting to Android with .NET 9 using Gradle Build via the Remote Deploy, the app crashes immediately at startup.

Note:  .NET 8 works fine, but .NET 9 causes the app to crash

using the command at runtime:
**adb logcat|grep godot**

> 01-22 19:27:18.818  1895  3450 I ActivityTaskManager: START u0 {act=android.intent.action.MAIN flg=0x10000000 cmp=com.example.test_android/com.godot.game.GodotApp} from uid 2000
01-22 19:27:18.857  1895  2224 I ActivityManager: Start proc 1876:com.example.test_android/u0a411 for next-top-activity {com.example.test_android/com.godot.game.GodotApp}
01-22 19:27:19.885  1895  2006 W ActivityTaskManager:   Force finishing activity com.example.test_android/com.godot.game.GodotApp
01-22 19:27:19.987  1895  2220 I GameServiceProviderInstance: Failed to remove task overlay. This is expected if the task is already destroyed: GameSessionRecord{mTaskId=306, mState=GAME_SESSION_ATTACHED, mRootComponentName=ComponentInfo{com.example.test_android/com.godot.game.GodotApp}, mIGameSession=android.service.games.IGameSession$Stub$Proxy@3a45eaa, mSurfacePackage=android.view.SurfaceControlViewHost$SurfacePackage@3504a9b}
01-22 19:27:20.386  1895  2200 W ActivityTaskManager: Activity top resumed state loss timeout for ActivityRecord{691de90 u0 com.example.test_android/com.godot.game.GodotApp} t-1 f}}







### Steps to reproduce

1. Godot 4.4.beta1
2. Create a new project
3. Create C# script and attach to a node in the main scene
4. Update the visual studio project to target .NET 9: 
` <TargetFramework>net9.0</TargetFramework>`
4. Remote Deploy to Android with Gradle Build

### Minimal reproduction project (MRP)

N/A


Discussion / hints:
This is likely because .NET 9 introduced a new Java class (`net.dot.android.crypto.DotnetX509KeyManager`) in https://github.com/dotnet/runtime/pull/103337 and we're embedding the `System.Security.Cryptography.Native.Android` jar library from .NET 8. We were hoping the Java library wouldn't change often so it would remain compatible with newer versions for a while.

Fix patch:
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 578f723e700f..0f50f725258c 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -55,6 +55,10 @@
 #include "modules/modules_enabled.gen.h" // For mono.
 #include "modules/svg/image_loader_svg.h"
 
+#ifdef MODULE_MONO_ENABLED
+#include "modules/mono/utils/path_utils.h"
+#endif
+
 #ifdef ANDROID_ENABLED
 #include "../os_android.h"
 #endif
@@ -2526,6 +2530,43 @@ bool EditorExportPlatformAndroid::has_valid_username_and_password(const Ref<Edit
 	return valid;
 }
 
+#ifdef MODULE_MONO_ENABLED
+bool _validate_dotnet_tfm(const String &required_tfm, String &r_error) {
+	String assembly_name = path::get_csharp_project_name();
+	String project_path = ProjectSettings::get_singleton()->globalize_path("res://" + assembly_name + ".csproj");
+
+	if (!FileAccess::exists(project_path)) {
+		return true;
+	}
+
+	String pipe;
+	List<String> args;
+	args.push_back("build");
+	args.push_back(project_path);
+	args.push_back("--getProperty:TargetFramework");
+
+	int exitcode;
+	Error err = OS::get_singleton()->execute("dotnet", args, &pipe, &exitcode, true);
+	if (err != OK || exitcode != 0) {
+		if (err != OK) {
+			WARN_PRINT("Failed to execute dotnet command. Error " + String(error_names[err]));
+		} else if (exitcode != 0) {
+			print_line(pipe);
+			WARN_PRINT("dotnet command exited with code " + itos(exitcode) + ". See output above for more details.");
+		}
+		r_error += vformat(TTR("Unable to determine the C# project's TFM, it may be incompatible. The export template only supports '%s'. Make sure the project targets '%s' or consider using gradle builds instead."), required_tfm, required_tfm) + "\n";
+	} else {
+		String tfm = pipe.strip_edges();
+		if (tfm != required_tfm) {
+			r_error += vformat(TTR("C# project targets '%s' but the export template only supports '%s'. Consider using gradle builds instead."), tfm, required_tfm) + "\n";
+			return false;
+		}
+	}
+
+	return true;
+}
+#endif
+
 bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
 	String err;
 	bool valid = false;
@@ -2534,6 +2575,15 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
 #ifdef MODULE_MONO_ENABLED
 	// Android export is still a work in progress, keep a message as a warning.
 	err += TTR("Exporting to Android when using C#/.NET is experimental.") + "\n";
+
+	if (!gradle_build_enabled) {
+		// For template exports we only support .NET 8 because the template
+		// includes .jar dependencies that may only be compatible with .NET 8.
+		if (!_validate_dotnet_tfm("net8.0", err)) {
+			r_error = err;
+			return false;
+		}
+	}
 #endif
 
 	// Look for export templates (first official, and if defined custom templates).


Test patch: