forked from openrs2/openrs2
parent
597aa2018e
commit
7db0c971ee
@ -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) |
||||
} |
||||
} |
||||
} |
@ -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") |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue