diff --git a/.gitignore b/.gitignore
index 3f64cf50..7c533c48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -78,3 +78,10 @@ __pycache__/
# Written by 40250 uitaskbar.py (SaveMouseButtonSettings) into the working directory at runtime.
/project/mouse.cfg
+
+# native Android client (./build-android-native.sh): SDL3 download, NDK builds, Gradle output
+/build-android-native/
+/android-native/.gradle/
+/android-native/app/build/
+/android-native/build/
+/android-native/local.properties
diff --git a/android-native/app/build.gradle b/android-native/app/build.gradle
new file mode 100644
index 00000000..01dc2201
--- /dev/null
+++ b/android-native/app/build.gradle
@@ -0,0 +1,39 @@
+// SDLActivity shell around native_render (libmain.so). The native libraries, the SDL Java sources
+// and the assets come from ../build-android-native, produced by ../build-android-native.sh.
+plugins {
+ id 'com.android.application'
+}
+
+def nativeBuild = file("${rootDir}/../build-android-native")
+
+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/native_render/android-assets"]
+ }
+ packagingOptions {
+ jniLibs { useLegacyPackaging = true }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
+ }
+ lint { abortOnError = false }
+}
diff --git a/android-native/app/src/main/AndroidManifest.xml b/android-native/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..55ee99cb
--- /dev/null
+++ b/android-native/app/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-native/app/src/main/java/org/metin2port/client/MainActivity.java b/android-native/app/src/main/java/org/metin2port/client/MainActivity.java
new file mode 100644
index 00000000..764f3497
--- /dev/null
+++ b/android-native/app/src/main/java/org/metin2port/client/MainActivity.java
@@ -0,0 +1,27 @@
+package org.metin2port.client;
+
+import android.content.Intent;
+
+import org.libsdl.app.SDLActivity;
+
+/**
+ * Runs native_render'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"
+ * Without arguments the client runs against the in-process fake server once Client/ is in files/.
+ */
+public class MainActivity extends SDLActivity {
+ @Override
+ protected String[] getLibraries() {
+ return new String[] { "SDL3", "main" };
+ }
+
+ @Override
+ protected String[] getArguments() {
+ Intent intent = getIntent();
+ String args = intent != null ? intent.getStringExtra("args") : null;
+ if (args == null || args.trim().isEmpty())
+ return new String[0];
+ return args.trim().split("\\s+");
+ }
+}
diff --git a/android-native/app/src/main/res/values/strings.xml b/android-native/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..515878cb
--- /dev/null
+++ b/android-native/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ Metin2
+
diff --git a/android-native/build.gradle b/android-native/build.gradle
new file mode 100644
index 00000000..5b7ccba7
--- /dev/null
+++ b/android-native/build.gradle
@@ -0,0 +1,3 @@
+plugins {
+ id 'com.android.application' version '8.7.3' apply false
+}
diff --git a/android-native/gradle.properties b/android-native/gradle.properties
new file mode 100644
index 00000000..d24c4a10
--- /dev/null
+++ b/android-native/gradle.properties
@@ -0,0 +1,2 @@
+org.gradle.jvmargs=-Xmx2048m
+android.useAndroidX=false
diff --git a/android-native/gradle/wrapper/gradle-wrapper.jar b/android-native/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..2b338a93
Binary files /dev/null and b/android-native/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/android-native/gradle/wrapper/gradle-wrapper.properties b/android-native/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..b5c11a6f
--- /dev/null
+++ b/android-native/gradle/wrapper/gradle-wrapper.properties
@@ -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
diff --git a/android-native/gradlew b/android-native/gradlew
new file mode 100755
index 00000000..3427607f
--- /dev/null
+++ b/android-native/gradlew
@@ -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 "$@"
diff --git a/android-native/settings.gradle b/android-native/settings.gradle
new file mode 100644
index 00000000..65b4096d
--- /dev/null
+++ b/android-native/settings.gradle
@@ -0,0 +1,15 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = 'metin2-native'
+include ':app'
diff --git a/build-android-native.sh b/build-android-native.sh
new file mode 100755
index 00000000..ba2d5e85
--- /dev/null
+++ b/build-android-native.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+# Build the native (SDL3 + Vulkan) 40250 client as an Android arm64 APK.
+# ./build-android-native.sh [Debug|Release] [--install]
+# Output: android-native/app/build/outputs/apk/debug/app-debug.apk
+# The 1.4 GB Client/ directory is not in the APK; push it once with ./push-android-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-native"
+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
+if [ ! -f "$OUT/sdl3/lib/libSDL3.so" ]; then
+ cmake -S "$SDL_SRC" -B "$OUT/sdl3-build" -G "Unix Makefiles" \
+ -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN" -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM="android-$API" \
+ -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"
+fi
+
+# --- libmain.so (native_render + 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" \
+ -DCMAKE_BUILD_TYPE="$CONFIG" -DMTGODOT_BUILD_TOOLS=OFF -DBUILD_TESTING=OFF \
+ -DMT_BUILD_NATIVE_RENDER_PROTOTYPE=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/native_render/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-native && ./gradlew --quiet "assemble$variant")
+APK="$REPO/android-native/app/build/outputs/apk/debug/app-debug.apk"
+ls -la "$APK"
+
+if [ "$INSTALL" = 1 ]; then
+ adb install -r "$APK"
+fi
diff --git a/extension/src/port/CMakeLists.txt b/extension/src/port/CMakeLists.txt
index 406a2cd6..1fb3705d 100644
--- a/extension/src/port/CMakeLists.txt
+++ b/extension/src/port/CMakeLists.txt
@@ -47,6 +47,11 @@ if(NOT MSVC)
set(MT_PORT_WARNING_FLAGS -Wno-unknown-pragmas -Wno-format-security)
endif()
target_compile_options(port_logic PUBLIC ${MT_PORT_WARNING_FLAGS})
+# 40250 headers keep `#pragma comment(lib, "d3d8.lib")` etc. verbatim; clang turns them into ELF
+# .deplibs that lld then fails to resolve (ld64 on Apple ignores unknown ones).
+if(CMAKE_SYSTEM_NAME MATCHES "Android|Linux")
+ target_link_options(port_logic INTERFACE LINKER:--no-dependent-libraries)
+endif()
target_compile_features(port_logic PUBLIC cxx_std_20)
# Armadillo nanomite markers (StdAfx.h `__asm _emit` jumps over a signature): x86-32 MSVC inline asm that no
# target here accepts, and a no-op without the Armadillo wrapper, so they expand to nothing.
diff --git a/native_render/README.md b/native_render/README.md
index 15b59cd2..0b2b7542 100644
--- a/native_render/README.md
+++ b/native_render/README.md
@@ -32,21 +32,28 @@ returned by SDL's `SDL_GetBasePath`). `MT_PYTHON_STDLIB` can override the zip pa
## Android integration
-With an Android toolchain and SDL3 Android AAR/Prefab available to CMake, the
-same option builds `libmain.so` for SDLActivity. The `mt_native_android_assets`
-target prepares `native.vert.spv`, `native.frag.spv`, and `python27.zip` under
-`/native_render/android-assets` (`python27.zip` is included when the
-embedded Python target is enabled); include that directory in the SDL app's
-`assets.srcDirs`. The app must allow network access for login. The 40250 `Client`
-directory must be placed at `SDL_GetPrefPath("mtgodot", "native-render")/Client`,
-with `pack/Index` present, before live mode starts. The Godot APK is a separate
-application and does not launch this SDL renderer.
+`./build-android-native.sh [Debug|Release] [--install]` builds the arm64 APK
+(`android-native/app/build/outputs/apk/debug/app-debug.apk`, package
+`org.metin2port.client`): it downloads SDL 3.4.16 (sha256-checked) into
+`build-android-native/_deps`, builds `libSDL3.so`, cross-builds `libmain.so`
+(this target + `port_platform` + embedded Python) at API 24, and wraps both in the
+SDLActivity shell under `android-native/` (`MainActivity` passes the launch intent's
+`args` extra as argv). `mt_native_android_assets` supplies `native.vert.spv`,
+`native.frag.spv` and `python27.zip` as APK assets.
-The standalone `arm64-v8a` renderer cross-builds at Android API 24. The full
-live-client cross-build currently stops in `extension/src/port/common/Win32Crt.cpp`:
-the NDK exposes `` at API 24 but does not declare `iconv` until API 28.
-This is a port-runtime prerequisite for an Android live-client APK; raising the
-minimum API level is not assumed here.
+The 40250 `Client` directory is not packaged. `./push-android-client.sh` copies it
+to the app's `files/Client` (`SDL_GetPrefPath`) with `run-as`, so it needs the
+debug-signed APK installed first. Without arguments the app runs against the
+in-process fake server; for a real server:
+
+```sh
+adb shell am start -n org.metin2port.client/.MainActivity \
+ --es args "--live-server HOST:AUTH_PORT:GAME_PORT --login-screen"
+```
+
+Code page conversion does not use iconv: `extension/src/codepage` carries Microsoft's
+WindowsBestFit tables for 874/932/936/949/950/1250-1258 (regenerate with
+`python3 script/gen_codepage_tables.py`), so Android converts the same as macOS.
The Vulkan portability enumeration extension is selected only when advertised
by the loader. DDS, TGA, and memory textures retain their existing decoders;
diff --git a/push-android-client.sh b/push-android-client.sh
new file mode 100755
index 00000000..7c9eb7c4
--- /dev/null
+++ b/push-android-client.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+# Copy the 40250 Client/ directory into the Android app's private files/ (SDL_GetPrefPath), where
+# native_render looks for files/Client/pack/Index. Needs the debug APK installed (run-as).
+# ./push-android-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 (script/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"