├── .gitignore ├── LICENSE ├── README.md ├── app ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── native_init │ └── xposed_init │ ├── cpp │ ├── CMakeLists.txt │ ├── hook.h │ └── nativelib.c │ ├── java │ └── io │ │ └── github │ │ └── juby210 │ │ └── swiftbackupprem │ │ ├── BackupModule.kt │ │ ├── Consts.kt │ │ ├── DexKit.kt │ │ ├── MainActivity.kt │ │ ├── Module.java │ │ ├── ui │ │ ├── component │ │ │ ├── SettingsSwitch.kt │ │ │ └── SettingsTextField.kt │ │ └── theme │ │ │ └── Theme.kt │ │ └── util │ │ └── PreferencesManager.kt │ └── res │ ├── drawable │ └── ic_launcher_foreground.xml │ ├── mipmap-anydpi-v26 │ └── ic_launcher.xml │ └── values │ └── colors.xml ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | release 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Juby210 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftBackupPrem 2 | Swift Backup Premium LSPosed module (tested on v4.2.3, v4.2.5 and v5.0.4, but should also work on newer versions - thanks to [DexKit](https://github.com/LuckyPray/DexKit)). 3 | 4 | Optionally also allows you to set your own firebase credentials, so only your firebase instance is used. 5 | 6 | It is highly recommened to set your own firebase credentials, because the developer can ban you from accessing original app using his firebase instance. 7 | 8 | Step-by-step setup for your own firebase instance: 9 | 10 | 11 | https://user-images.githubusercontent.com/31005896/203136303-36079018-3199-4863-864b-40293342f262.mp4 12 | 13 | 14 | Since October 2023 Google disabled Custom URI schemes by default for new projects. Check `Enable custom URI scheme` while creating Android OAuth client (~3:36 on guide video). 15 | 16 | ![image](https://github.com/Juby210/SwiftBackupPrem/assets/31005896/8049f7e2-26db-418b-9611-171be77b61f1) 17 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | plugins { 4 | id("com.android.application") 5 | id("kotlin-android") 6 | } 7 | 8 | android { 9 | namespace = "io.github.juby210.swiftbackupprem" 10 | compileSdk = 34 11 | ndkVersion = "25.1.8937393" 12 | 13 | defaultConfig { 14 | applicationId = "io.github.juby210.swiftbackupprem" 15 | minSdk = 27 16 | targetSdk = 34 17 | versionCode = 204 18 | versionName = "2.0.4" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | isMinifyEnabled = true 24 | isShrinkResources = true 25 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 26 | } 27 | } 28 | externalNativeBuild { 29 | cmake { 30 | path("src/main/cpp/CMakeLists.txt") 31 | version = "3.22.1" 32 | } 33 | } 34 | compileOptions { 35 | sourceCompatibility = JavaVersion.VERSION_11 36 | targetCompatibility = JavaVersion.VERSION_11 37 | } 38 | buildFeatures { 39 | buildConfig = true 40 | compose = true 41 | resValues = false 42 | } 43 | composeOptions.kotlinCompilerExtensionVersion = "1.5.8" 44 | tasks.withType { 45 | kotlinOptions { 46 | jvmTarget = "11" 47 | } 48 | } 49 | } 50 | 51 | dependencies { 52 | compileOnly("de.robv.android.xposed:api:82") 53 | implementation("org.luckypray:dexkit:2.0.0") 54 | 55 | // AndroidX 56 | implementation("androidx.core:core-ktx:1.12.0") 57 | implementation("androidx.activity:activity-compose:1.8.2") 58 | 59 | // Compose 60 | val composeVersion = "1.6.0" 61 | implementation("androidx.compose.ui:ui:$composeVersion") 62 | implementation("androidx.compose.ui:ui-tooling:$composeVersion") 63 | implementation("androidx.compose.material3:material3:1.1.2") 64 | } 65 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Uncomment this to preserve the line number information for 2 | # debugging stack traces. 3 | -keepattributes SourceFile,LineNumberTable 4 | 5 | # Repackage classes into the top-level. 6 | -repackageclasses 7 | 8 | # Amount of optimization iterations, taken from an SO post 9 | -optimizationpasses 5 10 | 11 | # Broaden access modifiers to increase results during optimization 12 | -allowaccessmodification 13 | 14 | -keep class io.github.juby210.swiftbackupprem.Module { *; } 15 | -keep class io.github.juby210.swiftbackupprem.DexKit { 16 | public final void findObfuscatedClasses(android.content.Context, java.lang.ClassLoader, java.lang.String); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 26 | 29 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/native_init: -------------------------------------------------------------------------------- 1 | libnativelib.so 2 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | io.github.juby210.swiftbackupprem.Module 2 | -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | 6 | cmake_minimum_required(VERSION 3.22.1) 7 | 8 | # Declares and names the project. 9 | 10 | project("app") 11 | 12 | # Creates and names a library, sets it as either STATIC 13 | # or SHARED, and provides the relative paths to its source code. 14 | # You can define multiple libraries, and CMake builds them for you. 15 | # Gradle automatically packages shared libraries with your APK. 16 | 17 | add_library( # Sets the name of the library. 18 | nativelib 19 | 20 | # Sets the library as a shared library. 21 | SHARED 22 | 23 | # Provides a relative path to your source file(s). 24 | nativelib.c) 25 | 26 | target_link_libraries(nativelib) 27 | -------------------------------------------------------------------------------- /app/src/main/cpp/hook.h: -------------------------------------------------------------------------------- 1 | typedef int (*HookFunType)(void *func, void *replace, void **backup); 2 | 3 | typedef int (*UnhookFunType)(void *func); 4 | 5 | typedef void (*NativeOnModuleLoaded)(const char *name, void *handle); 6 | 7 | typedef struct { 8 | uint32_t version; 9 | HookFunType hook_func; 10 | UnhookFunType unhook_func; 11 | } NativeAPIEntries; 12 | 13 | typedef NativeOnModuleLoaded (*NativeInit)(const NativeAPIEntries *entries); 14 | -------------------------------------------------------------------------------- /app/src/main/cpp/nativelib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "hook.h" 6 | 7 | static HookFunType hook_func = NULL; 8 | 9 | jint (*backup)(JavaVM *, void *); 10 | 11 | jint fakeLoad(JavaVM *, void *) { 12 | return JNI_VERSION_1_6; 13 | } 14 | 15 | bool ends_with(const char *a, const char *b) { 16 | size_t len = strlen(a); 17 | size_t len2 = strlen(b); 18 | if (len2 > len) return false; 19 | return strncmp(a + len - len2, b, len2) == 0; 20 | } 21 | 22 | void on_library_loaded(const char *name, void *handle) { 23 | if (ends_with(name, "libnative-lib.so")) { 24 | void *target = dlsym(handle, "JNI_OnLoad"); 25 | hook_func(target, (void *) fakeLoad, (void **) &backup); 26 | } 27 | } 28 | 29 | NativeOnModuleLoaded native_init(const NativeAPIEntries *entries) { 30 | hook_func = entries->hook_func; 31 | return on_library_loaded; 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/juby210/swiftbackupprem/BackupModule.kt: -------------------------------------------------------------------------------- 1 | package io.github.juby210.swiftbackupprem 2 | 3 | import android.content.Context 4 | import de.robv.android.xposed.XC_MethodHook 5 | import de.robv.android.xposed.XposedBridge 6 | import io.github.juby210.swiftbackupprem.util.PreferencesManager 7 | import org.json.JSONArray 8 | import org.json.JSONObject 9 | import java.io.File 10 | 11 | fun hookBackupApk(cl: ClassLoader, ctx: Context, customFirebaseApp: Boolean, prefs: PreferencesManager) { 12 | val pathsA = cl.loadClass("${paths!!.name}\$a") 13 | XposedBridge.hookMethod(backupApk!!.getDeclaredMethod("c"), object : XC_MethodHook() { 14 | override fun afterHookedMethod(param: MethodHookParam) { 15 | val pathsClass = paths!! 16 | val aInstance = pathsClass.declaredFields.first { it.type == pathsA }.get(null) 17 | val instance = pathsA.getDeclaredMethod("d").invoke(aInstance) 18 | val basePath = pathsClass.getDeclaredMethod("m").invoke(instance) as String 19 | 20 | val dir = File(basePath, "sbp") 21 | if (!dir.exists()) dir.mkdir() 22 | 23 | val apkFile = File(dir, "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}).apk") 24 | if (!apkFile.exists()) 25 | File(ctx.packageManager.getPackageInfo(BuildConfig.APPLICATION_ID, 0).applicationInfo.sourceDir).copyTo(apkFile, true) 26 | 27 | if (customFirebaseApp) with(prefs) { 28 | val json = JSONObject().apply { 29 | put("client", JSONArray().apply { 30 | put(JSONObject().apply { 31 | put("client_info", JSONObject().apply { put("mobilesdk_app_id", googleAppId) }) 32 | put("api_key", JSONArray().apply { put(JSONObject().apply { put("current_key", googleApiKey) }) }) 33 | }) 34 | }) 35 | put("project_info", JSONObject().apply { 36 | put("firebase_url", firebaseDatabaseUrl) 37 | put("project_number", gcmDefaultSenderId) 38 | put("storage_bucket", googleStorageBucket) 39 | put(Consts.projectId, projectId) 40 | }) 41 | put(Consts.oauthClientId, clientId) 42 | }.toString() 43 | File(dir, "google-services.json").run { if (!exists() || readText() != json) writeText(json) } 44 | } 45 | } 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/juby210/swiftbackupprem/Consts.kt: -------------------------------------------------------------------------------- 1 | package io.github.juby210.swiftbackupprem 2 | 3 | object Consts { 4 | const val packageName = "org.swiftapps.swiftbackup" 5 | 6 | const val googleAppId = "google_app_id" 7 | const val googleApiKey = "google_api_key" 8 | const val firebaseDatabaseUrl = "firebase_database_url" 9 | const val gcmDefaultSenderId = "gcm_defaultSenderId" 10 | const val googleStorageBucket = "google_storage_bucket" 11 | const val projectId = "project_id" 12 | const val oauthClientId = "oauth_client_id" 13 | 14 | @JvmStatic 15 | val classNames = mapOf(561 to "kf.s0", 569 to "rf.r0") 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/juby210/swiftbackupprem/DexKit.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("DexKit") 2 | 3 | package io.github.juby210.swiftbackupprem 4 | 5 | import android.content.Context 6 | import android.util.Log 7 | import android.widget.Toast 8 | import org.luckypray.dexkit.DexKitBridge 9 | import java.lang.reflect.Modifier 10 | 11 | private val classesClientId = mapOf(561 to "kf.s0", 569 to "rf.r0", 590 to "eh.u") 12 | private val classesBackupApk = mapOf(561 to "org.swiftapps.swiftbackup.common.w1", 569 to "org.swiftapps.swiftbackup.common.n2", 590 to "org.swiftapps.swiftbackup.common.c2") 13 | private val classesPaths = mapOf(561 to "me.b", 569 to "te.c", 590 to "org.swiftapps.swiftbackup.a") 14 | 15 | @JvmField 16 | var clientId: Class<*>? = null 17 | @JvmField 18 | var backupApk: Class<*>? = null 19 | @JvmField 20 | var paths: Class<*>? = null 21 | 22 | @Suppress("DEPRECATION") 23 | fun findObfuscatedClasses(ctx: Context, cl: ClassLoader, sourceDir: String) { 24 | val ver = Integer.valueOf(ctx.packageManager.getPackageInfo(Consts.packageName, 0).versionCode) 25 | if (classesClientId.containsKey(ver)) { 26 | clientId = cl.loadClass(classesClientId[ver]) 27 | backupApk = cl.loadClass(classesBackupApk[ver]) 28 | paths = cl.loadClass(classesPaths[ver]) 29 | } else { 30 | System.loadLibrary("dexkit") 31 | val excludePackages = listOf("android", "androidx", "com", "iammert", "java", "javax", "kotlin", "kotlinx", "moe", "nz.mega", 32 | "okhttp3", "okio", "retrofit", "rikka") 33 | DexKitBridge.create(sourceDir).use { bridge -> 34 | bridge.findClass { 35 | excludePackages(excludePackages) 36 | matcher { 37 | fields { 38 | add { 39 | modifiers(Modifier.PUBLIC or Modifier.STATIC or Modifier.FINAL) 40 | name("a") 41 | } 42 | add { 43 | modifiers(Modifier.PRIVATE or Modifier.STATIC or Modifier.FINAL) 44 | name("b") 45 | } 46 | add { 47 | modifiers(Modifier.PRIVATE or Modifier.STATIC or Modifier.FINAL) 48 | name("c") 49 | type("java.lang.String") 50 | } 51 | add { 52 | modifiers(Modifier.PRIVATE or Modifier.STATIC or Modifier.FINAL) 53 | name("d") 54 | type("android.net.Uri") 55 | } 56 | count(4) 57 | } 58 | addMethod { 59 | modifiers(Modifier.PUBLIC or Modifier.FINAL) 60 | returnType("android.content.Intent") 61 | name("f") 62 | addParamType("boolean") 63 | } 64 | } 65 | }.singleOrNull()?.let { 66 | clientId = it.getInstance(cl) 67 | Log.d("SBP", "Found client id class: ${it.name}") 68 | } 69 | 70 | bridge.findClass { 71 | searchPackages("org.swiftapps.swiftbackup.common") 72 | matcher { 73 | fields { 74 | addForName("a") 75 | count(1) 76 | } 77 | addMethod { 78 | modifiers(Modifier.PRIVATE or Modifier.FINAL) 79 | returnType("void") 80 | name("c") 81 | paramCount(0) 82 | usingStrings("stable", "swift_backup_apks/", "SwiftBackupApkSaver") 83 | } 84 | } 85 | }.singleOrNull()?.let { 86 | backupApk = it.getInstance(cl) 87 | Log.d("SBP", "Found backup apk class: ${it.name}") 88 | } 89 | 90 | bridge.findClass { 91 | excludePackages(excludePackages) 92 | matcher { 93 | methods { 94 | add { 95 | name("") 96 | addParamType("org.swiftapps.swiftbackup.anonymous.MFirebaseUser") 97 | addParamType("java.lang.String") 98 | paramCount(2) 99 | usingStrings("accounts/", "backups/", "cache/", "apps/", "local/", "cloud/", "icon_cache/", "sms/", "calls/") 100 | } 101 | add { 102 | modifiers(Modifier.PUBLIC or Modifier.FINAL) 103 | returnType("java.lang.String") 104 | name("m") 105 | paramCount(0) 106 | } 107 | } 108 | } 109 | }.singleOrNull()?.let { 110 | paths = it.getInstance(cl) 111 | Log.d("SBP", "Found paths class: ${it.name}") 112 | } 113 | } 114 | 115 | if (clientId == null || backupApk == null || paths == null) Toast.makeText( 116 | ctx, 117 | "[SBP] Couldn't fully hook Swift Backup. Check if there's module update or report an issue.", 118 | Toast.LENGTH_LONG 119 | ).show() 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/juby210/swiftbackupprem/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.juby210.swiftbackupprem 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.widget.Toast 7 | import androidx.activity.ComponentActivity 8 | import androidx.activity.compose.rememberLauncherForActivityResult 9 | import androidx.activity.compose.setContent 10 | import androidx.activity.enableEdgeToEdge 11 | import androidx.activity.result.contract.ActivityResultContracts 12 | import androidx.compose.foundation.layout.* 13 | import androidx.compose.foundation.rememberScrollState 14 | import androidx.compose.foundation.verticalScroll 15 | import androidx.compose.material3.* 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.platform.LocalClipboardManager 18 | import androidx.compose.ui.platform.LocalUriHandler 19 | import androidx.compose.ui.text.AnnotatedString 20 | import androidx.compose.ui.unit.dp 21 | import io.github.juby210.swiftbackupprem.ui.component.SettingsSwitch 22 | import io.github.juby210.swiftbackupprem.ui.component.SettingsTextField 23 | import io.github.juby210.swiftbackupprem.ui.theme.Theme 24 | import io.github.juby210.swiftbackupprem.util.PreferencesManager 25 | import kotlinx.coroutines.* 26 | import org.json.JSONObject 27 | import kotlin.system.exitProcess 28 | 29 | class MainActivity : ComponentActivity() { 30 | @SuppressLint("WorldReadableFiles") 31 | @OptIn(ExperimentalMaterial3Api::class) 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | enableEdgeToEdge() 34 | super.onCreate(savedInstanceState) 35 | 36 | val prefs: PreferencesManager 37 | try { 38 | @Suppress("DEPRECATION") 39 | prefs = PreferencesManager(getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", MODE_WORLD_READABLE)) 40 | } catch (e: Throwable) { 41 | Toast.makeText(this, "Enable module in LSPosed manager before using it", Toast.LENGTH_SHORT).show() 42 | finishAndRemoveTask() 43 | exitProcess(0) 44 | } 45 | 46 | setContent { 47 | Theme { 48 | Scaffold( 49 | topBar = { 50 | TopAppBar( 51 | title = { Text("SwiftBackupPrem") } 52 | ) 53 | } 54 | ) { paddingValues -> 55 | Column(modifier = Modifier.padding(paddingValues).fillMaxSize().verticalScroll(state = rememberScrollState())) { 56 | SettingsSwitch( 57 | label = "Custom firebase app", 58 | secondaryLabel = "Recommended, forces Swift Backup to use your own firebase credentials", 59 | pref = prefs.customFirebaseApp, 60 | onPrefChange = { prefs.customFirebaseApp = it } 61 | ) 62 | if (prefs.customFirebaseApp) { 63 | SettingsTextField( 64 | label = "Google App ID", 65 | pref = prefs.googleAppId, 66 | onPrefChange = { prefs.googleAppId = it } 67 | ) 68 | SettingsTextField( 69 | label = "Google Api Key", 70 | pref = prefs.googleApiKey, 71 | onPrefChange = { prefs.googleApiKey = it } 72 | ) 73 | SettingsTextField( 74 | label = "Firebase Database URL", 75 | pref = prefs.firebaseDatabaseUrl, 76 | onPrefChange = { prefs.firebaseDatabaseUrl = it } 77 | ) 78 | SettingsTextField( 79 | label = "GCM Default Sender ID", 80 | pref = prefs.gcmDefaultSenderId, 81 | onPrefChange = { prefs.gcmDefaultSenderId = it } 82 | ) 83 | SettingsTextField( 84 | label = "Google Storage Bucket", 85 | pref = prefs.googleStorageBucket, 86 | onPrefChange = { prefs.googleStorageBucket = it } 87 | ) 88 | SettingsTextField( 89 | label = "Project ID", 90 | pref = prefs.projectId, 91 | onPrefChange = { prefs.projectId = it } 92 | ) 93 | SettingsTextField( 94 | label = "Client ID", 95 | pref = prefs.clientId, 96 | onPrefChange = { prefs.clientId = it } 97 | ) 98 | 99 | val pickJson = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { 100 | if (it != null) { 101 | contentResolver.openInputStream(it)?.use { inputStream -> 102 | try { 103 | val json = JSONObject(inputStream.bufferedReader().use { r -> r.readText() }) 104 | with(prefs) { 105 | with(json.getJSONArray("client").getJSONObject(0)) { 106 | googleAppId = getJSONObject("client_info").getString("mobilesdk_app_id") 107 | googleApiKey = getJSONArray("api_key").getJSONObject(0).getString("current_key") 108 | } 109 | with(json.getJSONObject("project_info")) { 110 | firebaseDatabaseUrl = getString("firebase_url") 111 | gcmDefaultSenderId = getString("project_number") 112 | googleStorageBucket = getString("storage_bucket") 113 | projectId = getString(Consts.projectId) 114 | } 115 | if (json.has(Consts.oauthClientId)) clientId = json.getString(Consts.oauthClientId) 116 | } 117 | } catch (e: Throwable) { 118 | Toast.makeText(this@MainActivity, "Failed to parse json\n$e", Toast.LENGTH_LONG).show() 119 | Log.e("SBP", "Failed to parse json", e) 120 | } 121 | } 122 | } 123 | } 124 | Button( 125 | onClick = { pickJson.launch("application/json") }, 126 | modifier = Modifier.padding(horizontal = 15.dp, vertical = 5.dp).fillMaxWidth().height(40.dp) 127 | ) { 128 | Text("Import from google-services.json") 129 | } 130 | 131 | val uriHandler = LocalUriHandler.current 132 | Button( 133 | onClick = { uriHandler.openUri("https://console.firebase.google.com/u/0/") }, 134 | modifier = Modifier.padding(horizontal = 15.dp, vertical = 5.dp).fillMaxWidth().height(40.dp) 135 | ) { 136 | Text("Open Firebase Console") 137 | } 138 | 139 | Button( 140 | onClick = { uriHandler.openUri("https://console.developers.google.com/") }, 141 | modifier = Modifier.padding(horizontal = 15.dp, vertical = 5.dp).fillMaxWidth().height(40.dp) 142 | ) { 143 | Text("Open Google Developer Console") 144 | } 145 | 146 | val clip = LocalClipboardManager.current 147 | Button( 148 | onClick = { 149 | clip.setText( 150 | AnnotatedString( 151 | "{\n" + 152 | " \"rules\": {\n" + 153 | " \"users\": {\n" + 154 | " \"\$uid\": {\n" + 155 | " \".read\": \"\$uid === auth.uid\",\n" + 156 | " \".write\": \"\$uid === auth.uid\"\n" + 157 | " }\n" + 158 | " }\n" + 159 | " }\n" + 160 | "}" 161 | ) 162 | ) 163 | }, 164 | modifier = Modifier.padding(horizontal = 15.dp, vertical = 5.dp).fillMaxWidth().height(40.dp) 165 | ) { 166 | Text("Copy database rules") 167 | } 168 | 169 | Button( 170 | onClick = { clip.setText(AnnotatedString(Consts.packageName)) }, 171 | modifier = Modifier.padding(horizontal = 15.dp, vertical = 5.dp).fillMaxWidth().height(40.dp) 172 | ) { 173 | Text("Copy Swift Backup package name") 174 | } 175 | 176 | Button( 177 | onClick = { clip.setText(AnnotatedString(randomFingerprint())) }, 178 | modifier = Modifier.padding(horizontal = 15.dp, vertical = 5.dp).fillMaxWidth().height(40.dp) 179 | ) { 180 | Text("Copy random fingerprint") 181 | } 182 | 183 | Button( 184 | onClick = { uriHandler.openUri("https://console.cloud.google.com/apis/library/drive.googleapis.com?project=${prefs.projectId}") }, 185 | modifier = Modifier.padding(horizontal = 15.dp, vertical = 5.dp).fillMaxWidth().height(40.dp) 186 | ) { 187 | Text("Enable Google Drive API") 188 | } 189 | } 190 | } 191 | } 192 | } 193 | } 194 | } 195 | 196 | private val chars = ('A'..'F') + ('0'..'9') 197 | private fun randomFingerprint() = List(20) { chars.random().toString() + chars.random() }.joinToString(":") 198 | } 199 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/juby210/swiftbackupprem/Module.java: -------------------------------------------------------------------------------- 1 | package io.github.juby210.swiftbackupprem; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.Arrays; 6 | 7 | import de.robv.android.xposed.*; 8 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 9 | import io.github.juby210.swiftbackupprem.util.PreferencesManager; 10 | 11 | public final class Module implements IXposedHookLoadPackage { 12 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { 13 | if (!lpparam.packageName.equals(Consts.packageName)) return; 14 | System.loadLibrary("nativelib"); 15 | 16 | var xPrefs = new XSharedPreferences(BuildConfig.APPLICATION_ID); 17 | xPrefs.makeWorldReadable(); 18 | var prefs = new PreferencesManager(xPrefs); 19 | var customFirebaseApp = prefs.getCustomFirebaseApp() && 20 | prefs.getGoogleAppId().length() > 0 && 21 | prefs.getGoogleApiKey().length() > 0 && 22 | prefs.getFirebaseDatabaseUrl().length() > 0 && 23 | prefs.getGcmDefaultSenderId().length() > 0 && 24 | prefs.getProjectId().length() > 0 && 25 | prefs.getClientId().length() > 0; 26 | 27 | var cl = lpparam.classLoader; 28 | var sa = cl.loadClass("org.swiftapps.swiftbackup.SwiftApp"); 29 | XposedHelpers.findAndHookMethod(sa, "onCreate", new XC_MethodHook() { 30 | @SuppressWarnings("JavaReflectionInvocation") 31 | public void beforeHookedMethod(MethodHookParam param) throws Throwable { 32 | var ctx = (Context) param.thisObject; 33 | DexKit.findObfuscatedClasses(ctx, cl, lpparam.appInfo.sourceDir); 34 | var c = cl.loadClass("com.google.firebase.FirebaseApp"); 35 | if (customFirebaseApp) { 36 | var options = cl.loadClass("com.google.firebase.FirebaseOptions"); 37 | var params = new Class[7]; 38 | Arrays.fill(params, String.class); 39 | var constructor = options.getDeclaredConstructor(params); 40 | 41 | c.getDeclaredMethod("initializeApp", Context.class, options).invoke( 42 | null, 43 | ctx, 44 | constructor.newInstance( 45 | prefs.getGoogleAppId(), 46 | prefs.getGoogleApiKey(), 47 | prefs.getFirebaseDatabaseUrl(), 48 | null, 49 | prefs.getGcmDefaultSenderId(), 50 | prefs.getGoogleStorageBucket(), 51 | prefs.getProjectId() 52 | ) 53 | ); 54 | 55 | if (DexKit.clientId != null) XposedBridge.hookMethod(DexKit.clientId.getDeclaredMethod("f", boolean.class), new XC_MethodHook() { 56 | public void beforeHookedMethod(MethodHookParam param) throws Throwable { 57 | var clientId = DexKit.clientId.getDeclaredField("c"); 58 | clientId.setAccessible(true); 59 | clientId.set(null, prefs.getClientId()); 60 | } 61 | }); 62 | } else c.getDeclaredMethod("initializeApp", Context.class).invoke(null, param.thisObject); 63 | 64 | if (DexKit.backupApk != null && DexKit.paths != null) BackupModuleKt.hookBackupApk(cl, ctx, customFirebaseApp, prefs); 65 | } 66 | }); 67 | 68 | if (customFirebaseApp) 69 | XposedHelpers.findAndHookMethod("org.swiftapps.swiftbackup.cloud.d", cl, "d", XC_MethodReplacement.returnConstant(Boolean.FALSE)); 70 | 71 | var c = cl.loadClass("org.swiftapps.swiftbackup.common.V$a"); 72 | for (var m : c.getDeclaredMethods()) { 73 | if (m.getName().equals("invoke")) { 74 | XposedBridge.hookMethod(m, XC_MethodReplacement.returnConstant(Boolean.TRUE)); 75 | break; 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/juby210/swiftbackupprem/ui/component/SettingsSwitch.kt: -------------------------------------------------------------------------------- 1 | package io.github.juby210.swiftbackupprem.ui.component 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material3.* 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.text.font.FontWeight 10 | import androidx.compose.ui.unit.dp 11 | import androidx.compose.ui.unit.sp 12 | 13 | @Composable 14 | fun SettingsSwitch( 15 | label: String, 16 | secondaryLabel: String, 17 | pref: Boolean, 18 | onPrefChange: (Boolean) -> Unit, 19 | ) { 20 | Row( 21 | modifier = Modifier 22 | .fillMaxWidth() 23 | .padding(horizontal = 16.dp, vertical = 5.dp) 24 | .clickable { onPrefChange(!pref) }, 25 | horizontalArrangement = Arrangement.spacedBy(15.dp), 26 | verticalAlignment = Alignment.CenterVertically 27 | ) { 28 | Column( 29 | verticalArrangement = Arrangement.spacedBy(2.dp), 30 | modifier = Modifier.weight(0.95f, true) 31 | ) { 32 | ProvideTextStyle( 33 | MaterialTheme.typography.titleLarge.copy( 34 | fontWeight = FontWeight.Normal, 35 | fontSize = 18.sp 36 | ) 37 | ) { 38 | Text(text = label, softWrap = true) 39 | } 40 | ProvideTextStyle( 41 | MaterialTheme.typography.bodyMedium.copy( 42 | color = MaterialTheme.colorScheme.onSurface.copy(0.6f) 43 | ) 44 | ) { 45 | Text(text = secondaryLabel) 46 | } 47 | } 48 | 49 | Spacer(Modifier.weight(0.05f, true)) 50 | 51 | Switch( 52 | checked = pref, 53 | onCheckedChange = { onPrefChange(!pref) } 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/juby210/swiftbackupprem/ui/component/SettingsTextField.kt: -------------------------------------------------------------------------------- 1 | package io.github.juby210.swiftbackupprem.ui.component 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.material3.* 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.unit.dp 8 | 9 | @OptIn(ExperimentalMaterial3Api::class) 10 | @Composable 11 | fun SettingsTextField( 12 | label: String, 13 | pref: String, 14 | onPrefChange: (String) -> Unit, 15 | ) { 16 | Box(modifier = Modifier.padding(horizontal = 15.dp, vertical = 10.dp)) { 17 | OutlinedTextField( 18 | modifier = Modifier.fillMaxWidth(), 19 | value = pref, 20 | onValueChange = onPrefChange, 21 | label = { Text(label) }, 22 | singleLine = true 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/juby210/swiftbackupprem/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package io.github.juby210.swiftbackupprem.ui.theme 2 | 3 | import android.os.Build 4 | import androidx.compose.foundation.isSystemInDarkTheme 5 | import androidx.compose.material3.* 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.platform.LocalContext 8 | 9 | @Composable 10 | fun Theme(content: @Composable () -> Unit) { 11 | val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S 12 | val isDarkTheme = isSystemInDarkTheme() 13 | val colorScheme = when { 14 | dynamicColor && isDarkTheme -> dynamicDarkColorScheme(LocalContext.current) 15 | dynamicColor && !isDarkTheme -> dynamicLightColorScheme(LocalContext.current) 16 | isDarkTheme -> darkColorScheme() 17 | else -> lightColorScheme() 18 | } 19 | 20 | MaterialTheme( 21 | colorScheme = colorScheme, 22 | content = content 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/juby210/swiftbackupprem/util/PreferencesManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.juby210.swiftbackupprem.util 2 | 3 | import android.content.SharedPreferences 4 | import androidx.compose.runtime.* 5 | import androidx.core.content.edit 6 | import io.github.juby210.swiftbackupprem.Consts 7 | import kotlin.reflect.KProperty 8 | 9 | class PreferencesManager(private val prefs: SharedPreferences) { 10 | private class Preference( 11 | private val key: String, 12 | defaultValue: T, 13 | getter: (key: String, defaultValue: T) -> T, 14 | private val setter: (key: String, newValue: T) -> Unit 15 | ) { 16 | var value by mutableStateOf(getter(key, defaultValue)) 17 | private set 18 | 19 | operator fun getValue(thisRef: Any?, property: KProperty<*>) = value 20 | operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) { 21 | value = newValue 22 | setter(key, newValue) 23 | } 24 | } 25 | 26 | private fun getString(key: String, defaultValue: String) = prefs.getString(key, defaultValue) ?: defaultValue 27 | private fun getBoolean(key: String, defaultValue: Boolean) = prefs.getBoolean(key, defaultValue) 28 | 29 | private fun putString(key: String, value: String?) = prefs.edit { putString(key, value) } 30 | private fun putBoolean(key: String, value: Boolean) = prefs.edit { putBoolean(key, value) } 31 | 32 | private fun stringPreference( 33 | key: String 34 | ) = Preference( 35 | key = key, 36 | defaultValue = "", 37 | getter = ::getString, 38 | setter = ::putString 39 | ) 40 | 41 | @Suppress("SameParameterValue") 42 | private fun booleanPreference( 43 | key: String 44 | ) = Preference( 45 | key = key, 46 | defaultValue = false, 47 | getter = ::getBoolean, 48 | setter = ::putBoolean 49 | ) 50 | 51 | var googleAppId by stringPreference(Consts.googleAppId) 52 | var googleApiKey by stringPreference(Consts.googleApiKey) 53 | var firebaseDatabaseUrl by stringPreference(Consts.firebaseDatabaseUrl) 54 | var gcmDefaultSenderId by stringPreference(Consts.gcmDefaultSenderId) 55 | var googleStorageBucket by stringPreference(Consts.googleStorageBucket) 56 | var projectId by stringPreference(Consts.projectId) 57 | var clientId by stringPreference(Consts.oauthClientId) 58 | 59 | var customFirebaseApp by booleanPreference("custom_firebase_app") 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffa000 4 | 5 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | //noinspection JcenterRepositoryObsolete 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath("com.android.tools.build:gradle:8.2.2") 10 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22") 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | //noinspection JcenterRepositoryObsolete 18 | jcenter() 19 | } 20 | } 21 | 22 | tasks.register("clean") { 23 | delete(rootProject.layout.buildDirectory) 24 | } 25 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juby210/SwiftBackupPrem/050290fa64fb3e460d72dd8d1bbbd3c3ade80729/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 06 16:02:37 CET 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | include(":app") 2 | rootProject.name = "SwiftBackupPrem" 3 | --------------------------------------------------------------------------------