Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Knossos.NET.Android/Properties/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,12 @@
android:process=":game"
android:launchMode="singleTask">
</activity>
<activity
android:name=".FlagsActivity"
android:process=":flags"
android:exported="false"
android:noHistory="true"
android:excludeFromRecents="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" />
</application>
</manifest>
149 changes: 149 additions & 0 deletions Knossos.NET.Android/java/com/knossosnet/knossosnet/FlagsActivity.java
Original file line number Diff line number Diff line change
@@ -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<FlagsActivity> _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<String> 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<File> loadList = orderForLoad(dir);
List<File> failed = new ArrayList<>();
for (File so : loadList) if (!tryLoad(so)) failed.add(so);
for (File so : failed) tryLoad(so);
}

private static List<File> orderForLoad(File dir) {
ArrayList<File> 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<File> 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();
});
}
}
72 changes: 71 additions & 1 deletion Knossos.NET/Classes/AndroidHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -183,6 +185,73 @@ public static void LaunchFSO(string engineLibPath, string? workingFolder, string
}
}

/// <summary>
/// Gets FSO flags
/// </summary>
/// <param name="engineLibPath"></param>
/// <param name="timeoutMs"></param>
/// <returns>json string or empty string</returns>
public static async Task<string> 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<string>(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<string> { "-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<string> _tcs;

public FlagsResultReceiver(TaskCompletionSource<string> 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() => "";
Expand All @@ -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<string> GetFlagsStringFSOAsync(string engineLibPath, int timeoutMs = 30000) { await Task.Delay(1); return ""; }
#endif
}

23 changes: 12 additions & 11 deletions Knossos.NET/Classes/FsoBuild.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,10 +330,6 @@ public async Task<FsoResult> RunFSO(FsoExecType executableType, string cmdline,
/// <returns>A FlagsJsonV1 structure or null if failed</returns>
public async Task<FlagsJsonV1?> GetFlagsV1Async()
{
#if ANDROID
// no flags on android
return null;
#else
var executable = GetExecutable(FsoExecType.Flags);
var fullpath = GetExecutablePath(executable);
if (fullpath == null)
Expand All @@ -353,16 +349,21 @@ public async Task<FsoResult> 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<FlagsJsonV1>(output);
#else

if (KnUtils.IsLinux || KnUtils.IsMacOS)
{
KnUtils.Chmod(fullpath!,"+x");
}

using (var cmd = new Process())
{
cmd.StartInfo.FileName = fullpath;
Expand Down Expand Up @@ -433,6 +434,7 @@ public async Task<FsoResult> RunFSO(FsoExecType executableType, string cmdline,
}
return JsonSerializer.Deserialize<FlagsJsonV1>(result);
}
#endif
}
catch (JsonException exJson)
{
Expand All @@ -455,7 +457,6 @@ public async Task<FsoResult> RunFSO(FsoExecType executableType, string cmdline,
Log.Add(Log.LogSeverity.Error, "FSO EXE OUTPUT ", output);
return null;
}
#endif
}

/// <summary>
Expand Down