layout: restructure into src/ tests/ android/ scripts/ tools/
- extension/src/{port,platform,codepage} -> src/; native_render -> src/host
(+ dxt, shaders/); libgr2 -> src/gr2; extension/third_party -> third_party
- extension/tests -> tests/port, libgr2/tests -> tests/gr2
- android-native -> android (build.sh, push-client.sh moved in)
- script -> scripts; tools/40250 -> tools/server; oracle -> tools/granny-oracle;
perf tools -> tools/perf
- all build trees under build/ (native, release, android, port-gate)
- xrender:: CMake aliases -> mt::; port-map ledger impl paths rewritten
No code changes. ctest 15/15, port_gate macos+android PASS, port_map check 0 errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
70710477cf
commit
a46093104c
@@ -0,0 +1,39 @@
|
||||
// SDLActivity shell around src/host (libmain.so). The native libraries, the SDL Java sources
|
||||
// and the assets come from ../build/android, produced by build.sh.
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
def nativeBuild = file("${rootDir}/../build/android")
|
||||
|
||||
android {
|
||||
namespace = 'org.metin2port.client'
|
||||
compileSdk 35
|
||||
defaultConfig {
|
||||
applicationId 'org.metin2port.client'
|
||||
minSdk 24
|
||||
targetSdk 35
|
||||
versionCode 1
|
||||
versionName '0.1'
|
||||
ndk { abiFilters 'arm64-v8a' }
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
sourceSets.main {
|
||||
java.srcDirs += "${nativeBuild}/_deps/SDL3-3.4.16/android-project/app/src/main/java"
|
||||
jniLibs.srcDirs = ["${nativeBuild}/jniLibs"]
|
||||
assets.srcDirs = ["${nativeBuild}/app-build/src/host/android-assets"]
|
||||
}
|
||||
packagingOptions {
|
||||
jniLibs { useLegacyPackaging = true }
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
lint { abortOnError = false }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x400003" android:required="true" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<application android:label="@string/app_name"
|
||||
android:allowBackup="false"
|
||||
android:appCategory="game"
|
||||
android:isGame="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
android:hardwareAccelerated="true"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
|
||||
android:preferMinimalPostProcessing="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,293 @@
|
||||
package org.metin2port.client;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.RoundedCorner;
|
||||
import android.view.Choreographer;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsController;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
/**
|
||||
* Runs src/host's main() (libmain.so). Command-line arguments come from the launch intent,
|
||||
* e.g. adb shell am start -n org.metin2port.client/.MainActivity \
|
||||
* --es args "--live-server 192.168.21.203:11000:13000 --login-screen"
|
||||
* A launcher start (no arguments) opens the login screen against DEFAULT_ARGS' server, so the
|
||||
* installed app is played by hand on the real server. Any explicit args replace DEFAULT_ARGS; args
|
||||
* without --live-server (e.g. --es args "--fake-mobs 64") run the in-process fake-server smoke flow.
|
||||
*/
|
||||
public class MainActivity extends SDLActivity {
|
||||
// --perf-log: src/host/perf_log.h writes perf-*.csv under files/perf (tools/perf/pull_perf_logs.sh).
|
||||
private static final String DEFAULT_ARGS =
|
||||
"--live-server 192.168.21.203:11000:13000 --login-screen --perf-log --show-fps";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||
applyImmersiveMode();
|
||||
// --refresh-rate N pins the display mode for a test run; otherwise src/host switches it with
|
||||
// the frame-rate setting (setDisplayRefreshRate).
|
||||
final float pinned = requestedRefreshRate(launchArgs());
|
||||
if (pinned > 0)
|
||||
applyRefreshRate(pinned);
|
||||
}
|
||||
|
||||
private String launchArgs() {
|
||||
Intent intent = getIntent();
|
||||
String args = intent != null ? intent.getStringExtra("args") : null;
|
||||
return args == null || args.trim().isEmpty() ? DEFAULT_ARGS : args;
|
||||
}
|
||||
|
||||
// --refresh-rate N (0 = system default); -1 when absent.
|
||||
private static float requestedRefreshRate(String args) {
|
||||
final String[] tokens = args.trim().split("\\s+");
|
||||
for (int i = 0; i + 1 < tokens.length; ++i) {
|
||||
if (tokens[i].equals("--refresh-rate")) {
|
||||
try {
|
||||
return Float.parseFloat(tokens[i + 1]);
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks for the display mode with the current resolution and the refresh rate closest to `wanted`
|
||||
* (the highest one when wanted < 0). The vendor can still override it; currentRefreshRate() reports
|
||||
* what the display actually runs at.
|
||||
*/
|
||||
private int applyRefreshRate(float wanted) {
|
||||
if (wanted == 0 || Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return 0;
|
||||
final Display display = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ? getDisplay()
|
||||
: getWindowManager().getDefaultDisplay();
|
||||
if (display == null)
|
||||
return 0;
|
||||
final Display.Mode current = display.getMode();
|
||||
Display.Mode best = null;
|
||||
for (Display.Mode mode : display.getSupportedModes()) {
|
||||
if (mode.getPhysicalWidth() != current.getPhysicalWidth() ||
|
||||
mode.getPhysicalHeight() != current.getPhysicalHeight())
|
||||
continue;
|
||||
if (best == null) {
|
||||
best = mode;
|
||||
continue;
|
||||
}
|
||||
final float rate = mode.getRefreshRate();
|
||||
final boolean better = wanted < 0 ? rate > best.getRefreshRate()
|
||||
: Math.abs(rate - wanted) < Math.abs(best.getRefreshRate() - wanted);
|
||||
if (better)
|
||||
best = mode;
|
||||
}
|
||||
if (best == null)
|
||||
return 0;
|
||||
final WindowManager.LayoutParams params = getWindow().getAttributes();
|
||||
params.preferredDisplayModeId = best.getModeId();
|
||||
getWindow().setAttributes(params);
|
||||
Log.i("Metin2Args", "display mode " + best.getModeId() + " " + best.getPhysicalWidth() + "x" +
|
||||
best.getPhysicalHeight() + " @ " + best.getRefreshRate() + " Hz");
|
||||
return Math.round(best.getRefreshRate());
|
||||
}
|
||||
|
||||
// The app-vsync rate, measured with a Choreographer callback. It differs from the panel's refresh rate
|
||||
// when the system renders the app at a divisor of it (ColorOS drops a non-game app to 60 fps after
|
||||
// 2.5 s without a touch while the panel stays at 120 Hz). Started by the first currentRenderRate().
|
||||
private volatile float renderRate = -1f;
|
||||
private boolean renderRateStarted = false;
|
||||
|
||||
// Called from native code (android_perf.h), off the UI thread.
|
||||
public float currentRenderRate() {
|
||||
synchronized (this) {
|
||||
if (!renderRateStarted) {
|
||||
renderRateStarted = true;
|
||||
runOnUiThread(() -> Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
|
||||
long windowStart = 0;
|
||||
int frames = 0;
|
||||
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
if (windowStart == 0) {
|
||||
windowStart = frameTimeNanos;
|
||||
} else if (++frames >= 30) {
|
||||
renderRate = frames * 1e9f / (frameTimeNanos - windowStart);
|
||||
windowStart = frameTimeNanos;
|
||||
frames = 0;
|
||||
}
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
return renderRate;
|
||||
}
|
||||
|
||||
// Called from native code (android_perf.h): battery temperature in C, -1 if unknown.
|
||||
public float batteryTemperature() {
|
||||
final Intent battery = registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
||||
final int tenths = battery != null ? battery.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -10) : -10;
|
||||
return tenths / 10f;
|
||||
}
|
||||
|
||||
// Called from native code (android_perf.h): the display mode whose refresh rate is closest to `hz`
|
||||
// (the highest one when hz < 0), for the frame-rate setting. The panel then need not scan out at 120 Hz
|
||||
// for a 60 fps game.
|
||||
public void setDisplayRefreshRate(float hz) {
|
||||
runOnUiThread(() -> applyRefreshRate(hz));
|
||||
}
|
||||
|
||||
// Called from native code (android_perf.h): the highest refresh rate of the current resolution.
|
||||
public float maxRefreshRate() {
|
||||
final Display display = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ? getDisplay()
|
||||
: getWindowManager().getDefaultDisplay();
|
||||
if (display == null)
|
||||
return -1f;
|
||||
float best = display.getRefreshRate();
|
||||
final Display.Mode current = display.getMode();
|
||||
for (Display.Mode mode : display.getSupportedModes())
|
||||
if (mode.getPhysicalWidth() == current.getPhysicalWidth() &&
|
||||
mode.getPhysicalHeight() == current.getPhysicalHeight())
|
||||
best = Math.max(best, mode.getRefreshRate());
|
||||
return best;
|
||||
}
|
||||
|
||||
// Called from native code (android_perf.h): BATTERY_PROPERTY_CURRENT_NOW as reported -- uA per the
|
||||
// docs, mA on some vendors; the sign convention varies too (android_perf::battery_power sorts both
|
||||
// out). 0 if unknown.
|
||||
public float batteryCurrentRaw() {
|
||||
final BatteryManager manager = (BatteryManager) getSystemService(BATTERY_SERVICE);
|
||||
if (manager == null)
|
||||
return 0f;
|
||||
final int raw = manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW);
|
||||
return raw == Integer.MIN_VALUE ? 0f : raw;
|
||||
}
|
||||
|
||||
// Called from native code (android_perf.h): battery voltage in mV, -1 if unknown.
|
||||
public float batteryVoltage() {
|
||||
final Intent battery = registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
||||
return battery != null ? battery.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1) : -1f;
|
||||
}
|
||||
|
||||
// Called from native code (android_perf.h): 1 while a charger is connected (the current then no
|
||||
// longer measures the game's draw), 0 otherwise.
|
||||
public float batteryPlugged() {
|
||||
final Intent battery = registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
||||
return battery != null && battery.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0 ? 1f : 0f;
|
||||
}
|
||||
|
||||
// Called from native code (android_perf.h): the rate the display runs at now.
|
||||
public float currentRefreshRate() {
|
||||
final Display display = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ? getDisplay()
|
||||
: getWindowManager().getDefaultDisplay();
|
||||
return display != null ? display.getRefreshRate() : -1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
applyImmersiveMode();
|
||||
}
|
||||
|
||||
// Called from native code (touch_controller long press).
|
||||
public void performHaptic() {
|
||||
runOnUiThread(() -> getWindow().getDecorView().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus)
|
||||
applyImmersiveMode();
|
||||
}
|
||||
|
||||
private void applyImmersiveMode() {
|
||||
final View decor = getWindow().getDecorView();
|
||||
decor.setSystemUiVisibility(
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
|
||||
View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
getWindow().setDecorFitsSystemWindows(false);
|
||||
final WindowInsetsController controller = getWindow().getInsetsController();
|
||||
if (controller != null) {
|
||||
controller.hide(WindowInsets.Type.systemBars());
|
||||
controller.setSystemBarsBehavior(
|
||||
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[] { "SDL3", "main" };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getArguments() {
|
||||
Intent intent = getIntent();
|
||||
String args = intent != null ? intent.getStringExtra("args") : null;
|
||||
Log.i("Metin2Args", "native args: " + args);
|
||||
if (args == null || args.trim().isEmpty())
|
||||
args = DEFAULT_ARGS;
|
||||
if (!args.contains("--safe-inset-px"))
|
||||
args = args.trim() + " --safe-inset-px " + safeInsetPx();
|
||||
// Test phase: FPS readout in the top-left corner. Drop this line for release builds.
|
||||
if (!args.contains("--show-fps"))
|
||||
args = args.trim() + " --show-fps";
|
||||
return args.trim().split("\\s+");
|
||||
}
|
||||
|
||||
/**
|
||||
* Pixels the game UI keeps clear at the left and right edges in landscape: the largest rounded
|
||||
* screen corner (a window hugging a corner is clipped until it is a full radius in) and any
|
||||
* display cutout. The 3D view still fills the screen.
|
||||
*/
|
||||
private int safeInsetPx() {
|
||||
int inset = 0;
|
||||
final Display display = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ? getDisplay()
|
||||
: getWindowManager().getDefaultDisplay();
|
||||
if (display == null)
|
||||
return 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
final int[] positions = { RoundedCorner.POSITION_TOP_LEFT, RoundedCorner.POSITION_TOP_RIGHT,
|
||||
RoundedCorner.POSITION_BOTTOM_LEFT, RoundedCorner.POSITION_BOTTOM_RIGHT };
|
||||
for (int position : positions) {
|
||||
final RoundedCorner corner = display.getRoundedCorner(position);
|
||||
if (corner != null)
|
||||
inset = Math.max(inset, corner.getRadius());
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// The activity may not be landscape yet: any side's cutout can end up left or right.
|
||||
final DisplayCutout cutout = display.getCutout();
|
||||
if (cutout != null)
|
||||
inset = Math.max(inset, Math.max(Math.max(cutout.getSafeInsetLeft(), cutout.getSafeInsetRight()),
|
||||
Math.max(cutout.getSafeInsetTop(), cutout.getSafeInsetBottom())));
|
||||
}
|
||||
Log.i("Metin2Args", "safe inset px: " + inset);
|
||||
return inset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrientationBis(int w, int h, boolean resizable, String hint) {
|
||||
// Enforce landscape-only on mobile devices (sensorLandscape allows flipping 180 deg but never portrait).
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Metin2</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '8.7.3' apply false
|
||||
}
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the native (SDL3 + Vulkan) 40250 client as an Android arm64 APK.
|
||||
# ./android/build.sh [Debug|Release] [--install]
|
||||
# Output: android/app/build/outputs/apk/debug/app-debug.apk
|
||||
# The 1.4 GB Client/ directory is not in the APK; push it once with ./android/push-client.sh.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
REPO="$PWD"
|
||||
|
||||
CONFIG=Release
|
||||
INSTALL=0
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
Debug|Release) CONFIG="$a" ;;
|
||||
--install) INSTALL=1 ;;
|
||||
*) echo "usage: $0 [Debug|Release] [--install]" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
API=24
|
||||
OUT="$REPO/build/android"
|
||||
JOBS="${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)}"
|
||||
SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$HOME/Library/Android/sdk}}"
|
||||
|
||||
# --- NDK: $ANDROID_NDK_HOME, else the installed 27.x, else the newest ---
|
||||
NDK="${ANDROID_NDK_ROOT:-${ANDROID_NDK_HOME:-}}"
|
||||
if [ -z "$NDK" ] && [ -d "$SDK/ndk" ]; then
|
||||
v=$(ls -1 "$SDK/ndk" | grep '^27\.' | sort -V | tail -1 || true)
|
||||
[ -n "$v" ] || v=$(ls -1 "$SDK/ndk" | sort -V | tail -1)
|
||||
NDK="$SDK/ndk/$v"
|
||||
fi
|
||||
[ -f "$NDK/build/cmake/android.toolchain.cmake" ] || { echo "Android NDK not found (set ANDROID_NDK_HOME)" >&2; exit 1; }
|
||||
echo "NDK: $NDK"
|
||||
TOOLCHAIN="$NDK/build/cmake/android.toolchain.cmake"
|
||||
GLSLC="${GLSLC:-$(command -v glslc || true)}"
|
||||
[ -n "$GLSLC" ] || { echo "glslc not found (brew install shaderc)" >&2; exit 1; }
|
||||
|
||||
# --- SDL3 source (Java SDLActivity + libSDL3.so), same release as the macOS build ---
|
||||
SDL_VER=3.4.16
|
||||
SDL_SHA256=7322236cd12090c3eb40b9728be4d49c76f66ad17d04369584d4ecad5cf77c68
|
||||
SDL_SRC="$OUT/_deps/SDL3-$SDL_VER"
|
||||
if [ ! -f "$SDL_SRC/CMakeLists.txt" ]; then
|
||||
mkdir -p "$OUT/_deps"
|
||||
tarball="$OUT/_deps/SDL3-$SDL_VER.tar.gz"
|
||||
curl -fL -o "$tarball" "https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VER/SDL3-$SDL_VER.tar.gz"
|
||||
echo "$SDL_SHA256 $tarball" | shasum -a 256 -c -
|
||||
tar xzf "$tarball" -C "$OUT/_deps"
|
||||
fi
|
||||
# 16 KB page alignment (Android 15+ devices check it): NDK r27 links with max-page-size=16384 when
|
||||
# ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES is on. The stamp rebuilds an SDL3 cached from before the flag.
|
||||
PAGE_SIZES=-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON
|
||||
if [ ! -f "$OUT/sdl3/lib/libSDL3.so" ] || [ ! -f "$OUT/sdl3/.page16k" ]; then
|
||||
rm -rf "$OUT/sdl3-build" "$OUT/sdl3"
|
||||
cmake -S "$SDL_SRC" -B "$OUT/sdl3-build" -G "Unix Makefiles" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN" -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM="android-$API" "$PAGE_SIZES" \
|
||||
-DCMAKE_BUILD_TYPE=Release -DSDL_SHARED=ON -DSDL_STATIC=OFF -DSDL_TEST_LIBRARY=OFF \
|
||||
-DSDL_TESTS=OFF -DSDL_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX="$OUT/sdl3"
|
||||
cmake --build "$OUT/sdl3-build" -j "$JOBS"
|
||||
cmake --install "$OUT/sdl3-build"
|
||||
touch "$OUT/sdl3/.page16k"
|
||||
fi
|
||||
|
||||
# --- libmain.so (src/host + port_platform + embedded Python) and its assets ---
|
||||
cmake -S . -B "$OUT/app-build" -G "Unix Makefiles" -Wno-deprecated \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN" -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM="android-$API" "$PAGE_SIZES" \
|
||||
-DCMAKE_BUILD_TYPE="$CONFIG" -DMT_BUILD_TOOLS=OFF -DBUILD_TESTING=OFF \
|
||||
-DMT_BUILD_NATIVE_RENDER=ON -DSDL3_DIR="$OUT/sdl3/lib/cmake/SDL3" \
|
||||
-DCMAKE_FIND_ROOT_PATH="$OUT/sdl3" -DGLSLC="$GLSLC" >/dev/null
|
||||
cmake --build "$OUT/app-build" --target main mt_native_android_assets -j "$JOBS"
|
||||
|
||||
mkdir -p "$OUT/jniLibs/arm64-v8a"
|
||||
cp -f "$OUT/sdl3/lib/libSDL3.so" "$OUT/app-build/src/host/libmain.so" "$OUT/jniLibs/arm64-v8a/"
|
||||
STRIP=$(ls -1d "$NDK"/toolchains/llvm/prebuilt/*/bin/llvm-strip | head -1)
|
||||
"$STRIP" --strip-unneeded "$OUT/jniLibs/arm64-v8a/libSDL3.so" "$OUT/jniLibs/arm64-v8a/libmain.so"
|
||||
|
||||
# --- APK ---
|
||||
if [ -z "${JAVA_HOME:-}" ]; then
|
||||
for j in /opt/homebrew/opt/openjdk@21 /opt/homebrew/opt/openjdk@17; do
|
||||
[ -x "$j/bin/java" ] && { export JAVA_HOME="$j"; break; }
|
||||
done
|
||||
fi
|
||||
export ANDROID_HOME="$SDK"
|
||||
variant=Debug # debug-signed either way (sideload); CONFIG picks the native optimisation level
|
||||
(cd android && ./gradlew --quiet "assemble$variant")
|
||||
APK="$REPO/android/app/build/outputs/apk/debug/app-debug.apk"
|
||||
ls -la "$APK"
|
||||
|
||||
if [ "$INSTALL" = 1 ]; then
|
||||
adb install -r "$APK"
|
||||
fi
|
||||
@@ -0,0 +1,2 @@
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
android.useAndroidX=false
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|grep -E -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|grep -E -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copy the 40250 Client/ directory into the Android app's private files/ (SDL_GetPrefPath), where
|
||||
# src/host looks for files/Client/pack/Index. Needs the debug APK installed (run-as).
|
||||
# ./android/push-client.sh [--locale XX] [CLIENT_DIR]
|
||||
# --locale XX writes the pushed locale.cfg from CLIENT_DIR/locale_XX.cfg. --locale zh defaults
|
||||
# CLIENT_DIR to build/zh-locale/Client (scripts/build_zh_locale.py), whose pack/ registers locale_zh.
|
||||
# Symbolic links in CLIENT_DIR (the zh client links the original packs) are copied as their targets.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
PKG=org.metin2port.client
|
||||
LOCALE=""
|
||||
if [ "${1:-}" = "--locale" ]; then
|
||||
LOCALE="${2:?--locale needs a locale code}"
|
||||
shift 2
|
||||
fi
|
||||
if [ "$LOCALE" = zh ]; then
|
||||
CLIENT="${1:-build/zh-locale/Client}"
|
||||
else
|
||||
CLIENT="${1:-Client}"
|
||||
fi
|
||||
[ -f "$CLIENT/pack/Index" ] || { echo "$CLIENT/pack/Index not found" >&2; exit 1; }
|
||||
# The archive's top directory is basename(CLIENT DIR); the app reads files/Client.
|
||||
[ "$(basename "$CLIENT")" = Client ] || { echo "CLIENT_DIR must be named Client" >&2; exit 1; }
|
||||
if [ -n "$LOCALE" ]; then
|
||||
[ -f "$CLIENT/locale_$LOCALE.cfg" ] || { echo "$CLIENT/locale_$LOCALE.cfg not found" >&2; exit 1; }
|
||||
pack="$(cut -d' ' -f3 < "$CLIENT/locale_$LOCALE.cfg" | tr -d '\r\n')"
|
||||
tr -d '\r' < "$CLIENT/pack/Index" | grep -qxF "locale_$pack" ||
|
||||
{ echo "locale_$pack is not registered in $CLIENT/pack/Index" >&2; exit 1; }
|
||||
fi
|
||||
TMP=/data/local/tmp/mt-client.tar
|
||||
echo "packing $CLIENT ..."
|
||||
tar -h -C "$(dirname "$CLIENT")" -cf - "$(basename "$CLIENT")" | adb shell "cat > $TMP"
|
||||
echo "unpacking into $PKG files/ ..."
|
||||
adb shell "run-as $PKG sh -c 'mkdir -p files && rm -rf files/Client && cd files && tar -xf $TMP' && rm -f $TMP"
|
||||
if [ -n "$LOCALE" ]; then
|
||||
adb shell "run-as $PKG sh -c 'cp files/Client/locale_$LOCALE.cfg files/Client/locale.cfg'"
|
||||
echo "locale.cfg: $(adb shell "run-as $PKG cat files/Client/locale.cfg")"
|
||||
fi
|
||||
adb shell "run-as $PKG ls files/Client/pack/Index" >/dev/null && echo "done: files/Client"
|
||||
@@ -0,0 +1,15 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = 'metin2-native'
|
||||
include ':app'
|
||||
Reference in New Issue
Block a user