This commit is contained in:
Syer10
2026-07-12 22:57:38 -04:00
parent 4fdef7d17d
commit c18155cf06
5 changed files with 77 additions and 190 deletions

View File

@@ -102,6 +102,7 @@ object Extension {
jarSavePath jarSavePath
} }
} }
apkUrl != null -> { apkUrl != null -> {
installAPK { installAPK {
val apkName = Uri.parse(apkUrl).lastPathSegment!! val apkName = Uri.parse(apkUrl).lastPathSegment!!
@@ -112,7 +113,10 @@ object Extension {
apkSavePath apkSavePath
} }
} }
else -> throw NullPointerException("Could not find extension url for $pkgName")
else -> {
throw NullPointerException("Could not find extension url for $pkgName")
}
} }
} }
@@ -247,7 +251,7 @@ object Extension {
pkgName, pkgName,
packageInfo.versionName, packageInfo.versionName,
packageInfo.versionCode, packageInfo.versionCode,
contentWarning contentWarning,
) )
} catch (e: Throwable) { } catch (e: Throwable) {
// free up the file descriptor if exists // free up the file descriptor if exists
@@ -270,16 +274,19 @@ object Extension {
): String { ): String {
val jarFile = Path(fetcher()) val jarFile = Path(fetcher())
val jarZip = ZipFile.builder() val jarZip =
.setPath(jarFile) ZipFile
.get() .builder()
.setPath(jarFile)
.get()
return jarZip.use { jarZip -> return jarZip.use { jarZip ->
val manifest = jarZip.getInputStream(jarZip.getEntry("AndroidManifest.xml")) val manifest =
.use { jarZip
AndroidManifestParser.parse(it) .getInputStream(jarZip.getEntry("AndroidManifest.xml"))
} .use {
AndroidManifestParser.parse(it)
}
val pkgName = manifest.packageName val pkgName = manifest.packageName
// check if we don't have the extension already installed // check if we don't have the extension already installed
@@ -316,20 +323,23 @@ object Extension {
// throw Exception("This apk is not a signed with the official tachiyomi signature") // throw Exception("This apk is not a signed with the official tachiyomi signature")
// } // }
fun List<AndroidManifestParser.MetaData>.getString(name: String): String? { fun List<AndroidManifestParser.MetaData>.getString(name: String): String? = this.find { it.name == name }?.value
return this.find { it.name == name }?.value
}
var contentWarning = manifest.application!!.metaData.getString(METADATA_CONTENT_WARNING) var contentWarning =
?.toIntOrNull() manifest.application!!
.metaData
.getString(METADATA_CONTENT_WARNING)
?.toIntOrNull()
if (contentWarning == null) { if (contentWarning == null) {
contentWarning = manifest.application.metaData.getString(METADATA_NSFW) contentWarning = manifest.application.metaData
.getString(METADATA_NSFW)
?.toIntOrNull() ?.toIntOrNull()
?: 0 ?: 0
} }
val sourceClass = val sourceClass =
manifest.application.metaData.getString(METADATA_SOURCE_CLASS)!! manifest.application.metaData
.getString(METADATA_SOURCE_CLASS)!!
.trim() .trim()
val className = val className =
@@ -364,7 +374,7 @@ object Extension {
pkgName, pkgName,
manifest.versionName, manifest.versionName,
manifest.versionCode!!, manifest.versionCode!!,
contentWarning contentWarning,
) )
} catch (e: Throwable) { } catch (e: Throwable) {
// free up the file descriptor if exists // free up the file descriptor if exists
@@ -373,7 +383,8 @@ object Extension {
try { try {
uninstallExtension(pkgName) uninstallExtension(pkgName)
} catch (_: Exception) {} } catch (_: Exception) {
}
throw e throw e
} }
} }
@@ -409,7 +420,6 @@ object Extension {
else -> "all" else -> "all"
} }
// update extension info // update extension info
transaction { transaction {
if (ExtensionTable.selectAll().where { ExtensionTable.pkgName eq pkgName }.firstOrNull() == null) { if (ExtensionTable.selectAll().where { ExtensionTable.pkgName eq pkgName }.firstOrNull() == null) {

View File

@@ -22,21 +22,16 @@ object AndroidManifestParser {
data class AndroidManifest( data class AndroidManifest(
@XmlSerialName("package", "", "") @XmlSerialName("package", "", "")
val packageName: String, val packageName: String,
@XmlSerialName("versionCode", ANDROID_NS, "android") @XmlSerialName("versionCode", ANDROID_NS, "android")
val versionCode: Int? = null, val versionCode: Int? = null,
@XmlSerialName("versionName", ANDROID_NS, "android") @XmlSerialName("versionName", ANDROID_NS, "android")
val versionName: String? = null, val versionName: String? = null,
@XmlElement(true) @XmlElement(true)
@XmlSerialName("uses-sdk", "", "") @XmlSerialName("uses-sdk", "", "")
val usesSdk: UsesSdk? = null, val usesSdk: UsesSdk? = null,
@XmlElement(true) @XmlElement(true)
@XmlSerialName("uses-feature", "", "") @XmlSerialName("uses-feature", "", "")
val usesFeatures: List<UsesFeature> = emptyList(), val usesFeatures: List<UsesFeature> = emptyList(),
@XmlElement(true) @XmlElement(true)
@XmlSerialName("application", "", "") @XmlSerialName("application", "", "")
val application: Application? = null, val application: Application? = null,
@@ -47,7 +42,6 @@ object AndroidManifestParser {
data class UsesSdk( data class UsesSdk(
@XmlSerialName("minSdkVersion", ANDROID_NS, "android") @XmlSerialName("minSdkVersion", ANDROID_NS, "android")
val minSdkVersion: Int? = null, val minSdkVersion: Int? = null,
@XmlSerialName("targetSdkVersion", ANDROID_NS, "android") @XmlSerialName("targetSdkVersion", ANDROID_NS, "android")
val targetSdkVersion: Int? = null, val targetSdkVersion: Int? = null,
) )
@@ -64,20 +58,15 @@ object AndroidManifestParser {
data class Application( data class Application(
@XmlSerialName("label", ANDROID_NS, "android") @XmlSerialName("label", ANDROID_NS, "android")
val label: String? = null, val label: String? = null,
@XmlSerialName("icon", ANDROID_NS, "android") @XmlSerialName("icon", ANDROID_NS, "android")
val icon: String? = null, val icon: String? = null,
@XmlSerialName("allowBackup", ANDROID_NS, "android") @XmlSerialName("allowBackup", ANDROID_NS, "android")
val allowBackup: Boolean? = null, val allowBackup: Boolean? = null,
@XmlSerialName("extractNativeLibs", ANDROID_NS, "android") @XmlSerialName("extractNativeLibs", ANDROID_NS, "android")
val extractNativeLibs: Boolean? = null, val extractNativeLibs: Boolean? = null,
@XmlElement(true) @XmlElement(true)
@XmlSerialName("meta-data", "", "") @XmlSerialName("meta-data", "", "")
val metaData: List<MetaData> = emptyList(), val metaData: List<MetaData> = emptyList(),
@XmlElement(true) @XmlElement(true)
@XmlSerialName("activity", "", "") @XmlSerialName("activity", "", "")
val activities: List<Activity> = emptyList(), val activities: List<Activity> = emptyList(),
@@ -88,10 +77,8 @@ object AndroidManifestParser {
data class MetaData( data class MetaData(
@XmlSerialName("name", ANDROID_NS, "android") @XmlSerialName("name", ANDROID_NS, "android")
val name: String, val name: String,
@XmlSerialName("value", ANDROID_NS, "android") @XmlSerialName("value", ANDROID_NS, "android")
val value: String? = null, val value: String? = null,
@XmlSerialName("resource", ANDROID_NS, "android") @XmlSerialName("resource", ANDROID_NS, "android")
val resource: String? = null, val resource: String? = null,
) )
@@ -101,13 +88,10 @@ object AndroidManifestParser {
data class Activity( data class Activity(
@XmlSerialName("name", ANDROID_NS, "android") @XmlSerialName("name", ANDROID_NS, "android")
val name: String, val name: String,
@XmlSerialName("exported", ANDROID_NS, "android") @XmlSerialName("exported", ANDROID_NS, "android")
val exported: Boolean? = null, val exported: Boolean? = null,
@XmlSerialName("theme", ANDROID_NS, "android") @XmlSerialName("theme", ANDROID_NS, "android")
val theme: String? = null, val theme: String? = null,
@XmlElement(true) @XmlElement(true)
@XmlSerialName("intent-filter", "", "") @XmlSerialName("intent-filter", "", "")
val intentFilters: List<IntentFilter> = emptyList(), val intentFilters: List<IntentFilter> = emptyList(),
@@ -119,11 +103,9 @@ object AndroidManifestParser {
@XmlElement(true) @XmlElement(true)
@XmlSerialName("action", "", "") @XmlSerialName("action", "", "")
val actions: List<Action> = emptyList(), val actions: List<Action> = emptyList(),
@XmlElement(true) @XmlElement(true)
@XmlSerialName("category", "", "") @XmlSerialName("category", "", "")
val categories: List<Category> = emptyList(), val categories: List<Category> = emptyList(),
@XmlElement(true) @XmlElement(true)
@XmlSerialName("data", "", "") @XmlSerialName("data", "", "")
val data: List<Data> = emptyList(), val data: List<Data> = emptyList(),
@@ -148,24 +130,22 @@ object AndroidManifestParser {
data class Data( data class Data(
@XmlSerialName("scheme", ANDROID_NS, "android") @XmlSerialName("scheme", ANDROID_NS, "android")
val scheme: String? = null, val scheme: String? = null,
@XmlSerialName("host", ANDROID_NS, "android") @XmlSerialName("host", ANDROID_NS, "android")
val host: String? = null, val host: String? = null,
@XmlSerialName("pathPattern", ANDROID_NS, "android") @XmlSerialName("pathPattern", ANDROID_NS, "android")
val pathPattern: String? = null, val pathPattern: String? = null,
) )
private val xml = XML { private val xml =
autoPolymorphic = false XML {
repairNamespaces = true autoPolymorphic = false
xmlDeclMode = XmlDeclMode.Minimal repairNamespaces = true
defaultPolicy { xmlDeclMode = XmlDeclMode.Minimal
unknownChildHandler = UnknownChildHandler { _, _, _, _, _ -> emptyList() } defaultPolicy {
unknownChildHandler = UnknownChildHandler { _, _, _, _, _ -> emptyList() }
}
} }
}
@OptIn(ExperimentalXmlUtilApi::class) @OptIn(ExperimentalXmlUtilApi::class)
fun parse(input: InputStream): AndroidManifest = fun parse(input: InputStream): AndroidManifest = xml.decodeFromReader(AndroidManifest.serializer(), KtXmlReader(input))
xml.decodeFromReader(AndroidManifest.serializer(), KtXmlReader(input))
} }

View File

@@ -94,8 +94,8 @@ object PackageTools {
} }
/** A modified version of `xyz.nulldev.androidcompat.pm.InstalledPackage.info` */ /** A modified version of `xyz.nulldev.androidcompat.pm.InstalledPackage.info` */
fun getPackageInfo(apkFile: Path): PackageInfo { fun getPackageInfo(apkFile: Path): PackageInfo =
return ApkParsers.getMetaInfo(apkFile.toFile()).toPackageInfo(apkFile.toFile()).apply { ApkParsers.getMetaInfo(apkFile.toFile()).toPackageInfo(apkFile.toFile()).apply {
val parsed = ApkFile(apkFile.toFile()) val parsed = ApkFile(apkFile.toFile())
val dbFactory = DocumentBuilderFactory.newInstance() val dbFactory = DocumentBuilderFactory.newInstance()
val dBuilder = dbFactory.newDocumentBuilder() val dBuilder = dbFactory.newDocumentBuilder()
@@ -137,7 +137,6 @@ object PackageTools {
.map { Signature(it.data) } .map { Signature(it.data) }
.toTypedArray() .toTypedArray()
} }
}
fun getSignatureHash(pkgInfo: PackageInfo): String? { fun getSignatureHash(pkgInfo: PackageInfo): String? {
val signatures = pkgInfo.signatures val signatures = pkgInfo.signatures

View File

@@ -18,19 +18,23 @@ object ResourceArscIconParser {
fun extractIcon( fun extractIcon(
jar: Path, jar: Path,
iconPath: Path iconPath: Path,
) { ) {
ZipFile.builder() ZipFile
.builder()
.setPath(jar) .setPath(jar)
.get() .get()
.use { zip -> .use { zip ->
val packages = zip.getInputStream(zip.getEntry("resources.arsc")) val packages =
.use { ArscParser(it.readBytes()).parse() } zip
.getInputStream(zip.getEntry("resources.arsc"))
.use { ArscParser(it.readBytes()).parse() }
val icon = packages val icon =
.flatMap { it.iconCandidates() } packages
.maxByOrNull { it.density } .flatMap { it.iconCandidates() }
?: return .maxByOrNull { it.density }
?: return
val entry = zip.getEntry(icon.path) ?: return val entry = zip.getEntry(icon.path) ?: return
@@ -42,19 +46,21 @@ object ResourceArscIconParser {
} }
} }
fun extractIcon( fun extractIcon(zip: ZipFile): InputStream {
zip: ZipFile, val packages =
): InputStream { zip
val packages = zip.getInputStream(zip.getEntry("resources.arsc")) .getInputStream(zip.getEntry("resources.arsc"))
.use { ArscParser(it.readBytes()).parse() } .use { ArscParser(it.readBytes()).parse() }
val icon = packages val icon =
.flatMap { it.iconCandidates() } packages
.maxByOrNull { it.density } .flatMap { it.iconCandidates() }
?: throw NullPointerException("No valid icons") .maxByOrNull { it.density }
?: throw NullPointerException("No valid icons")
val entry = zip.getEntry(icon.path) val entry =
?: throw NullPointerException("Icon ${icon.path} missing") zip.getEntry(icon.path)
?: throw NullPointerException("Icon ${icon.path} missing")
return zip.getInputStream(entry) return zip.getInputStream(entry)
} }
@@ -76,19 +82,22 @@ object ResourceArscIconParser {
} }
private fun Config.density(): Int = private fun Config.density(): Int =
ByteBuffer.wrap(id) ByteBuffer
.wrap(id)
.order(ByteOrder.LITTLE_ENDIAN) .order(ByteOrder.LITTLE_ENDIAN)
.getShort(14) .getShort(14)
.toInt() and 0xffff .toInt() and 0xffff
private val rasterExtensions = setOf( private val rasterExtensions =
"png", setOf(
"webp", "png",
"jpg", "webp",
"jpeg", "jpg",
) "jpeg",
)
private fun isRasterImage(path: String): Boolean = private fun isRasterImage(path: String): Boolean =
path.substringAfterLast('.', "") path
.substringAfterLast('.', "")
.lowercase() in rasterExtensions .lowercase() in rasterExtensions
} }

View File

@@ -1,111 +0,0 @@
import org.apache.commons.compress.archivers.zip.ZipFile
import pxb.android.arsc.ArscDumper
import pxb.android.arsc.ArscParser
import pxb.android.axml.AxmlParser
import suwayomi.tachidesk.manga.impl.util.AndroidManifestParser
import suwayomi.tachidesk.manga.impl.util.ResourceArscIconParser
import kotlin.io.path.Path
import kotlin.io.path.absolutePathString
import kotlin.test.Test
class JarTest {
@Test
fun jarTest() {
println(Path("./").absolutePathString())
val jar = Path("./build/tachiyomi-all.ahottie-v1.4.3.jar")
val zip = ZipFile.builder()
.setPath(jar)
.get()
val manifest = zip.getInputStream(zip.getEntry("AndroidManifest.xml"))
.use {
AndroidManifestParser.parse(it)
}
//
// reader.parse()
// println(reader.resourceTable)
// val resourceTable = reader.resourceTable
// println(resourceTable.stringPool)
// //println(table.stringPool.get(1))
// // table::class.members.forEach {
// // if (it.name == "packageMap") {
// // it.isAccessible = true
// // val map = it.call(table) as Map<Short, ResourcePackage>
// // map.forEach { short, packagz ->
// // println(short)
// // println(packagz.name to packagz.id)
// // }
// // }
// // }
// val apkTranslator = ApkMetaTranslator(resourceTable, null)
// val xmlTranslator = XmlTranslator()
// val xmlStreamer = CompositeXmlStreamer(xmlTranslator, apkTranslator)
// val data = zip.getInputStream(zip.getEntry("AndroidManifest.xml"))
// .use {
// it.readBytes()
// }
//
//
// val buffer = ByteBuffer.wrap(data)
// val binaryXmlParser = BinaryXmlParser(buffer, resourceTable)
// //binaryXmlParser.locale = preferredLocale
// binaryXmlParser.xmlStreamer = xmlStreamer
// binaryXmlParser.parse()
// val manifestXml = xmlTranslator.xml
// println(manifestXml)
// val iconPaths = apkTranslator.iconPaths
// iconPaths.forEach {
// println(it.path)
// }
// val parser = zip.getInputStream(zip.getEntry("resources.arsc"))
// .use {
// ArscParser(it.readBytes())
// }
// val parsed = parser.parse()
// ArscDumper.dump(parsed)
//
// parsed.forEach {
// print("pkg.name: ")
// println(it.name)
// it.types.toList().forEachIndexed { t, (i, type) ->
// print("pkg.types.$t.type.name: ")
// println(type.name)
// print("pkg.types.$t.type.id: ")
// println(type.id)
// type.specs.forEachIndexed { y, spec ->
// print("pkg.types.$t.type.specs.$y.id: ")
// println(spec.id)
// print("pkg.types.$t.type.specs.$y.name: ")
// println(spec.name)
// print("pkg.types.$t.type.specs.$y.flags: ")
// println(spec.flags)
// }
// type.configs.forEachIndexed { y, config ->
// print("pkg.types.$t.type.configs.$y.id: ")
// println(config.id.toHexString(HexFormat.UpperCase))
// print("pkg.types.$t.type.configs.$y.entryCount: ")
// println(config.entryCount)
// config.resources.forEach {
// print("pkg.types.$t.type.configs.$y.resources.${it.key}.flag: ")
// println(it.value.flag)
// print("pkg.types.$t.type.configs.$y.resources.${it.key}.value: ")
// println(it.value.value)
// print("pkg.types.$t.type.configs.$y.resources.${it.key}.spec.id: ")
// println(it.value.spec.id)
// print("pkg.types.$t.type.configs.$y.resources.${it.key}.spec.name: ")
// println(it.value.spec.name)
// print("pkg.types.$t.type.configs.$y.resources.${it.key}.spec.flags: ")
// println(it.value.spec.flags)
// }
// }
// }
// }
// val fileTest = Path("./build/icon.png")
// ResourceArscIconParser.extractIcon(jar, fileTest)
println(manifest.packageName)
}
}