From 93cf8c3090889e1228b94df4d171601d8f7dd7f0 Mon Sep 17 00:00:00 2001 From: bunnei Date: Sat, 4 Feb 2023 00:55:02 -0800 Subject: [PATCH] android: frontend: Integrate key installation for SAF. --- .../yuzu/yuzu_emu/ui/main/MainActivity.java | 30 ++++++++++++-- .../yuzu/yuzu_emu/ui/main/MainPresenter.java | 5 +++ .../yuzu_emu/utils/FileBrowserHelper.java | 9 +++++ .../org/yuzu/yuzu_emu/utils/FileUtil.java | 37 +++++++++++++++++- .../yuzu/yuzu_emu/utils/StartupHandler.java | 24 +++++++----- src/android/app/src/main/jni/native.cpp | 2 +- .../{ic_cia_install.png => ic_install.png} | Bin .../{ic_cia_install.png => ic_install.png} | Bin .../{ic_cia_install.png => ic_install.png} | Bin .../{ic_cia_install.png => ic_install.png} | Bin .../{ic_cia_install.png => ic_install.png} | Bin .../{ic_cia_install.png => ic_install.png} | Bin .../{ic_cia_install.png => ic_install.png} | Bin .../{ic_cia_install.png => ic_install.png} | Bin .../{ic_cia_install.png => ic_install.png} | Bin .../{ic_cia_install.png => ic_install.png} | Bin .../app/src/main/res/menu/menu_game_grid.xml | 6 +-- .../app/src/main/res/values/strings.xml | 6 ++- src/core/crypto/key_manager.cpp | 2 +- src/core/crypto/key_manager.h | 2 +- 20 files changed, 102 insertions(+), 21 deletions(-) rename src/android/app/src/main/res/drawable-hdpi/{ic_cia_install.png => ic_install.png} (100%) rename src/android/app/src/main/res/drawable-mdpi/{ic_cia_install.png => ic_install.png} (100%) rename src/android/app/src/main/res/drawable-night-hdpi/{ic_cia_install.png => ic_install.png} (100%) rename src/android/app/src/main/res/drawable-night-mdpi/{ic_cia_install.png => ic_install.png} (100%) rename src/android/app/src/main/res/drawable-night-xhdpi/{ic_cia_install.png => ic_install.png} (100%) rename src/android/app/src/main/res/drawable-night-xxhdpi/{ic_cia_install.png => ic_install.png} (100%) rename src/android/app/src/main/res/drawable-night-xxxhdpi/{ic_cia_install.png => ic_install.png} (100%) rename src/android/app/src/main/res/drawable-xhdpi/{ic_cia_install.png => ic_install.png} (100%) rename src/android/app/src/main/res/drawable-xxhdpi/{ic_cia_install.png => ic_install.png} (100%) rename src/android/app/src/main/res/drawable-xxxhdpi/{ic_cia_install.png => ic_install.png} (100%) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java index 26ff149142..7fdd692c2e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java @@ -6,18 +6,22 @@ import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import org.yuzu.yuzu_emu.NativeLibrary; import org.yuzu.yuzu_emu.R; import org.yuzu.yuzu_emu.activities.EmulationActivity; import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity; import org.yuzu.yuzu_emu.model.GameProvider; import org.yuzu.yuzu_emu.ui.platform.PlatformGamesFragment; import org.yuzu.yuzu_emu.utils.AddDirectoryHelper; +import org.yuzu.yuzu_emu.utils.DirectoryInitialization; import org.yuzu.yuzu_emu.utils.FileBrowserHelper; +import org.yuzu.yuzu_emu.utils.FileUtil; import org.yuzu.yuzu_emu.utils.PicassoUtils; import org.yuzu.yuzu_emu.utils.StartupHandler; import org.yuzu.yuzu_emu.utils.ThemeUtil; @@ -116,8 +120,13 @@ public final class MainActivity extends AppCompatActivity implements MainView { switch (request) { case MainPresenter.REQUEST_ADD_DIRECTORY: FileBrowserHelper.openDirectoryPicker(this, - MainPresenter.REQUEST_ADD_DIRECTORY, - R.string.select_game_folder); + MainPresenter.REQUEST_ADD_DIRECTORY, + R.string.select_game_folder); + break; + case MainPresenter.REQUEST_INSTALL_KEYS: + FileBrowserHelper.openFilePicker(this, + MainPresenter.REQUEST_INSTALL_KEYS, + R.string.install_keys); break; } } @@ -132,7 +141,6 @@ public final class MainActivity extends AppCompatActivity implements MainView { super.onActivityResult(requestCode, resultCode, result); switch (requestCode) { case MainPresenter.REQUEST_ADD_DIRECTORY: - // If the user picked a file, as opposed to just backing out. if (resultCode == MainActivity.RESULT_OK) { int takeFlags = (Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); getContentResolver().takePersistableUriPermission(Uri.parse(result.getDataString()), takeFlags); @@ -144,6 +152,22 @@ public final class MainActivity extends AppCompatActivity implements MainView { mPresenter.onDirectorySelected(FileBrowserHelper.getSelectedDirectory(result)); } break; + + case MainPresenter.REQUEST_INSTALL_KEYS: + if (resultCode == MainActivity.RESULT_OK) { + int takeFlags = (Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); + getContentResolver().takePersistableUriPermission(Uri.parse(result.getDataString()), takeFlags); + String dstPath = DirectoryInitialization.getUserDirectory() + "/keys/"; + if (FileUtil.copyUriToInternalStorage(this, result.getData(), dstPath, "prod.keys")) { + if (NativeLibrary.ReloadKeys()) { + Toast.makeText(this, R.string.install_keys_success, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, R.string.install_keys_failure, Toast.LENGTH_SHORT).show(); + launchFileListActivity(MainPresenter.REQUEST_INSTALL_KEYS); + } + } + } + break; } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.java index 01f577600e..82667a98fc 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.java @@ -11,6 +11,7 @@ import org.yuzu.yuzu_emu.utils.AddDirectoryHelper; public final class MainPresenter { public static final int REQUEST_ADD_DIRECTORY = 1; + public static final int REQUEST_INSTALL_KEYS = 2; private final MainView mView; private String mDirToAdd; private long mLastClickTime = 0; @@ -46,6 +47,10 @@ public final class MainPresenter { case R.id.button_add_directory: launchFileListActivity(REQUEST_ADD_DIRECTORY); return true; + + case R.id.button_install_keys: + launchFileListActivity(REQUEST_INSTALL_KEYS); + return true; } return false; diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileBrowserHelper.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileBrowserHelper.java index 6175f39c44..4dab914c7c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileBrowserHelper.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileBrowserHelper.java @@ -10,6 +10,15 @@ public final class FileBrowserHelper { activity.startActivityForResult(i, requestCode); } + public static void openFilePicker(FragmentActivity activity, int requestCode, int title) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + intent.putExtra(Intent.EXTRA_TITLE, title); + intent.setType("*/*"); + activity.startActivityForResult(intent, requestCode); + } + public static String getSelectedDirectory(Intent result) { return result.getDataString(); } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.java index 624fd4a883..8665704cc4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.java @@ -12,8 +12,9 @@ import androidx.documentfile.provider.DocumentFile; import org.yuzu.yuzu_emu.model.MinimalDocumentFile; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; @@ -243,6 +244,40 @@ public class FileUtil { return size; } + public static boolean copyUriToInternalStorage(Context context, Uri sourceUri, String destinationParentPath, String destinationFilename) { + InputStream input = null; + FileOutputStream output = null; + try { + input = context.getContentResolver().openInputStream(sourceUri); + output = new FileOutputStream(destinationParentPath + "/" + destinationFilename); + byte[] buffer = new byte[1024]; + int len; + while ((len = input.read(buffer)) != -1) { + output.write(buffer, 0, len); + } + output.flush(); + return true; + } catch (Exception e) { + Log.error("[FileUtil]: Cannot copy file, error: " + e.getMessage()); + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + Log.error("[FileUtil]: Cannot close input file, error: " + e.getMessage()); + } + } + if (output != null) { + try { + output.close(); + } catch (IOException e) { + Log.error("[FileUtil]: Cannot close output file, error: " + e.getMessage()); + } + } + } + return false; + } + public static boolean isRootTreeUri(Uri uri) { final List paths = uri.getPathSegments(); return paths.size() == 2 && PATH_TREE.equals(paths.get(0)); diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/StartupHandler.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/StartupHandler.java index 6d3e58e187..749a06b328 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/StartupHandler.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/StartupHandler.java @@ -2,6 +2,10 @@ package org.yuzu.yuzu_emu.utils; import android.content.SharedPreferences; import android.preference.PreferenceManager; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.widget.TextView; + import androidx.appcompat.app.AlertDialog; import org.yuzu.yuzu_emu.R; @@ -13,7 +17,7 @@ public final class StartupHandler { private static SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.getAppContext()); private static void handleStartupPromptDismiss(MainActivity parent) { - parent.launchFileListActivity(MainPresenter.REQUEST_ADD_DIRECTORY); + parent.launchFileListActivity(MainPresenter.REQUEST_INSTALL_KEYS); } private static void markFirstBoot() { @@ -26,14 +30,16 @@ public final class StartupHandler { if (mPreferences.getBoolean("FirstApplicationLaunch", true)) { markFirstBoot(); - // Prompt user with standard first boot disclaimer - new AlertDialog.Builder(parent) - .setTitle(R.string.app_name) - .setIcon(R.mipmap.ic_launcher) - .setMessage(parent.getResources().getString(R.string.app_disclaimer)) - .setPositiveButton(android.R.string.ok, null) - .setOnDismissListener(dialogInterface -> handleStartupPromptDismiss(parent)) - .show(); + AlertDialog.Builder builder = new AlertDialog.Builder(parent); + builder.setMessage(Html.fromHtml(parent.getResources().getString(R.string.app_disclaimer))); + builder.setTitle(R.string.app_name); + builder.setIcon(R.mipmap.ic_launcher); + builder.setPositiveButton(android.R.string.ok, null); + builder.setOnDismissListener(dialogInterface -> handleStartupPromptDismiss(parent)); + + AlertDialog alert = builder.create(); + alert.show(); + ((TextView) alert.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); } } } diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 358316c48c..6d1e75c407 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -271,7 +271,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_SetAppDirectory(JNIEnv* env, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadKeys(JNIEnv* env, [[maybe_unused]] jclass clazz) { Core::Crypto::KeyManager::Instance().ReloadKeys(); - return static_cast(Core::Crypto::KeyManager::Instance().IsKeysLoaded()); + return static_cast(Core::Crypto::KeyManager::Instance().AreKeysLoaded()); } void Java_org_yuzu_yuzu_1emu_NativeLibrary_UnPauseEmulation([[maybe_unused]] JNIEnv* env, diff --git a/src/android/app/src/main/res/drawable-hdpi/ic_cia_install.png b/src/android/app/src/main/res/drawable-hdpi/ic_install.png similarity index 100% rename from src/android/app/src/main/res/drawable-hdpi/ic_cia_install.png rename to src/android/app/src/main/res/drawable-hdpi/ic_install.png diff --git a/src/android/app/src/main/res/drawable-mdpi/ic_cia_install.png b/src/android/app/src/main/res/drawable-mdpi/ic_install.png similarity index 100% rename from src/android/app/src/main/res/drawable-mdpi/ic_cia_install.png rename to src/android/app/src/main/res/drawable-mdpi/ic_install.png diff --git a/src/android/app/src/main/res/drawable-night-hdpi/ic_cia_install.png b/src/android/app/src/main/res/drawable-night-hdpi/ic_install.png similarity index 100% rename from src/android/app/src/main/res/drawable-night-hdpi/ic_cia_install.png rename to src/android/app/src/main/res/drawable-night-hdpi/ic_install.png diff --git a/src/android/app/src/main/res/drawable-night-mdpi/ic_cia_install.png b/src/android/app/src/main/res/drawable-night-mdpi/ic_install.png similarity index 100% rename from src/android/app/src/main/res/drawable-night-mdpi/ic_cia_install.png rename to src/android/app/src/main/res/drawable-night-mdpi/ic_install.png diff --git a/src/android/app/src/main/res/drawable-night-xhdpi/ic_cia_install.png b/src/android/app/src/main/res/drawable-night-xhdpi/ic_install.png similarity index 100% rename from src/android/app/src/main/res/drawable-night-xhdpi/ic_cia_install.png rename to src/android/app/src/main/res/drawable-night-xhdpi/ic_install.png diff --git a/src/android/app/src/main/res/drawable-night-xxhdpi/ic_cia_install.png b/src/android/app/src/main/res/drawable-night-xxhdpi/ic_install.png similarity index 100% rename from src/android/app/src/main/res/drawable-night-xxhdpi/ic_cia_install.png rename to src/android/app/src/main/res/drawable-night-xxhdpi/ic_install.png diff --git a/src/android/app/src/main/res/drawable-night-xxxhdpi/ic_cia_install.png b/src/android/app/src/main/res/drawable-night-xxxhdpi/ic_install.png similarity index 100% rename from src/android/app/src/main/res/drawable-night-xxxhdpi/ic_cia_install.png rename to src/android/app/src/main/res/drawable-night-xxxhdpi/ic_install.png diff --git a/src/android/app/src/main/res/drawable-xhdpi/ic_cia_install.png b/src/android/app/src/main/res/drawable-xhdpi/ic_install.png similarity index 100% rename from src/android/app/src/main/res/drawable-xhdpi/ic_cia_install.png rename to src/android/app/src/main/res/drawable-xhdpi/ic_install.png diff --git a/src/android/app/src/main/res/drawable-xxhdpi/ic_cia_install.png b/src/android/app/src/main/res/drawable-xxhdpi/ic_install.png similarity index 100% rename from src/android/app/src/main/res/drawable-xxhdpi/ic_cia_install.png rename to src/android/app/src/main/res/drawable-xxhdpi/ic_install.png diff --git a/src/android/app/src/main/res/drawable-xxxhdpi/ic_cia_install.png b/src/android/app/src/main/res/drawable-xxxhdpi/ic_install.png similarity index 100% rename from src/android/app/src/main/res/drawable-xxxhdpi/ic_cia_install.png rename to src/android/app/src/main/res/drawable-xxxhdpi/ic_install.png diff --git a/src/android/app/src/main/res/menu/menu_game_grid.xml b/src/android/app/src/main/res/menu/menu_game_grid.xml index cd515afbf2..3eb8cf817d 100644 --- a/src/android/app/src/main/res/menu/menu_game_grid.xml +++ b/src/android/app/src/main/res/menu/menu_game_grid.xml @@ -14,9 +14,9 @@ android:title="@string/select_game_folder" app:showAsAction="ifRoom" /> diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 893f6aa1aa..1c6858a607 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -3,7 +3,7 @@ yuzu - This software will run games for the Nintendo Switch game console. No game titles are included.\n\nBefore you run, please place your rightfully owned Switch game files onto your device storage. + This software will run games for the Nintendo Switch game console. No game titles or keys are included.<br /><br />Before you begin, please locate your prod.keys ]]> file on your device storage.<br /><br />Learn more]]> yuzu yuzu yuzu Switch emulator notifications @@ -49,7 +49,9 @@ Select game folder - Install CIA + Install keys + Keys successfully installed + Keys file (prod.keys) is invalid Settings diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 0bd5859d00..4ff2c50e59 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -706,7 +706,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti } } -bool KeyManager::IsKeysLoaded() const { +bool KeyManager::AreKeysLoaded() const { return !s128_keys.empty() && !s256_keys.empty(); } diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index fb991ae54d..8c864503b1 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -268,7 +268,7 @@ public: bool AddTicketPersonalized(Ticket raw); void ReloadKeys(); - bool IsKeysLoaded() const; + bool AreKeysLoaded() const; private: KeyManager();