Browse Source
style: remove unused DriverResolver and refactor driver handling in CustomSettingsHandler
style: remove unused DriverResolver and refactor driver handling in CustomSettingsHandler
- Deleted `DriverResolver` - Moved and streamlined driver path extraction logic into `CustomSettingsHandler`. - Improved string resource usage and ensured consistent formatting across dialogs.pull/49/head
3 changed files with 122 additions and 409 deletions
-
3src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
-
156src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt
-
372src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt
@ -1,372 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later |
|
||||
|
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
package org.yuzu.yuzu_emu.utils |
|
||||
|
|
||||
import android.widget.Toast |
|
||||
import androidx.core.net.toUri |
|
||||
import androidx.fragment.app.FragmentActivity |
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder |
|
||||
import kotlinx.coroutines.Dispatchers |
|
||||
import kotlinx.coroutines.withContext |
|
||||
import okhttp3.OkHttpClient |
|
||||
import okhttp3.Request |
|
||||
import org.yuzu.yuzu_emu.R |
|
||||
import org.yuzu.yuzu_emu.fragments.DriverFetcherFragment |
|
||||
import org.yuzu.yuzu_emu.fragments.DriverFetcherFragment.SortMode |
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel |
|
||||
import java.io.File |
|
||||
import java.io.FileOutputStream |
|
||||
import java.io.IOException |
|
||||
import kotlin.coroutines.resume |
|
||||
import kotlin.coroutines.suspendCoroutine |
|
||||
|
|
||||
object DriverResolver { |
|
||||
private val client = OkHttpClient() |
|
||||
|
|
||||
// Mirror of the repositories from DriverFetcherFragment |
|
||||
private val repoList = listOf( |
|
||||
DriverRepo("Mr. Purple Turnip", "MrPurple666/purple-turnip", 0), |
|
||||
DriverRepo("GameHub Adreno 8xx", "crueter/GameHub-8Elite-Drivers", 1), |
|
||||
DriverRepo("KIMCHI Turnip", "K11MCH1/AdrenoToolsDrivers", 2, true), |
|
||||
DriverRepo("Weab-Chan Freedreno", "Weab-chan/freedreno_turnip-CI", 3) |
|
||||
) |
|
||||
|
|
||||
private data class DriverRepo( |
|
||||
val name: String, |
|
||||
val path: String, |
|
||||
val sort: Int, |
|
||||
val useTagName: Boolean = false |
|
||||
) |
|
||||
|
|
||||
/** |
|
||||
* Extract driver path from custom settings INI content |
|
||||
*/ |
|
||||
fun extractDriverPath(customSettings: String): String? { |
|
||||
val lines = customSettings.lines() |
|
||||
var inGpuDriverSection = false |
|
||||
|
|
||||
for (line in lines) { |
|
||||
val trimmed = line.trim() |
|
||||
if (trimmed.startsWith("[") && trimmed.endsWith("]")) { |
|
||||
inGpuDriverSection = trimmed == "[GpuDriver]" |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
if (inGpuDriverSection && trimmed.startsWith("driver_path=")) { |
|
||||
return trimmed.substringAfter("driver_path=") |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return null |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Check if a driver exists and handle missing drivers |
|
||||
*/ |
|
||||
suspend fun ensureDriverExists( |
|
||||
driverPath: String, |
|
||||
activity: FragmentActivity, |
|
||||
driverViewModel: DriverViewModel |
|
||||
): Boolean { |
|
||||
Log.info("[DriverResolver] Checking driver path: $driverPath") |
|
||||
|
|
||||
val driverFile = File(driverPath) |
|
||||
if (driverFile.exists()) { |
|
||||
Log.info("[DriverResolver] Driver exists at: $driverPath") |
|
||||
return true |
|
||||
} |
|
||||
|
|
||||
Log.warning("[DriverResolver] Driver not found: $driverPath") |
|
||||
|
|
||||
// Extract driver name from path |
|
||||
val driverName = extractDriverNameFromPath(driverPath) |
|
||||
if (driverName == null) { |
|
||||
Log.error("[DriverResolver] Could not extract driver name from path") |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
Log.info("[DriverResolver] Searching for downloadable driver: $driverName") |
|
||||
|
|
||||
// Check if driver exists locally with different path |
|
||||
val localDriver = findLocalDriver(driverName) |
|
||||
if (localDriver != null) { |
|
||||
Log.info("[DriverResolver] Found local driver: ${localDriver.first}") |
|
||||
// The game can use this local driver, no need to download |
|
||||
return true |
|
||||
} |
|
||||
|
|
||||
// Search for downloadable driver |
|
||||
val downloadableDriver = findDownloadableDriver(driverName) |
|
||||
if (downloadableDriver != null) { |
|
||||
Log.info("[DriverResolver] Found downloadable driver: ${downloadableDriver.name}") |
|
||||
|
|
||||
val shouldInstall = askUserToInstallDriver(activity, downloadableDriver.name) |
|
||||
if (shouldInstall) { |
|
||||
return downloadAndInstallDriver(activity, downloadableDriver, driverViewModel) |
|
||||
} |
|
||||
} else { |
|
||||
Log.warning("[DriverResolver] No downloadable driver found for: $driverName") |
|
||||
showDriverNotFoundDialog(activity, driverName) |
|
||||
} |
|
||||
|
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Extract driver name from full path |
|
||||
*/ |
|
||||
private fun extractDriverNameFromPath(driverPath: String): String? { |
|
||||
val file = File(driverPath) |
|
||||
val fileName = file.name |
|
||||
|
|
||||
// Remove .zip extension and extract meaningful name |
|
||||
if (fileName.endsWith(".zip")) { |
|
||||
return fileName.substring(0, fileName.length - 4) |
|
||||
} |
|
||||
|
|
||||
return fileName |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Find driver in local storage by name matching |
|
||||
*/ |
|
||||
private fun findLocalDriver(driverName: String): Pair<String, GpuDriverMetadata>? { |
|
||||
val availableDrivers = GpuDriverHelper.getDrivers() |
|
||||
|
|
||||
// Try exact match first |
|
||||
availableDrivers.find { (_, metadata) -> |
|
||||
metadata.name?.contains(driverName, ignoreCase = true) == true |
|
||||
}?.let { return it } |
|
||||
|
|
||||
// Try partial match |
|
||||
availableDrivers.find { (path, metadata) -> |
|
||||
path.contains(driverName, ignoreCase = true) || |
|
||||
metadata.name?.contains( |
|
||||
extractKeywords(driverName).first(), |
|
||||
ignoreCase = true |
|
||||
) == true |
|
||||
}?.let { return it } |
|
||||
|
|
||||
return null |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Extract keywords from driver name for matching |
|
||||
*/ |
|
||||
private fun extractKeywords(driverName: String): List<String> { |
|
||||
val keywords = mutableListOf<String>() |
|
||||
|
|
||||
// Common driver patterns |
|
||||
when { |
|
||||
driverName.contains("turnip", ignoreCase = true) -> keywords.add("turnip") |
|
||||
driverName.contains("purple", ignoreCase = true) -> keywords.add("purple") |
|
||||
driverName.contains("kimchi", ignoreCase = true) -> keywords.add("kimchi") |
|
||||
driverName.contains("freedreno", ignoreCase = true) -> keywords.add("freedreno") |
|
||||
driverName.contains("gamehub", ignoreCase = true) -> keywords.add("gamehub") |
|
||||
} |
|
||||
|
|
||||
// Version patterns |
|
||||
Regex("v?\\d+\\.\\d+\\.\\d+").find(driverName)?.value?.let { keywords.add(it) } |
|
||||
|
|
||||
if (keywords.isEmpty()) { |
|
||||
keywords.add(driverName) |
|
||||
} |
|
||||
|
|
||||
return keywords |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Find downloadable driver that matches the required driver |
|
||||
*/ |
|
||||
private suspend fun findDownloadableDriver(driverName: String): DriverFetcherFragment.Artifact? { |
|
||||
val keywords = extractKeywords(driverName) |
|
||||
|
|
||||
for (repo in repoList) { |
|
||||
// Check if this repo is relevant based on driver name |
|
||||
val isRelevant = keywords.any { keyword -> |
|
||||
repo.name.contains(keyword, ignoreCase = true) || |
|
||||
keyword.contains(repo.name.split(" ").first(), ignoreCase = true) |
|
||||
} |
|
||||
|
|
||||
if (!isRelevant) continue |
|
||||
|
|
||||
try { |
|
||||
val releases = fetchReleases(repo) |
|
||||
val latestRelease = releases.firstOrNull { !it.prerelease } |
|
||||
|
|
||||
latestRelease?.artifacts?.forEach { artifact -> |
|
||||
if (matchesDriverName(artifact.name, driverName, keywords)) { |
|
||||
return artifact |
|
||||
} |
|
||||
} |
|
||||
} catch (e: Exception) { |
|
||||
Log.error( |
|
||||
"[DriverResolver] Failed to fetch releases for ${repo.name}: ${e.message}" |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return null |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Check if artifact name matches the required driver |
|
||||
*/ |
|
||||
private fun matchesDriverName( |
|
||||
artifactName: String, |
|
||||
requiredName: String, |
|
||||
keywords: List<String> |
|
||||
): Boolean { |
|
||||
// Exact match |
|
||||
if (artifactName.equals(requiredName, ignoreCase = true)) return true |
|
||||
|
|
||||
// Keyword matching |
|
||||
return keywords.any { keyword -> |
|
||||
artifactName.contains(keyword, ignoreCase = true) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Fetch releases from GitHub repo |
|
||||
*/ |
|
||||
private suspend fun fetchReleases(repo: DriverRepo): List<DriverFetcherFragment.Release> { |
|
||||
return withContext(Dispatchers.IO) { |
|
||||
val request = Request.Builder() |
|
||||
.url("https://api.github.com/repos/${repo.path}/releases") |
|
||||
.build() |
|
||||
|
|
||||
client.newCall(request).execute().use { response -> |
|
||||
if (!response.isSuccessful) { |
|
||||
throw IOException("Failed to fetch releases: ${response.code}") |
|
||||
} |
|
||||
|
|
||||
val body = response.body?.string() ?: throw IOException("Empty response") |
|
||||
DriverFetcherFragment.Release.fromJsonArray(body, repo.useTagName, SortMode.Default) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Ask user if they want to install the missing driver |
|
||||
*/ |
|
||||
private suspend fun askUserToInstallDriver( |
|
||||
activity: FragmentActivity, |
|
||||
driverName: String |
|
||||
): Boolean { |
|
||||
return suspendCoroutine { continuation -> |
|
||||
activity.runOnUiThread { |
|
||||
MaterialAlertDialogBuilder(activity) |
|
||||
.setTitle(activity.getString(R.string.missing_gpu_driver_title)) |
|
||||
.setMessage(activity.getString(R.string.missing_gpu_driver_message, driverName)) |
|
||||
.setPositiveButton(activity.getString(R.string.install)) { _, _ -> |
|
||||
continuation.resume(true) |
|
||||
} |
|
||||
.setNegativeButton(activity.getString(R.string.cancel)) { _, _ -> |
|
||||
continuation.resume(false) |
|
||||
} |
|
||||
.setCancelable(false) |
|
||||
.show() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Download and install driver automatically |
|
||||
*/ |
|
||||
private suspend fun downloadAndInstallDriver( |
|
||||
activity: FragmentActivity, |
|
||||
artifact: DriverFetcherFragment.Artifact, |
|
||||
driverViewModel: DriverViewModel |
|
||||
): Boolean { |
|
||||
return try { |
|
||||
Log.info("[DriverResolver] Downloading driver: ${artifact.name}") |
|
||||
Toast.makeText( |
|
||||
activity, |
|
||||
activity.getString(R.string.downloading_driver), |
|
||||
Toast.LENGTH_SHORT |
|
||||
).show() |
|
||||
|
|
||||
val cacheDir = |
|
||||
activity.externalCacheDir ?: throw IOException("Cache directory not available") |
|
||||
cacheDir.mkdirs() |
|
||||
|
|
||||
val file = File(cacheDir, artifact.name) |
|
||||
|
|
||||
// Download the driver |
|
||||
withContext(Dispatchers.IO) { |
|
||||
val request = Request.Builder() |
|
||||
.url(artifact.url) |
|
||||
.header("Accept", "application/octet-stream") |
|
||||
.build() |
|
||||
|
|
||||
client.newBuilder() |
|
||||
.followRedirects(true) |
|
||||
.followSslRedirects(true) |
|
||||
.build() |
|
||||
.newCall(request).execute().use { response -> |
|
||||
if (!response.isSuccessful) { |
|
||||
throw IOException("Download failed: ${response.code}") |
|
||||
} |
|
||||
|
|
||||
response.body?.byteStream()?.use { input -> |
|
||||
FileOutputStream(file).use { output -> |
|
||||
input.copyTo(output) |
|
||||
} |
|
||||
} ?: throw IOException("Empty response body") |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (file.length() == 0L) { |
|
||||
throw IOException("Downloaded file is empty") |
|
||||
} |
|
||||
|
|
||||
// Install the driver on main thread |
|
||||
withContext(Dispatchers.Main) { |
|
||||
val driverData = GpuDriverHelper.getMetadataFromZip(file) |
|
||||
val driverPath = "${GpuDriverHelper.driverStoragePath}${file.name}" |
|
||||
|
|
||||
if (GpuDriverHelper.copyDriverToInternalStorage(file.toUri())) { |
|
||||
driverViewModel.onDriverAdded(Pair(driverPath, driverData)) |
|
||||
Log.info("[DriverResolver] Successfully installed driver: ${driverData.name}") |
|
||||
Toast.makeText( |
|
||||
activity, |
|
||||
activity.getString(R.string.driver_installed), |
|
||||
Toast.LENGTH_SHORT |
|
||||
).show() |
|
||||
true |
|
||||
} else { |
|
||||
throw IOException("Failed to install driver") |
|
||||
} |
|
||||
} |
|
||||
} catch (e: Exception) { |
|
||||
Log.error("[DriverResolver] Failed to download/install driver: ${e.message}") |
|
||||
withContext(Dispatchers.Main) { |
|
||||
MaterialAlertDialogBuilder(activity) |
|
||||
.setTitle(activity.getString(R.string.driver_installation_failed_title)) |
|
||||
.setMessage( |
|
||||
activity.getString(R.string.driver_installation_failed_message, e.message) |
|
||||
) |
|
||||
.setPositiveButton(activity.getString(R.string.ok)) { dialog, _ -> dialog.dismiss() } |
|
||||
.show() |
|
||||
} |
|
||||
false |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Show dialog when driver cannot be found |
|
||||
*/ |
|
||||
private fun showDriverNotFoundDialog(activity: FragmentActivity, driverName: String) { |
|
||||
activity.runOnUiThread { |
|
||||
MaterialAlertDialogBuilder(activity) |
|
||||
.setTitle(activity.getString(R.string.driver_not_available_title)) |
|
||||
.setMessage(activity.getString(R.string.driver_not_available_message, driverName)) |
|
||||
.setPositiveButton(activity.getString(R.string.ok)) { dialog, _ -> dialog.dismiss() } |
|
||||
.show() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue