diff --git a/app/src/main/java/app/gamenative/service/SteamService.kt b/app/src/main/java/app/gamenative/service/SteamService.kt index 897015dc4d..7d5470c060 100644 --- a/app/src/main/java/app/gamenative/service/SteamService.kt +++ b/app/src/main/java/app/gamenative/service/SteamService.kt @@ -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 @@ -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) { + 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)) } } } diff --git a/app/src/main/java/app/gamenative/utils/LsfgVkManager.kt b/app/src/main/java/app/gamenative/utils/LsfgVkManager.kt index 352237cbb3..3fce1697c8 100644 --- a/app/src/main/java/app/gamenative/utils/LsfgVkManager.kt +++ b/app/src/main/java/app/gamenative/utils/LsfgVkManager.kt @@ -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 @@ -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 -------------------------------------------------------- @@ -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() } @@ -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() @@ -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 -> + 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 ----------------------------------------------------------- @@ -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") + } 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 ----------------------------------------------- /** diff --git a/app/src/main/assets/lsfg_vk/android_arm64_v8a/liblsfg-vk-layer.so b/app/src/main/jniLibs/arm64-v8a/liblsfg-vk-layer.so similarity index 52% rename from app/src/main/assets/lsfg_vk/android_arm64_v8a/liblsfg-vk-layer.so rename to app/src/main/jniLibs/arm64-v8a/liblsfg-vk-layer.so index 9ce3449679..02026ea997 100755 Binary files a/app/src/main/assets/lsfg_vk/android_arm64_v8a/liblsfg-vk-layer.so and b/app/src/main/jniLibs/arm64-v8a/liblsfg-vk-layer.so differ