#!/usr/bin/env bash # Builds FreeMyTV.apk without Gradle: aapt2 -> javac -> d8 -> zipalign -> apksigner. # Needs a JDK (17+) and the Android SDK command-line tools (build-tools 34+, platform android-34). See README.md. set -euo pipefail cd "$(dirname "$0")" # --- Tools SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}" if [ -z "$SDK" ]; then for d in "$HOME/Library/Android/sdk" "$HOME/Android/Sdk" /opt/homebrew/share/android-commandlinetools \ /usr/local/share/android-commandlinetools /usr/lib/android-sdk; do if [ -d "$d/build-tools" ]; then SDK="$d"; break; fi done fi [ -n "$SDK" ] && [ -d "$SDK/build-tools" ] || { echo "Android SDK not found. Install it and set ANDROID_HOME (see README)."; exit 1; } BT="$SDK/build-tools/$(ls "$SDK/build-tools" | sort -V | tail -1)" AJ="$(ls -d "$SDK"/platforms/android-*/android.jar 2>/dev/null | sort -V | tail -1 || true)" [ -f "${AJ:-}" ] || { echo "Android platform not found. Run: sdkmanager \"platforms;android-34\""; exit 1; } JAVAC="${JAVA_HOME:+$JAVA_HOME/bin/}javac" KEYTOOL="${JAVA_HOME:+$JAVA_HOME/bin/}keytool" command -v "$JAVAC" >/dev/null 2>&1 || { echo "JDK not found. Install JDK 17 and set JAVA_HOME."; exit 1; } # --- Settings: config.properties -> Config.java if [ ! -f config.properties ]; then cp config.example.properties config.properties echo "Created config.properties: add your TMDB key and your city there (optional), then build again." fi prop() { local v v="$(grep -E "^[[:space:]]*$1[[:space:]]*=" config.properties | tail -1 | cut -d= -f2- || true)" v="$(printf '%s' "$v" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d '"\\')" printf '%s' "${v:-$2}" } number() { local v; v="$(prop "$1" "$2")" [[ "$v" =~ ^-?[0-9]+(\.[0-9]+)?$ ]] || { echo "config.properties: $1 must be a number (got \"$v\")" >&2; exit 1; } printf '%s' "$v" } LAT="$(number latitude 40.7128)" LON="$(number longitude -74.0060)" ASSIST=false; [ "$(prop disable_assistant false)" = "true" ] && ASSIST=true CRYPTO=false; [ "$(prop show_crypto true)" = "true" ] && CRYPTO=true ANIMATION=false; [ "$(prop hide_animation false)" = "true" ] && ANIMATION=true rm -rf out && mkdir -p out/gen/io/freemytv out/classes cat > out/gen/io/freemytv/Config.java </dev/null)" in MINGW*|MSYS*|CYGWIN*) cat > out/FixAssets.java <<'EOF' import java.nio.file.*; import java.util.zip.*; class FixAssets { public static void main(String[] args) throws Exception { Path apk = Paths.get(args[0]); Path tmp = apk.resolveSibling(apk.getFileName() + ".fixed"); int renamed = 0; try (ZipInputStream in = new ZipInputStream(Files.newInputStream(apk)); ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(tmp))) { for (ZipEntry e; (e = in.getNextEntry()) != null; ) { byte[] data = in.readAllBytes(); String name = e.getName().replace('\\', '/'); if (!name.equals(e.getName())) renamed++; ZipEntry n = new ZipEntry(name); if (e.getMethod() == ZipEntry.STORED) { // Android reads fonts straight from the APK n.setMethod(ZipEntry.STORED); n.setSize(data.length); n.setCompressedSize(data.length); CRC32 crc = new CRC32(); crc.update(data); n.setCrc(crc.getValue()); } out.putNextEntry(n); out.write(data); out.closeEntry(); } } if (renamed > 0) Files.move(tmp, apk, StandardCopyOption.REPLACE_EXISTING); else Files.delete(tmp); System.out.println("FixAssets: renamed " + renamed + " asset paths in " + apk.getFileName()); } } EOF "${JAVA_HOME:+$JAVA_HOME/bin/}java" out/FixAssets.java out/base.apk ;; esac # On Windows javac separates paths with ";", and d8 and apksigner are .bat files. CPSEP=:; BAT=; case "$(uname -s 2>/dev/null)" in MINGW*|MSYS*|CYGWIN*) CPSEP=";"; BAT=.bat ;; esac "$JAVAC" -nowarn -Xlint:-options -source 8 -target 8 -bootclasspath "$AJ$CPSEP$BT/core-lambda-stubs.jar" -classpath "$AJ" \ -encoding UTF-8 -d out/classes $(find app/src out/gen -name '*.java') "$BT/d8$BAT" --release --min-api 26 --lib "$AJ" --output out $(find out/classes -name '*.class') if command -v zip >/dev/null; then (cd out && zip -qj base.apk classes.dex) else "${JAVA_HOME:+$JAVA_HOME/bin/}jar" -u -M -f out/base.apk -C out classes.dex; fi # Git Bash has no zip "$BT/zipalign" -f -p 4 out/base.apk out/aligned.apk # --- Sign with your own key (created once; updates need the same key, so keep freemytv.keystore) if [ ! -f freemytv.keystore ]; then "$KEYTOOL" -genkeypair -keystore freemytv.keystore -alias freemytv -keyalg RSA -keysize 2048 -validity 20000 \ -storepass freemytv -keypass freemytv -dname "CN=Free My TV" >/dev/null 2>&1 fi "$BT/apksigner$BAT" sign --ks freemytv.keystore --ks-pass pass:freemytv --key-pass pass:freemytv --out FreeMyTV.apk out/aligned.apk echo "Built FreeMyTV.apk ($(du -h FreeMyTV.apk | cut -f1)). Install: adb install -r FreeMyTV.apk"