After building the APK, android/build.sh now picks the attached device (ANDROID_SERIAL for several), installs the APK (reinstalling on a signature mismatch), syncs the zh client into files/Client and, with --launch, wakes the phone and starts the app. Without a device it only builds. push-client.sh is now incremental (android/sync_client.py): the device keeps files/Client/.mt_sync (size, mtime, path of what the last sync wrote), so later syncs send only new and changed files as one tar streamed through `adb shell -T run-as` (no copy in /data/local/tmp) and delete the ones gone locally. Only game resources are synced; settings and logs the app writes there are kept. --locale zh rebuilds build/zh-locale/Client first when its inputs changed. android/adb_env.sh picks adb and falls back to server port 5038 when :5037 cannot bind. Verified against a fake adb device: first sync (1.4 GB, byte-identical), no-op resync, changed files, deletions, locale switch, --full, the install/launch path and the no-device path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
138 lines
6.1 KiB
Bash
Executable File
138 lines
6.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build the native (SDL3 + Vulkan) 40250 client as an Android arm64 APK and, when a phone is attached,
|
|
# install it and bring its game files up to date, so the app is ready to play.
|
|
# ./android/build.sh [Debug|Release] [--no-deploy] [--no-push] [--locale XX] [--full-push] [--launch]
|
|
# Output: android/app/build/outputs/apk/debug/app-debug.apk
|
|
# Deploy (skipped when no device is ready; ANDROID_SERIAL picks one of several):
|
|
# adb install -r, then ./android/push-client.sh --locale XX (default zh), which sends only the game
|
|
# files changed since the last push (the first push is the whole 1.4 GB client). --no-push installs
|
|
# only, --full-push resends everything, --launch wakes the phone and starts the app afterwards.
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
REPO="$PWD"
|
|
|
|
CONFIG=Release
|
|
DEPLOY=1
|
|
PUSH=1
|
|
LOCALE=zh
|
|
FULL_PUSH=0
|
|
LAUNCH=0
|
|
usage="usage: $0 [Debug|Release] [--no-deploy] [--no-push] [--locale XX] [--full-push] [--launch]"
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
Debug|Release) CONFIG="$1" ;;
|
|
--install) ;; # the default now; kept for old command lines
|
|
--no-deploy) DEPLOY=0 ;;
|
|
--no-push) PUSH=0 ;;
|
|
--locale) LOCALE="${2:?--locale needs a locale code}"; shift ;;
|
|
--full-push) FULL_PUSH=1 ;;
|
|
--launch) LAUNCH=1 ;;
|
|
*) echo "$usage" >&2; exit 2 ;;
|
|
esac
|
|
shift
|
|
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"
|
|
|
|
[ "$DEPLOY" = 1 ] || exit 0
|
|
. android/adb_env.sh
|
|
if ! adb_device; then
|
|
echo "APK built; install and push skipped. Attach the phone and rerun (or ./android/push-client.sh)."
|
|
exit 0
|
|
fi
|
|
echo "device: $DEVICE ($("$ADB" shell getprop ro.product.model | tr -d '\r'))"
|
|
if ! out=$("$ADB" install -r "$APK" 2>&1); then
|
|
echo "$out" >&2
|
|
case "$out" in
|
|
*INSTALL_FAILED_UPDATE_INCOMPATIBLE*|*INSTALL_FAILED_VERSION_DOWNGRADE*)
|
|
# Signed by another machine's debug key (or a newer build): only a reinstall helps. That drops the
|
|
# app's data, so the push below starts over with the whole client.
|
|
echo "reinstalling $PKG (the installed copy has another signature; its data is removed)"
|
|
"$ADB" uninstall "$PKG" >/dev/null
|
|
"$ADB" install "$APK" ;;
|
|
*)
|
|
echo "install failed; on some phones USB installs must be allowed in developer options and confirmed on screen" >&2
|
|
exit 1 ;;
|
|
esac
|
|
fi
|
|
echo "installed $PKG"
|
|
if [ "$PUSH" = 1 ]; then
|
|
push_args=(--locale "$LOCALE")
|
|
[ "$FULL_PUSH" = 1 ] && push_args+=(--full)
|
|
python3 android/sync_client.py "${push_args[@]}"
|
|
fi
|
|
if [ "$LAUNCH" = 1 ]; then
|
|
# The app exits with a Vulkan surface error when started behind the lock screen.
|
|
"$ADB" shell input keyevent KEYCODE_WAKEUP
|
|
if "$ADB" shell dumpsys window | grep -qE 'mDreamingLockscreen=true|isKeyguardShowing=true|mKeyguardShowing=true'; then
|
|
echo "the phone is locked: unlock it, then start Metin2 from the launcher" >&2
|
|
else
|
|
"$ADB" shell am start -n "$PKG/.MainActivity" >/dev/null && echo "started $PKG"
|
|
fi
|
|
fi
|