Skip to content

Commit c2b667b

Browse files
committed
fix: Add some extension methods from morphe-library
1 parent 9320cf0 commit c2b667b

File tree

4 files changed

+310
-0
lines changed

4 files changed

+310
-0
lines changed

api/morphe-patcher.api

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,12 @@ public final class app/morphe/patcher/patch/PatchResult {
677677
public final fun getPatch ()Lapp/morphe/patcher/patch/Patch;
678678
}
679679

680+
public final class app/morphe/patcher/patch/PatchUtilsKt {
681+
public static final fun mostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
682+
public static synthetic fun mostCommonCompatibleVersions$default (Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
683+
public static final fun setOptions (Ljava/util/Set;Ljava/util/Map;)V
684+
}
685+
680686
public final class app/morphe/patcher/patch/RawResourcePatch : app/morphe/patcher/patch/Patch {
681687
public fun toString ()Ljava/lang/String;
682688
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Code hard forked from:
3+
* https://github.com/revanced/revanced-library/tree/06733072045c8016a75f232dec76505c0ba2e1cd
4+
*/
5+
6+
package app.morphe.patcher.patch
7+
8+
import java.util.logging.Logger
9+
10+
typealias Count = Int
11+
typealias VersionMap = LinkedHashMap<VersionName, Count>
12+
typealias PackageNameMap = Map<PackageName, VersionMap>
13+
typealias PatchName = String
14+
typealias OptionKey = String
15+
typealias OptionValue = Any?
16+
typealias PatchesOptions = Map<PatchName, Map<OptionKey, OptionValue>>
17+
18+
private val logger = Logger.getLogger("PatchUtils")
19+
20+
/**
21+
* Get the count of versions for each compatible package from the set of [Patch] ordered by the most common version.
22+
*
23+
* @param packageNames The names of the compatible packages to include. If null, all packages will be included.
24+
* @param countUnusedPatches Whether to count patches that are not used.
25+
* @return A map of package names to a map of versions to their count.
26+
*/
27+
fun Set<Patch<*>>.mostCommonCompatibleVersions(
28+
packageNames: Set<String>? = null,
29+
countUnusedPatches: Boolean = false,
30+
): PackageNameMap = buildMap {
31+
fun filterWantedPackages(compatiblePackages: List<Package>): List<Package> {
32+
val wantedPackages = packageNames?.toHashSet() ?: return compatiblePackages
33+
return compatiblePackages.filter { (name, _) -> name in wantedPackages }
34+
}
35+
36+
this@mostCommonCompatibleVersions.filter { it.use || countUnusedPatches }
37+
.flatMap { it.compatiblePackages ?: emptyList() }
38+
.let(::filterWantedPackages)
39+
.forEach { (name, versions) ->
40+
if (versions?.isEmpty() == true) {
41+
return@forEach
42+
}
43+
44+
val versionMap = getOrPut(name) { linkedMapOf() }
45+
46+
versions?.forEach { version ->
47+
versionMap[version] = versionMap.getOrDefault(version, 0) + 1
48+
}
49+
}
50+
51+
// Sort the version maps by the most common version.
52+
forEach { (packageName, versionMap) ->
53+
this[packageName] =
54+
versionMap
55+
.asIterable()
56+
.sortedWith(compareByDescending { it.value })
57+
.associate { it.key to it.value } as VersionMap
58+
}
59+
}
60+
61+
/**
62+
* Set the options for a set of patches that have a name.
63+
*
64+
* @param options The options to set. The key is the patch name and the value is a map of option keys to option values.
65+
*/
66+
fun Set<Patch<*>>.setOptions(options: PatchesOptions) = filter { it.name != null }.forEach { patch ->
67+
options[patch.name]?.forEach setOption@{ (optionKey, optionValue) ->
68+
if (optionKey !in patch.options) {
69+
return@setOption logger.warning(
70+
"Could not set option for the \"${patch.name}\" patch because " +
71+
"option with key \"${optionKey}\" does not exist",
72+
)
73+
}
74+
75+
try {
76+
patch.options[optionKey] = optionValue
77+
} catch (e: OptionException) {
78+
logger.warning("Could not set option value for the \"${patch.name}\" patch: ${e.message}")
79+
}
80+
}
81+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Code hard forked from:
3+
* https://github.com/revanced/revanced-library/tree/06733072045c8016a75f232dec76505c0ba2e1cd
4+
*/
5+
6+
package app.morphe.patcher.patch
7+
8+
import kotlin.test.Test
9+
import kotlin.test.assertEquals
10+
11+
internal class MostCommonCompatibleVersionsTest {
12+
private val patches =
13+
arrayOf(
14+
newPatch("some.package", setOf("a")) { stringOption("string", "value") },
15+
newPatch("some.package", setOf("a", "b"), use = false),
16+
newPatch("some.package", setOf("a", "b", "c"), use = false),
17+
newPatch("some.other.package", setOf("b"), use = false),
18+
newPatch("some.other.package", setOf("b", "c")) { booleanOption("bool", true) },
19+
newPatch("some.other.package", setOf("b", "c", "d")),
20+
newPatch("some.other.other.package") { intsOption("intArray", listOf(1, 2, 3)) },
21+
newPatch("some.other.other.package", setOf("a")),
22+
newPatch("some.other.other.package", setOf("b")),
23+
newPatch("some.other.other.other.package", use = false),
24+
newPatch("some.other.other.other.package", use = false),
25+
).toSet()
26+
27+
@Test
28+
fun `empty because package is incompatible with any version`() {
29+
assertEqualsVersions(
30+
expected = emptyMap(),
31+
patches = setOf(newPatch("some.package", emptySet(), use = true)),
32+
compatiblePackageNames = setOf("some.package"),
33+
)
34+
}
35+
36+
@Test
37+
fun `empty list of versions because package is unconstrained to any version`() {
38+
assertEqualsVersions(
39+
expected = mapOf("some.package" to linkedMapOf()),
40+
patches = setOf(newPatch("some.package")),
41+
compatiblePackageNames = setOf("some.package"),
42+
countUnusedPatches = true,
43+
)
44+
}
45+
46+
@Test
47+
fun `empty because no known package was supplied`() {
48+
assertEqualsVersions(
49+
expected = emptyMap(),
50+
patches,
51+
compatiblePackageNames = setOf("unknown.package"),
52+
)
53+
}
54+
55+
@Test
56+
fun `common versions correctly ordered for each package`() {
57+
fun assertEqualsExpected(compatiblePackageNames: Set<String>?) =
58+
assertEqualsVersions(
59+
expected =
60+
mapOf(
61+
"some.package" to linkedMapOf("a" to 3, "b" to 2, "c" to 1),
62+
"some.other.package" to linkedMapOf("b" to 3, "c" to 2, "d" to 1),
63+
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
64+
"some.other.other.other.package" to linkedMapOf(),
65+
),
66+
patches,
67+
compatiblePackageNames,
68+
countUnusedPatches = true,
69+
)
70+
71+
assertEqualsExpected(
72+
compatiblePackageNames =
73+
setOf(
74+
"some.package",
75+
"some.other.package",
76+
"some.other.other.package",
77+
"some.other.other.other.package",
78+
),
79+
)
80+
81+
assertEqualsExpected(
82+
compatiblePackageNames = null,
83+
)
84+
}
85+
86+
@Test
87+
fun `common versions correctly ordered for each package without counting unused patches`() {
88+
assertEqualsVersions(
89+
expected =
90+
mapOf(
91+
"some.package" to linkedMapOf("a" to 1),
92+
"some.other.package" to linkedMapOf("b" to 2, "c" to 2, "d" to 1),
93+
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
94+
),
95+
patches,
96+
compatiblePackageNames =
97+
setOf(
98+
"some.package",
99+
"some.other.package",
100+
"some.other.other.package",
101+
"some.other.other.other.package",
102+
),
103+
countUnusedPatches = false,
104+
)
105+
}
106+
107+
@Test
108+
fun `return 'a' because it is the most common version`() {
109+
val patches =
110+
arrayOf("a", "a", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d")
111+
.map { version -> newPatch("some.package", setOf(version)) }
112+
.toSet()
113+
114+
assertEqualsVersion("a", patches, "some.package")
115+
}
116+
117+
@Test
118+
fun `return null because no patches were supplied`() {
119+
assertEqualsVersion(null, emptySet<BytecodePatch>(), "some.package")
120+
}
121+
122+
@Test
123+
fun `return null because no patch is compatible with the supplied package name`() {
124+
val patches = setOf(newPatch("some.package", setOf("a")))
125+
126+
assertEqualsVersion(null, patches, "other.package")
127+
}
128+
129+
@Test
130+
fun `return null because no compatible package is constrained to a version`() {
131+
val patches =
132+
setOf(
133+
newPatch("other.package"),
134+
newPatch("other.package"),
135+
)
136+
137+
assertEqualsVersion(null, patches, "other.package")
138+
}
139+
140+
private fun assertEqualsVersions(
141+
expected: PackageNameMap,
142+
patches: Set<Patch<*>>,
143+
compatiblePackageNames: Set<String>?,
144+
countUnusedPatches: Boolean = false,
145+
) = assertEquals(
146+
expected,
147+
patches.mostCommonCompatibleVersions(compatiblePackageNames, countUnusedPatches),
148+
)
149+
150+
private fun assertEqualsVersion(
151+
expected: String?,
152+
patches: Set<Patch<*>>,
153+
compatiblePackageName: String,
154+
) {
155+
assertEquals(
156+
expected,
157+
patches.mostCommonCompatibleVersions(setOf(compatiblePackageName))
158+
.entries.firstOrNull()?.value?.keys?.firstOrNull(),
159+
)
160+
}
161+
162+
private fun newPatch(
163+
packageName: String,
164+
versions: Set<String>? = null,
165+
use: Boolean = true,
166+
options: PatchBuilder<*>.() -> Unit = {},
167+
) = bytecodePatch(
168+
name = "test",
169+
use = use,
170+
) {
171+
if (versions == null) {
172+
compatibleWith(packageName)
173+
} else {
174+
compatibleWith(
175+
if (versions.isEmpty()) {
176+
packageName()
177+
} else {
178+
packageName(*versions.toTypedArray())
179+
},
180+
)
181+
}
182+
183+
options()
184+
}
185+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Code hard forked from:
3+
* https://github.com/revanced/revanced-library/tree/06733072045c8016a75f232dec76505c0ba2e1cd
4+
*/
5+
6+
package app.morphe.patcher.patch
7+
8+
import kotlin.test.Test
9+
import kotlin.test.assertEquals
10+
11+
internal class OptionsTest {
12+
@Test
13+
fun `serializes and deserializes`() {
14+
val options = mapOf(
15+
"Test patch" to mapOf("key1" to "test", "key2" to false),
16+
)
17+
18+
val patch = bytecodePatch("Test patch") {
19+
stringOption("key1")
20+
booleanOption("key2", true)
21+
}
22+
val duplicatePatch = bytecodePatch("Test patch") {
23+
stringOption("key1")
24+
}
25+
val unnamedPatch = bytecodePatch {
26+
booleanOption("key1")
27+
}
28+
29+
setOf(patch, duplicatePatch, unnamedPatch).setOptions(options)
30+
31+
assert(patch.options["key1"].value == "test")
32+
assert(patch.options["key2"].value == false)
33+
34+
assertEquals(patch.options["key1"].value, duplicatePatch.options["key1"].value)
35+
36+
assert(unnamedPatch.options["key1"].value == null)
37+
}
38+
}

0 commit comments

Comments
 (0)