Browse Source

Use OpenGL registry to replace magic numbers with constants

pull/66/head
Graham 2 years ago
parent
commit
7db0c971ee
  1. 1
      buildSrc/src/main/java/Versions.kt
  2. 1
      deob-ast/build.gradle.kts
  3. 2
      deob-ast/src/main/java/dev/openrs2/deob/ast/AstDeobfuscator.kt
  4. 86
      deob-ast/src/main/java/dev/openrs2/deob/ast/gl/GlRegistry.kt
  5. 207
      deob-ast/src/main/java/dev/openrs2/deob/ast/transform/GlConstantTransformer.kt
  6. 51692
      deob-ast/src/main/resources/dev/openrs2/deob/ast/gl.xml

1
buildSrc/src/main/java/Versions.kt

@ -7,6 +7,7 @@ object Versions {
const val guice = "4.2.2"
const val inlineLogger = "1.0.2"
const val javaParser = "3.15.14"
const val jdom = "2.0.6"
const val jimfs = "1.1"
const val junit = "5.6.0"
const val kotlin = "1.3.70"

1
deob-ast/build.gradle.kts

@ -11,6 +11,7 @@ application {
dependencies {
implementation(project(":common"))
implementation("com.github.javaparser:javaparser-symbol-solver-core:${Versions.javaParser}")
implementation("org.jdom:jdom2:${Versions.jdom}")
}
publishing {

2
deob-ast/src/main/java/dev/openrs2/deob/ast/AstDeobfuscator.kt

@ -15,6 +15,7 @@ import dev.openrs2.deob.ast.transform.BitMaskTransformer
import dev.openrs2.deob.ast.transform.ComplementTransformer
import dev.openrs2.deob.ast.transform.EncloseTransformer
import dev.openrs2.deob.ast.transform.ForLoopConditionTransformer
import dev.openrs2.deob.ast.transform.GlConstantTransformer
import dev.openrs2.deob.ast.transform.IdentityTransformer
import dev.openrs2.deob.ast.transform.IfElseTransformer
import dev.openrs2.deob.ast.transform.IncrementTransformer
@ -99,6 +100,7 @@ class AstDeobfuscator(private val modules: List<Path>) {
NewInstanceTransformer(),
IncrementTransformer(),
ForLoopConditionTransformer(),
GlConstantTransformer(),
EncloseTransformer()
)
}

86
deob-ast/src/main/java/dev/openrs2/deob/ast/gl/GlRegistry.kt

@ -0,0 +1,86 @@
package dev.openrs2.deob.ast.gl
import com.google.common.collect.HashMultimap
import com.google.common.collect.ImmutableSetMultimap
import org.jdom2.input.SAXBuilder
import java.io.InputStream
data class GlEnum(val name: String, val value: Long)
data class GlGroup(val name: String, val enums: List<GlEnum>)
data class GlParameter(val name: String, val bitfield: Boolean, val group: GlGroup?)
data class GlCommand(val name: String, val parameters: List<GlParameter>)
data class GlRegistry(val enums: ImmutableSetMultimap<Long, GlEnum>, val commands: Map<String, GlCommand>) {
companion object {
private fun parseValue(s: String): Long {
return if (s.startsWith("0x")) {
java.lang.Long.parseUnsignedLong(s.substring(2), 16)
} else {
s.toLong()
}
}
fun parse(): GlRegistry {
return GlRegistry::class.java.getResourceAsStream("/dev/openrs2/deob/ast/gl.xml").use(::parse)
}
fun parse(input: InputStream): GlRegistry {
val root = SAXBuilder().build(input).rootElement
// create enums and groups
val enumsBuilder = ImmutableSetMultimap.builder<Long, GlEnum>()
val groupsBuilder = HashMultimap.create<String, GlEnum>()
for (elements in root.getChildren("enums")) {
for (element in elements.getChildren("enum")) {
val name = element.getAttributeValue("name")
val value = parseValue(element.getAttributeValue("value"))
val enum = GlEnum(name, value)
enumsBuilder.put(value, enum)
val groups = element.getAttributeValue("group") ?: continue
for (group in groups.split(",")) {
groupsBuilder.put(group, enum)
}
}
}
val groups = groupsBuilder.asMap().mapValues { entry ->
// sort by name length ascending so names with vendor suffixes come last
GlGroup(entry.key, entry.value.sortedBy { enum -> enum.name.length }.toList())
}
// create parameters and commands
val commands = mutableMapOf<String, GlCommand>()
for (element in root.getChild("commands").getChildren("command")) {
val commandName = element.getChild("proto").getChildText("name")
val parameters = mutableListOf<GlParameter>()
for (paramElement in element.getChildren("param")) {
val paramName = paramElement.getChildText("name")
val type = paramElement.getChildText("ptype")
val bitfield = type == "GLbitfield"
val groupName = paramElement.getAttributeValue("group")
val group = if (groupName != null) {
groups[groupName]
} else {
null
}
parameters += GlParameter(paramName, bitfield, group)
}
commands[commandName] = GlCommand(commandName, parameters)
}
return GlRegistry(enumsBuilder.build(), commands)
}
}
}

207
deob-ast/src/main/java/dev/openrs2/deob/ast/transform/GlConstantTransformer.kt

@ -0,0 +1,207 @@
package dev.openrs2.deob.ast.transform
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.NodeList
import com.github.javaparser.ast.body.BodyDeclaration
import com.github.javaparser.ast.body.FieldDeclaration
import com.github.javaparser.ast.body.VariableDeclarator
import com.github.javaparser.ast.expr.BinaryExpr
import com.github.javaparser.ast.expr.Expression
import com.github.javaparser.ast.expr.FieldAccessExpr
import com.github.javaparser.ast.expr.IntegerLiteralExpr
import com.github.javaparser.ast.expr.MethodCallExpr
import com.github.javaparser.ast.expr.NameExpr
import com.github.javaparser.ast.type.PrimitiveType
import com.github.javaparser.resolution.types.ResolvedPrimitiveType
import com.github.michaelbull.logging.InlineLogger
import dev.openrs2.deob.ast.gl.GlCommand
import dev.openrs2.deob.ast.gl.GlEnum
import dev.openrs2.deob.ast.gl.GlGroup
import dev.openrs2.deob.ast.gl.GlParameter
import dev.openrs2.deob.ast.gl.GlRegistry
import dev.openrs2.deob.ast.util.checkedAsInt
import dev.openrs2.deob.ast.util.walk
class GlConstantTransformer : Transformer() {
private val enums = mutableSetOf<GlEnum>()
override fun preTransform(units: Map<String, CompilationUnit>) {
enums.clear()
}
override fun transformUnit(
units: Map<String, CompilationUnit>,
unit: CompilationUnit
) {
if (!units.containsKey(GL_CLASS)) {
return
}
unit.walk { expr: MethodCallExpr ->
if (!expr.nameAsString.startsWith("gl")) {
return@walk
}
expr.scope.ifPresent { scope ->
val type = scope.calculateResolvedType()
if (!type.isReferenceType) {
return@ifPresent
}
val name = type.asReferenceType().qualifiedName
if (name == GL_CLASS) {
transformCall(unit, expr)
}
}
}
}
override fun postTransform(units: Map<String, CompilationUnit>) {
val glUnit = units[GL_CLASS] ?: return
val glInterface = glUnit.primaryType.orElseThrow()
// remove existing declarations first to maintain sort order
for (enum in enums) {
val declaration = glInterface.getFieldByName(enum.name)
declaration.ifPresent { it.remove() }
}
val fields = enums.sortedBy(GlEnum::value).map { it.toDeclaration() }
glInterface.members.addAll(0, fields)
}
private fun transformCall(unit: CompilationUnit, expr: MethodCallExpr) {
val name = expr.nameAsString
val command = REGISTRY.commands[name] ?: error("Failed to find $name in the OpenGL registry")
var offset = false
var registryIndex = 0
for (argument in expr.arguments) {
val type = argument.calculateResolvedType()
if (offset) {
if (type != ResolvedPrimitiveType.INT) {
error("Expecting integer offset after primitive array")
}
offset = false
continue
}
when {
type.isArray -> offset = type.asArrayType().componentType.isPrimitive
type.isPrimitive -> transformArgument(unit, command, command.parameters[registryIndex], argument)
!type.isReferenceType -> error("Expecting array, reference or primitive type")
}
registryIndex++
}
if (registryIndex != command.parameters.size) {
error("Command parameters inconsistent with registry")
}
}
private val GlCommand.vendor: String?
get() = VENDORS.firstOrNull { name.endsWith(it) }
private fun GlGroup.firstEnumOrNull(value: Int, vendor: String?): GlEnum? {
if (vendor != null) {
val enum = enums.filter { it.name.endsWith("_$vendor") }.firstOrNull { it.value == value.toLong() }
if (enum != null) {
return enum
}
}
return enums.firstOrNull { it.value == value.toLong() }
}
private fun GlEnum.toExpr(): Expression {
return FieldAccessExpr(NameExpr(GL_CLASS_UNQUALIFIED), name)
}
private fun GlEnum.toDeclaration(): BodyDeclaration<*> {
return FieldDeclaration(
NodeList(),
VariableDeclarator(PrimitiveType.intType(), name, value.toInt().toHexLiteralExpr())
)
}
private fun Int.toHexLiteralExpr(): IntegerLiteralExpr {
return IntegerLiteralExpr("0x${Integer.toUnsignedString(this, 16)}")
}
private fun transformArgument(
unit: CompilationUnit,
command: GlCommand,
parameter: GlParameter,
argument: Expression
) {
if (!argument.isIntegerLiteralExpr) {
return
}
var value = argument.asIntegerLiteralExpr().checkedAsInt()
val vendor = command.vendor
val group = parameter.group ?: return
if (parameter.bitfield) {
if (value == 0) {
return
}
val bitfieldEnums = mutableListOf<GlEnum>()
for (i in 0..31) {
val bit = 1 shl i
if (value and bit == 0) {
continue
}
val enum = group.firstEnumOrNull(bit, vendor)
if (enum != null) {
bitfieldEnums += enum
value = value and bit.inv()
}
}
if (bitfieldEnums.isEmpty()) {
logger.warn { "Missing all enums in ${command.name}'s ${parameter.name} bitfield: $value" }
return
}
unit.addImport(GL_CLASS)
enums += bitfieldEnums
val expr = bitfieldEnums.sortedBy(GlEnum::value)
.map { it.toExpr() }
.reduce { a, b -> BinaryExpr(a, b, BinaryExpr.Operator.BINARY_OR) }
if (value != 0) {
logger.warn { "Missing some enums in ${command.name}'s ${parameter.name} bitfield: $value" }
argument.replace(BinaryExpr(expr, value.toHexLiteralExpr(), BinaryExpr.Operator.BINARY_OR))
} else {
argument.replace(expr)
}
} else {
val enum = group.firstEnumOrNull(value, vendor)
if (enum != null) {
unit.addImport(GL_CLASS)
enums += enum
argument.replace(enum.toExpr())
} else {
logger.warn { "Missing enum for ${command.name}'s ${parameter.name} parameter: $value" }
}
}
}
companion object {
private val logger = InlineLogger()
private const val GL_CLASS_UNQUALIFIED = "GL"
private const val GL_CLASS = "javax.media.opengl.$GL_CLASS_UNQUALIFIED"
private val REGISTRY = GlRegistry.parse()
private val VENDORS = setOf("ARB", "EXT")
}
}

51692
deob-ast/src/main/resources/dev/openrs2/deob/ast/gl.xml

File diff suppressed because it is too large
Loading…
Cancel
Save