Implement firmware game checker on Android
All checks were successful
eden-license / license-header (pull_request) Successful in 15s
All checks were successful
eden-license / license-header (pull_request) Successful in 15s
Signed-off-by: crueter <crueter@eden-emu.dev>
This commit is contained in:
parent
d702d6d7bf
commit
67edb897bf
11 changed files with 126 additions and 128 deletions
|
@ -273,37 +273,6 @@ object NativeLibrary {
|
||||||
|
|
||||||
external fun initMultiplayer()
|
external fun initMultiplayer()
|
||||||
|
|
||||||
// TODO(crueter): Implement this--may need to implant it into the loader
|
|
||||||
@Keep
|
|
||||||
@JvmStatic
|
|
||||||
fun gameRequiresFirmware() {
|
|
||||||
val emulationActivity = sEmulationActivity.get()
|
|
||||||
if (emulationActivity == null) {
|
|
||||||
Log.warning("[NativeLibrary] EmulationActivity is null, can't exit.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val builder = MaterialAlertDialogBuilder(emulationActivity)
|
|
||||||
.setTitle(R.string.loader_requires_firmware)
|
|
||||||
.setMessage(
|
|
||||||
Html.fromHtml(
|
|
||||||
emulationActivity.getString(R.string.loader_requires_firmware_description),
|
|
||||||
Html.FROM_HTML_MODE_LEGACY
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
|
||||||
emulationActivity.finish()
|
|
||||||
}
|
|
||||||
.setOnDismissListener { emulationActivity.finish() }
|
|
||||||
|
|
||||||
emulationActivity.runOnUiThread {
|
|
||||||
val alert = builder.create()
|
|
||||||
alert.show()
|
|
||||||
(alert.findViewById<View>(android.R.id.message) as TextView).movementMethod =
|
|
||||||
LinkMovementMethod.getInstance()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun exitEmulationActivity(resultCode: Int) {
|
fun exitEmulationActivity(resultCode: Int) {
|
||||||
|
@ -454,12 +423,21 @@ object NativeLibrary {
|
||||||
external fun verifyFirmware(): Int
|
external fun verifyFirmware(): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs decryption keys from the specified path.
|
* Check if a game requires firmware to be playable.
|
||||||
|
*
|
||||||
|
* @param programId The game's Program ID.
|
||||||
|
* @return Whether or not the game requires firmware to be playable.
|
||||||
|
*/
|
||||||
|
external fun gameRequiresFirmware(programId: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs keys from the specified path.
|
||||||
*
|
*
|
||||||
* @param path The path to install keys from.
|
* @param path The path to install keys from.
|
||||||
|
* @param ext What extension the keys should have.
|
||||||
* @return The result code.
|
* @return The result code.
|
||||||
*/
|
*/
|
||||||
external fun installDecryptionKeys(path: String): Int
|
external fun installKeys(path: String, ext: String): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the PatchManager for any addons that are available
|
* Checks the PatchManager for any addons that are available
|
||||||
|
|
|
@ -3,11 +3,16 @@
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.pm.ShortcutInfoCompat
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
|
@ -33,6 +38,10 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
|
|
||||||
class GameAdapter(private val activity: AppCompatActivity) :
|
class GameAdapter(private val activity: AppCompatActivity) :
|
||||||
AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>(exact = false) {
|
AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>(exact = false) {
|
||||||
|
@ -171,8 +180,9 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
||||||
fun onClick(game: Game) {
|
fun onClick(game: Game) {
|
||||||
val gameExists = DocumentFile.fromSingleUri(
|
val gameExists = DocumentFile.fromSingleUri(
|
||||||
YuzuApplication.appContext,
|
YuzuApplication.appContext,
|
||||||
Uri.parse(game.path)
|
game.path.toUri()
|
||||||
)?.exists() == true
|
)?.exists() == true
|
||||||
|
|
||||||
if (!gameExists) {
|
if (!gameExists) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
YuzuApplication.appContext,
|
YuzuApplication.appContext,
|
||||||
|
@ -184,29 +194,49 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val preferences =
|
val launch: () -> Unit = {
|
||||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
val preferences =
|
||||||
preferences.edit()
|
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||||
.putLong(
|
preferences.edit {
|
||||||
game.keyLastPlayedTime,
|
putLong(
|
||||||
System.currentTimeMillis()
|
game.keyLastPlayedTime,
|
||||||
)
|
System.currentTimeMillis()
|
||||||
.apply()
|
)
|
||||||
|
|
||||||
activity.lifecycleScope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val shortcut =
|
|
||||||
ShortcutInfoCompat.Builder(YuzuApplication.appContext, game.path)
|
|
||||||
.setShortLabel(game.title)
|
|
||||||
.setIcon(GameIconUtils.getShortcutIcon(activity, game))
|
|
||||||
.setIntent(game.launchIntent)
|
|
||||||
.build()
|
|
||||||
ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activity.lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val shortcut =
|
||||||
|
ShortcutInfoCompat.Builder(YuzuApplication.appContext, game.path)
|
||||||
|
.setShortLabel(game.title)
|
||||||
|
.setIcon(GameIconUtils.getShortcutIcon(activity, game))
|
||||||
|
.setIntent(game.launchIntent)
|
||||||
|
.build()
|
||||||
|
ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val action = HomeNavigationDirections.actionGlobalEmulationActivity(game, true)
|
||||||
|
binding.root.findNavController().navigate(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
val action = HomeNavigationDirections.actionGlobalEmulationActivity(game, true)
|
if (NativeLibrary.gameRequiresFirmware(game.programId) && !NativeLibrary.isFirmwareAvailable()) {
|
||||||
binding.root.findNavController().navigate(action)
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.loader_requires_firmware)
|
||||||
|
.setMessage(
|
||||||
|
Html.fromHtml(
|
||||||
|
activity.getString(R.string.loader_requires_firmware_description),
|
||||||
|
Html.FROM_HTML_MODE_LEGACY
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||||
|
launch()
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _,_ -> }
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
launch()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLongClick(game: Game): Boolean {
|
fun onLongClick(game: Game): Boolean {
|
||||||
|
|
|
@ -478,6 +478,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startEmulation(programIndex: Int = 0) {
|
private fun startEmulation(programIndex: Int = 0) {
|
||||||
|
println("PROGRAM INDEX: $programIndex")
|
||||||
if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
|
if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
|
||||||
if (!DirectoryInitialization.areDirectoriesReady) {
|
if (!DirectoryInitialization.areDirectoriesReady) {
|
||||||
DirectoryInitialization.start()
|
DirectoryInitialization.start()
|
||||||
|
|
|
@ -352,7 +352,7 @@ class SetupFragment : Fragment() {
|
||||||
val getProdKey =
|
val getProdKey =
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
mainActivity.processKey(result)
|
mainActivity.processKey(result, "keys")
|
||||||
if (NativeLibrary.areKeysPresent()) {
|
if (NativeLibrary.areKeysPresent()) {
|
||||||
keyCallback.onStepCompleted()
|
keyCallback.onStepCompleted()
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,9 +47,6 @@ import info.debatty.java.stringsimilarity.Jaccard
|
||||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
|
||||||
import android.view.ViewParent
|
|
||||||
import androidx.core.view.doOnNextLayout
|
import androidx.core.view.doOnNextLayout
|
||||||
|
|
||||||
class GamesFragment : Fragment() {
|
class GamesFragment : Fragment() {
|
||||||
|
@ -151,7 +148,7 @@ class GamesFragment : Fragment() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
gamesViewModel.games.collect(viewLifecycleOwner) {
|
gamesViewModel.games.collect(viewLifecycleOwner) {
|
||||||
if (it.size > 0) {
|
if (it.isNotEmpty()) {
|
||||||
setAdapter(it)
|
setAdapter(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,7 +358,7 @@ class GamesFragment : Fragment() {
|
||||||
|
|
||||||
popup.setOnMenuItemClickListener { item ->
|
popup.setOnMenuItemClickListener { item ->
|
||||||
currentFilter = item.itemId
|
currentFilter = item.itemId
|
||||||
preferences.edit().putInt(PREF_SORT_TYPE, currentFilter).apply()
|
preferences.edit { putInt(PREF_SORT_TYPE, currentFilter) }
|
||||||
filterAndSearch()
|
filterAndSearch()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ package org.yuzu.yuzu_emu.ui.main
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.ParcelFileDescriptor
|
||||||
|
import android.provider.OpenableColumns
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
@ -47,6 +49,7 @@ import java.io.BufferedOutputStream
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import androidx.core.net.toFile
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), ThemeProvider {
|
class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
@ -328,12 +331,18 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
|
|
||||||
val getProdKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
val getProdKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
processKey(result)
|
processKey(result, "keys")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processKey(result: Uri): Boolean {
|
val getAmiiboKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
if (FileUtil.getExtension(result) != "keys") {
|
if (result != null) {
|
||||||
|
processKey(result, "bin", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun processKey(result: Uri, extension: String = "keys", check: Boolean = true): Boolean {
|
||||||
|
if (FileUtil.getExtension(result) != extension) {
|
||||||
MessageDialogFragment.newInstance(
|
MessageDialogFragment.newInstance(
|
||||||
this,
|
this,
|
||||||
titleId = R.string.keys_failed,
|
titleId = R.string.keys_failed,
|
||||||
|
@ -348,23 +357,26 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
|
|
||||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||||
if (FileUtil.copyUriToInternalStorage(
|
if (FileUtil.copyUriToInternalStorage(
|
||||||
result, dstPath, "prod.keys"
|
result, dstPath, ""
|
||||||
) != null
|
) != null
|
||||||
) {
|
) {
|
||||||
if (NativeLibrary.reloadKeys()) {
|
if (NativeLibrary.reloadKeys()) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
applicationContext, R.string.keys_install_success, Toast.LENGTH_SHORT
|
applicationContext, R.string.keys_install_success, Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
homeViewModel.setCheckKeys(true)
|
|
||||||
|
|
||||||
val firstTimeSetup =
|
if (check) {
|
||||||
PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
homeViewModel.setCheckKeys(true)
|
||||||
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
|
|
||||||
if (!firstTimeSetup) {
|
val firstTimeSetup =
|
||||||
homeViewModel.setCheckFirmware(true)
|
PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||||
|
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
|
||||||
|
if (!firstTimeSetup) {
|
||||||
|
homeViewModel.setCheckFirmware(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
gamesViewModel.reloadGames(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
gamesViewModel.reloadGames(true)
|
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
MessageDialogFragment.newInstance(
|
MessageDialogFragment.newInstance(
|
||||||
|
@ -381,11 +393,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
val getFirmware = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
val getFirmware = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
if (result == null) {
|
if (result != null) {
|
||||||
return@registerForActivityResult
|
processFirmware(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
processFirmware(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processFirmware(result: Uri, onComplete: (() -> Unit)? = null) {
|
fun processFirmware(result: Uri, onComplete: (() -> Unit)? = null) {
|
||||||
|
@ -458,44 +468,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
val getAmiiboKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
|
||||||
if (result == null) {
|
|
||||||
return@registerForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FileUtil.getExtension(result) != "bin") {
|
|
||||||
MessageDialogFragment.newInstance(
|
|
||||||
this,
|
|
||||||
titleId = R.string.keys_failed,
|
|
||||||
descriptionId = R.string.install_amiibo_keys_failure_extension_description
|
|
||||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
|
||||||
return@registerForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
contentResolver.takePersistableUriPermission(
|
|
||||||
result, Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
)
|
|
||||||
|
|
||||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
|
||||||
if (FileUtil.copyUriToInternalStorage(
|
|
||||||
result, dstPath, "key_retail.bin"
|
|
||||||
) != null
|
|
||||||
) {
|
|
||||||
if (NativeLibrary.reloadKeys()) {
|
|
||||||
Toast.makeText(
|
|
||||||
applicationContext, R.string.keys_install_success, Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
} else {
|
|
||||||
MessageDialogFragment.newInstance(
|
|
||||||
this,
|
|
||||||
titleId = R.string.keys_failed,
|
|
||||||
descriptionId = R.string.error_keys_failed_init,
|
|
||||||
helpLinkId = R.string.dumping_keys_quickstart_link
|
|
||||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val installGameUpdate = registerForActivityResult(
|
val installGameUpdate = registerForActivityResult(
|
||||||
ActivityResultContracts.OpenMultipleDocuments()
|
ActivityResultContracts.OpenMultipleDocuments()
|
||||||
) { documents: List<Uri> ->
|
) { documents: List<Uri> ->
|
||||||
|
|
|
@ -285,7 +285,6 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
|
||||||
.program_index = static_cast<s32>(program_index),
|
.program_index = static_cast<s32>(program_index),
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO(crueter): Place checks somewhere around here
|
|
||||||
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params);
|
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params);
|
||||||
if (m_load_result != Core::SystemResultStatus::Success) {
|
if (m_load_result != Core::SystemResultStatus::Success) {
|
||||||
return m_load_result;
|
return m_load_result;
|
||||||
|
@ -795,10 +794,17 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyFirmware(JNIEnv* env, jclass cl
|
||||||
return static_cast<int>(FirmwareManager::VerifyFirmware(EmulationSession::GetInstance().System()));
|
return static_cast<int>(FirmwareManager::VerifyFirmware(EmulationSession::GetInstance().System()));
|
||||||
}
|
}
|
||||||
|
|
||||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_installDecryptionKeys(JNIEnv* env, jclass clazz, jstring jpath) {
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_gameRequiresFirmware(JNIEnv* env, jclass clazz, jstring jprogramId) {
|
||||||
const auto path = Common::Android::GetJString(env, jpath);
|
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||||
|
|
||||||
return static_cast<int>(FirmwareManager::InstallDecryptionKeys(path));
|
return FirmwareManager::GameRequiresFirmware(program_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_installKeys(JNIEnv* env, jclass clazz, jstring jpath, jstring jext) {
|
||||||
|
const auto path = Common::Android::GetJString(env, jpath);
|
||||||
|
const auto ext = Common::Android::GetJString(env, jext);
|
||||||
|
|
||||||
|
return static_cast<int>(FirmwareManager::InstallKeys(path, ext));
|
||||||
}
|
}
|
||||||
|
|
||||||
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj,
|
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj,
|
||||||
|
|
|
@ -502,4 +502,11 @@
|
||||||
<item>@string/error_firmware_corrupted</item>
|
<item>@string/error_firmware_corrupted</item>
|
||||||
<item>@string/error_firmware_too_new</item>
|
<item>@string/error_firmware_too_new</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="installKeysResults">
|
||||||
|
<item>""</item>
|
||||||
|
<item>@string/error_keys_copy_failed</item>
|
||||||
|
<item>@string/error_keys_invalid_filename</item>
|
||||||
|
<item>@string/error_keys_failed_init</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "firmware_manager.h"
|
#include "firmware_manager.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
@ -9,12 +12,15 @@
|
||||||
#include "core/crypto/key_manager.h"
|
#include "core/crypto/key_manager.h"
|
||||||
#include "frontend_common/content_manager.h"
|
#include "frontend_common/content_manager.h"
|
||||||
|
|
||||||
FirmwareManager::KeyInstallResult FirmwareManager::InstallDecryptionKeys(std::string location)
|
FirmwareManager::KeyInstallResult FirmwareManager::InstallKeys(std::string location, std::string extension)
|
||||||
{
|
{
|
||||||
LOG_INFO(Frontend, "Installing key files from {}", location);
|
LOG_INFO(Frontend, "Installing key files from {}", location);
|
||||||
|
|
||||||
|
const auto keys_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::KeysDir);
|
||||||
|
|
||||||
const std::filesystem::path prod_key_path = location;
|
const std::filesystem::path prod_key_path = location;
|
||||||
const std::filesystem::path key_source_path = prod_key_path.parent_path();
|
const std::filesystem::path key_source_path = prod_key_path.parent_path();
|
||||||
|
|
||||||
if (!Common::FS::IsDir(key_source_path)) {
|
if (!Common::FS::IsDir(key_source_path)) {
|
||||||
return InvalidDir;
|
return InvalidDir;
|
||||||
}
|
}
|
||||||
|
@ -39,9 +45,8 @@ FirmwareManager::KeyInstallResult FirmwareManager::InstallDecryptionKeys(std::st
|
||||||
return ErrorWrongFilename;
|
return ErrorWrongFilename;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto yuzu_keys_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::KeysDir);
|
|
||||||
for (const auto &key_file : source_key_files) {
|
for (const auto &key_file : source_key_files) {
|
||||||
std::filesystem::path destination_key_file = yuzu_keys_dir / key_file.filename();
|
std::filesystem::path destination_key_file = keys_dir / key_file.filename();
|
||||||
if (!std::filesystem::copy_file(key_file,
|
if (!std::filesystem::copy_file(key_file,
|
||||||
destination_key_file,
|
destination_key_file,
|
||||||
std::filesystem::copy_options::overwrite_existing)) {
|
std::filesystem::copy_options::overwrite_existing)) {
|
||||||
|
@ -54,7 +59,6 @@ FirmwareManager::KeyInstallResult FirmwareManager::InstallDecryptionKeys(std::st
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reinitialize the key manager
|
// Reinitialize the key manager
|
||||||
|
|
||||||
Core::Crypto::KeyManager::Instance().ReloadKeys();
|
Core::Crypto::KeyManager::Instance().ReloadKeys();
|
||||||
|
|
||||||
if (ContentManager::AreKeysPresent()) {
|
if (ContentManager::AreKeysPresent()) {
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#ifndef FIRMWARE_MANAGER_H
|
#ifndef FIRMWARE_MANAGER_H
|
||||||
#define FIRMWARE_MANAGER_H
|
#define FIRMWARE_MANAGER_H
|
||||||
|
|
||||||
|
@ -15,7 +18,6 @@
|
||||||
#include "core/hle/service/set/system_settings_server.h"
|
#include "core/hle/service/set/system_settings_server.h"
|
||||||
#include "core/hle/result.h"
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
|
|
||||||
namespace FirmwareManager {
|
namespace FirmwareManager {
|
||||||
|
|
||||||
static constexpr std::array<const char *, 5> KEY_INSTALL_RESULT_STRINGS = {
|
static constexpr std::array<const char *, 5> KEY_INSTALL_RESULT_STRINGS = {
|
||||||
|
@ -40,14 +42,15 @@ enum KeyInstallResult {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Installs decryption keys for the emulator.
|
* @brief Installs any arbitrary set of keys for the emulator.
|
||||||
* @param location Where the keys are located.
|
* @param location Where the keys are located.
|
||||||
|
* @param expected_extension What extension the file should have.
|
||||||
* @return A result code for the operation.
|
* @return A result code for the operation.
|
||||||
*/
|
*/
|
||||||
KeyInstallResult InstallDecryptionKeys(std::string location);
|
KeyInstallResult InstallKeys(std::string location, std::string expected_extension);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Get a string representation of a result from InstallDecryptionKeys.
|
* \brief Get a string representation of a result from InstallKeys.
|
||||||
* \param result The result code.
|
* \param result The result code.
|
||||||
* \return A string representation of the passed result code.
|
* \return A string representation of the passed result code.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -4355,13 +4355,13 @@ void GMainWindow::OnInstallDecryptionKeys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString key_source_location = QFileDialog::getOpenFileName(
|
const QString key_source_location = QFileDialog::getOpenFileName(
|
||||||
this, tr("Select Dumped Keys Location"), {}, QStringLiteral("prod.keys (prod.keys)"), {},
|
this, tr("Select Dumped Keys Location"), {}, QStringLiteral("Decryption Keys (*.keys)"), {},
|
||||||
QFileDialog::ReadOnly);
|
QFileDialog::ReadOnly);
|
||||||
if (key_source_location.isEmpty()) {
|
if (key_source_location.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FirmwareManager::KeyInstallResult result = FirmwareManager::InstallDecryptionKeys(key_source_location.toStdString());
|
FirmwareManager::KeyInstallResult result = FirmwareManager::InstallKeys(key_source_location.toStdString(), "keys");
|
||||||
|
|
||||||
system->GetFileSystemController().CreateFactories(*vfs);
|
system->GetFileSystemController().CreateFactories(*vfs);
|
||||||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue