Skip to content
Merged
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
58 changes: 33 additions & 25 deletions app/src/main/java/app/gamenative/service/SteamService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import app.gamenative.utils.CaseInsensitiveFileSystem
import app.gamenative.utils.ContainerUtils
import app.gamenative.utils.FileUtils
import app.gamenative.utils.LicenseSerializer
import app.gamenative.utils.LsfgVkManager
import app.gamenative.utils.MarkerUtils
import app.gamenative.utils.Net
import app.gamenative.utils.SteamUtils
Expand Down Expand Up @@ -2279,33 +2280,40 @@ class SteamService : Service(), IChallengeUrlChanged {
val appId = downloadInfo.gameId
val steamId = userSteamId
val containerId = "${GameSource.STEAM.name}_$appId"
if (steamId != null && !ContainerUtils.isLocalSavesOnly(svc.applicationContext, containerId)) {
downloadInfo.setPostInstallSyncing(true)
downloadInfo.updateStatusMessage("Syncing saves...")
PluviaApp.events.emit(AndroidEvent.PostInstallSyncStatusChanged(appId, true))
try {
val container = ContainerUtils.getOrCreateContainer(svc.applicationContext, containerId)
val prefixToPath: (String) -> String = { prefix ->
PathType.from(prefix).toAbsPath(container, appId, steamId.accountID)
}
val postSyncInfo = forceSyncUserFiles(
appId = appId,
prefixToPath = prefixToPath,
preferredSave = SaveLocation.Remote,
parentScope = parentScope,
).await()
if (postSyncInfo.syncResult !in setOf(SyncResult.Success, SyncResult.UpToDate)) {
Timber.w("[PostInstallSync] Cloud save sync finished with ${postSyncInfo.syncResult} for app $appId")
// Skip post-install sync for utility apps (e.g., Lossless Scaling)
val isUtilityApp = appId == LsfgVkManager.LOSSLESS_SCALING_APP_ID
if (!isUtilityApp) {
Comment thread
joshuatam marked this conversation as resolved.
if (steamId != null && !ContainerUtils.isLocalSavesOnly(svc.applicationContext, containerId)) {
downloadInfo.setPostInstallSyncing(true)
downloadInfo.updateStatusMessage("Syncing saves...")
PluviaApp.events.emit(AndroidEvent.PostInstallSyncStatusChanged(appId, true))
try {
val container = ContainerUtils.getOrCreateContainer(svc.applicationContext, containerId)
val prefixToPath: (String) -> String = { prefix ->
PathType.from(prefix).toAbsPath(container, appId, steamId.accountID)
}
val postSyncInfo = forceSyncUserFiles(
appId = appId,
prefixToPath = prefixToPath,
preferredSave = SaveLocation.Remote,
parentScope = parentScope,
).await()
if (postSyncInfo.syncResult !in setOf(SyncResult.Success, SyncResult.UpToDate)) {
Timber.w("[PostInstallSync] Cloud save sync finished with ${postSyncInfo.syncResult} for app $appId")
}
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Timber.e(e, "[PostInstallSync] Cloud save sync failed for app $appId")
} finally {
downloadInfo.setPostInstallSyncing(false)
downloadInfo.updateStatusMessage(null)
PluviaApp.events.emit(AndroidEvent.PostInstallSyncStatusChanged(appId, false))
}
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Timber.e(e, "[PostInstallSync] Cloud save sync failed for app $appId")
} finally {
downloadInfo.setPostInstallSyncing(false)
downloadInfo.updateStatusMessage(null)
PluviaApp.events.emit(AndroidEvent.PostInstallSyncStatusChanged(appId, false))
}
} else {
Timber.d("Skipped container creation Lossless Scaling")
PluviaApp.events.emit(AndroidEvent.PostInstallSyncStatusChanged(appId, false))
}
}
}
Expand Down
103 changes: 95 additions & 8 deletions app/src/main/java/app/gamenative/utils/LsfgVkManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import java.util.concurrent.Executors
import app.gamenative.BuildConfig
import app.gamenative.service.SteamService
import com.winlator.container.Container
import com.winlator.container.ContainerManager
import com.winlator.core.FileUtils
import com.winlator.core.envvars.EnvVars
import java.io.File
Expand Down Expand Up @@ -80,11 +81,10 @@ object LsfgVkManager {
private const val ENV_PROCESS = "LSFG_PROCESS"

// Current runtime version (bumped when the bundled .so changes)
private const val RUNTIME_VERSION = "v1.3.2-android-arm64-v8a"
private const val RUNTIME_VERSION = "v1.3.3-android-arm64-v8a"

// Asset paths
// Asset path for manifest (still in assets)
private const val ASSET_DIR = "lsfg_vk/android_arm64_v8a"
private const val ASSET_LIB = "$ASSET_DIR/$LIB_FILENAME"
private const val ASSET_MANIFEST = "$ASSET_DIR/$MANIFEST_FILENAME"

// ---- Public API --------------------------------------------------------
Expand Down Expand Up @@ -269,8 +269,18 @@ object LsfgVkManager {
localLibDir.mkdirs()
layerDir.mkdirs()

// Copy the layer .so from assets
FileUtils.copy(context, ASSET_LIB, libFile)
// Copy the layer .so from native library directory (jniLibs)
val nativeLibDir = File(context.applicationInfo.nativeLibraryDir)
val sourceLib = File(nativeLibDir, LIB_FILENAME)
if (!sourceLib.exists()) {
Timber.tag(TAG).e("Native library not found: %s", sourceLib.absolutePath)
return false
}
sourceLib.inputStream().use { input ->
libFile.outputStream().use { output ->
input.copyTo(output)
}
}
// Write the manifest with patched library_path
val manifestText = context.assets.open(ASSET_MANIFEST)
.bufferedReader().use { it.readText() }
Expand Down Expand Up @@ -301,6 +311,10 @@ object LsfgVkManager {
Timber.tag(TAG).d("Runtime %s already installed in %s", RUNTIME_VERSION, rootDir)
}

// Delete the Lossless Scaling container if it exists (no longer needed)
// We now copy the DLL directly from Steam install dir instead of creating a container
deleteLosslessScalingContainerIfExists(context)

// Copy Lossless.dll from Steam install dir into the container
val dllFile = File(dllDir, LOSSLESS_DLL_NAME)
val steamDll = findSteamDll()
Expand Down Expand Up @@ -432,11 +446,59 @@ object LsfgVkManager {
/**
* Find Lossless.dll in the Steam install directory for app 993090.
* Returns the File if it exists, null otherwise.
*
* This function searches all possible Steam install paths directly
* without creating a container for the Lossless Scaling app.
*/
private fun findSteamDll(): File? {
val appDir = SteamService.getAppDirPath(LOSSLESS_SCALING_APP_ID)
val dll = File(appDir, LOSSLESS_DLL_NAME)
return dll.takeIf { it.isFile }
return findSteamDllDirect()
}

/**
* Find Lossless.dll by searching all Steam install paths directly.
* This avoids creating a container for app 993090 (Lossless Scaling).
*
* Search order:
* 1. Check if app is already installed in any known Steam path
* 2. Look for common install directory names
*
* @return File pointing to Lossless.dll if found, null otherwise
*/
private fun findSteamDllDirect(): File? {
// Get app info to find the install directory name
val appInfo = SteamService.getAppInfoOf(LOSSLESS_SCALING_APP_ID)
val installDirName = appInfo?.installDir?.takeIf { it.isNotBlank() } ?: "Lossless Scaling"

// Search all possible Steam install paths
val searchPaths = SteamService.allInstallPaths

for (basePath in searchPaths) {
val appDir = File(basePath, installDirName)
val dll = File(appDir, LOSSLESS_DLL_NAME)
if (dll.isFile) {
Timber.tag(TAG).d("Found Lossless.dll at: %s", dll.absolutePath)
return dll
}
}

// Fallback: search for any directory containing Lossless.dll in Steam paths
for (basePath in searchPaths) {
val baseDir = File(basePath)
if (!baseDir.exists() || !baseDir.isDirectory) continue

baseDir.listFiles()?.forEach { subDir ->
Comment thread
joshuatam marked this conversation as resolved.
if (subDir.isDirectory) {
val dll = File(subDir, LOSSLESS_DLL_NAME)
if (dll.isFile) {
Timber.tag(TAG).d("Found Lossless.dll in fallback search at: %s", dll.absolutePath)
return dll
}
}
}
}

Timber.tag(TAG).w("Lossless.dll not found in any Steam install path")
return null
}

// ---- Helpers -----------------------------------------------------------
Expand Down Expand Up @@ -507,6 +569,31 @@ object LsfgVkManager {

private fun formatFlowScale(value: Float): String =
String.format(Locale.US, "%.2f", value.coerceIn(0.25f, 1.0f))

/**
* Delete the Lossless Scaling container if it exists.
* This container is no longer needed since we copy the DLL directly from
* the Steam install directory instead of creating a container for app 993090.
* This saves storage space.
*/
private fun deleteLosslessScalingContainerIfExists(context: Context) {
try {
val containerManager = ContainerManager(context)
val losslessContainer = containerManager.getContainerById("STEAM_$LOSSLESS_SCALING_APP_ID")
if (losslessContainer != null) {
Timber.tag(TAG).i("Deleting Lossless Scaling container to save storage")
if (FileUtils.delete(losslessContainer.rootDir)) {
containerManager.containers.remove(losslessContainer)
Timber.tag(TAG).i("Successfully deleted Lossless Scaling container")
Comment thread
joshuatam marked this conversation as resolved.
} else {
Timber.tag(TAG).w("Failed to delete Lossless Scaling container directory")
}
}
} catch (t: Throwable) {
Timber.tag(TAG).w(t, "Error while trying to delete Lossless Scaling container")
}
}

// ---- Runtime hot-reload -----------------------------------------------

/**
Expand Down
Binary file not shown.
Loading