Merge pull request 'rework dsl, add mod configurations (remapped), add support for other mappings' (#3) from owlsys/other-mappings into mistress
All checks were successful
Publish to snapshot maven / build (push) Successful in 40s

Reviewed-on: #3
Reviewed-by: Ecorous <ecorous@outlook.com>
This commit is contained in:
Ecorous 2024-07-04 14:39:40 -04:00
commit 97048555c6
17 changed files with 845 additions and 251 deletions

View file

@ -7,7 +7,7 @@ plugins {
} }
group = "dev.frogmc" group = "dev.frogmc"
version = "0.0.1-alpha.12" version = "0.0.1-alpha.13"
repositories { repositories {
maven { maven {
@ -18,17 +18,20 @@ repositories {
name = "FrogMC Maven Snapshots" name = "FrogMC Maven Snapshots"
url = uri("https://maven.frogmc.dev/snapshots") url = uri("https://maven.frogmc.dev/snapshots")
} }
mavenLocal()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
implementation("dev.frogmc:thyroxine:0.0.1-alpha.5") implementation("dev.frogmc:thyroxine:0.0.1-alpha.6")
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-tree:9.7")
implementation("com.google.code.gson:gson:2.10.1") implementation("com.google.code.gson:gson:2.10.1")
implementation("org.vineflower:vineflower:1.10.1") implementation("org.vineflower:vineflower:1.10.1")
testImplementation(kotlin("test")) testImplementation(kotlin("test"))
implementation("com.electronwill.night-config:toml:3.7.3") implementation("com.electronwill.night-config:toml:3.8.0")
implementation("com.google.jimfs:jimfs:1.3.0") implementation("com.google.jimfs:jimfs:1.3.0")
} }

View file

@ -12,4 +12,6 @@ object Constants {
const val GEN_RUN_CONFIGS_TASK = "genRunConfigs" const val GEN_RUN_CONFIGS_TASK = "genRunConfigs"
const val RUN_CLIENT_TASK = "runClient" const val RUN_CLIENT_TASK = "runClient"
const val RUNT_SERVER_TASK = "runServer" const val RUNT_SERVER_TASK = "runServer"
const val CLEAR_LOCAL_CACHE_TASK = "clearLocalCache"
const val CLEAR_GLOBAL_CACHE_TASK = "clearGlobalCache"
} }

View file

@ -0,0 +1,70 @@
package dev.frogmc.phytotelma
import org.gradle.api.plugins.JavaPlugin
object ModConfigurations {
val configurations = listOf(
ModConfiguration(
JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME, listOf(
JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME
), DependencyType.RUNTIME
),
ModConfiguration(
JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME, listOf(
JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME
), DependencyType.RUNTIME
),
ModConfiguration(
JavaPlugin.API_CONFIGURATION_NAME, listOf(
JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME
), DependencyType.COMPILE
),
ModConfiguration(
JavaPlugin.COMPILE_ONLY_API_CONFIGURATION_NAME, listOf(
JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME
), DependencyType.COMPILE
),
ModConfiguration(
JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, listOf(
JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME
)
),
ModConfiguration(
JavaPlugin.TEST_COMPILE_ONLY_CONFIGURATION_NAME, listOf(
JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME
)
),
ModConfiguration(
JavaPlugin.RUNTIME_ONLY_CONFIGURATION_NAME, listOf(
JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME,
)
),
ModConfiguration(
JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME, listOf(
JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME,
)
)
)
}
data class ModConfiguration(
val name: String,
val classpathNames: List<String>,
val dependencyType: DependencyType = DependencyType.NONE,
)
enum class DependencyType {
NONE,
RUNTIME,
COMPILE
}

View file

