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
14 changes: 13 additions & 1 deletion app/src/main/java/app/gamenative/gamefixes/GameFixesRegistry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ object GameFixesRegistry {
EPIC_Fix_59a0c86d02da42e8ba6444cb171e61bf,
).associateBy { it.gameSource to it.gameId }

private var fixesProvider: () -> Map<Pair<GameSource, String>, GameFix> = { fixes }

fun applyFor(context: Context, appId: String, container: Container) {
val source = ContainerUtils.extractGameSourceFromContainerId(appId)
val gameId = ContainerUtils.extractGameIdFromContainerId(appId)?.toString() ?: return
Expand All @@ -53,7 +55,7 @@ object GameFixesRegistry {
else -> gameId
}
Timber.i("GameFixesRegistry: Applying fixes for game: $source $catalogId if available")
val fix = fixes[source to catalogId] ?: return
val fix = fixesProvider()[source to catalogId] ?: return
val (installPath, installPathWindows) = resolvePaths(context, source, gameId) ?: return
fix.apply(context, catalogId, installPath, installPathWindows, container)
}
Expand All @@ -80,4 +82,14 @@ object GameFixesRegistry {
else -> null
}
}

/**
* Test-only hook to override the game-fixes provider.
* Not intended for production code paths.
*
* @param provider Fixes provider for tests; pass null to restore the default provider.
*/
internal fun setFixesProviderForTests(provider: (() -> Map<Pair<GameSource, String>, GameFix>)?) {
fixesProvider = provider ?: { fixes }
}
}
14 changes: 13 additions & 1 deletion app/src/main/java/app/gamenative/utils/LaunchDependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ class LaunchDependencies {
private val launchDependencies: List<LaunchDependency> = listOf(
GogScriptInterpreterDependency,
)

private var dependenciesProvider: () -> List<LaunchDependency> = { launchDependencies }
}

fun getLaunchDependencies(container: Container, gameSource: GameSource, gameId: Int): List<LaunchDependency> =
launchDependencies.filter { it.appliesTo(container, gameSource, gameId) }
dependenciesProvider().filter { it.appliesTo(container, gameSource, gameId) }

suspend fun ensureLaunchDependencies(
context: Context,
Expand All @@ -46,4 +48,14 @@ class LaunchDependencies {
setLoadingProgress(1f)
}
}

/**
* Test-only hook to override the launch dependency provider.
* Not intended for production code paths.
*
* @param provider Dependency provider for tests; pass null to restore the default provider.
*/
internal fun setDependenciesProviderForTests(provider: (() -> List<LaunchDependency>)?) {
dependenciesProvider = provider ?: { launchDependencies }
}
}
21 changes: 16 additions & 5 deletions app/src/main/java/app/gamenative/utils/PreInstallSteps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package app.gamenative.utils

import app.gamenative.data.GameSource
import app.gamenative.enums.Marker
import app.gamenative.service.gog.GOGService
import com.winlator.container.Container
import java.io.File

Expand Down Expand Up @@ -33,7 +32,9 @@ object PreInstallSteps {
UbisoftConnectStep,
)

private val allMarkers = steps.map { it.marker }.distinct()
private var stepsProvider: () -> List<PreInstallStep> = { steps }
private fun currentSteps(): List<PreInstallStep> = stepsProvider()
private fun allMarkers(): List<Marker> = currentSteps().map { it.marker }.distinct()

