555 lines
17 KiB
Bash
Executable File
555 lines
17 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# rebuild-apk.sh — Rebuild and sign a decoded APK directory
|
|
#
|
|
# Pipeline: apktool b → zipalign (optional) → sign → verify
|
|
#
|
|
# Exit codes:
|
|
# 0 — success
|
|
# 1 — error (build failed, signing failed)
|
|
# 2 — manual action needed (missing tools)
|
|
set -euo pipefail
|
|
|
|
# Ensure user-local bin is in PATH (install-dep.sh installs tools there)
|
|
if [[ -d "$HOME/.local/bin" ]] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
|
|
export PATH="$HOME/.local/bin:$PATH"
|
|
fi
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: rebuild-apk.sh <decoded-dir> [OPTIONS]
|
|
|
|
Rebuild an apktool-decoded APK directory back into a signed APK.
|
|
If the decoded dir contains .xapk-origin/ (from decode-apk.sh), automatically
|
|
reassembles a complete XAPK with all split APKs re-signed.
|
|
|
|
Arguments:
|
|
<decoded-dir> Path to the apktool-decoded APK directory
|
|
|
|
Options:
|
|
-o, --output <file> Output path (default: <decoded-dir>-neutralized.apk/.xapk)
|
|
--auto-keystore Auto-detect best keystore: ~/.android/debug.keystore,
|
|
then previous .neutralizer-debug.keystore, then generate new
|
|
--debug-key Sign with an auto-generated debug keystore (default)
|
|
--keystore <file> Path to a custom keystore file
|
|
--key-alias <alias> Key alias within the keystore (default: key0)
|
|
--key-pass <password> Key password (default: android)
|
|
--store-pass <password> Keystore password (default: android)
|
|
--no-sign Skip signing (output unsigned APK)
|
|
--no-res Skip resource recompilation (apktool b --no-res)
|
|
--zipalign Run zipalign before signing (recommended, default if available)
|
|
--no-zipalign Skip zipalign
|
|
-h, --help Show this help message
|
|
|
|
Output:
|
|
BUILD_OK:<output-apk>
|
|
BUILD_WARNING:Resources were not recompiled (--no-res fallback)
|
|
SIGN_OK:<output-apk>
|
|
VERIFY_OK:<output-apk>
|
|
KEYSTORE_USED:<path>
|
|
KEYSTORE_SOURCE:debug-standard|debug-previous|debug-generated|custom
|
|
SPLIT_SIGNED:<filename> (XAPK only)
|
|
XAPK_ASSEMBLED:<output-xapk> (XAPK only)
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# =====================================================================
|
|
# Argument parsing
|
|
# =====================================================================
|
|
|
|
DECODED_DIR=""
|
|
OUTPUT=""
|
|
USE_DEBUG_KEY=true
|
|
USE_AUTO_KEYSTORE=false
|
|
KEYSTORE=""
|
|
KEY_ALIAS="key0"
|
|
KEY_PASS="android"
|
|
STORE_PASS="android"
|
|
DO_SIGN=true
|
|
DO_ZIPALIGN=true
|
|
NO_ZIPALIGN=false
|
|
FORCE_NO_RES=false
|
|
BUILD_USED_NO_RES=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-o|--output)
|
|
shift
|
|
if [[ $# -eq 0 ]]; then echo "Error: --output requires a file argument" >&2; exit 1; fi
|
|
OUTPUT="$1"; shift ;;
|
|
--auto-keystore) USE_AUTO_KEYSTORE=true; USE_DEBUG_KEY=false; shift ;;
|
|
--debug-key) USE_DEBUG_KEY=true; USE_AUTO_KEYSTORE=false; shift ;;
|
|
--keystore)
|
|
shift
|
|
if [[ $# -eq 0 ]]; then echo "Error: --keystore requires a file argument" >&2; exit 1; fi
|
|
KEYSTORE="$1"; USE_DEBUG_KEY=false; shift ;;
|
|
--key-alias)
|
|
shift
|
|
if [[ $# -eq 0 ]]; then echo "Error: --key-alias requires an argument" >&2; exit 1; fi
|
|
KEY_ALIAS="$1"; shift ;;
|
|
--key-pass)
|
|
shift
|
|
if [[ $# -eq 0 ]]; then echo "Error: --key-pass requires an argument" >&2; exit 1; fi
|
|
KEY_PASS="$1"; shift ;;
|
|
--store-pass)
|
|
shift
|
|
if [[ $# -eq 0 ]]; then echo "Error: --store-pass requires an argument" >&2; exit 1; fi
|
|
STORE_PASS="$1"; shift ;;
|
|
--no-sign) DO_SIGN=false; shift ;;
|
|
--no-res) FORCE_NO_RES=true; shift ;;
|
|
--zipalign) DO_ZIPALIGN=true; shift ;;
|
|
--no-zipalign) NO_ZIPALIGN=true; DO_ZIPALIGN=false; shift ;;
|
|
-h|--help) usage ;;
|
|
-*) echo "Error: Unknown option $1" >&2; usage ;;
|
|
*) DECODED_DIR="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$DECODED_DIR" ]]; then
|
|
echo "Error: No decoded directory specified." >&2
|
|
usage
|
|
fi
|
|
|
|
if [[ ! -d "$DECODED_DIR" ]]; then
|
|
echo "Error: Directory not found: $DECODED_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Auto-detect XAPK origin
|
|
IS_XAPK=false
|
|
XAPK_ORIGIN_DIR="$DECODED_DIR/.xapk-origin"
|
|
if [[ -f "$XAPK_ORIGIN_DIR/metadata.json" ]]; then
|
|
IS_XAPK=true
|
|
fi
|
|
|
|
# Default output name — .xapk if original was XAPK, .apk otherwise
|
|
if [[ -z "$OUTPUT" ]]; then
|
|
local_dir="${DECODED_DIR%/}"
|
|
if [[ "$IS_XAPK" == true ]]; then
|
|
OUTPUT="${local_dir}-neutralized.xapk"
|
|
else
|
|
OUTPUT="${local_dir}-neutralized.apk"
|
|
fi
|
|
fi
|
|
|
|
# =====================================================================
|
|
# Tool checks
|
|
# =====================================================================
|
|
|
|
info() { echo "[INFO] $*"; }
|
|
ok() { echo "[OK] $*"; }
|
|
fail() { echo "[FAIL] $*" >&2; }
|
|
|
|
if ! command -v apktool &>/dev/null; then
|
|
fail "apktool is not installed. Run: install-dep.sh apktool"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v java &>/dev/null; then
|
|
fail "java is not installed. Run: install-dep.sh java"
|
|
exit 1
|
|
fi
|
|
|
|
# Determine signing tool
|
|
SIGNER=""
|
|
if [[ "$DO_SIGN" == true ]]; then
|
|
if command -v apksigner &>/dev/null; then
|
|
SIGNER="apksigner"
|
|
elif command -v jarsigner &>/dev/null; then
|
|
SIGNER="jarsigner"
|
|
else
|
|
fail "Neither apksigner nor jarsigner found. Install apksigner or use --no-sign."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# XAPK requires apksigner (v2/v3 signatures needed for split APKs on Android 7+)
|
|
if [[ "$IS_XAPK" == true ]] && [[ "$DO_SIGN" == true ]] && [[ "$SIGNER" != "apksigner" ]]; then
|
|
fail "XAPK rebuild requires apksigner for APK Signature Scheme v2/v3."
|
|
echo " jarsigner only supports v1 signatures, which do not work with split APKs on Android 7+." >&2
|
|
echo " Install apksigner (part of Android SDK build-tools) or use --no-sign." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# XAPK rebuild requires zip
|
|
if [[ "$IS_XAPK" == true ]]; then
|
|
if ! command -v zip &>/dev/null; then
|
|
fail "XAPK rebuild requires 'zip' command to assemble the final XAPK."
|
|
echo " Install zip: apt install zip / brew install zip" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Check zipalign availability
|
|
ZIPALIGN_CMD=""
|
|
if [[ "$DO_ZIPALIGN" == true ]] && [[ "$NO_ZIPALIGN" == false ]]; then
|
|
if command -v zipalign &>/dev/null; then
|
|
ZIPALIGN_CMD="zipalign"
|
|
else
|
|
# Check Android SDK
|
|
for sdk_dir in "${ANDROID_HOME:-}" "${ANDROID_SDK_ROOT:-}"; do
|
|
if [[ -n "$sdk_dir" ]] && [[ -d "$sdk_dir/build-tools" ]]; then
|
|
latest_bt=$(ls -1 "$sdk_dir/build-tools" 2>/dev/null | sort -V | tail -1)
|
|
if [[ -n "$latest_bt" ]] && [[ -f "$sdk_dir/build-tools/$latest_bt/zipalign" ]]; then
|
|
ZIPALIGN_CMD="$sdk_dir/build-tools/$latest_bt/zipalign"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
if [[ -z "$ZIPALIGN_CMD" ]]; then
|
|
info "zipalign not found — skipping alignment (APK will still work)"
|
|
DO_ZIPALIGN=false
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# =====================================================================
|
|
# Step 1: Build APK with apktool
|
|
# =====================================================================
|
|
|
|
echo "=== Rebuilding APK ==="
|
|
echo "Source: $DECODED_DIR"
|
|
echo "Output: $OUTPUT"
|
|
echo
|
|
|
|
info "Running apktool build..."
|
|
BUILT_APK="$DECODED_DIR/dist/$(basename "$DECODED_DIR").apk"
|
|
|
|
build_apk() {
|
|
local no_res_flag="${1:-}"
|
|
# Clean previous build artifacts
|
|
rm -rf "$DECODED_DIR/dist/" 2>/dev/null || true
|
|
if apktool b $no_res_flag "$DECODED_DIR" 2>&1; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
if [[ "$FORCE_NO_RES" == true ]]; then
|
|
# User explicitly requested --no-res
|
|
if ! build_apk "--no-res"; then
|
|
fail "apktool build failed (with --no-res)."
|
|
echo "Tip: If this is a framework error, try: rm -f ~/.local/share/apktool/framework/1.apk"
|
|
exit 1
|
|
fi
|
|
BUILD_USED_NO_RES=true
|
|
else
|
|
# First attempt: normal build
|
|
if ! build_apk ""; then
|
|
info "Build failed — retrying with --no-res (skipping resource recompilation)..."
|
|
if ! build_apk "--no-res"; then
|
|
fail "apktool build failed (both normal and --no-res)."
|
|
echo "Tip: If this is a framework error, try: rm -f ~/.local/share/apktool/framework/1.apk"
|
|
exit 1
|
|
fi
|
|
BUILD_USED_NO_RES=true
|
|
fi
|
|
fi
|
|
|
|
# apktool outputs to dist/ inside the decoded dir
|
|
if [[ ! -f "$BUILT_APK" ]]; then
|
|
# Try finding any APK in dist/
|
|
BUILT_APK=$(find "$DECODED_DIR/dist/" -name "*.apk" -type f | head -1)
|
|
if [[ -z "$BUILT_APK" ]] || [[ ! -f "$BUILT_APK" ]]; then
|
|
fail "Built APK not found in $DECODED_DIR/dist/"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
ok "APK built: $BUILT_APK"
|
|
echo "BUILD_OK:$BUILT_APK"
|
|
if [[ "$BUILD_USED_NO_RES" == true ]]; then
|
|
echo "BUILD_WARNING:Resources were not recompiled (--no-res fallback)"
|
|
fi
|
|
|
|
# =====================================================================
|
|
# Step 2: Zipalign (optional)
|
|
# =====================================================================
|
|
|
|
ALIGNED_APK="$BUILT_APK"
|
|
if [[ "$DO_ZIPALIGN" == true ]] && [[ -n "$ZIPALIGN_CMD" ]]; then
|
|
info "Running zipalign..."
|
|
ALIGNED_APK="${BUILT_APK%.apk}-aligned.apk"
|
|
if "$ZIPALIGN_CMD" -f 4 "$BUILT_APK" "$ALIGNED_APK"; then
|
|
ok "Zipaligned: $ALIGNED_APK"
|
|
# Remove unaligned APK
|
|
rm -f "$BUILT_APK"
|
|
else
|
|
info "Zipalign failed — continuing with unaligned APK"
|
|
ALIGNED_APK="$BUILT_APK"
|
|
fi
|
|
fi
|
|
|
|
# =====================================================================
|
|
# Step 3: Sign APK
|
|
# =====================================================================
|
|
|
|
if [[ "$DO_SIGN" == false ]]; then
|
|
if [[ "$IS_XAPK" == true ]]; then
|
|
# For unsigned XAPK, just output the base APK — cannot assemble unsigned XAPK
|
|
cp "$ALIGNED_APK" "$OUTPUT"
|
|
ok "Unsigned base APK saved to: $OUTPUT"
|
|
echo "BUILD_OK:$OUTPUT"
|
|
echo
|
|
echo "WARNING: APK is unsigned and cannot be installed without signing."
|
|
echo " XAPK assembly skipped (split APKs require signing for Android 7+)."
|
|
echo " Split APKs are preserved in: $XAPK_ORIGIN_DIR/splits/"
|
|
else
|
|
cp "$ALIGNED_APK" "$OUTPUT"
|
|
ok "Unsigned APK saved to: $OUTPUT"
|
|
echo "BUILD_OK:$OUTPUT"
|
|
echo
|
|
echo "WARNING: APK is unsigned and cannot be installed without signing."
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
# Resolve keystore
|
|
KEYSTORE_SOURCE=""
|
|
|
|
if [[ "$USE_AUTO_KEYSTORE" == true ]]; then
|
|
# Priority 1: Android SDK standard debug keystore
|
|
ANDROID_DEBUG_KS="$HOME/.android/debug.keystore"
|
|
if [[ -f "$ANDROID_DEBUG_KS" ]]; then
|
|
KEYSTORE="$ANDROID_DEBUG_KS"
|
|
KEY_ALIAS="androiddebugkey"
|
|
KEY_PASS="android"
|
|
STORE_PASS="android"
|
|
KEYSTORE_SOURCE="debug-standard"
|
|
info "Using Android SDK debug keystore: $KEYSTORE"
|
|
fi
|
|
|
|
# Priority 2: Previous neutralizer debug keystore
|
|
if [[ -z "$KEYSTORE_SOURCE" ]]; then
|
|
PREV_DEBUG_KS="$DECODED_DIR/.neutralizer-debug.keystore"
|
|
if [[ -f "$PREV_DEBUG_KS" ]]; then
|
|
KEYSTORE="$PREV_DEBUG_KS"
|
|
KEYSTORE_SOURCE="debug-previous"
|
|
info "Using previous neutralizer debug keystore: $KEYSTORE"
|
|
fi
|
|
fi
|
|
|
|
# Priority 3: Generate new debug keystore (fallback)
|
|
if [[ -z "$KEYSTORE_SOURCE" ]]; then
|
|
KEYSTORE="$DECODED_DIR/.neutralizer-debug.keystore"
|
|
info "Generating debug keystore..."
|
|
keytool -genkeypair \
|
|
-keystore "$KEYSTORE" \
|
|
-alias "$KEY_ALIAS" \
|
|
-keyalg RSA \
|
|
-keysize 2048 \
|
|
-validity 10000 \
|
|
-storepass "$STORE_PASS" \
|
|
-keypass "$KEY_PASS" \
|
|
-dname "CN=SDK Neutralizer Debug Key, OU=Debug, O=Debug, L=Unknown, ST=Unknown, C=US" \
|
|
2>/dev/null
|
|
ok "Debug keystore generated: $KEYSTORE"
|
|
KEYSTORE_SOURCE="debug-generated"
|
|
fi
|
|
elif [[ "$USE_DEBUG_KEY" == true ]]; then
|
|
KEYSTORE="$DECODED_DIR/.neutralizer-debug.keystore"
|
|
if [[ ! -f "$KEYSTORE" ]]; then
|
|
info "Generating debug keystore..."
|
|
keytool -genkeypair \
|
|
-keystore "$KEYSTORE" \
|
|
-alias "$KEY_ALIAS" \
|
|
-keyalg RSA \
|
|
-keysize 2048 \
|
|
-validity 10000 \
|
|
-storepass "$STORE_PASS" \
|
|
-keypass "$KEY_PASS" \
|
|
-dname "CN=SDK Neutralizer Debug Key, OU=Debug, O=Debug, L=Unknown, ST=Unknown, C=US" \
|
|
2>/dev/null
|
|
ok "Debug keystore generated: $KEYSTORE"
|
|
fi
|
|
KEYSTORE_SOURCE="debug-generated"
|
|
else
|
|
# Custom keystore provided via --keystore
|
|
KEYSTORE_SOURCE="custom"
|
|
fi
|
|
|
|
if [[ ! -f "$KEYSTORE" ]]; then
|
|
fail "Keystore not found: $KEYSTORE"
|
|
exit 1
|
|
fi
|
|
|
|
echo "KEYSTORE_USED:$KEYSTORE"
|
|
echo "KEYSTORE_SOURCE:$KEYSTORE_SOURCE"
|
|
|
|
info "Signing APK with $SIGNER..."
|
|
|
|
if [[ "$SIGNER" == "apksigner" ]]; then
|
|
apksigner sign \
|
|
--ks "$KEYSTORE" \
|
|
--ks-key-alias "$KEY_ALIAS" \
|
|
--ks-pass "pass:$STORE_PASS" \
|
|
--key-pass "pass:$KEY_PASS" \
|
|
--out "$OUTPUT" \
|
|
"$ALIGNED_APK"
|
|
elif [[ "$SIGNER" == "jarsigner" ]]; then
|
|
# jarsigner signs in-place, so copy first
|
|
cp "$ALIGNED_APK" "$OUTPUT"
|
|
jarsigner \
|
|
-keystore "$KEYSTORE" \
|
|
-storepass "$STORE_PASS" \
|
|
-keypass "$KEY_PASS" \
|
|
-signedjar "$OUTPUT" \
|
|
"$ALIGNED_APK" \
|
|
"$KEY_ALIAS"
|
|
fi
|
|
|
|
if [[ ! -f "$OUTPUT" ]]; then
|
|
fail "Signed APK not found at: $OUTPUT"
|
|
exit 1
|
|
fi
|
|
|
|
ok "APK signed: $OUTPUT"
|
|
echo "SIGN_OK:$OUTPUT"
|
|
|
|
# Clean up intermediate files
|
|
rm -f "$ALIGNED_APK" 2>/dev/null || true
|
|
|
|
# =====================================================================
|
|
# Step 4: Verify signature
|
|
# =====================================================================
|
|
|
|
info "Verifying signature..."
|
|
|
|
if [[ "$SIGNER" == "apksigner" ]]; then
|
|
if apksigner verify "$OUTPUT" 2>/dev/null; then
|
|
ok "Signature verified (apksigner)"
|
|
echo "VERIFY_OK:$OUTPUT"
|
|
else
|
|
info "Signature verification returned warnings (may still be installable)"
|
|
fi
|
|
elif [[ "$SIGNER" == "jarsigner" ]]; then
|
|
if jarsigner -verify "$OUTPUT" 2>/dev/null; then
|
|
ok "Signature verified (jarsigner)"
|
|
echo "VERIFY_OK:$OUTPUT"
|
|
else
|
|
info "Signature verification returned warnings (may still be installable)"
|
|
fi
|
|
fi
|
|
|
|
# =====================================================================
|
|
# Step 5: XAPK assembly (if original was XAPK)
|
|
# =====================================================================
|
|
|
|
if [[ "$IS_XAPK" == true ]] && [[ "$DO_SIGN" == true ]]; then
|
|
echo
|
|
echo "=== Assembling XAPK ==="
|
|
|
|
XAPK_WORKDIR=$(mktemp -d "${TMPDIR:-/tmp}/xapk-rebuild-XXXXXX")
|
|
xapk_cleanup() { rm -rf "$XAPK_WORKDIR"; }
|
|
trap xapk_cleanup EXIT
|
|
|
|
# Read base APK name from metadata
|
|
BASE_APK_NAME=$(sed -n 's/.*"base_apk"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$XAPK_ORIGIN_DIR/metadata.json" | head -1)
|
|
if [[ -z "$BASE_APK_NAME" ]]; then
|
|
BASE_APK_NAME="base.apk"
|
|
fi
|
|
|
|
# Copy signed base APK
|
|
cp "$OUTPUT" "$XAPK_WORKDIR/$BASE_APK_NAME"
|
|
info "Copied signed base APK as $BASE_APK_NAME"
|
|
|
|
# Re-sign and copy each split APK
|
|
if [[ -d "$XAPK_ORIGIN_DIR/splits" ]]; then
|
|
for split_apk in "$XAPK_ORIGIN_DIR/splits/"*.apk; do
|
|
if [[ ! -f "$split_apk" ]]; then continue; fi
|
|
split_name=$(basename "$split_apk")
|
|
info "Signing split: $split_name"
|
|
apksigner sign \
|
|
--ks "$KEYSTORE" \
|
|
--ks-key-alias "$KEY_ALIAS" \
|
|
--ks-pass "pass:$STORE_PASS" \
|
|
--key-pass "pass:$KEY_PASS" \
|
|
--out "$XAPK_WORKDIR/$split_name" \
|
|
"$split_apk"
|
|
echo "SPLIT_SIGNED:$split_name"
|
|
done
|
|
fi
|
|
|
|
# Copy manifest.json and icon from .xapk-origin/
|
|
if [[ -f "$XAPK_ORIGIN_DIR/manifest.json" ]]; then
|
|
cp "$XAPK_ORIGIN_DIR/manifest.json" "$XAPK_WORKDIR/"
|
|
fi
|
|
for icon_file in "$XAPK_ORIGIN_DIR"/icon.png "$XAPK_ORIGIN_DIR"/icon.jpg; do
|
|
if [[ -f "$icon_file" ]]; then
|
|
cp "$icon_file" "$XAPK_WORKDIR/"
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Assemble XAPK (zip with no compression for APKs)
|
|
# Make output path absolute for the subshell cd
|
|
XAPK_OUTPUT=$(realpath -m "$OUTPUT" 2>/dev/null || echo "$(pwd)/$OUTPUT")
|
|
# Remove the base APK output (it's now inside the XAPK)
|
|
rm -f "$XAPK_OUTPUT" 2>/dev/null || true
|
|
|
|
(cd "$XAPK_WORKDIR" && zip -r -0 "$XAPK_OUTPUT" . 2>&1) || {
|
|
fail "Failed to assemble XAPK archive"
|
|
exit 1
|
|
}
|
|
|
|
if [[ ! -f "$XAPK_OUTPUT" ]]; then
|
|
fail "XAPK output not found at: $XAPK_OUTPUT"
|
|
exit 1
|
|
fi
|
|
# Update OUTPUT to the absolute path used
|
|
OUTPUT="$XAPK_OUTPUT"
|
|
|
|
ok "XAPK assembled: $XAPK_OUTPUT"
|
|
echo "XAPK_ASSEMBLED:$XAPK_OUTPUT"
|
|
|
|
# Clean up workdir
|
|
xapk_cleanup
|
|
trap - EXIT
|
|
fi
|
|
|
|
# =====================================================================
|
|
# Summary
|
|
# =====================================================================
|
|
|
|
echo
|
|
echo "=== Rebuild Complete ==="
|
|
|
|
if [[ "$IS_XAPK" == true ]]; then
|
|
echo "Output XAPK: $OUTPUT"
|
|
else
|
|
echo "Output APK: $OUTPUT"
|
|
fi
|
|
|
|
if [[ "$DO_SIGN" == true ]]; then
|
|
SIGN_DESC="$KEYSTORE_SOURCE"
|
|
case "$KEYSTORE_SOURCE" in
|
|
debug-standard) SIGN_DESC="Android SDK debug key (~/.android/debug.keystore)" ;;
|
|
debug-previous) SIGN_DESC="previous neutralizer debug key" ;;
|
|
debug-generated) SIGN_DESC="auto-generated debug key" ;;
|
|
custom) SIGN_DESC="custom keystore ($KEYSTORE)" ;;
|
|
esac
|
|
echo "Signed with: $SIGNER ($SIGN_DESC)"
|
|
fi
|
|
|
|
OUTPUT_SIZE=$(stat -f%z "$OUTPUT" 2>/dev/null || stat -c%s "$OUTPUT" 2>/dev/null || echo "unknown")
|
|
echo "Output size: $OUTPUT_SIZE bytes"
|
|
|
|
if [[ "$BUILD_USED_NO_RES" == true ]]; then
|
|
echo
|
|
echo "NOTE: Resources were NOT recompiled (--no-res was used)."
|
|
echo " The APK uses original resources. Manifest XML changes (e.g., android:enabled)"
|
|
echo " may not be reflected unless they were applied before decode."
|
|
fi
|
|
|
|
echo
|
|
echo "WARNING: Play Integrity / SafetyNet will FAIL — expected for enterprise sideloading."
|
|
if [[ "$IS_XAPK" == true ]]; then
|
|
echo "Install via: adb install-multiple <base.apk> <split1.apk> <split2.apk> ..."
|
|
echo " or: use a split APK installer (e.g., SAI — Split APKs Installer)"
|
|
echo " or: unzip the XAPK and run: adb install-multiple *.apk"
|
|
else
|
|
echo "Install via: adb install $OUTPUT"
|
|
fi
|
|
|
|
exit 0
|