@ -1,29 +1,14 @@
package dev.frogmc.phytotelma package dev.frogmc.phytotelma
import dev.frogmc.phytotelma.accesswidener.AccessWidener
import dev.frogmc.phytotelma.build.PhytotelmaBuildTask
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.run.AssetDownloader
import dev.frogmc.phytotelma.run.RunConfigGenerator
import dev.frogmc.phytotelma.run.task.RunGameTask
import dev.frogmc.phytotelma.vineflower.ParchmentJavadocProvider
import dev.frogmc.thyroxine.provider.ParchmentProvider
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.JavaPlugin import org.gradle.api.plugins.JavaPlugin
import org.jetbrains.java.decompiler.main.Fernflower import org.gradle.configurationcache.extensions.capitalized
import org.jetbrains.java.decompiler.main.decompiler.PrintStreamLogger
import org.jetbrains.java.decompiler.main.decompiler.SingleFileSaver
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences
import java.io.PrintStream
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.createDirectories
import kotlin.io.path.deleteExisting
import kotlin.io.path.exists
class PhytotelmaPlugin : Plugin<Project> { class PhytotelmaPlugin : Plugin<Project> {
@ -43,6 +28,10 @@ class PhytotelmaPlugin : Plugin<Project> {
} }
project.repositories.apply { project.repositories.apply {
maven {
it.name = "Remapped Mod Dependencies"
it.url = project.layout.buildDirectory.asFile.get().resolve("remappedMods").toURI()
}
maven { maven {
it.name = "Minecraft/Local" it.name = "Minecraft/Local"
it.url = localCacheDir.toUri() it.url = localCacheDir.toUri()
@ -68,70 +57,20 @@ class PhytotelmaPlugin : Plugin<Project> {
PhytotelmaGradleExtensionImpl::class.java PhytotelmaGradleExtensionImpl::class.java
) )
project.extensions.findByType(PhytotelmaGradleExtension::class.java) ?: return ModConfigurations.configurations.forEach { conf ->
project.configurations.create("mod" + conf.name.capitalized()) { c ->
c.isCanBeResolved = true
c.isCanBeConsumed = false
project.tasks.register("genSources") {task -> when (conf.dependencyType) {
task.group = Constants.TASK_GROUP DependencyType.RUNTIME -> project.configurations.getByName(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME)
task.actions.add { .extendsFrom(c)
val projectData = ProjectStorage.get(it.project)
val fileName = projectData.remappedGameJarPath!!.fileName.toString() DependencyType.COMPILE -> project.configurations.getByName(JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME)
val output = .extendsFrom(c)
projectData.remappedGameJarPath!!.resolveSibling(
fileName.substring( DependencyType.NONE -> {}
0,
fileName.lastIndexOf("-")
) + "-sources.jar"
)
if (output.exists()) {
println("Output $output already exists, deleting!")
output.deleteExisting()
} }
val options = mutableMapOf<String, Any>()
println("Preparing Parchment...")
val parchment = ParchmentProvider.getParchment(
projectData.minecraftVersion!!,
projectData.parchmentVersion!!,
globalCacheDir.resolve("org/parchmentmc/parchment/${projectData.minecraftVersion}/${projectData.parchmentVersion}/")
)
options[IFabricJavadocProvider.PROPERTY_NAME] = ParchmentJavadocProvider(parchment)
println("Decompiling...")
val logger = PrintStreamLogger(PrintStream(System.out))
options.putAll(
mapOf(
IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES to "1",
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING to "1",
IFernflowerPreferences.REMOVE_SYNTHETIC to "1",
IFernflowerPreferences.LOG_LEVEL to "error",
IFernflowerPreferences.THREADS to Runtime.getRuntime().availableProcessors().toString(),
IFernflowerPreferences.INDENT_STRING to "\t"
)
)
val decomp = Fernflower(
SingleFileSaver(output.toFile()),
options, logger
)
decomp.addSource(projectData.remappedGameJarPath!!.toFile())
decomp.decompileContext()
}
}
project.tasks.register(Constants.GEN_RUN_CONFIGS_TASK) {task ->
task.group = Constants.TASK_GROUP
task.doFirst {
RunConfigGenerator.generate(project)
}
}
project.tasks.register(Constants.RUN_CLIENT_TASK, RunGameTask::class.java, Env.CLIENT)
project.tasks.register(Constants.RUNT_SERVER_TASK, RunGameTask::class.java, Env.SERVER)
project.tasks.register(Constants.DOWNLOAD_ASSETS_TASK) { task ->
task.group = Constants.TASK_GROUP
task.doFirst {
AssetDownloader.download(it.project)
} }
} }
@ -149,16 +88,6 @@ class PhytotelmaPlugin : Plugin<Project> {
it.isCanBeConsumed = false it.isCanBeConsumed = false
it.isTransitive = false it.isTransitive = false
} }
val buildTask = project.tasks.register(Constants.BUILD_TASK, PhytotelmaBuildTask::class.java)
project.tasks.getByName("build").dependsOn(buildTask)
project.tasks.register(Constants.UPDATE_AW_TASK) { task ->
task.group = Constants.TASK_GROUP
task.actions.addFirst {
AccessWidener.apply(project, ProjectStorage.get(it.project).remappedGameJarPath!!)
}
}
} }
companion object { companion object {

View file

@ -1,5 +1,6 @@
package dev.frogmc.phytotelma package dev.frogmc.phytotelma
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
@ -8,15 +9,19 @@ object ProjectStorage {
private val data = mutableMapOf<Project, ProjectData>() private val data = mutableMapOf<Project, ProjectData>()
fun get(project: Project): ProjectData { fun get(project: Project): ProjectData {
if (!data.containsKey(project)){ if (!data.containsKey(project)) {
data[project] = ProjectData(null, null, null, null) data[project] = ProjectData(null, null, null, null, null, null)
} }
return data[project]!! return data[project]!!
} }
} }
class ProjectData(var localCacheDir: Path?, class ProjectData(
var minecraftVersion: String?, var localCacheDir: Path?,
var remappedGameJarPath: Path?, var minecraftVersion: String?,
var parchmentVersion: String?) var remappedGameJarPath: Path?,
var mappings: MappingBundle?,
var mappingsName: String?,
var targetNamespace: String?
)

View file

@ -1,3 +1,5 @@
@file:Suppress("unused")
package dev.frogmc.phytotelma package dev.frogmc.phytotelma
import com.google.gson.Gson import com.google.gson.Gson
@ -5,9 +7,11 @@ 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.Files
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.* import kotlin.io.path.createDirectories
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>()
@ -77,17 +81,6 @@ object VersionChecker {
return fetchVersionData(version).downloads.client return fetchVersionData(version).downloads.client
} }
fun downloadAssetIndex(version: String, path: Path): String {
val index = fetchVersionData(version).assetIndex
val dest = path.resolve(index.id + ".json")
if (dest.notExists()) {
CachingHttpClient.getUncached(URI.create(index.url)).use {
Files.copy(it, dest)
}
}
return index.id
}
private fun rawDownload(url: String): ByteArray { private fun rawDownload(url: String): ByteArray {
return CachingHttpClient.getUncached(URI.create(url)).readAllBytes() return CachingHttpClient.getUncached(URI.create(url)).readAllBytes()
} }

View file

@ -9,6 +9,7 @@ import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.plugins.JavaPlugin import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.plugins.JavaPluginExtension
import org.objectweb.asm.* import org.objectweb.asm.*
import org.objectweb.asm.tree.ClassNode
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Path import java.nio.file.Path
@ -45,18 +46,20 @@ object AccessWidener {
private fun findDependencyAWs(project: Project): Stream<String> { private fun findDependencyAWs(project: Project): Stream<String> {
val conf = project.configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME) val conf = project.configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)
val projectDeps = conf.dependencies.filterIsInstance<ProjectDependency>() val projectDeps = conf.dependencies.filterIsInstance<ProjectDependency>()
.mapNotNull { getAWFile(it.dependencyProject) }.stream().flatMap { readTransitiveAW(it) } .mapNotNull { getAWFile(it.dependencyProject) }.flatMap { readTransitiveAW(it) }
val dependencies = conf.resolvedConfiguration.files.map { it.toPath() }.filter { it.exists() && it.isRegularFile() }.stream().flatMap { val dependencies =
FileSystems.newFileSystem(it).use {fs -> conf.resolvedConfiguration.files.map { it.toPath() }.filter { it.exists() && it.isRegularFile() }.stream()
val metadata = fs.getPath(Constants.MOD_METADATA_FILE) .flatMap {
PARSER.parse(metadata, FileNotFoundAction.READ_NOTHING) FileSystems.newFileSystem(it).use { fs ->
.get<String>("frog.extensions.accesswidener")?.let { name -> val metadata = fs.getPath(Constants.MOD_METADATA_FILE)
return@flatMap readTransitiveAW(metadata.resolveSibling(name)) PARSER.parse(metadata, FileNotFoundAction.READ_NOTHING)
.get<String>("frog.extensions.accesswidener")?.let { name ->
return@flatMap readTransitiveAW(metadata.resolveSibling(name)).stream()
}
} }
} return@flatMap null
return@flatMap null }.filter { it != null }
}.filter { it != null } return Stream.concat(projectDeps.stream(), dependencies)
return Stream.concat(projectDeps, dependencies)
} }
fun needsUpdate(project: Project): Boolean { fun needsUpdate(project: Project): Boolean {
@ -70,24 +73,75 @@ object AccessWidener {
return false return false
} }
private fun readAW(project: Project): Stream<String> { fun checkAW(path: Path, gamePath: Path) {
val reader = path.bufferedReader(StandardCharsets.UTF_8)
if (!HEADER.test(reader.readLine() ?: "")) {
error("AccessWidener validation failed (invalid header)")
}
FileSystems.newFileSystem(gamePath).use { fs ->
reader.lines().toList().forEachIndexed { index, line ->
val checkString = line.substring(0, line.indexOf("#"))
if (checkString.isNotEmpty()) {
val parts = checkString.split(SEPARATOR)
if (parts.size < 3) {
error("AccessWidener validation failed in line ${index + 2}: $line")
}
if (parts[1] == "class") {
val target = parts[2]
if (fs.getPath(target).notExists()) {
error("AccessWidener validation failed in line ${index + 2}: $line (Invalid target)")
}
}
if (parts.size < 5) {
error("AccessWidener validation failed in line ${index + 2}: $line (Declaration missing)")
}
val owner = parts[2]
val name = parts[3]
val desc = parts[4]
when (parts[1]) {
"method" -> {
val classReader = ClassReader(fs.getPath(owner).readBytes())
val node = ClassNode()
classReader.accept(node, 0)
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)")
}
}
"field" -> {
val classReader = ClassReader(fs.getPath(owner).readBytes())
val node = ClassNode()
classReader.accept(node, 0)
if (node.fields.none { it.name.equals(name) && it.desc.equals(desc) }) {
error("AccessWidener validation failed in line ${index + 2}: $line (Could not find target field)")
}
}
}
}
}
}
}
private fun readAW(project: Project): List<String> {
val awFile = getAWFile(project) val awFile = getAWFile(project)
println("Found accesswidener in project: $awFile") println("Found accesswidener in project: $awFile")
return awFile?.bufferedReader(StandardCharsets.UTF_8) return awFile?.bufferedReader(StandardCharsets.UTF_8)
.takeIf { HEADER.test(it?.readLine() ?: "") }?.lines() .takeIf { HEADER.test(it?.readLine() ?: "") }?.lines()
?.map { it.replace("transitive-", "") } ?.map { it.replace("transitive-", "") }?.toList()
?: Stream.empty() ?: emptyList()
} }
private fun readTransitiveAW(path: Path): Stream<String> { private fun readTransitiveAW(path: Path): List<String> {
return path.bufferedReader(StandardCharsets.UTF_8).takeIf { HEADER.test(it.readLine() ?: "") }?.lines()?.filter { it.startsWith("transitive-") }?: Stream.empty() return path.bufferedReader(StandardCharsets.UTF_8).takeIf { HEADER.test(it.readLine() ?: "") }?.lines()
?.filter { it.startsWith("transitive-") }?.toList() ?: emptyList()
} }
private fun readAllAWs(project: Project): Stream<Entry> { private fun readAllAWs(project: Project): Stream<Entry> {
return Stream.concat( return Stream.concat(
findDependencyAWs(project), findDependencyAWs(project),
readAW(project) readAW(project).stream()
) )
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
.map { if (it.contains("#")) it.split("#")[0] else it }.filter { !HEADER.test(it) } .map { if (it.contains("#")) it.split("#")[0] else it }.filter { !HEADER.test(it) }

View file

@ -0,0 +1,260 @@
package dev.frogmc.phytotelma.ext
import dev.frogmc.phytotelma.PhytotelmaPlugin
import dev.frogmc.phytotelma.mappings.filterClasses
import dev.frogmc.phytotelma.mappings.renameDstNamespace
import dev.frogmc.phytotelma.mappings.renameNamespaces
import dev.frogmc.thyroxine.api.data.MappingBundle
import dev.frogmc.thyroxine.parser.tiny.TinyV2Parser
import dev.frogmc.thyroxine.provider.MojmapProvider
import dev.frogmc.thyroxine.provider.ParchmentProvider
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Provider
import java.nio.charset.StandardCharsets
import java.nio.file.FileSystems
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.readText
@Suppress("MemberVisibilityCanBePrivate", "unused")
abstract class MinecraftConfiguration @Inject constructor(
private val project: Project,
private val objects: ObjectFactory
) {
var version: String? = null
var mappings: Provider<MappingBundle> = mojmapParchment()
internal lateinit var mappingsName: String
internal lateinit var targetNamespace: String
fun mojmapParchment(): Provider<MappingBundle> {
return mojmapParchment {}
}
fun mojmapParchment(action: Action<ParchmentConfiguration>): Provider<MappingBundle> {
return project.provider {
val conf = objects.newInstance(ParchmentConfiguration::class.java)
conf.gameVersion = version
action.execute(conf)
val cacheDir = PhytotelmaPlugin.globalCacheDir
if (conf.version == null) {
conf.version = ParchmentProvider.findForMinecraftVersion(conf.gameVersion)
}
mappingsName = "mojmap($version)+parchment(${conf.gameVersion}, ${conf.version})"
targetNamespace = "mojmap"
return@provider MappingBundle.merge(
MojmapProvider.get(
version,
cacheDir.resolve("net/minecraft/client/$version/client-$version.txt")
).orElseThrow().reverse(),
ParchmentProvider.getParchment(
version,
cacheDir.resolve("org/parchmentmc/parchment/${conf.gameVersion}/${conf.version}")
)
).renameDstNamespace(targetNamespace)
}
}
fun parchment(action: Action<ParchmentConfiguration>): Provider<MappingBundle> {
return project.provider {
val conf = objects.newInstance(ParchmentConfiguration::class.java)
conf.gameVersion = version
action.execute(conf)
val cacheDir = PhytotelmaPlugin.globalCacheDir
if (conf.version == null) {
conf.version = ParchmentProvider.findForMinecraftVersion(conf.gameVersion)
}
mappingsName = "parchment(${conf.gameVersion}, ${conf.version})"
targetNamespace = "parchment"
return@provider ParchmentProvider.getParchment(
version,
cacheDir.resolve("org/parchmentmc/parchment/${conf.gameVersion}/${conf.version}")
).renameNamespaces("mojmap", "parchment")
}
}
fun mojmap(): Provider<MappingBundle> {
return project.provider {
val cacheDir = PhytotelmaPlugin.globalCacheDir
mappingsName = "mojmap($version)"
targetNamespace = "mojmap"
return@provider MojmapProvider.get(
version,
cacheDir.resolve("net/minecraft/client/$version/client-$version.txt")
).orElseThrow().reverse().renameDstNamespace(targetNamespace)
}
}
fun quiltMappings(action: Action<VersionConfiguration>): Provider<MappingBundle> {
return project.provider {
val conf = objects.newInstance(VersionConfiguration::class.java)
action.execute(conf)
if (conf.version == null) {
error("No version provided for quilt mappings!")
}
mappingsName = "quilt-mappings(${conf.version})"
targetNamespace = "quilt-mappings"
// Use qm via intermediary because hashed publications are broken
return@provider twoStepMappings(
"net.fabricmc:intermediary:$version:v2",
"org.quiltmc:quilt-mappings:${conf.version}:intermediary-v2"
).filterClasses { !it.startsWith("net/minecraft/unmapped") }
.flatten(true).renameDstNamespace(targetNamespace)
}
}
fun yarn(action: Action<VersionConfiguration>): Provider<MappingBundle> {
return project.provider {
val conf = objects.newInstance(VersionConfiguration::class.java)
action.execute(conf)
if (conf.version == null) {
error("No version provided for yarn!")
}
mappingsName = "yarn(${conf.version})"
targetNamespace = "yarn"
// Use qm via intermediary because hashed publications are broken
return@provider twoStepMappings(
"net.fabricmc:intermediary:$version:v2",
"net.fabricmc:yarn:${conf.version}:v2"
).filterClasses { !it.startsWith("net/minecraft/class_") }
.flatten(true).renameDstNamespace(targetNamespace)
}
}
fun twoStepMappings(action: Action<TwoStepMappingsConfiguration>): Provider<MappingBundle> {
return project.provider {
val conf = objects.newInstance(TwoStepMappingsConfiguration::class.java)
action.execute(conf)
if (conf.first == null) {
error("No version provided for first mapping step!")
}
if (conf.second == null) {
error("No version provided for second mapping step!")
}
mappingsName = "custom/two-step(${conf.first!!}, ${conf.second!!})"
val bundle = twoStepMappings(conf.first!!, conf.second!!)
targetNamespace = bundle.flattenData().dstNamespace
return@provider bundle
}
}
fun tinyMappings(action: Action<NameConfiguration>): Provider<MappingBundle> {
return project.provider {
val conf = objects.newInstance(NameConfiguration::class.java)
action.execute(conf)
if (conf.name == null) {
error("No maven coordinate provided for tiny mappings!")
}
mappingsName = "custom/tiny(${conf.name})"
val bundle = tinyMappings(conf.name!!)
targetNamespace = bundle.flattenData().dstNamespace
return@provider bundle
}
}
fun mappings(action: Action<MappingBundle>): Provider<MappingBundle> {
return project.provider {
val bundle = MappingBundle()
action.execute(bundle)
mappingsName = "custom(${bundle.srcNamespaces()}, ${bundle.dstNamespaces()})"
targetNamespace = bundle.flattenData().dstNamespace
return@provider bundle
}
}
fun layer(action: Action<LayerConfiguration>): Provider<MappingBundle> {
return project.provider {
val conf = objects.newInstance(LayerConfiguration::class.java)
action.execute(conf)
var back = MappingBundle()
var name = "layer["
val layers = conf.layers.mapIndexed { index, provider ->
val bundle = provider.get().flatten(true)
if (index == 0) {
name += mappingsName
back.insert(bundle)
return@mapIndexed bundle
}
name += ", $mappingsName"
back = back.flatten(false)
val prevDst = back.data[0].dstNamespace
val dst = bundle.data[0].dstNamespace
val remapped = MappingBundle.merge(bundle.reverse(), back)
.flatten(prevDst, dst)
.insert(back.docsForNamespace(dst))
back.insert(remapped)
return@mapIndexed remapped
}
mappingsName = "$name]"
val result = MappingBundle.merge(*layers.toTypedArray())
.flatten("official", layers.last().dstNamespaces()[0])
targetNamespace = result.data[0].dstNamespace
return@provider result
}
}
private fun twoStepMappings(intermediary: String, mappings: String): MappingBundle {
return MappingBundle.merge(tinyMappings(intermediary), tinyMappings(mappings))
}
private fun tinyMappings(name: String): MappingBundle {
mavenMappings(name).let { path ->
FileSystems.newFileSystem(path).use {
return TinyV2Parser.parse(it.getPath("mappings/mappings.tiny").readText(StandardCharsets.UTF_8))
}
}
}
@Suppress("UnstableApiUsage")
private fun mavenMappings(name: String): Path {
val configuration = project.configurations.create("mappings") {
it.isCanBeResolved = true
it.isCanBeDeclared = true
}
configuration.dependencies.add(project.dependencies.create(name))
val path = configuration.resolve().first().toPath()
project.configurations.remove(configuration)
return path
}
abstract class TwoStepMappingsConfiguration {
var first: String? = null
var second: String? = null
}
abstract class ParchmentConfiguration {
var gameVersion: String? = null
var version: String? = null
}
abstract class LayerConfiguration {
internal val layers = mutableListOf<Provider<MappingBundle>>()
fun add(mappings: Provider<MappingBundle>): LayerConfiguration {
layers.add(mappings)
return this
}
}
}
abstract class NameConfiguration {
var name: String? = null
}
abstract class VersionConfiguration {
var version: String? = null
}

View file

@ -1,25 +1,13 @@
package dev.frogmc.phytotelma.ext package dev.frogmc.phytotelma.ext
import org.gradle.api.provider.Provider import org.gradle.api.Action
@Suppress("unused") @Suppress("unused")
interface PhytotelmaGradleExtension { interface PhytotelmaGradleExtension {
fun minecraft(version: String, parchmentGameVersion: String? = null, parchmentVersion: String? = null) fun minecraft(action: Action<MinecraftConfiguration>)
fun minecraft(version: Provider<String>, parchmentGameVersion: Provider<String>? = null, parchmentVersion: Provider<String>? = null) { fun loader(action: Action<VersionConfiguration>)
minecraft(version.get(), parchmentGameVersion?.get(), parchmentVersion?.get())
}
fun loader(version: String) fun froglib(action: Action<VersionConfiguration>)
fun loader(version: Provider<String>) {
loader(version.get())
}
fun froglib(version: String)
fun froglib(version: Provider<String>) {
froglib(version.get())
}
} }

View file

@ -1,105 +1,328 @@
package dev.frogmc.phytotelma.ext package dev.frogmc.phytotelma.ext
import dev.frogmc.phytotelma.Constants import com.electronwill.nightconfig.core.file.FileNotFoundAction
import dev.frogmc.phytotelma.PhytotelmaPlugin import com.electronwill.nightconfig.toml.TomlParser
import dev.frogmc.phytotelma.ProjectStorage import dev.frogmc.phytotelma.*
import dev.frogmc.phytotelma.VersionChecker
import dev.frogmc.phytotelma.accesswidener.AccessWidener import dev.frogmc.phytotelma.accesswidener.AccessWidener
import dev.frogmc.phytotelma.build.PhytotelmaBuildTask
import dev.frogmc.phytotelma.common.Env
import dev.frogmc.phytotelma.mappings.renameDstNamespace
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.vineflower.FrogJavadocProvider
import dev.frogmc.thyroxine.Thyroxine import dev.frogmc.thyroxine.Thyroxine
import dev.frogmc.thyroxine.parser.ProguardParser import dev.frogmc.thyroxine.api.Mapper
import dev.frogmc.thyroxine.api.data.MappingBundle
import dev.frogmc.thyroxine.provider.MojmapProvider import dev.frogmc.thyroxine.provider.MojmapProvider
import dev.frogmc.thyroxine.provider.ParchmentProvider import net.fabricmc.fernflower.api.IFabricJavadocProvider
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.plugins.JavaBasePlugin
import org.gradle.api.plugins.JavaPlugin
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.decompiler.PrintStreamLogger
import org.jetbrains.java.decompiler.main.decompiler.SingleFileSaver
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences
import java.io.OutputStream
import java.io.PrintStream
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import javax.inject.Inject import javax.inject.Inject
import kotlin.io.path.createParentDirectories import kotlin.io.path.*
import kotlin.io.path.notExists
abstract class PhytotelmaGradleExtensionImpl : PhytotelmaGradleExtension { abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
private val project: Project,
private val objects: ObjectFactory
) : PhytotelmaGradleExtension {
@Inject private fun setupTasks() {
abstract fun getProject(): Project project.tasks.register("genSources") { task ->
task.group = Constants.TASK_GROUP
task.actions.add {
val projectData = ProjectStorage.get(it.project)
val fileName = projectData.remappedGameJarPath!!.fileName.toString()
val output =
projectData.remappedGameJarPath!!.resolveSibling(
fileName.substring(
0,
fileName.lastIndexOf("-")
) + "-sources.jar"
)
if (output.exists()) {
println("Output $output already exists, deleting!")
output.deleteExisting()
}
val options = mutableMapOf<String, Any>()
println("Preparing Parchment...")
val javadocs = ProjectStorage.get(project).mappings!!
options[IFabricJavadocProvider.PROPERTY_NAME] =
FrogJavadocProvider(javadocs.get(ProjectStorage.get(project).targetNamespace))
override fun minecraft(version: String, parchmentGameVersion: String?, parchmentVersion: String?) { println("Decompiling...")
if (VersionChecker.validateVersion(version, offlineMode = getProject().gradle.startParameter.isOffline)) { val logger = PrintStreamLogger(PrintStream(System.out))
println("Setting up Minecraft...") options.putAll(
val parchmentGameVer = parchmentGameVersion?: version mapOf(
val parchment = IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES to "1",
parchmentVersion ?: kotlin.runCatching { ParchmentProvider.findForMinecraftVersion(parchmentGameVer) } IFernflowerPreferences.BYTECODE_SOURCE_MAPPING to "1",
.getOrDefault("") IFernflowerPreferences.REMOVE_SYNTHETIC to "1",
val projectData = ProjectStorage.get(getProject()) IFernflowerPreferences.LOG_LEVEL to "error",
IFernflowerPreferences.THREADS to Runtime.getRuntime().availableProcessors().toString(),
IFernflowerPreferences.INDENT_STRING to "\t"
)
)
val decomp = Fernflower(
SingleFileSaver(output.toFile()),
options, logger
)
decomp.addSource(projectData.remappedGameJarPath!!.toFile())
decomp.decompileContext()
}
}
project.tasks.register(Constants.GEN_RUN_CONFIGS_TASK) { task ->
task.group = Constants.TASK_GROUP
task.doFirst {
RunConfigGenerator.generate(project)
}
}
project.tasks.register(Constants.RUN_CLIENT_TASK, RunGameTask::class.java, Env.CLIENT)
project.tasks.register(Constants.RUNT_SERVER_TASK, RunGameTask::class.java, Env.SERVER)
project.tasks.register(Constants.DOWNLOAD_ASSETS_TASK) { task ->
task.group = Constants.TASK_GROUP
task.doFirst {
AssetDownloader.download(it.project, true)
}
}
val buildTask = project.tasks.register(Constants.BUILD_TASK, PhytotelmaBuildTask::class.java)
project.tasks.getByName(JavaBasePlugin.BUILD_TASK_NAME).dependsOn(buildTask)
project.tasks.getByName(JavaPlugin.JAR_TASK_NAME).actions.addLast { task ->
val storage = ProjectStorage.get(project)
if (storage.targetNamespace != "mojmap") {
val mappings = MappingBundle.merge(
storage.mappings!!.reverse(), MojmapProvider.get(
storage.minecraftVersion!!,
PhytotelmaPlugin.globalCacheDir.resolve("net/minecraft/client/${storage.minecraftVersion}/client-${storage.minecraftVersion}.txt")
).orElseThrow().reverse().renameDstNamespace("mojmap")
).forNamespaces(storage.targetNamespace, "mojmap")
val parser = TomlParser()
task.outputs.files.forEach { file ->
val temp = Files.createTempFile("", file.name)
Files.copy(file.toPath(), temp, StandardCopyOption.REPLACE_EXISTING)
FileSystems.newFileSystem(temp).use { fs ->
val metadata = fs.getPath(Constants.MOD_METADATA_FILE)
parser.parse(metadata, FileNotFoundAction.READ_NOTHING)
.get<String>("frog.extensions.accesswidener")?.let { name ->
val aw = metadata.resolveSibling(name)
AccessWidener.checkAW(aw, ProjectStorage.get(project).remappedGameJarPath!!)
val mapper = Mapper(mappings) { listOf() }
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)
Files.deleteIfExists(temp)
}
}
}
project.tasks.register(Constants.UPDATE_AW_TASK) { task ->
task.group = Constants.TASK_GROUP
task.actions.addFirst {
AccessWidener.apply(project, ProjectStorage.get(it.project).remappedGameJarPath!!)
}
}
project.tasks.register(Constants.CLEAR_LOCAL_CACHE_TASK, Delete::class.java) { task ->
task.group = Constants.TASK_GROUP
task.delete = setOf(ProjectStorage.get(project).localCacheDir)
}
project.tasks.register(Constants.CLEAR_GLOBAL_CACHE_TASK, Delete::class.java) { task ->
task.group = Constants.TASK_GROUP
task.delete = setOf(PhytotelmaPlugin.globalCacheDir)
}
project.afterEvaluate {
remapModDependencies()
}
}
private fun remapModDependencies() {
val out = System.out
// Mute the output from thyroxine as there may be a lot of remapping operations here
System.setOut(PrintStream(OutputStream.nullOutputStream()))
ModConfigurations.configurations.forEach { conf ->
val artifacts = project.configurations.getByName("mod" + conf.name.capitalized())
.resolvedConfiguration.resolvedArtifacts
if (artifacts.isEmpty()) {
return
}
val target = project.configurations.create("mod" + conf.name.capitalized() + "Mapped") { c ->
c.isTransitive = false
conf.classpathNames.forEach {
project.configurations.getByName(it).extendsFrom(c)
}
}
val storage = ProjectStorage.get(project)
val mappings = MappingBundle.merge(
storage.mappings!!.reverse(), MojmapProvider.get(
storage.minecraftVersion!!,
PhytotelmaPlugin.globalCacheDir.resolve("net/minecraft/client/${storage.minecraftVersion}/client-${storage.minecraftVersion}.txt")
).orElseThrow().reverse().renameDstNamespace("mojmap")
).forNamespaces("mojmap", storage.targetNamespace)
val targetPath = project.layout.buildDirectory.asFile.get().toPath().resolve("remappedMods")
.resolve("dev/frogmc/phytotelma/remapped_mods")
val remappedPaths = mutableListOf<Path>()
artifacts.forEach { artifact ->
val group = artifact.moduleVersion.id.group
val name = artifact.moduleVersion.id.name
val groupname = (group + "_" + name).replace(".", "_")
val version = artifact.moduleVersion.id.version
val classifier = artifact.classifier
val remappedPath = targetPath.resolve(groupname).resolve(version)
.resolve(groupname + "-" + version + (classifier?.let { "-$it" } ?: "") + ".jar")
remappedPath.createParentDirectories()
remappedPaths.add(remappedPath)
val pom = remappedPath.resolveSibling(remappedPath.fileName.toString().removeSuffix(".jar") + ".pom")
pom.writeText(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n" +
"\t\t xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
"\t<modelVersion>4.0.0</modelVersion>\n" +
"\t<groupId>dev.frogmc.phytotelma.remapped_mods</groupId>\n" +
"\t<artifactId>$groupname</artifactId>\n" +
"\t<version>$version</version>\n" +
"</project>"
)
remappedPaths.add(pom)
Thyroxine.remap(mappings, artifact.file.toPath(), remappedPath, false, false)
project.dependencies.add(
target.name,
"dev.frogmc.phytotelma.remapped_mods:$groupname:$version" + (classifier?.let { ":$it" } ?: "")
)
}
}
System.setOut(out)
}
override fun minecraft(action: Action<MinecraftConfiguration>) {
setupTasks()
val mcConf = objects.newInstance(MinecraftConfiguration::class.java)
action.execute(mcConf)
if (mcConf.version == null) {
error("No Minecraft version provided!")
}
val version = mcConf.version!!
if (VersionChecker.validateVersion(version, offlineMode = project.gradle.startParameter.isOffline)) {
val projectData = ProjectStorage.get(project)
projectData.minecraftVersion = version projectData.minecraftVersion = version
projectData.parchmentVersion = parchment
println("Valid version! $version") val mappings = mcConf.mappings.get()
val clientJar = VersionChecker.downloadClient(getProject(), version) println("Using mappings: " + mcConf.mappingsName)
projectData.mappings = mappings
val clientJar = VersionChecker.downloadClient(project, version)
val remappedJar = val remappedJar =
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
println("Time to setup Minecraft!") val applyAW = AccessWidener.needsUpdate(project)
val applyAW = AccessWidener.needsUpdate(getProject()) if (remappedJar.notExists() || applyAW || mcConf.mappingsName != projectData.mappingsName) {
if (remappedJar.notExists() || applyAW) { projectData.mappingsName = mcConf.mappingsName
println("Remapping the game...") println("Remapping the game...")
val data = kotlin.runCatching { projectData.targetNamespace = mcConf.targetNamespace
ProguardParser.read( Thyroxine.remap(
MojmapProvider.get( mappings.forNamespaces("official", mcConf.targetNamespace),
version, clientJar, remappedJar, true, true
clientJar.resolveSibling("client-$version.txt") )
).orElseThrow()
).reverse()
}.getOrNull()
val paramMappings = if (parchment.isNotEmpty()) kotlin.runCatching {
ParchmentProvider.getParchment(
parchmentGameVer, parchment,
PhytotelmaPlugin.globalCacheDir.resolve("org/parchmentmc/parchment/$parchmentGameVer/$parchment"),
getProject().gradle.startParameter.isRefreshDependencies
)
}.getOrNull() else {
println("Parameter mappings will not be present as no parchment version was found. It may be possible to declare it manually.")
null
}
if (paramMappings == null && parchment.isNotEmpty()) {
println("Parameter mappings will not be present as the version $parchmentVersion for minecraft version $parchmentGameVer could not be found")
}
if (data != null) {
Thyroxine.remap(data, clientJar, remappedJar, true, paramMappings)
} else {
error("Failed to remap the game as no mojmap version was found for game version $version. Other mapping formats may be implemented in the future.")
}
} }
println("Adding dependencies...")
getProject().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(version) {
getProject().dependencies.add(Constants.MINECRAFT_CONFIGURATION, it) project.dependencies.add(Constants.MINECRAFT_CONFIGURATION, it)
} }
VersionChecker.savePomFile(version, remappedJar.parent) VersionChecker.savePomFile(version, remappedJar.parent)
getProject().dependencies.add(Constants.MINECRAFT_CONFIGURATION, "net.minecraft:client:$version:remapped") project.dependencies.add(Constants.MINECRAFT_CONFIGURATION, "net.minecraft:client:$version:remapped")
println("Generating run configurations...") RunConfigGenerator.generate(project)
RunConfigGenerator.generate(getProject()) AssetDownloader.download(project)
if (applyAW) { if (applyAW) {
getProject().afterEvaluate { project.afterEvaluate {
println("Applying AccessWideners...") println("Applying AccessWideners...")
AccessWidener.apply(getProject(), remappedJar) AccessWidener.apply(project, remappedJar)
} }
} }
println("Done!")
} else {
println("Invalid version! $version")
error("Invalid minecraft version provided: $version")
} }
} }
override fun loader(version: String) { override fun loader(action: Action<VersionConfiguration>) {
getProject().dependencies.add("implementation", "dev.frogmc:frogloader:$version") val conf = objects.newInstance(VersionConfiguration::class.java)
action.execute(conf)
if (conf.version == null) {
error("No loader version provided!")
}
project.dependencies.add("implementation", "dev.frogmc:frogloader:${conf.version!!}")
} }
override fun froglib(version: String) { override fun froglib(action: Action<VersionConfiguration>) {
getProject().dependencies.add("implementation", "dev.frogmc:froglib:$version") val conf = objects.newInstance(VersionConfiguration::class.java)
action.execute(conf)
if (conf.version == null) {
error("No froglib version provided!")
}
project.dependencies.add("modImplementation", "dev.frogmc:froglib:${conf.version!!}")
} }
} }

View file

@ -0,0 +1,48 @@
package dev.frogmc.phytotelma.mappings
import dev.frogmc.thyroxine.api.data.DocumentationData
import dev.frogmc.thyroxine.api.data.MappingBundle
import dev.frogmc.thyroxine.api.data.MappingData
fun MappingBundle.renameNamespaces(newSrc: String?, newDst: String): MappingBundle {
val oldData = if (srcNamespaces().size > 1 || dstNamespaces().size > 1) flattenData() else data[0]
val newData = MappingData(newSrc ?: oldData.srcNamespace, newDst)
val newDocs = DocumentationData(newDst)
oldData.apply {
newData.classes.putAll(classes)
newData.fields.putAll(fields)
newData.methods.putAll(methods)
newData.parameters.putAll(parameters)
}
return MappingBundle(newData, get(oldData.dstNamespace)?.let { newDocs.insert(it) })
}
fun MappingBundle.renameDstNamespace(newDst: String): MappingBundle {
return renameNamespaces(null, newDst)
}
fun MappingBundle.filterClasses(filter: (String) -> Boolean): MappingBundle {
val oldData = data.last()
val newData = MappingData(oldData.srcNamespace, oldData.dstNamespace)
val oldDocs = if (documentation.isNotEmpty()) documentation.last() else null
oldData.apply {
newData.classes.putAll(classes.filter { filter.invoke(it.value) })
newData.fields.putAll(fields.filter { newData.classes.containsKey(it.key.owner) })
newData.methods.putAll(methods.filter { newData.classes.containsKey(it.key.owner) })
newData.parameters.putAll(parameters.filter { newData.classes.containsKey(it.key.owner) })
}
val bundle = MappingBundle(data.subList(0, data.size - 1).plus(newData), mutableListOf())
if (oldDocs != null) {
val newDocs = DocumentationData(newData.dstNamespace)
newDocs.classes.addAll(oldDocs.classes.filter { filter.invoke(it.name) })
bundle.insert(newDocs)
}
return bundle
}

View file

@ -110,8 +110,8 @@ object Nester {
val matcher = pattern.matcher(version) val matcher = pattern.matcher(version)
matcher.find() matcher.find()
val semver = matcher.group(1) + val semver = matcher.group(1) +
(if (matcher.group(2) != null) "-"+matcher.group(2) else "") + (if (matcher.group(2) != null) "-" + matcher.group(2) else "") +
(if (matcher.group(3) != null) "+"+matcher.group(3) else "") (if (matcher.group(3) != null) "+" + matcher.group(3) else "")
it.getPath("frog.mod.toml").writeText( it.getPath("frog.mod.toml").writeText(
""" """
[frog] [frog]

View file

@ -9,43 +9,59 @@ 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.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.createDirectories import kotlin.io.path.createDirectories
import kotlin.io.path.notExists
import kotlin.io.path.reader import kotlin.io.path.reader
object AssetDownloader { object AssetDownloader {
private const val ASSETS_URL = "https://resources.download.minecraft.net" private const val ASSETS_URL = "https://resources.download.minecraft.net"
fun download(project: Project) { 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 id = VersionChecker.downloadAssetIndex(version, path.resolve("indexes")) val assetIndex = VersionChecker.fetchVersionData(version).assetIndex
val dest = path.resolve("indexes").resolve(assetIndex.id + ".json")
var overwrite = manualInvocation
if (dest.notExists()) {
CachingHttpClient.getUncached(URI.create(assetIndex.url)).use {
Files.copy(it, dest)
}
overwrite = true
}
val index = Gson().fromJson( val index = Gson().fromJson(
path.resolve("indexes").resolve("$id.json").reader(StandardCharsets.UTF_8), path.resolve("indexes").resolve("${assetIndex.id}.json").reader(StandardCharsets.UTF_8),
JsonObject::class.java JsonObject::class.java
) )
val objectsPath = path.resolve("objects") val objectsPath = path.resolve("objects")
objectsPath.createDirectories() objectsPath.createDirectories()
val entries = index.getAsJsonObject("objects").entrySet() val entries = index.getAsJsonObject("objects").entrySet()
val totalSize = entries.size val totalSize = entries.size
print("Downloading $totalSize assets... ()") if (overwrite) {
System.out.flush() print("Downloading $totalSize assets... ()")
System.out.flush()
}
entries.forEachIndexed { i, it -> entries.forEachIndexed { i, it ->
val hash = it.value.asJsonObject.get("hash").asString val hash = it.value.asJsonObject.get("hash").asString
/*val size = it.value.asJsonObject.get("size").asInt /*val size = it.value.asJsonObject.get("size").asInt
val name = it.key*/ val name = it.key*/
// TODO asset downloading for versions <=1.7 (legacy) // TODO asset downloading for versions <=1.7 (legacy)
print("\rDownloading $totalSize assets... ($i/$totalSize): $hash"+" ".repeat(30)) if (overwrite) {
System.out.flush() print("\rDownloading $totalSize assets... ($i/$totalSize): $hash" + " ".repeat(30))
get(objectsPath, hash) System.out.flush()
}
get(objectsPath, hash, overwrite)
}
if (overwrite) {
println("\rDownloading $totalSize assets... Done!" + " ".repeat(50))
} }
println("\rDownloading $totalSize assets... Done!"+" ".repeat(50))
} }
private fun get(localDir: Path, hash: String) { private fun get(localDir: Path, hash: String, overwrite: Boolean) {
val shortHash = hash.substring(0, 2) val shortHash = hash.substring(0, 2)
val path = localDir.resolve("$shortHash/$hash") val path = localDir.resolve("$shortHash/$hash")
CachingHttpClient.downloadTo(URI.create("$ASSETS_URL/$shortHash/$hash"), path, true) CachingHttpClient.downloadTo(URI.create("$ASSETS_URL/$shortHash/$hash"), path, overwrite)
} }
} }

View file

@ -34,7 +34,7 @@ object RunConfigGenerator {
assetIndexPath.createDirectories() assetIndexPath.createDirectories()
} }
val projectData = ProjectStorage.get(project) val projectData = ProjectStorage.get(project)
val indexId = VersionChecker.downloadAssetIndex(projectData.minecraftVersion!!, assetIndexPath) val indexId = VersionChecker.fetchVersionData(projectData.minecraftVersion!!).assetIndex.id
val projectName = if (project.rootDir == project.projectDir) "" else " (${project.name})" val projectName = if (project.rootDir == project.projectDir) "" else " (${project.name})"

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.downloadAssetIndex(ProjectStorage.get(project).minecraftVersion!!, assetIndexPath) val indexId = VersionChecker.fetchVersionData(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()

View file

@ -0,0 +1,28 @@
package dev.frogmc.phytotelma.vineflower
import dev.frogmc.thyroxine.api.data.DocumentationData
import net.fabricmc.fernflower.api.IFabricJavadocProvider
import org.jetbrains.java.decompiler.struct.StructClass
import org.jetbrains.java.decompiler.struct.StructField
import org.jetbrains.java.decompiler.struct.StructMethod
import java.util.stream.Collectors
class FrogJavadocProvider(data: DocumentationData) : IFabricJavadocProvider {
private val classMap = data.classes.stream().collect(Collectors.toMap({ it.name() }, { it }))
override fun getClassDoc(structClass: StructClass): String? {
return classMap[structClass.qualifiedName]?.javadoc?.joinToString("\n")
}
override fun getFieldDoc(structClass: StructClass, structField: StructField): String? {
return classMap[structClass.qualifiedName]
?.getField(structField.name, structField.descriptor)
?.orElse(null)?.javadoc?.joinToString("\n")
}
override fun getMethodDoc(structClass: StructClass, structMethod: StructMethod): String? {
return classMap[structClass.qualifiedName]?.getMethod(structMethod.name, structMethod.descriptor)
?.orElse(null)?.javadoc?.joinToString("\n")
}
}

View file

@ -1,25 +0,0 @@
package dev.frogmc.phytotelma.vineflower
import dev.frogmc.thyroxine.api.data.Parchment
import net.fabricmc.fernflower.api.IFabricJavadocProvider
import org.jetbrains.java.decompiler.struct.StructClass
import org.jetbrains.java.decompiler.struct.StructField
import org.jetbrains.java.decompiler.struct.StructMethod
class ParchmentJavadocProvider(private val parchment: Parchment) : IFabricJavadocProvider {
override fun getClassDoc(structClass: StructClass): String? {
return parchment.getClass(structClass.qualifiedName).orElse(null)?.javadoc?.joinToString("\n")
}
override fun getFieldDoc(structClass: StructClass, structField: StructField): String? {
return parchment.getClass(structClass.qualifiedName)
.flatMap { it.getField(structField.name, structField.descriptor) }
.orElse(null)?.javadoc?.joinToString("\n")
}
override fun getMethodDoc(structClass: StructClass, structMethod: StructMethod): String? {
return parchment.getClass(structClass.qualifiedName)
.flatMap { it.getMethod(structMethod.name, structMethod.descriptor) }
.orElse(null)?.javadoc?.joinToString("\n")
}
}