feat: Refactor autoloadContent to streamline directory scanning for NSPs

This commit is contained in:
Jochem Kuipers 2025-10-27 12:37:16 +01:00
parent 42fa39672b
commit 4e94f4731b

View file

@ -15,8 +15,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.kenjinx.android.KenjinxNative import org.kenjinx.android.KenjinxNative
import org.kenjinx.android.KeyboardMode
import org.kenjinx.android.MainActivity import org.kenjinx.android.MainActivity
import java.io.File
import java.util.Locale import java.util.Locale
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -125,7 +125,7 @@ class HomeViewModel(
// Helper function to compare update versions from filenames // Helper function to compare update versions from filenames
// Returns true if newPath represents a newer version than currentPath // Returns true if newPath represents a newer version than currentPath
private fun shouldSelectNewerUpdate(currentPath: String, newPath: String, allPaths: List<String>): Boolean { private fun shouldSelectNewerUpdate(currentPath: String, newPath: String): Boolean {
// Extract version numbers from filenames using regex pattern [vXXXXXX] // Extract version numbers from filenames using regex pattern [vXXXXXX]
val versionPattern = Regex("\\[v(\\d+)]") val versionPattern = Regex("\\[v(\\d+)]")
@ -135,21 +135,13 @@ class HomeViewModel(
return newVersion > currentVersion return newVersion > currentVersion
} }
// Scans configured directories for NSPs containing DLCs/Updates and associates them to known titles. // Scans configured directory for NSPs containing DLCs/Updates and associates them to known titles.
private fun autoloadContent() { private fun autoloadContent() {
val prefs = sharedPref ?: return val prefs = sharedPref ?: return
// Prefer a single, explicitly selected updates/DLC folder (updatesFolder). Fallback to legacy autoloadDirs.
val updatesFolder = prefs.getString("updatesFolder", "") ?: "" val updatesFolder = prefs.getString("updatesFolder", "") ?: ""
val dirs: List<String> = if (updatesFolder.isNotBlank()) {
listOf(updatesFolder.trim())
} else {
// Legacy: semicolon-separated list under key autoloadDirs
val raw = prefs.getString("autoloadDirs", "") ?: ""
if (raw.isBlank()) emptyList() else raw.split(';').map { it.trim() }.filter { it.isNotEmpty() }
}
if (dirs.isEmpty()) return if (updatesFolder.isEmpty()) return
// Build a map of titleId -> helpers // Build a map of titleId -> helpers
val gamesByTitle = loadedCache.mapNotNull { g -> val gamesByTitle = loadedCache.mapNotNull { g ->
@ -160,85 +152,83 @@ class HomeViewModel(
var updatesAdded = 0 var updatesAdded = 0
var dlcAdded = 0 var dlcAdded = 0
for (dir in dirs) { val base = File(updatesFolder)
val base = java.io.File(dir) if (!base.exists() || !base.isDirectory) return
if (!base.exists() || !base.isDirectory) continue
base.walkTopDown().forEach fileLoop@{ f -> base.walkTopDown().forEach fileLoop@{ f ->
if (!f.isFile) return@fileLoop if (!f.isFile) return@fileLoop
val name = f.name.lowercase(Locale.getDefault()) val name = f.name.lowercase(Locale.getDefault())
if (!name.endsWith(".nsp")) return@fileLoop if (!name.endsWith(".nsp")) return@fileLoop
// Extract title ID from filename // Extract title ID from filename
val tidPattern = Regex("\\[([0-9a-fA-F]{16})]") val tidPattern = Regex("\\[([0-9a-fA-F]{16})]")
val tidMatch = tidPattern.find(name) ?: return@fileLoop val tidMatch = tidPattern.find(name) ?: return@fileLoop
val fileTid = tidMatch.groupValues[1].lowercase(Locale.getDefault()) val fileTid = tidMatch.groupValues[1].lowercase(Locale.getDefault())
// Try to find DLC content for all games // Try to find DLC content for all games
var isDlc = false var isDlc = false
try { try {
for ((_, tidOrig) in gamesByTitle) { for ((_, tidOrig) in gamesByTitle) {
val contents = KenjinxNative.deviceGetDlcContentList(f.absolutePath, tidOrig.toLong(16)) val contents = KenjinxNative.deviceGetDlcContentList(f.absolutePath, tidOrig.toLong(16))
if (contents.isNotEmpty()) { if (contents.isNotEmpty()) {
isDlc = true isDlc = true
val containerPath = f.absolutePath val containerPath = f.absolutePath
val vm = DlcViewModel(tidOrig) val vm = DlcViewModel(tidOrig)
val already = vm.data?.any { it.path == containerPath } == true val already = vm.data?.any { it.path == containerPath } == true
if (!already) { if (!already) {
val container = DlcContainerList(containerPath) val container = DlcContainerList(containerPath)
for (content in contents) { for (content in contents) {
container.dlc_nca_list.add( container.dlc_nca_list.add(
DlcContainer( DlcContainer(
true, true,
KenjinxNative.deviceGetDlcTitleId(containerPath, content).toLong(16), KenjinxNative.deviceGetDlcTitleId(containerPath, content).toLong(16),
content content
)
) )
} )
vm.data?.add(container)
vm.saveChanges()
dlcAdded++
} }
break vm.data?.add(container)
vm.saveChanges()
dlcAdded++
} }
break
} }
} catch (_: Throwable) { }
if (isDlc) return@fileLoop
// Treat as Title Update - convert update ID to base ID
// Update title IDs end in 800, base game IDs end in 000
val baseTid = if (fileTid.endsWith("800")) {
fileTid.substring(0, fileTid.length - 3) + "000"
} else {
fileTid
} }
} catch (_: Throwable) { }
val originalTid = gamesByTitle[baseTid] if (isDlc) return@fileLoop
if (originalTid != null) {
val vm = TitleUpdateViewModel(originalTid)
val path = f.absolutePath
val exists = (vm.data?.paths?.contains(path) == true)
if (!exists) { // Treat as Title Update - convert update ID to base ID
// Add the new update path // Update title IDs end in 800, base game IDs end in 000
vm.data?.paths?.add(path) val baseTid = if (fileTid.endsWith("800")) {
fileTid.substring(0, fileTid.length - 3) + "000"
} else {
fileTid
}
// Auto-select this update if it's newer than the currently selected one val originalTid = gamesByTitle[baseTid]
// or if no update is currently selected if (originalTid != null) {
val currentSelected = vm.data?.selected ?: "" val vm = TitleUpdateViewModel(originalTid)
val shouldSelect = currentSelected.isEmpty() || val path = f.absolutePath
shouldSelectNewerUpdate(currentSelected, path, vm.data?.paths ?: mutableListOf()) val exists = (vm.data?.paths?.contains(path) == true)
if (shouldSelect) { if (!exists) {
vm.data?.selected = path // Add the new update path
} vm.data?.paths?.add(path)
vm.saveChanges() // Auto-select this update if it's newer than the currently selected one
updatesAdded++ // or if no update is currently selected
val currentSelected = vm.data?.selected ?: ""
val shouldSelect = currentSelected.isEmpty() ||
shouldSelectNewerUpdate(currentSelected, path)
if (shouldSelect) {
vm.data?.selected = path
} }
vm.saveChanges()
updatesAdded++
} }
} }
} }