/**
* Returns a list of pre-install commands (marker + guest executable). Each entry is a
Expand All @@ -53,7 +54,7 @@ object PreInstallSteps {

val commands = mutableListOf<PreInstallCommand>()

for (step in steps) {
for (step in currentSteps()) {
if (step.appliesTo(
container = container,
gameSource = gameSource,
Expand Down Expand Up @@ -83,7 +84,7 @@ object PreInstallSteps {
fun markAllDone(container: Container) {
val gameDir = getGameDir(container) ?: return
val gameDirPath = gameDir.absolutePath
for (marker in allMarkers) {
for (marker in allMarkers()) {
MarkerUtils.addMarker(gameDirPath, marker)
}
}
Expand All @@ -95,7 +96,7 @@ object PreInstallSteps {
}

private fun resetMarkers(gameDirPath: String) {
for (marker in allMarkers) {
for (marker in allMarkers()) {
MarkerUtils.removeMarker(gameDirPath, marker)
}
}
Expand All @@ -111,4 +112,14 @@ object PreInstallSteps {
}
return null
}

/**
* Test-only hook to override the pre-install step provider.
* Not intended for production code paths.
*
* @param provider Steps provider for tests; pass null to restore the default provider.
*/
internal fun setStepsProviderForTests(provider: (() -> List<PreInstallStep>)?) {
stepsProvider = provider ?: { steps }
}
}
112 changes: 112 additions & 0 deletions app/src/test/java/app/gamenative/gamefixes/GameFixesRegistryTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package app.gamenative.gamefixes

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import app.gamenative.data.GOGGame
import app.gamenative.service.gog.GOGService
import com.winlator.container.Container
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.runs
import io.mockk.unmockkAll
import io.mockk.verify
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import kotlin.io.path.createTempDirectory

