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
110 changes: 56 additions & 54 deletions app/src/main/java/app/gamenative/ui/PluviaMain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import app.gamenative.enums.SaveLocation
import app.gamenative.enums.SyncResult
import app.gamenative.events.AndroidEvent
import app.gamenative.service.SteamService
import app.gamenative.service.gog.GOGService
import app.gamenative.ui.component.ConnectingServersScreen
import app.gamenative.ui.component.dialog.GameFeedbackDialog
import app.gamenative.ui.component.dialog.LoadingDialog
Expand Down Expand Up @@ -81,15 +82,15 @@ import com.winlator.core.TarCompressorUtils
import com.winlator.xenvironment.ImageFs
import com.winlator.xenvironment.ImageFsInstaller
import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientObjects.ECloudPendingRemoteOperation
import java.io.File
import java.util.Date
import java.util.EnumSet
import kotlin.reflect.KFunction2
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import java.util.Date
import java.util.EnumSet
import kotlin.reflect.KFunction2

@Composable
fun PluviaMain(
Expand Down Expand Up @@ -180,7 +181,7 @@ fun PluviaMain(
}

MainViewModel.MainUiEvent.OnBackPressed -> {
if (SteamService.isGameRunning){
if (SteamService.isGameRunning) {
gameBackAction?.invoke() ?: run { navController.popBackStack() }
} else if (hasBack) {
// TODO: check if back leads to log out and present confidence modal
Expand Down Expand Up @@ -208,19 +209,23 @@ fun PluviaMain(

// Extract game ID from appId (format: "STEAM_<id>" or "CUSTOM_GAME_<id>")
val gameId = ContainerUtils.extractGameIdFromContainerId(launchRequest.appId)
val gameSource = ContainerUtils.extractGameSourceFromContainerId(launchRequest.appId)

val isInstalled = when (gameSource) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Legacy GOG shortcuts without a GOG_ prefix are treated as STEAM, so valid GOG installs will be rejected as “not installed” and never launched.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/ui/PluviaMain.kt, line 215:

<comment>Legacy GOG shortcuts without a `GOG_` prefix are treated as STEAM, so valid GOG installs will be rejected as “not installed” and never launched.</comment>

<file context>
@@ -208,19 +209,24 @@ fun PluviaMain(
+                                    val gameSource = ContainerUtils.extractGameSourceFromContainerId(launchRequest.appId)
+
+                                    // TODO: Check gameSource (which should always default thanks to the intent manager, have a gameSource.
+                                    val isInstalled = when (gameSource) {
+                                        GameSource.STEAM -> {
+                                            SteamService.isAppInstalled(gameId)
</file context>
Fix with Cubic

GameSource.STEAM -> {
SteamService.isAppInstalled(gameId)
}

// First check if it's a Steam game and if it's installed
val isSteamInstalled = SteamService.isAppInstalled(gameId)
GameSource.GOG -> {
GOGService.isGameInstalled(gameId.toString())
}

// If not installed as Steam game, check if it's a custom game
val customGamePath = if (!isSteamInstalled) {
CustomGameScanner.findCustomGameById(gameId)
} else {
null
GameSource.CUSTOM_GAME -> {
CustomGameScanner.isGameInstalled(gameId)
}
}

// If neither Steam installed nor custom game found, show error
if (!isSteamInstalled && customGamePath == null) {
if (!isInstalled) {
val appName = SteamService.getAppInfoOf(gameId)?.name ?: "App ${launchRequest.appId}"
Timber.tag("IntentLaunch").w("Game not installed: $appName (${launchRequest.appId})")

Expand All @@ -235,27 +240,13 @@ fun PluviaMain(
return@let
}

// If it's a custom game, update the appId to use CUSTOM_GAME format
val finalAppId = if (customGamePath != null && !isSteamInstalled) {
"${GameSource.CUSTOM_GAME.name}_$gameId"
} else {
launchRequest.appId
}

// Update launchRequest with the correct appId if it was changed
val updatedLaunchRequest = if (finalAppId != launchRequest.appId) {
launchRequest.copy(appId = finalAppId)
} else {
launchRequest
}

if (updatedLaunchRequest.containerConfig != null) {
if (launchRequest.containerConfig != null) {
IntentLaunchManager.applyTemporaryConfigOverride(
context,
updatedLaunchRequest.appId,
updatedLaunchRequest.containerConfig,
launchRequest.appId,
launchRequest.containerConfig,
)
Timber.tag("IntentLaunch").i("Applied container config override for app ${updatedLaunchRequest.appId}")
Timber.tag("IntentLaunch").i("Applied container config override for app ${launchRequest.appId}")
}

if (navController.currentDestination?.route != PluviaScreen.Home.route) {
Expand All @@ -266,20 +257,19 @@ fun PluviaMain(
}
}

viewModel.setLaunchedAppId(updatedLaunchRequest.appId)
viewModel.setLaunchedAppId(launchRequest.appId)
viewModel.setBootToContainer(false)
preLaunchApp(
context = context,
appId = updatedLaunchRequest.appId,
appId = launchRequest.appId,
setLoadingDialogVisible = viewModel::setLoadingDialogVisible,
setLoadingProgress = viewModel::setLoadingDialogProgress,
setLoadingMessage = viewModel::setLoadingDialogMessage,
setMessageDialogState = setMessageDialogState,
onSuccess = viewModel::launchApp,
)
}
}
else if (PluviaApp.xEnvironment == null) {
} else if (PluviaApp.xEnvironment == null) {
Timber.i("Navigating to library")
navController.navigate(PluviaScreen.Home.route)

Expand All @@ -294,7 +284,7 @@ fun PluviaMain(
message = context.getString(
R.string.main_update_available_message,
currentUpdateInfo.versionName,
currentUpdateInfo.releaseNotes?.let { "\n\n$it" } ?: ""
currentUpdateInfo.releaseNotes?.let { "\n\n$it" } ?: "",
),
confirmBtnText = context.getString(R.string.main_update_button),
dismissBtnText = context.getString(R.string.main_later_button),
Expand Down Expand Up @@ -405,7 +395,8 @@ fun PluviaMain(

// Start GOGService if user has GOG
if (app.gamenative.service.gog.GOGService.hasStoredCredentials(context) &&
!app.gamenative.service.gog.GOGService.isRunning) {
!app.gamenative.service.gog.GOGService.isRunning
) {
Timber.tag("GOG").d("[PluviaMain]: Starting GOGService for logged-in user")
app.gamenative.service.gog.GOGService.start(context)
} else {
Expand Down Expand Up @@ -512,6 +503,7 @@ fun PluviaMain(
setMessageDialogState(MessageDialogState(false))
}
}

DialogType.SUPPORT -> {
onConfirmClick = {
uriHandler.openUri(Constants.Misc.KO_FI_LINK)
Expand Down Expand Up @@ -749,7 +741,7 @@ fun PluviaMain(
versionName = updateInfo.versionName,
onProgress = { progress ->
viewModel.setLoadingDialogProgress(progress)
}
},
)

viewModel.setLoadingDialogVisible(false)
Expand Down Expand Up @@ -967,7 +959,7 @@ fun PluviaMain(
CoroutineScope(Dispatchers.Main).launch {
val currentRoute = navController.currentBackStackEntry
?.destination
?.route // ← this is the screen’s route string
?.route // ← this is the screen’s route string

if (currentRoute == PluviaScreen.XServer.route) {
navController.popBackStack()
Expand Down Expand Up @@ -1050,29 +1042,35 @@ fun preLaunchApp(
context = context,
).await()
}
if (container.containerVariant.equals(Container.GLIBC) && !SteamService.isFileInstallable(context, "imagefs_patches_gamenative.tzst")) {
if (container.containerVariant.equals(Container.GLIBC) &&
!SteamService.isFileInstallable(context, "imagefs_patches_gamenative.tzst")
) {
setLoadingMessage("Downloading Wine")
SteamService.downloadImageFsPatches(
onDownloadProgress = { setLoadingProgress(it / 1.0f) },
this,
context = context,
).await()
} else {
if (container.wineVersion.contains("proton-9.0-arm64ec") && !SteamService.isFileInstallable(context, "proton-9.0-arm64ec.txz")) {
if (container.wineVersion.contains("proton-9.0-arm64ec") &&
!SteamService.isFileInstallable(context, "proton-9.0-arm64ec.txz")
) {
setLoadingMessage("Downloading arm64ec Proton")
SteamService.downloadFile(
onDownloadProgress = { setLoadingProgress(it / 1.0f) },
this,
context = context,
"proton-9.0-arm64ec.txz"
"proton-9.0-arm64ec.txz",
).await()
} else if (container.wineVersion.contains("proton-9.0-x86_64") && !SteamService.isFileInstallable(context, "proton-9.0-x86_64.txz")) {
} else if (container.wineVersion.contains("proton-9.0-x86_64") &&
!SteamService.isFileInstallable(context, "proton-9.0-x86_64.txz")
) {
setLoadingMessage("Downloading x86_64 Proton")
SteamService.downloadFile(
onDownloadProgress = { setLoadingProgress(it / 1.0f) },
this,
context = context,
"proton-9.0-x86_64.txz"
"proton-9.0-x86_64.txz",
).await()
}
if (container.wineVersion.contains("proton-9.0-x86_64") || container.wineVersion.contains("proton-9.0-arm64ec")) {
Expand All @@ -1093,13 +1091,15 @@ fun preLaunchApp(
}
}
}
if (!container.isUseLegacyDRM && !container.isLaunchRealSteam && !SteamService.isFileInstallable(context, "experimental-drm-20260101.tzst")) {
if (!container.isUseLegacyDRM && !container.isLaunchRealSteam &&
!SteamService.isFileInstallable(context, "experimental-drm-20260101.tzst")
) {
setLoadingMessage("Downloading extras")
SteamService.downloadFile(
onDownloadProgress = { setLoadingProgress(it / 1.0f) },
this,
context = context,
"experimental-drm-20260101.tzst"
"experimental-drm-20260101.tzst",
).await()
}
if (container.isLaunchRealSteam && !SteamService.isFileInstallable(context, "steam.tzst")) {
Expand All @@ -1116,13 +1116,14 @@ fun preLaunchApp(
onDownloadProgress = { setLoadingProgress(it / 1.0f) },
this,
context = context,
"steam-token.tzst"
"steam-token.tzst",
).await()
}
val loadingMessage = if (container.containerVariant.equals(Container.GLIBC))
val loadingMessage = if (container.containerVariant.equals(Container.GLIBC)) {
context.getString(R.string.main_installing_glibc)
else
} else {
context.getString(R.string.main_installing_bionic)
}
setLoadingMessage(loadingMessage)
val imageFsInstallSuccess =
ImageFsInstaller.installIfNeededFuture(context, context.assets, container) { progress ->
Expand Down Expand Up @@ -1218,7 +1219,7 @@ fun preLaunchApp(
message = context.getString(
R.string.main_save_conflict_message,
Date(postSyncInfo.localTimestamp).toString(),
Date(postSyncInfo.remoteTimestamp).toString()
Date(postSyncInfo.remoteTimestamp).toString(),
),
dismissBtnText = context.getString(R.string.main_keep_local),
confirmBtnText = context.getString(R.string.main_keep_remote),
Expand Down Expand Up @@ -1261,6 +1262,7 @@ fun preLaunchApp(
)
}
}

SyncResult.UnknownFail,
SyncResult.DownloadFail,
SyncResult.UpdateFail,
Expand Down Expand Up @@ -1303,7 +1305,7 @@ fun preLaunchApp(
R.string.main_upload_in_progress_message,
gameName,
pro.machineName,
dateStr
dateStr,
),
dismissBtnText = context.getString(R.string.ok),
),
Expand All @@ -1320,7 +1322,7 @@ fun preLaunchApp(
R.string.main_pending_upload_message,
gameName,
pro.machineName,
dateStr
dateStr,
),
confirmBtnText = context.getString(R.string.main_play_anyway),
dismissBtnText = context.getString(R.string.cancel),
Expand All @@ -1338,7 +1340,7 @@ fun preLaunchApp(
R.string.main_app_running_other_device,
pro.machineName,
gameName,
dateStr
dateStr,
),
confirmBtnText = context.getString(R.string.main_play_anyway),
dismissBtnText = context.getString(R.string.cancel),
Expand Down
Loading