Skip to content

Commit

Permalink
feat: Add minecraft crashed dialog
Browse files Browse the repository at this point in the history
fix: Updating mod loader without showing user update prompt
fix: Not unzipping mrpack when refreshing an already downloaded pack
chore: Use mod ids in config storage, not names
  • Loading branch information
0ffz committed Mar 31, 2024
1 parent f2bd683 commit f4d9dc3
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.charleskorn.kaml.decodeFromStream
import com.mineinabyss.launchy.data.Formats
import com.mineinabyss.launchy.data.GroupName
import com.mineinabyss.launchy.data.ModID
import com.mineinabyss.launchy.data.ModName
import com.mineinabyss.launchy.data.modpacks.InstanceModLoaders
import com.mineinabyss.launchy.logic.ModDownloader
import com.mineinabyss.launchy.logic.hashing.Hashing.checksum
Expand Down Expand Up @@ -47,8 +46,8 @@ data class InstanceUserConfig(
val userAgreedDeps: InstanceModLoaders? = null,
val fullEnabledGroups: Set<GroupName> = setOf(),
val fullDisabledGroups: Set<GroupName> = setOf(),
val toggledMods: Set<ModName> = setOf(),
val toggledConfigs: Set<ModName> = setOf(),
val toggledMods: Set<ModID> = setOf(),
val toggledConfigs: Set<ModID> = setOf(),
val seenGroups: Set<GroupName> = setOf(),
val modDownloadInfo: Map<ModID, DownloadInfo> = mapOf(),
// val configDownloadInfo: Map<ModID, DownloadInfo> = mapOf(),
Expand Down
8 changes: 4 additions & 4 deletions src/main/kotlin/com/mineinabyss/launchy/data/modpacks/Mods.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.mineinabyss.launchy.data.modpacks

import com.mineinabyss.launchy.data.GroupName
import com.mineinabyss.launchy.data.ModName
import com.mineinabyss.launchy.data.ModID

data class Mods(
val modGroups: Map<Group, Set<Mod>>,
Expand All @@ -10,12 +10,12 @@ data class Mods(
val mods = modGroups.values.flatten().toSet()

private val nameToGroup: Map<GroupName, Group> = groups.associateBy { it.name }
private val nameToMod: Map<ModName, Mod> = modGroups.values
private val idToMod: Map<ModID, Mod> = modGroups.values
.flatten()
.associateBy { it.info.name }
.associateBy { it.modId }

//
fun getMod(name: ModName): Mod? = nameToMod[name]
fun getModById(id: ModID): Mod? = idToMod[id]
fun getGroup(name: GroupName): Group? = nameToGroup[name]

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,8 @@ sealed class PackSource {
override suspend fun updateInstance(instance: GameInstance): Result<GameInstance> {
return runCatching {
val downloadTo = type.getFilePath(instance.configDir)
Downloader.download(url, downloadTo, whenChanged = {
type.afterDownload(instance.configDir)
})
Downloader.download(url, downloadTo)
type.afterDownload(instance.configDir)
GameInstance(instance.configDir)
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/kotlin/com/mineinabyss/launchy/logic/Launcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ object Launcher {

override fun onExit(p0: Int) {
println("Exited with state $p0")

when (p0) {
255 -> dialog = Dialog.Error("Minecraft crashed!", "See logs for more info.")
}
state.setProcessFor(pack.instance, null)
}

Expand Down
47 changes: 28 additions & 19 deletions src/main/kotlin/com/mineinabyss/launchy/logic/ModDownloader.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mineinabyss.launchy.logic

import com.mineinabyss.launchy.data.ModID
import com.mineinabyss.launchy.data.config.DownloadInfo
import com.mineinabyss.launchy.data.config.HashCheck
import com.mineinabyss.launchy.data.modpacks.InstanceModLoaders
Expand Down Expand Up @@ -92,9 +93,9 @@ object ModDownloader {
* Primarily the mod loader/minecraft version.
*/
suspend fun GameInstanceState.ensureDependenciesReady(state: LaunchyState) = coroutineScope {
val currentDeps = userAgreedModLoaders
val currentDeps = queued.userAgreedModLoaders
if (currentDeps == null) {
userAgreedModLoaders = modpack.modLoaders
queued.userAgreedModLoaders = modpack.modLoaders
}
installMCAndModLoaders(state, currentDeps ?: modpack.modLoaders)
}
Expand Down Expand Up @@ -140,12 +141,30 @@ object ModDownloader {
}
}

suspend fun GameInstanceState.checkHashes(
state: Map<ModID, DownloadInfo>
): List<Pair<ModID, DownloadInfo>> = coroutineScope {
state.map { (modId, info) ->
async(AppDispatchers.IOContext) {
val check = runCatching { info.calculateSha1Hash(instance.minecraftDir) }.getOrNull()
modId to info.copy(
hashCheck = when {
check == info.desiredHash -> HashCheck.VERIFIED
else -> HashCheck.FAILED
}
)
}
}.awaitAll()
}

/**
* Updates mod loader versions and mods to latest modpack definition.
*/
suspend fun GameInstanceState.startInstall(state: LaunchyState, ignoreCachedCheck: Boolean = false): Result<*> =
coroutineScope {
userAgreedModLoaders = modpack.modLoaders
suspend fun GameInstanceState.startInstall(
state: LaunchyState,
ignoreCachedCheck: Boolean = false
): Result<*> = coroutineScope {
queued.userAgreedModLoaders = modpack.modLoaders
ensureDependenciesReady(state)
copyOverrides(state)

Expand All @@ -170,19 +189,9 @@ object ModDownloader {
}

// Check hashes
val updatedHashes = queued.modDownloadInfo
.filterValues { it.hashCheck == HashCheck.UNKNOWN || it.hashCheck == HashCheck.FAILED }
.map { (modId, info) ->
async(AppDispatchers.IOContext) {
val check = runCatching { info.calculateSha1Hash(instance.minecraftDir) }.getOrNull()
modId to info.copy(
hashCheck = when {
check == info.desiredHash -> HashCheck.VERIFIED
else -> HashCheck.FAILED
}
)
}
}.awaitAll()
val updatedHashes = checkHashes(queued.modDownloadInfo
.filterValues { it.hashCheck == HashCheck.UNKNOWN || it.hashCheck == HashCheck.FAILED })


updatedHashes.forEach { (modId, newInfo) ->
queued.modDownloadInfo[modId] = newInfo
Expand All @@ -198,6 +207,6 @@ object ModDownloader {

saveToConfig()

return@coroutineScope Result.success(Unit)
return@coroutineScope Result.success(Unit)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.mineinabyss.launchy.state.modpack

import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.*
import com.mineinabyss.launchy.data.ModID
import com.mineinabyss.launchy.data.config.DownloadInfo
import com.mineinabyss.launchy.data.config.InstanceUserConfig
Expand All @@ -15,7 +13,8 @@ class DownloadQueueState(
) {
/** Live mod download info, including mods that have been removed from the latest modpack version. */
val modDownloadInfo = mutableStateMapOf<ModID, DownloadInfo>().apply {
putAll(userConfig.modDownloadInfo)
val availableIds = toggles.availableMods.map { it.modId }
putAll(userConfig.modDownloadInfo.filter { it.key in availableIds })
}

/** Mods whose download url matches a previously downloaded url and exist on the filesystem */
Expand Down Expand Up @@ -44,9 +43,11 @@ class DownloadQueueState(
}

val areModLoaderUpdatesAvailable by derivedStateOf {
modpack.modLoaders != userConfig.userAgreedDeps
modpack.modLoaders != userAgreedModLoaders
}

var userAgreedModLoaders by mutableStateOf(userConfig.userAgreedDeps)

val needsInstall by derivedStateOf { updates + newDownloads + failures }

val areUpdatesQueued by derivedStateOf { updates.isNotEmpty() }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.mineinabyss.launchy.state.modpack

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.mineinabyss.launchy.data.config.GameInstance
import com.mineinabyss.launchy.data.config.InstanceUserConfig
import com.mineinabyss.launchy.data.modpacks.Modpack
Expand All @@ -15,16 +12,15 @@ class GameInstanceState(
val toggles: ModTogglesState = ModTogglesState(modpack, userConfig)
val queued = DownloadQueueState(userConfig, modpack, toggles)
val downloads = DownloadState()
var userAgreedModLoaders by mutableStateOf(userConfig.userAgreedDeps)

fun saveToConfig() {
userConfig.copy(
fullEnabledGroups = modpack.mods.modGroups
.filter { toggles.enabledMods.containsAll(it.value) }.keys
.map { it.name }.toSet(),
userAgreedDeps = userAgreedModLoaders,
toggledMods = toggles.enabledMods.mapTo(mutableSetOf()) { it.info.name },
toggledConfigs = toggles.enabledConfigs.mapTo(mutableSetOf()) { it.info.name } + toggles.enabledMods.filter { it.info.forceConfigDownload }
userAgreedDeps = queued.userAgreedModLoaders,
toggledMods = toggles.enabledMods.mapTo(mutableSetOf()) { it.modId },
toggledConfigs = toggles.enabledConfigs.mapTo(mutableSetOf()) { it.modId } + toggles.enabledMods.filter { it.info.forceConfigDownload }
.mapTo(mutableSetOf()) { it.info.name },
seenGroups = modpack.mods.groups.map { it.name }.toSet(),
modDownloadInfo = queued.modDownloadInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ class ModTogglesState(
val modpack: Modpack,
val modpackConfig: InstanceUserConfig
) {
val availableMods = mutableStateSetOf<Mod>().apply {
addAll(modpack.mods.mods)
}
val enabledMods = mutableStateSetOf<Mod>().apply {
addAll(modpackConfig.toggledMods.mapNotNull { modpack.mods.getMod(it) })
addAll(modpackConfig.toggledMods.mapNotNull { modpack.mods.getModById(it) })
val defaultEnabled = modpack.mods.groups
.filter { it.enabledByDefault }
.map { it.name } - modpackConfig.seenGroups
Expand All @@ -35,7 +38,7 @@ class ModTogglesState(
}

val enabledConfigs: MutableSet<Mod> = mutableStateSetOf<Mod>().apply {
addAll(modpackConfig.toggledConfigs.mapNotNull { modpack.mods.getMod(it) })
addAll(modpackConfig.toggledConfigs.mapNotNull { modpack.mods.getModById(it) })
}

init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ fun PlayButton(
if (process == null) {
when {
// Assume this means not launched before
packState.userAgreedModLoaders == null -> {
packState.queued.userAgreedModLoaders == null -> {
AppDispatchers.profileLaunch.launchOrShowDialog {
packState.startInstall(state)
Launcher.launch(state, packState, state.profile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ fun InfoBar(modifier: Modifier = Modifier) {
InstallButton(
state.processFor(packState.instance) == null
&& !packState.downloads.isDownloading
&& (packState.queued.areOperationsQueued || packState.userAgreedModLoaders == null)
&& (packState.queued.areOperationsQueued || packState.queued.userAgreedModLoaders == null)
&& state.inProgressTasks.isEmpty(),
Modifier.width(Constants.SETTINGS_PRIMARY_BUTTON_WIDTH)
)
Expand All @@ -60,7 +60,7 @@ fun InfoBar(modifier: Modifier = Modifier) {
ActionButton(
shown = packState.queued.areModLoaderUpdatesAvailable,
icon = Icons.Rounded.HistoryEdu,
desc = "Mod loader updates:\n${packState.userAgreedModLoaders?.fullVersionName ?: "Not installed"} -> ${packState.modpack.modLoaders.fullVersionName}",
desc = "Mod loader updates:\n${packState.queued.userAgreedModLoaders?.fullVersionName ?: "Not installed"} -> ${packState.modpack.modLoaders.fullVersionName}",
count = 1
)
ActionButton(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,17 @@ import com.mineinabyss.launchy.data.Constants.SETTINGS_HORIZONTAL_PADDING
import com.mineinabyss.launchy.data.modpacks.Group
import com.mineinabyss.launchy.data.modpacks.Mod
import com.mineinabyss.launchy.data.modpacks.ModConfig
import com.mineinabyss.launchy.logic.AppDispatchers
import com.mineinabyss.launchy.logic.DesktopHelpers
import com.mineinabyss.launchy.logic.Instances.delete
import com.mineinabyss.launchy.logic.Instances.updateInstance
import com.mineinabyss.launchy.logic.ModDownloader.checkHashes
import com.mineinabyss.launchy.state.InProgressTask
import com.mineinabyss.launchy.ui.elements.*
import com.mineinabyss.launchy.ui.screens.LocalGameInstanceState
import com.mineinabyss.launchy.ui.screens.Screen
import com.mineinabyss.launchy.ui.screens.screen
import kotlinx.coroutines.launch
import kotlin.io.path.listDirectoryEntries

@Composable
Expand Down Expand Up @@ -110,6 +114,17 @@ fun OptionsTab() {
OutlinedButton(onClick = { DesktopHelpers.openDirectory(pack.instance.minecraftDir) }) {
Text("Open .minecraft folder")
}
OutlinedButton(onClick = {
AppDispatchers.IO.launch {
state.runTask("checkHashes", InProgressTask("Checking hashes")) {
pack.checkHashes(pack.queued.modDownloadInfo).forEach { (modId, newInfo) ->
pack.queued.modDownloadInfo[modId] = newInfo
}
}
}
}) {
Text("Re-check hashes")
}
}

TitleSmall("Danger zone")
Expand Down

0 comments on commit f4d9dc3

Please sign in to comment.