Skip to content
Open
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
1,267 changes: 1,267 additions & 0 deletions app/schemas/app.gamenative.db.PluviaDatabase/20.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion app/src/main/java/app/gamenative/data/AppInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ data class AppInfo (
val branch: String = "public",
@ColumnInfo(name = "recovered_install_size_bytes", defaultValue = "0")
val recoveredInstallSizeBytes: Long = 0L,
)
@ColumnInfo("custom_install_path", defaultValue = "")
val customInstallPath: String = "",
) {
val isImported: Boolean
get() = customInstallPath != ""
}
3 changes: 2 additions & 1 deletion app/src/main/java/app/gamenative/db/PluviaDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const val DATABASE_NAME = "pluvia.db"
DownloadingAppInfo::class,
SteamUnlockedBranch::class,
],
version = 19,
version = 20,
// For db migration, visit https://developer.android.com/training/data-storage/room/migrating-db-versions for more information
exportSchema = true, // It is better to handle db changes carefully, as GN is getting much more users.
autoMigrations = [
Expand All @@ -72,6 +72,7 @@ const val DATABASE_NAME = "pluvia.db"
// v16 users will fallback to destructive migration (only cached Steam data, re-fetched on login)
AutoMigration(from = 17, to = 18), // Added workshop_mods, enabled_workshop_item_ids, workshop_download_pending to steam_app
AutoMigration(from = 18, to = 19), // Added recovered_install_size_bytes to app_info
AutoMigration(from = 19, to = 20), // Added custom_install_path to app_info
]
)
@TypeConverters(
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/app/gamenative/db/dao/SteamAppDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,7 @@ interface SteamAppDao {

@Query("UPDATE steam_app SET workshop_mods = 0, enabled_workshop_item_ids = '', workshop_download_pending = 0 WHERE id = :appId")
suspend fun clearWorkshopState(appId: Int)

@Query("SELECT * FROM steam_app WHERE config LIKE '%\"installDir\":\"' || :dirName || '\",%'")
suspend fun findSteamAppWithInstallDir(dirName: String): List<SteamApp>
Comment thread
joshuatam marked this conversation as resolved.
}
43 changes: 38 additions & 5 deletions app/src/main/java/app/gamenative/service/SteamService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ import app.gamenative.statsgen.Achievement
import app.gamenative.statsgen.StatType
import app.gamenative.statsgen.StatsAchievementsGenerator
import app.gamenative.statsgen.VdfParser
import app.gamenative.utils.CustomGameScanner
import java.nio.ByteBuffer
import java.nio.ByteOrder

Expand Down Expand Up @@ -600,6 +601,10 @@ class SteamService : Service(), IChallengeUrlChanged {
return runBlocking(Dispatchers.IO) { instance?.appInfoDao?.getInstalledApp(appId) }
}

fun findSteamAppWithInstallDir(dirName: String): List<SteamApp>? {
return runBlocking(Dispatchers.IO) { instance?.appDao?.findSteamAppWithInstallDir(dirName) }
}
Comment thread
joshuatam marked this conversation as resolved.

fun getInstalledDepotsOf(appId: Int): List<Int>? {
return getInstalledApp(appId)?.downloadedDepots
}
Expand Down Expand Up @@ -934,6 +939,13 @@ class SteamService : Service(), IChallengeUrlChanged {

fun getAppDirPath(gameId: Int): String {
val info = getAppInfoOf(gameId)

// For installed game, check whether it has customInstallPath and return it
val appInfo = getInstalledApp(gameId)
if (appInfo != null && appInfo.isImported) {
return appInfo.customInstallPath
}
Comment thread
joshuatam marked this conversation as resolved.

val appName = getAppDirName(info)
val oldName = info?.name.orEmpty()
val names = if (oldName.isNotEmpty() && oldName != appName) listOf(appName, oldName) else listOf(appName)
Expand Down Expand Up @@ -1167,8 +1179,30 @@ class SteamService : Service(), IChallengeUrlChanged {

fun deleteApp(appId: Int): Boolean {
// snapshot path before marker removal (removing the marker changes resolution)
val appDirPath = getAppDirPath(appId)
MarkerUtils.removeMarker(appDirPath, Marker.DOWNLOAD_COMPLETE_MARKER)
val appInfo = getInstalledApp(appId)
val result = if (appInfo?.isImported == true) {
Comment thread
joshuatam marked this conversation as resolved.
// For imported game, do cleanup
// Remove from manual folders list and invalidate cache
val folderPath = appInfo.customInstallPath
val manualFolders = PrefManager.customGameManualFolders.toMutableSet()
manualFolders.remove(folderPath)
PrefManager.customGameManualFolders = manualFolders
CustomGameScanner.invalidateCache()
Comment thread
joshuatam marked this conversation as resolved.

MarkerUtils.removeMarker(folderPath, Marker.DOWNLOAD_COMPLETE_MARKER)

true
} else {
val appDirPath = getAppDirPath(appId)
val appDir = File(appDirPath)

if (appDir.exists()) {
MarkerUtils.removeMarker(appDirPath, Marker.DOWNLOAD_COMPLETE_MARKER)
}

File(appDirPath).deleteRecursively()
}

// Remove from DB
workshopPausedApps.remove(appId)
with(instance!!) {
Expand All @@ -1190,7 +1224,7 @@ class SteamService : Service(), IChallengeUrlChanged {
}
}

return File(appDirPath).deleteRecursively()
return result
}

fun downloadApp(appId: Int): DownloadInfo? {
Expand Down Expand Up @@ -1952,8 +1986,7 @@ class SteamService : Service(), IChallengeUrlChanged {
val updatedDlcDepots = (appInfo.dlcDepots + selectedDlcAppIds).distinct()

instance?.appInfoDao?.update(
AppInfo(
downloadingAppId,
appInfo.copy(
isDownloaded = true,
downloadedDepots = updatedDownloadedDepots.sorted(),
dlcDepots = updatedDlcDepots.sorted(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import app.gamenative.utils.SteamUtils
import app.gamenative.utils.StorageUtils
import app.gamenative.workshop.WorkshopManager
import app.gamenative.NetworkMonitor
import app.gamenative.service.SteamService.Companion.getInstalledApp
import com.google.android.play.core.splitcompat.SplitCompat
import com.posthog.PostHog
import com.winlator.container.ContainerData
Expand All @@ -78,6 +79,7 @@ import app.gamenative.ui.screen.library.GameMigrationDialog
import app.gamenative.ui.component.dialog.state.GameManagerDialogState
import app.gamenative.ui.util.SnackbarManager
import app.gamenative.utils.ContainerUtils.getContainer
import app.gamenative.utils.CustomGameScanner
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import timber.log.Timber
Expand Down Expand Up @@ -1166,6 +1168,8 @@ class SteamAppScreen : BaseAppScreen() {
hideUninstallDialog(libraryItem.appId)

CoroutineScope(Dispatchers.IO).launch {
val installedAppInfo = getInstalledApp(libraryItem.gameId)!!

val success = SteamService.deleteApp(gameId)
DownloadService.invalidateCache()
withContext(Dispatchers.Main) {
Expand All @@ -1188,6 +1192,13 @@ class SteamAppScreen : BaseAppScreen() {
SnackbarManager.show(context.getString(R.string.steam_uninstall_failed))
}
}

// Back to home screen as the game is imported
if (success && installedAppInfo.isImported) {
withContext(Dispatchers.Main) {
onBack()
}
}
Comment thread
joshuatam marked this conversation as resolved.
}
},
) {
Expand Down
55 changes: 55 additions & 0 deletions app/src/main/java/app/gamenative/utils/CustomGameScanner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,26 @@ import android.provider.Settings
import androidx.core.content.ContextCompat
import app.gamenative.PluviaApp
import app.gamenative.PrefManager
import app.gamenative.data.AppInfo
import app.gamenative.data.GameSource
import app.gamenative.data.LibraryItem
import app.gamenative.enums.Marker
import app.gamenative.events.AndroidEvent
import app.gamenative.service.DownloadService
import app.gamenative.service.SteamService
import app.gamenative.service.SteamService.Companion.INVALID_APP_ID
import app.gamenative.service.SteamService.Companion.getMainAppDepots
import com.winlator.container.Container
import com.winlator.container.ContainerManager
import java.io.File
import kotlin.math.abs
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.json.JSONObject
import timber.log.Timber
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.text.ifEmpty

object CustomGameScanner {

Expand Down Expand Up @@ -500,6 +509,52 @@ object CustomGameScanner {
return null
}

if (SteamService.instance != null) {
val steamApps = SteamService.findSteamAppWithInstallDir(dirName = folder.name)
if (steamApps?.size == 1) {
val steamApp = steamApps[0]

if (SteamService.getInstalledApp(steamApp.id) == null) {
val preferredLanguage = PrefManager.containerLanguage
val mainDepots = getMainAppDepots(steamApp.id, preferredLanguage)
val mainAppDepots = mainDepots.filter { (_, depot) ->
depot.dlcAppId == INVALID_APP_ID
}
val mainAppDepotIds = mainAppDepots.keys.sorted()

runBlocking {
SteamService.instance?.appInfoDao?.insert(
AppInfo(
steamApp.id,
isDownloaded = true,
downloadedDepots = mainAppDepotIds,
dlcDepots = emptyList(),
branch = "public",
customInstallPath = folderPath
),
)
}

MarkerUtils.addMarker(folderPath, Marker.DOWNLOAD_COMPLETE_MARKER)
}

val idPart = steamApp.id
val appId = "${GameSource.STEAM.name}_$idPart"

return LibraryItem(
index = 0,
appId = appId,
name = steamApp.name,
iconHash = steamApp.clientIconHash,
capsuleImageUrl = steamApp.getCapsuleUrl(),
headerImageUrl = steamApp.getHeaderImageUrl().orEmpty().ifEmpty { steamApp.headerUrl },
heroImageUrl = steamApp.getHeroUrl().ifEmpty { steamApp.headerUrl },
isShared = false,
gameSource = GameSource.STEAM,
)
}
}

val idPart = getOrGenerateGameId(folder)
val appId = "${GameSource.CUSTOM_GAME.name}_$idPart"

Expand Down
9 changes: 7 additions & 2 deletions app/src/main/java/com/winlator/core/WineUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.List;
import java.util.Locale;

import app.gamenative.PrefManager;
import timber.log.Timber;

public abstract class WineUtils {
Expand Down Expand Up @@ -62,8 +63,12 @@ public static void createDosdevicesSymlinks(Context context, Container container
FileUtils.symlink(path, dosdevicesPath+"/"+drive[0].toLowerCase(Locale.ENGLISH)+":");

// Check if this is the A: drive (game directory)
if (drive[0].equals("A") && path.contains("/Steam/steamapps/common/")) {
gameDirectoryPath = path;
if (drive[0].equals("A")) {
if (path.contains("/Steam/steamapps/common/")) {
gameDirectoryPath = path;
} else if (PrefManager.INSTANCE.getCustomGameManualFolders().contains(path)) {
gameDirectoryPath = path;
}
}
}

Expand Down
Loading