Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@

package gg.essential.installer.download.util

import kotlinx.serialization.Serializable

/**
* A endpoint which consists of just a full URL and fallback URLs.
*/
@Serializable
data class CompleteURL(
override val primaryURL: String,
override val fallbackURLs: List<String> = emptyList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@

package gg.essential.installer.download.util

import kotlinx.serialization.Serializable

/**
* An interface representing an Endpoint, which consists of a primary URL and fallback URLs
*/
interface Endpoint {
@Serializable
sealed interface Endpoint {

val primaryURL: String
val fallbackURLs: List<String>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,31 @@ import gg.essential.elementa.constraints.*
import gg.essential.elementa.dsl.*
import gg.essential.elementa.effects.ScissorEffect
import gg.essential.elementa.layoutdsl.*
import gg.essential.elementa.state.v2.combinators.map
import gg.essential.elementa.state.v2.memo
import gg.essential.elementa.state.v2.mutableStateOf
import gg.essential.elementa.state.v2.stateOf
import gg.essential.elementa.state.v2.toV1
import gg.essential.elementa.util.onAnimationFrame
import gg.essential.installer.exitInstaller
import gg.essential.installer.gui.*
import gg.essential.installer.gui.component.*
import gg.essential.installer.install.InstallStep
import gg.essential.installer.install.InstallSteps
import gg.essential.installer.install.start
import gg.essential.installer.launchInMainCoroutineScope
import gg.essential.installer.launcher.InstallInfo
import gg.essential.installer.launcher.Launcher
import gg.essential.installer.launcher.Launchers
import gg.essential.installer.logging.Logging.logger
import gg.essential.installer.mod.ModManager
import gg.essential.installer.platform.Platform
import gg.essential.universal.UDesktop
import gg.essential.universal.UScreen
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.awt.Color
import gg.essential.installer.launcher.Installation as LInstallation

/**
* Debug page, of random things I need(ed) during testing
Expand All @@ -58,6 +69,9 @@ object DebugPage : InstallerPage() {
textButton("No Launchers page", ButtonStyle.GRAY, Modifier.width(200f).height(48f)) {
PageHandler.navigateTo(NoLauncherFoundPage)
}
textButton("Install Everything", ButtonStyle.GRAY, Modifier.width(200f).height(48f)) {
PageHandler.navigateTo(InstallEverything)
}
textButton("Restart", ButtonStyle.GRAY, Modifier.width(200f).height(48f)) {
exitInstaller(true)
}
Expand All @@ -82,4 +96,81 @@ object DebugPage : InstallerPage() {
scroller.setVerticalScrollBarComponent(scrollbar)
}

object InstallEverything : InstallerPage() {

private val map = memo {
ModManager.getAvailableMCVersions()().associateWith { ModManager.getAvailableModloaders(stateOf(it))() }
}
private val count = map.map { m -> m.entries.sumOf { it.value.size } }

private val installation = mutableStateOf<InstallStep<*, *>?>(null)

override fun LayoutScope.layoutPage() {

titleAndBody(
"Install everything",
"""
This page allows you to install every single supported version & modloader in bulk.
This will create ${count.getUntracked()} profiles/installs!
Select a modloader on the right to install all possible profiles to it.
""",
modifier = Modifier.alignTopLeft()
)
column(Modifier.width(320f).alignTopRight(), Arrangement.spacedBy(8f, FloatPosition.START)) {
forEach(Launchers.launchers) { launcher ->
launcherButton(launcher)
}
}
val desc = memo {
val inst = installation() ?: return@memo "Not installing..."
val currentStep = inst.currentStep()
val stepsCompleted = inst.stepsCompleted()
val numberOfSteps = inst.numberOfSteps()
"Installing: ${currentStep.id} ($stepsCompleted/$numberOfSteps)"
}
box(Modifier.alignBottomRight()) {
installerText(desc)
}
}

private fun <I : LInstallation, NI : InstallInfo.New, EI : InstallInfo.Edit<I>> LayoutScope.launcherButton(launcher: Launcher<I, NI, EI>) {
button(stateOf(ButtonStyle.GRAY), disabled = installation.map { it != null }, modifier = Modifier.fillWidth().height(96f)) {
row(Modifier.fillWidth(padding = 24f), Arrangement.spacedBy(24f, FloatPosition.START)) {
box(Modifier.width(64f).heightAspect(1f)) {
image(launcher.type.icon, Modifier.fillParent().color(Color.WHITE))
}
installerBoldText(launcher.type.displayName, Modifier.color(InstallerPalette.TEXT))
}
}.onLeftClick {
if (installation.getUntracked() != null) return@onLeftClick
val installationInfos = map.getUntracked().flatMap { (mcVersion, modloaders) ->
modloaders.mapNotNull { modloader ->
val installInfo = launcher.getNewInstallInfo(
"Debug - $mcVersion ${modloader.type.displayName}",
ModManager.getBestModVersion(mcVersion, modloader.type).getUntracked() ?: return@mapNotNull null,
mcVersion,
modloader,
modloader.getBestModloaderVersion(mcVersion).getUntracked() ?: return@mapNotNull null
)
InstallSteps.merge(
modloader.getInstallSteps(installInfo),
launcher.getNewInstallationInstallSteps(installInfo),
)
}
}
val inst = InstallSteps.merge(*installationInfos.toTypedArray()).convertToSingleInstallStep()

installation.set(inst)

launchInMainCoroutineScope {
logger.info("Starting Install")
inst.start()
installation.set(null)
}
}
}


}

}
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,11 @@ class CurseForge(
* The real installer modifies this JSON a bit (removes a few fields), but it shouldn't be a problem if we just include the entire one.
*/
private suspend fun getModloaderJson(installInfo: InstallInfo): JsonObject {
var id = "${installInfo.modloader.type.name.lowercase()}-${installInfo.modloaderVersion.numeric}"
if (installInfo.modloader.type == ModloaderType.FABRIC || installInfo.modloader.type == ModloaderType.QUILT) {
id += "-${installInfo.mcVersion}"
val modloaderType = installInfo.modloader.type
val id = "${modloaderType.name.lowercase()}-" + when (modloaderType) {
ModloaderType.FABRIC, ModloaderType.QUILT -> "${installInfo.modloaderVersion.numeric}-${installInfo.mcVersion}"
ModloaderType.NEOFORGE -> installInfo.modloaderVersion.full
else -> installInfo.modloaderVersion.numeric
}
logger.info("Fetching $id from curseforge.")
val url = MetadataManager.installer.urls.curseforgeModloaderInfo.replace("{id}", id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import java.util.*

@Serializable
data class CurseForgeInstance(
val baseModLoader: Modloader,
val baseModLoader: Modloader?,
val lastPlayed: Instant = Instant.MIN,
// val isVanilla: Boolean, // Removed, since we don't need it, and some instances seem to miss it?
val guid: String = UUID.randomUUID().toString(),
Expand All @@ -44,6 +44,6 @@ data class CurseForgeInstance(
)

val modloaderInfo: ModloaderInfo
get() = ModloaderInfo.fromVersionString(baseModLoader.name)
get() = ModloaderInfo.fromVersionString(baseModLoader?.name ?: gameVersion.toString())

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package gg.essential.installer.launcher.vanilla
import gg.essential.installer.minecraft.MCVersion
import gg.essential.installer.modloader.ModloaderInfo
import gg.essential.installer.modloader.ModloaderType
import gg.essential.installer.modloader.ModloaderVersion
import gg.essential.installer.util.InstantAsIso8601Serializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
Expand All @@ -44,6 +45,10 @@ data class MinecraftInstallationData(
get() = when (ModloaderInfo.fromVersionString(lastVersionId).type) {
ModloaderType.NONE_MODERN -> MCVersion.fromString(lastVersionId)
ModloaderType.FORGE -> MCVersion.fromString(lastVersionId.split('-').first()) // Example: 1.18.2-forge-40.0.12
ModloaderType.NEOFORGE -> {
val numeric = ModloaderVersion.fromVersion(ModloaderType.NEOFORGE, lastVersionId).numeric
MCVersion.fromString("1." + numeric.substring(0..<numeric.lastIndexOf('.')))
} // Example: neoforge-21.5.14-beta
ModloaderType.FABRIC -> MCVersion.fromString(lastVersionId.split('-').last()) // Example: fabric-loader-0.15.3-1.20.4
else -> MCVersion.fromString(lastVersionId, false) // Try parsing non-strictly, to at least get the version hopefully
}
Expand Down
3 changes: 3 additions & 0 deletions installer/src/main/kotlin/gg/essential/installer/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,16 @@ private val height = System.getProperty("ui.height")?.toIntOrNull() ?: 600
private val scaleFactor = System.getProperty("ui.scaleFactor")?.toIntOrNull() ?: 1
private val resizable = System.getProperty("ui.resizable")?.toBoolean() ?: false
private val debug = System.getProperty("installer.debug")?.toBoolean() ?: false
private val noModInstall = System.getProperty("installer.noModInstall")?.toBoolean() ?: false

private lateinit var mainCoroutineScope: CoroutineScope
private var requestRestart = false
private var requestShutdown = false

fun isDebug() = debug

fun isNoModInstallMode() = noModInstall

fun main(args: Array<String>) {
// Most important things to be loaded before anything else is run
// This also sets up log4j's log file property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ data class InstallerMetadata(
val forgeInstaller: String,
val fabric: String,
val fabricFallback: String,
val neoforge: String,
val neoforgeInstaller: String,
val minecraftVersions: String,
val curseforgeModloaderInfo: String,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ sealed interface ModVersionProvider {
@Transient
override val type = "url"
@Transient
override val logger: Logger = LoggerFactory.getLogger("URL Mod Version Provider ($versionURL $downloadInfoURL)")
override val logger: Logger = LoggerFactory.getLogger("URL Mod Version Provider")

override suspend fun getAvailableModVersions(): Map<MCVersion, Map<ModloaderType, ModVersions>> {
return withContext(Dispatchers.IO) {
Expand Down Expand Up @@ -91,8 +91,7 @@ sealed interface ModVersionProvider {

}

val versionsString =
versions.map { entry -> entry.key.toString() + "-" + entry.value.map { it.key.name + "-" + (it.value.latestFeatured ?: it.value.latest).version } }.joinToString("; ")
val versionsString = versions.entries.joinToString("; ") { (mcVersion, map) -> "$mcVersion-[${map.entries.joinToString(",") { (type, versions) -> "$type-${(versions.latestFeatured ?: versions.latest).version}" }}]" }
logger.info("Versions: $versionsString")
versions
}
Expand Down Expand Up @@ -162,8 +161,7 @@ sealed interface ModVersionProvider {
)
}
}
val versionsString =
versionsMap.map { entry -> entry.key.toString() + "-" + entry.value.map { it.key.name + "-" + (it.value.latestFeatured ?: it.value.latest).version } }.joinToString("; ")
val versionsString = versionsMap.entries.joinToString("; ") { (mcVersion, map) -> "$mcVersion-[${map.entries.joinToString(",") { (type, versions) -> "$type-${(versions.latestFeatured ?: versions.latest).version}" }}]" }
logger.info("Versions: $versionsString")
versionsMap
}
Expand Down
17 changes: 17 additions & 0 deletions installer/src/main/kotlin/gg/essential/installer/mod/ModManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ package gg.essential.installer.mod

import gg.essential.elementa.state.v2.State
import gg.essential.elementa.state.v2.combinators.map
import gg.essential.elementa.state.v2.filter
import gg.essential.elementa.state.v2.memo
import gg.essential.elementa.state.v2.mutableStateOf
import gg.essential.elementa.state.v2.toListState
import gg.essential.installer.download.DownloadRequest
import gg.essential.installer.download.util.DownloadInfo
import gg.essential.installer.install.InstallSteps
import gg.essential.installer.install.installationStep
import gg.essential.installer.isNoModInstallMode
import gg.essential.installer.launcher.InstallInfo
import gg.essential.installer.logging.Logging.logger
import gg.essential.installer.metadata.BRAND
Expand All @@ -31,6 +34,7 @@ import gg.essential.installer.metadata.MetadataManager
import gg.essential.installer.metadata.VERSION
import gg.essential.installer.metadata.data.ModMetadata
import gg.essential.installer.minecraft.MCVersion
import gg.essential.installer.modloader.Modloader
import gg.essential.installer.modloader.ModloaderType
import gg.essential.installer.platform.Platform
import kotlinx.serialization.ExperimentalSerializationApi
Expand Down Expand Up @@ -68,6 +72,16 @@ object ModManager {

suspend fun loadModVersionsAndMetadata() {
logger.info("Loading mod versions and metadata!")

if (isNoModInstallMode()) {
logger.warn("Running in no mod install mode! This means mod versions will not actually be loaded!")
MCVersion.refreshKnownMcVersions() // Hack, since this is otherwise refreshed after this method...
val version = ModVersion("", "", DownloadInfo("", "", true))
val map = Modloader.entries.associate { it.type to ModVersions(version, null, listOf(version)) }
availableVersions.set(MCVersion.knownVersions.filter { it >= MCVersion(8, 9) }.getUntracked().associateWith { map })
return
}

val dataProviders = MetadataManager.dataProviders

logger.debug("Version provider: {}", dataProviders.modVersionProviderStrategy)
Expand Down Expand Up @@ -189,6 +203,9 @@ object ModManager {

@OptIn(ExperimentalSerializationApi::class)
fun getInstallSteps(installInfo: InstallInfo): InstallSteps {
if (isNoModInstallMode()) {
return InstallSteps()
}
val modVersion = installInfo.modVersion
val downloadInfo = modVersion.downloadInfo
val filename = if(modVersion.version.isBlank()) "$BRAND-${installInfo.mcVersion}.jar" else "$BRAND-${modVersion.version}-${installInfo.mcVersion}.jar"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,39 +77,6 @@ object ForgeModloader : Modloader(ModloaderType.FORGE) {
val libraries: List<ModernMinecraftVersionProfile.Library>? = null,
)

/**
* Represents the json format of the Minecraft launcher's modern version profiles
*
* It does not include the full spec, merely what is (or is thought to be) required by the installer to function
* Due to the missing fields, these should never be serialized to a file directly from these object.
* You should always use the full/raw version profile json provided by the modloader.
*/
@Serializable
data class ModernMinecraftVersionProfile(
val id: MinecraftVersionProfileId,
val inheritsFrom: String,
val releaseTime: String,
val time: String,
val type: String,
val mainClass: String,
val arguments: Arguments,
val libraries: List<Library>,
) {

@Serializable
data class Arguments(val game: List<String>, val jvm: List<String>)

@Serializable
data class Library(val name: String, val downloads: Downloads)

@Serializable
data class Downloads(val artifact: Artifact)

@Serializable
data class Artifact(val path: String? = null, val url: String?, val sha1: String, val size: Long)

}

override fun getMinecraftVersionProfileId(mcVersion: MCVersion, modloaderVersion: ModloaderVersion): MinecraftVersionProfileId {
// Old forge has been very inconsistent with names for versions, this format is how modern versions format it, with "-wrapper" at the end to not mess with normal forge
return "$mcVersion-forge-${modloaderVersion.numeric}-wrapper".replace(Regex("-{2,}"), "-")
Expand Down
Loading
Loading