Skip to content
This repository has been archived by the owner on Feb 5, 2023. It is now read-only.

Commit

Permalink
fixed bug that local added games are not showing up (#7)
Browse files Browse the repository at this point in the history
added different steam library path support
  • Loading branch information
DatL4g committed Oct 3, 2022
1 parent f23e55e commit 196f015
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 49 deletions.
45 changes: 45 additions & 0 deletions src/main/kotlin/dev/datlag/dxvkotool/common/ExtendFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import java.io.File
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
import java.nio.file.Files
import java.nio.file.LinkOption
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.attribute.FileTime
import kotlin.math.max
Expand Down Expand Up @@ -124,3 +125,47 @@ fun File.isDirectorySafely(): Boolean {
Files.isDirectory(this.toPath())
}.getOrNull() ?: false
}

fun File.isSymlinkSafely(): Boolean {
return runCatching {
Files.isSymbolicLink(this.toPath())
}.getOrNull() ?: runCatching {
!Files.isRegularFile(this.toPath(), LinkOption.NOFOLLOW_LINKS)
}.getOrNull() ?: false
}

fun File.getRealFile(): File {
return if (isSymlinkSafely()) runCatching {
Files.readSymbolicLink(this.toPath()).toFile()
}.getOrNull() ?: this else this
}

fun File.isSame(file: File?): Boolean {
return if (file == null) {
false
} else {
this == file || runCatching {
this.absoluteFile == file.absoluteFile || Files.isSameFile(this.toPath(), file.toPath())
}.getOrNull() ?: false
}
}

fun Collection<File>.normalize(mustExist: Boolean = true): List<File> {
val list: MutableList<File> = mutableListOf()
this.forEach { file ->
var realFile = file.getRealFile()
if (mustExist) {
if (!realFile.existsSafely()) {
if (file.existsSafely()) {
realFile = file
} else {
return@forEach
}
}
}
if (list.firstOrNull { it.isSame(realFile) } == null) {
list.add(realFile)
}
}
return list
}
5 changes: 3 additions & 2 deletions src/main/kotlin/dev/datlag/dxvkotool/db/DB.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import dev.datlag.sqldelight.db.SteamGame
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.transform
import java.io.File

Expand Down Expand Up @@ -57,7 +58,7 @@ object DB {
}
}

emit(gameList.toList())
}
return@transform emit(gameList.toList())
}.flowOn(Dispatchers.IO)

}
24 changes: 23 additions & 1 deletion src/main/kotlin/dev/datlag/dxvkotool/io/Acf.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package dev.datlag.dxvkotool.io

import dev.datlag.dxvkotool.model.AppManifest
import dev.datlag.dxvkotool.model.LibraryConfig
import dev.datlag.dxvkotool.other.Constants
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonObject

object Acf {
fun toJson(value: String): String {
Expand All @@ -24,4 +28,22 @@ object Acf {
this.location = location
}
}
}

fun toLibraryConfigs(value: String): List<LibraryConfig> {
val jsonValue = toJson(value)

val jsonObject: JsonElement? = runCatching {
Constants.json.decodeFromString<JsonElement>(jsonValue)
}.getOrNull()

return jsonObject?.jsonObject?.values?.mapNotNull {
val jsonElement = runCatching {
it.jsonObject
}.getOrNull() ?: it

runCatching {
Constants.json.decodeFromJsonElement<LibraryConfig>(jsonElement)
}.getOrNull()
} ?: emptyList()
}
}
9 changes: 2 additions & 7 deletions src/main/kotlin/dev/datlag/dxvkotool/io/GameIO.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.transform
Expand All @@ -22,18 +21,14 @@ import java.io.File

