fix remaining issues, improve speed

This commit is contained in:
moehreag 2024-05-28 14:15:21 +02:00
parent 80a9fc3fdc
commit 67c253f2f0
3 changed files with 75 additions and 78 deletions

View file

@ -26,7 +26,7 @@ dependencies {
implementation("com.google.code.gson:gson:2.10.1") implementation("com.google.code.gson:gson:2.10.1")
implementation("org.vineflower:vineflower:1.10.1") implementation("org.vineflower:vineflower:1.10.1")
testImplementation(kotlin("test")) testImplementation(kotlin("test"))
implementation("com.electronwill.night-config:toml:3.7.1") implementation("com.electronwill.night-config:toml:3.7.2")
implementation("com.google.jimfs:jimfs:1.3.0") implementation("com.google.jimfs:jimfs:1.3.0")
} }

View file

@ -7,12 +7,15 @@ import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.plugins.JavaPluginExtension
import org.objectweb.asm.* import org.objectweb.asm.*
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.nio.file.* import java.nio.file.FileSystems
import java.nio.file.attribute.BasicFileAttributes import java.nio.file.Path
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentSkipListSet
import java.util.regex.Pattern import java.util.regex.Pattern
import java.util.stream.Stream
import kotlin.io.path.bufferedReader import kotlin.io.path.bufferedReader
import kotlin.io.path.exists
import kotlin.io.path.readBytes import kotlin.io.path.readBytes
import kotlin.io.path.readLines
import kotlin.io.path.writeBytes import kotlin.io.path.writeBytes
object AccessWidener { object AccessWidener {
@ -21,13 +24,12 @@ object AccessWidener {
private var awHash = "" private var awHash = ""
private val HEADER = Pattern.compile("accessWidener\\s+v[12]\\s+.*").asMatchPredicate() private val HEADER = Pattern.compile("accessWidener\\s+v[12]\\s+.*").asMatchPredicate()
private val COMMENT = Pattern.compile("^#.*").asMatchPredicate()
private val SEPARATOR = "[\\t ]+".toRegex() private val SEPARATOR = "[\\t ]+".toRegex()
private fun getAWFile(project: Project): Path? { private fun getAWFile(project: Project): Path? {
project.extensions.getByType(JavaPluginExtension::class.java).sourceSets.forEach { set -> project.extensions.getByType(JavaPluginExtension::class.java).sourceSets.forEach { set ->
set.resources.filter { it.name == "frog.mod.toml" }.firstOrNull { set.resources.filter { it.name == "frog.mod.toml" }.firstOrNull {
if (it == null){ if (it == null) {
println("Please make sure a 'frog.mod.toml' file is present in the mod's resources!") println("Please make sure a 'frog.mod.toml' file is present in the mod's resources!")
return null return null
} }
@ -58,37 +60,39 @@ object AccessWidener {
return false return false
} }
private fun readAW(project: Project): List<String> { private fun readAW(project: Project): Stream<String> {
val awFile = getAWFile(project) val awFile = getAWFile(project)
println("Found accesswidener in project: $awFile") println("Found accesswidener in project: $awFile")
return awFile?.readLines(StandardCharsets.UTF_8)?.map { it.replace("transitive-", "") }?.toList() return awFile?.bufferedReader(StandardCharsets.UTF_8)?.lines()?.map { it.replace("transitive-", "") }
?: listOf() ?: Stream.empty()
} }
private fun readTransitiveAW(path: Path): List<String> { private fun readTransitiveAW(path: Path): Stream<String> {
return path.bufferedReader(StandardCharsets.UTF_8).lines().filter { it.startsWith("transitive-") }.toList() return path.bufferedReader(StandardCharsets.UTF_8).lines().filter { it.startsWith("transitive-") }
} }
private fun parseAW(entries: List<String>): List<Entry> { private fun readAllAWs(project: Project): Stream<Entry> {
return entries.asSequence().filter { it.isNotBlank() } return Stream.concat(
.filter { !COMMENT.test(it) }.filter { !HEADER.test(it) }.distinct() findDependencyAWs(project).parallelStream().flatMap { i -> readTransitiveAW(i) },
.map { it.split(SEPARATOR) }.filter { it.isNotEmpty() } .map { Entry(it) }.toList() readAW(project)
} )
.filter { it.isNotBlank() }
private fun readAllAWs(project: Project): List<Entry> { .map { if (it.contains("#")) it.split("#")[0] else it }.filter { !HEADER.test(it) }
return findDependencyAWs(project).flatMap { readTransitiveAW(it) }.plus(readAW(project)) .filter { it.isNotBlank() }
.let { parseAW(it) } .unordered().distinct()
.map { it.split(SEPARATOR) }.filter { it.isNotEmpty() }.map { Entry(it) }
} }
fun apply(project: Project, input: Path) { fun apply(project: Project, input: Path) {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
val entries: List<Entry> = readAllAWs(project) val classNames: MutableSet<String> = ConcurrentSkipListSet()
val classMap: MutableMap<String, Entry> = ConcurrentHashMap()
val classMap: MutableMap<String, Entry> = mutableMapOf() val methods: MutableMap<String, MutableMap<String, Entry>> = ConcurrentHashMap()
val methods: MutableMap<String, MutableMap<String, Entry>> = mutableMapOf() val fields: MutableMap<String, MutableMap<String, Entry>> = ConcurrentHashMap()
val fields: MutableMap<String, MutableMap<String, Entry>> = mutableMapOf() val mutations: MutableMap<String, MutableMap<String, Entry>> = ConcurrentHashMap()
val mutations: MutableMap<String, MutableMap<String, Entry>> = mutableMapOf() val entries = readAllAWs(project)
entries.forEach { e -> entries.forEach { e ->
classNames.add(e.className)
if ("class" == e.targetType) { if ("class" == e.targetType) {
if (e.type == AccessType.MUTABLE) { if (e.type == AccessType.MUTABLE) {
throw IllegalArgumentException("aw format error: classes can not have a 'mutable' modifier (at: $e)") throw IllegalArgumentException("aw format error: classes can not have a 'mutable' modifier (at: $e)")
@ -105,7 +109,7 @@ object AccessWidener {
if (e.type == AccessType.MUTABLE) { if (e.type == AccessType.MUTABLE) {
throw IllegalArgumentException("aw format error: methods can not have a 'mutable' modifier (at: $e)") throw IllegalArgumentException("aw format error: methods can not have a 'mutable' modifier (at: $e)")
} }
val map = methods.computeIfAbsent(e.className) { mutableMapOf() } val map = methods.computeIfAbsent(e.className) { ConcurrentHashMap() }
val id = e.name + e.descriptor val id = e.name + e.descriptor
if (!map.containsKey(id)) { if (!map.containsKey(id)) {
map[id] = e map[id] = e
@ -119,28 +123,43 @@ object AccessWidener {
if (e.type == AccessType.EXTENDABLE) { if (e.type == AccessType.EXTENDABLE) {
throw IllegalArgumentException("aw format error: fields can not have a 'extendable' modifier (at: $e)") throw IllegalArgumentException("aw format error: fields can not have a 'extendable' modifier (at: $e)")
} }
val map = fields.computeIfAbsent(e.className) { mutableMapOf() } val map = fields.computeIfAbsent(e.className) { ConcurrentHashMap() }
val id = e.name + e.descriptor val id = e.name + e.descriptor
if (e.type == AccessType.MUTABLE) { if (e.type == AccessType.MUTABLE) {
mutations.computeIfAbsent(e.className) { mutableMapOf() }.putIfAbsent(id, e) mutations.computeIfAbsent(e.className) { ConcurrentHashMap() }.putIfAbsent(id, e)
return
}
if (!map.containsKey(id)) {
map[id] = e
} else { } else {
val other = map[id]!! if (!map.containsKey(id)) {
if (e.isAccessGreaterThan(other)) { map[id] = e
classMap[id] = e } else {
val other = map[id]!!
if (e.isAccessGreaterThan(other)) {
classMap[id] = e
}
} }
} }
} }
} }
FileSystems.newFileSystem(input).use { FileSystems.newFileSystem(input).use {
Files.walkFileTree(it.getPath("/"), AWFileVisitor(classMap, fields, methods, mutations)) classNames.parallelStream().forEach { className ->
val file = it.getPath("/$className.class")
if (file.exists()) {
val reader = ClassReader(file.readBytes())
val writer = ClassWriter(0)
val mapper = AWClassVisitor(writer, classMap, fields, methods, mutations, className)
reader.accept(mapper, 0)
file.writeBytes(writer.toByteArray())
}
}
} }
println("Finished widening ${classMap.size} classes, ${methods.size} methods and ${fields.size+mutations.size} fields in ${"%2f".format(System.currentTimeMillis()-startTime)}") println(
"Applied AccessWideners in ${
"%.2fs".format(
(System.currentTimeMillis() - startTime) / 1000f
)
}"
)
} }
} }
@ -171,7 +190,8 @@ enum class AccessType(val id: String, val access: Int) {
companion object { companion object {
fun of(name: String): AccessType { fun of(name: String): AccessType {
return entries.first { a -> a.id == name } return entries.firstOrNull { a -> a.id == name }
?: throw IllegalArgumentException("Access type '$name' is not known. Known types: ${entries.map { it.id }} (and transitive variants)")
} }
} }
} }
@ -195,31 +215,6 @@ private enum class Access(val index: Int) {
} }
} }
class AWFileVisitor(
private val classMap: Map<String, Entry>,
private val fields: Map<String, Map<String, Entry>>,
private val methods: Map<String, Map<String, Entry>>,
private val mutations: Map<String, Map<String, Entry>>,
) : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
if (file.fileName.toString().endsWith(".class")) {
val className = file.toString().substring(1, file.toString().length - 6)
if (classMap.containsKey(className) || fields.containsKey(className) || methods.containsKey(className) || mutations.containsKey(className)) {
val reader = ClassReader(file.readBytes())
val writer = ClassWriter(0)
val mapper = AWClassVisitor(writer, classMap, fields, methods, mutations, className)
reader.accept(mapper, 0)
file.writeBytes(writer.toByteArray())
}
}
return FileVisitResult.CONTINUE
}
}
class AWClassVisitor( class AWClassVisitor(
visitor: ClassVisitor, visitor: ClassVisitor,
private val classMap: Map<String, Entry>, private val classMap: Map<String, Entry>,
@ -239,14 +234,15 @@ class AWClassVisitor(
var access = acc var access = acc
val e = classMap[className] val e = classMap[className]
if (e != null) { if (e != null) {
access = access and ((Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED or Opcodes.ACC_PUBLIC).inv()) access = access and (Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED or Opcodes.ACC_PUBLIC).inv()
access = access.or(e.type.access) access = access or e.type.access
} else if (fields.containsKey(className) || methods.containsKey(className) || mutations.containsKey( }
if (fields.containsKey(className) || methods.containsKey(className) || mutations.containsKey(
className className
) )
) { // make all classes with modifications public as well ) { // make all classes with modifications public as well
access = access and ((Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED or Opcodes.ACC_PUBLIC).inv()) access = access and (Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED or Opcodes.ACC_PUBLIC).inv()
access = access.or(Opcodes.ACC_PUBLIC) access = access or Opcodes.ACC_PUBLIC
} }
super.visit(version, access, name, signature, superName, interfaces) super.visit(version, access, name, signature, superName, interfaces)
} }
@ -263,15 +259,16 @@ class AWClassVisitor(
if (map != null) { if (map != null) {
val e = map[name + descriptor] val e = map[name + descriptor]
if (e != null) { if (e != null) {
access = access and ((Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED or Opcodes.ACC_PUBLIC).inv())// remove all access modifiers access =
access = access.or(e.type.access)// re-add the new one access and (Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED or Opcodes.ACC_PUBLIC).inv() // remove all access modifiers
access = access or e.type.access // re-add the new one
} }
} }
map = mutations[className] map = mutations[className]
if (map != null) { if (map != null) {
val e = map[name + descriptor] val e = map[name + descriptor]
if (e != null) { if (e != null) {
access = access.or(Opcodes.ACC_FINAL.inv()) // always AccessType.MUTABLE access = access and Opcodes.ACC_FINAL.inv() // always AccessType.MUTABLE
} }
} }
return super.visitField(access, name, descriptor, signature, value) return super.visitField(access, name, descriptor, signature, value)
@ -289,8 +286,9 @@ class AWClassVisitor(
if (map != null) { if (map != null) {
val e = map[name + descriptor] val e = map[name + descriptor]
if (e != null) { if (e != null) {
access = access and ((Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED or Opcodes.ACC_PUBLIC).inv())// remove all access modifiers access =
access = access.or(e.type.access)// re-add the new one access and ((Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED or Opcodes.ACC_PUBLIC).inv())// remove all access modifiers
access = access or (e.type.access)// re-add the new one
} }
} }
return super.visitMethod(access, name, descriptor, signature, exceptions) return super.visitMethod(access, name, descriptor, signature, exceptions)

View file

@ -28,8 +28,8 @@ fun Project.minecraft(
val remappedJar = clientJar.resolveSibling("client-$version-remapped.jar") val remappedJar = clientJar.resolveSibling("client-$version-remapped.jar")
PhytotelmaPlugin.remappedGameJarPath = remappedJar PhytotelmaPlugin.remappedGameJarPath = remappedJar
println("Time to setup Minecraft!") println("Time to setup Minecraft!")
var applyAW = false val applyAW = AccessWidener.needsUpdate(this)
if (remappedJar.notExists() || AccessWidener.needsUpdate(this)) { if (remappedJar.notExists() || applyAW) {
println("Remapping the game...") println("Remapping the game...")
val data = ProguardParser.read(MojmapProvider.get(version, clientJar.resolveSibling("client-$version.txt")).orElseThrow()).reverse() val data = ProguardParser.read(MojmapProvider.get(version, clientJar.resolveSibling("client-$version.txt")).orElseThrow()).reverse()
val paramMappings = ParchmentProvider.getParchment( val paramMappings = ParchmentProvider.getParchment(
@ -37,7 +37,6 @@ fun Project.minecraft(
PhytotelmaPlugin.nonsenseCacheDir.resolve("org/parchmentmc/parchment/$version/$parchmentVersion") PhytotelmaPlugin.nonsenseCacheDir.resolve("org/parchmentmc/parchment/$version/$parchmentVersion")
) )
NonsenseRemapper.remap(data, clientJar, remappedJar, true, paramMappings) NonsenseRemapper.remap(data, clientJar, remappedJar, true, paramMappings)
applyAW = true
} }
println("Adding dependencies...") println("Adding dependencies...")
dependencies.add("implementation","net.minecrell:terminalconsoleappender:1.2.0") dependencies.add("implementation","net.minecrell:terminalconsoleappender:1.2.0")