aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorProducdevity <y.gherbi.dev@gmail.com>2025-08-31 03:33:54 +0200
committercrueter <crueter@eden-emu.dev>2025-08-31 03:33:54 +0200
commit39e27bc954dd16d296266eaa08ea613e8e5da38d (patch)
tree9ea89310c5e23e8d02246956a34e6823cd2017ad /src
parent21c77bdcac2390c6b15091296786a9239c6ca529 (diff)
[android] fix intent-auto-driver-install (#369)
Resolving drivers based on the artifact name was too buggy and inconsistent, this PR improves it. Well, I like to think it does Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/369 Reviewed-by: crueter <crueter@eden-emu.dev> Reviewed-by: Lizzie <lizzie@eden-emu.dev> Co-authored-by: Producdevity <y.gherbi.dev@gmail.com> Co-committed-by: Producdevity <y.gherbi.dev@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt94
2 files changed, 102 insertions, 19 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt
index a317be14d5..377313d0aa 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt
@@ -124,11 +124,16 @@ object CustomSettingsHandler {
// Check for driver requirements if activity and driverViewModel are provided
if (activity != null && driverViewModel != null) {
- val driverPath = extractDriverPath(customSettings)
- if (driverPath != null) {
- Log.info("[CustomSettingsHandler] Custom settings specify driver: $driverPath")
+ val rawDriverPath = extractDriverPath(customSettings)
+ if (rawDriverPath != null) {
+ // Normalize to local storage path (we only store drivers under driverStoragePath)
+ val driverFilename = rawDriverPath.substringAfterLast('/')
+ .substringAfterLast('\\')
+ val localDriverPath = "${GpuDriverHelper.driverStoragePath}$driverFilename"
+ Log.info("[CustomSettingsHandler] Custom settings specify driver: $rawDriverPath (normalized: $localDriverPath)")
+
// Check if driver exists in the driver storage
- val driverFile = File(driverPath)
+ val driverFile = File(localDriverPath)
if (!driverFile.exists()) {
Log.info("[CustomSettingsHandler] Driver not found locally: ${driverFile.name}")
@@ -182,7 +187,7 @@ object CustomSettingsHandler {
}
// Attempt to download and install the driver
- val driverUri = DriverResolver.ensureDriverAvailable(driverPath, activity) { progress ->
+ val driverUri = DriverResolver.ensureDriverAvailable(driverFilename, activity) { progress ->
progressChannel.trySend(progress.toInt())
}
@@ -209,12 +214,12 @@ object CustomSettingsHandler {
return null
}
- // Verify the downloaded driver
- val installedFile = File(driverPath)
+ // Verify the downloaded driver (from normalized local path)
+ val installedFile = File(localDriverPath)
val metadata = GpuDriverHelper.getMetadataFromZip(installedFile)
if (metadata.name == null) {
Log.error(
- "[CustomSettingsHandler] Downloaded driver is invalid: $driverPath"
+ "[CustomSettingsHandler] Downloaded driver is invalid: $localDriverPath"
)
Toast.makeText(
activity,
@@ -232,7 +237,7 @@ object CustomSettingsHandler {
}
// Add to driver list
- driverViewModel.onDriverAdded(Pair(driverPath, metadata))
+ driverViewModel.onDriverAdded(Pair(localDriverPath, metadata))
Log.info(
"[CustomSettingsHandler] Successfully downloaded and installed driver: ${metadata.name}"
)
@@ -268,7 +273,7 @@ object CustomSettingsHandler {
// Driver exists, verify it's valid
val metadata = GpuDriverHelper.getMetadataFromZip(driverFile)
if (metadata.name == null) {
- Log.error("[CustomSettingsHandler] Invalid driver file: $driverPath")
+ Log.error("[CustomSettingsHandler] Invalid driver file: $localDriverPath")
Toast.makeText(
activity,
activity.getString(
@@ -459,6 +464,8 @@ object CustomSettingsHandler {
if (inGpuDriverSection && trimmed.startsWith("driver_path=")) {
return trimmed.substringAfter("driver_path=")
+ .trim()
+ .removeSurrounding("\"", "\"")
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt
index 74f98ccbd2..2072344bdf 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt
@@ -68,6 +68,48 @@ object DriverResolver {
val filename: String
)
+ // Matching helpers
+ private val KNOWN_SUFFIXES = listOf(
+ ".adpkg.zip",
+ ".zip",
+ ".7z",
+ ".tar.gz",
+ ".tar.xz",
+ ".rar"
+ )
+
+ private fun stripKnownSuffixes(name: String): String {
+ var result = name
+ var changed: Boolean
+ do {
+ changed = false
+ for (s in KNOWN_SUFFIXES) {
+ if (result.endsWith(s, ignoreCase = true)) {
+ result = result.dropLast(s.length)
+ changed = true
+ }
+ }
+ } while (changed)
+ return result
+ }
+
+ private fun normalizeName(name: String): String {
+ val base = stripKnownSuffixes(name.lowercase())
+ // Remove non-alphanumerics to make substring checks resilient
+ return base.replace(Regex("[^a-z0-9]+"), " ").trim()
+ }
+
+ private fun tokenize(name: String): Set<String> =
+ normalizeName(name).split(Regex("\\s+")).filter { it.isNotBlank() }.toSet()
+
+ // Jaccard similarity between two sets
+ private fun jaccard(a: Set<String>, b: Set<String>): Double {
+ if (a.isEmpty() || b.isEmpty()) return 0.0
+ val inter = a.intersect(b).size.toDouble()
+ val uni = a.union(b).size.toDouble()
+ return if (uni == 0.0) 0.0 else inter / uni
+ }
+
/**
* Resolve a driver download URL from its filename
* @param filename The driver filename (e.g., "turnip_mrpurple-T19-toasted.adpkg.zip")
@@ -98,7 +140,7 @@ object DriverResolver {
async {
searchRepository(repoPath, filename)
}
- }.mapNotNull { it.await() }.firstOrNull().also { resolved ->
+ }.firstNotNullOfOrNull { it.await() }.also { resolved ->
// Cache the result if found
resolved?.let {
urlCache[filename] = it
@@ -119,22 +161,56 @@ object DriverResolver {
releaseCache[repoPath] = it
}
- // Search through all releases and artifacts
+ // First pass: exact name (case-insensitive) against asset filenames
+ val target = filename.lowercase()
for (release in releases) {
for (artifact in release.artifacts) {
- if (artifact.name == filename) {
- Log.info(
- "[DriverResolver] Found $filename in $repoPath/${release.tagName}"
- )
+ if (artifact.name.equals(filename, ignoreCase = true) || artifact.name.lowercase() == target) {
+ Log.info("[DriverResolver] Found $filename in $repoPath/${release.tagName}")
return@withContext ResolvedDriver(
downloadUrl = artifact.url.toString(),
repoPath = repoPath,
releaseTag = release.tagName,
- filename = filename
+ filename = artifact.name
+ )
+ }
+ }
+ }
+
+ // Second pass: fuzzy match by asset filenames only
+ val reqNorm = normalizeName(filename)
+ val reqTokens = tokenize(filename)
+ var best: ResolvedDriver? = null
+ var bestScore = 0.0
+
+ for (release in releases) {
+ for (artifact in release.artifacts) {
+ val artNorm = normalizeName(artifact.name)
+ val artTokens = tokenize(artifact.name)
+
+ var score = jaccard(reqTokens, artTokens)
+ // Boost if one normalized name contains the other
+ if (artNorm.contains(reqNorm) || reqNorm.contains(artNorm)) {
+ score = maxOf(score, 0.92)
+ }
+
+ if (score > bestScore) {
+ bestScore = score
+ best = ResolvedDriver(
+ downloadUrl = artifact.url.toString(),
+ repoPath = repoPath,
+ releaseTag = release.tagName,
+ filename = artifact.name
)
}
}
}
+
+ // Threshold to avoid bad guesses, this worked fine in testing but might need tuning
+ if (best != null && bestScore >= 0.6) {
+ Log.info("[DriverResolver] Fuzzy matched $filename -> ${best.filename} in ${best.repoPath} (score=%.2f)".format(bestScore))
+ return@withContext best
+ }
null
} catch (e: Exception) {
Log.error("[DriverResolver] Failed to search $repoPath: ${e.message}")
@@ -296,8 +372,8 @@ object DriverResolver {
context: Context,
onProgress: ((Float) -> Unit)? = null
): Uri? {
- // Extract filename from path
- val filename = driverPath.substringAfterLast('/')
+ // Extract filename from path (support both separators)
+ val filename = driverPath.substringAfterLast('/').substringAfterLast('\\')
// Check if driver already exists locally
val localPath = "${GpuDriverHelper.driverStoragePath}$filename"