summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java/org
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2026-08-03 20:02:47 +0200
committerJosJuice <josjuice@gmail.com>2026-08-15 10:44:28 +0200
commit49ebb1cfea589e66134039d4ecca911e0e14af43 (patch)
treee962b2bdb71fc41738cfc90b1d02273def13136e /Source/Android/app/src/main/java/org
parentc64b02dace5e5e71dae9fbfbd258ffa3562573ef (diff)
Android: Use TitleDatabase for game list
By using the title database we ship with Dolphin, we get more uniform game titles than if we grab what's in each game's opening.bnr file. It also becomes possible for users to supply a custom titles.txt file. This has been implemented in DolphinQt since a long time ago.
Diffstat (limited to 'Source/Android/app/src/main/java/org')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt5
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt2
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt4
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt6
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt2
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TitleDatabase.kt17
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt35
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt5
8 files changed, 64 insertions, 12 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt
index dbcfdd42e2..e7ea8e5519 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt
@@ -61,13 +61,14 @@ class GameAdapter : RecyclerView.Adapter<GameViewHolder>(),
val gameFile = mGameFiles[position]
holder.apply {
+ val title = gameFile.getTitle(GameFileCacheManager.getTitleDatabase())
if (BooleanSetting.MAIN_SHOW_GAME_TITLES.boolean) {
- binding.textGameTitle.text = gameFile.getTitle()
+ binding.textGameTitle.text = title
binding.textGameTitle.visibility = View.VISIBLE
binding.textGameTitleInner.visibility = View.GONE
binding.textGameCaption.visibility = View.VISIBLE
} else {
- binding.textGameTitleInner.text = gameFile.getTitle()
+ binding.textGameTitleInner.text = title
binding.textGameTitleInner.visibility = View.VISIBLE
binding.textGameTitle.visibility = View.GONE
binding.textGameCaption.visibility = View.GONE
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt
index 4f43601e68..7cbb3000e2 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt
@@ -48,7 +48,7 @@ class GameRowPresenter : Presenter() {
holder.apply {
imageScreenshot.setImageDrawable(null)
- cardParent.titleText = gameFile.getTitle()
+ cardParent.titleText = gameFile.getTitle(GameFileCacheManager.getTitleDatabase())
holder.gameFile = gameFile
// Set the background color of the card
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt
index 0384da89c0..b97fd987e6 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt
@@ -51,7 +51,7 @@ class GameDetailsDialog : DialogFragment() {
textTimePlayed.visibility = View.GONE
}
- textGameTitle.text = gameFile.getTitle()
+ textGameTitle.text = gameFile.getTitle(GameFileCacheManager.getTitleDatabase())
textDescription.text = gameFile.getDescription()
if (gameFile.getDescription().isEmpty()) {
textDescription.visibility = View.GONE
@@ -118,7 +118,7 @@ class GameDetailsDialog : DialogFragment() {
textTimePlayed.visibility = View.GONE
}
- textGameTitle.text = gameFile.getTitle()
+ textGameTitle.text = gameFile.getTitle(GameFileCacheManager.getTitleDatabase())
textDescription.text = gameFile.getDescription()
if (gameFile.getDescription().isEmpty()) {
tvBinding.textDescription.visibility = View.GONE
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt
index 789a3cef17..3c641c6695 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt
@@ -104,6 +104,7 @@ import org.dolphinemu.dolphinemu.features.netplay.model.Player
import org.dolphinemu.dolphinemu.features.netplay.model.SaveTransferProgress
import org.dolphinemu.dolphinemu.features.netplay.model.TraversalState
import org.dolphinemu.dolphinemu.model.GameFile
+import org.dolphinemu.dolphinemu.services.GameFileCacheManager
import org.dolphinemu.dolphinemu.ui.theme.DolphinScaffold
import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme
import org.dolphinemu.dolphinemu.ui.theme.MenuSpacer
@@ -753,6 +754,7 @@ private fun GameGridItem(
gameFile: GameFile,
onClick: () -> Unit,
) {
+ val titleDatabase = GameFileCacheManager.getTitleDatabase()
Card(
onClick = onClick,
) {
@@ -762,7 +764,7 @@ private fun GameGridItem(
.data(gameFile)
.error(R.drawable.no_banner)
.build(),
- contentDescription = gameFile.getTitle(),
+ contentDescription = gameFile.getTitle(titleDatabase),
contentScale = ContentScale.Crop,
imageLoader = CoilUtils.imageLoader,
modifier = Modifier
@@ -770,7 +772,7 @@ private fun GameGridItem(
.aspectRatio(0.7f)
)
Text(
- text = gameFile.getTitle(),
+ text = gameFile.getTitle(titleDatabase),
style = MaterialTheme.typography.bodySmall,
maxLines = 2,
minLines = 2,
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt
index 26da174285..4794f5c1b7 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt
@@ -12,7 +12,7 @@ class GameFile private constructor(private val pointer: Long) {
external fun getPlatform(): Int
- external fun getTitle(): String
+ external fun getTitle(titleDatabase: TitleDatabase?): String
external fun getDescription(): String
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TitleDatabase.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TitleDatabase.kt
new file mode 100644
index 0000000000..e5089673b8
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TitleDatabase.kt
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.model
+
+import androidx.annotation.Keep
+
+@Keep
+class TitleDatabase private constructor(private val pointer: Long) {
+ external fun finalize()
+
+ external fun userTitleMapEquals(other: TitleDatabase?): Boolean
+
+ companion object {
+ @JvmStatic
+ external suspend fun load(): TitleDatabase
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt
index 51ce4a4f2f..a5e54aa0de 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt
@@ -7,6 +7,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asFlow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.async
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -14,10 +15,12 @@ import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
import org.dolphinemu.dolphinemu.features.settings.model.ConfigChangedCallback
import org.dolphinemu.dolphinemu.model.GameFile
import org.dolphinemu.dolphinemu.model.GameFileCache
+import org.dolphinemu.dolphinemu.model.TitleDatabase
import org.dolphinemu.dolphinemu.ui.platform.Platform
import org.dolphinemu.dolphinemu.ui.platform.PlatformTab
import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner
import java.util.Arrays
+import java.util.concurrent.atomic.AtomicReference
/**
* Loads game list data in the background.
@@ -25,6 +28,7 @@ import java.util.Arrays
object GameFileCacheManager {
private var gameFileCache: GameFileCache? = null
private val gameFiles = MutableLiveData(emptyArray<GameFile>())
+ private var titleDatabase = AtomicReference<TitleDatabase>(null)
private var firstLoadDone = false
private var recursiveScanEnabled = false
@@ -88,6 +92,8 @@ object GameFileCacheManager {
}
}
+ fun getTitleDatabase(): TitleDatabase? = titleDatabase.get()
+
/**
* Returns true if in the process of loading the cache for the first time.
*/
@@ -171,13 +177,22 @@ object GameFileCacheManager {
*/
private suspend fun load() {
if (!firstLoadDone) {
+ val titleDatabaseJob = MainScope().async(Dispatchers.IO) {
+ TitleDatabase.load()
+ }
+
withContext(Dispatchers.IO) {
setUpAutomaticRescan()
+
gameFileCache!!.load()
+
+ titleDatabase.set(titleDatabaseJob.await())
+
if (gameFileCache!!.getSize() != 0) {
updateGameFileArray()
}
}
+
firstLoadDone = true
}
@@ -196,16 +211,29 @@ object GameFileCacheManager {
loadInProgress.asFlow().first { !it }
}
+ val titleDatabaseJob = MainScope().async(Dispatchers.IO) {
+ val newTitleDatabase = TitleDatabase.load()
+ val titleDatabaseChanged =
+ !newTitleDatabase.userTitleMapEquals(titleDatabase.get())
+ if (titleDatabaseChanged) newTitleDatabase else null
+ }
+
withContext(Dispatchers.IO) {
val gamePaths = GameFileCache.getAllGamePaths()
-
val changed = gameFileCache!!.update(gamePaths)
if (changed) {
updateGameFileArray()
}
val additionalMetadataChanged = gameFileCache!!.updateAdditionalMetadata()
- if (additionalMetadataChanged) {
+
+ val newTitleDatabase = titleDatabaseJob.await()
+ val titleDatabaseChanged = newTitleDatabase != null
+ if (titleDatabaseChanged) {
+ titleDatabase.set(newTitleDatabase)
+ }
+
+ if (additionalMetadataChanged || titleDatabaseChanged) {
updateGameFileArray()
}
@@ -219,8 +247,9 @@ object GameFileCacheManager {
private fun updateGameFileArray() {
val gameFilesTemp = gameFileCache!!.getAllGames()
+ val titleDatabase = titleDatabase.get()
Arrays.sort(gameFilesTemp) { lhs, rhs ->
- lhs.getTitle().compareTo(rhs.getTitle(), ignoreCase = true)
+ lhs.getTitle(titleDatabase).compareTo(rhs.getTitle(titleDatabase), ignoreCase = true)
}
gameFiles.postValue(gameFilesTemp)
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt
index 2d9ad354fb..fd13ff3175 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt
@@ -138,7 +138,10 @@ class SyncProgramsJobService : JobService() {
TvContractCompat.PreviewPrograms.COLUMN_TYPE,
TvContractCompat.PreviewProgramColumns.TYPE_GAME
)
- values.put(TvContract.Programs.COLUMN_TITLE, game.getTitle())
+ values.put(
+ TvContract.Programs.COLUMN_TITLE,
+ game.getTitle(GameFileCacheManager.getTitleDatabase())
+ )
values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, game.getDescription())
values.put(TvContract.Programs.COLUMN_POSTER_ART_URI, banner.toString())
values.put(