object GameIO {

val allGamesFlow: Flow<List<Game>> = combine(SteamIO.steamGamesFlow, DB.otherGames, LegendaryIO.heroicFlatpakLegendaryGamesFlow) { t1, t2, _ ->
private val allGamesFlow: Flow<List<Game>> = combine(SteamIO.steamGamesFlow, DB.otherGames) { t1, t2 ->
listFrom(t1, t2)
}.transform {
emit(it)
combine(it.map { game -> game.cacheInfoCollector }) { list ->
list
}.collect()
}.flowOn(Dispatchers.IO)

val allGamesPartitioned: Flow<GamePartition> = allGamesFlow.transform { list ->
val (steamGames, otherGamesFlat) = list.partition { it is Game.Steam }
val (epicGames, otherGames) = otherGamesFlat.partition { (it as? Game.Other?)?.isEpicGame == true }

emit(
GamePartition(
steamGames,
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/dev/datlag/dxvkotool/io/LegendaryIO.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import java.io.File

object LegendaryIO {

val systemLegendaryGamesFlow: MutableStateFlow<List<LegendaryGame>> = MutableStateFlow(emptyList())
val heroicFlatpakLegendaryGamesFlow: MutableStateFlow<List<LegendaryGame>> = MutableStateFlow(emptyList())
private val systemLegendaryGamesFlow: MutableStateFlow<List<LegendaryGame>> = MutableStateFlow(emptyList())
private val heroicFlatpakLegendaryGamesFlow: MutableStateFlow<List<LegendaryGame>> = MutableStateFlow(emptyList())

val legendaryGamesFlow = combine(systemLegendaryGamesFlow, heroicFlatpakLegendaryGamesFlow) { t1, t2 ->
listFrom(t1, t2)
Expand Down
87 changes: 60 additions & 27 deletions src/main/kotlin/dev/datlag/dxvkotool/io/SteamIO.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package dev.datlag.dxvkotool.io

import dev.datlag.dxvkotool.common.canReadSafely
import dev.datlag.dxvkotool.common.existsSafely
import dev.datlag.dxvkotool.common.isDirectorySafely
import dev.datlag.dxvkotool.common.listFilesSafely
import dev.datlag.dxvkotool.common.listFrom
import dev.datlag.dxvkotool.common.normalize
import dev.datlag.dxvkotool.model.AppManifest
import dev.datlag.dxvkotool.model.Game
import dev.datlag.dxvkotool.other.Constants
Expand All @@ -13,60 +17,89 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File

object SteamIO {
fun reload(scope: CoroutineScope) = scope.launch(Dispatchers.IO) {
defaultAcfFlow.emit(getDefaultAcf())
flatpakAcfFlow.emit(getFlatpakAcf())
systemSteamAppsFoldersFlow.emit(getSystemAcf())
flatpakSteamAppsFoldersFlow.emit(getFlatpakAcf())
}

private fun getDefaultAcf() = (File(Constants.userDir, Constants.STEAM_DEFAULT_ROOT).listFiles() ?: emptyArray()).filter {
it.extension.equals("acf", true)
private fun getSystemAcf(): List<File> {
val systemSteamFolders = listOf(
File(Constants.userDir, Constants.STEAM_DEFAULT_ROOT),
File(Constants.userDir, Constants.STEAM_SYMLINK_ROOT)
)

return getSteamAppsFolders(systemSteamFolders)
}

private fun getFlatpakAcf() = (File(Constants.userDir, Constants.STEAM_FLATPAK_ROOT).listFiles() ?: emptyArray()).filter {
it.extension.equals("acf", true)
private fun getFlatpakAcf(): List<File> {
val flatpakSteamFolders = listOf(
File(Constants.userDir, Constants.STEAM_FLATPAK_ROOT),
File(Constants.userDir, Constants.STEAM_FLATPAK_SYMLINK_ROOT),
)

return getSteamAppsFolders(flatpakSteamFolders)
}

val defaultAcfFlow: MutableStateFlow<List<File>> = MutableStateFlow(emptyList())
private fun getSteamAppsFolders(list: Collection<File>): List<File> {
val steamAppsFolder: MutableList<File> = mutableListOf()

list.forEach {
steamAppsFolder.add(File(it, "steamapps/"))
steamAppsFolder.addAll(listOf(
File(it, "libraryfolders.vdf"),
File(it, "steamapps/libraryfolders.vdf"),
File(it, "config/libraryfolders.vdf")
).filter { file -> file.existsSafely() && file.canReadSafely() }.flatMap { file ->
Acf.toLibraryConfigs(file.readText())
}.map { config -> config.path }.toSet().map { path ->
File(path, "steamapps/")
})
}

val flatpakAcfFlow: MutableStateFlow<List<File>> = MutableStateFlow(emptyList())
return steamAppsFolder.normalize()
}

val acfFlow = combine(defaultAcfFlow, flatpakAcfFlow) { t1, t2 ->
listFrom(t1, t2)
private val systemSteamAppsFoldersFlow: MutableStateFlow<List<File>> = MutableStateFlow(emptyList())
private val flatpakSteamAppsFoldersFlow: MutableStateFlow<List<File>> = MutableStateFlow(emptyList())
private val steamAppsFoldersFlow = combine(systemSteamAppsFoldersFlow, flatpakSteamAppsFoldersFlow) { t1, t2 ->
listFrom(t1, t2).normalize()
}.flowOn(Dispatchers.IO)

val appManifestFlow: Flow<List<AppManifest>> = acfFlow.transform { acfFiles ->
return@transform emit(coroutineScope {
private val acfFlow = steamAppsFoldersFlow.transform { list ->
val acfList = list.flatMap { file ->
file.listFilesSafely().filter {
it.extension.equals("acf", true) && it.canReadSafely()
}
}
return@transform emit(acfList)
}.flowOn(Dispatchers.IO)

private val appManifestFlow: Flow<List<AppManifest>> = acfFlow.transform { acfFiles ->
val configList = withContext(Dispatchers.IO) {
acfFiles.map {
async {
Acf.toAppManifest(it.readText(), it.parentFile?.absolutePath ?: it.absolutePath).getOrNull()
}
}
}.awaitAll().filterNotNull())
}.flowOn(Dispatchers.IO)

val defaultShaderCacheFoldersFlow = flow<List<File>> {
emit((File(Constants.userDir, Constants.STEAM_SHADER_DEFAULT_ROOT).listFiles() ?: emptyArray()).filter {
it.isDirectorySafely()
})
}.awaitAll().filterNotNull()
return@transform emit(configList)
}.flowOn(Dispatchers.IO)

val flatpakShaderCacheFoldersFlow = flow<List<File>> {
emit((File(Constants.userDir, Constants.STEAM_SHADER_FLATPAK_ROOT).listFiles() ?: emptyArray()).filter {
it.isDirectorySafely()
private val shaderCacheFoldersFlow: Flow<List<File>> = steamAppsFoldersFlow.transform { list ->
return@transform emit(list.flatMap { file ->
File(file, "shadercache/").listFilesSafely().filter {
it.isDirectorySafely()
}
})
}.flowOn(Dispatchers.IO)

val shaderCacheFoldersFlow = combine(defaultShaderCacheFoldersFlow, flatpakShaderCacheFoldersFlow) { t1, t2 ->
listFrom(t1, t2)
}.flowOn(Dispatchers.IO)

val steamGamesFlow = combine(appManifestFlow, shaderCacheFoldersFlow) { t1, t2 ->
val foldersWithManifest = t2.associateWith { file ->
t1.firstOrNull {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/dev/datlag/dxvkotool/model/Game.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ sealed class Game(
}
t.info.emit(cacheInfo)
}
emit(matchingCacheWithItem)
return@transform emit(matchingCacheWithItem.keys)
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/main/kotlin/dev/datlag/dxvkotool/model/LibraryConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.datlag.dxvkotool.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class LibraryConfig(
@SerialName("path") val path: String
)
13 changes: 6 additions & 7 deletions src/main/kotlin/dev/datlag/dxvkotool/other/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ import org.apache.tika.Tika
import java.time.format.DateTimeFormatter

object Constants {
const val STEAM_DEFAULT_ROOT = ".steam/steam/steamapps/"
const val STEAM_FLATPAK_ROOT = ".var/app/com.valvesoftware.Steam/.steam/steam/steamapps/"
const val STEAM_DEFAULT_ROOT = ".local/share/Steam/"
const val STEAM_SYMLINK_ROOT = ".steam/steam/"
const val STEAM_FLATPAK_ROOT = ".var/app/com.valvesoftware.Steam/.local/share/Steam/"
const val STEAM_FLATPAK_SYMLINK_ROOT = ".var/app/com.valvesoftware.Steam/.steam/steam/"

const val STEAM_SHADER_DEFAULT_ROOT = "$STEAM_DEFAULT_ROOT/shadercache/"
const val STEAM_SHADER_FLATPAK_ROOT = "$STEAM_FLATPAK_ROOT/shadercache/"

const val ACF_ALL_ENDING_WITH_COMMA = "\"\\S+\"\\s+\"(\\S|[ ])+\"(?!(\\s+)?})"
const val ACF_ALL_ENDING_WITH_COLON = "\"(\\S|[ ])+\"(?!([,]|\\s+(}|])))"
const val ACF_ALL_ENDING_WITH_COMMA = "\"\\S+\"\\s+\"(\\S|[ ])*\"(?!(\\s+)?})"
const val ACF_ALL_ENDING_WITH_COLON = "\"(\\S|[ ])*\"(?!([,]|\\s+(}|])))"
const val ACF_ALL_ENDING_WITH_PARENTHESIS = "(}|])(?!([,]|\\s+(}|])))"

const val SYSTEM_DEFAULT_LEGENDARY = ".config/legendary/"
Expand Down
3 changes: 1 addition & 2 deletions src/main/kotlin/dev/datlag/dxvkotool/ui/compose/GameCard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package dev.datlag.dxvkotool.ui.compose
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
Expand All @@ -25,7 +24,7 @@ import dev.datlag.dxvkotool.model.Game
@Composable
@Preview
fun GameCard(game: Game) {
val caches by game.caches.collectAsState()
val caches by game.cacheInfoCollector.collectAsState(emptySet())

ElevatedCard(modifier = Modifier.fillMaxSize()) {
AsyncImage(
Expand Down

0 comments on commit 196f015

Please sign in to comment.