Compare commits

...

7 commits

Author SHA1 Message Date
owlsys 7add673c73 Merge pull request 'Datagen insanity, dependency, mixin & AW remapping' (#4) from owlsys/insanity into mistress
All checks were successful
Publish to snapshot maven / build (push) Successful in 1m11s
Reviewed-on: #4
2024-08-27 07:01:04 -04:00
moehreag 170bbe7d0f allow specification of a different intermediary namespace,
allow alternative version manifests
2024-08-20 11:06:50 +02:00
moehreag 755ef292ab fix a bug 2024-08-05 15:59:06 +02:00
moehreag 64a589abbc I believe a few things are broken. 2024-08-04 17:39:24 +02:00
moehreag 9cccca3d8d add project configuration caching (I'm serious) 2024-08-04 00:11:41 +02:00
moehreag e54543b2b7 remap transitive AWs if necessary and AW remapping/validation 2024-08-03 18:45:51 +02:00
moehreag 7a2f8aefb0 various fixes, datagen task/run config, mapping insanity 2024-07-08 20:46:43 +02:00
22 changed files with 899 additions and 168 deletions

View file

@ -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")

View file

@ -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

View file

@ -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"
} }

View file

@ -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")) Thyroxine.remap(
return@forEachLine mappings,
} temp,
} file.toPath(),
appendLine(it) false,
} defaultRemappingSteps(ProjectStorage.get(project).remappedGameJarPath!!)
} )
aw.writeText(buffer)
}
}
Thyroxine.remap(mappings, temp, file.toPath(), false, false)
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!!,
project.projectDir.resolve("storageMappings.tiny").writer()
)
val officialStore = storage.mappings!!.forNamespaces(storage.targetNamespace!!, "official").reverse()
TinyV2Writer.write(
MappingBundle(officialStore),
project.projectDir.resolve("officialStore.tiny").toPath().writer()
)
val mojOfficial = 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).data[0].reverse()
).forNamespaces("mojmap", storage.targetNamespace)
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
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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> {

View file

@ -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() {
this.inputs.files.forEach { file ->
if (file.name.endsWith(".jar") && !(file.name.contains("-dev.") || file.name.contains("-sources."))) { if (file.name.endsWith(".jar") && !(file.name.contains("-dev.") || file.name.contains("-sources."))) {
Files.copy(file.toPath(), outFile, StandardCopyOption.REPLACE_EXISTING) Files.copy(file.toPath(), outputFile, StandardCopyOption.REPLACE_EXISTING)
FileSystems.newFileSystem(outFile).use { fs -> FileSystems.newFileSystem(outputFile).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 manifest = fs.getPath("META-INF/MANIFEST.MF") val manifest = fs.getPath("META-INF/MANIFEST.MF")
val lines = manifest.readLines().filter { it.isNotBlank() } val lines = manifest.readLines().filter { it.isNotBlank() }
.plus( .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()}")
}
} }
} }
} }

View file

@ -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)

View file

@ -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>)
} }

View file

@ -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)
}
} }

View file

@ -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()
}

View file

@ -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"
}
}
}

View 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())
}
}
}
}

View file

@ -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")
} }
} }

View file

@ -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()) {

View file

@ -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",

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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")
)
}
}
}

View file

@ -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}"
)
}
}

View file

@ -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 {