diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml new file mode 100644 index 0000000000..ec1e0714e9 --- /dev/null +++ b/.github/workflows/release-build.yml @@ -0,0 +1,43 @@ +name: Release Build + +on: + push: + tags: + - "v*" + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set env + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + + - name: Set up Java + run: echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV + + - name: Build with Gradle + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew assembleRelease --no-daemon + + - name: Sign APK + id: sign_apk + uses: ilharp/sign-android-release@v1 + with: + releaseDir: ./app/build/outputs/apk/release/ + signingKey: ${{ secrets.SIGNING_KEYSTORE }} + keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} + keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }} + keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }} + + - name: Add version to APK + run: mv ${{ steps.sign_apk.outputs.signedFile }} revanced-manager-${{ env.RELEASE_VERSION }}.apk + + - name: Publish release APK + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: false + files: revanced-manager-${{ env.RELEASE_VERSION }}.apk diff --git a/.github/workflows/update-documentation.yml b/.github/workflows/update-documentation.yml new file mode 100644 index 0000000000..77097e2fe6 --- /dev/null +++ b/.github/workflows/update-documentation.yml @@ -0,0 +1,19 @@ +name: Update documentation + +on: + push: + paths: + - docs/** + +jobs: + trigger: + runs-on: ubuntu-latest + name: Dispatch event to documentation repository + if: github.ref == 'refs/heads/main' + steps: + - uses: peter-evans/repository-dispatch@v2 + with: + token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }} + repository: revanced/revanced-documentation + event-type: update-documentation + client-payload: '{"repo": "${{ github.event.repository.name }}", "ref": "${{ github.ref }}"}' diff --git a/.gitignore b/.gitignore index 154ff109d8..05c971486d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,7 @@ *.iml .gradle /local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -/.idea/deploymentTargetDropDown.xml -/.idea/misc.xml -/.idea/gradle.xml +/.idea .DS_Store /build /captures diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d33521af..0000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 897c96afe8..0000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -ReVanced Manager \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b589d56e9f..0000000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml deleted file mode 100644 index d8e9561668..0000000000 --- a/.idea/discord.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 01940c92e0..0000000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index ed76bea38e..0000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index 217e5c51fb..0000000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index d673ade909..0000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f4c..0000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index c1b3706aaf..1a48684176 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,24 @@ By using Material 3 and Material You, we are ensuring that the app's user interf * **Better performance:** Jetpack Compose uses the power of the Android framework to provide smooth and fast performance, which enhances the user experience. * **Modern and efficient UI development:** Jetpack Compose provides a modern and efficient way of building UI, which makes it easier for developers to create beautiful and performant user interfaces. +## ๐Ÿ”ฝ Download + +You can obtain ReVanced Manager by downloading it from either [revanced.app/download](https://revanced.app/download) or [GitHub Releases](https://github.com/ReVanced/revanced-manager/releases) + +## ๐Ÿ“ Prerequisites + +For a list of prerequisites, refer to [docs/0_prerequisites.md](docs/0_prerequisites.md) + +## ๐Ÿ”ด Issues + +For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose). + +## ๐ŸŒ Translation + +[![Crowdin](https://badges.crowdin.net/revanced/localized.svg)](https://crowdin.com/project/revanced) + +We're accepting translations on [Crowdin](https://translate.revanced.app) + +## ๐Ÿ› ๏ธ Building Manager from source + +For instructions on how to build ReVanced Manager from source, refer to [docs/4_building.md](docs/4_building.md) \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0e7ea14e77..e5c3f4842a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -131,11 +131,8 @@ dependencies { ksp(libs.room.compiler) // ReVanced - implementation(libs.patcher) - - // Signing - implementation(libs.apksign) - implementation(libs.bcpkix.jdk18on) + implementation(libs.revanced.patcher) + implementation(libs.revanced.library) implementation(libs.libsu.core) implementation(libs.libsu.service) diff --git a/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json b/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json index 543d7a70ad..041d1dea4e 100644 --- a/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json +++ b/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "5515d164bc8f713201506d42a02d337f", + "identityHash": "371c7a84b122a2de8b660b35e6e9ce14", "entities": [ { "tableName": "patch_bundles", @@ -160,7 +160,7 @@ }, { "tableName": "downloaded_app", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `version` TEXT NOT NULL, `file` TEXT NOT NULL, PRIMARY KEY(`package_name`, `version`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `version` TEXT NOT NULL, `directory` TEXT NOT NULL, PRIMARY KEY(`package_name`, `version`))", "fields": [ { "fieldPath": "packageName", @@ -175,8 +175,8 @@ "notNull": true }, { - "fieldPath": "file", - "columnName": "file", + "fieldPath": "directory", + "columnName": "directory", "affinity": "TEXT", "notNull": true } @@ -300,7 +300,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5515d164bc8f713201506d42a02d337f')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '371c7a84b122a2de8b660b35e6e9ce14')" ] } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/MainActivity.kt b/app/src/main/java/app/revanced/manager/MainActivity.kt index 162ecff6eb..944656e32d 100644 --- a/app/src/main/java/app/revanced/manager/MainActivity.kt +++ b/app/src/main/java/app/revanced/manager/MainActivity.kt @@ -1,24 +1,36 @@ package app.revanced.manager +import android.content.ActivityNotFoundException +import android.content.Intent import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import app.revanced.manager.ui.component.AutoUpdatesDialog import app.revanced.manager.ui.destination.Destination import app.revanced.manager.ui.screen.AppInfoScreen -import app.revanced.manager.ui.screen.VersionSelectorScreen import app.revanced.manager.ui.screen.AppSelectorScreen import app.revanced.manager.ui.screen.DashboardScreen import app.revanced.manager.ui.screen.InstallerScreen import app.revanced.manager.ui.screen.PatchesSelectorScreen import app.revanced.manager.ui.screen.SettingsScreen +import app.revanced.manager.ui.screen.VersionSelectorScreen import app.revanced.manager.ui.theme.ReVancedManagerTheme import app.revanced.manager.ui.theme.Theme import app.revanced.manager.ui.viewmodel.MainViewModel +import app.revanced.manager.util.tag +import app.revanced.manager.util.toast import dev.olshevski.navigation.reimagined.AnimatedNavHost import dev.olshevski.navigation.reimagined.NavBackHandler import dev.olshevski.navigation.reimagined.navigate @@ -51,9 +63,48 @@ class MainActivity : ComponentActivity() { NavBackHandler(navController) - val showAutoUpdatesDialog by vm.prefs.showAutoUpdatesDialog.getAsState() - if (showAutoUpdatesDialog) { - AutoUpdatesDialog(vm::applyAutoUpdatePrefs) + val firstLaunch by vm.prefs.firstLaunch.getAsState() + + if (firstLaunch) { + var legacyActivityState by rememberSaveable { mutableStateOf(LegacyActivity.NOT_LAUNCHED) } + if (legacyActivityState == LegacyActivity.NOT_LAUNCHED) { + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { result: ActivityResult -> + if (result.resultCode == RESULT_OK) { + if (result.data != null) { + val jsonData = result.data!!.getStringExtra("data")!! + vm.applyLegacySettings(jsonData) + } + } else { + legacyActivityState = LegacyActivity.FAILED + toast(getString(R.string.legacy_import_failed)) + } + } + + val intent = Intent().apply { + setClassName( + "app.revanced.manager.flutter", + "app.revanced.manager.flutter.ExportSettingsActivity" + ) + } + + LaunchedEffect(Unit) { + try { + launcher.launch(intent) + } catch (e: Exception) { + if (e !is ActivityNotFoundException) { + toast(getString(R.string.legacy_import_failed)) + Log.e(tag, "Failed to launch legacy import activity: $e") + } + legacyActivityState = LegacyActivity.FAILED + } + } + + legacyActivityState = LegacyActivity.LAUNCHED + } else if (legacyActivityState == LegacyActivity.FAILED){ + AutoUpdatesDialog(vm::applyAutoUpdatePrefs) + } } AnimatedNavHost( @@ -120,4 +171,10 @@ class MainActivity : ComponentActivity() { } } } -} \ No newline at end of file + + private enum class LegacyActivity { + NOT_LAUNCHED, + LAUNCHED, + FAILED + } +} diff --git a/app/src/main/java/app/revanced/manager/data/platform/FileSystem.kt b/app/src/main/java/app/revanced/manager/data/platform/Filesystem.kt similarity index 81% rename from app/src/main/java/app/revanced/manager/data/platform/FileSystem.kt rename to app/src/main/java/app/revanced/manager/data/platform/Filesystem.kt index f037e8a6e6..ec01f09ba8 100644 --- a/app/src/main/java/app/revanced/manager/data/platform/FileSystem.kt +++ b/app/src/main/java/app/revanced/manager/data/platform/Filesystem.kt @@ -9,9 +9,18 @@ import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContracts import app.revanced.manager.util.RequestManageStorageContract -class FileSystem(private val app: Application) { +class Filesystem(private val app: Application) { val contentResolver = app.contentResolver // TODO: move Content Resolver operations to here. + /** + * A directory that gets cleared when the app restarts. + * Do not store paths to this directory in a parcel. + */ + val tempDir = app.cacheDir.resolve("ephemeral").apply { + deleteRecursively() + mkdirs() + } + fun externalFilesDir() = Environment.getExternalStorageDirectory().toPath() private fun usesManagePermission() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R diff --git a/app/src/main/java/app/revanced/manager/data/room/Converters.kt b/app/src/main/java/app/revanced/manager/data/room/Converters.kt index f8aa073d73..7de50382f2 100644 --- a/app/src/main/java/app/revanced/manager/data/room/Converters.kt +++ b/app/src/main/java/app/revanced/manager/data/room/Converters.kt @@ -16,5 +16,5 @@ class Converters { fun fileFromString(value: String) = File(value) @TypeConverter - fun fileToString(file: File): String = file.absolutePath + fun fileToString(file: File): String = file.path } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/data/room/apps/downloaded/DownloadedApp.kt b/app/src/main/java/app/revanced/manager/data/room/apps/downloaded/DownloadedApp.kt index a30063ffa4..60d1561df8 100644 --- a/app/src/main/java/app/revanced/manager/data/room/apps/downloaded/DownloadedApp.kt +++ b/app/src/main/java/app/revanced/manager/data/room/apps/downloaded/DownloadedApp.kt @@ -11,5 +11,5 @@ import java.io.File data class DownloadedApp( @ColumnInfo(name = "package_name") val packageName: String, @ColumnInfo(name = "version") val version: String, - @ColumnInfo(name = "file") val file: File, + @ColumnInfo(name = "directory") val directory: File, ) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/data/room/selection/SelectionDao.kt b/app/src/main/java/app/revanced/manager/data/room/selection/SelectionDao.kt index 2e288d9793..630c5d66e8 100644 --- a/app/src/main/java/app/revanced/manager/data/room/selection/SelectionDao.kt +++ b/app/src/main/java/app/revanced/manager/data/room/selection/SelectionDao.kt @@ -35,6 +35,9 @@ abstract class SelectionDao { @Query("DELETE FROM patch_selections WHERE patch_bundle = :uid") abstract suspend fun clearForPatchBundle(uid: Int) + @Query("DELETE FROM patch_selections WHERE package_name = :packageName") + abstract suspend fun clearForPackage(packageName: String) + @Query("DELETE FROM patch_selections") abstract suspend fun reset() diff --git a/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt b/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt index 37c152e54c..a5420a5caa 100644 --- a/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt +++ b/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt @@ -1,17 +1,19 @@ package app.revanced.manager.di -import app.revanced.manager.data.platform.FileSystem +import app.revanced.manager.data.platform.Filesystem import app.revanced.manager.data.platform.NetworkInfo import app.revanced.manager.domain.repository.* import app.revanced.manager.domain.worker.WorkerRepository import app.revanced.manager.network.api.ReVancedAPI +import org.koin.core.module.dsl.createdAtStart import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val repositoryModule = module { singleOf(::ReVancedAPI) - singleOf(::GithubRepository) - singleOf(::FileSystem) + singleOf(::Filesystem) { + createdAtStart() + } singleOf(::NetworkInfo) singleOf(::PatchBundlePersistenceRepository) singleOf(::PatchSelectionRepository) diff --git a/app/src/main/java/app/revanced/manager/di/ServiceModule.kt b/app/src/main/java/app/revanced/manager/di/ServiceModule.kt index 78b99c2725..c30a711f67 100644 --- a/app/src/main/java/app/revanced/manager/di/ServiceModule.kt +++ b/app/src/main/java/app/revanced/manager/di/ServiceModule.kt @@ -1,21 +1,11 @@ package app.revanced.manager.di -import app.revanced.manager.network.service.GithubService import app.revanced.manager.network.service.HttpService import app.revanced.manager.network.service.ReVancedService import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val serviceModule = module { - fun provideReVancedService( - client: HttpService, - ): ReVancedService { - return ReVancedService( - client = client, - ) - } - - single { provideReVancedService(get()) } + singleOf(::ReVancedService) singleOf(::HttpService) - singleOf(::GithubService) } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt b/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt index 4362caa523..826d1de541 100644 --- a/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt +++ b/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt @@ -2,19 +2,19 @@ package app.revanced.manager.domain.manager import android.app.Application import android.content.Context -import app.revanced.manager.util.signing.Signer -import app.revanced.manager.util.signing.SigningOptions +import app.revanced.library.ApkSigner +import app.revanced.library.ApkUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.io.ByteArrayInputStream import java.io.File +import java.io.InputStream import java.io.OutputStream import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardCopyOption -import kotlin.io.path.exists +import java.security.UnrecoverableKeyException class KeystoreManager(app: Application, private val prefs: PreferencesManager) { - companion object { + companion object Constants { /** * Default alias and password for the keystore. */ @@ -22,37 +22,55 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) { } private val keystorePath = - app.getDir("signing", Context.MODE_PRIVATE).resolve("manager.keystore").toPath() + app.getDir("signing", Context.MODE_PRIVATE).resolve("manager.keystore") private suspend fun updatePrefs(cn: String, pass: String) = prefs.edit { prefs.keystoreCommonName.value = cn prefs.keystorePass.value = pass } + private suspend fun signingOptions(path: File = keystorePath) = ApkUtils.SigningOptions( + keyStore = path, + keyStorePassword = null, + alias = prefs.keystoreCommonName.get(), + signer = prefs.keystoreCommonName.get(), + password = prefs.keystorePass.get() + ) + suspend fun sign(input: File, output: File) = withContext(Dispatchers.Default) { - Signer( - SigningOptions( - prefs.keystoreCommonName.get(), - prefs.keystorePass.get(), - keystorePath - ) - ).signApk( - input, - output - ) + ApkUtils.sign(input, output, signingOptions()) } suspend fun regenerate() = withContext(Dispatchers.Default) { - Signer(SigningOptions(DEFAULT, DEFAULT, keystorePath)).regenerateKeystore() + val ks = ApkSigner.newKeyStore( + listOf( + ApkSigner.KeyStoreEntry( + DEFAULT, DEFAULT + ) + ) + ) + keystorePath.outputStream().use { + ks.store(it, null) + } + updatePrefs(DEFAULT, DEFAULT) } - suspend fun import(cn: String, pass: String, keystore: Path): Boolean { - if (!Signer(SigningOptions(cn, pass, keystore)).canUnlock()) { + suspend fun import(cn: String, pass: String, keystore: InputStream): Boolean { + val keystoreData = keystore.readBytes() + + try { + val ks = ApkSigner.readKeyStore(ByteArrayInputStream(keystoreData), null) + + ApkSigner.readKeyCertificatePair(ks, cn, pass) + } catch (_: UnrecoverableKeyException) { + return false + } catch (_: IllegalArgumentException) { return false } + withContext(Dispatchers.IO) { - Files.copy(keystore, keystorePath, StandardCopyOption.REPLACE_EXISTING) + Files.write(keystorePath.toPath(), keystoreData) } updatePrefs(cn, pass) @@ -63,7 +81,7 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) { suspend fun export(target: OutputStream) { withContext(Dispatchers.IO) { - Files.copy(keystorePath, target) + Files.copy(keystorePath.toPath(), target) } } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt b/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt index 34d7af60e0..44f617940f 100644 --- a/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt +++ b/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt @@ -19,6 +19,9 @@ class PreferencesManager( val preferSplits = booleanPreference("prefer_splits", false) - val showAutoUpdatesDialog = booleanPreference("show_auto_updates_dialog", true) + val firstLaunch = booleanPreference("first_launch", true) val managerAutoUpdates = booleanPreference("manager_auto_updates", false) + + val disableSelectionWarning = booleanPreference("disable_selection_warning", false) + val enableSelectionWarningCountdown = booleanPreference("enable_selection_warning_countdown", true) } diff --git a/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt index 7035c6c4ba..fe339a2eda 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt @@ -1,34 +1,61 @@ package app.revanced.manager.domain.repository +import android.app.Application +import android.content.Context import app.revanced.manager.data.room.AppDatabase +import app.revanced.manager.data.room.AppDatabase.Companion.generateUid import app.revanced.manager.data.room.apps.downloaded.DownloadedApp +import app.revanced.manager.network.downloader.AppDownloader import kotlinx.coroutines.flow.distinctUntilChanged import java.io.File class DownloadedAppRepository( + app: Application, db: AppDatabase ) { + private val dir = app.getDir("downloaded-apps", Context.MODE_PRIVATE) private val dao = db.downloadedAppDao() fun getAll() = dao.getAllApps().distinctUntilChanged() - suspend fun get(packageName: String, version: String) = dao.get(packageName, version) + fun getApkFileForApp(app: DownloadedApp): File = getApkFileForDir(dir.resolve(app.directory)) + private fun getApkFileForDir(directory: File) = directory.listFiles()!!.first() + + suspend fun download( + app: AppDownloader.App, + preferSplits: Boolean, + onDownload: suspend (downloadProgress: Pair?) -> Unit = {}, + ): File { + this.get(app.packageName, app.version)?.let { downloaded -> + return getApkFileForApp(downloaded) + } + + // Converted integers cannot contain / or .. unlike the package name or version, so they are safer to use here. + val relativePath = File(generateUid().toString()) + val savePath = dir.resolve(relativePath).also { it.mkdirs() } + + try { + app.download(savePath, preferSplits, onDownload) - suspend fun add( - packageName: String, - version: String, - file: File - ) = dao.insert( - DownloadedApp( - packageName = packageName, - version = version, - file = file - ) - ) + dao.insert(DownloadedApp( + packageName = app.packageName, + version = app.version, + directory = relativePath, + )) + } catch (e: Exception) { + savePath.deleteRecursively() + throw e + } + + // Return the Apk file. + return getApkFileForDir(savePath) + } + + suspend fun get(packageName: String, version: String) = dao.get(packageName, version) suspend fun delete(downloadedApps: Collection) { downloadedApps.forEach { - it.file.deleteRecursively() + dir.resolve(it.directory).deleteRecursively() } dao.delete(downloadedApps) diff --git a/app/src/main/java/app/revanced/manager/domain/repository/GithubRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/GithubRepository.kt deleted file mode 100644 index 79587dfca4..0000000000 --- a/app/src/main/java/app/revanced/manager/domain/repository/GithubRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package app.revanced.manager.domain.repository - -import app.revanced.manager.network.service.GithubService - -// TODO: delete this when the revanced api adds download count. -class GithubRepository(private val service: GithubService) { - suspend fun getChangelog(repo: String) = service.getChangelog(repo) -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchSelectionRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchSelectionRepository.kt index cade429164..c34e5efd6b 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/PatchSelectionRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchSelectionRepository.kt @@ -25,6 +25,10 @@ class PatchSelectionRepository(db: AppDatabase) { ) }) + suspend fun clearSelection(packageName: String) { + dao.clearForPackage(packageName) + } + suspend fun reset() = dao.reset() suspend fun export(bundleUid: Int): SerializedSelection = dao.exportSelection(bundleUid) diff --git a/app/src/main/java/app/revanced/manager/network/downloader/APKMirror.kt b/app/src/main/java/app/revanced/manager/network/downloader/APKMirror.kt index 3a055ef576..30c6fbee62 100644 --- a/app/src/main/java/app/revanced/manager/network/downloader/APKMirror.kt +++ b/app/src/main/java/app/revanced/manager/network/downloader/APKMirror.kt @@ -171,7 +171,7 @@ class APKMirror : AppDownloader, KoinComponent { saveDirectory: File, preferSplit: Boolean, onDownload: suspend (downloadProgress: Pair?) -> Unit - ): File { + ) { val variants = httpClient.getHtml { url(apkMirror + downloadLink) } .div { withClass = "variants-table" @@ -246,18 +246,10 @@ class APKMirror : AppDownloader, KoinComponent { } } - val saveLocation = if (variant.apkType == APKType.BUNDLE) - saveDirectory.resolve(version).also { it.mkdirs() } - else - saveDirectory.resolve("$version.apk") + val targetFile = saveDirectory.resolve("base.apk") try { - val downloadLocation = if (variant.apkType == APKType.BUNDLE) - saveLocation.resolve("temp.zip") - else - saveLocation - - httpClient.download(downloadLocation) { + httpClient.download(targetFile) { url(apkMirror + downloadLink) onDownload { bytesSentTotal, contentLength -> onDownload(bytesSentTotal.div(100000).toFloat().div(10) to contentLength.div(100000).toFloat().div(10)) @@ -267,16 +259,11 @@ class APKMirror : AppDownloader, KoinComponent { if (variant.apkType == APKType.BUNDLE) { // TODO: Extract temp.zip - downloadLocation.delete() + targetFile.delete() } - } catch (e: Exception) { - saveLocation.deleteRecursively() - throw e } finally { onDownload(null) } - - return saveLocation } } diff --git a/app/src/main/java/app/revanced/manager/network/downloader/AppDownloader.kt b/app/src/main/java/app/revanced/manager/network/downloader/AppDownloader.kt index a6a17622f4..dcefa26e49 100644 --- a/app/src/main/java/app/revanced/manager/network/downloader/AppDownloader.kt +++ b/app/src/main/java/app/revanced/manager/network/downloader/AppDownloader.kt @@ -22,7 +22,6 @@ interface AppDownloader { saveDirectory: File, preferSplit: Boolean, onDownload: suspend (downloadProgress: Pair?) -> Unit = {} - ): File + ) } - } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt b/app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt index d97b335ce2..416e06295e 100644 --- a/app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt +++ b/app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt @@ -21,7 +21,8 @@ data class ReVancedReleaseMeta( val draft: Boolean, val prerelease: Boolean, @SerialName("created_at") val createdAt: String, - @SerialName("published_at") val publishedAt: String + @SerialName("published_at") val publishedAt: String, + val body: String, ) @Serializable diff --git a/app/src/main/java/app/revanced/manager/network/service/GithubService.kt b/app/src/main/java/app/revanced/manager/network/service/GithubService.kt deleted file mode 100644 index 2c293848ee..0000000000 --- a/app/src/main/java/app/revanced/manager/network/service/GithubService.kt +++ /dev/null @@ -1,15 +0,0 @@ -package app.revanced.manager.network.service - -import app.revanced.manager.network.dto.GithubChangelog -import app.revanced.manager.network.utils.APIResponse -import io.ktor.client.request.url -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -class GithubService(private val client: HttpService) { - suspend fun getChangelog(repo: String): APIResponse = withContext(Dispatchers.IO) { - client.request { - url("https://api.github.com/repos/revanced/$repo/releases/latest") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/Aligning.kt b/app/src/main/java/app/revanced/manager/patcher/Aligning.kt deleted file mode 100644 index d8a672d61e..0000000000 --- a/app/src/main/java/app/revanced/manager/patcher/Aligning.kt +++ /dev/null @@ -1,38 +0,0 @@ -package app.revanced.manager.patcher - -import app.revanced.manager.patcher.alignment.ZipAligner -import app.revanced.manager.patcher.alignment.zip.ZipFile -import app.revanced.manager.patcher.alignment.zip.structures.ZipEntry -import app.revanced.patcher.PatcherResult -import java.io.File - -// This is the same aligner used by the CLI. -// It will be removed eventually. -object Aligning { - fun align(result: PatcherResult, inputFile: File, outputFile: File) { - // logger.info("Aligning ${inputFile.name} to ${outputFile.name}") - - if (outputFile.exists()) outputFile.delete() - - ZipFile(outputFile).use { file -> - result.dexFiles.forEach { - file.addEntryCompressData( - ZipEntry.createWithName(it.name), - it.stream.readBytes() - ) - } - - result.resourceFile?.let { - file.copyEntriesFromFileAligned( - ZipFile(it), - ZipAligner::getEntryAlignment - ) - } - - file.copyEntriesFromFileAligned( - ZipFile(inputFile, readonly = true), - ZipAligner::getEntryAlignment - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/Session.kt b/app/src/main/java/app/revanced/manager/patcher/Session.kt index 54e028fad4..35b80e5e37 100644 --- a/app/src/main/java/app/revanced/manager/patcher/Session.kt +++ b/app/src/main/java/app/revanced/manager/patcher/Session.kt @@ -1,9 +1,10 @@ package app.revanced.manager.patcher +import app.revanced.library.ApkUtils import app.revanced.manager.ui.viewmodel.ManagerLogger import app.revanced.patcher.Patcher import app.revanced.patcher.PatcherOptions -import app.revanced.patcher.patch.PatchClass +import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.PatchResult import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -13,7 +14,7 @@ import java.nio.file.Files import java.nio.file.StandardCopyOption import java.util.logging.Logger -internal typealias PatchList = List +internal typealias PatchList = List> class Session( cacheDir: String, @@ -23,16 +24,17 @@ class Session( private val input: File, private val onStepSucceeded: suspend () -> Unit ) : Closeable { - private val temporary = File(cacheDir).resolve("manager").also { it.mkdirs() } + private val tempDir = File(cacheDir).resolve("patcher").also { it.mkdirs() } private val patcher = Patcher( PatcherOptions( inputFile = input, - resourceCachePath = temporary.resolve("aapt-resources"), + resourceCachePath = tempDir.resolve("aapt-resources"), frameworkFileDirectory = frameworkDir, aaptBinaryPath = aaptPath ) ) + private suspend fun Patcher.applyPatchesVerbose() { this.apply(true).collect { (patch, exception) -> if (exception == null) { @@ -69,7 +71,8 @@ class Session( logger.info("Writing patched files...") val result = patcher.get() - val aligned = temporary.resolve("aligned.apk").also { Aligning.align(result, input, it) } + val aligned = tempDir.resolve("aligned.apk") + ApkUtils.copyAligned(input, aligned, result) logger.info("Patched apk saved to $aligned") @@ -80,12 +83,12 @@ class Session( } override fun close() { - temporary.delete() + tempDir.deleteRecursively() patcher.close() } companion object { - operator fun PatchResult.component1() = patchName + operator fun PatchResult.component1() = patch.name operator fun PatchResult.component2() = exception } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt b/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt index 25e26be448..959768e62b 100644 --- a/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt +++ b/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt @@ -1,9 +1,14 @@ package app.revanced.manager.patcher.aapt import android.content.Context +import android.os.Build.SUPPORTED_ABIS as DEVICE_ABIS import java.io.File object Aapt { + private val WORKING_ABIS = setOf("arm64-v8a", "x86", "x86_64") + + fun supportsDevice() = (DEVICE_ABIS intersect WORKING_ABIS).isNotEmpty() + fun binary(context: Context): File? { return File(context.applicationInfo.nativeLibraryDir).resolveAapt() } diff --git a/app/src/main/java/app/revanced/manager/patcher/alignment/ZipAlign.kt b/app/src/main/java/app/revanced/manager/patcher/alignment/ZipAlign.kt deleted file mode 100644 index 4f8504c86c..0000000000 --- a/app/src/main/java/app/revanced/manager/patcher/alignment/ZipAlign.kt +++ /dev/null @@ -1,11 +0,0 @@ -package app.revanced.manager.patcher.alignment - -import app.revanced.manager.patcher.alignment.zip.structures.ZipEntry - -internal object ZipAligner { - private const val DEFAULT_ALIGNMENT = 4 - private const val LIBRARY_ALIGNMENT = 4096 - - fun getEntryAlignment(entry: ZipEntry): Int? = - if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/alignment/zip/Extensions.kt b/app/src/main/java/app/revanced/manager/patcher/alignment/zip/Extensions.kt deleted file mode 100644 index c022005fe1..0000000000 --- a/app/src/main/java/app/revanced/manager/patcher/alignment/zip/Extensions.kt +++ /dev/null @@ -1,33 +0,0 @@ -package app.revanced.manager.patcher.alignment.zip - -import java.io.DataInput -import java.io.DataOutput -import java.nio.ByteBuffer - -fun UInt.toLittleEndian() = - (((this.toInt() and 0xff000000.toInt()) shr 24) or ((this.toInt() and 0x00ff0000) shr 8) or ((this.toInt() and 0x0000ff00) shl 8) or (this.toInt() shl 24)).toUInt() - -fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort() - -fun UInt.toBigEndian() = (((this.toInt() and 0xff) shl 24) or ((this.toInt() and 0xff00) shl 8) - or ((this.toInt() and 0x00ff0000) ushr 8) or (this.toInt() ushr 24)).toUInt() - -fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort() - -fun ByteBuffer.getUShort() = this.getShort().toUShort() -fun ByteBuffer.getUInt() = this.getInt().toUInt() - -fun ByteBuffer.putUShort(ushort: UShort) = this.putShort(ushort.toShort()) -fun ByteBuffer.putUInt(uint: UInt) = this.putInt(uint.toInt()) - -fun DataInput.readUShort() = this.readShort().toUShort() -fun DataInput.readUInt() = this.readInt().toUInt() - -fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt()) -fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt()) - -fun DataInput.readUShortLE() = this.readUShort().toBigEndian() -fun DataInput.readUIntLE() = this.readUInt().toBigEndian() - -fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian()) -fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian()) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/alignment/zip/ZipFile.kt b/app/src/main/java/app/revanced/manager/patcher/alignment/zip/ZipFile.kt deleted file mode 100644 index a49ffed170..0000000000 --- a/app/src/main/java/app/revanced/manager/patcher/alignment/zip/ZipFile.kt +++ /dev/null @@ -1,188 +0,0 @@ -package app.revanced.manager.patcher.alignment.zip - -import app.revanced.manager.patcher.alignment.zip.structures.ZipEndRecord -import app.revanced.manager.patcher.alignment.zip.structures.ZipEntry - -import java.io.Closeable -import java.io.File -import java.io.IOException -import java.io.RandomAccessFile -import java.nio.ByteBuffer -import java.nio.channels.FileChannel -import java.util.zip.CRC32 -import java.util.zip.Deflater - -class ZipFile(file: File, private val readonly: Boolean = false) : Closeable { - var entries: MutableList = mutableListOf() - - private val filePointer: RandomAccessFile = RandomAccessFile(file, if (readonly) "r" else "rw") - private var CDNeedsRewrite = false - - private val compressionLevel = 5 - - init { - //if file isn't empty try to load entries - if (file.length() > 0) { - val endRecord = findEndRecord() - - if (endRecord.diskNumber > 0u || endRecord.totalEntries != endRecord.diskEntries) - throw IllegalArgumentException("Multi-file archives are not supported") - - entries = readEntries(endRecord).toMutableList() - } - - //seek back to start for writing - filePointer.seek(0) - } - - private fun assertWritable() { - if (readonly) throw IOException("Archive is read-only") - } - - private fun findEndRecord(): ZipEndRecord { - //look from end to start since end record is at the end - for (i in filePointer.length() - 1 downTo 0) { - filePointer.seek(i) - //possible beginning of signature - if (filePointer.readByte() == 0x50.toByte()) { - //seek back to get the full int - filePointer.seek(i) - val possibleSignature = filePointer.readUIntLE() - if (possibleSignature == ZipEndRecord.ECD_SIGNATURE) { - filePointer.seek(i) - return ZipEndRecord.fromECD(filePointer) - } - } - } - - throw Exception("Couldn't find end record") - } - - private fun readEntries(endRecord: ZipEndRecord): List { - filePointer.seek(endRecord.centralDirectoryStartOffset.toLong()) - - val numberOfEntries = endRecord.diskEntries.toInt() - - return buildList(numberOfEntries) { - for (i in 1..numberOfEntries) { - add( - ZipEntry.fromCDE(filePointer).also - { - //for some reason the local extra field can be different from the central one - it.readLocalExtra( - filePointer.channel.map( - FileChannel.MapMode.READ_ONLY, - it.localHeaderOffset.toLong() + 28, - 2 - ) - ) - }) - } - } - } - - private fun writeCD() { - val CDStart = filePointer.channel.position().toUInt() - - entries.forEach { - filePointer.channel.write(it.toCDE()) - } - - val entriesCount = entries.size.toUShort() - - val endRecord = ZipEndRecord( - 0u, - 0u, - entriesCount, - entriesCount, - filePointer.channel.position().toUInt() - CDStart, - CDStart, - "" - ) - - filePointer.channel.write(endRecord.toECD()) - } - - private fun addEntry(entry: ZipEntry, data: ByteBuffer) { - CDNeedsRewrite = true - - entry.localHeaderOffset = filePointer.channel.position().toUInt() - - filePointer.channel.write(entry.toLFH()) - filePointer.channel.write(data) - - entries.add(entry) - } - - fun addEntryCompressData(entry: ZipEntry, data: ByteArray) { - assertWritable() - - val compressor = Deflater(compressionLevel, true) - compressor.setInput(data) - compressor.finish() - - val uncompressedSize = data.size - val compressedData = - ByteArray(uncompressedSize) //i'm guessing compression won't make the data bigger - - val compressedDataLength = compressor.deflate(compressedData) - val compressedBuffer = - ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray()) - - compressor.end() - - val crc = CRC32() - crc.update(data) - - entry.compression = 8u //deflate compression - entry.uncompressedSize = uncompressedSize.toUInt() - entry.compressedSize = compressedDataLength.toUInt() - entry.crc32 = crc.value.toUInt() - - addEntry(entry, compressedBuffer) - } - - private fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) { - assertWritable() - - alignment?.let { - //calculate where data would end up - val dataOffset = filePointer.filePointer + entry.LFHSize - - val mod = dataOffset % alignment - - //wrong alignment - if (mod != 0L) { - //add padding at end of extra field - entry.localExtraField = - entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt()) - } - } - - addEntry(entry, data) - } - - fun getDataForEntry(entry: ZipEntry): ByteBuffer { - return filePointer.channel.map( - FileChannel.MapMode.READ_ONLY, - entry.dataOffset.toLong(), - entry.compressedSize.toLong() - ) - } - - fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) { - assertWritable() - - for (entry in file.entries) { - if (entries.any { it.fileName == entry.fileName }) continue //don't add duplicates - - val data = file.getDataForEntry(entry) - addEntryCopyData(entry, data, entryAlignment(entry)) - } - } - - override fun close() { - if (CDNeedsRewrite) writeCD() - filePointer.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/alignment/zip/structures/ZipEndRecord.kt b/app/src/main/java/app/revanced/manager/patcher/alignment/zip/structures/ZipEndRecord.kt deleted file mode 100644 index 06555cd3fc..0000000000 --- a/app/src/main/java/app/revanced/manager/patcher/alignment/zip/structures/ZipEndRecord.kt +++ /dev/null @@ -1,77 +0,0 @@ -package app.revanced.manager.patcher.alignment.zip.structures - -import app.revanced.manager.patcher.alignment.zip.putUInt -import app.revanced.manager.patcher.alignment.zip.putUShort -import app.revanced.manager.patcher.alignment.zip.readUIntLE -import app.revanced.manager.patcher.alignment.zip.readUShortLE -import java.io.DataInput -import java.nio.ByteBuffer -import java.nio.ByteOrder - -data class ZipEndRecord( - val diskNumber: UShort, - val startingDiskNumber: UShort, - val diskEntries: UShort, - val totalEntries: UShort, - val centralDirectorySize: UInt, - val centralDirectoryStartOffset: UInt, - val fileComment: String, -) { - - companion object { - const val ECD_HEADER_SIZE = 22 - const val ECD_SIGNATURE = 0x06054b50u - - fun fromECD(input: DataInput): ZipEndRecord { - val signature = input.readUIntLE() - - if (signature != ECD_SIGNATURE) - throw IllegalArgumentException("Input doesn't start with end record signature") - - val diskNumber = input.readUShortLE() - val startingDiskNumber = input.readUShortLE() - val diskEntries = input.readUShortLE() - val totalEntries = input.readUShortLE() - val centralDirectorySize = input.readUIntLE() - val centralDirectoryStartOffset = input.readUIntLE() - val fileCommentLength = input.readUShortLE() - var fileComment = "" - - if (fileCommentLength > 0u) { - val fileCommentBytes = ByteArray(fileCommentLength.toInt()) - input.readFully(fileCommentBytes) - fileComment = fileCommentBytes.toString(Charsets.UTF_8) - } - - return ZipEndRecord( - diskNumber, - startingDiskNumber, - diskEntries, - totalEntries, - centralDirectorySize, - centralDirectoryStartOffset, - fileComment - ) - } - } - - fun toECD(): ByteBuffer { - val commentBytes = fileComment.toByteArray(Charsets.UTF_8) - - val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size).also { it.order(ByteOrder.LITTLE_ENDIAN) } - - buffer.putUInt(ECD_SIGNATURE) - buffer.putUShort(diskNumber) - buffer.putUShort(startingDiskNumber) - buffer.putUShort(diskEntries) - buffer.putUShort(totalEntries) - buffer.putUInt(centralDirectorySize) - buffer.putUInt(centralDirectoryStartOffset) - buffer.putUShort(commentBytes.size.toUShort()) - - buffer.put(commentBytes) - - buffer.flip() - return buffer - } -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/alignment/zip/structures/ZipEntry.kt b/app/src/main/java/app/revanced/manager/patcher/alignment/zip/structures/ZipEntry.kt deleted file mode 100644 index f070386c2f..0000000000 --- a/app/src/main/java/app/revanced/manager/patcher/alignment/zip/structures/ZipEntry.kt +++ /dev/null @@ -1,189 +0,0 @@ -package app.revanced.manager.patcher.alignment.zip.structures - -import app.revanced.manager.patcher.alignment.zip.* -import java.io.DataInput -import java.nio.ByteBuffer -import java.nio.ByteOrder - -data class ZipEntry( - val version: UShort, - val versionNeeded: UShort, - val flags: UShort, - var compression: UShort, - val modificationTime: UShort, - val modificationDate: UShort, - var crc32: UInt, - var compressedSize: UInt, - var uncompressedSize: UInt, - val diskNumber: UShort, - val internalAttributes: UShort, - val externalAttributes: UInt, - var localHeaderOffset: UInt, - val fileName: String, - val extraField: ByteArray, - val fileComment: String, - var localExtraField: ByteArray = ByteArray(0), //separate for alignment -) { - val LFHSize: Int - get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size - - val dataOffset: UInt - get() = localHeaderOffset + LFHSize.toUInt() - - companion object { - const val CDE_HEADER_SIZE = 46 - const val CDE_SIGNATURE = 0x02014b50u - - const val LFH_HEADER_SIZE = 30 - const val LFH_SIGNATURE = 0x04034b50u - - fun createWithName(fileName: String): ZipEntry { - return ZipEntry( - 0x1403u, //made by unix, version 20 - 0u, - 0u, - 0u, - 0x0821u, //seems to be static time google uses, no idea - 0x0221u, //same as above - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - fileName, - ByteArray(0), - "" - ) - } - - fun fromCDE(input: DataInput): ZipEntry { - val signature = input.readUIntLE() - - if (signature != CDE_SIGNATURE) - throw IllegalArgumentException("Input doesn't start with central directory entry signature") - - val version = input.readUShortLE() - val versionNeeded = input.readUShortLE() - var flags = input.readUShortLE() - val compression = input.readUShortLE() - val modificationTime = input.readUShortLE() - val modificationDate = input.readUShortLE() - val crc32 = input.readUIntLE() - val compressedSize = input.readUIntLE() - val uncompressedSize = input.readUIntLE() - val fileNameLength = input.readUShortLE() - var fileName = "" - val extraFieldLength = input.readUShortLE() - val extraField = ByteArray(extraFieldLength.toInt()) - val fileCommentLength = input.readUShortLE() - var fileComment = "" - val diskNumber = input.readUShortLE() - val internalAttributes = input.readUShortLE() - val externalAttributes = input.readUIntLE() - val localHeaderOffset = input.readUIntLE() - - val variableFieldsLength = - fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt() - - if (variableFieldsLength > 0) { - val fileNameBytes = ByteArray(fileNameLength.toInt()) - input.readFully(fileNameBytes) - fileName = fileNameBytes.toString(Charsets.UTF_8) - - input.readFully(extraField) - - val fileCommentBytes = ByteArray(fileCommentLength.toInt()) - input.readFully(fileCommentBytes) - fileComment = fileCommentBytes.toString(Charsets.UTF_8) - } - - flags = (flags and 0b1000u.inv() - .toUShort()) //disable data descriptor flag as they are not used - - return ZipEntry( - version, - versionNeeded, - flags, - compression, - modificationTime, - modificationDate, - crc32, - compressedSize, - uncompressedSize, - diskNumber, - internalAttributes, - externalAttributes, - localHeaderOffset, - fileName, - extraField, - fileComment, - ) - } - } - - fun readLocalExtra(buffer: ByteBuffer) { - buffer.order(ByteOrder.LITTLE_ENDIAN) - localExtraField = ByteArray(buffer.getUShort().toInt()) - } - - fun toLFH(): ByteBuffer { - val nameBytes = fileName.toByteArray(Charsets.UTF_8) - - val buffer = ByteBuffer.allocate(LFH_HEADER_SIZE + nameBytes.size + localExtraField.size) - .also { it.order(ByteOrder.LITTLE_ENDIAN) } - - buffer.putUInt(LFH_SIGNATURE) - buffer.putUShort(versionNeeded) - buffer.putUShort(flags) - buffer.putUShort(compression) - buffer.putUShort(modificationTime) - buffer.putUShort(modificationDate) - buffer.putUInt(crc32) - buffer.putUInt(compressedSize) - buffer.putUInt(uncompressedSize) - buffer.putUShort(nameBytes.size.toUShort()) - buffer.putUShort(localExtraField.size.toUShort()) - - buffer.put(nameBytes) - buffer.put(localExtraField) - - buffer.flip() - return buffer - } - - fun toCDE(): ByteBuffer { - val nameBytes = fileName.toByteArray(Charsets.UTF_8) - val commentBytes = fileComment.toByteArray(Charsets.UTF_8) - - val buffer = - ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size) - .also { it.order(ByteOrder.LITTLE_ENDIAN) } - - buffer.putUInt(CDE_SIGNATURE) - buffer.putUShort(version) - buffer.putUShort(versionNeeded) - buffer.putUShort(flags) - buffer.putUShort(compression) - buffer.putUShort(modificationTime) - buffer.putUShort(modificationDate) - buffer.putUInt(crc32) - buffer.putUInt(compressedSize) - buffer.putUInt(uncompressedSize) - buffer.putUShort(nameBytes.size.toUShort()) - buffer.putUShort(extraField.size.toUShort()) - buffer.putUShort(commentBytes.size.toUShort()) - buffer.putUShort(diskNumber) - buffer.putUShort(internalAttributes) - buffer.putUInt(externalAttributes) - buffer.putUInt(localHeaderOffset) - - buffer.put(nameBytes) - buffer.put(extraField) - buffer.put(commentBytes) - - buffer.flip() - return buffer - } -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt b/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt index 08b3c33ddf..7c5b2ffd1e 100644 --- a/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt +++ b/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt @@ -3,16 +3,15 @@ package app.revanced.manager.patcher.patch import android.util.Log import app.revanced.manager.util.tag import app.revanced.patcher.PatchBundleLoader -import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages -import app.revanced.patcher.patch.PatchClass +import app.revanced.patcher.patch.Patch import java.io.File -class PatchBundle(private val loader: Iterable, val integrations: File?) { +class PatchBundle(private val loader: Iterable>, val integrations: File?) { constructor(bundleJar: File, integrations: File?) : this( - object : Iterable { - private fun load(): List = PatchBundleLoader.Dex(bundleJar) + object : Iterable> { + private fun load(): Iterable> = PatchBundleLoader.Dex(bundleJar, optimizedDexDirectory = null) - override fun iterator() = load().iterator() + override fun iterator(): Iterator> = load().iterator() }, integrations ) { diff --git a/app/src/main/java/app/revanced/manager/patcher/patch/PatchInfo.kt b/app/src/main/java/app/revanced/manager/patcher/patch/PatchInfo.kt index 20d1c88677..8002fa99df 100644 --- a/app/src/main/java/app/revanced/manager/patcher/patch/PatchInfo.kt +++ b/app/src/main/java/app/revanced/manager/patcher/patch/PatchInfo.kt @@ -1,50 +1,70 @@ package app.revanced.manager.patcher.patch import androidx.compose.runtime.Immutable -import app.revanced.patcher.annotation.Package -import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages -import app.revanced.patcher.extensions.PatchExtensions.dependencies -import app.revanced.patcher.extensions.PatchExtensions.description -import app.revanced.patcher.extensions.PatchExtensions.include -import app.revanced.patcher.extensions.PatchExtensions.options -import app.revanced.patcher.extensions.PatchExtensions.patchName -import app.revanced.patcher.patch.PatchClass -import app.revanced.patcher.patch.PatchOption +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.options.PatchOption import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toImmutableSet data class PatchInfo( val name: String, val description: String?, - val dependencies: ImmutableList?, val include: Boolean, val compatiblePackages: ImmutableList?, val options: ImmutableList