plugins { alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.android) } def releaseStoreFile = System.getenv("FRIDABOX_RELEASE_STORE_FILE") def releaseStorePassword = System.getenv("FRIDABOX_RELEASE_STORE_PASSWORD") def releaseKeyAlias = System.getenv("FRIDABOX_RELEASE_KEY_ALIAS") def releaseKeyPassword = System.getenv("FRIDABOX_RELEASE_KEY_PASSWORD") def releaseSigningValues = [releaseStoreFile, releaseStorePassword, releaseKeyAlias, releaseKeyPassword] def releaseSigningConfigured = releaseSigningValues.every { it != null && !it.trim().isEmpty() } if (releaseSigningValues.any { it != null && !it.trim().isEmpty() } && !releaseSigningConfigured) { throw new GradleException("Release signing is incomplete. Set all four FRIDABOX_RELEASE_* environment variables.") } android { namespace 'com.qm4rs.fridabox' compileSdk rootProject.ext.compileSdkVersion ndkVersion = "29.0.14206865" defaultConfig { applicationId "com.qm4rs.fridabox" minSdk rootProject.ext.minSdk targetSdk rootProject.ext.targetSdkVersion versionCode rootProject.ext.versionCode versionName rootProject.ext.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" splits { abi { enable true reset() include "armeabi-v7a", "arm64-v8a", "x86", "x86_64" universalApk false } } } signingConfigs { if (releaseSigningConfigured) { release { storeFile file(releaseStoreFile) storePassword releaseStorePassword keyAlias releaseKeyAlias keyPassword releaseKeyPassword } } } buildTypes { release { if (releaseSigningConfigured) { signingConfig signingConfigs.release } minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_21 targetCompatibility JavaVersion.VERSION_21 } buildFeatures { viewBinding true buildConfig true } sourceSets { debug.assets.srcDir("$buildDir/generated/demoGuestAssets") } kotlinOptions { jvmTarget = '21' } packaging { jniLibs { excludes.add("**/libandroidx.graphics.path.so") useLegacyPackaging true } resources { excludes.add("**/libandroidx.graphics.path.so") } } applicationVariants.configureEach { variant -> variant.outputs.configureEach { output -> output.outputFileName = "FridaBox_${variant.versionName}_${output.baseName}.apk" } } lint { abortOnError false checkReleaseBuilds false } } tasks.register("copyDemoGuestDebug", Copy) { dependsOn(":sample-guest:assembleDebug") from(project(":sample-guest").layout.buildDirectory.file("outputs/apk/debug/sample-guest-debug.apk")) into(layout.buildDirectory.dir("generated/demoGuestAssets/demo")) rename { "sample-guest.apk" } } tasks.matching { it.name == "mergeDebugAssets" }.configureEach { dependsOn("copyDemoGuestDebug") } tasks.matching { it.name.toLowerCase().contains("debug") && it.name.toLowerCase().contains("lint") } .configureEach { dependsOn("copyDemoGuestDebug") } tasks.register("verifyDebugApkHasNoGadget") { dependsOn("assembleDebug") doLast { def apks = fileTree("$buildDir/outputs/apk/debug").matching { include("*.apk") }.files if (apks.isEmpty()) { throw new GradleException("No debug APKs were produced") } apks.each { apk -> def entries = zipTree(apk).files.findAll { it.name.toLowerCase().contains("gadget") && it.name.toLowerCase().endsWith(".so") } if (!entries.isEmpty()) { throw new GradleException("${apk.name} must not bundle Frida Gadget: ${entries*.name}") } } } } tasks.register("verifyInstrumentationOrdering") { doLast { def source = project(":Bcore").file("src/main/java/top/niunaijun/blackbox/app/BActivityThread.java").text def loader = source.indexOf("FridaGadgetLoader.loadAtProcessBind()") def makeApplication = source.indexOf("makeApplication(", loader) if (loader < 0 || makeApplication < 0 || loader >= makeApplication) { throw new GradleException("Frida Gadget loader must execute before guest makeApplication") } } } tasks.register("verifyRuntimeBridgeAssets") { doLast { def bridgeAssets = [ "java-7.0.11.js", "java-7.0.12.js", "java-7.0.13.js", "il2cpp-0.12.2.js", "il2cpp-0.13.0.js", "il2cpp-0.13.1.js" ] def bridgeRoot = file("src/main/assets/fridabox/bridges") bridgeAssets.each { name -> def asset = new File(bridgeRoot, name) if (!asset.isFile() || asset.length() == 0 || asset.length() > 2 * 1024 * 1024) { throw new GradleException("Missing or invalid runtime bridge asset: ${name}") } def source = new String(asset.bytes, java.nio.charset.StandardCharsets.UTF_8) if (!source.startsWith("šŸ“¦\n") || !source.contains("\nāœ„\n")) { throw new GradleException("Runtime bridge is not a valid frida-compile bundle: ${name}") } } } } tasks.register("verifyTablerAndroidIcons", Exec) { workingDir(rootProject.projectDir) def nodeExecutable = System.getProperty("os.name").toLowerCase().contains("windows") ? "node.exe" : "node" commandLine(nodeExecutable, "tools/generate_tabler_android_icons.mjs", "--check") doFirst { if (!rootProject.file("node_modules/@tabler/icons/package.json").isFile()) { throw new GradleException("Missing @tabler/icons. Run npm ci before building FridaBox.") } } } tasks.register("verifyDemoGuestUnmodified") { dependsOn("copyDemoGuestDebug") doLast { def source = project(":sample-guest").layout.buildDirectory.file("outputs/apk/debug/sample-guest-debug.apk").get().asFile def embedded = layout.buildDirectory.file("generated/demoGuestAssets/demo/sample-guest.apk").get().asFile if (!java.util.Arrays.equals(java.security.MessageDigest.getInstance("SHA-256").digest(source.bytes), java.security.MessageDigest.getInstance("SHA-256").digest(embedded.bytes))) { throw new GradleException("Embedded sample APK differs from generated sample APK") } } } tasks.named("check").configure { dependsOn("verifyDebugApkHasNoGadget", "verifyInstrumentationOrdering", "verifyDemoGuestUnmodified", "verifyTablerAndroidIcons", "verifyRuntimeBridgeAssets") } dependencies { implementation project(':Bcore') implementation libs.appcompat implementation libs.material implementation libs.constraintlayout implementation libs.core.ktx implementation "org.tukaani:xz:1.12" testImplementation libs.junit androidTestImplementation libs.ext.junit androidTestImplementation libs.espresso.core }