mirror of
https://github.com/revanced/revanced-patcher.git
synced 2025-04-30 13:24:26 +02:00
161 lines
5.4 KiB
Kotlin
161 lines
5.4 KiB
Kotlin
package app.revanced.patcher
|
|
|
|
import app.revanced.patcher.patch.*
|
|
import kotlinx.coroutines.flow.flow
|
|
import java.io.Closeable
|
|
import java.util.logging.Logger
|
|
|
|
/**
|
|
* A Patcher.
|
|
*
|
|
* @param config The configuration to use for the patcher.
|
|
*/
|
|
class Patcher(private val config: PatcherConfig) : Closeable {
|
|
private val logger = Logger.getLogger(this::class.java.name)
|
|
|
|
/**
|
|
* The context containing the current state of the patcher.
|
|
*/
|
|
val context = PatcherContext(config)
|
|
|
|
init {
|
|
context.resourceContext.decodeResources(ResourcePatchContext.ResourceMode.NONE)
|
|
}
|
|
|
|
/**
|
|
* Add patches.
|
|
*
|
|
* @param patches The patches to add.
|
|
*/
|
|
operator fun plusAssign(patches: Set<Patch<*>>) {
|
|
// Add all patches to the executablePatches set.
|
|
context.executablePatches += patches
|
|
|
|
// Add all patches and their dependencies to the allPatches set.
|
|
patches.forEach { patch ->
|
|
fun Patch<*>.addRecursively() =
|
|
also(context.allPatches::add).dependencies.forEach(Patch<*>::addRecursively)
|
|
|
|
patch.addRecursively()
|
|
}
|
|
|
|
context.allPatches.let { allPatches ->
|
|
// Check, if what kind of resource mode is required.
|
|
config.resourceMode = if (allPatches.any { patch -> patch.anyRecursively { it is ResourcePatch } }) {
|
|
ResourcePatchContext.ResourceMode.FULL
|
|
} else if (allPatches.any { patch -> patch.anyRecursively { it is RawResourcePatch } }) {
|
|
ResourcePatchContext.ResourceMode.RAW_ONLY
|
|
} else {
|
|
ResourcePatchContext.ResourceMode.NONE
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute added patches.
|
|
*
|
|
* @return A flow of [PatchResult]s.
|
|
*/
|
|
operator fun invoke() = flow {
|
|
fun Patch<*>.execute(
|
|
executedPatches: LinkedHashMap<Patch<*>, PatchResult>,
|
|
): PatchResult {
|
|
// If the patch was executed before or failed, return it's the result.
|
|
executedPatches[this]?.let { patchResult ->
|
|
patchResult.exception ?: return patchResult
|
|
|
|
return PatchResult(this, PatchException("The patch '$this' failed previously"))
|
|
}
|
|
|
|
// Recursively execute all dependency patches.
|
|
dependencies.forEach { dependency ->
|
|
dependency.execute(executedPatches).exception?.let {
|
|
return PatchResult(
|
|
this,
|
|
PatchException(
|
|
"The patch \"$this\" depends on \"$dependency\", which raised an exception:\n${it.stackTraceToString()}",
|
|
),
|
|
)
|
|
}
|
|
}
|
|
|
|
// Execute the patch.
|
|
return try {
|
|
execute(context)
|
|
|
|
PatchResult(this)
|
|
} catch (exception: PatchException) {
|
|
PatchResult(this, exception)
|
|
} catch (exception: Exception) {
|
|
PatchResult(this, PatchException(exception))
|
|
}.also { executedPatches[this] = it }
|
|
}
|
|
|
|
// Prevent decoding the app manifest twice if it is not needed.
|
|
if (config.resourceMode != ResourcePatchContext.ResourceMode.NONE) {
|
|
context.resourceContext.decodeResources(config.resourceMode)
|
|
}
|
|
|
|
logger.info("Initializing lookup maps")
|
|
|
|
// Accessing the lazy lookup maps to initialize them.
|
|
context.bytecodeContext.lookupMaps
|
|
|
|
logger.info("Executing patches")
|
|
|
|
val executedPatches = LinkedHashMap<Patch<*>, PatchResult>()
|
|
|
|
context.executablePatches.sortedBy { it.name }.forEach { patch ->
|
|
val patchResult = patch.execute(executedPatches)
|
|
|
|
// If an exception occurred or the patch has no finalize block, emit the result.
|
|
if (patchResult.exception != null || patch.finalizeBlock == null) {
|
|
emit(patchResult)
|
|
}
|
|
}
|
|
|
|
val succeededPatchesWithFinalizeBlock = executedPatches.values.filter {
|
|
it.exception == null && it.patch.finalizeBlock != null
|
|
}
|
|
|
|
succeededPatchesWithFinalizeBlock.asReversed().forEach { executionResult ->
|
|
val patch = executionResult.patch
|
|
|
|
val result =
|
|
try {
|
|
patch.finalize(context)
|
|
|
|
executionResult
|
|
} catch (exception: PatchException) {
|
|
PatchResult(patch, exception)
|
|
} catch (exception: Exception) {
|
|
PatchResult(patch, PatchException(exception))
|
|
}
|
|
|
|
if (result.exception != null) {
|
|
emit(
|
|
PatchResult(
|
|
patch,
|
|
PatchException(
|
|
"The patch \"$patch\" raised an exception: ${result.exception.stackTraceToString()}",
|
|
result.exception,
|
|
),
|
|
),
|
|
)
|
|
} else if (patch in context.executablePatches) {
|
|
emit(result)
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun close() = context.close()
|
|
|
|
/**
|
|
* Compile and save patched APK files.
|
|
*
|
|
* @return The [PatcherResult] containing the patched APK files.
|
|
*/
|
|
@OptIn(InternalApi::class)
|
|
fun get() = PatcherResult(context.bytecodeContext.get(), context.resourceContext.get())
|
|
}
|