This commit is contained in:
2026-08-21 14:30:50 +02:00
commit d548ff24af
30 changed files with 7952 additions and 0 deletions
@@ -0,0 +1,13 @@
package app.twitter.patches
import app.morphe.patcher.patch.bytecodePatch
import app.twitter.patches.shared.Constants.COMPATIBILITY_X
@Suppress("unused")
val noopPatch = bytecodePatch(
name = "No-op",
description = "Makes no changes; validates the X patching pipeline.",
default = true
) {
compatibleWith(COMPATIBILITY_X)
}
@@ -0,0 +1,20 @@
package app.twitter.patches.shared
import app.morphe.patcher.patch.ApkFileType
import app.morphe.patcher.patch.AppTarget
import app.morphe.patcher.patch.Compatibility
internal object Constants {
val COMPATIBILITY_X = Compatibility(
name = "X",
packageName = "com.twitter.android",
apkFileType = ApkFileType.APKM,
appIconColor = 0x000000,
targets = listOf(
AppTarget(
version = "12.7.1-release.0",
minSdk = 28
)
)
)
}
@@ -0,0 +1,152 @@
/*
* Copyright 2025 Morphe.
* https://github.com/MorpheApp/morphe-patches-template
*/
package util
import app.morphe.patcher.patch.Patch
import app.morphe.patcher.patch.loadPatchesFromJar
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import java.io.File
import java.net.URLClassLoader
import java.util.jar.Manifest
fun main() {
val patchFiles = setOf(
File("build/libs/").listFiles { file ->
val fileName = file.name
!fileName.contains("javadoc") &&
!fileName.contains("sources") &&
fileName.endsWith(".mpp")
}!!.first()
)
val loadedPatches = loadPatchesFromJar(patchFiles)
val patchClassLoader = URLClassLoader(patchFiles.map { it.toURI().toURL() }.toTypedArray())
val manifest = patchClassLoader.getResources("META-INF/MANIFEST.MF")
while (manifest.hasMoreElements()) {
Manifest(manifest.nextElement().openStream())
.mainAttributes
.getValue("Version")
?.let {
generatePatchList(it, loadedPatches)
}
}
}
@Suppress("DEPRECATION")
private fun generatePatchList(version: String, patches: Set<Patch<*>>) {
val listJson = File("../patches-list.json")
val patchesMap = patches.sortedBy { it.name }.map { patch ->
JsonPatch(
name = patch.name!!,
description = patch.description,
default = patch.default,
dependencies = patch.dependencies.map { it.javaClass.simpleName },
// Map each Compatibility to a JsonCompatibility object with full metadata.
// Patches with null compatiblePackages are universal (apply to any app).
compatiblePackages = patch.compatibility?.map { compat ->
JsonCompatibility(
packageName = compat.packageName!!,
name = compat.name,
description = compat.description,
apkFileType = compat.apkFileType?.name,
// Format as #RRGGBB string for readability; null if not set
appIconColor = compat.appIconColor?.let { "#%06X".format(it) },
signatures = compat.signatures,
targets = compat.targets.map { target ->
JsonCompatibility.Target(
version = target.version,
versionCodes = target.versionCodes?.mapKeys { it.key.name },
isExperimental = target.isExperimental,
minSdk = target.minSdk,
description = target.description,
)
},
)
},
options = patch.options.values.map { option ->
JsonPatch.Option(
key = option.key,
title = option.title,
description = option.description,
required = option.required,
type = option.type.toString(),
default = option.default,
values = option.values,
)
}
)
}
val gson = GsonBuilder()
.serializeNulls()
.disableHtmlEscaping()
.setPrettyPrinting()
.create()
val jsonObject = JsonObject()
jsonObject.addProperty(
"NOTE",
"Do NOT manually edit this file. This file is automatically updated when " +
"semantic release (release.yml) runs. Manually editing this file can break " +
"your releases and break third party tools that use this file."
)
jsonObject.addProperty("version", version)
jsonObject.add("patches", gson.toJsonTree(patchesMap))
listJson.writeText(gson.toJson(jsonObject))
}
/** JSON representation of a patch entry in patches-list.json. */
@Suppress("unused")
private class JsonPatch(
val name: String? = null,
val description: String? = null,
val default: Boolean = true,
val dependencies: List<String>,
/** Null means the patch is universal and applies to any app. */
val compatiblePackages: List<JsonCompatibility>? = null,
val options: List<Option>,
) {
class Option(
val key: String,
val title: String?,
val description: String?,
val required: Boolean,
val type: String,
val default: Any?,
val values: Map<String, Any?>?,
)
}
/** JSON representation of a compatible app entry, including name and per-version metadata. */
@Suppress("unused")
private class JsonCompatibility(
/** Android package name, e.g. com.google.android.youtube. */
val packageName: String,
/** Human-readable app name declared in Compatibility, e.g. "YouTube". */
val name: String?,
/** User-facing description of the app. */
val description: String?,
/** Target unpatched app file type, e.g. APK, APKM. Null if not specified. */
val apkFileType: String?,
/** App icon background color as #RRGGBB string, or null if not set. */
val appIconColor: String?,
/** Valid SHA-256 signatures of the app. */
val signatures: Set<String>?,
val targets: List<Target>,
) {
class Target(
val version: String?,
val versionCodes: Map<String, Int>?,
val isExperimental: Boolean,
/** Minimum device SDK version. Null means any SDK version. */
val minSdk: Int?,
/** Optional user-facing note about this specific version. */
val description: String?,
)
}