Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ android {
pickFirst "lib/x86_64/libgojni.so"

jniLibs {
useLegacyPackaging true
useLegacyPackaging false
}
}

Expand Down Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config"
android:largeHeap="true"
android:extractNativeLibs="true">
android:extractNativeLibs="false">
<!-- Set custom default icon. This is used when no icon is set for incoming notification messages.
See README(https://goo.gl/l4GJaQ) for more. -->
<meta-data
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ buildscript {
minSdkVersion = 28
compileSdkVersion = 36
targetSdkVersion = 36
ndkVersion = "27.1.12297006"
ndkVersion = "28.0.13004108"
kotlinVersion = "2.1.20"
}
subprojects { subproject ->
Expand Down
113 changes: 113 additions & 0 deletions android/check_elf_alignment.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/bin/bash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To make this script more robust, I recommend adding set -euo pipefail at the beginning.

  • set -e: Exits the script immediately if any command fails. This would prevent the script from providing a misleading 'success' message if, for example, the unzip command at line 64 fails.
  • set -u: Treats unset variables as an error, which helps catch typos in variable names.
  • set -o pipefail: Ensures that a pipeline's exit code is the exit code of the rightmost command to exit with a non-zero status, or zero if all commands of the pipeline exit successfully.

This is a standard best practice for writing safer shell scripts.

Suggested change
#!/bin/bash
#!/bin/bash
set -euo pipefail

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 <pagesize_kb>"; }; 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 "====================="
2 changes: 2 additions & 0 deletions patches/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -13,6 +14,7 @@ console.log('Running postinstall patches...\n');
patchJcenter();
patchNativeEventEmitter();
patchReactNativeNotifications();
patchVisionCamera();

console.log('\nAll patches applied successfully.');
})();
29 changes: 29 additions & 0 deletions patches/patch-sifir-android.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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()');
}
}
}
42 changes: 42 additions & 0 deletions patches/patch-vision-camera.mjs
Original file line number Diff line number Diff line change
@@ -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<NativeModule> =\n\s*listOf\(\n\s*CameraViewModule\(reactContext\),\n\s*CameraDevicesManager\(reactContext\)\n\s*\)/m,
`override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
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()');
}