You've already forked revanced-patcher
mirror of
https://github.com/revanced/revanced-patcher
synced 2025-09-17 07:30:49 +02:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fb5b82da4e | ||
![]() |
5970e32aa5 | ||
![]() |
0f00d33f4e | ||
![]() |
83187c9edd | ||
![]() |
79d70cff4b | ||
![]() |
6f72c4c4c0 | ||
![]() |
c8eedac4d9 | ||
![]() |
60a8278ae8 | ||
![]() |
ada5a033de | ||
![]() |
109b8a296d | ||
![]() |
e2faf4ca9b | ||
![]() |
2134182a0e | ||
![]() |
d8b5b8bb7c | ||
![]() |
49970b5926 | ||
![]() |
c1fbd8cf8c | ||
![]() |
a467fbb704 | ||
![]() |
5a4bd7a76e | ||
![]() |
e5ca86fac6 | ||
![]() |
68d9e9f02c | ||
![]() |
494a9a09ac | ||
![]() |
06a88839de | ||
![]() |
4c41b955df | ||
![]() |
614e555f4c | ||
![]() |
d6ed06a327 | ||
![]() |
2fc4ec4021 | ||
![]() |
f565c4f6a7 | ||
![]() |
35749454ab | ||
![]() |
2b492e7a5e | ||
![]() |
081a5a6849 | ||
![]() |
852ae7d8d1 |
@@ -7,7 +7,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@semantic-release/commit-analyzer",
|
["@semantic-release/commit-analyzer", {
|
||||||
|
"releaseRules": [
|
||||||
|
{"type": "build", "release": "patch"}
|
||||||
|
]
|
||||||
|
}],
|
||||||
"@semantic-release/release-notes-generator",
|
"@semantic-release/release-notes-generator",
|
||||||
"@semantic-release/changelog",
|
"@semantic-release/changelog",
|
||||||
"gradle-semantic-release-plugin",
|
"gradle-semantic-release-plugin",
|
||||||
|
63
CHANGELOG.md
63
CHANGELOG.md
@@ -1,3 +1,66 @@
|
|||||||
|
# [2.2.0](https://github.com/revanced/revanced-patcher/compare/v2.1.2...v2.2.0) (2022-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* DomFileEditor opening in- and output streams on the same file ([83187c9](https://github.com/revanced/revanced-patcher/commit/83187c9edd7b088bc18960c5eb9a2042ca536b5f))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* remove deprecated functions ([ada5a03](https://github.com/revanced/revanced-patcher/commit/ada5a033de3cf94e7255ec2d522520f86431f001))
|
||||||
|
* streams overload for `XmlFileHolder` ([6f72c4c](https://github.com/revanced/revanced-patcher/commit/6f72c4c4c051e48c8d03d2a7b2cfc1c53028ed86))
|
||||||
|
|
||||||
|
# [2.2.0-dev.3](https://github.com/revanced/revanced-patcher/compare/v2.2.0-dev.2...v2.2.0-dev.3) (2022-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* DomFileEditor opening in- and output streams on the same file ([83187c9](https://github.com/revanced/revanced-patcher/commit/83187c9edd7b088bc18960c5eb9a2042ca536b5f))
|
||||||
|
|
||||||
|
# [2.2.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v2.2.0-dev.1...v2.2.0-dev.2) (2022-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* streams overload for `XmlFileHolder` ([6f72c4c](https://github.com/revanced/revanced-patcher/commit/6f72c4c4c051e48c8d03d2a7b2cfc1c53028ed86))
|
||||||
|
|
||||||
|
# [2.2.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v2.1.2...v2.2.0-dev.1) (2022-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* remove deprecated functions ([ada5a03](https://github.com/revanced/revanced-patcher/commit/ada5a033de3cf94e7255ec2d522520f86431f001))
|
||||||
|
|
||||||
|
## [2.1.2](https://github.com/revanced/revanced-patcher/compare/v2.1.1...v2.1.2) (2022-06-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* invert fingerprint resolution condition of `customFingerprint` ([e2faf4c](https://github.com/revanced/revanced-patcher/commit/e2faf4ca9b6de23300b20ab471ee9dc365b04339))
|
||||||
|
|
||||||
|
## [2.1.1](https://github.com/revanced/revanced-patcher/compare/v2.1.0...v2.1.1) (2022-06-28)
|
||||||
|
|
||||||
|
# [2.1.0](https://github.com/revanced/revanced-patcher/compare/v2.0.4...v2.1.0) (2022-06-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* log failed patches due to failed dependencies ([a467fbb](https://github.com/revanced/revanced-patcher/commit/a467fbb704eebe812cdec14025398dab2af43959))
|
||||||
|
|
||||||
|
## [2.0.4](https://github.com/revanced/revanced-patcher/compare/v2.0.3...v2.0.4) (2022-06-27)
|
||||||
|
|
||||||
|
## [2.0.3](https://github.com/revanced/revanced-patcher/compare/v2.0.2...v2.0.3) (2022-06-27)
|
||||||
|
|
||||||
|
## [2.0.2](https://github.com/revanced/revanced-patcher/compare/v2.0.1...v2.0.2) (2022-06-27)
|
||||||
|
|
||||||
|
## [2.0.1](https://github.com/revanced/revanced-patcher/compare/v2.0.0...v2.0.1) (2022-06-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* use `Exception` instead of `MethodNotFoundException` ([2fc4ec4](https://github.com/revanced/revanced-patcher/commit/2fc4ec40217a917ea6106ddc87be332f725aa13c))
|
||||||
|
|
||||||
# [2.0.0](https://github.com/revanced/revanced-patcher/compare/v1.11.0...v2.0.0) (2022-06-26)
|
# [2.0.0](https://github.com/revanced/revanced-patcher/compare/v1.11.0...v2.0.0) (2022-06-26)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -21,11 +21,10 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
|
||||||
|
|
||||||
implementation("xpp3:xpp3:1.1.4c")
|
implementation("xpp3:xpp3:1.1.4c")
|
||||||
implementation("org.smali:smali:2.5.2")
|
implementation("org.smali:smali:2.5.2")
|
||||||
implementation("org.apktool:apktool-lib:2.6.5-SNAPSHOT")
|
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
||||||
|
implementation("org.apktool:apktool-lib:2.6.9-SNAPSHOT")
|
||||||
|
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
}
|
}
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 2.0.0
|
version = 2.2.0
|
||||||
|
@@ -241,31 +241,35 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
/**
|
/**
|
||||||
* Apply a [patch] and its dependencies recursively.
|
* Apply a [patch] and its dependencies recursively.
|
||||||
* @param patch The [patch] to apply.
|
* @param patch The [patch] to apply.
|
||||||
* @param appliedPatches A list of [patch] names, to prevent applying [patch]es twice.
|
* @param appliedPatches A map of [patch]es paired to a boolean indicating their success, to prevent infinite recursion.
|
||||||
* @return The result of executing the [patch].
|
* @return The result of executing the [patch].
|
||||||
*/
|
*/
|
||||||
private fun applyPatch(
|
private fun applyPatch(
|
||||||
patch: Class<out Patch<Data>>, appliedPatches: MutableList<String>
|
patch: Class<out Patch<Data>>,
|
||||||
|
appliedPatches: MutableMap<String, Boolean>
|
||||||
): PatchResult {
|
): PatchResult {
|
||||||
val patchName = patch.patchName
|
val patchName = patch.patchName
|
||||||
|
|
||||||
// if the patch has already applied silently skip it
|
// if the patch has already applied silently skip it
|
||||||
if (appliedPatches.contains(patchName)) {
|
if (appliedPatches.contains(patchName)) {
|
||||||
logger.trace("Skipping patch $patchName because it has already been applied")
|
if (!appliedPatches[patchName]!!)
|
||||||
|
return PatchResultError("'$patchName' did not succeed previously")
|
||||||
|
|
||||||
|
logger.trace("Skipping '$patchName' because it has already been applied")
|
||||||
|
|
||||||
return PatchResultSuccess()
|
return PatchResultSuccess()
|
||||||
}
|
}
|
||||||
appliedPatches.add(patchName)
|
|
||||||
|
|
||||||
// recursively apply all dependency patches
|
// recursively apply all dependency patches
|
||||||
patch.dependencies?.forEach {
|
patch.dependencies?.forEach {
|
||||||
val patchDependency = it.java
|
val patchDependency = it.java
|
||||||
|
|
||||||
val result = applyPatch(patchDependency, appliedPatches)
|
val result = applyPatch(patchDependency, appliedPatches)
|
||||||
|
|
||||||
if (result.isSuccess()) return@forEach
|
if (result.isSuccess()) return@forEach
|
||||||
|
|
||||||
val errorMessage = result.error()!!.message
|
val errorMessage = result.error()!!.message
|
||||||
return PatchResultError("$patchName depends on ${patchDependency.patchName} but the following error was raised: $errorMessage")
|
return PatchResultError("'$patchName' depends on '${patchDependency.patchName}' but the following error was raised: $errorMessage")
|
||||||
}
|
}
|
||||||
|
|
||||||
val patchInstance = patch.getDeclaredConstructor().newInstance()
|
val patchInstance = patch.getDeclaredConstructor().newInstance()
|
||||||
@@ -273,7 +277,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
// if the current patch is a resource patch but resource patching is disabled, return an error
|
// if the current patch is a resource patch but resource patching is disabled, return an error
|
||||||
val isResourcePatch = patchInstance is ResourcePatch
|
val isResourcePatch = patchInstance is ResourcePatch
|
||||||
if (!options.patchResources && isResourcePatch) {
|
if (!options.patchResources && isResourcePatch) {
|
||||||
return PatchResultError("$patchName is a resource patch, but resource patching is disabled.")
|
return PatchResultError("'$patchName' is a resource patch, but resource patching is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: find a solution for this
|
// TODO: find a solution for this
|
||||||
@@ -285,11 +289,14 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
bytecodeData
|
bytecodeData
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace("Executing patch $patchName of type: ${if (isResourcePatch) "resource" else "bytecode"}")
|
logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}")
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
patchInstance.execute(data)
|
val result = patchInstance.execute(data)
|
||||||
|
appliedPatches[patchName] = result.isSuccess()
|
||||||
|
result
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
appliedPatches[patchName] = false
|
||||||
PatchResultError(e)
|
PatchResultError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -301,7 +308,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
*/
|
*/
|
||||||
fun applyPatches(stopOnError: Boolean = false) = sequence {
|
fun applyPatches(stopOnError: Boolean = false) = sequence {
|
||||||
logger.trace("Applying all patches")
|
logger.trace("Applying all patches")
|
||||||
val appliedPatches = mutableListOf<String>()
|
val appliedPatches = mutableMapOf<String, Boolean>() // first is success, second is name
|
||||||
|
|
||||||
for (patch in data.patches) {
|
for (patch in data.patches) {
|
||||||
val patchResult = applyPatch(patch, appliedPatches)
|
val patchResult = applyPatch(patch, appliedPatches)
|
||||||
|
@@ -13,9 +13,7 @@ data class PatcherData(
|
|||||||
internal val resourceCacheDirectory: String,
|
internal val resourceCacheDirectory: String,
|
||||||
val packageMetadata: PackageMetadata
|
val packageMetadata: PackageMetadata
|
||||||
) {
|
) {
|
||||||
|
|
||||||
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
||||||
|
|
||||||
internal val bytecodeData = BytecodeData(internalClasses)
|
internal val bytecodeData = BytecodeData(internalClasses)
|
||||||
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
||||||
}
|
}
|
@@ -28,6 +28,15 @@ class BytecodeData(
|
|||||||
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
||||||
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||||
classes.find(predicate)?.let { proxy(it) }
|
classes.find(predicate)?.let { proxy(it) }
|
||||||
|
|
||||||
|
fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
||||||
|
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
||||||
|
if (proxy == null) {
|
||||||
|
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
||||||
|
this.classes.add(proxy)
|
||||||
|
}
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class MethodNotFoundException(s: String) : Exception(s)
|
internal class MethodNotFoundException(s: String) : Exception(s)
|
||||||
@@ -58,12 +67,3 @@ internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BytecodeData.proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
|
||||||
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
|
||||||
if (proxy == null) {
|
|
||||||
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
|
||||||
this.classes.add(proxy)
|
|
||||||
}
|
|
||||||
return proxy
|
|
||||||
}
|
|
@@ -4,43 +4,39 @@ import app.revanced.patcher.data.Data
|
|||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
import javax.xml.transform.TransformerFactory
|
import javax.xml.transform.TransformerFactory
|
||||||
import javax.xml.transform.dom.DOMSource
|
import javax.xml.transform.dom.DOMSource
|
||||||
import javax.xml.transform.stream.StreamResult
|
import javax.xml.transform.stream.StreamResult
|
||||||
|
|
||||||
class ResourceData(private val resourceCacheDirectory: File) : Data {
|
class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<File> {
|
||||||
private fun resolve(path: String) = resourceCacheDirectory.resolve(path)
|
val xmlEditor = XmlFileHolder()
|
||||||
|
|
||||||
fun forEach(action: (File) -> Unit) = resourceCacheDirectory.walkTopDown().forEach(action)
|
operator fun get(path: String) = resourceCacheDirectory.resolve(path)
|
||||||
fun get(path: String) = resolve(path)
|
|
||||||
|
|
||||||
fun replace(path: String, oldValue: String, newValue: String, oldValueIsRegex: Boolean = false) {
|
override fun iterator() = resourceCacheDirectory.walkTopDown().iterator()
|
||||||
// TODO: buffer this somehow
|
|
||||||
val content = resolve(path).readText()
|
|
||||||
|
|
||||||
if (oldValueIsRegex) {
|
inner class XmlFileHolder {
|
||||||
content.replace(Regex(oldValue), newValue)
|
operator fun get(inputStream: InputStream, outputStream: OutputStream) =
|
||||||
return
|
DomFileEditor(inputStream, lazyOf(outputStream))
|
||||||
}
|
|
||||||
|
operator fun get(path: String) = DomFileEditor(this@ResourceData[path])
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getXmlEditor(path: String) = DomFileEditor(resolve(path))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DomFileEditor internal constructor(private val domFile: File) : Closeable {
|
class DomFileEditor internal constructor(inputStream: InputStream, private val outputStream: Lazy<OutputStream>) : Closeable {
|
||||||
val file: Document
|
|
||||||
|
|
||||||
init {
|
// lazily open an output stream
|
||||||
val factory = DocumentBuilderFactory.newInstance()
|
// this is required because when constructing a DomFileEditor the output stream is created along with the input stream, which is not allowed
|
||||||
|
// the workaround is to lazily create the output stream. This way it would be used after the input stream is closed, which happens in the constructor
|
||||||
|
constructor(file: File) : this(file.inputStream(), lazy { file.outputStream() })
|
||||||
|
|
||||||
val builder = factory.newDocumentBuilder()
|
val file: Document =
|
||||||
|
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream).also(Document::normalize)
|
||||||
|
|
||||||
// this will expectedly throw
|
override fun close() =
|
||||||
file = builder.parse(domFile)
|
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), StreamResult(outputStream.value))
|
||||||
file.normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() = TransformerFactory.newInstance().newTransformer()
|
|
||||||
.transform(DOMSource(file), StreamResult(domFile.outputStream()))
|
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ package app.revanced.patcher.fingerprint.method.impl
|
|||||||
|
|
||||||
import app.revanced.patcher.data.impl.BytecodeData
|
import app.revanced.patcher.data.impl.BytecodeData
|
||||||
import app.revanced.patcher.data.impl.MethodNotFoundException
|
import app.revanced.patcher.data.impl.MethodNotFoundException
|
||||||
import app.revanced.patcher.data.impl.proxy
|
|
||||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
|
import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
|
||||||
import app.revanced.patcher.extensions.softCompareTo
|
import app.revanced.patcher.extensions.softCompareTo
|
||||||
import app.revanced.patcher.fingerprint.Fingerprint
|
import app.revanced.patcher.fingerprint.Fingerprint
|
||||||
@@ -35,7 +34,7 @@ abstract class MethodFingerprint(
|
|||||||
* @throws MethodNotFoundException If the resolution of the [Method] has not happened.
|
* @throws MethodNotFoundException If the resolution of the [Method] has not happened.
|
||||||
*/
|
*/
|
||||||
var result: MethodFingerprintResult? = null
|
var result: MethodFingerprintResult? = null
|
||||||
get() = field ?: throw MethodNotFoundException("${this.name} has not been resolved yet.")
|
get() = field ?: throw Exception("${this.name} has not been resolved yet.")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +48,7 @@ data class MethodFingerprintResult(
|
|||||||
val method: Method,
|
val method: Method,
|
||||||
val classDef: ClassDef,
|
val classDef: ClassDef,
|
||||||
val patternScanResult: PatternScanResult?,
|
val patternScanResult: PatternScanResult?,
|
||||||
val data: BytecodeData
|
internal val data: BytecodeData
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Returns a mutable clone of [classDef]
|
* Returns a mutable clone of [classDef]
|
||||||
|
@@ -68,7 +68,7 @@ object MethodFingerprintUtils {
|
|||||||
)
|
)
|
||||||
) return false
|
) return false
|
||||||
|
|
||||||
if (methodFingerprint.customFingerprint != null && methodFingerprint.customFingerprint!!(context))
|
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(context))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if (methodFingerprint.strings != null) {
|
if (methodFingerprint.strings != null) {
|
||||||
|
@@ -36,12 +36,7 @@ import org.jf.dexlib2.util.Preconditions
|
|||||||
@Description("Example demonstration of a bytecode patch.")
|
@Description("Example demonstration of a bytecode patch.")
|
||||||
@ExampleResourceCompatibility
|
@ExampleResourceCompatibility
|
||||||
@Version("0.0.1")
|
@Version("0.0.1")
|
||||||
class ExampleBytecodePatch : BytecodePatch(
|
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||||
|
|
||||||
listOf(
|
|
||||||
ExampleFingerprint
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// This function will be executed by the patcher.
|
// This function will be executed by the patcher.
|
||||||
// You can treat it as a constructor
|
// You can treat it as a constructor
|
||||||
override fun execute(data: BytecodeData): PatchResult {
|
override fun execute(data: BytecodeData): PatchResult {
|
||||||
|
@@ -18,8 +18,8 @@ import org.w3c.dom.Element
|
|||||||
@Version("0.0.1")
|
@Version("0.0.1")
|
||||||
class ExampleResourcePatch : ResourcePatch() {
|
class ExampleResourcePatch : ResourcePatch() {
|
||||||
override fun execute(data: ResourceData): PatchResult {
|
override fun execute(data: ResourceData): PatchResult {
|
||||||
data.getXmlEditor("AndroidManifest.xml").use { domFileEditor ->
|
data.xmlEditor["AndroidManifest.xml"].use { editor ->
|
||||||
val element = domFileEditor // regular DomFileEditor
|
val element = editor // regular DomFileEditor
|
||||||
.file
|
.file
|
||||||
.getElementsByTagName("application")
|
.getElementsByTagName("application")
|
||||||
.item(0) as Element
|
.item(0) as Element
|
||||||
@@ -30,18 +30,6 @@ class ExampleResourcePatch : ResourcePatch() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate through all available resources
|
|
||||||
data.forEach {
|
|
||||||
if (it.extension.lowercase() != "xml") return@forEach
|
|
||||||
|
|
||||||
data.replace(
|
|
||||||
it.path,
|
|
||||||
"\\ddip", // regex supported
|
|
||||||
"0dip",
|
|
||||||
true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return PatchResultSuccess()
|
return PatchResultSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user