@RunWith(RobolectricTestRunner::class)
class GameFixesRegistryTest {
private lateinit var context: Context
private lateinit var container: Container

@Before
fun setUp() {
context = ApplicationProvider.getApplicationContext()
container = mockk(relaxed = true)
mockkObject(GOGService.Companion)
}

@After
fun tearDown() {
GameFixesRegistry.setFixesProviderForTests(null)
unmockkAll()
}
Comment thread
unbelievableflavour marked this conversation as resolved.

@Test
fun applyFor_usesInjectedFixesProviderForTests() {
val installDir = createTempDirectory(prefix = "registry-gog-injected-fix").toFile()
every { GOGService.getGOGGameOf("999") } returns GOGGame(
id = "999",
title = "Injected",
isInstalled = true,
installPath = installDir.absolutePath,
)
val fakeFix = mockk<GameFix>()
every {
fakeFix.apply(
context,
"999",
installDir.absolutePath,
"A:\\",
container,
)
} returns true
GameFixesRegistry.setFixesProviderForTests {
mapOf((app.gamenative.data.GameSource.GOG to "999") to fakeFix)
}

GameFixesRegistry.applyFor(context, "GOG_999", container)

verify(exactly = 1) {
fakeFix.apply(
context,
"999",
installDir.absolutePath,
"A:\\",
container,
)
}
}

@Test
fun applyFor_appliesKnownGogLaunchArgFix_whenInstalledGamePathExists() {
val installDir = createTempDirectory(prefix = "registry-gog-fix").toFile()
every { GOGService.getGOGGameOf("1129934535") } returns GOGGame(
id = "1129934535",
title = "Mars: War Logs",
isInstalled = true,
installPath = installDir.absolutePath,
)
every { container.execArgs } returns ""
every { container.execArgs = any() } just runs
every { container.saveData() } just runs

GameFixesRegistry.applyFor(context, "GOG_1129934535", container)

verify(exactly = 1) { container.execArgs = "-lang=eng" }
verify(exactly = 1) { container.saveData() }
}

@Test
fun applyFor_doesNothing_whenGogGameIsNotInstalled() {
every { GOGService.getGOGGameOf("1129934535") } returns GOGGame(
id = "1129934535",
title = "Mars: War Logs",
isInstalled = false,
installPath = "",
)
every { container.execArgs } returns "-novid"

GameFixesRegistry.applyFor(context, "GOG_1129934535", container)

verify(exactly = 0) { container.execArgs = "-lang=eng" }
verify(exactly = 0) { container.saveData() }
assertEquals("-novid", container.execArgs)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package app.gamenative.gamefixes

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import app.gamenative.data.GameSource
import app.gamenative.service.gog.GOGDownloadManager
import app.gamenative.service.gog.GOGService
import com.winlator.container.Container
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkAll
import io.mockk.verify
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.io.File
import kotlin.io.path.createTempDirectory

@RunWith(RobolectricTestRunner::class)
class GOGDependencyFixTest {
private lateinit var context: Context
private lateinit var container: Container

@Before
fun setUp() {
context = ApplicationProvider.getApplicationContext()
container = mockk(relaxed = true)
mockkObject(GOGService.Companion)
}

@After
fun tearDown() {
unmockkAll()
}
Comment thread
unbelievableflavour marked this conversation as resolved.

@Test
fun apply_returnsTrue_withoutDownloading_whenDependencyAlreadyInstalled() {
val installDir = createTempDirectory(prefix = "gog-dep-fix-satisfied").toFile()
val marker = File(installDir, "_CommonRedist/ISI/scriptinterpreter.exe")
marker.parentFile?.mkdirs()
marker.writeText("ok")
val fix = GOGDependencyFix(GameSource.GOG, "123", listOf("ISI"))

val result = fix.apply(context, "123", installDir.absolutePath, "A:\\", container)

assertTrue(result)
verify(exactly = 0) { GOGService.getInstance() }
}

@Test
fun apply_returnsFalse_whenUnsatisfied_andServiceUnavailable() {
val installDir = createTempDirectory(prefix = "gog-dep-fix-no-service").toFile()
val fix = GOGDependencyFix(GameSource.GOG, "123", listOf("ISI"))
every { GOGService.getInstance() } returns null

val result = fix.apply(context, "123", installDir.absolutePath, "A:\\", container)

assertFalse(result)
}

@Test
fun apply_downloadsDependencies_whenUnsatisfied_andServiceAvailable() {
val installDir = createTempDirectory(prefix = "gog-dep-fix-download").toFile()
val fix = GOGDependencyFix(GameSource.GOG, "123", listOf("ISI"))
val service = mockk<GOGService>()
val downloadManager = mockk<GOGDownloadManager>()
every { GOGService.getInstance() } returns service
every { service.gogDownloadManager } returns downloadManager
coEvery {
downloadManager.downloadDependenciesWithProgress(
gameId = "123",
dependencies = listOf("ISI"),
gameDir = installDir,
supportDir = File(installDir, "_CommonRedist"),
onProgress = null,
)
} returns Result.success(Unit)

val result = fix.apply(context, "123", installDir.absolutePath, "A:\\", container)

assertTrue(result)
coVerify(exactly = 1) {
downloadManager.downloadDependenciesWithProgress(
gameId = "123",
dependencies = listOf("ISI"),
gameDir = installDir,
supportDir = File(installDir, "_CommonRedist"),
onProgress = null,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package app.gamenative.gamefixes

import app.gamenative.data.GameSource
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test

class KeyedGameFixTypesTest {
@Test
fun keyedRegistryFix_exposesSourceAndId_forRegistryLookup() {
val fix = STEAM_Fix_22330

assertTrue(fix is KeyedRegistryKeyFix)
assertEquals(GameSource.STEAM, fix.gameSource)
assertEquals("22330", fix.gameId)
}

@Test
fun keyedLaunchArgFix_exposesSourceAndId_forRegistryLookup() {
val fix = GOG_Fix_1129934535

assertTrue(fix is KeyedLaunchArgFix)
assertEquals(GameSource.GOG, fix.gameSource)
assertEquals("1129934535", fix.gameId)
}

@Test
fun gogDependencyFix_exposesSourceAndId_forRegistryLookup() {
val fix = GOG_Fix_2147483047

assertTrue(fix is GOGDependencyFix)
assertEquals(GameSource.GOG, fix.gameSource)
assertEquals("2147483047", fix.gameId)
}
}
Loading
Loading