Read media type and pass it to UninstallProgram

This commit is contained in:
PabloMK7 2026-03-08 13:51:29 +01:00 committed by OpenSauce
parent e878174df8
commit 32da5ea0ae
6 changed files with 78 additions and 33 deletions

View File

@ -25,6 +25,7 @@ import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.citra.citra_emu.activities.EmulationActivity
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.Log
@ -132,7 +133,27 @@ object NativeLibrary {
* If not set, it auto-detects a location
*/
external fun setUserDirectory(directory: String)
external fun getInstalledGamePaths(): Array<String?>
data class InstalledGame(
val path: String,
val mediaType: Game.MediaType
)
fun getInstalledGamePaths(): Array<InstalledGame> {
val games = getInstalledGamePathsImpl()
return games.mapNotNull { entry ->
entry?.let {
val sep = it.lastIndexOf('|')
if (sep == -1) return@mapNotNull null
val path = it.substring(0, sep)
val mediaType = Game.MediaType.fromInt(it.substring(sep + 1).toInt())
InstalledGame(path, mediaType!!)
}
}.toTypedArray()
}
private external fun getInstalledGamePathsImpl(): Array<String?>
// Create the config.ini file.
external fun createConfigFile()
@ -230,7 +251,10 @@ object NativeLibrary {
external fun playTimeManagerGetPlayTime(titleId: Long): Long
external fun playTimeManagerGetCurrentTitleId(): Long
external fun uninstallTitle(titleId: Long): Boolean
private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean {
return uninstallTitle(titleId, mediaType.value)
}
private var coreErrorAlertResult = false
private val coreErrorAlertLock = Object()

View File

@ -330,9 +330,9 @@ class GameAdapter(
popup.setOnMenuItemClickListener { menuItem ->
val uninstallAction: () -> Unit = {
when (menuItem.itemId) {
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(titleId)
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId)
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId)
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(titleId, game.mediaType)
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId, Game.MediaType.SDMC)
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId, Game.MediaType.SDMC)
}
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
bottomSheetDialog.dismiss()

View File

@ -133,14 +133,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
var intentGame: Game? = null
if (intentUri != null) {
intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) {
GameHelper.getGame(intentUri, isInstalled = false, addedToLibrary = false)
// isInstalled, addedToLibrary and mediaType do not matter here
GameHelper.getGame(intentUri, isInstalled = false, addedToLibrary = false, mediaType = Game.MediaType.GAME_CARD)
} else {
null
}
} else if (oldIntentInfo.first != null) {
val gameUri = Uri.parse(oldIntentInfo.first)
intentGame = if (Game.extensions.contains(FileUtil.getExtension(gameUri))) {
GameHelper.getGame(gameUri, isInstalled = false, addedToLibrary = false)
// isInstalled, addedToLibrary and mediaType do not matter here
GameHelper.getGame(gameUri, isInstalled = false, addedToLibrary = false, mediaType = Game.MediaType.GAME_CARD)
} else {
null
}

View File

@ -20,6 +20,7 @@ class Game(
val description: String = "",
val path: String = "",
val titleId: Long = 0L,
val mediaType: MediaType = MediaType.GAME_CARD,
val company: String = "",
val regions: String = "",
val isInstalled: Boolean = false,
@ -58,10 +59,23 @@ class Game(
result = 31 * result + regions.hashCode()
result = 31 * result + path.hashCode()
result = 31 * result + titleId.hashCode()
result = 31 * result + mediaType.hashCode()
result = 31 * result + company.hashCode()
return result
}
enum class MediaType(val value: Int) {
NAND(0),
SDMC(1),
GAME_CARD(2);
companion object {
fun fromInt(value: Int): MediaType? {
return MediaType.entries.find { it.value == value }
}
}
}
companion object {
val allExtensions: Set<String> get() = extensions + badExtensions

View File

@ -32,7 +32,7 @@ object GameHelper {
addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
NativeLibrary.getInstalledGamePaths().forEach {
games.add(getGame(Uri.parse(it), isInstalled = true, addedToLibrary = true))
games.add(getGame(Uri.parse(it.path), isInstalled = true, addedToLibrary = true, it.mediaType))
}
// Cache list of games found on disk
@ -62,13 +62,13 @@ object GameHelper {
addGamesRecursive(games, FileUtil.listFiles(it.uri), depth - 1)
} else {
if (Game.allExtensions.contains(FileUtil.getExtension(it.uri))) {
games.add(getGame(it.uri, isInstalled = false, addedToLibrary = true))
games.add(getGame(it.uri, isInstalled = false, addedToLibrary = true, Game.MediaType.GAME_CARD))
}
}
}
}
fun getGame(uri: Uri, isInstalled: Boolean, addedToLibrary: Boolean): Game {
fun getGame(uri: Uri, isInstalled: Boolean, addedToLibrary: Boolean, mediaType: Game.MediaType): Game {
val filePath = uri.toString()
var nativePath: String? = null
var gameInfo: GameInfo?
@ -95,6 +95,7 @@ object GameHelper {
nativePath!!
},
gameInfo?.getTitleID() ?: 0,
mediaType,
gameInfo?.getCompany() ?: "",
if (isEncrypted) { CitraApplication.appContext.getString(R.string.unsupported_encrypted) } else { gameInfo?.getRegions() ?: "" },
isInstalled,

View File

@ -652,34 +652,38 @@ void Java_org_citra_citra_1emu_NativeLibrary_setUserDirectory(JNIEnv* env,
FileUtil::SetCurrentDir(GetJString(env, j_directory));
}
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getInstalledGamePaths(
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getInstalledGamePathsImpl(
JNIEnv* env, [[maybe_unused]] jclass clazz) {
std::vector<std::string> games;
const FileUtil::DirectoryEntryCallable ScanDir =
[&games, &ScanDir](u64*, const std::string& directory, const std::string& virtual_name) {
std::string path = directory + virtual_name;
if (FileUtil::IsDirectory(path)) {
path += '/';
FileUtil::ForeachDirectoryEntry(nullptr, path, ScanDir);
} else {
if (!FileUtil::Exists(path))
return false;
auto loader = Loader::GetLoader(path);
if (loader) {
bool executable{};
const Loader::ResultStatus result = loader->IsExecutable(executable);
if (Loader::ResultStatus::Success == result && executable) {
games.emplace_back(path);
}
Service::FS::MediaType media_type;
const FileUtil::DirectoryEntryCallable ScanDir = [&games, &ScanDir, &media_type](
u64*, const std::string& directory,
const std::string& virtual_name) {
std::string path = directory + virtual_name;
if (FileUtil::IsDirectory(path)) {
path += '/';
FileUtil::ForeachDirectoryEntry(nullptr, path, ScanDir);
} else {
if (!FileUtil::Exists(path))
return false;
auto loader = Loader::GetLoader(path);
if (loader) {
bool executable{};
const Loader::ResultStatus result = loader->IsExecutable(executable);
if (Loader::ResultStatus::Success == result && executable) {
games.emplace_back(path + "|" + std::to_string(static_cast<int>(media_type)));
}
}
return true;
};
}
return true;
};
media_type = Service::FS::MediaType::SDMC;
ScanDir(nullptr, "",
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
"Nintendo "
"3DS/00000000000000000000000000000000/"
"00000000000000000000000000000000/title/00040000");
media_type = Service::FS::MediaType::NAND;
ScanDir(nullptr, "",
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"00000000000000000000000000000000/title/00040010");
@ -1108,10 +1112,10 @@ void Java_org_citra_citra_1emu_NativeLibrary_setInsertedCartridge(JNIEnv* env, j
}
jboolean Java_org_citra_citra_1emu_NativeLibrary_uninstallTitle(JNIEnv* env, jobject obj,
jlong j_titleid) {
const auto titleid = static_cast<long long>(env, j_titleid);
// TODO: Don't hard-code to SDMC? (CBA to pass a value from Kotlin at the moment) -OS
const auto result = Service::AM::UninstallProgram(Service::FS::MediaType::SDMC, titleid);
jlong j_titleid, jint j_mediatype) {
const auto titleid = static_cast<u64>(j_titleid);
const auto result =
Service::AM::UninstallProgram(static_cast<Service::FS::MediaType>(j_mediatype), titleid);
if (result.IsError()) {
LOG_ERROR(Frontend, "Failed to uninstall '{}': 0x{:08X}", std::to_string(titleid),
result.raw);