diff --git a/api/.gitignore b/api/.gitignore deleted file mode 100644 index b63da455..00000000 --- a/api/.gitignore +++ /dev/null @@ -1,42 +0,0 @@ -.gradle -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ - -### IntelliJ IDEA ### -.idea/modules.xml -.idea/jarRepositories.xml -.idea/compiler.xml -.idea/libraries/ -*.iws -*.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### Eclipse ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ - -### Mac OS ### -.DS_Store \ No newline at end of file diff --git a/api/build.gradle.kts b/api/build.gradle.kts deleted file mode 100644 index 81a0def5..00000000 --- a/api/build.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id("config-kotlin") - id("config-publish") -} - -dependencies { - api(libs.bundles.asm) - api(libs.mappings) - compileOnly(projects.internals) -} - -publishing { - publications { - create("maven") { - from(components["java"]) - groupId = "net.weavemc.api" - artifactId = "common" - version = "${project.version}" - } - } -} \ No newline at end of file diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index b5a0fabf..e15e84c7 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -5,3 +5,5 @@ dependencyResolutionManagement { } } } + +rootProject.name = "weave-build-logic" \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 9ee8276e..6dd37a16 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,5 +5,6 @@ subprojects { repositories { mavenCentral() maven("https://repo.weavemc.dev/releases") + mavenLocal() } } \ No newline at end of file diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index de82de1a..251a8532 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -31,4 +31,10 @@ gradlePlugin { base { archivesName = "Weave-Gradle" +} + +tasks { + processResources { + expand("version" to version) + } } \ No newline at end of file diff --git a/gradle-plugin/launcher/build.gradle.kts b/gradle-plugin/launcher/build.gradle.kts new file mode 100644 index 00000000..4afbde0e --- /dev/null +++ b/gradle-plugin/launcher/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + id("config-kotlin") + id("config-publish") +} + +kotlin { + jvmToolchain(8) +} + +dependencies { + implementation(project(":internals")) + runtimeOnly(libs.log4j.slf4j2.impl) + runtimeOnly(libs.log4j.core) + runtimeOnly(libs.terminalconsoleappender) { + exclude(group = "org.apache.logging.log4j") + } + + runtimeOnly(libs.bundles.jline) +} + +publishing { + publications { + create("maven") { + from(components["java"]) + groupId = "net.weavemc" + artifactId = "weave-gradle-launcher" + version = project.version.toString() + } + } +} \ No newline at end of file diff --git a/gradle-plugin/launcher/src/main/kotlin/net/weavemc/gradle/Launcher.kt b/gradle-plugin/launcher/src/main/kotlin/net/weavemc/gradle/Launcher.kt new file mode 100644 index 00000000..73df5699 --- /dev/null +++ b/gradle-plugin/launcher/src/main/kotlin/net/weavemc/gradle/Launcher.kt @@ -0,0 +1,71 @@ +package net.weavemc.gradle + +import net.weavemc.internals.* +import java.net.URL +import java.net.URLClassLoader +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.createDirectories +import kotlin.io.path.exists + +fun main(args: Array) { + System.setProperty("terminal.ansi", "true") + + val version = args.firstOrNull() ?: error("Specify a version to launch!") + val versionInfo = fetchVersionManifest()?.fetchVersion(version) ?: error("Could not fetch version $version!") + + val gameDir = getMinecraftDir().also { it.createDirectories() } + val gamePath = gameDir.resolve("versions").resolve(version).resolve("$version.jar") + if (!gamePath.exists()) error("Minecraft JAR does not exist?") + + val librariesDir = gameDir.resolve("libraries") + val libraryPaths = mutableListOf(gamePath) + + // TODO: extract to function? + for (lib in versionInfo.relevantLibraries) for (download in lib.downloads.allDownloads) { + val target = download.path.split('/').fold(librariesDir) { acc, curr -> acc.resolve(curr) } + libraryPaths.add(target) + if (!target.exists()) DownloadUtil.download(URL(download.url), target) + } + + val gameArgs = buildList { + fun addArgument(name: String, value: String) { + val prefix = "--$name" + if (prefix !in this) { + add(prefix) + add(value) + } + } + + addAll(args.drop(1)) + addArgument("accessToken", "0") + addArgument("version", versionInfo.id) + addArgument("assetIndex", versionInfo.assetIndex.id) + addArgument("assetsDir", gameDir.resolve("assets").absolutePathString()) + }.toTypedArray() + + val urls = libraryPaths.mapToArray { it.toUri().toURL() } + DevLauncherClassLoader(urls).loadClass(versionInfo.mainClass) + .getMethod("main", gameArgs::class.java)(null, gameArgs) +} + +class DevLauncherClassLoader(urls: Array) : URLClassLoader(urls) { + private val disallowedReloading = listOf( + "java.", "javax.", "org.xml.", "org.w3c.", "sun.", "jdk.", "com.sun.management.", + "kotlin.", "kotlinx.", "org.slf4j." + ) + + override fun loadClass(name: String, resolve: Boolean): Class<*> { + findLoadedClass(name)?.let { return it } + if (disallowedReloading.any { name.startsWith(it) }) return super.loadClass(name, resolve) + + val clazz = findClass(name) + if (resolve) resolveClass(clazz) + return clazz + } +} + +private inline fun Collection.mapToArray(block: (A) -> B): Array { + val iterator = iterator() + return Array(size) { block(iterator.next()) } +} \ No newline at end of file diff --git a/gradle-plugin/launcher/src/main/resources/log4j2.xml b/gradle-plugin/launcher/src/main/resources/log4j2.xml new file mode 100644 index 00000000..cf2d9bb3 --- /dev/null +++ b/gradle-plugin/launcher/src/main/resources/log4j2.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gradle-plugin/src/main/kotlin/net/weavemc/gradle/util/Constants.kt b/gradle-plugin/src/main/kotlin/net/weavemc/gradle/Constants.kt similarity index 86% rename from gradle-plugin/src/main/kotlin/net/weavemc/gradle/util/Constants.kt rename to gradle-plugin/src/main/kotlin/net/weavemc/gradle/Constants.kt index 7467ebff..9f09c005 100644 --- a/gradle-plugin/src/main/kotlin/net/weavemc/gradle/util/Constants.kt +++ b/gradle-plugin/src/main/kotlin/net/weavemc/gradle/Constants.kt @@ -1,4 +1,4 @@ -package net.weavemc.gradle.util +package net.weavemc.gradle import kotlinx.serialization.json.Json import net.weavemc.internals.MinecraftVersion @@ -23,11 +23,6 @@ object Constants { * The global JSON serializer */ val JSON = Json { ignoreUnknownKeys = true } - - /** - * The version manifest URL - */ - const val VERSION_MANIFEST = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json" } val MinecraftVersion.cacheDirectory get() = Constants.CACHE_DIR.resolve("cache-${versionName}").also { it.mkdirs() } diff --git a/gradle-plugin/src/main/kotlin/net/weavemc/gradle/WeaveGradle.kt b/gradle-plugin/src/main/kotlin/net/weavemc/gradle/WeaveGradle.kt index eab2e7a1..d1ba2f30 100644 --- a/gradle-plugin/src/main/kotlin/net/weavemc/gradle/WeaveGradle.kt +++ b/gradle-plugin/src/main/kotlin/net/weavemc/gradle/WeaveGradle.kt @@ -1,24 +1,27 @@ package net.weavemc.gradle import kotlinx.serialization.encodeToString -import net.weavemc.gradle.configuration.WeaveMinecraftExtension -import net.weavemc.gradle.configuration.pullDeps -import net.weavemc.gradle.util.Constants -import net.weavemc.gradle.util.localCache -import net.weavemc.gradle.util.minecraftJarCache -import net.weavemc.internals.MappingsRetrieval -import net.weavemc.internals.MinecraftVersion +import net.weavemc.gradle.configuration.* +import net.weavemc.internals.* import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPlugin -import org.gradle.api.tasks.Delete -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.SourceSetContainer -import org.gradle.api.tasks.TaskAction +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.* import org.gradle.api.tasks.bundling.Jar +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.jvm.toolchain.JavaToolchainService import org.gradle.kotlin.dsl.* +import java.net.URL +import java.nio.file.Path +import java.util.zip.ZipInputStream +import kotlin.io.path.* + +val weaveGradleVersion by lazy { + (object {}).javaClass.classLoader.getResourceAsStream("weave-gradle-version")?.readBytes()?.decodeToString() +} /** * Gradle build system plugin used to automate the setup of a modding environment. @@ -29,33 +32,110 @@ class WeaveGradle : Plugin { * * @param project The target project. */ + @OptIn(ExperimentalPathApi::class) override fun apply(project: Project) { // Applying our default plugins project.pluginManager.apply(JavaPlugin::class) val ext = project.extensions.create("weave", WeaveMinecraftExtension::class) - project.afterEvaluate { - if (!ext.configuration.isPresent) throw GradleException( - "Configuration is missing, make sure to add a configuration through the weave {} block!" - ) + val devLaunch by project.configurations.creating { isCanBeResolved = true } + val devWeaveLoader by project.configurations.creating { + isCanBeResolved = true + isTransitive = false + } - if (!ext.version.isPresent) throw GradleException( - "Set a Minecraft version through the weave {} block!" - ) + if (weaveGradleVersion != null) { + project.dependencies.add(devLaunch.name, "net.weavemc:weave-gradle-launcher:$weaveGradleVersion") { + exclude(group = "net.weavemc") // exclude unrelocated transitive dependencies: they are in the agent + } - val version = ext.version.getOrElse(MinecraftVersion.V1_8_9) - pullDeps(version, ext.configuration.get().namespace) + project.dependencies.add(devWeaveLoader.name, "net.weavemc:loader:$weaveGradleVersion:all") } val writeModConfig = project.tasks.register("writeModConfig") - project.tasks.named("jar") { + val jarProvider = project.tasks.named("jar") { dependsOn(writeModConfig) from(writeModConfig) } project.tasks.named("clean") { delete(writeModConfig) } + + val version by lazy { ext.version.getOrElse(MinecraftVersion.V1_8_9) } + val versionInfo by lazy { + fetchVersionManifest()?.fetchVersion(version.versionName) + ?: throw GradleException("Could not fetch Minecraft version $version") + } + + val launchGame by project.tasks.registering(JavaExec::class) { + val toolchains = project.extensions.getByType() + val java = project.extensions.getByType() + + javaLauncher.set(toolchains.launcherFor { + languageVersion.set( + JavaLanguageVersion.of( + maxOf( + versionInfo.javaVersion.majorVersion, + java.toolchain.languageVersion.get().asInt() + ) + ) + ) + + vendor.set(java.toolchain.vendor) + implementation.set(java.toolchain.implementation) + }) + + dependsOn(jarProvider) + workingDir(project.localGradleCache().dir("game-workdir").also { it.asFile.mkdirs() }) + mainClass.set("net.weavemc.gradle.LauncherKt") + args(version.versionName, "--version", version.versionName) + classpath(devLaunch) + + doFirst { + val librariesDir = project.localGradleCache().dir("game-libraries").asFile.toPath() + val nativesDir = project.localGradleCache().dir("game-natives").asFile.toPath().also { + it.deleteRecursively() + it.createDirectories() + } + + val allNatives = versionInfo.relevantLibraries.asSequence().mapNotNull { it.downloads.natives } + val allNativePaths = mutableListOf() + + for (native in allNatives) { + val target = native.path.split('/').fold(librariesDir) { acc, curr -> acc.resolve(curr) } + allNativePaths.add(target) + if (!target.exists()) DownloadUtil.download(URL(native.url), target) + } + + for (zip in allNativePaths) runCatching { unzip(zip, nativesDir) }.onFailure { + println("Failed unzipping $zip, ignoring") + it.printStackTrace() + } + + if (devWeaveLoader.files.size != 1) throw GradleException( + "Weave-Loader unexpectedly is comprised of multiple files: ${devWeaveLoader.files}" + ) + + jvmArgs( + "-javaagent:${devWeaveLoader.singleFile.absolutePath}", + "-Djava.library.path=${nativesDir.absolutePathString()}", + "-Dweave.devlauncher.mods=${jarProvider.get().outputs.files.singleFile.absolutePath}" + ) + } + } + + project.afterEvaluate { + if (!ext.configuration.isPresent) throw GradleException( + "Configuration is missing, make sure to add a configuration through the weave {} block!" + ) + + if (!ext.version.isPresent) throw GradleException( + "Set a Minecraft version through the weave {} block!" + ) + + pullDeps(version, versionInfo, ext.configuration.get().namespace) + } } open class WriteModConfig : DefaultTask() { @@ -64,7 +144,7 @@ class WeaveGradle : Plugin { @TaskAction fun run() { - val config = project.extensions.getByName("minecraft").configuration.get() + val config = project.extensions.getByName("weave").configuration.get() output.get().asFile.writeText(Constants.JSON.encodeToString(config)) } } @@ -73,5 +153,13 @@ class WeaveGradle : Plugin { fun MinecraftVersion.loadMergedMappings() = MappingsRetrieval.loadMergedWeaveMappings(versionName, minecraftJarCache).mappings - val Project.sourceSets get() = extensions.getByName("sourceSets") + +private fun unzip(file: Path, to: Path) = ZipInputStream(file.inputStream()).use { zip -> + generateSequence { zip.nextEntry }.filterNot { it.isDirectory }.forEach { entry -> + entry.name.split("/") + .fold(to) { acc, curr -> acc.resolve(curr) } + .also { it.parent.createDirectories() } + .writeBytes(zip.readBytes()) + } +} \ No newline at end of file diff --git a/gradle-plugin/src/main/kotlin/net/weavemc/gradle/configuration/DependencyManager.kt b/gradle-plugin/src/main/kotlin/net/weavemc/gradle/configuration/DependencyManager.kt index 149def94..6055a740 100644 --- a/gradle-plugin/src/main/kotlin/net/weavemc/gradle/configuration/DependencyManager.kt +++ b/gradle-plugin/src/main/kotlin/net/weavemc/gradle/configuration/DependencyManager.kt @@ -2,10 +2,10 @@ package net.weavemc.gradle.configuration import com.grappenmaker.mappings.* import kotlinx.serialization.Serializable -import net.weavemc.gradle.loadMergedMappings -import net.weavemc.gradle.sourceSets -import net.weavemc.gradle.util.* +import net.weavemc.gradle.* +import net.weavemc.internals.DownloadUtil import net.weavemc.internals.MinecraftVersion +import net.weavemc.internals.VersionInfo import org.gradle.api.Project import org.gradle.api.logging.LogLevel import org.gradle.kotlin.dsl.get @@ -13,25 +13,18 @@ import org.objectweb.asm.ClassVisitor import java.io.File import java.net.URL -private inline fun String?.decodeJSON() = - if (this != null) Constants.JSON.decodeFromString(this) else null - /** * Pulls dependencies from [addMinecraftAssets] and [addMappedMinecraft] */ -fun Project.pullDeps(version: MinecraftVersion, namespace: String) { - addMinecraftAssets(version) +fun Project.pullDeps(version: MinecraftVersion, versionInfo: VersionInfo, namespace: String) { + addMinecraftAssets(version, versionInfo) addMappedMinecraft(version, namespace) } /** * Adds Minecraft as a dependency by providing the jar to the projects file tree. */ -private fun Project.addMinecraftAssets(version: MinecraftVersion) { - val manifest = DownloadUtil.fetch(Constants.VERSION_MANIFEST).decodeJSON() ?: return - val versionEntry = manifest.versions.find { it.id == version.versionName } ?: return - val versionInfo = DownloadUtil.fetch(versionEntry.url).decodeJSON() ?: return - +private fun Project.addMinecraftAssets(version: MinecraftVersion, versionInfo: VersionInfo) { val client = versionInfo.downloads.client DownloadUtil.checksumAndDownload(URL(client.url), client.sha1, version.minecraftJarCache.toPath()) @@ -46,7 +39,7 @@ private fun Project.addMinecraftAssets(version: MinecraftVersion) { private fun Project.retrieveWideners(): List { // Cursed code - val ext = extensions["minecraft"] as WeaveMinecraftExtension + val ext = extensions["weave"] as WeaveMinecraftExtension val wideners = ext.configuration.get().accessWideners.toHashSet() val widenerFiles = mutableListOf() @@ -90,22 +83,4 @@ private fun Project.addMappedMinecraft(version: MinecraftVersion, namespace: Str joinedWideners?.write()?.let { joinedFile.writeText(it.joinToString("\n")) } ?: joinedFile.delete() dependencies.add("compileOnly", project.files(mapped)) -}.onFailure { it.printStackTrace() } - -@Serializable -private data class VersionManifest(val versions: List) - -@Serializable -private data class ManifestVersion(val id: String, val url: String) - -@Serializable -private data class VersionInfo(val downloads: VersionDownloads, val libraries: List) - -@Serializable -private data class VersionDownloads(val client: VersionDownload) - -@Serializable -private data class VersionDownload(val url: String, val sha1: String) - -@Serializable -private data class Library(val name: String) +}.onFailure { it.printStackTrace() } \ No newline at end of file diff --git a/gradle-plugin/src/main/resources/weave-gradle-version b/gradle-plugin/src/main/resources/weave-gradle-version new file mode 100644 index 00000000..03f59033 --- /dev/null +++ b/gradle-plugin/src/main/resources/weave-gradle-version @@ -0,0 +1 @@ +$version \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8fb9aca7..5a77fd18 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,9 +6,11 @@ mixin = "0.13.4+mixin.0.8.5" # Applying two different versions of kotlin is possible, but hacky kotlin = "2.0.0" kxser = "1.7.1" -mappings-util = "0.1.6" +mappings-util = "0.1.7" gradle-shadow = "8.1.1" klog = "0.0.5" +log4j = "2.19.0" +jline = "3.23.0" [libraries] asm = { module = "org.ow2.asm:asm", version.ref = "asm" } @@ -23,9 +25,17 @@ gradle-shadow = { module = "com.github.johnrengelman.shadow:com.github.johnrenge kotlin-compiler = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } +log4j-slf4j2-impl = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version.ref = "log4j" } +log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } +terminalconsoleappender = { module = "net.minecrell:terminalconsoleappender", version = "1.3.0" } +jline-terminal = { module = "org.jline:jline-terminal", version.ref = "jline" } +jline-reader = { module = "org.jline:jline-reader", version.ref = "jline" } +jline-terminal-jansi = { module = "org.jline:jline-terminal-jansi", version.ref = "jline" } + [plugins] serialization-dsl = { id = "org.jetbrains.kotlin.plugin.serialization", version = "1.9.22" } [bundles] asm = ["asm", "asm-tree", "asm-commons", "asm-util"] -kotlin-plugins = ["kotlin-compiler", "kotlin-serialization"] \ No newline at end of file +kotlin-plugins = ["kotlin-compiler", "kotlin-serialization"] +jline = [ "jline-reader", "jline-terminal", "jline-terminal-jansi" ] \ No newline at end of file diff --git a/gradle-plugin/src/main/kotlin/net/weavemc/gradle/util/DownloadUtil.kt b/internals/src/main/kotlin/net/weavemc/internals/DownloadUtil.kt similarity index 96% rename from gradle-plugin/src/main/kotlin/net/weavemc/gradle/util/DownloadUtil.kt rename to internals/src/main/kotlin/net/weavemc/internals/DownloadUtil.kt index 1d6705a1..91fccfae 100644 --- a/gradle-plugin/src/main/kotlin/net/weavemc/gradle/util/DownloadUtil.kt +++ b/internals/src/main/kotlin/net/weavemc/internals/DownloadUtil.kt @@ -1,4 +1,4 @@ -package net.weavemc.gradle.util +package net.weavemc.internals import java.io.IOException import java.net.URL @@ -46,7 +46,7 @@ object DownloadUtil { * @param url The URL to download from. * @param path The path to download to. */ - private fun download(url: URL, path: Path) { + fun download(url: URL, path: Path) { runCatching { url.openStream().use { input -> Files.createDirectories(path.parent) diff --git a/internals/src/main/kotlin/net/weavemc/internals/MinecraftManifest.kt b/internals/src/main/kotlin/net/weavemc/internals/MinecraftManifest.kt new file mode 100644 index 00000000..a389d60a --- /dev/null +++ b/internals/src/main/kotlin/net/weavemc/internals/MinecraftManifest.kt @@ -0,0 +1,121 @@ +package net.weavemc.internals + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json + +private inline fun String?.decodeJSON() = + if (this != null) manifestDecoder.decodeFromString(this) else null + +val manifestDecoder = Json { ignoreUnknownKeys = true } +const val minecraftManifestURL = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json" + +fun fetchVersionManifest() = DownloadUtil.fetch(minecraftManifestURL).decodeJSON() +fun VersionManifest.fetchVersion(id: String) = versions.find { it.id == id }?.fetch() +fun ManifestVersion.fetch() = DownloadUtil.fetch(url).decodeJSON() + +@Serializable +data class VersionManifest(val versions: List) + +@Serializable +data class ManifestVersion(val id: String, val url: String) + +@Serializable +data class VersionInfo( + val downloads: VersionDownloads, + val libraries: List, + val javaVersion: MinecraftJavaVersion, + val id: String, + val assetIndex: AssetIndex, + val mainClass: String, + val logging: LoggingConfiguration +) + +@Serializable +data class LoggingConfiguration( + val client: SideLoggingConfiguration, + val server: SideLoggingConfiguration? = null +) + +@Serializable +data class SideLoggingConfiguration( + val argument: String, + val file: LoggingFile, + val type: String +) + +@Serializable +data class LoggingFile( + val id: String, + val sha1: String, + val size: Int, + val url: String +) + +@Serializable +data class AssetIndex(val id: String) + +@Serializable +data class MinecraftJavaVersion(val component: String?, val majorVersion: Int) + +@Serializable +data class VersionDownloads(val client: VersionDownload) + +@Serializable +data class VersionDownload(val url: String, val sha1: String) + +val VersionInfo.relevantLibraries get() = libraries.filter { lib -> lib.rules?.all { it.matches } ?: true } + +@Serializable +data class Library( + val downloads: LibraryDownloads, + val name: String, + val rules: List? = null +) + +@Serializable +data class LibraryDownloads( + val artifact: LibraryArtifact? = null, + val classifiers: Map? = null +) + +val LibraryDownloads.natives get() = classifiers?.get("natives-${osIdentifier()}") +val LibraryDownloads.allDownloads get() = listOfNotNull(artifact) + listOfNotNull(natives) + +@Serializable +data class LibraryArtifact( + val path: String, + val sha1: String, + val size: Int, + val url: String +) + +@Serializable +data class OSRule( + val action: String, + val os: OSInfo? = null, +) + +val OSRule.allow + get() = when (action) { + "allow" -> true + "disallow" -> false + else -> error("Invalid action $action") + } + +val OSRule.matches get() = os == null || allow xor (os.name != osIdentifier()) + +@Serializable +data class OSInfo( + val name: String? = null, + val version: String? = null, + val arch: String? = null, +) + +fun osIdentifier() = with(System.getProperty("os.name")) { + when { + startsWith("Mac OS") -> "osx" + startsWith("Linux") -> "linux" + startsWith("Windows") -> "windows" + else -> error("Unsupported platform ($this) (for minecraft OS identification)") + } +} \ No newline at end of file diff --git a/internals/src/main/kotlin/net/weavemc/internals/Util.kt b/internals/src/main/kotlin/net/weavemc/internals/Util.kt index cebdbe6d..59333d26 100644 --- a/internals/src/main/kotlin/net/weavemc/internals/Util.kt +++ b/internals/src/main/kotlin/net/weavemc/internals/Util.kt @@ -1,4 +1,16 @@ package net.weavemc.internals -internal fun String.splitAround(c: Char): Pair = - substringBefore(c) to substringAfter(c, "") \ No newline at end of file +import kotlin.io.path.Path + +internal fun String.splitAround(c: Char): Pair = + substringBefore(c) to substringAfter(c, "") + +fun getMinecraftDir() = Path(System.getProperty("user.home", System.getenv("HOME") ?: System.getenv("USERPROFILE"))) + .resolve(with(System.getProperty("os.name").lowercase()) { + when { + contains("win") -> Path("AppData", "Roaming", ".minecraft") + contains("mac") -> Path("Library", "Application Support", "minecraft") + contains("nix") || contains ("nux") || contains("aix") -> Path(".minecraft") + else -> error("Unsupported platform ($this)") + } + }) \ No newline at end of file diff --git a/loader/src/main/kotlin/net/weavemc/loader/impl/WeaveLoader.kt b/loader/src/main/kotlin/net/weavemc/loader/impl/WeaveLoader.kt index 112912db..3030f3d9 100644 --- a/loader/src/main/kotlin/net/weavemc/loader/impl/WeaveLoader.kt +++ b/loader/src/main/kotlin/net/weavemc/loader/impl/WeaveLoader.kt @@ -193,7 +193,7 @@ public class WeaveLoader( loadAccessWidener(res.readBytes().decodeToString().trim().lines()) .remap(MappingsHandler.mergedMappings.mappings, MappingsHandler.environmentNamespace) - }.join().toTree() + }.takeIf { it.count() > 0 }?.join()?.toTree() ?: return InjectionHandler.registerModifier(object : Modifier { override val namespace = MappingsHandler.environmentNamespace diff --git a/loader/src/main/kotlin/net/weavemc/loader/impl/util/FileManager.kt b/loader/src/main/kotlin/net/weavemc/loader/impl/util/FileManager.kt index 8a0cc3d2..ff847237 100644 --- a/loader/src/main/kotlin/net/weavemc/loader/impl/util/FileManager.kt +++ b/loader/src/main/kotlin/net/weavemc/loader/impl/util/FileManager.kt @@ -2,6 +2,7 @@ package net.weavemc.loader.impl.util import me.xtrm.klog.dsl.klog import net.weavemc.internals.GameInfo +import net.weavemc.internals.getMinecraftDir import java.io.File import java.nio.file.Path import kotlin.io.path.* @@ -10,24 +11,16 @@ internal object FileManager { private val logger by klog val MODS_DIRECTORY = getOrCreateDirectory("mods") val DUMP_DIRECTORY = getOrCreateDirectory(".bytecode.out") + const val weaveDevModsProperty = "weave.devlauncher.mods" fun getVanillaMinecraftJar(): File { logger.trace("Searching for vanilla jar") - val os = System.getProperty("os.name").lowercase() run { - val userHome = System.getProperty("user.home", System.getenv("HOME") ?: System.getenv("USERPROFILE")) - val minecraftPath = when { - os.contains("win") -> Path("AppData", "Roaming", ".minecraft") - os.contains("mac") -> Path("Library", "Application Support", "minecraft") - os.contains("nix") || os.contains("nux") || os.contains("aix") -> Path(".minecraft") - else -> return@run - } - - val fullPath = Path(userHome).resolve(minecraftPath) - val regularPath = fullPath.resolve("versions") + val regularPath = getMinecraftDir().resolve("versions") .resolve(GameInfo.version.versionName) .resolve("${GameInfo.version.versionName}.jar") + if (regularPath.exists()) { return regularPath.toFile() } @@ -49,13 +42,19 @@ internal object FileManager { fun getMods(): List { val mods = mutableListOf() - logger.trace("Searching for mods in $MODS_DIRECTORY") - mods += MODS_DIRECTORY.walkMods() + val weaveDevMods = System.getProperty(weaveDevModsProperty) + if (weaveDevMods != null) { + logger.info("Detected dev mod property set ($weaveDevModsProperty), skipping regular mod loading") + weaveDevMods.splitToSequence(";").mapTo(mods) { ModJar(File(it), false) } + } else { + logger.trace("Searching for mods in $MODS_DIRECTORY") + mods += MODS_DIRECTORY.walkMods() - val specificVersionDirectory = MODS_DIRECTORY.resolve(GameInfo.version.versionName) - if (specificVersionDirectory.exists() && specificVersionDirectory.isDirectory()) { - logger.trace("Searching for mods in $specificVersionDirectory") - mods += specificVersionDirectory.walkMods(true) + val specificVersionDirectory = MODS_DIRECTORY.resolve(GameInfo.version.versionName) + if (specificVersionDirectory.exists() && specificVersionDirectory.isDirectory()) { + logger.trace("Searching for mods in $specificVersionDirectory") + mods += specificVersionDirectory.walkMods(true) + } } logger.info("Discovered ${mods.size} mod files") diff --git a/settings.gradle.kts b/settings.gradle.kts index 86de3ca7..23283313 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,4 +13,11 @@ plugins { } includeBuild("build-logic") -include("loader", "internals", "gradle-plugin") +include( + ":loader", + ":internals", + ":gradle-plugin", + ":gradle-plugin:launcher", +) + +rootProject.name = "Weave"