diff --git a/Knossos.NET.Android/Properties/AndroidManifest.xml b/Knossos.NET.Android/Properties/AndroidManifest.xml
index a4306270..62f6b296 100644
--- a/Knossos.NET.Android/Properties/AndroidManifest.xml
+++ b/Knossos.NET.Android/Properties/AndroidManifest.xml
@@ -46,5 +46,12 @@
android:process=":game"
android:launchMode="singleTask">
+
diff --git a/Knossos.NET.Android/java/com/knossosnet/knossosnet/FlagsActivity.java b/Knossos.NET.Android/java/com/knossosnet/knossosnet/FlagsActivity.java
new file mode 100644
index 00000000..48ed094a
--- /dev/null
+++ b/Knossos.NET.Android/java/com/knossosnet/knossosnet/FlagsActivity.java
@@ -0,0 +1,149 @@
+package com.knossosnet.knossosnet;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.lang.ref.WeakReference;
+import android.os.ResultReceiver;
+import android.app.Activity;
+
+public class FlagsActivity extends org.libsdl.app.SDLActivity {
+
+ private static WeakReference _self = null;
+ private static String _workingFolder = "";
+ private static volatile boolean _delivered = false;
+ private ResultReceiver _receiver = null;
+
+ /* FSO API */
+ public static String getWorkingFolder() { return _workingFolder; }
+
+ public static void setFlagsJson(String json) {
+ FlagsActivity act = (_self != null) ? _self.get() : null;
+ if (act != null) act.deliver(json);
+ }
+
+ public static void enableOverlay() {
+ }
+
+ public static void disableOverlay() {
+ }
+
+ /* ******* */
+
+ @Override
+ protected String[] getArguments() {
+ Intent i = getIntent();
+ ArrayList args = (i != null) ? i.getStringArrayListExtra("fsoArgs") : null;
+ if (args == null || args.isEmpty()) {
+ return new String[] { "-get_flags", "json_v2" };
+ }
+ return args.toArray(new String[0]);
+ }
+
+ @Override
+ protected String[] getLibraries() { return new String[] { }; }
+
+ @Override
+ protected String getMainSharedObject() {
+ String path = getIntent().getStringExtra("engineLibName");
+ return (path == null || path.isEmpty()) ? null : path;
+ }
+
+ @Override
+ protected String getMainFunction() { return "android_main"; }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ _self = new WeakReference<>(this);
+ _delivered = false;
+
+ Intent i = getIntent();
+ if (i != null) {
+ _workingFolder = i.getStringExtra("workingFolder");
+ if (_workingFolder == null) _workingFolder = "";
+ _receiver = i.getParcelableExtra("flagsReceiver");
+ }
+
+ loadNatives();
+
+ setResult(RESULT_CANCELED);
+
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ _self = null;
+ try {
+ String proc = android.app.Application.getProcessName();
+ if (proc != null && proc.endsWith(":flags")) {
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+ } catch (Throwable ignored) {}
+ }
+
+ private static final String[] PREFERRED_ORDER = new String[] {
+ "libSDL2.so", "libshaderc.so", "libopenal.so", "libavutil.so",
+ "libswresample.so", "libswscale.so", "libavcodec.so",
+ "libavformat.so", "libavfilter.so"
+ };
+
+ private void loadNatives() {
+ File dir = new File(getFilesDir(), "natives/");
+ List loadList = orderForLoad(dir);
+ List failed = new ArrayList<>();
+ for (File so : loadList) if (!tryLoad(so)) failed.add(so);
+ for (File so : failed) tryLoad(so);
+ }
+
+ private static List orderForLoad(File dir) {
+ ArrayList ordered = new ArrayList<>();
+ if (dir == null || !dir.isDirectory()) return ordered;
+ for (String name : PREFERRED_ORDER) {
+ File f = new File(dir, name);
+ if (f.isFile()) ordered.add(f);
+ }
+ File[] arr = dir.listFiles((d, name) -> name != null && name.endsWith(".so"));
+ if (arr != null) {
+ for (File f : arr) {
+ if (!containsName(ordered, f.getName()) && !isEngineName(f.getName())) {
+ ordered.add(f);
+ }
+ }
+ }
+ return ordered;
+ }
+
+ private boolean tryLoad(File so) {
+ try {
+ if (so != null && so.isFile()) { System.load(so.getAbsolutePath()); return true; }
+ } catch (UnsatisfiedLinkError e) { e.printStackTrace(); }
+ return false;
+ }
+
+ private static boolean containsName(List list, String name) {
+ for (File f : list) if (f.getName().equals(name)) return true;
+ return false;
+ }
+
+ private static boolean isEngineName(String name) {
+ if (name == null) return false;
+ return name.startsWith("libfso") || name.contains("libfs2");
+ }
+
+ private void deliver(final String json) {
+ if (_delivered) return;
+ _delivered = true;
+ runOnUiThread(() -> {
+ if (_receiver != null) {
+ Bundle b = new Bundle();
+ b.putString("flagsJson", json);
+ _receiver.send(Activity.RESULT_OK, b);
+ }
+ finish();
+ });
+ }
+}
\ No newline at end of file
diff --git a/Knossos.NET/Classes/AndroidHelper.cs b/Knossos.NET/Classes/AndroidHelper.cs
index 6413df55..f17038eb 100644
--- a/Knossos.NET/Classes/AndroidHelper.cs
+++ b/Knossos.NET/Classes/AndroidHelper.cs
@@ -2,8 +2,10 @@
using Android.App;
using System.Linq;
using Android.Content;
-#endif
using Avalonia.Threading;
+using Android.OS;
+#endif
+
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -183,6 +185,73 @@ public static void LaunchFSO(string engineLibPath, string? workingFolder, string
}
}
+ ///
+ /// Gets FSO flags
+ ///
+ ///
+ ///
+ /// json string or empty string
+ public static async Task GetFlagsStringFSOAsync(string engineLibPath, int timeoutMs = 30000)
+ {
+ try
+ {
+ var ctx = Application.Context;
+ string dstAbiDir = System.IO.Path.Combine(ctx.FilesDir!.AbsolutePath, "natives");
+ var fi = new FileInfo(engineLibPath);
+ var folderPath = fi.Directory!.FullName;
+ if (!folderPath.EndsWith("/"))
+ folderPath += "/";
+ StageAllToInternal(folderPath, dstAbiDir);
+ var libName = fi.Name;
+
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ using var receiver = new FlagsResultReceiver(tcs);
+
+ var intent = new Intent();
+ intent.SetClassName(ctx, "com.knossosnet.knossosnet.FlagsActivity");
+ intent.AddFlags(ActivityFlags.NewTask);
+ intent.PutExtra("engineLibName", System.IO.Path.Combine(dstAbiDir, libName));
+
+ intent.PutStringArrayListExtra("fsoArgs", new List { "-get_flags", "json_v2" });
+ intent.PutExtra("flagsReceiver", receiver);
+
+ ctx.StartActivity(intent);
+
+ var completed = await Task.WhenAny(tcs.Task, Task.Delay(timeoutMs)).ConfigureAwait(false);
+ if (completed != tcs.Task)
+ {
+ Log.Add(Log.LogSeverity.Error, "AndroidHelper.GetFlagsStringFSO", "Timeout waiting for fso flags (" + timeoutMs + "ms).");
+ return "";
+ }
+
+ return await tcs.Task.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "AndroidHelper.GetFlagsStringFSO", ex);
+ }
+ return "";
+ }
+
+ private sealed class FlagsResultReceiver : ResultReceiver
+ {
+ private readonly TaskCompletionSource _tcs;
+
+ public FlagsResultReceiver(TaskCompletionSource tcs)
+ : base(new Handler(Looper.MainLooper!))
+ {
+ _tcs = tcs;
+ }
+
+ protected override void OnReceiveResult(int resultCode, Bundle? resultData)
+ {
+ if (resultCode == (int)Result.Ok)
+ _tcs.TrySetResult(resultData?.GetString("flagsJson") ?? "");
+ else
+ _tcs.TrySetResult("");
+ }
+ }
+
#else
//Stubs
public static string? GetExternalAppFilesDir() => "";
@@ -193,6 +262,7 @@ public static void LaunchFSO(string engineLibPath, string? workingFolder, string
public static string GetDefaultKnetDataDir() => "";
public static string GetDefaultFSODataDir() => "";
public static void LaunchFSO(string engineLibPath, string? workingFolder, string cmdline) { }
+ public static async Task GetFlagsStringFSOAsync(string engineLibPath, int timeoutMs = 30000) { await Task.Delay(1); return ""; }
#endif
}
diff --git a/Knossos.NET/Classes/FsoBuild.cs b/Knossos.NET/Classes/FsoBuild.cs
index 1605a065..48087341 100644
--- a/Knossos.NET/Classes/FsoBuild.cs
+++ b/Knossos.NET/Classes/FsoBuild.cs
@@ -330,10 +330,6 @@ public async Task RunFSO(FsoExecType executableType, string cmdline,
/// A FlagsJsonV1 structure or null if failed
public async Task GetFlagsV1Async()
{
-#if ANDROID
- // no flags on android
- return null;
-#else
var executable = GetExecutable(FsoExecType.Flags);
var fullpath = GetExecutablePath(executable);
if (fullpath == null)
@@ -353,16 +349,21 @@ public async Task RunFSO(FsoExecType executableType, string cmdline,
}
Log.Add(Log.LogSeverity.Information, "FsoBuild.GetFlags()", "Getting FSO Flags from file: " + fullpath);
-
- if(KnUtils.IsLinux || KnUtils.IsMacOS)
- {
- KnUtils.Chmod(fullpath!,"+x");
- }
-
string output = string.Empty;
string stderr = string.Empty;
try
{
+#if ANDROID
+ output = await AndroidHelper.GetFlagsStringFSOAsync(fullpath!);
+ if (output == "") return null;
+ return JsonSerializer.Deserialize(output);
+#else
+
+ if (KnUtils.IsLinux || KnUtils.IsMacOS)
+ {
+ KnUtils.Chmod(fullpath!,"+x");
+ }
+
using (var cmd = new Process())
{
cmd.StartInfo.FileName = fullpath;
@@ -433,6 +434,7 @@ public async Task RunFSO(FsoExecType executableType, string cmdline,
}
return JsonSerializer.Deserialize(result);
}
+#endif
}
catch (JsonException exJson)
{
@@ -455,7 +457,6 @@ public async Task RunFSO(FsoExecType executableType, string cmdline,
Log.Add(Log.LogSeverity.Error, "FSO EXE OUTPUT ", output);
return null;
}
-#endif
}
///