Datagen insanity, dependency, mixin & AW remapping #4
|
@ -7,7 +7,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "dev.frogmc"
|
group = "dev.frogmc"
|
||||||
version = "0.0.1-alpha.15"
|
version = "0.0.1-alpha.16"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
|
@ -24,7 +24,7 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("dev.frogmc:thyroxine:0.0.1-alpha.6")
|
implementation("dev.frogmc:thyroxine:0.0.1-alpha.10")
|
||||||
implementation("org.ow2.asm:asm:9.7")
|
implementation("org.ow2.asm:asm:9.7")
|
||||||
implementation("org.ow2.asm:asm-commons:9.7")
|
implementation("org.ow2.asm:asm-commons:9.7")
|
||||||
implementation("org.ow2.asm:asm-tree:9.7")
|
implementation("org.ow2.asm:asm-tree:9.7")
|
||||||
|
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
||||||
#Sun May 12 17:35:40 BST 2024
|
#Sun May 12 17:35:40 BST 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package dev.frogmc.phytotelma
|
package dev.frogmc.phytotelma
|
||||||
|
|
||||||
|
import org.objectweb.asm.Opcodes
|
||||||
|
|
||||||
object Constants {
|
object Constants {
|
||||||
|
const val MOJMAP_NAMESPACE = "mojmap"
|
||||||
const val MOD_EXTENSION = ".frogmod"
|
const val MOD_EXTENSION = ".frogmod"
|
||||||
const val MOD_METADATA_FILE = "frog.mod.toml"
|
const val MOD_METADATA_FILE = "frog.mod.toml"
|
||||||
const val MINECRAFT_CONFIGURATION = "minecraftDependency"
|
const val MINECRAFT_CONFIGURATION = "minecraftDependency"
|
||||||
|
@ -14,4 +17,7 @@ object Constants {
|
||||||
const val RUNT_SERVER_TASK = "runServer"
|
const val RUNT_SERVER_TASK = "runServer"
|
||||||
const val CLEAR_LOCAL_CACHE_TASK = "clearLocalCache"
|
const val CLEAR_LOCAL_CACHE_TASK = "clearLocalCache"
|
||||||
const val CLEAR_GLOBAL_CACHE_TASK = "clearGlobalCache"
|
const val CLEAR_GLOBAL_CACHE_TASK = "clearGlobalCache"
|
||||||
|
const val ASM_VERSION = Opcodes.ASM9
|
||||||
|
const val ALTERNATIVE_RUNTIME_JAR_NAME = "runtime.jar"
|
||||||
|
const val MOJANG_MANIFEST_URL = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
|
||||||
}
|
}
|
|
@ -1,32 +1,41 @@
|
||||||
package dev.frogmc.phytotelma
|
package dev.frogmc.phytotelma
|
||||||
|
|
||||||
|
import com.electronwill.nightconfig.core.CommentedConfig
|
||||||
import com.electronwill.nightconfig.core.file.FileNotFoundAction
|
import com.electronwill.nightconfig.core.file.FileNotFoundAction
|
||||||
|
import com.electronwill.nightconfig.core.io.WritingMode
|
||||||
import com.electronwill.nightconfig.toml.TomlParser
|
import com.electronwill.nightconfig.toml.TomlParser
|
||||||
|
import com.electronwill.nightconfig.toml.TomlWriter
|
||||||
import dev.frogmc.phytotelma.accesswidener.AccessWidener
|
import dev.frogmc.phytotelma.accesswidener.AccessWidener
|
||||||
import dev.frogmc.phytotelma.build.PhytotelmaBuildTask
|
import dev.frogmc.phytotelma.build.PhytotelmaBuildTask
|
||||||
import dev.frogmc.phytotelma.common.Env
|
import dev.frogmc.phytotelma.common.Env
|
||||||
import dev.frogmc.phytotelma.ext.PhytotelmaGradleExtension
|
import dev.frogmc.phytotelma.ext.PhytotelmaGradleExtension
|
||||||
import dev.frogmc.phytotelma.ext.PhytotelmaGradleExtensionImpl
|
import dev.frogmc.phytotelma.ext.PhytotelmaGradleExtensionImpl
|
||||||
import dev.frogmc.phytotelma.mappings.renameDstNamespace
|
import dev.frogmc.phytotelma.mappings.renameDstNamespace
|
||||||
|
import dev.frogmc.phytotelma.mixin.remapper.MixinAnnotationRemapper
|
||||||
|
import dev.frogmc.phytotelma.nest.NestStripper
|
||||||
|
import dev.frogmc.phytotelma.nest.Nester
|
||||||
import dev.frogmc.phytotelma.run.AssetDownloader
|
import dev.frogmc.phytotelma.run.AssetDownloader
|
||||||
import dev.frogmc.phytotelma.run.RunConfigGenerator
|
import dev.frogmc.phytotelma.run.RunConfigGenerator
|
||||||
import dev.frogmc.phytotelma.run.task.RunGameTask
|
import dev.frogmc.phytotelma.run.task.RunGameTask
|
||||||
import dev.frogmc.phytotelma.vineflower.FrogJavadocProvider
|
import dev.frogmc.phytotelma.vineflower.FrogJavadocProvider
|
||||||
|
import dev.frogmc.thyroxine.RemappingStep
|
||||||
import dev.frogmc.thyroxine.Thyroxine
|
import dev.frogmc.thyroxine.Thyroxine
|
||||||
import dev.frogmc.thyroxine.api.Mapper
|
import dev.frogmc.thyroxine.api.Mapper
|
||||||
import dev.frogmc.thyroxine.api.data.MappingBundle
|
import dev.frogmc.thyroxine.api.data.MappingBundle
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData
|
||||||
import dev.frogmc.thyroxine.provider.MojmapProvider
|
import dev.frogmc.thyroxine.provider.MojmapProvider
|
||||||
|
import dev.frogmc.thyroxine.writer.tiny.TinyV2Writer
|
||||||
import net.fabricmc.fernflower.api.IFabricJavadocProvider
|
import net.fabricmc.fernflower.api.IFabricJavadocProvider
|
||||||
import org.gradle.api.Plugin
|
import org.gradle.api.Plugin
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.plugins.JavaBasePlugin
|
import org.gradle.api.plugins.JavaBasePlugin
|
||||||
import org.gradle.api.plugins.JavaPlugin
|
import org.gradle.api.plugins.JavaPlugin
|
||||||
import org.gradle.api.tasks.Delete
|
import org.gradle.api.tasks.Delete
|
||||||
import org.gradle.configurationcache.extensions.capitalized
|
|
||||||
import org.jetbrains.java.decompiler.main.Fernflower
|
import org.jetbrains.java.decompiler.main.Fernflower
|
||||||
import org.jetbrains.java.decompiler.main.decompiler.PrintStreamLogger
|
import org.jetbrains.java.decompiler.main.decompiler.PrintStreamLogger
|
||||||
import org.jetbrains.java.decompiler.main.decompiler.SingleFileSaver
|
import org.jetbrains.java.decompiler.main.decompiler.SingleFileSaver
|
||||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences
|
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences
|
||||||
|
import org.objectweb.asm.commons.ClassRemapper
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
@ -34,6 +43,7 @@ import java.nio.file.FileSystems
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
|
import java.util.*
|
||||||
import kotlin.io.path.*
|
import kotlin.io.path.*
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,7 +94,7 @@ class PhytotelmaPlugin : Plugin<Project> {
|
||||||
)
|
)
|
||||||
|
|
||||||
ModConfigurations.configurations.forEach { conf ->
|
ModConfigurations.configurations.forEach { conf ->
|
||||||
project.configurations.create("mod" + conf.name.capitalized()) { c ->
|
project.configurations.create("mod" + conf.name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() }) { c ->
|
||||||
c.isCanBeResolved = true
|
c.isCanBeResolved = true
|
||||||
c.isCanBeConsumed = false
|
c.isCanBeConsumed = false
|
||||||
|
|
||||||
|
@ -116,6 +126,12 @@ class PhytotelmaPlugin : Plugin<Project> {
|
||||||
}
|
}
|
||||||
|
|
||||||
setupTasks(project)
|
setupTasks(project)
|
||||||
|
|
||||||
|
project.afterEvaluate {
|
||||||
|
project.afterEvaluate {
|
||||||
|
ProjectStorage.write(project)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupTasks(project: Project) {
|
private fun setupTasks(project: Project) {
|
||||||
|
@ -186,60 +202,51 @@ class PhytotelmaPlugin : Plugin<Project> {
|
||||||
|
|
||||||
project.tasks.getByName(JavaPlugin.JAR_TASK_NAME).actions.addLast { task ->
|
project.tasks.getByName(JavaPlugin.JAR_TASK_NAME).actions.addLast { task ->
|
||||||
val storage = ProjectStorage.get(project)
|
val storage = ProjectStorage.get(project)
|
||||||
if (storage.targetNamespace != "mojmap") {
|
if (storage.targetNamespace != Constants.MOJMAP_NAMESPACE) {
|
||||||
val mappings = MappingBundle.merge(
|
val moj = if (storage.intermediaryNs == Constants.MOJMAP_NAMESPACE) {
|
||||||
storage.mappings!!.reverse(), MojmapProvider.get(
|
MojmapProvider.get(
|
||||||
storage.minecraftVersion!!,
|
storage.minecraftVersion!!,
|
||||||
globalCacheDir.resolve("net/minecraft/client/${storage.minecraftVersion}/client-${storage.minecraftVersion}.txt")
|
globalCacheDir.resolve("net/minecraft/client/${storage.minecraftVersion}/client-${storage.minecraftVersion}.txt")
|
||||||
).orElseThrow().reverse().renameDstNamespace("mojmap")
|
).orElseThrow().reverse().renameDstNamespace(Constants.MOJMAP_NAMESPACE)
|
||||||
).forNamespaces(storage.targetNamespace, "mojmap")
|
} else null
|
||||||
val parser = TomlParser()
|
val mappings = (moj?.let {
|
||||||
|
MappingBundle.merge(
|
||||||
|
storage.mappings!!.reverse(), it
|
||||||
|
)
|
||||||
|
} ?: storage.mappings!!).forNamespaces(storage.targetNamespace, storage.intermediaryNs)
|
||||||
|
val includeConfiguration = project.configurations.findByName(Constants.INCLUDE_CONFIGURATION)
|
||||||
task.outputs.files.forEach { file ->
|
task.outputs.files.forEach { file ->
|
||||||
val temp = Files.createTempFile("", file.name)
|
val temp = Files.createTempFile("", file.name)
|
||||||
Files.copy(file.toPath(), temp, StandardCopyOption.REPLACE_EXISTING)
|
Files.copy(file.toPath(), temp, StandardCopyOption.REPLACE_EXISTING)
|
||||||
FileSystems.newFileSystem(temp).use { fs ->
|
FileSystems.newFileSystem(temp).use { fs ->
|
||||||
|
if (includeConfiguration != null) {
|
||||||
|
val jijPath = fs.getPath("META-INF/jars")
|
||||||
|
val files = Nester.run(includeConfiguration, jijPath).map { it.toml() }.toList()
|
||||||
|
if (files.isNotEmpty()) {
|
||||||
|
val manifest = fs.getPath(Constants.MOD_METADATA_FILE)
|
||||||
|
val config: CommentedConfig =
|
||||||
|
tomlParser.parse(manifest, FileNotFoundAction.THROW_ERROR)
|
||||||
|
if (!config.add("frog.extensions.included_jars", files)) {
|
||||||
|
println("Failed to add included jars to mod manifest, make sure it doesn't include a key at 'frog.extensions.included_jars'!")
|
||||||
|
}
|
||||||
|
tomlWriter.write(config, manifest, WritingMode.REPLACE)
|
||||||
|
}
|
||||||
|
}
|
||||||
val metadata = fs.getPath(Constants.MOD_METADATA_FILE)
|
val metadata = fs.getPath(Constants.MOD_METADATA_FILE)
|
||||||
parser.parse(metadata, FileNotFoundAction.READ_NOTHING)
|
tomlParser.parse(metadata, FileNotFoundAction.READ_NOTHING)
|
||||||
.get<String>("frog.extensions.accesswidener")?.let { name ->
|
.get<String>("frog.extensions.accesswidener")?.let { name ->
|
||||||
val aw = metadata.resolveSibling(name)
|
val aw = metadata.resolveSibling(name)
|
||||||
AccessWidener.checkAW(aw, ProjectStorage.get(project).remappedGameJarPath!!)
|
AccessWidener.checkAW(aw, ProjectStorage.get(project).remappedGameJarPath!!)
|
||||||
val mapper = Mapper(mappings) { listOf() }
|
remapAccesswidener(mappings, aw)
|
||||||
val buffer = buildString {
|
|
||||||
aw.forEachLine {
|
|
||||||
if (it.contains("\\t") && !it.startsWith("#")) {
|
|
||||||
val parts = it.split("[\\t #]+".toRegex()).toMutableList()
|
|
||||||
|
|
||||||
if (parts.size > 2) {
|
|
||||||
|
|
||||||
val type = parts[1]
|
|
||||||
when (type) {
|
|
||||||
"class" -> {
|
|
||||||
parts[2] = mapper.map(parts[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
"fields" -> {
|
|
||||||
parts[3] = mapper.mapFieldName(parts[2], parts[3], parts[4])
|
|
||||||
parts[4] = mapper.mapDesc(parts[4])
|
|
||||||
parts[3] = mapper.map(parts[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
"methods" -> {
|
|
||||||
parts[3] = mapper.mapMethodName(parts[2], parts[3], parts[4])
|
|
||||||
parts[4] = mapper.mapMethodDesc(parts[4])
|
|
||||||
parts[2] = mapper.map(parts[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appendLine(parts.joinToString("\\t"))
|
|
||||||
return@forEachLine
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appendLine(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aw.writeText(buffer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Thyroxine.remap(mappings, temp, file.toPath(), false, false)
|
Thyroxine.remap(
|
||||||
|
mappings,
|
||||||
|
temp,
|
||||||
|
file.toPath(),
|
||||||
|
false,
|
||||||
|
defaultRemappingSteps(ProjectStorage.get(project).remappedGameJarPath!!)
|
||||||
|
)
|
||||||
Files.deleteIfExists(temp)
|
Files.deleteIfExists(temp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,29 +274,57 @@ class PhytotelmaPlugin : Plugin<Project> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun defaultRemappingSteps(sourceNsJar: Path): MutableList<RemappingStep> {
|
||||||
|
return mutableListOf(
|
||||||
|
RemappingStep(::ClassRemapper),
|
||||||
|
RemappingStep { cv, mapper -> MixinAnnotationRemapper(cv, mapper, sourceNsJar) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun remapModDependencies(project: Project) {
|
private fun remapModDependencies(project: Project) {
|
||||||
val out = System.out
|
val out = System.out
|
||||||
// Mute the output from thyroxine as there may be a lot of remapping operations here
|
// Mute the output from thyroxine as there may be a lot of remapping operations here
|
||||||
System.setOut(PrintStream(OutputStream.nullOutputStream()))
|
System.setOut(PrintStream(OutputStream.nullOutputStream()))
|
||||||
|
val mojmapGameJar = ProjectStorage.get(project).remappedGameJarPath!!.resolveSibling("mojmap.jar")
|
||||||
|
val version = ProjectStorage.get(project).minecraftVersion!!
|
||||||
|
val officialJar = VersionChecker.downloadClient(project, version)
|
||||||
ModConfigurations.configurations.forEach { conf ->
|
ModConfigurations.configurations.forEach { conf ->
|
||||||
val artifacts = project.configurations.getByName("mod" + conf.name.capitalized())
|
val artifacts = project.configurations.getByName("mod" + conf.name.replaceFirstChar {
|
||||||
|
if (it.isLowerCase()) it.titlecase(
|
||||||
|
Locale.getDefault()
|
||||||
|
) else it.toString()
|
||||||
|
})
|
||||||
.resolvedConfiguration.resolvedArtifacts
|
.resolvedConfiguration.resolvedArtifacts
|
||||||
if (artifacts.isEmpty()) {
|
if (artifacts.isEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val target = project.configurations.create("mod" + conf.name.capitalized() + "Mapped") { c ->
|
if (mojmapGameJar.notExists()) {
|
||||||
|
Thyroxine.run(version, officialJar, mojmapGameJar, true, false)
|
||||||
|
}
|
||||||
|
val target = project.configurations.create("mod" + conf.name.replaceFirstChar {
|
||||||
|
if (it.isLowerCase()) it.titlecase(
|
||||||
|
Locale.getDefault()
|
||||||
|
) else it.toString()
|
||||||
|
} + "Mapped") { c ->
|
||||||
c.isTransitive = false
|
c.isTransitive = false
|
||||||
conf.classpathNames.forEach {
|
conf.classpathNames.forEach {
|
||||||
project.configurations.getByName(it).extendsFrom(c)
|
project.configurations.getByName(it).extendsFrom(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val storage = ProjectStorage.get(project)
|
val storage = ProjectStorage.get(project)
|
||||||
val mappings = MappingBundle.merge(
|
TinyV2Writer.write(
|
||||||
storage.mappings!!.reverse(), MojmapProvider.get(
|
storage.mappings!!,
|
||||||
storage.minecraftVersion!!,
|
project.projectDir.resolve("storageMappings.tiny").writer()
|
||||||
globalCacheDir.resolve("net/minecraft/client/${storage.minecraftVersion}/client-${storage.minecraftVersion}.txt")
|
)
|
||||||
).orElseThrow().reverse().renameDstNamespace("mojmap")
|
val officialStore = storage.mappings!!.forNamespaces(storage.targetNamespace!!, "official").reverse()
|
||||||
).forNamespaces("mojmap", storage.targetNamespace)
|
TinyV2Writer.write(
|
||||||
|
MappingBundle(officialStore),
|
||||||
|
project.projectDir.resolve("officialStore.tiny").toPath().writer()
|
||||||
|
)
|
||||||
|
val mojOfficial = MojmapProvider.get(
|
||||||
|
storage.minecraftVersion!!,
|
||||||
|
globalCacheDir.resolve("net/minecraft/client/${storage.minecraftVersion}/client-${storage.minecraftVersion}.txt")
|
||||||
|
).orElseThrow().reverse().renameDstNamespace(Constants.MOJMAP_NAMESPACE).data[0].reverse()
|
||||||
val targetPath = project.layout.buildDirectory.asFile.get().toPath().resolve("remappedMods")
|
val targetPath = project.layout.buildDirectory.asFile.get().toPath().resolve("remappedMods")
|
||||||
.resolve("dev/frogmc/phytotelma/remapped_mods")
|
.resolve("dev/frogmc/phytotelma/remapped_mods")
|
||||||
val remappedPaths = mutableListOf<Path>()
|
val remappedPaths = mutableListOf<Path>()
|
||||||
|
@ -297,10 +332,10 @@ class PhytotelmaPlugin : Plugin<Project> {
|
||||||
val group = artifact.moduleVersion.id.group
|
val group = artifact.moduleVersion.id.group
|
||||||
val name = artifact.moduleVersion.id.name
|
val name = artifact.moduleVersion.id.name
|
||||||
val groupname = (group + "_" + name).replace(".", "_")
|
val groupname = (group + "_" + name).replace(".", "_")
|
||||||
val version = artifact.moduleVersion.id.version
|
val artifactVersion = artifact.moduleVersion.id.version
|
||||||
val classifier = artifact.classifier
|
val classifier = artifact.classifier
|
||||||
val remappedPath = targetPath.resolve(groupname).resolve(version)
|
val remappedPath = targetPath.resolve(groupname).resolve(artifactVersion)
|
||||||
.resolve(groupname + "-" + version + (classifier?.let { "-$it" } ?: "") + ".jar")
|
.resolve(groupname + "-" + artifactVersion + (classifier?.let { "-$it" } ?: "") + ".jar")
|
||||||
remappedPath.createParentDirectories()
|
remappedPath.createParentDirectories()
|
||||||
remappedPaths.add(remappedPath)
|
remappedPaths.add(remappedPath)
|
||||||
|
|
||||||
|
@ -312,23 +347,97 @@ class PhytotelmaPlugin : Plugin<Project> {
|
||||||
"\t<modelVersion>4.0.0</modelVersion>\n" +
|
"\t<modelVersion>4.0.0</modelVersion>\n" +
|
||||||
"\t<groupId>dev.frogmc.phytotelma.remapped_mods</groupId>\n" +
|
"\t<groupId>dev.frogmc.phytotelma.remapped_mods</groupId>\n" +
|
||||||
"\t<artifactId>$groupname</artifactId>\n" +
|
"\t<artifactId>$groupname</artifactId>\n" +
|
||||||
"\t<version>$version</version>\n" +
|
"\t<version>$artifactVersion</version>\n" +
|
||||||
"</project>"
|
"</project>"
|
||||||
)
|
)
|
||||||
remappedPaths.add(pom)
|
remappedPaths.add(pom)
|
||||||
|
|
||||||
Thyroxine.remap(mappings, artifact.file.toPath(), remappedPath, false, false)
|
val temp = remappedPath.resolveSibling(remappedPath.fileName.toString() + ".tmp")
|
||||||
|
Thyroxine.remap(
|
||||||
|
mojOfficial, artifact.file.toPath(), temp, false, defaultRemappingSteps(
|
||||||
|
mojmapGameJar
|
||||||
|
), mojmapGameJar
|
||||||
|
)
|
||||||
|
Thyroxine.remap(
|
||||||
|
officialStore, temp, remappedPath, false, defaultRemappingSteps(
|
||||||
|
officialJar
|
||||||
|
), officialJar
|
||||||
|
)
|
||||||
|
Files.deleteIfExists(temp)
|
||||||
|
NestStripper.stripJij(remappedPath)
|
||||||
|
|
||||||
|
FileSystems.newFileSystem(remappedPath).use { fs ->
|
||||||
|
val metadata = fs.getPath(Constants.MOD_METADATA_FILE)
|
||||||
|
tomlParser.parse(metadata, FileNotFoundAction.READ_NOTHING)
|
||||||
|
.get<String>("frog.extensions.accesswidener")?.let { name ->
|
||||||
|
val aw = metadata.resolveSibling(name)
|
||||||
|
remapAccesswidener(mojOfficial, aw)
|
||||||
|
remapAccesswidener(officialStore, aw)
|
||||||
|
|
||||||
|
AccessWidener.checkAW(aw, ProjectStorage.get(project).remappedGameJarPath!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
project.dependencies.add(
|
project.dependencies.add(
|
||||||
target.name,
|
target.name,
|
||||||
"dev.frogmc.phytotelma.remapped_mods:$groupname:$version" + (classifier?.let { ":$it" } ?: "")
|
"dev.frogmc.phytotelma.remapped_mods:$groupname:$artifactVersion" + (classifier?.let { ":$it" }
|
||||||
|
?: "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Files.deleteIfExists(mojmapGameJar)
|
||||||
}
|
}
|
||||||
System.setOut(out)
|
System.setOut(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun remapAccesswidener(mappings: MappingData, aw: Path) {
|
||||||
|
val mapper = Mapper(mappings) { listOf() }
|
||||||
|
val buffer = buildString {
|
||||||
|
aw.forEachLine {
|
||||||
|
if ((it.contains("\t") || it.contains(" ")) && !it.startsWith("#")) {
|
||||||
|
val parts = it.split("[\\t #]+".toRegex()).toMutableList()
|
||||||
|
|
||||||
|
if (parts.size > 2) {
|
||||||
|
|
||||||
|
val type = parts[1]
|
||||||
|
when (type) {
|
||||||
|
"class" -> {
|
||||||
|
parts[2] = mapper.map(parts[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
"field" -> {
|
||||||
|
parts[3] = mapper.mapFieldName(parts[2], parts[3], parts[4])
|
||||||
|
parts[4] = mapper.mapDesc(parts[4])
|
||||||
|
parts[2] = mapper.map(parts[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
"method" -> {
|
||||||
|
parts[3] = mapper.mapMethodName(parts[2], parts[3], parts[4])
|
||||||
|
parts[4] = mapper.mapMethodDesc(parts[4])
|
||||||
|
parts[2] = mapper.map(parts[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appendLine(parts.joinToString(" "))
|
||||||
|
return@forEachLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appendLine(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aw.writeText(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
lateinit var globalCacheDir: Path
|
lateinit var globalCacheDir: Path
|
||||||
|
val tomlParser = TomlParser()
|
||||||
|
val tomlWriter = TomlWriter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Project.getId(): String {
|
||||||
|
var path = name
|
||||||
|
var parent: Project? = this
|
||||||
|
while ((parent?.parent.also { parent = it }) != null) {
|
||||||
|
path = parent!!.name + "." + path
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
|
@ -1,20 +1,64 @@
|
||||||
package dev.frogmc.phytotelma
|
package dev.frogmc.phytotelma
|
||||||
|
|
||||||
|
import com.google.gson.FieldNamingPolicy
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonToken
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
import dev.frogmc.thyroxine.api.data.MappingBundle
|
import dev.frogmc.thyroxine.api.data.MappingBundle
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
|
import kotlin.io.path.exists
|
||||||
|
import kotlin.io.path.reader
|
||||||
|
import kotlin.io.path.writeText
|
||||||
|
|
||||||
object ProjectStorage {
|
object ProjectStorage {
|
||||||
|
|
||||||
private val data = mutableMapOf<Project, ProjectData>()
|
private val data = WeakHashMap<String, ProjectData>()
|
||||||
|
private val gson = GsonBuilder()
|
||||||
|
.setPrettyPrinting()
|
||||||
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
|
.registerTypeAdapter(ProjectData::class.java, ProjectDataTypeAdapter())
|
||||||
|
.create()
|
||||||
|
|
||||||
fun get(project: Project): ProjectData {
|
fun get(project: Project): ProjectData {
|
||||||
if (!data.containsKey(project)) {
|
val id = project.getId()
|
||||||
data[project] = ProjectData(null, null, null, null, null, null)
|
if (!data.containsKey(id)) {
|
||||||
|
val cacheFile = getCacheFile(project)
|
||||||
|
val projectData = cacheFile.takeIf { it.exists() }?.let { read(it) }
|
||||||
|
if (projectData != null) {
|
||||||
|
data[id] = projectData
|
||||||
|
} else {
|
||||||
|
println("Creating project data store for $id, size: ${data.size}, ${this.hashCode()}")
|
||||||
|
data[id] = ProjectData()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return data[project]!!
|
return data[id]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getCacheFile(project: Project): Path {
|
||||||
|
return project.projectDir.resolve(".gradle/phytotelma/${project.getId()}_data").toPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(project: Project) {
|
||||||
|
write(getCacheFile(project), data[project.getId()]!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun write(path: Path, projectData: ProjectData) {
|
||||||
|
path.writeText(gson.toJson(projectData))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun read(path: Path): ProjectData? {
|
||||||
|
val reader = path.takeIf { it.exists() }?.reader()
|
||||||
|
if (reader != null) {
|
||||||
|
val data = gson.fromJson(reader, ProjectData::class.java)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProjectData(
|
class ProjectData(
|
||||||
|
@ -23,5 +67,59 @@ class ProjectData(
|
||||||
var remappedGameJarPath: Path?,
|
var remappedGameJarPath: Path?,
|
||||||
var mappings: MappingBundle?,
|
var mappings: MappingBundle?,
|
||||||
var mappingsName: String?,
|
var mappingsName: String?,
|
||||||
var targetNamespace: String?
|
var intermediaryNs: String?,
|
||||||
)
|
var targetNamespace: String?,
|
||||||
|
var manifestUrl: String?
|
||||||
|
) {
|
||||||
|
internal constructor() : this(null, null, null, null, null, null, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProjectDataTypeAdapter : TypeAdapter<ProjectData>() {
|
||||||
|
override fun write(out: JsonWriter, value: ProjectData) {
|
||||||
|
out.beginObject()
|
||||||
|
out.serializeNulls = true
|
||||||
|
out.name("local_cache_dir").value(value.localCacheDir?.absolutePathString())
|
||||||
|
out.name("minecraft_version").value(value.minecraftVersion)
|
||||||
|
out.name("remapped_game_jar_path").value(value.remappedGameJarPath?.absolutePathString())
|
||||||
|
out.name("mappings_name").value(value.mappingsName)
|
||||||
|
out.name("target_namespace").value(value.targetNamespace)
|
||||||
|
out.endObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(r: JsonReader): ProjectData {
|
||||||
|
r.beginObject()
|
||||||
|
val data = ProjectData()
|
||||||
|
while (r.peek() != JsonToken.END_OBJECT) {
|
||||||
|
val name = r.nextName()
|
||||||
|
if (r.peek() == JsonToken.STRING) {
|
||||||
|
val value = r.nextString()
|
||||||
|
when (name) {
|
||||||
|
"local_cache_dir" -> {
|
||||||
|
data.localCacheDir = Path.of(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
"minecraft_version" -> {
|
||||||
|
data.minecraftVersion = value
|
||||||
|
}
|
||||||
|
|
||||||
|
"remapped_game_jar_path" -> {
|
||||||
|
data.remappedGameJarPath = Path.of(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
"mappings_name" -> {
|
||||||
|
data.mappingsName = value
|
||||||
|
}
|
||||||
|
|
||||||
|
"target_namespace" -> {
|
||||||
|
data.targetNamespace = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.skipValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.endObject()
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -2,32 +2,33 @@
|
||||||
|
|
||||||
package dev.frogmc.phytotelma
|
package dev.frogmc.phytotelma
|
||||||
|
|
||||||
|
import com.google.common.hash.Hashing
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import dev.frogmc.phytotelma.common.CachingHttpClient
|
import dev.frogmc.phytotelma.common.CachingHttpClient
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.createDirectories
|
import kotlin.io.path.*
|
||||||
import kotlin.io.path.exists
|
|
||||||
import kotlin.io.path.writeBytes
|
|
||||||
import kotlin.io.path.writeText
|
|
||||||
|
|
||||||
object VersionChecker {
|
object VersionChecker {
|
||||||
private var validVersions = mutableListOf<VersionUrl>()
|
private var validVersions = mutableListOf<VersionUrl>()
|
||||||
private var versionData: VersionData? = null
|
private var versionData: VersionData? = null
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
fun downloadClient(project: Project, version: String): Path {
|
fun downloadClient(project: Project, version: String): Path {
|
||||||
|
|
||||||
fetchVersionData(version)
|
fetchVersionData(project, version)
|
||||||
val clientData = fetchClientDownload(version)
|
val clientData = fetchClientDownload(project, version)
|
||||||
// download client data
|
// download client data
|
||||||
val downloadDirectory = PhytotelmaPlugin.globalCacheDir.resolve("net/minecraft/client/$version/")
|
val downloadDirectory = PhytotelmaPlugin.globalCacheDir.resolve("net/minecraft/client/$version/")
|
||||||
val downloadFile = downloadDirectory.resolve("client-$version.jar")
|
val downloadFile = downloadDirectory.resolve("client-$version.jar")
|
||||||
if (!project.gradle.startParameter.isRefreshDependencies && downloadFile.exists()) {
|
if (!project.gradle.startParameter.isRefreshDependencies && downloadFile.exists()) {
|
||||||
println("Client already downloaded! Assuming it's valid. FIXME: Add checksum validation.")
|
if (clientData.sha1 == Hashing.sha1().hashBytes(downloadFile.readBytes()).toString()) {
|
||||||
return downloadFile
|
return downloadFile
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
println("Downloading client...")
|
||||||
downloadDirectory.createDirectories()
|
downloadDirectory.createDirectories()
|
||||||
val raw = rawDownload(clientData.url)
|
val raw = rawDownload(clientData.url)
|
||||||
downloadFile.writeBytes(raw)
|
downloadFile.writeBytes(raw)
|
||||||
|
@ -46,39 +47,39 @@ object VersionChecker {
|
||||||
dir.resolve("client-$version.pom").writeText(content)
|
dir.resolve("client-$version.pom").writeText(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDependencies(version: String, action: (String) -> Unit) {
|
fun getDependencies(project: Project, version: String, action: (String) -> Unit) {
|
||||||
fetchVersionData(version).libraries.map { it.name }.forEach {
|
fetchVersionData(project, version).libraries.map { it.name }.forEach {
|
||||||
action.invoke(it)
|
action.invoke(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateVersion(version: String, ignoreCache: Boolean = false, offlineMode: Boolean): Boolean {
|
fun validateVersion(project: Project, version: String, ignoreCache: Boolean = false, offlineMode: Boolean): Boolean {
|
||||||
if (validVersions.isEmpty() || ignoreCache) getValidVersions(ignoreCache)
|
if (validVersions.isEmpty() || ignoreCache) getValidVersions(project, ignoreCache)
|
||||||
if (!validVersions.any { it.id == version }) {
|
if (!validVersions.any { it.id == version }) {
|
||||||
if (!offlineMode) {
|
if (!offlineMode) {
|
||||||
return validateVersion(version, ignoreCache = true, offlineMode = false)
|
return validateVersion(project, version, ignoreCache = true, offlineMode = false)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getVersionUrl(version: String): String {
|
fun getVersionUrl(project: Project, version: String): String {
|
||||||
if (validVersions.isEmpty()) getValidVersions()
|
if (validVersions.isEmpty()) getValidVersions(project)
|
||||||
return validVersions.first { it.id == version }.url
|
return validVersions.first { it.id == version }.url
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchVersionData(version: String): VersionData {
|
fun fetchVersionData(project: Project, version: String): VersionData {
|
||||||
if (versionData == null || versionData!!.id != version) {
|
if (versionData == null || versionData!!.id != version) {
|
||||||
val url = getVersionUrl(version)
|
val url = getVersionUrl(project, version)
|
||||||
val response = getUrl(url)
|
val response = getUrl(url)
|
||||||
versionData = Gson().fromJson(response, VersionData::class.java)
|
versionData = Gson().fromJson(response, VersionData::class.java)
|
||||||
}
|
}
|
||||||
return versionData!!
|
return versionData!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchClientDownload(version: String): VersionClientData {
|
fun fetchClientDownload(project: Project, version: String): VersionClientData {
|
||||||
return fetchVersionData(version).downloads.client
|
return fetchVersionData(project, version).downloads.client
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rawDownload(url: String): ByteArray {
|
private fun rawDownload(url: String): ByteArray {
|
||||||
|
@ -89,12 +90,12 @@ object VersionChecker {
|
||||||
return CachingHttpClient.getString(URI.create(url), ignoreCache)
|
return CachingHttpClient.getString(URI.create(url), ignoreCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getValidVersions(ignoreCache: Boolean = false) {
|
private fun getValidVersions(project: Project, ignoreCache: Boolean = false) {
|
||||||
if (validVersions.isNotEmpty() && !ignoreCache) return
|
if (validVersions.isNotEmpty() && !ignoreCache) return
|
||||||
// get json from https://piston-meta.mojang.com/mc/game/version_manifest_v2.json
|
// get json from https://piston-meta.mojang.com/mc/game/version_manifest_v2.json
|
||||||
// make http request
|
// make http request
|
||||||
|
|
||||||
val url = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
|
val url = ProjectStorage.get(project).manifestUrl!!
|
||||||
val response = getUrl(url, ignoreCache)
|
val response = getUrl(url, ignoreCache)
|
||||||
// response is in format of {versions: [{id: "1.17.1", type: "release"}, ...]}
|
// response is in format of {versions: [{id: "1.17.1", type: "release"}, ...]}
|
||||||
// parse json
|
// parse json
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.electronwill.nightconfig.core.file.FileNotFoundAction
|
||||||
import com.electronwill.nightconfig.toml.TomlParser
|
import com.electronwill.nightconfig.toml.TomlParser
|
||||||
import com.google.common.hash.Hashing
|
import com.google.common.hash.Hashing
|
||||||
import dev.frogmc.phytotelma.Constants
|
import dev.frogmc.phytotelma.Constants
|
||||||
|
import dev.frogmc.phytotelma.getId
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.artifacts.ProjectDependency
|
import org.gradle.api.artifacts.ProjectDependency
|
||||||
import org.gradle.api.plugins.JavaPlugin
|
import org.gradle.api.plugins.JavaPlugin
|
||||||
|
@ -22,7 +23,7 @@ object AccessWidener {
|
||||||
|
|
||||||
private val PARSER = TomlParser()
|
private val PARSER = TomlParser()
|
||||||
|
|
||||||
private val awHashes = mutableMapOf<Project, String>()
|
private val awHashes = mutableMapOf<String, String>()
|
||||||
private val HEADER = Pattern.compile("accessWidener\\s+v[12]\\s+.*").asMatchPredicate()
|
private val HEADER = Pattern.compile("accessWidener\\s+v[12]\\s+.*").asMatchPredicate()
|
||||||
private val SEPARATOR = "[\\t ]+".toRegex()
|
private val SEPARATOR = "[\\t ]+".toRegex()
|
||||||
|
|
||||||
|
@ -65,14 +66,18 @@ object AccessWidener {
|
||||||
fun needsUpdate(project: Project): Boolean {
|
fun needsUpdate(project: Project): Boolean {
|
||||||
getAWFile(project)?.let {
|
getAWFile(project)?.let {
|
||||||
val hash = Hashing.sha256().hashBytes(it.readBytes()).toString()
|
val hash = Hashing.sha256().hashBytes(it.readBytes()).toString()
|
||||||
if (!awHashes.containsKey(project) || hash != awHashes[project]) {
|
if (!awHashes.containsKey(project.getId()) || hash != awHashes[project.getId()]) {
|
||||||
awHashes[project] = hash
|
awHashes[project.getId()] = hash
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasAW(project: Project): Boolean {
|
||||||
|
return getAWFile(project) != null
|
||||||
|
}
|
||||||
|
|
||||||
fun checkAW(path: Path, gamePath: Path) {
|
fun checkAW(path: Path, gamePath: Path) {
|
||||||
val reader = path.bufferedReader(StandardCharsets.UTF_8)
|
val reader = path.bufferedReader(StandardCharsets.UTF_8)
|
||||||
if (!HEADER.test(reader.readLine() ?: "")) {
|
if (!HEADER.test(reader.readLine() ?: "")) {
|
||||||
|
@ -80,7 +85,7 @@ object AccessWidener {
|
||||||
}
|
}
|
||||||
FileSystems.newFileSystem(gamePath).use { fs ->
|
FileSystems.newFileSystem(gamePath).use { fs ->
|
||||||
reader.lines().toList().forEachIndexed { index, line ->
|
reader.lines().toList().forEachIndexed { index, line ->
|
||||||
val checkString = line.substring(0, line.indexOf("#"))
|
val checkString = if (line.contains("#")) line.substring(0, line.indexOf("#")) else line
|
||||||
|
|
||||||
if (checkString.isNotEmpty()) {
|
if (checkString.isNotEmpty()) {
|
||||||
val parts = checkString.split(SEPARATOR)
|
val parts = checkString.split(SEPARATOR)
|
||||||
|
@ -89,9 +94,10 @@ object AccessWidener {
|
||||||
}
|
}
|
||||||
if (parts[1] == "class") {
|
if (parts[1] == "class") {
|
||||||
val target = parts[2]
|
val target = parts[2]
|
||||||
if (fs.getPath(target).notExists()) {
|
if (fs.getPath("/$target.class").notExists()) {
|
||||||
error("AccessWidener validation failed in line ${index + 2}: $line (Invalid target)")
|
error("AccessWidener validation failed in line ${index + 2}: $line (Invalid target)")
|
||||||
}
|
}
|
||||||
|
return@forEachIndexed
|
||||||
}
|
}
|
||||||
if (parts.size < 5) {
|
if (parts.size < 5) {
|
||||||
error("AccessWidener validation failed in line ${index + 2}: $line (Declaration missing)")
|
error("AccessWidener validation failed in line ${index + 2}: $line (Declaration missing)")
|
||||||
|
@ -101,16 +107,17 @@ object AccessWidener {
|
||||||
val desc = parts[4]
|
val desc = parts[4]
|
||||||
when (parts[1]) {
|
when (parts[1]) {
|
||||||
"method" -> {
|
"method" -> {
|
||||||
val classReader = ClassReader(fs.getPath(owner).readBytes())
|
val classReader = ClassReader(fs.getPath("/$owner.class").readBytes())
|
||||||
val node = ClassNode()
|
val node = ClassNode()
|
||||||
classReader.accept(node, 0)
|
classReader.accept(node, 0)
|
||||||
if (node.methods.none { it.name.equals(name) && it.desc.equals(desc) }) {
|
if (node.methods.none { it.name.equals(name) && it.desc.equals(desc) }) {
|
||||||
error("AccessWidener validation failed in line ${index + 2}: $line (Could not find target method)")
|
val similar = "\nSimilar methods:\n ${node.methods.filter { it.name.equals(name) || it.desc.equals(desc) }.joinToString("\n") { it.name+" ${it.desc}" }}"
|
||||||
|
error("AccessWidener validation failed in line ${index + 2}: $line (Could not find target method)"+ if (node.methods.any { it.name.equals(name) || it.desc.equals(desc) }) similar else "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"field" -> {
|
"field" -> {
|
||||||
val classReader = ClassReader(fs.getPath(owner).readBytes())
|
val classReader = ClassReader(fs.getPath("/$owner.class").readBytes())
|
||||||
val node = ClassNode()
|
val node = ClassNode()
|
||||||
classReader.accept(node, 0)
|
classReader.accept(node, 0)
|
||||||
if (node.fields.none { it.name.equals(name) && it.desc.equals(desc) }) {
|
if (node.fields.none { it.name.equals(name) && it.desc.equals(desc) }) {
|
||||||
|
@ -135,7 +142,8 @@ object AccessWidener {
|
||||||
|
|
||||||
private fun readTransitiveAW(path: Path): List<String> {
|
private fun readTransitiveAW(path: Path): List<String> {
|
||||||
return path.bufferedReader(StandardCharsets.UTF_8).takeIf { HEADER.test(it.readLine() ?: "") }?.lines()
|
return path.bufferedReader(StandardCharsets.UTF_8).takeIf { HEADER.test(it.readLine() ?: "") }?.lines()
|
||||||
?.filter { it.startsWith("transitive-") }?.toList() ?: emptyList()
|
?.filter { it.startsWith("transitive-") }
|
||||||
|
?.map { it.replace("transitive-", "") }?.toList() ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readAllAWs(project: Project): Stream<Entry> {
|
private fun readAllAWs(project: Project): Stream<Entry> {
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
package dev.frogmc.phytotelma.build
|
package dev.frogmc.phytotelma.build
|
||||||
|
|
||||||
import com.electronwill.nightconfig.core.CommentedConfig
|
|
||||||
import com.electronwill.nightconfig.core.file.FileNotFoundAction
|
|
||||||
import com.electronwill.nightconfig.core.io.WritingMode
|
|
||||||
import com.electronwill.nightconfig.toml.TomlParser
|
|
||||||
import com.electronwill.nightconfig.toml.TomlWriter
|
|
||||||
import dev.frogmc.phytotelma.Constants
|
import dev.frogmc.phytotelma.Constants
|
||||||
import dev.frogmc.phytotelma.ProjectStorage
|
import dev.frogmc.phytotelma.ProjectStorage
|
||||||
import dev.frogmc.phytotelma.nest.Nester
|
|
||||||
import org.gradle.api.DefaultTask
|
import org.gradle.api.DefaultTask
|
||||||
import org.gradle.api.tasks.OutputFile
|
import org.gradle.api.tasks.OutputFile
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask
|
import org.gradle.api.tasks.bundling.AbstractArchiveTask
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
|
@ -28,7 +23,6 @@ abstract class PhytotelmaBuildTask : DefaultTask() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
group = Constants.TASK_GROUP
|
group = Constants.TASK_GROUP
|
||||||
val includeConfiguration = project.configurations.findByName(Constants.INCLUDE_CONFIGURATION)
|
|
||||||
|
|
||||||
val jarTask = project.tasks.getByName("jar")
|
val jarTask = project.tasks.getByName("jar")
|
||||||
dependsOn.add(jarTask)
|
dependsOn.add(jarTask)
|
||||||
|
@ -41,39 +35,28 @@ abstract class PhytotelmaBuildTask : DefaultTask() {
|
||||||
outputFile = outFile
|
outputFile = outFile
|
||||||
outputs.upToDateWhen { false }
|
outputs.upToDateWhen { false }
|
||||||
inputs.files(jarTask.outputs)
|
inputs.files(jarTask.outputs)
|
||||||
|
}
|
||||||
|
|
||||||
actions.add { task ->
|
@TaskAction
|
||||||
task.inputs.files.forEach { file ->
|
fun process() {
|
||||||
if (file.name.endsWith(".jar") && !(file.name.contains("-dev.") || file.name.contains("-sources."))) {
|
this.inputs.files.forEach { file ->
|
||||||
Files.copy(file.toPath(), outFile, StandardCopyOption.REPLACE_EXISTING)
|
if (file.name.endsWith(".jar") && !(file.name.contains("-dev.") || file.name.contains("-sources."))) {
|
||||||
FileSystems.newFileSystem(outFile).use { fs ->
|
Files.copy(file.toPath(), outputFile, StandardCopyOption.REPLACE_EXISTING)
|
||||||
if (includeConfiguration != null) {
|
FileSystems.newFileSystem(outputFile).use { fs ->
|
||||||
val jijPath = fs.getPath("META-INF/jars")
|
|
||||||
val files = Nester.run(includeConfiguration, jijPath).map { it.toml() }.toList()
|
val manifest = fs.getPath("META-INF/MANIFEST.MF")
|
||||||
if (files.isNotEmpty()) {
|
val lines = manifest.readLines().filter { it.isNotBlank() }
|
||||||
val manifest = fs.getPath(Constants.MOD_METADATA_FILE)
|
.plus(
|
||||||
val config: CommentedConfig =
|
"""
|
||||||
TomlParser().parse(manifest, FileNotFoundAction.THROW_ERROR)
|
|
||||||
if (!config.add("frog.extensions.included_jars", files)) {
|
|
||||||
println("Failed to add included jars to mod manifest, make sure it doesn't include a key at 'frog.extensions.included_jars'!")
|
|
||||||
}
|
|
||||||
TomlWriter().write(config, manifest, WritingMode.REPLACE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val manifest = fs.getPath("META-INF/MANIFEST.MF")
|
|
||||||
val lines = manifest.readLines().filter { it.isNotBlank() }
|
|
||||||
.plus(
|
|
||||||
"""
|
|
||||||
Built-By: Phytotelma ${this.javaClass.`package`.implementationVersion}
|
Built-By: Phytotelma ${this.javaClass.`package`.implementationVersion}
|
||||||
Target-Namespace: Mojmap
|
Target-Namespace: ${ProjectStorage.get(project).intermediaryNs}
|
||||||
Built-For: Minecraft ${ProjectStorage.get(project).minecraftVersion}
|
Built-For: Minecraft ${ProjectStorage.get(project).minecraftVersion}
|
||||||
Build-Date: ${LocalDateTime.now()}
|
Build-Date: ${LocalDateTime.now()}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
manifest.writeLines(lines, StandardCharsets.UTF_8)
|
manifest.writeLines(lines, StandardCharsets.UTF_8)
|
||||||
}
|
|
||||||
println("Built mod to ${outFile.toUri()}")
|
|
||||||
}
|
}
|
||||||
|
println("Built mod to ${outputFile.toUri()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.frogmc.phytotelma.ext
|
package dev.frogmc.phytotelma.ext
|
||||||
|
|
||||||
|
import dev.frogmc.phytotelma.Constants
|
||||||
import dev.frogmc.phytotelma.PhytotelmaPlugin
|
import dev.frogmc.phytotelma.PhytotelmaPlugin
|
||||||
import dev.frogmc.phytotelma.mappings.filterClasses
|
import dev.frogmc.phytotelma.mappings.filterClasses
|
||||||
import dev.frogmc.phytotelma.mappings.renameDstNamespace
|
import dev.frogmc.phytotelma.mappings.renameDstNamespace
|
||||||
|
@ -27,6 +28,8 @@ abstract class MinecraftConfiguration @Inject constructor(
|
||||||
|
|
||||||
val version: Property<String> = objects.property(String::class.java).unset()
|
val version: Property<String> = objects.property(String::class.java).unset()
|
||||||
var mappings: Provider<MappingBundle> = mojmapParchment()
|
var mappings: Provider<MappingBundle> = mojmapParchment()
|
||||||
|
val intermediaryNamespace: Property<String> = objects.property(String::class.java).convention(Constants.MOJMAP_NAMESPACE)
|
||||||
|
val manifestUrl: Property<String> = objects.property(String::class.java).convention(Constants.MOJANG_MANIFEST_URL)
|
||||||
internal lateinit var mappingsName: String
|
internal lateinit var mappingsName: String
|
||||||
internal lateinit var targetNamespace: String
|
internal lateinit var targetNamespace: String
|
||||||
|
|
||||||
|
@ -43,7 +46,7 @@ abstract class MinecraftConfiguration @Inject constructor(
|
||||||
val cacheDir = PhytotelmaPlugin.globalCacheDir
|
val cacheDir = PhytotelmaPlugin.globalCacheDir
|
||||||
|
|
||||||
mappingsName = "mojmap(${version.get()})+parchment(${conf.gameVersion.get()}, ${conf.version.get()})"
|
mappingsName = "mojmap(${version.get()})+parchment(${conf.gameVersion.get()}, ${conf.version.get()})"
|
||||||
targetNamespace = "mojmap"
|
targetNamespace = Constants.MOJMAP_NAMESPACE
|
||||||
|
|
||||||
return@provider MappingBundle.merge(
|
return@provider MappingBundle.merge(
|
||||||
MojmapProvider.get(
|
MojmapProvider.get(
|
||||||
|
@ -52,7 +55,7 @@ abstract class MinecraftConfiguration @Inject constructor(
|
||||||
).orElseThrow().reverse(),
|
).orElseThrow().reverse(),
|
||||||
ParchmentProvider.getParchment(
|
ParchmentProvider.getParchment(
|
||||||
version.get(),
|
version.get(),
|
||||||
cacheDir.resolve("org/parchmentmc/parchment/${conf.gameVersion}/${conf.version}")
|
cacheDir.resolve("org/parchmentmc/parchment/${conf.gameVersion.get()}/${conf.version.get()}")
|
||||||
)
|
)
|
||||||
).renameDstNamespace(targetNamespace)
|
).renameDstNamespace(targetNamespace)
|
||||||
}
|
}
|
||||||
|
@ -72,7 +75,7 @@ abstract class MinecraftConfiguration @Inject constructor(
|
||||||
return@provider ParchmentProvider.getParchment(
|
return@provider ParchmentProvider.getParchment(
|
||||||
version.get(),
|
version.get(),
|
||||||
cacheDir.resolve("org/parchmentmc/parchment/${conf.gameVersion.get()}/${conf.version.get()}")
|
cacheDir.resolve("org/parchmentmc/parchment/${conf.gameVersion.get()}/${conf.version.get()}")
|
||||||
).renameNamespaces("mojmap", "parchment")
|
).renameNamespaces(Constants.MOJMAP_NAMESPACE, "parchment")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +83,7 @@ abstract class MinecraftConfiguration @Inject constructor(
|
||||||
return project.provider {
|
return project.provider {
|
||||||
val cacheDir = PhytotelmaPlugin.globalCacheDir
|
val cacheDir = PhytotelmaPlugin.globalCacheDir
|
||||||
mappingsName = "mojmap(${version.get()})"
|
mappingsName = "mojmap(${version.get()})"
|
||||||
targetNamespace = "mojmap"
|
targetNamespace = Constants.MOJMAP_NAMESPACE
|
||||||
return@provider MojmapProvider.get(
|
return@provider MojmapProvider.get(
|
||||||
version.get(),
|
version.get(),
|
||||||
cacheDir.resolve("net/minecraft/client/${version.get()}/client-${version.get()}.txt")
|
cacheDir.resolve("net/minecraft/client/${version.get()}/client-${version.get()}.txt")
|
||||||
|
@ -101,8 +104,7 @@ abstract class MinecraftConfiguration @Inject constructor(
|
||||||
return@provider twoStepMappings(
|
return@provider twoStepMappings(
|
||||||
"net.fabricmc:intermediary:${version.get()}:v2",
|
"net.fabricmc:intermediary:${version.get()}:v2",
|
||||||
"org.quiltmc:quilt-mappings:${conf.version.get()}:intermediary-v2"
|
"org.quiltmc:quilt-mappings:${conf.version.get()}:intermediary-v2"
|
||||||
).filterClasses { !it.startsWith("net/minecraft/unmapped") }
|
).flatten().renameDstNamespace(targetNamespace)
|
||||||
.flatten(true).renameDstNamespace(targetNamespace)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,8 +121,24 @@ abstract class MinecraftConfiguration @Inject constructor(
|
||||||
return@provider twoStepMappings(
|
return@provider twoStepMappings(
|
||||||
"net.fabricmc:intermediary:${version.get()}:v2",
|
"net.fabricmc:intermediary:${version.get()}:v2",
|
||||||
"net.fabricmc:yarn:${conf.version.get()}:v2"
|
"net.fabricmc:yarn:${conf.version.get()}:v2"
|
||||||
).filterClasses { !it.startsWith("net/minecraft/class_") }
|
).flatten(true).renameDstNamespace(targetNamespace)
|
||||||
.flatten(true).renameDstNamespace(targetNamespace)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun feather(action: Action<VersionConfiguration>): Provider<MappingBundle> {
|
||||||
|
return project.provider {
|
||||||
|
val conf = objects.newInstance(VersionConfiguration::class.java)
|
||||||
|
action.execute(conf)
|
||||||
|
if (!conf.version.isPresent) {
|
||||||
|
error("No version provided for yarn!")
|
||||||
|
}
|
||||||
|
mappingsName = "yarn(${conf.version.get()})"
|
||||||
|
targetNamespace = "yarn"
|
||||||
|
// Use qm via intermediary because hashed publications are broken
|
||||||
|
return@provider twoStepMappings(
|
||||||
|
"net.ornithemc:calamus-intermediary:${version.get()}:v2",
|
||||||
|
"net.fabricmc:yarn:${conf.version.get()}:v2"
|
||||||
|
).flatten(true).renameDstNamespace(targetNamespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,12 +196,15 @@ abstract class MinecraftConfiguration @Inject constructor(
|
||||||
var name = "layer["
|
var name = "layer["
|
||||||
|
|
||||||
val layers = conf.layers.get().mapIndexed { index, provider ->
|
val layers = conf.layers.get().mapIndexed { index, provider ->
|
||||||
val bundle = (provider.get() as MappingBundle).flatten(true)
|
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
|
val bundle = (provider.get() as MappingBundle).flatten()
|
||||||
name += mappingsName
|
name += mappingsName
|
||||||
back.insert(bundle)
|
back.insert(bundle)
|
||||||
return@mapIndexed bundle
|
return@mapIndexed bundle
|
||||||
}
|
}
|
||||||
|
val bundle = (provider.get() as MappingBundle)
|
||||||
|
.filterClasses { !it.startsWith("net/minecraft/unmapped") && !it.startsWith("net/minecraft/class_") }
|
||||||
|
.flatten(true)
|
||||||
name += ", $mappingsName"
|
name += ", $mappingsName"
|
||||||
|
|
||||||
back = back.flatten(false)
|
back = back.flatten(false)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.frogmc.phytotelma.ext
|
package dev.frogmc.phytotelma.ext
|
||||||
|
|
||||||
|
import dev.frogmc.phytotelma.ext.datagen.DatagenExtension
|
||||||
import org.gradle.api.Action
|
import org.gradle.api.Action
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
|
@ -10,4 +11,6 @@ interface PhytotelmaGradleExtension {
|
||||||
fun loader(action: Action<VersionConfiguration>)
|
fun loader(action: Action<VersionConfiguration>)
|
||||||
|
|
||||||
fun froglib(action: Action<VersionConfiguration>)
|
fun froglib(action: Action<VersionConfiguration>)
|
||||||
|
|
||||||
|
fun datagen(action: Action<DatagenExtension>)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,19 @@ import dev.frogmc.phytotelma.Constants
|
||||||
import dev.frogmc.phytotelma.ProjectStorage
|
import dev.frogmc.phytotelma.ProjectStorage
|
||||||
import dev.frogmc.phytotelma.VersionChecker
|
import dev.frogmc.phytotelma.VersionChecker
|
||||||
import dev.frogmc.phytotelma.accesswidener.AccessWidener
|
import dev.frogmc.phytotelma.accesswidener.AccessWidener
|
||||||
|
import dev.frogmc.phytotelma.ext.datagen.DatagenExtension
|
||||||
import dev.frogmc.phytotelma.run.AssetDownloader
|
import dev.frogmc.phytotelma.run.AssetDownloader
|
||||||
import dev.frogmc.phytotelma.run.RunConfigGenerator
|
import dev.frogmc.phytotelma.run.RunConfigGenerator
|
||||||
|
import dev.frogmc.phytotelma.run.RuntimeAccessFixVisitor
|
||||||
|
import dev.frogmc.phytotelma.run.datagen.DatagenConfigGenerator
|
||||||
|
import dev.frogmc.phytotelma.run.datagen.DatagenTask
|
||||||
|
import dev.frogmc.thyroxine.RemappingStep
|
||||||
import dev.frogmc.thyroxine.Thyroxine
|
import dev.frogmc.thyroxine.Thyroxine
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData
|
||||||
import org.gradle.api.Action
|
import org.gradle.api.Action
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.model.ObjectFactory
|
import org.gradle.api.model.ObjectFactory
|
||||||
|
import java.nio.file.Files
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.io.path.createParentDirectories
|
import kotlin.io.path.createParentDirectories
|
||||||
import kotlin.io.path.notExists
|
import kotlin.io.path.notExists
|
||||||
|
@ -28,7 +35,7 @@ abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
val version = mcConf.version.get()
|
val version = mcConf.version.get()
|
||||||
|
|
||||||
if (VersionChecker.validateVersion(version, offlineMode = project.gradle.startParameter.isOffline)) {
|
if (VersionChecker.validateVersion(project, version, offlineMode = project.gradle.startParameter.isOffline)) {
|
||||||
|
|
||||||
val projectData = ProjectStorage.get(project)
|
val projectData = ProjectStorage.get(project)
|
||||||
projectData.minecraftVersion = version
|
projectData.minecraftVersion = version
|
||||||
|
@ -42,7 +49,8 @@ abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
|
||||||
projectData.localCacheDir!!.resolve("net/minecraft/client/$version/client-$version-remapped.jar")
|
projectData.localCacheDir!!.resolve("net/minecraft/client/$version/client-$version-remapped.jar")
|
||||||
remappedJar.createParentDirectories()
|
remappedJar.createParentDirectories()
|
||||||
projectData.remappedGameJarPath = remappedJar
|
projectData.remappedGameJarPath = remappedJar
|
||||||
val applyAW = AccessWidener.needsUpdate(project)
|
var applyAW = AccessWidener.needsUpdate(project)
|
||||||
|
projectData.intermediaryNs = mcConf.intermediaryNamespace.get()
|
||||||
if (remappedJar.notExists() || applyAW || mcConf.mappingsName != projectData.mappingsName) {
|
if (remappedJar.notExists() || applyAW || mcConf.mappingsName != projectData.mappingsName) {
|
||||||
projectData.mappingsName = mcConf.mappingsName
|
projectData.mappingsName = mcConf.mappingsName
|
||||||
println("Remapping the game...")
|
println("Remapping the game...")
|
||||||
|
@ -51,25 +59,39 @@ abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
|
||||||
mappings.forNamespaces("official", mcConf.targetNamespace),
|
mappings.forNamespaces("official", mcConf.targetNamespace),
|
||||||
clientJar, remappedJar, true, true
|
clientJar, remappedJar, true, true
|
||||||
)
|
)
|
||||||
|
VersionChecker.savePomFile(version, remappedJar.parent)
|
||||||
|
RunConfigGenerator.generate(project)
|
||||||
|
applyAW = AccessWidener.hasAW(project)
|
||||||
}
|
}
|
||||||
|
|
||||||
project.dependencies.add(
|
project.dependencies.add(
|
||||||
Constants.MINECRAFT_CONFIGURATION,
|
Constants.MINECRAFT_CONFIGURATION,
|
||||||
"net.minecrell:terminalconsoleappender:1.2.0"
|
"net.minecrell:terminalconsoleappender:1.2.0"
|
||||||
)
|
)
|
||||||
VersionChecker.getDependencies(version) {
|
VersionChecker.getDependencies(project, version) {
|
||||||
project.dependencies.add(Constants.MINECRAFT_CONFIGURATION, it)
|
project.dependencies.add(Constants.MINECRAFT_CONFIGURATION, it)
|
||||||
}
|
}
|
||||||
VersionChecker.savePomFile(version, remappedJar.parent)
|
|
||||||
project.dependencies.add(Constants.MINECRAFT_CONFIGURATION, "net.minecraft:client:$version:remapped")
|
project.dependencies.add(Constants.MINECRAFT_CONFIGURATION, "net.minecraft:client:$version:remapped")
|
||||||
|
|
||||||
RunConfigGenerator.generate(project)
|
|
||||||
AssetDownloader.download(project)
|
AssetDownloader.download(project)
|
||||||
|
|
||||||
if (applyAW) {
|
if (applyAW) {
|
||||||
project.afterEvaluate {
|
project.afterEvaluate {
|
||||||
println("Applying AccessWideners...")
|
println("Applying AccessWideners...")
|
||||||
AccessWidener.apply(project, remappedJar)
|
AccessWidener.apply(project, remappedJar)
|
||||||
|
if (mcConf.targetNamespace != Constants.MOJMAP_NAMESPACE) {
|
||||||
|
Thyroxine.remap(
|
||||||
|
MappingData("", ""),
|
||||||
|
remappedJar,
|
||||||
|
remappedJar.resolveSibling(Constants.ALTERNATIVE_RUNTIME_JAR_NAME),
|
||||||
|
false,
|
||||||
|
listOf(RemappingStep { classVisitor, _ ->
|
||||||
|
RuntimeAccessFixVisitor(classVisitor)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Files.deleteIfExists(remappedJar.resolveSibling(Constants.ALTERNATIVE_RUNTIME_JAR_NAME))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +103,7 @@ abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
|
||||||
if (!conf.version.isPresent) {
|
if (!conf.version.isPresent) {
|
||||||
error("No loader version provided!")
|
error("No loader version provided!")
|
||||||
}
|
}
|
||||||
project.dependencies.add("implementation", "dev.frogmc:frogloader:${conf.version.get()}")
|
project.dependencies.add(Constants.MINECRAFT_CONFIGURATION, "dev.frogmc:frogloader:${conf.version.get()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun froglib(action: Action<VersionConfiguration>) {
|
override fun froglib(action: Action<VersionConfiguration>) {
|
||||||
|
@ -92,4 +114,11 @@ abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
project.dependencies.add("modImplementation", "dev.frogmc:froglib:${conf.version.get()}")
|
project.dependencies.add("modImplementation", "dev.frogmc:froglib:${conf.version.get()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun datagen(action: Action<DatagenExtension>) {
|
||||||
|
val conf = objects.newInstance(DatagenExtension::class.java)
|
||||||
|
action.execute(conf)
|
||||||
|
DatagenConfigGenerator.generate(project, conf)
|
||||||
|
project.tasks.register("runDatagen", DatagenTask::class.java, conf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package dev.frogmc.phytotelma.ext.datagen
|
||||||
|
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.model.ObjectFactory
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
abstract class DatagenExtension @Inject constructor(project: Project, objects: ObjectFactory) {
|
||||||
|
|
||||||
|
val generatorClass: Property<String> = objects.property(String::class.java).unset()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,270 @@
|
||||||
|
package dev.frogmc.phytotelma.mixin.remapper
|
||||||
|
|
||||||
|
import com.google.common.base.Strings
|
||||||
|
import dev.frogmc.phytotelma.Constants
|
||||||
|
import org.objectweb.asm.*
|
||||||
|
import org.objectweb.asm.commons.AnnotationRemapper
|
||||||
|
import org.objectweb.asm.commons.ClassRemapper
|
||||||
|
import org.objectweb.asm.commons.FieldRemapper
|
||||||
|
import org.objectweb.asm.commons.MethodRemapper
|
||||||
|
import org.objectweb.asm.commons.Remapper
|
||||||
|
import org.objectweb.asm.tree.ClassNode
|
||||||
|
import java.nio.file.FileSystems
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.exists
|
||||||
|
import kotlin.io.path.readBytes
|
||||||
|
|
||||||
|
class MixinAnnotationRemapper(
|
||||||
|
cv: ClassVisitor,
|
||||||
|
private val mapper: Remapper,
|
||||||
|
private val mojmapGameJar: Path
|
||||||
|
) : ClassRemapper(Constants.ASM_VERSION, cv, mapper) {
|
||||||
|
|
||||||
|
private var target: String? = null
|
||||||
|
private var targetNode: ClassNode? = null
|
||||||
|
|
||||||
|
override fun visit(
|
||||||
|
version: Int,
|
||||||
|
access: Int,
|
||||||
|
name: String?,
|
||||||
|
signature: String?,
|
||||||
|
superName: String?,
|
||||||
|
interfaces: Array<out String>?
|
||||||
|
) {
|
||||||
|
super.visit(version, access, name, signature, superName, interfaces)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
|
||||||
|
if (descriptor == null || descriptor != "Lorg/spongepowered/asm/mixin/Mixin;") {
|
||||||
|
return super.visitAnnotation(descriptor, visible)
|
||||||
|
}
|
||||||
|
return object : AnnotationVisitor(Constants.ASM_VERSION, super.visitAnnotation(descriptor, visible)) {
|
||||||
|
override fun visitArray(name: String?): AnnotationVisitor {
|
||||||
|
return object : AnnotationVisitor(Constants.ASM_VERSION, super.visitArray(name)) {
|
||||||
|
override fun visit(none: String?, value: Any?) {
|
||||||
|
var newVal = value
|
||||||
|
if (value is Type) {
|
||||||
|
target = value.internalName
|
||||||
|
} else if (
|
||||||
|
value is String
|
||||||
|
) {
|
||||||
|
if (target == null) {
|
||||||
|
target = value
|
||||||
|
}
|
||||||
|
newVal = mapper.map(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.visit(none, newVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(name: String?, value: Any?) {
|
||||||
|
val newVal = when (value) {
|
||||||
|
is String -> {
|
||||||
|
target = value
|
||||||
|
mapper.map(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Type -> {
|
||||||
|
target = value.internalName
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> value
|
||||||
|
}
|
||||||
|
|
||||||
|
super.visit(name, newVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitEnd() {
|
||||||
|
if (target != null) {
|
||||||
|
targetNode = ClassNode()
|
||||||
|
FileSystems.newFileSystem(mojmapGameJar).use { fs ->
|
||||||
|
fs.getPath("$target.class").takeIf { it.exists() }
|
||||||
|
?.readBytes()?.let(::ClassReader).let { it?.accept(targetNode, 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.visitEnd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitMethod(
|
||||||
|
access: Int,
|
||||||
|
name: String?,
|
||||||
|
descriptor: String?,
|
||||||
|
signature: String?,
|
||||||
|
exceptions: Array<out String>?
|
||||||
|
): MethodVisitor? {
|
||||||
|
val newMethodName = target?.let { mapper.mapMethodName(it, name, descriptor) }?: name
|
||||||
|
return super.visitMethod(access, newMethodName, descriptor, signature, exceptions)?.let {
|
||||||
|
if (targetNode != null) {
|
||||||
|
MixinMethodVisitor(it, mapper, targetNode!!)
|
||||||
|
} else it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitField(
|
||||||
|
access: Int,
|
||||||
|
name: String?,
|
||||||
|
descriptor: String?,
|
||||||
|
signature: String?,
|
||||||
|
value: Any?
|
||||||
|
): FieldVisitor? {
|
||||||
|
val newFieldName = target?.let { mapper.mapFieldName(it, name, descriptor) }?:name
|
||||||
|
return super.visitField(access, newFieldName, descriptor, signature, value)?.let {
|
||||||
|
if (targetNode != null) {
|
||||||
|
MixinFieldVisitor(it, mapper, targetNode!!)
|
||||||
|
} else it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MixinFieldVisitor(fv: FieldVisitor, private val mapper: Remapper, private val targetNode: ClassNode) :
|
||||||
|
FieldRemapper(Constants.ASM_VERSION, fv, mapper) {
|
||||||
|
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
|
||||||
|
return MixinAnnotationVisitor(super.visitAnnotation(descriptor, visible), descriptor!!, mapper, targetNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MixinMethodVisitor(mv: MethodVisitor, private val mapper: Remapper, private val targetNode: ClassNode) :
|
||||||
|
MethodRemapper(Constants.ASM_VERSION, mv, mapper) {
|
||||||
|
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
|
||||||
|
return MixinAnnotationVisitor(super.visitAnnotation(descriptor, visible), descriptor!!, mapper, targetNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MixinAnnotationVisitor(
|
||||||
|
av: AnnotationVisitor,
|
||||||
|
private val desc: String,
|
||||||
|
private val mapper: Remapper,
|
||||||
|
private val owner: ClassNode,
|
||||||
|
private var annotationName: String = ""
|
||||||
|
) :
|
||||||
|
AnnotationRemapper(Constants.ASM_VERSION, desc, av, mapper) {
|
||||||
|
|
||||||
|
override fun visitAnnotation(name: String?, descriptor: String?): AnnotationVisitor {
|
||||||
|
return MixinAnnotationVisitor(super.visitAnnotation(name, descriptor), descriptor!!, mapper, owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitArray(name: String?): AnnotationVisitor {
|
||||||
|
return MixinAnnotationVisitor(super.visitArray(name), desc, mapper, owner, name!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(n: String?, value: Any?) {
|
||||||
|
val name = n ?: annotationName
|
||||||
|
var newVal = value
|
||||||
|
if (value is String) {
|
||||||
|
if (desc == "Lorg/spongepowered/asm/mixin/gen/Invoker;") {
|
||||||
|
val methodName = value.substringBefore("(")
|
||||||
|
val desc = value.indexOf("(").takeIf { it > -1 }?.let(value::substring)
|
||||||
|
if (desc == null) {
|
||||||
|
val method = owner.methods.find { it.name == methodName }
|
||||||
|
if (method != null) {
|
||||||
|
newVal = mapper.mapMethodName(owner.name, methodName, method.desc)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newVal = mapper.mapMethodName(owner.name, methodName, desc)
|
||||||
|
}
|
||||||
|
} else if (desc == "Lorg/spongepowered/asm/mixin/gen/Accessor;") {
|
||||||
|
val field = owner.fields.find { it.name == value }
|
||||||
|
if (field != null) {
|
||||||
|
newVal = mapper.mapFieldName(owner.name, value, field.desc)
|
||||||
|
}
|
||||||
|
} else if (name == "aliases") {
|
||||||
|
val field = owner.fields.find { it.name == value }
|
||||||
|
if (field != null) {
|
||||||
|
newVal = mapper.mapFieldName(owner.name, value, field.desc)
|
||||||
|
}
|
||||||
|
} else if (name == "method") {
|
||||||
|
val methodName = value.substringBefore("(")
|
||||||
|
val desc = value.indexOf("(").takeIf { it > -1 }?.let(value::substring)
|
||||||
|
if (desc == null) {
|
||||||
|
val method = owner.methods.find { it.name == methodName }
|
||||||
|
if (method != null) {
|
||||||
|
val newName = mapper.mapMethodName(
|
||||||
|
owner.name,
|
||||||
|
methodName,
|
||||||
|
method.desc
|
||||||
|
)
|
||||||
|
newVal = newName + mapper.mapMethodDesc(method.desc)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val newName = mapper.mapMethodName(
|
||||||
|
owner.name,
|
||||||
|
methodName,
|
||||||
|
desc
|
||||||
|
)
|
||||||
|
newVal = newName + mapper.mapMethodDesc(desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (name == "target") {
|
||||||
|
newVal = remapMemberInfo(value, mapper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.visit(name, newVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun remapMemberInfo(input: String, mapper: Remapper): String {
|
||||||
|
var desc: String? = null
|
||||||
|
var owner: String? = null
|
||||||
|
var name = Strings.nullToEmpty(input).replace("\\s".toRegex(), "")
|
||||||
|
var tail = ""
|
||||||
|
|
||||||
|
val arrowPos: Int = name.indexOf("->")
|
||||||
|
if (arrowPos > -1) {
|
||||||
|
tail = name.substring(arrowPos + 2)
|
||||||
|
name = name.substring(0, arrowPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
val lastDotPos = name.lastIndexOf('.')
|
||||||
|
val semiColonPos = name.indexOf(';')
|
||||||
|
if (lastDotPos > -1) {
|
||||||
|
owner = name.substring(0, lastDotPos).replace('.', '/')
|
||||||
|
name = name.substring(lastDotPos + 1)
|
||||||
|
} else if (semiColonPos > -1 && name.startsWith("L")) {
|
||||||
|
owner = name.substring(1, semiColonPos).replace('.', '/')
|
||||||
|
name = name.substring(semiColonPos + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val parenPos = name.indexOf('(')
|
||||||
|
val colonPos = name.indexOf(':')
|
||||||
|
if (parenPos > -1) {
|
||||||
|
desc = name.substring(parenPos)
|
||||||
|
name = name.substring(0, parenPos)
|
||||||
|
} else if (colonPos > -1) {
|
||||||
|
desc = name.substring(colonPos + 1)
|
||||||
|
name = name.substring(0, colonPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((name.indexOf('/') > -1 || name.indexOf('.') > -1) && owner == null) {
|
||||||
|
owner = name
|
||||||
|
name = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var quantifier = ""
|
||||||
|
if (name.endsWith("*")) {
|
||||||
|
quantifier = "*"
|
||||||
|
name = name.substring(0, name.length - 1)
|
||||||
|
} else if (name.endsWith("+")) {
|
||||||
|
quantifier = "+"
|
||||||
|
name = name.substring(0, name.length - 1)
|
||||||
|
} else if (name.endsWith("}")) {
|
||||||
|
quantifier = ""
|
||||||
|
val bracePos = name.indexOf("{")
|
||||||
|
if (bracePos >= 0) {
|
||||||
|
quantifier = name.substring(bracePos)
|
||||||
|
name = name.substring(0, bracePos)
|
||||||
|
}
|
||||||
|
} else if (name.indexOf("{") >= 0) {
|
||||||
|
quantifier = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val mappedOwner = mapper.map(owner!!)
|
||||||
|
val mappedDesc = mapper.mapMethodDesc(desc!!)
|
||||||
|
val mappedName = mapper.mapMethodName(owner, name, desc)
|
||||||
|
return "L$mappedOwner;$mappedName$mappedDesc$quantifier$tail"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/main/kotlin/dev/frogmc/phytotelma/nest/NestStripper.kt
Normal file
25
src/main/kotlin/dev/frogmc/phytotelma/nest/NestStripper.kt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package dev.frogmc.phytotelma.nest
|
||||||
|
|
||||||
|
import dev.frogmc.phytotelma.Constants
|
||||||
|
import dev.frogmc.phytotelma.PhytotelmaPlugin
|
||||||
|
import java.nio.file.FileSystems
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.exists
|
||||||
|
import kotlin.io.path.inputStream
|
||||||
|
import kotlin.io.path.outputStream
|
||||||
|
|
||||||
|
object NestStripper {
|
||||||
|
|
||||||
|
fun stripJij(modJar: Path) {
|
||||||
|
FileSystems.newFileSystem(modJar).use { fs ->
|
||||||
|
val path = fs.getPath(Constants.MOD_METADATA_FILE)
|
||||||
|
path.takeIf { it.exists() }?.apply {
|
||||||
|
val config = PhytotelmaPlugin.tomlParser.parse(this.inputStream())
|
||||||
|
config.remove<Any>("frog.extensions.included_jars")
|
||||||
|
PhytotelmaPlugin.tomlWriter.write(config, this.outputStream())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,9 @@ package dev.frogmc.phytotelma.nest
|
||||||
|
|
||||||
import com.electronwill.nightconfig.core.Config
|
import com.electronwill.nightconfig.core.Config
|
||||||
import com.electronwill.nightconfig.core.UnmodifiableConfig
|
import com.electronwill.nightconfig.core.UnmodifiableConfig
|
||||||
import com.electronwill.nightconfig.toml.TomlParser
|
|
||||||
import com.google.common.jimfs.Jimfs
|
import com.google.common.jimfs.Jimfs
|
||||||
import dev.frogmc.phytotelma.Constants
|
import dev.frogmc.phytotelma.Constants
|
||||||
|
import dev.frogmc.phytotelma.PhytotelmaPlugin
|
||||||
import org.gradle.api.artifacts.Configuration
|
import org.gradle.api.artifacts.Configuration
|
||||||
import org.gradle.api.artifacts.ProjectDependency
|
import org.gradle.api.artifacts.ProjectDependency
|
||||||
import org.gradle.api.plugins.JavaPlugin
|
import org.gradle.api.plugins.JavaPlugin
|
||||||
|
@ -135,7 +135,7 @@ object Nester {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readModId(toml: Path): String {
|
private fun readModId(toml: Path): String {
|
||||||
return TomlParser().parse(toml.inputStream()).get("frog.mod.id")
|
return PhytotelmaPlugin.tomlParser.parse(toml.inputStream()).get("frog.mod.id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ object AssetDownloader {
|
||||||
fun download(project: Project, manualInvocation: Boolean = false) {
|
fun download(project: Project, manualInvocation: Boolean = false) {
|
||||||
val version = ProjectStorage.get(project).minecraftVersion!!
|
val version = ProjectStorage.get(project).minecraftVersion!!
|
||||||
val path = PhytotelmaPlugin.globalCacheDir.resolve("assets")
|
val path = PhytotelmaPlugin.globalCacheDir.resolve("assets")
|
||||||
val assetIndex = VersionChecker.fetchVersionData(version).assetIndex
|
val assetIndex = VersionChecker.fetchVersionData(project, version).assetIndex
|
||||||
val dest = path.resolve("indexes").resolve(assetIndex.id + ".json")
|
val dest = path.resolve("indexes").resolve(assetIndex.id + ".json")
|
||||||
var overwrite = manualInvocation
|
var overwrite = manualInvocation
|
||||||
if (dest.notExists()) {
|
if (dest.notExists()) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.frogmc.phytotelma.run
|
package dev.frogmc.phytotelma.run
|
||||||
|
|
||||||
|
import dev.frogmc.phytotelma.Constants
|
||||||
import dev.frogmc.phytotelma.PhytotelmaPlugin
|
import dev.frogmc.phytotelma.PhytotelmaPlugin
|
||||||
import dev.frogmc.phytotelma.ProjectStorage
|
import dev.frogmc.phytotelma.ProjectStorage
|
||||||
import dev.frogmc.phytotelma.VersionChecker
|
import dev.frogmc.phytotelma.VersionChecker
|
||||||
|
@ -15,26 +16,29 @@ import kotlin.io.path.createParentDirectories
|
||||||
import kotlin.io.path.notExists
|
import kotlin.io.path.notExists
|
||||||
|
|
||||||
object RunConfigGenerator {
|
object RunConfigGenerator {
|
||||||
private const val LOG4J_CONFIG_PATH = ".gradle/phytotelma/log4j.xml"
|
val ADAPTERS = arrayOf(EclipseAdapter(), IdeaAdapter())
|
||||||
private const val ASSET_DIR = "assets"
|
|
||||||
private val ADAPTERS = arrayOf(EclipseAdapter(), IdeaAdapter())
|
|
||||||
|
|
||||||
fun generate(project: Project) {
|
fun generate(project: Project) {
|
||||||
val log4jPath = project.rootDir.resolve(LOG4J_CONFIG_PATH).toPath().absolute()
|
|
||||||
if (log4jPath.notExists()) {
|
|
||||||
log4jPath.createParentDirectories()
|
|
||||||
RunConfigGenerator::class.java.getResourceAsStream("/log4j.xml").use { input ->
|
|
||||||
Files.copy(input!!, log4jPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val assetPath = PhytotelmaPlugin.globalCacheDir.resolve(ASSET_DIR).absolute()
|
val assetPath = PhytotelmaPlugin.globalCacheDir.resolve("assets").absolute()
|
||||||
val assetIndexPath = assetPath.resolve("indexes")
|
val assetIndexPath = assetPath.resolve("indexes")
|
||||||
if (assetIndexPath.notExists()) {
|
if (assetIndexPath.notExists()) {
|
||||||
assetIndexPath.createDirectories()
|
assetIndexPath.createDirectories()
|
||||||
}
|
}
|
||||||
|
val indexId = VersionChecker.fetchVersionData(project, ProjectStorage.get(project).minecraftVersion!!).assetIndex.id
|
||||||
|
val log4jPath = project.rootDir.resolve(".gradle/phytotelma/log4j.xml").toPath().absolute()
|
||||||
|
if (log4jPath.notExists()) {
|
||||||
|
log4jPath.createParentDirectories()
|
||||||
|
this::class.java.getResourceAsStream("/log4j.xml").use { input ->
|
||||||
|
Files.copy(input!!, log4jPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val projectData = ProjectStorage.get(project)
|
val projectData = ProjectStorage.get(project)
|
||||||
val indexId = VersionChecker.fetchVersionData(projectData.minecraftVersion!!).assetIndex.id
|
var runtimeGameJar = projectData.remappedGameJarPath!!
|
||||||
|
if (projectData.targetNamespace != Constants.MOJMAP_NAMESPACE) {
|
||||||
|
runtimeGameJar = runtimeGameJar.resolveSibling(Constants.ALTERNATIVE_RUNTIME_JAR_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
val projectName = if (project.rootDir == project.projectDir) "" else " (${project.name})"
|
val projectName = if (project.rootDir == project.projectDir) "" else " (${project.name})"
|
||||||
|
|
||||||
|
@ -50,14 +54,15 @@ object RunConfigGenerator {
|
||||||
mutableListOf(
|
mutableListOf(
|
||||||
"-Xmx${project.properties.getOrDefault("frogmc.gameHeap", "2048M")}",
|
"-Xmx${project.properties.getOrDefault("frogmc.gameHeap", "2048M")}",
|
||||||
"-Dfrogmc.development=true",
|
"-Dfrogmc.development=true",
|
||||||
"-Dfrogmc.plugin.minecraft.gameJar=${projectData.remappedGameJarPath}",
|
"-Dfrogmc.plugin.minecraft.gameJar=${runtimeGameJar}",
|
||||||
"-Dlog4j2.configurationFile=$log4jPath",
|
"-Dlog4j2.configurationFile=$log4jPath",
|
||||||
"-Dlog4j2.formatMsgLookups=true"
|
"-Dlog4j2.formatMsgNoLookups=true"
|
||||||
).apply {
|
).also {
|
||||||
if (project.gradle.startParameter.consoleOutput != ConsoleOutput.Plain) {
|
if (project.gradle.startParameter.consoleOutput != ConsoleOutput.Plain) {
|
||||||
add("-Dfrogmc.log.disableAnsi=false")
|
it.add("-Dfrogmc.log.disableAnsi=false")
|
||||||
}
|
}
|
||||||
}.toTypedArray(), if (env == Env.CLIENT) {
|
}.toTypedArray(),
|
||||||
|
if (env == Env.CLIENT) {
|
||||||
arrayOf(
|
arrayOf(
|
||||||
"--assetsDir", assetPath.toString(),
|
"--assetsDir", assetPath.toString(),
|
||||||
"--version", "FrogMC",
|
"--version", "FrogMC",
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package dev.frogmc.phytotelma.run
|
||||||
|
|
||||||
|
import dev.frogmc.phytotelma.Constants
|
||||||
|
import org.objectweb.asm.ClassVisitor
|
||||||
|
import org.objectweb.asm.FieldVisitor
|
||||||
|
import org.objectweb.asm.MethodVisitor
|
||||||
|
import org.objectweb.asm.Opcodes
|
||||||
|
|
||||||
|
class RuntimeAccessFixVisitor(cv: ClassVisitor): ClassVisitor(Constants.ASM_VERSION, cv) {
|
||||||
|
|
||||||
|
private fun fixAccess(access: Int): Int {
|
||||||
|
return if ((access and 0x7) != Opcodes.ACC_PRIVATE) (access and 0x7.inv()) or Opcodes.ACC_PUBLIC else access
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(
|
||||||
|
version: Int,
|
||||||
|
access: Int,
|
||||||
|
name: String?,
|
||||||
|
signature: String?,
|
||||||
|
superName: String?,
|
||||||
|
interfaces: Array<out String>?
|
||||||
|
) {
|
||||||
|
super.visit(version, fixAccess(access), name, signature, superName, interfaces)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitField(
|
||||||
|
access: Int,
|
||||||
|
name: String?,
|
||||||
|
descriptor: String?,
|
||||||
|
signature: String?,
|
||||||
|
value: Any?
|
||||||
|
): FieldVisitor {
|
||||||
|
return super.visitField(fixAccess(access), name, descriptor, signature, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitInnerClass(name: String?, outerName: String?, innerName: String?, access: Int) {
|
||||||
|
super.visitInnerClass(name, outerName, innerName, fixAccess(access))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitMethod(
|
||||||
|
access: Int,
|
||||||
|
name: String?,
|
||||||
|
descriptor: String?,
|
||||||
|
signature: String?,
|
||||||
|
exceptions: Array<out String>?
|
||||||
|
): MethodVisitor {
|
||||||
|
return super.visitMethod(fixAccess(access), name, descriptor, signature, exceptions)
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ class IdeaAdapter : RunConfigAdapter {
|
||||||
val runDir = project.projectDir.resolve("run")
|
val runDir = project.projectDir.resolve("run")
|
||||||
runDir.mkdirs()
|
runDir.mkdirs()
|
||||||
|
|
||||||
|
|
||||||
file.writer(StandardCharsets.UTF_8).use {
|
file.writer(StandardCharsets.UTF_8).use {
|
||||||
it.write(
|
it.write(
|
||||||
"""
|
"""
|
||||||
|
@ -57,6 +58,7 @@ class IdeaAdapter : RunConfigAdapter {
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldGenerate(): Boolean {
|
override fun shouldGenerate(): Boolean {
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
package dev.frogmc.phytotelma.run.datagen
|
||||||
|
|
||||||
|
import dev.frogmc.phytotelma.Constants
|
||||||
|
import dev.frogmc.phytotelma.ProjectStorage
|
||||||
|
import dev.frogmc.phytotelma.ext.datagen.DatagenExtension
|
||||||
|
import dev.frogmc.phytotelma.run.RunConfigGenerator
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.logging.configuration.ConsoleOutput
|
||||||
|
import org.gradle.api.plugins.JavaPluginExtension
|
||||||
|
import java.nio.file.Files
|
||||||
|
import kotlin.io.path.absolute
|
||||||
|
import kotlin.io.path.createParentDirectories
|
||||||
|
import kotlin.io.path.notExists
|
||||||
|
|
||||||
|
object DatagenConfigGenerator {
|
||||||
|
|
||||||
|
fun generate(project: Project, conf: DatagenExtension) {
|
||||||
|
|
||||||
|
val projectName = if (project.rootDir == project.projectDir) "" else " (${project.name})"
|
||||||
|
|
||||||
|
val log4jPath = project.rootDir.resolve(".gradle/phytotelma/log4j.xml").toPath().absolute()
|
||||||
|
if (log4jPath.notExists()) {
|
||||||
|
log4jPath.createParentDirectories()
|
||||||
|
this::class.java.getResourceAsStream("/log4j.xml").use { input ->
|
||||||
|
Files.copy(input!!, log4jPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val projectData = ProjectStorage.get(project)
|
||||||
|
var runtimeGameJar = projectData.remappedGameJarPath!!
|
||||||
|
if (projectData.targetNamespace != Constants.MOJMAP_NAMESPACE) {
|
||||||
|
runtimeGameJar = runtimeGameJar.resolveSibling(Constants.ALTERNATIVE_RUNTIME_JAR_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (adapter in RunConfigGenerator.ADAPTERS) {
|
||||||
|
if (!adapter.shouldGenerate())
|
||||||
|
continue
|
||||||
|
|
||||||
|
adapter.generate(
|
||||||
|
project,
|
||||||
|
"Minecraft (Data Generation)$projectName",
|
||||||
|
"dev.frogmc.frogloader.impl.launch.server.FrogServer",
|
||||||
|
mutableListOf(
|
||||||
|
"-Xmx${project.properties.getOrDefault("frogmc.gameHeap", "2048M")}",
|
||||||
|
"-Dfrogmc.development=true",
|
||||||
|
"-Dfrogmc.plugin.minecraft.gameJar=${runtimeGameJar}",
|
||||||
|
"-Dlog4j2.configurationFile=$log4jPath",
|
||||||
|
"-Dlog4j2.formatMsgNoLookups=true",
|
||||||
|
"-Dfrog.datagen.enabled=true",
|
||||||
|
"-Dfrog.datagen.modid=${Datagen.readModId(project)}",
|
||||||
|
"-Dfrog.datagen.generator=${conf.generatorClass.get()}",
|
||||||
|
"-Dfrog.datagen.output=${
|
||||||
|
project.extensions.findByType(JavaPluginExtension::class.java)?.sourceSets?.maybeCreate(
|
||||||
|
"generated"
|
||||||
|
)?.resources?.srcDirs?.first()?.toString()
|
||||||
|
}"
|
||||||
|
).also {
|
||||||
|
if (project.gradle.startParameter.consoleOutput != ConsoleOutput.Plain) {
|
||||||
|
it.add("-Dfrogmc.log.disableAnsi=false")
|
||||||
|
}
|
||||||
|
}.toTypedArray(),
|
||||||
|
arrayOf("--nogui")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package dev.frogmc.phytotelma.run.datagen
|
||||||
|
|
||||||
|
import com.electronwill.nightconfig.toml.TomlParser
|
||||||
|
import dev.frogmc.phytotelma.Constants
|
||||||
|
import dev.frogmc.phytotelma.common.Env
|
||||||
|
import dev.frogmc.phytotelma.ext.datagen.DatagenExtension
|
||||||
|
import dev.frogmc.phytotelma.run.task.RunGameTask
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.plugins.JavaPluginExtension
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
object Datagen {
|
||||||
|
fun readModId(project: Project): String {
|
||||||
|
project.extensions.findByType(JavaPluginExtension::class.java)?.sourceSets?.forEach { set ->
|
||||||
|
set.resources.filter { it.name == Constants.MOD_METADATA_FILE }.firstOrNull {
|
||||||
|
return TomlParser().parse(it.readText()).get("frog.mod.id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error("Mod ID could not be read!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DatagenTask @Inject constructor(conf: DatagenExtension) : RunGameTask(Env.SERVER) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
val output = project.extensions.findByType(JavaPluginExtension::class.java)?.sourceSets?.maybeCreate(
|
||||||
|
"generated"
|
||||||
|
)?.resources?.srcDirs?.first()?.toString()
|
||||||
|
jvmArguments.addAll(
|
||||||
|
"-Dfrog.datagen.enabled=true",
|
||||||
|
"-Dfrog.datagen.modid=${Datagen.readModId(project)}",
|
||||||
|
"-Dfrog.datagen.generator=${conf.generatorClass.get()}",
|
||||||
|
"-Dfrog.datagen.output=${output}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ abstract class RunGameTask @Inject constructor(env: Env) : JavaExec() {
|
||||||
if (assetIndexPath.notExists()) {
|
if (assetIndexPath.notExists()) {
|
||||||
assetIndexPath.createDirectories()
|
assetIndexPath.createDirectories()
|
||||||
}
|
}
|
||||||
val indexId = VersionChecker.fetchVersionData(ProjectStorage.get(project).minecraftVersion!!).assetIndex.id
|
val indexId = VersionChecker.fetchVersionData(project, ProjectStorage.get(project).minecraftVersion!!).assetIndex.id
|
||||||
val log4jPath = project.rootDir.resolve(".gradle/phytotelma/log4j.xml").toPath().absolute()
|
val log4jPath = project.rootDir.resolve(".gradle/phytotelma/log4j.xml").toPath().absolute()
|
||||||
if (log4jPath.notExists()) {
|
if (log4jPath.notExists()) {
|
||||||
log4jPath.createParentDirectories()
|
log4jPath.createParentDirectories()
|
||||||
|
@ -54,10 +54,16 @@ abstract class RunGameTask @Inject constructor(env: Env) : JavaExec() {
|
||||||
SourceSet.MAIN_SOURCE_SET_NAME
|
SourceSet.MAIN_SOURCE_SET_NAME
|
||||||
).runtimeClasspath.filter { it.exists() })
|
).runtimeClasspath.filter { it.exists() })
|
||||||
|
|
||||||
|
val projectData = ProjectStorage.get(project)
|
||||||
|
var runtimeGameJar = projectData.remappedGameJarPath!!
|
||||||
|
if (projectData.targetNamespace != Constants.MOJMAP_NAMESPACE) {
|
||||||
|
runtimeGameJar = runtimeGameJar.resolveSibling(Constants.ALTERNATIVE_RUNTIME_JAR_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
jvmArguments.addAll(
|
jvmArguments.addAll(
|
||||||
"-Xmx${project.properties.getOrDefault("frogmc.gameHeap", "2048M")}",
|
"-Xmx${project.properties.getOrDefault("frogmc.gameHeap", "2048M")}",
|
||||||
"-Dfrogmc.development=true",
|
"-Dfrogmc.development=true",
|
||||||
"-Dfrogmc.plugin.minecraft.gameJar=${ProjectStorage.get(project).remappedGameJarPath}",
|
"-Dfrogmc.plugin.minecraft.gameJar=${runtimeGameJar}",
|
||||||
"-Dlog4j2.configurationFile=$log4jPath",
|
"-Dlog4j2.configurationFile=$log4jPath",
|
||||||
"-Dlog4j2.formatMsgNoLookups=true",
|
"-Dlog4j2.formatMsgNoLookups=true",
|
||||||
writeArgFile()
|
writeArgFile()
|
||||||
|
@ -68,6 +74,7 @@ abstract class RunGameTask @Inject constructor(env: Env) : JavaExec() {
|
||||||
}
|
}
|
||||||
|
|
||||||
mainClass.set("dev.frogmc.frogloader.impl.launch.${env.id}.Frog${env.pascalName}")
|
mainClass.set("dev.frogmc.frogloader.impl.launch.${env.id}.Frog${env.pascalName}")
|
||||||
|
setStandardInput(System.`in`)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeArgFile(): String {
|
private fun writeArgFile(): String {
|
||||||
|
|
Loading…
Reference in a new issue