Skip to content

Commit 6cd6e95

Browse files
committed
config: Android: 16 KB page size
1 parent 1aee033 commit 6cd6e95

File tree

7 files changed

+195
-4
lines changed

7 files changed

+195
-4
lines changed

android/app/build.gradle

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ android {
166166
pickFirst "lib/x86_64/libgojni.so"
167167

168168
jniLibs {
169-
useLegacyPackaging true
169+
// when useLegacyPackaging is false (default). Setting it to true would require
170+
// compressed libraries which is not recommended for 16KB devices.
171+
// Per official Android docs: https://developer.android.com/guide/practices/page-sizes
172+
useLegacyPackaging false
170173
}
171174
}
172175

@@ -198,7 +201,9 @@ dependencies {
198201
implementation files("../../node_modules/react-native-tor/android/libs/sifir_android.aar")
199202
// gif
200203
implementation 'com.facebook.fresco:fresco:3.1.3'
201-
implementation 'com.facebook.fresco:animated-gif:3.1.3'
204+
205+
// NOTE: Fresco's animated-gif bundles `libgifimage.so`, which may be ELF-unaligned
206+
// implementation 'com.facebook.fresco:animated-gif:3.1.3'
202207

203208
// Pegasus
204209
// implementation(name:"Lndmobile", ext:"aar")

android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
android:theme="@style/AppTheme"
3636
android:networkSecurityConfig="@xml/network_security_config"
3737
android:largeHeap="true"
38-
android:extractNativeLibs="true">
38+
android:extractNativeLibs="false">
3939
<!-- Set custom default icon. This is used when no icon is set for incoming notification messages.
4040
See README(https://goo.gl/l4GJaQ) for more. -->
4141
<meta-data

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ buildscript {
44
minSdkVersion = 28
55
compileSdkVersion = 36
66
targetSdkVersion = 36
7-
ndkVersion = "27.1.12297006"
7+
ndkVersion = "28.0.13004108"
88
kotlinVersion = "2.1.20"
99
}
1010
subprojects { subproject ->

android/check_elf_alignment.sh

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/bin/bash
2+
progname="${0##*/}"
3+
progname="${progname%.sh}"
4+
5+
# usage: check_elf_alignment.sh [path to *.so files|path to *.apk]
6+
7+
cleanup_trap() {
8+
if [ -n "${tmp}" -a -d "${tmp}" ]; then
9+
rm -rf ${tmp}
10+
fi
11+
exit $1
12+
}
13+
14+
usage() {
15+
echo "Host side script to check the ELF alignment of shared libraries."
16+
echo "Shared libraries are reported ALIGNED when their ELF regions are"
17+
echo "16 KB or 64 KB aligned. Otherwise they are reported as UNALIGNED."
18+
echo
19+
echo "Usage: ${progname} [input-path|input-APK|input-APEX]"
20+
}
21+
22+
if [ ${#} -ne 1 ]; then
23+
usage
24+
exit
25+
fi
26+
27+
case ${1} in
28+
--help | -h | -\?)
29+
usage
30+
exit
31+
;;
32+
33+
*)
34+
dir="${1}"
35+
;;
36+
esac
37+
38+
if ! [ -f "${dir}" -o -d "${dir}" ]; then
39+
echo "Invalid file: ${dir}" >&2
40+
exit 1
41+
fi
42+
43+
if [[ "${dir}" == *.apk ]]; then
44+
trap 'cleanup_trap' EXIT
45+
46+
echo
47+
echo "Recursively analyzing $dir"
48+
echo
49+
50+
if { zipalign --help 2>&1 | grep -q "\-P <pagesize_kb>"; }; then
51+
echo "=== APK zip-alignment ==="
52+
zipalign -v -c -P 16 4 "${dir}" | egrep 'lib/arm64-v8a|lib/x86_64|Verification'
53+
echo "========================="
54+
else
55+
echo "NOTICE: Zip alignment check requires build-tools version 35.0.0-rc3 or higher."
56+
echo " You can install the latest build-tools by running the below command"
57+
echo " and updating your \$PATH:"
58+
echo
59+
echo " sdkmanager \"build-tools;35.0.0-rc3\""
60+
fi
61+
62+
dir_filename=$(basename "${dir}")
63+
tmp=$(mktemp -d -t "${dir_filename%.apk}_out_XXXXX")
64+
unzip "${dir}" lib/* -d "${tmp}" >/dev/null 2>&1
65+
dir="${tmp}"
66+
fi
67+
68+
if [[ "${dir}" == *.apex ]]; then
69+
trap 'cleanup_trap' EXIT
70+
71+
echo
72+
echo "Recursively analyzing $dir"
73+
echo
74+
75+
dir_filename=$(basename "${dir}")
76+
tmp=$(mktemp -d -t "${dir_filename%.apex}_out_XXXXX")
77+
deapexer extract "${dir}" "${tmp}" || { echo "Failed to deapex." && exit 1; }
78+
dir="${tmp}"
79+
fi
80+
81+
RED="\e[31m"
82+
GREEN="\e[32m"
83+
ENDCOLOR="\e[0m"
84+
85+
unaligned_libs=()
86+
87+
echo
88+
echo "=== ELF alignment ==="
89+
90+
matches="$(find "${dir}" -type f)"
91+
IFS=$'\n'
92+
for match in $matches; do
93+
# We could recursively call this script or rewrite it to though.
94+
[[ "${match}" == *".apk" ]] && echo "WARNING: doesn't recursively inspect .apk file: ${match}"
95+
[[ "${match}" == *".apex" ]] && echo "WARNING: doesn't recursively inspect .apex file: ${match}"
96+
97+
[[ $(file "${match}") == *"ELF"* ]] || continue
98+
99+
res="$(objdump -p "${match}" | grep LOAD | awk '{ print $NF }' | head -1)"
100+
if [[ $res =~ 2\*\*(1[4-9]|[2-9][0-9]|[1-9][0-9]{2,}) ]]; then
101+
echo -e "${match}: ${GREEN}ALIGNED${ENDCOLOR} ($res)"
102+
else
103+
echo -e "${match}: ${RED}UNALIGNED${ENDCOLOR} ($res)"
104+
unaligned_libs+=("${match}")
105+
fi
106+
done
107+
108+
if [ ${#unaligned_libs[@]} -gt 0 ]; then
109+
echo -e "${RED}Found ${#unaligned_libs[@]} unaligned libs (only arm64-v8a/x86_64 libs need to be aligned).${ENDCOLOR}"
110+
elif [ -n "${dir_filename}" ]; then
111+
echo -e "ELF Verification Successful"
112+
fi
113+
echo "====================="

patches/index.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { patchSifirAndroid } from './patch-sifir-android.mjs';
55
import { patchJcenter } from './patch-jcenter.mjs';
66
import { patchNativeEventEmitter } from './patch-native-event-emitter.mjs';
77
import { patchReactNativeNotifications } from './patch-react-native-notifications.mjs';
8+
import { patchVisionCamera } from './patch-vision-camera.mjs';
89

910
console.log('Running postinstall patches...\n');
1011

@@ -13,6 +14,7 @@ console.log('Running postinstall patches...\n');
1314
patchJcenter();
1415
patchNativeEventEmitter();
1516
patchReactNativeNotifications();
17+
patchVisionCamera();
1618

1719
console.log('\nAll patches applied successfully.');
1820
})();

patches/patch-sifir-android.mjs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export async function patchSifirAndroid() {
1818
// Remove unsupported arm64 JNI
1919
await compressing.zip.uncompress(aarPath, extractPath);
2020

21+
// react-native-tor's AAR typically uses standard ABI folder names (e.g. arm64-v8a).
22+
// Remove arm64 JNI payload to avoid shipping an ELF that isn't 16KB-aligned.
23+
fs.rmSync(extractPath + '/jni/arm64-v8a', { force: true, recursive: true });
24+
// Back-compat in case an older layout was used.
2125
fs.rmSync(extractPath + '/jni/arm64', { force: true, recursive: true });
2226
fs.rmSync(aarPath);
2327

@@ -48,4 +52,29 @@ export async function patchSifirAndroid() {
4852
fs.writeFileSync(torBridgeRequestPath, content);
4953
console.log(' - Fixed TorBridgeRequest.kt for Kotlin 2.0');
5054
}
55+
56+
// Prevent startup crash when sifir_android JNI isn't present for the current ABI.
57+
// (e.g. when stripping arm64-v8a JNI for 16KB ELF alignment compliance)
58+
const torPackagePath =
59+
'./node_modules/react-native-tor/android/src/main/java/com/reactnativetor/TorPackage.kt';
60+
61+
if (fs.existsSync(torPackagePath)) {
62+
let content = fs.readFileSync(torPackagePath, 'utf8');
63+
64+
// Replace the eager loadLibrary() with a guarded version that skips module init.
65+
// Keeps the app running even when Tor native bits are absent.
66+
if (content.includes('System.loadLibrary("sifir_android")')) {
67+
content = content.replace(
68+
' System.loadLibrary("sifir_android")',
69+
` try {
70+
System.loadLibrary("sifir_android")
71+
} catch (e: UnsatisfiedLinkError) {
72+
// Native library not available for this ABI; disable Tor module.
73+
return emptyList()
74+
}`
75+
);
76+
fs.writeFileSync(torPackagePath, content);
77+
console.log(' - Guarded TorPackage.kt loadLibrary()');
78+
}
79+
}
5180
}

patches/patch-vision-camera.mjs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Prevent crash when VisionCamera native library cannot be loaded on the device/ABI.
2+
// This repo targets 16KB page-size compliance; some prebuilt native libs may not load on
3+
// certain emulator/device setups.
4+
5+
import fs from 'fs';
6+
7+
export function patchVisionCamera() {
8+
const cameraPackagePath =
9+
'./node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/react/CameraPackage.kt';
10+
11+
if (!fs.existsSync(cameraPackagePath)) {
12+
console.log('Patching react-native-vision-camera');
13+
console.log(' - Skipping: CameraPackage.kt not found');
14+
return;
15+
}
16+
17+
console.log('Patching react-native-vision-camera');
18+
19+
let content = fs.readFileSync(cameraPackagePath, 'utf8');
20+
21+
// Guard native module creation so an UnsatisfiedLinkError in VisionCamera's static init
22+
// doesn't crash the whole app at startup.
23+
if (!content.includes('catch (e: UnsatisfiedLinkError)')) {
24+
content = content.replace(
25+
/override fun createNativeModules\(reactContext: ReactApplicationContext\): List<NativeModule> =\n\s*listOf\(\n\s*CameraViewModule\(reactContext\),\n\s*CameraDevicesManager\(reactContext\)\n\s*\)/m,
26+
`override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
27+
try {
28+
listOf(
29+
CameraViewModule(reactContext),
30+
CameraDevicesManager(reactContext)
31+
)
32+
} catch (e: UnsatisfiedLinkError) {
33+
// VisionCamera native lib failed to load. Disable camera modules instead of crashing.
34+
emptyList()
35+
}`
36+
);
37+
}
38+
39+
fs.writeFileSync(cameraPackagePath, content);
40+
console.log(' - Guarded CameraPackage.kt createNativeModules()');
41+
}
42+

0 commit comments

Comments
 (0)