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 change: 1 addition & 0 deletions app/src/main/java/app/gamenative/data/DepotInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ data class DepotInfo(
val realm: SteamRealm = SteamRealm.Unknown,
val systemDefined: Boolean = false,
val steamDeck: Boolean = false,
val installScript: String = "",
) {
/** Windows or OS-untagged (neither Linux nor macOS) */
val isWindowsCompatible: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import app.gamenative.utils.ExecutableSelectionUtils
import app.gamenative.utils.LsfgQuickMenuHelper
import app.gamenative.utils.ManifestComponentHelper
import app.gamenative.utils.PreInstallSteps
import app.gamenative.utils.installscript.InstallScriptExecutor
import app.gamenative.utils.SteamTokenLogin
import app.gamenative.utils.SteamUtils
import app.gamenative.utils.WineProcessSnapshotHelper
Expand Down Expand Up @@ -3051,6 +3052,7 @@ private fun setupXEnvironment(
}

var preInstallCommands: List<PreInstallSteps.PreInstallCommand> = emptyList()
var installScriptRunProcessCommands = emptyList<InstallScriptExecutor.RunProcessCommand>()
var gameExecutable = ""

if (container != null) {
Expand All @@ -3059,6 +3061,40 @@ private fun setupXEnvironment(
} catch (e: Exception) {
Timber.tag("GameFixes").w(e, "Game fixes failed before launch")
}
if (gameSource == GameSource.STEAM) {
try {
val numericGameId = ContainerUtils.extractGameIdFromContainerId(appId)
val steamApp = SteamService.getAppInfoOf(numericGameId)
val appInfoObj = runBlocking(Dispatchers.IO) {
SteamService.instance?.appInfoDao?.get(numericGameId)
}
val gameDir = PreInstallSteps.getGameDir(container)
if (steamApp != null && appInfoObj != null && gameDir != null) {
val installDir = "A:"
val scripts = InstallScriptExecutor.collectScripts(
steamApp = steamApp,
appInfo = appInfoObj,
gameDir = gameDir,
installDir = installDir,
language = container.language,
appId = numericGameId,
)
if (scripts.isNotEmpty()) {
Timber.tag("InstallScript").i("Applying registry keys from ${scripts.size} install script(s)")
InstallScriptExecutor.applyRegistryKeys(container, scripts, container.language)
installScriptRunProcessCommands = InstallScriptExecutor.getRunProcessCommands(
container = container,
scripts = scripts,
screenInfo = xServer.screenInfo.toString(),
is64Bit = container.isWoW64Mode,
)
}
}
} catch (e: Exception) {
Timber.tag("InstallScript").w(e, "InstallScript execution failed")
}
}

if (container.startupSelection == Container.STARTUP_SELECTION_AGGRESSIVE) {
if (container.containerVariant.equals(Container.BIONIC)){
Timber.d("Incorrect startup selection detected. Reverting to essential startup selection")
Expand Down Expand Up @@ -3090,8 +3126,9 @@ private fun setupXEnvironment(
xServer.screenInfo.toString(),
containerVariantChanged,
)
guestProgramLauncherComponent.guestExecutable =
preInstallCommands.firstOrNull()?.executable ?: gameExecutable
val firstChainedExecutable = preInstallCommands.firstOrNull()?.executable
?: installScriptRunProcessCommands.firstOrNull()?.executable
guestProgramLauncherComponent.guestExecutable = firstChainedExecutable ?: gameExecutable
guestProgramLauncherComponent.isWoW64Mode = wow64Mode
// Set steam type for selecting appropriate box64rc
guestProgramLauncherComponent.setSteamType(container.getSteamType())
Expand Down Expand Up @@ -3132,7 +3169,7 @@ private fun setupXEnvironment(
containerVariantChanged = containerVariantChanged,
onError = onGameLaunchError
)
if (preInstallCommands.isNotEmpty()) {
if (preInstallCommands.isNotEmpty() || installScriptRunProcessCommands.isNotEmpty()) {
PluviaApp.events.emit(AndroidEvent.SetBootingSplashText("Installing prerequisites..."))
} else {
PluviaApp.events.emit(AndroidEvent.SetBootingSplashText("Launching game..."))
Expand Down Expand Up @@ -3211,35 +3248,58 @@ private fun setupXEnvironment(
PluviaApp.events.emit(AndroidEvent.GuestProgramTerminated)
}

fun chainPreInstallSteps(remaining: List<PreInstallSteps.PreInstallCommand>) {
data class ChainedCommand(
val executable: String,
val onComplete: () -> Unit,
)

val chainedPreInstall = preInstallCommands.map { cmd ->
ChainedCommand(cmd.executable) { PreInstallSteps.markStepDone(container, cmd.marker) }
}
val chainedInstallScript = installScriptRunProcessCommands.map { cmd ->
ChainedCommand(cmd.executable) {
if (cmd.hasRunKey != null) {
val exitCode = InstallScriptExecutor.readExitCode(container)
if (exitCode == 0) {
InstallScriptExecutor.markRunProcessComplete(container, cmd.hasRunKey)
} else {
Timber.tag("InstallScript").w(
"Run process exited with code $exitCode, will retry next launch",
)
}
}
}
}
val allChainedCommands = chainedPreInstall + chainedInstallScript
Comment thread
playday3008 marked this conversation as resolved.

fun chainCommands(remaining: List<ChainedCommand>) {
if (remaining.isEmpty()) {
guestProgramLauncherComponent.setGuestExecutable(gameExecutable)
guestProgramLauncherComponent.setTerminationCallback(gameTerminationCallback)
return
}
guestProgramLauncherComponent.setGuestExecutable(remaining.first().executable)
guestProgramLauncherComponent.setTerminationCallback { _ ->
val current = remaining.first()
PreInstallSteps.markStepDone(container, current.marker)
remaining.first().onComplete()
guestProgramLauncherComponent.setPreUnpack(null)
try {
guestProgramLauncherComponent.execShellCommand("wineserver -k")
} catch (e: Exception) {
Timber.w(e, "wineserver -k between pre-install steps (non-fatal)")
Timber.w(e, "wineserver -k between chained commands (non-fatal)")
}
val nextRemaining = remaining.drop(1)
if (nextRemaining.isEmpty()) {
PluviaApp.events.emit(AndroidEvent.SetBootingSplashText("Launching game..."))
} else {
PluviaApp.events.emit(AndroidEvent.SetBootingSplashText("Installing prerequisites..."))
}
chainPreInstallSteps(nextRemaining)
chainCommands(nextRemaining)
guestProgramLauncherComponent.start()
}
}

if (preInstallCommands.isNotEmpty()) {
chainPreInstallSteps(preInstallCommands)
if (allChainedCommands.isNotEmpty()) {
chainCommands(allChainedCommands)
} else {
guestProgramLauncherComponent.setTerminationCallback(gameTerminationCallback)
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/app/gamenative/utils/KeyValueUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ fun KeyValue.generateSteamApp(): SteamApp {
systemDefined = currentDepot["systemdefined"].asBoolean(),
optionalDlcId = currentDepot["config"]["optionaldlc"].asInteger(INVALID_APP_ID),
steamDeck = currentDepot["config"]["steamdeck"].asBoolean(false),
installScript = currentDepot["installscript"].value.orEmpty(),
)
},
branches = this["depots"]["branches"].children.associate {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ object PreInstallSteps {
return "wine explorer /desktop=shell,$screenInfo $wrapped"
}

private fun getGameDir(container: Container): File? {
internal fun getGameDir(container: Container): File? {
for (drive in Container.drivesIterator(container.drives)) {
if (drive[0].equals("A", ignoreCase = true)) return File(drive[1])
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package app.gamenative.utils.installscript

data class RegistryValues(
val strings: Map<String, String> = emptyMap(),
val dwords: Map<String, Long> = emptyMap(),
)

data class RegistryAction(
val keyPath: String,
val values: RegistryValues,
val languageOverrides: Map<String, RegistryValues> = emptyMap(),
)

data class OSRequirement(
val is64BitWindows: Boolean? = null,
val osType: String? = null,
)

data class RunProcessAction(
val name: String,
val process: String,
val command: String = "",
val hasRunKey: String? = null,
val noCleanUp: Boolean = false,
val minimumHasRunValue: Int = 0,
val requirementOS: OSRequirement? = null,
val asCurrentUser: Boolean = false,
)

data class InstallScript(
val sourcePath: String,
val registryActions: List<RegistryAction> = emptyList(),
val runProcessActions: List<RunProcessAction> = emptyList(),
)
Loading