Adding first APK build

This commit is contained in:
ml
2026-07-20 09:45:08 +02:00
parent 8712269b51
commit 9e2c9ffbe6
42 changed files with 10012 additions and 6 deletions
@@ -0,0 +1,145 @@
package de.ladkau.questshock;
import android.Manifest;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.libsdl.app.SDLActivity;
/**
* Everything here runs before super.onCreate() (which is what loads the
* native libraries and eventually calls Shockolate's own main()) - see
* android/engine-patches/ for why: engine/ itself is never modified, so
* this Java-side setup is the only place left to prepare
* /sdcard/questshock/ the way Shockolate's plain relative-path file I/O
* ("res/data/...", confirmed via grep - none of it goes through SDL_RWops,
* so Android's asset-manager fallback for SDL_RWFromFile doesn't apply
* here) expects to find it.
*/
public class QuestShockActivity extends SDLActivity {
private static final String TAG = "QuestShock";
private static final int PERMISSION_REQUEST_STORAGE = 1;
private static final String GAME_DIR = "/sdcard/questshock";
// Loaded here (rather than waiting for SDLActivity's own
// getLibraries()/loadLibraries(), which only runs from super.onCreate())
// purely to get nativeChdir() below - Android's dynamic linker resolves
// "main"'s own dependencies (SDL2, SDL2_mixer, fluidsynth) from the
// APK's native library directory regardless of Java-side load order, so
// loading it early here is safe. SDLActivity loading "main" again later
// is a harmless no-op (System.loadLibrary is idempotent per
// ClassLoader).
static {
System.loadLibrary("main");
}
// See android/app/src/main/cpp/questshock_native.c - android.system.Os
// has no public chdir() (confirmed against the actual API 34 stub jar).
private static native void nativeChdir(String path);
private Bundle mSavedInstanceState;
@Override
protected String[] getLibraries() {
return new String[] {
"SDL2",
"SDL2_mixer",
"fluidsynth",
"main"
};
}
@Override
protected void onCreate(Bundle savedInstanceState) {
mSavedInstanceState = savedInstanceState;
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE },
PERMISSION_REQUEST_STORAGE);
return;
}
setUpGameDirAndContinue();
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PERMISSION_REQUEST_STORAGE) {
setUpGameDirAndContinue();
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void setUpGameDirAndContinue() {
File gameDir = new File(GAME_DIR);
if (!gameDir.exists() && !gameDir.mkdirs()) {
Log.e(TAG, "Could not create " + GAME_DIR + " - res/data and res/sound will not be found");
}
// Extract our own bundled build artifacts (shaders, the soundfont,
// and the "get assets" instructions) - never the proprietary game
// data, which the user must supply themselves (see
// res/GET_ASSETS_QUEST.txt). Only done once: if res/GET_ASSETS_QUEST.txt
// is already there, assume a previous run already extracted everything.
File marker = new File(gameDir, "GET_ASSETS_QUEST.txt");
if (!marker.exists()) {
copyAssetDir("shaders", new File(gameDir, "shaders"));
copyAssetFile("res/soundfont.sf2", new File(gameDir, "res/soundfont.sf2"));
copyAssetFile("GET_ASSETS_QUEST.txt", marker);
}
// chdir() is process-wide, not per-thread - already in effect for
// every thread (including the one that will run Shockolate's own
// SDL_main) by the time super.onCreate() below starts it.
nativeChdir(GAME_DIR);
super.onCreate(mSavedInstanceState);
}
private void copyAssetFile(String assetPath, File dest) {
if (dest.exists()) {
return;
}
File parent = dest.getParentFile();
if (parent != null) {
parent.mkdirs();
}
AssetManager assets = getAssets();
try (InputStream in = assets.open(assetPath);
OutputStream out = new FileOutputStream(dest)) {
byte[] buf = new byte[64 * 1024];
int n;
while ((n = in.read(buf)) > 0) {
out.write(buf, 0, n);
}
} catch (IOException e) {
Log.e(TAG, "Could not extract asset " + assetPath + " to " + dest, e);
}
}
private void copyAssetDir(String assetDir, File destDir) {
AssetManager assets = getAssets();
String[] entries;
try {
entries = assets.list(assetDir);
} catch (IOException e) {
Log.e(TAG, "Could not list asset dir " + assetDir, e);
return;
}
if (entries == null || entries.length == 0) {
return;
}
destDir.mkdirs();
for (String entry : entries) {
copyAssetFile(assetDir + "/" + entry, new File(destDir, entry));
}
}
}