Skip to content

Commit c39acda

Browse files
committed
IT FUCKING WORKS WOOOOOOOOOOOOOOOOOOOOOOOOO
1 parent 1c336e6 commit c39acda

File tree

11 files changed

+291
-35
lines changed

11 files changed

+291
-35
lines changed

sploon-plugin/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
plugins {
22
kotlin("jvm") version "2.0.20"
3+
kotlin("plugin.serialization") version "2.0.20"
4+
id("com.gradleup.shadow") version "8.3.5"
35
`java-gradle-plugin`
46
}
57

@@ -22,4 +24,9 @@ gradlePlugin {
2224

2325
repositories {
2426
mavenCentral()
27+
}
28+
29+
dependencies {
30+
implementation("io.sigpipe:jbsdiff:1.0")
31+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
2532
}
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
package io.github.sploonmc.sploon
22

33
import io.github.sploonmc.sploon.ext.SploonDependenciesExt
4-
import io.github.sploonmc.sploon.minecraft.MinecraftVersion
4+
import io.github.sploonmc.sploon.piston.getPistonVersions
55
import org.gradle.api.Plugin
66
import org.gradle.api.Project
77
import org.gradle.api.problems.ProblemReporter
88
import org.gradle.api.problems.Problems
99
import javax.inject.Inject
1010

1111
abstract class SploonPlugin @Inject constructor(val problems: Problems) : Plugin<Project> {
12-
override fun apply(target: Project) {
12+
override fun apply(project: Project) {
1313
val reporter: ProblemReporter = problems.forNamespace(SPLOON_NAME)
1414

15-
target.dependencies.extensions.create(SPLOON_NAME, SploonDependenciesExt::class.java, target.dependencies, reporter)
15+
project.dependencies.extensions.create("sploon", SploonDependenciesExt::class.java, project, reporter)
16+
}
17+
18+
companion object {
19+
val PISTON_VERSIONS = getPistonVersions()
1620
}
1721
}

sploon-plugin/src/main/kotlin/io/github/sploonmc/sploon/ext/SploonDependenciesExt.kt

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,21 @@ package io.github.sploonmc.sploon.ext
33
import io.github.sploonmc.sploon.SPLOON_NAME
44
import io.github.sploonmc.sploon.minecraft.MappingType
55
import io.github.sploonmc.sploon.minecraft.MinecraftVersion
6+
import io.github.sploonmc.sploon.patcher.Patcher
7+
import org.gradle.api.Project
68
import org.gradle.api.artifacts.dsl.DependencyHandler
9+
import org.gradle.api.internal.artifacts.dependencies.DefaultFileCollectionDependency
10+
import org.gradle.api.invocation.Gradle
711
import org.gradle.api.problems.ProblemReporter
812
import org.gradle.api.problems.Severity
913

1014
abstract class SploonDependenciesExt(
11-
private val dependencies: DependencyHandler,
15+
private val project: Project,
1216
private val problemReporter: ProblemReporter,
1317
) {
18+
val dependencies = project.dependencies
19+
val gradle = project.gradle
20+
1421
/**
1522
* Adds the Spigot API dependency. This does not include CraftBukkit internals or Minecraft code (often referred to as NMS)
1623
* @param version The version of minecraft to download the Spigot API for.
@@ -21,7 +28,8 @@ abstract class SploonDependenciesExt(
2128
problemReporter.reportInvalidVersion(string)
2229
return
2330
}
24-
if (mcVersion !in MinecraftVersion.VERSIONS) {
31+
32+
if (!MinecraftVersion.validateVersion(mcVersion)) {
2533
problemReporter.reportInvalidVersion(string)
2634
return
2735
}
@@ -33,12 +41,21 @@ abstract class SploonDependenciesExt(
3341
problemReporter.reportInvalidVersion(string)
3442
return
3543
}
36-
if (mcVersion !in MinecraftVersion.VERSIONS) {
44+
45+
if (!MinecraftVersion.validateVersion(mcVersion)) {
3746
problemReporter.reportInvalidVersion(string)
3847
return
3948
}
4049

41-
println("adding mc internals and spigot for mc $mcVersion with mappings ${mapping::class.simpleName}")
50+
val patcher = Patcher(mcVersion, gradle)
51+
patcher.download()
52+
println("Patcher downloaded")
53+
54+
if (!patcher.spigotHashMatches) patcher.patch()
55+
56+
println("Patcher finished")
57+
58+
dependencies.add("compileOnly", dependencies.create(project.files(patcher.spigotJar.toFile())))
4259
}
4360

4461
private fun ProblemReporter.reportInvalidVersion(input: String) {
@@ -48,7 +65,7 @@ abstract class SploonDependenciesExt(
4865
.severity(Severity.ERROR)
4966
.contextualLabel("invalid minecraft version")
5067
.solution("Please enter a valid Minecraft version, such as 1.21.3. Has Spigot updated yet?")
51-
.withException(MinecraftVersion.InvalidMinecraftVersion(input))
68+
.withException(MinecraftVersion.InvalidMinecraftVersionException(input))
5269
.details("The given Minecraft version is invalid and could not be found on Spigot.")
5370
}
5471
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
package io.github.sploonmc.sploon
22

3-
const val SPLOON_NAME = "sploon"
3+
const val SPLOON_NAME = "sploon"
4+
const val JAR_VERSIONS_PATH = "META-INF/versions/"
Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package io.github.sploonmc.sploon.minecraft
22

3+
import io.github.sploonmc.sploon.getUri
4+
import io.github.sploonmc.sploon.patcher.Patcher
35
import java.net.URI
4-
import java.net.http.HttpClient
5-
import java.net.http.HttpRequest
6-
import java.net.http.HttpResponse
76
import java.util.regex.Pattern
87

98
data class MinecraftVersion(val major: Int, val minor: Int, val rev: Int) {
@@ -21,35 +20,14 @@ data class MinecraftVersion(val major: Int, val minor: Int, val rev: Int) {
2120
return 21
2221
}
2322

24-
class InvalidMinecraftVersion(input: String) : RuntimeException("Invalid Minecraft version: $input - Has Spigot updated yet?")
23+
class InvalidMinecraftVersionException(input: String) : RuntimeException("Invalid Minecraft version: $input - Has Spigot updated yet?")
2524

2625
companion object {
2726
fun fromString(input: String) = runCatching {
2827
val numbers = input.split(".").map(String::toInt)
2928
MinecraftVersion(numbers.getOrNull(0) ?: 0, numbers.getOrNull(1) ?: 0, numbers.getOrNull(2) ?: 0)
3029
}.getOrNull()
3130

32-
private fun getVersions(): List<MinecraftVersion> {
33-
val uri = URI("https://hub.spigotmc.org/versions/")
34-
val versionPattern = Pattern.compile("([0-9]+\\.[0-9]+\\.[0-9]+|[0-9]+\\.[0-9]+)")
35-
36-
val response = HttpClient.newHttpClient().send(
37-
HttpRequest.newBuilder()
38-
.uri(uri)
39-
.GET()
40-
.build(),
41-
HttpResponse.BodyHandlers.ofString()
42-
).body()
43-
44-
val matcher = versionPattern.matcher(response)
45-
46-
return buildList {
47-
while (matcher.find()) {
48-
fromString(matcher.group())?.let(::add)
49-
}
50-
}
51-
}
52-
53-
val VERSIONS = getVersions().toSet()
31+
fun validateVersion(version: MinecraftVersion) = runCatching { getUri(URI("${Patcher.PATCH_REPO_BASE_URL}/$version.json")) }.isSuccess
5432
}
5533
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.github.sploonmc.sploon.patcher
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class PatchedVersionMeta(
8+
@SerialName("patch_file") val patchFile: String,
9+
@SerialName("commit_hashes") val commitHashes: CommitHashes,
10+
@SerialName("patch_hash") val patchHash: String,
11+
@SerialName("vanilla_jar_hash") val vanillaJarHash: String,
12+
@SerialName("patched_jar_hash") val patchedJarHash: String,
13+
@SerialName("vanilla_download_url") val vanillaDownloadUrl: String
14+
)
15+
16+
@Serializable
17+
data class CommitHashes(
18+
@SerialName("BuildData") val buildData: String,
19+
@SerialName("Bukkit") val bukkit: String,
20+
@SerialName("CraftBukkit") val craftBukkit: String,
21+
@SerialName("Spigot") val spigot: String
22+
)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package io.github.sploonmc.sploon.patcher
2+
3+
import io.github.sploonmc.sploon.JAR_VERSIONS_PATH
4+
import io.github.sploonmc.sploon.JSON
5+
import io.github.sploonmc.sploon.SploonPlugin.Companion.PISTON_VERSIONS
6+
import io.github.sploonmc.sploon.downloadUri
7+
import io.github.sploonmc.sploon.extractJar
8+
import io.github.sploonmc.sploon.getUri
9+
import io.github.sploonmc.sploon.minecraft.MinecraftVersion
10+
import io.github.sploonmc.sploon.piston.getVersionMeta
11+
import io.github.sploonmc.sploon.sha1
12+
import io.sigpipe.jbsdiff.Patch
13+
import org.gradle.api.invocation.Gradle
14+
import java.net.URI
15+
import java.util.jar.JarFile
16+
import kotlin.io.path.Path
17+
import kotlin.io.path.deleteIfExists
18+
import kotlin.io.path.exists
19+
import kotlin.io.path.outputStream
20+
import kotlin.io.path.readBytes
21+
import kotlin.io.path.readText
22+
23+
class Patcher(val version: MinecraftVersion, gradle: Gradle) {
24+
val cacheDir = Path(gradle.gradleUserHomeDir.path, "caches/sploon")
25+
val versionMeta = PISTON_VERSIONS.getVersionMeta(version)
26+
val patchedMeta = JSON.decodeFromString<PatchedVersionMeta>(getUri(URI("$PATCH_REPO_BASE_URL/$version.json")))
27+
val vanillaJar = cacheDir.resolve("vanilla/$version.jar")
28+
val patch = cacheDir.resolve("patches/$version.patch")
29+
val spigotJar = cacheDir.resolve("spigot/$version.jar")
30+
val vanillaHashMatches = if (vanillaJar.exists()) vanillaJar.sha1() == patchedMeta.vanillaJarHash else false
31+
val patchHashMatches = if (patch.exists()) patch.sha1() == patchedMeta.patchHash else false
32+
val spigotHashMatches = if (spigotJar.exists()) spigotJar.sha1() == patchedMeta.patchedJarHash else false
33+
val isCached = vanillaHashMatches && patchHashMatches && spigotHashMatches
34+
35+
fun download() {
36+
if (isCached) return
37+
38+
vanillaJar.toFile().parentFile.mkdirs()
39+
patch.toFile().parentFile.mkdirs()
40+
spigotJar.toFile().parentFile.mkdirs()
41+
42+
if (!vanillaHashMatches) downloadUri(URI(patchedMeta.vanillaDownloadUrl), vanillaJar)
43+
if (!patchHashMatches) downloadUri(URI("$PATCH_REPO_BASE_URL/$version.patch"), patch)
44+
}
45+
46+
fun patch() {
47+
val vanillaJarArchive = JarFile(vanillaJar.toFile())
48+
val needsExtraction = vanillaJarArchive.getJarEntry(JAR_VERSIONS_PATH) != null
49+
val extractedVanillaJar = if (needsExtraction) {
50+
println("Vanilla jar needs extracting")
51+
val vanillaJarExtractionPath = cacheDir.resolve("vanilla/extracted/$version")
52+
vanillaJarExtractionPath.deleteIfExists()
53+
vanillaJarExtractionPath.toFile().mkdirs()
54+
55+
extractJar(vanillaJar, vanillaJarExtractionPath)
56+
57+
val extractionPath = vanillaJarExtractionPath.resolve(JAR_VERSIONS_PATH)
58+
val versionsFilePath = vanillaJarExtractionPath.resolve("META-INF/versions.list")
59+
60+
val fileContent = runCatching(versionsFilePath::readText).getOrNull()
61+
62+
if (fileContent == null) {
63+
println("Failed reading versions.list. Using $vanillaJar instead.")
64+
vanillaJar
65+
} else {
66+
extractionPath.resolve(fileContent.split("\t")[2])
67+
}
68+
} else {
69+
println("Vanilla jar does not need extracting")
70+
vanillaJar
71+
}
72+
73+
val vanillaBytes = extractedVanillaJar.readBytes()
74+
val patchBytes = patch.readBytes()
75+
spigotJar.outputStream().use { stream -> Patch.patch(vanillaBytes, patchBytes, stream) }
76+
}
77+
78+
companion object {
79+
const val PATCH_REPO_BASE_URL = "https://raw.githubusercontent.com/SploonMC/patches/refs/heads/master"
80+
}
81+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.github.sploonmc.sploon.piston
2+
3+
import io.github.sploonmc.sploon.getUriJson
4+
import io.github.sploonmc.sploon.minecraft.MinecraftVersion
5+
import java.net.URI
6+
7+
private const val PISTON_META_URL = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
8+
9+
fun getPistonVersions() = getUriJson<PistonVersionsResponse>(URI(PISTON_META_URL))
10+
11+
fun PistonVersionsResponse.getVersionMeta(version: MinecraftVersion) = getUriJson<PistonVersionMeta>(
12+
URI(
13+
versions.find { pistonVersion -> pistonVersion.id == version.toString() }?.url
14+
?: throw MinecraftVersion.InvalidMinecraftVersionException(version.toString())
15+
)
16+
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.github.sploonmc.sploon.piston
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class PistonVersion(
7+
val id: String,
8+
val url: String,
9+
)
10+
11+
@Serializable
12+
data class PistonVersionsResponse(
13+
val versions: List<PistonVersion>
14+
)
15+
16+
@Serializable
17+
data class PistonVersionDownload(
18+
val url: String
19+
)
20+
21+
@Serializable
22+
data class PistonVersionDownloads(
23+
val server: PistonVersionDownload
24+
)
25+
26+
@Serializable
27+
data class PistonVersionMeta(
28+
val downloads: PistonVersionDownloads
29+
)

0 commit comments

Comments
 (0)