summaryrefslogtreecommitdiff
path: root/plugin/src/main/kotlin/moe/nea/archenemy/mojang/MinecraftProvider.kt
blob: 64c1d04ba62574431fff11aed52c4df09de14330 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package moe.nea.archenemy.mojang

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import moe.nea.archenemy.DownloadUtils
import moe.nea.archenemy.MCSide
import moe.nea.archenemy.util.getNullsafeIdentifier
import net.minecraftforge.artifactural.api.artifact.Artifact
import net.minecraftforge.artifactural.api.artifact.ArtifactIdentifier
import net.minecraftforge.artifactural.api.artifact.ArtifactType
import net.minecraftforge.artifactural.api.artifact.Streamable
import net.minecraftforge.artifactural.api.repository.Repository
import net.minecraftforge.artifactural.base.artifact.StreamableArtifact
import java.io.File
import java.io.IOException
import java.net.URL
import java.security.MessageDigest
import java.util.concurrent.ConcurrentHashMap

fun MessageDigest.updateField(text: String, value: String) {
    this.update(text)
    this.update(":")
    this.update(value)
    this.update(";")
}

fun MessageDigest.update(text: String) {
    this.update(text.encodeToByteArray())
}

class MinecraftProvider(val sharedExtension: ArchenemySharedExtension) : Repository {

    data class MinecraftCoordinate(
        val version: String,
        val side: MCSide,
    )

    private val manifest by lazy {
        URL("https://launchermeta.mojang.com/mc/game/version_manifest.json").openStream().use {
            Json.decodeFromStream<MojangVersionManifest>(it)
        }
    }
    private val providers = mutableSetOf<MinecraftCoordinate>()
    private val versionManifest: MutableMap<String, MojangVersionMetadata> = ConcurrentHashMap()
    private val json = Json {
        ignoreUnknownKeys = true
    }

    fun getDependencyCoordinate(minecraftCoordinate: MinecraftCoordinate): String {
        providers.add(minecraftCoordinate)
        return "archenemy.mojang:minecraft:${minecraftCoordinate.version}:${minecraftCoordinate.side}"
    }

    fun getMappingsDependencyCoordinate(minecraftCoordinate: MinecraftCoordinate): String {
        providers.add(minecraftCoordinate)
        return "archenemy.mojang:minecraft:${minecraftCoordinate.version}:${minecraftCoordinate.side}-mappings@txt"
    }


    private fun getVersionManifest(version: String): MojangVersionMetadata {
        return versionManifest.computeIfAbsent(version) {
            val versionMetadata = manifest.versions.find { it.id == version }
            if (versionMetadata == null)
                throw IOException("Invalid minecraft version $version")
            val metadata = URL(versionMetadata.url).openStream().use {
                json.decodeFromStream<MojangVersionMetadata>(it)
            }
            metadata
        }
    }

    private fun downloadMinecraft(coordinate: MinecraftCoordinate, mappings: Boolean): File {
        val metadata = getVersionManifest(coordinate.version)
        val downloadType = when (coordinate.side) {
            MCSide.CLIENT -> "client"
            MCSide.SERVER -> "server"
        } + if (mappings) "_mappings" else ""
        val download = metadata.downloads[downloadType]
            ?: throw IOException("Invalid minecraft side $downloadType for ${coordinate.version}")
        val targetFile =
            sharedExtension.getLocalCacheDirectory().resolve("minecraft-raw")
                .resolve("minecraft-${coordinate.version}-${coordinate.side}.${if (mappings) "txt" else "jar"}")
        DownloadUtils.downloadFile(URL(download.url), download.sha1, targetFile)
        return targetFile
    }

    private fun provideStreamableMinecraftJar(coordinate: MinecraftCoordinate): Streamable {
        return Streamable {
            downloadMinecraft(coordinate, false).inputStream()
        }
    }

    private fun provideStreamableMappings(coordinate: MinecraftCoordinate): Streamable {
        return Streamable {
            downloadMinecraft(coordinate, true).inputStream()
        }
    }


    override fun getArtifact(identifier: ArtifactIdentifier?): Artifact {
        if (identifier == null) return Artifact.none()
        if (identifier.name != "minecraft") return Artifact.none()
        if (identifier.group != "archenemy.mojang") return Artifact.none()
        if (identifier.extension == "pom") return Artifact.none()
        val coordinate =
            MinecraftCoordinate(identifier.version, MCSide.valueOf(identifier.classifier.removeSuffix("-mappings")))
        val isMappings = identifier.classifier.endsWith("-mappings")
        if (!providers.contains(coordinate))
            error("Unregistered minecraft dependency")
        if (identifier.extension == "jar" && !isMappings) {
            return StreamableArtifact.ofStreamable(
                getNullsafeIdentifier(identifier),
                ArtifactType.BINARY,
                provideStreamableMinecraftJar(coordinate)
            )
        }
        if (identifier.extension == "txt" && isMappings) {
            return StreamableArtifact.ofStreamable(
                getNullsafeIdentifier(identifier),
                ArtifactType.OTHER,
                provideStreamableMappings(coordinate)
            )
        }
        return Artifact.none()
    }
}