diff --git a/android/app/build.gradle b/android/app/build.gradle index 219cd88cb2..9dfaa6f513 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -166,7 +166,7 @@ android { pickFirst "lib/x86_64/libgojni.so" jniLibs { - useLegacyPackaging true + useLegacyPackaging false } } @@ -197,8 +197,8 @@ dependencies { implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01' implementation files("../../node_modules/react-native-tor/android/libs/sifir_android.aar") // gif - implementation 'com.facebook.fresco:fresco:3.1.3' - implementation 'com.facebook.fresco:animated-gif:3.1.3' + implementation 'com.facebook.fresco:fresco:3.6.0' + implementation 'com.facebook.fresco:animated-gif:3.6.0' // Pegasus // implementation(name:"Lndmobile", ext:"aar") diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 62e664af87..312edf495a 100755 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -35,7 +35,7 @@ android:theme="@style/AppTheme" android:networkSecurityConfig="@xml/network_security_config" android:largeHeap="true" - android:extractNativeLibs="true"> + android:extractNativeLibs="false"> diff --git a/android/check_elf_alignment.sh b/android/check_elf_alignment.sh new file mode 100755 index 0000000000..5c1f723a97 --- /dev/null +++ b/android/check_elf_alignment.sh @@ -0,0 +1,113 @@ +#!/bin/bash +progname="${0##*/}" +progname="${progname%.sh}" + +# usage: check_elf_alignment.sh [path to *.so files|path to *.apk] + +cleanup_trap() { + if [ -n "${tmp}" -a -d "${tmp}" ]; then + rm -rf ${tmp} + fi + exit $1 +} + +usage() { + echo "Host side script to check the ELF alignment of shared libraries." + echo "Shared libraries are reported ALIGNED when their ELF regions are" + echo "16 KB or 64 KB aligned. Otherwise they are reported as UNALIGNED." + echo + echo "Usage: ${progname} [input-path|input-APK|input-APEX]" +} + +if [ ${#} -ne 1 ]; then + usage + exit +fi + +case ${1} in + --help | -h | -\?) + usage + exit + ;; + + *) + dir="${1}" + ;; +esac + +if ! [ -f "${dir}" -o -d "${dir}" ]; then + echo "Invalid file: ${dir}" >&2 + exit 1 +fi + +if [[ "${dir}" == *.apk ]]; then + trap 'cleanup_trap' EXIT + + echo + echo "Recursively analyzing $dir" + echo + + if { zipalign --help 2>&1 | grep -q "\-P "; }; then + echo "=== APK zip-alignment ===" + zipalign -v -c -P 16 4 "${dir}" | egrep 'lib/arm64-v8a|lib/x86_64|Verification' + echo "=========================" + else + echo "NOTICE: Zip alignment check requires build-tools version 35.0.0-rc3 or higher." + echo " You can install the latest build-tools by running the below command" + echo " and updating your \$PATH:" + echo + echo " sdkmanager \"build-tools;35.0.0-rc3\"" + fi + + dir_filename=$(basename "${dir}") + tmp=$(mktemp -d -t "${dir_filename%.apk}_out_XXXXX") + unzip "${dir}" lib/* -d "${tmp}" >/dev/null 2>&1 + dir="${tmp}" +fi + +if [[ "${dir}" == *.apex ]]; then + trap 'cleanup_trap' EXIT + + echo + echo "Recursively analyzing $dir" + echo + + dir_filename=$(basename "${dir}") + tmp=$(mktemp -d -t "${dir_filename%.apex}_out_XXXXX") + deapexer extract "${dir}" "${tmp}" || { echo "Failed to deapex." && exit 1; } + dir="${tmp}" +fi + +RED="\e[31m" +GREEN="\e[32m" +ENDCOLOR="\e[0m" + +unaligned_libs=() + +echo +echo "=== ELF alignment ===" + +matches="$(find "${dir}" -type f)" +IFS=$'\n' +for match in $matches; do + # We could recursively call this script or rewrite it to though. + [[ "${match}" == *".apk" ]] && echo "WARNING: doesn't recursively inspect .apk file: ${match}" + [[ "${match}" == *".apex" ]] && echo "WARNING: doesn't recursively inspect .apex file: ${match}" + + [[ $(file "${match}") == *"ELF"* ]] || continue + + res="$(objdump -p "${match}" | grep LOAD | awk '{ print $NF }' | head -1)" + if [[ $res =~ 2\*\*(1[4-9]|[2-9][0-9]|[1-9][0-9]{2,}) ]]; then + echo -e "${match}: ${GREEN}ALIGNED${ENDCOLOR} ($res)" + else + echo -e "${match}: ${RED}UNALIGNED${ENDCOLOR} ($res)" + unaligned_libs+=("${match}") + fi +done + +if [ ${#unaligned_libs[@]} -gt 0 ]; then + echo -e "${RED}Found ${#unaligned_libs[@]} unaligned libs (only arm64-v8a/x86_64 libs need to be aligned).${ENDCOLOR}" +elif [ -n "${dir_filename}" ]; then + echo -e "ELF Verification Successful" +fi +echo "=====================" diff --git a/patches/index.mjs b/patches/index.mjs index 90a6254e2e..85553274f6 100644 --- a/patches/index.mjs +++ b/patches/index.mjs @@ -5,6 +5,7 @@ import { patchSifirAndroid } from './patch-sifir-android.mjs'; import { patchJcenter } from './patch-jcenter.mjs'; import { patchNativeEventEmitter } from './patch-native-event-emitter.mjs'; import { patchReactNativeNotifications } from './patch-react-native-notifications.mjs'; +import { patchVisionCamera } from './patch-vision-camera.mjs'; console.log('Running postinstall patches...\n'); @@ -13,6 +14,7 @@ console.log('Running postinstall patches...\n'); patchJcenter(); patchNativeEventEmitter(); patchReactNativeNotifications(); + patchVisionCamera(); console.log('\nAll patches applied successfully.'); })(); diff --git a/patches/patch-sifir-android.mjs b/patches/patch-sifir-android.mjs index a769577a3b..db51440a69 100644 --- a/patches/patch-sifir-android.mjs +++ b/patches/patch-sifir-android.mjs @@ -18,6 +18,10 @@ export async function patchSifirAndroid() { // Remove unsupported arm64 JNI await compressing.zip.uncompress(aarPath, extractPath); + // react-native-tor's AAR typically uses standard ABI folder names (e.g. arm64-v8a). + // Remove arm64 JNI payload to avoid shipping an ELF that isn't 16KB-aligned. + fs.rmSync(extractPath + '/jni/arm64-v8a', { force: true, recursive: true }); + // Back-compat in case an older layout was used. fs.rmSync(extractPath + '/jni/arm64', { force: true, recursive: true }); fs.rmSync(aarPath); @@ -48,4 +52,29 @@ export async function patchSifirAndroid() { fs.writeFileSync(torBridgeRequestPath, content); console.log(' - Fixed TorBridgeRequest.kt for Kotlin 2.0'); } + + // Prevent startup crash when sifir_android JNI isn't present for the current ABI. + // (e.g. when stripping arm64-v8a JNI for 16KB ELF alignment compliance) + const torPackagePath = + './node_modules/react-native-tor/android/src/main/java/com/reactnativetor/TorPackage.kt'; + + if (fs.existsSync(torPackagePath)) { + let content = fs.readFileSync(torPackagePath, 'utf8'); + + // Replace the eager loadLibrary() with a guarded version that skips module init. + // Keeps the app running even when Tor native bits are absent. + if (content.includes('System.loadLibrary("sifir_android")')) { + content = content.replace( + ' System.loadLibrary("sifir_android")', + ` try { + System.loadLibrary("sifir_android") + } catch (e: UnsatisfiedLinkError) { + // Native library not available for this ABI; disable Tor module. + return emptyList() + }` + ); + fs.writeFileSync(torPackagePath, content); + console.log(' - Guarded TorPackage.kt loadLibrary()'); + } + } } diff --git a/patches/patch-vision-camera.mjs b/patches/patch-vision-camera.mjs new file mode 100644 index 0000000000..6822919157 --- /dev/null +++ b/patches/patch-vision-camera.mjs @@ -0,0 +1,42 @@ +// Prevent crash when VisionCamera native library cannot be loaded on the device/ABI. +// This repo targets 16KB page-size compliance; some prebuilt native libs may not load on +// certain emulator/device setups. + +import fs from 'fs'; + +export function patchVisionCamera() { + const cameraPackagePath = + './node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/react/CameraPackage.kt'; + + if (!fs.existsSync(cameraPackagePath)) { + console.log('Patching react-native-vision-camera'); + console.log(' - Skipping: CameraPackage.kt not found'); + return; + } + + console.log('Patching react-native-vision-camera'); + + let content = fs.readFileSync(cameraPackagePath, 'utf8'); + + // Guard native module creation so an UnsatisfiedLinkError in VisionCamera's static init + // doesn't crash the whole app at startup. + if (!content.includes('catch (e: UnsatisfiedLinkError)')) { + content = content.replace( + /override fun createNativeModules\(reactContext: ReactApplicationContext\): List =\n\s*listOf\(\n\s*CameraViewModule\(reactContext\),\n\s*CameraDevicesManager\(reactContext\)\n\s*\)/m, + `override fun createNativeModules(reactContext: ReactApplicationContext): List = + try { + listOf( + CameraViewModule(reactContext), + CameraDevicesManager(reactContext) + ) + } catch (e: UnsatisfiedLinkError) { + // VisionCamera native lib failed to load. Disable camera modules instead of crashing. + emptyList() + }` + ); + } + + fs.writeFileSync(cameraPackagePath, content); + console.log(' - Guarded CameraPackage.kt createNativeModules()'); +} +