Skip to content

Commit

Permalink
arsclib
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Jul 15, 2023
1 parent 083278e commit 1fce0c8
Show file tree
Hide file tree
Showing 96 changed files with 2,011 additions and 1,286 deletions.
42 changes: 42 additions & 0 deletions arsclib-utils/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store
39 changes: 39 additions & 0 deletions arsclib-utils/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
plugins {
kotlin("jvm")
`maven-publish`
}

group = "app.revanced"

dependencies {
implementation("io.github.reandroid:ARSCLib:1.1.7")
}

java {
withSourcesJar()
}

kotlin {
jvmToolchain(11)
}

publishing {
repositories {
if (System.getenv("GITHUB_ACTOR") != null)
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
else
mavenLocal()
}
publications {
register<MavenPublication>("gpr") {
from(components["java"])
}
}
}
42 changes: 42 additions & 0 deletions arsclib-utils/src/main/kotlin/app/revanced/arsc/ApkException.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package app.revanced.arsc

/**
* An exception thrown when working with [Apk]s.
*
* @param message The exception message.
* @param throwable The corresponding [Throwable].
*/
// TODO: this probably needs a better name but idk what to call it.
sealed class ApkException(message: String, throwable: Throwable? = null) : Exception(message, throwable) {
/**
* An exception when decoding resources.
*
* @param message The exception message.
* @param throwable The corresponding [Throwable].
*/
class Decode(message: String, throwable: Throwable? = null) : ApkException(message, throwable)

/**
* An exception when encoding resources.
*
* @param message The exception message.
* @param throwable The corresponding [Throwable].
*/
class Encode(message: String, throwable: Throwable? = null) : ApkException(message, throwable)

/**
* An exception thrown when a reference could not be resolved.
*
* @param ref The invalid reference.
* @param throwable The corresponding [Throwable].
*/
class InvalidReference(ref: String, throwable: Throwable? = null) :
ApkException("Failed to resolve: $ref", throwable) {
constructor(type: String, name: String, throwable: Throwable? = null) : this("@$type/$name", throwable)
}

/**
* An exception thrown when the [Apk] does not have a resource table, but was expected to have one.
*/
object MissingResourceTable : ApkException("Apk does not have a resource table.")
}
140 changes: 140 additions & 0 deletions arsclib-utils/src/main/kotlin/app/revanced/arsc/archive/Archive.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package app.revanced.arsc.archive

import app.revanced.arsc.ApkException
import app.revanced.arsc.logging.Logger
import app.revanced.arsc.resource.ResourceContainer
import app.revanced.arsc.resource.ResourceFile
import app.revanced.arsc.xml.LazyXMLInputSource
import com.reandroid.apk.ApkModule
import com.reandroid.archive.ByteInputSource
import com.reandroid.archive.InputSource
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock
import com.reandroid.arsc.chunk.xml.ResXmlDocument
import com.reandroid.xml.XMLDocument
import java.io.File

private fun isResXml(inputSource: InputSource) = inputSource.openStream().use { ResXmlDocument.isResXmlBlock(it) }

/**
* A class for reading/writing files in an [ApkModule].
*
* @param module The [ApkModule] to operate on.
*/
class Archive(private val module: ApkModule) {
lateinit var resources: ResourceContainer

/**
* The result of a [read] operation.
*
* @param xml Whether the contents were decoded from a [ResXmlDocument].
* @param data The contents of the file.
*/
class ReadResult(val xml: Boolean, val data: ByteArray)

/**
* The zip archive.
*/
private val archive = module.apkArchive

private val lockedFiles = mutableMapOf<String, ResourceFile>()

/**
* Lock the [ResourceFile], preventing it from being opened again until it is unlocked.
*/
fun lock(file: ResourceFile) {
val path = file.handle.archivePath
if (lockedFiles.contains(path)) {
throw ApkException.Decode("${file.handle.virtualPath} is locked. If you are a patch developer, make sure you always close files.")
}
lockedFiles[path] = file
}

/**
* Unlock the [ResourceFile], allowing patches to open it again.
*/
fun unlock(file: ResourceFile) {
lockedFiles.remove(file.handle.archivePath)
}

/**
* Closes all open files and encodes all XML files to binary XML.
*
* @param logger The [Logger] of the [app.revanced.patcher.Patcher].
*/
fun cleanup(logger: Logger?) {
lockedFiles.values.toList().forEach {
logger?.warn("${it.handle.virtualPath} was never closed!")
it.close()
}

archive.listInputSources().filterIsInstance<LazyXMLInputSource>()
.forEach(LazyXMLInputSource::encode)
}

/**
* Save the archive to disk.
*
* @param output The file to write the updated archive to.
*/
fun save(output: File) = module.writeApk(output)

/**
* Read an entry from the archive.
*
* @param path The archive path to read from.
* @return A [ReadResult] containing the contents of the entry.
*/
fun read(path: String) =
archive.getInputSource(path)?.let { inputSource ->
val xml = when {
inputSource is LazyXMLInputSource -> inputSource.document
isResXml(inputSource) -> module.loadResXmlDocument(
inputSource
).decodeToXml(resources.resourceTable.entryStore, resources.packageBlock?.id ?: 0)

else -> null
}

ReadResult(
xml != null,
xml?.toText()?.toByteArray() ?: inputSource.openStream().use { it.readAllBytes() })
}

/**
* Reads the manifest from the archive as an [AndroidManifestBlock].
*
* @return The [AndroidManifestBlock] contained in this archive.
*/
fun readManifest(): AndroidManifestBlock =
archive.getInputSource(AndroidManifestBlock.FILE_NAME).openStream().use { AndroidManifestBlock.load(it) }

/**
* Reads all dex files from the archive.
*
* @return A [Map] containing all the dex files.
*/
fun readDexFiles() = module.listDexFiles().associate { file -> file.name to file.openStream().use { it.readAllBytes() } }

/**
* Write the byte array to the archive entry.
*
* @param path The archive path to read from.
* @param content The content of the file.
*/
fun writeRaw(path: String, content: ByteArray) =
archive.add(ByteInputSource(content, path))

/**
* Write the XML to the entry associated.
*
* @param path The archive path to read from.
* @param document The XML document to encode.
*/
fun writeXml(path: String, document: XMLDocument) = archive.add(
LazyXMLInputSource(
path,
document,
resources,
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package app.revanced.arsc.logging
interface Logger {
fun error(msg: String)
fun warn(msg: String)
fun info(msg: String)
fun trace(msg: String)
}
Loading

0 comments on commit 1fce0c8

Please sign in to comment.