summaryrefslogtreecommitdiffstats
path: root/src/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt58
-rw-r--r--src/android/app/src/main/jni/native.cpp86
-rw-r--r--src/android/app/src/main/res/drawable/ic_system_update_alt.xml9
-rw-r--r--src/android/app/src/main/res/values/strings.xml9
6 files changed, 182 insertions, 5 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index 22af9e435..4be9ade14 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -227,6 +227,8 @@ object NativeLibrary {
external fun setAppDirectory(directory: String)
+ external fun installFileToNand(filename: String): Int
+
external fun initializeGpuDriver(
hookLibDir: String?,
customDriverDir: String?,
@@ -507,4 +509,15 @@ object NativeLibrary {
const val RELEASED = 0
const val PRESSED = 1
}
+
+ /**
+ * Result from installFileToNand
+ */
+ object InstallFileToNandResult {
+ const val Success = 0
+ const val SuccessFileOverwritten = 1
+ const val Error = 2
+ const val ErrorBaseGame = 3
+ const val ErrorFilenameExtension = 4
+ }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index bdc337501..536163eb6 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -95,6 +95,11 @@ class HomeSettingsFragment : Fragment() {
R.drawable.ic_nfc
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) },
HomeSetting(
+ R.string.install_game_content,
+ R.string.install_game_content_description,
+ R.drawable.ic_system_update_alt
+ ) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) },
+ HomeSetting(
R.string.select_games_folder,
R.string.select_games_folder_description,
R.drawable.ic_add
@@ -103,7 +108,12 @@ class HomeSettingsFragment : Fragment() {
R.string.manage_save_data,
R.string.import_export_saves_description,
R.drawable.ic_save
- ) { ImportExportSavesFragment().show(parentFragmentManager, ImportExportSavesFragment.TAG) },
+ ) {
+ ImportExportSavesFragment().show(
+ parentFragmentManager,
+ ImportExportSavesFragment.TAG
+ )
+ },
HomeSetting(
R.string.install_prod_keys,
R.string.install_prod_keys_description,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 3fca0a7e6..041d16f3a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -467,4 +467,62 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
}
+
+ val installGameUpdate =
+ registerForActivityResult(ActivityResultContracts.OpenDocument()) {
+ if (it == null)
+ return@registerForActivityResult
+
+ IndeterminateProgressDialogFragment.newInstance(
+ this@MainActivity,
+ R.string.install_game_content
+ ) {
+ val result = NativeLibrary.installFileToNand(it.toString())
+ lifecycleScope.launch {
+ withContext(Dispatchers.Main) {
+ when (result) {
+ NativeLibrary.InstallFileToNandResult.Success -> {
+ Toast.makeText(
+ applicationContext,
+ R.string.install_game_content_success,
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+
+ NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
+ Toast.makeText(
+ applicationContext,
+ R.string.install_game_content_success_overwrite,
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+
+ NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
+ MessageDialogFragment.newInstance(
+ R.string.install_game_content_failure,
+ R.string.install_game_content_failure_base
+ ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ }
+
+ NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
+ MessageDialogFragment.newInstance(
+ R.string.install_game_content_failure,
+ R.string.install_game_content_failure_file_extension,
+ R.string.install_game_content_help_link
+ ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ }
+
+ else -> {
+ MessageDialogFragment.newInstance(
+ R.string.install_game_content_failure,
+ R.string.install_game_content_failure_description,
+ R.string.install_game_content_help_link
+ ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ }
+ }
+ }
+ }
+ return@newInstance result
+ }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }
}
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 7ebed5e6a..4091c23d1 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -28,7 +28,10 @@
#include "core/core.h"
#include "core/cpu_manager.h"
#include "core/crypto/key_manager.h"
+#include "core/file_sys/card_image.h"
#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/submission_package.h"
+#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/frontend/applets/cabinet.h"
#include "core/frontend/applets/controller.h"
@@ -94,6 +97,74 @@ public:
m_native_window = native_window;
}
+ int InstallFileToNand(std::string filename) {
+ const auto copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
+ std::size_t block_size) {
+ if (src == nullptr || dest == nullptr) {
+ return false;
+ }
+ if (!dest->Resize(src->GetSize())) {
+ return false;
+ }
+
+ using namespace Common::Literals;
+ std::vector<u8> buffer(1_MiB);
+
+ for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
+ const auto read = src->Read(buffer.data(), buffer.size(), i);
+ dest->Write(buffer.data(), read, i);
+ }
+ return true;
+ };
+
+ enum InstallResult {
+ Success = 0,
+ SuccessFileOverwritten = 1,
+ InstallError = 2,
+ ErrorBaseGame = 3,
+ ErrorFilenameExtension = 4,
+ };
+
+ m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
+ m_system.GetFileSystemController().CreateFactories(*m_vfs);
+
+ std::shared_ptr<FileSys::NSP> nsp;
+ if (filename.ends_with("nsp")) {
+ nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
+ if (nsp->IsExtractedType()) {
+ return InstallError;
+ }
+ } else if (filename.ends_with("xci")) {
+ const auto xci =
+ std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
+ nsp = xci->GetSecurePartitionNSP();
+ } else {
+ return ErrorFilenameExtension;
+ }
+
+ if (!nsp) {
+ return InstallError;
+ }
+
+ if (nsp->GetStatus() != Loader::ResultStatus::Success) {
+ return InstallError;
+ }
+
+ const auto res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
+ *nsp, true, copy_func);
+
+ switch (res) {
+ case FileSys::InstallResult::Success:
+ return Success;
+ case FileSys::InstallResult::OverwriteExisting:
+ return SuccessFileOverwritten;
+ case FileSys::InstallResult::ErrorBaseInstall:
+ return ErrorBaseGame;
+ default:
+ return InstallError;
+ }
+ }
+
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
const std::string& custom_driver_name,
const std::string& file_redirect_dir) {
@@ -154,14 +225,14 @@ public:
m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
m_vulkan_library);
+ m_system.SetFilesystem(m_vfs);
+
// Initialize system.
auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
m_software_keyboard = android_keyboard.get();
m_system.SetShuttingDown(false);
m_system.ApplySettings();
m_system.HIDCore().ReloadInputDevices();
- m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
- m_system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
m_system.SetAppletFrontendSet({
nullptr, // Amiibo Settings
nullptr, // Controller Selector
@@ -173,7 +244,8 @@ public:
std::move(android_keyboard), // Software Keyboard
nullptr, // Web Browser
});
- m_system.GetFileSystemController().CreateFactories(*m_system.GetFilesystem());
+ m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
+ m_system.GetFileSystemController().CreateFactories(*m_vfs);
// Initialize account manager
m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
@@ -398,7 +470,7 @@ private:
InputCommon::InputSubsystem m_input_subsystem;
Common::DetachedTasks m_detached_tasks;
Core::PerfStatsResults m_perf_stats{};
- std::shared_ptr<FileSys::RealVfsFilesystem> m_vfs;
+ std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
bool m_is_running{};
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
@@ -466,6 +538,12 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
Common::FS::SetAppDirectory(GetJString(env, j_directory));
}
+int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env,
+ [[maybe_unused]] jclass clazz,
+ jstring j_file) {
+ return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file));
+}
+
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
jstring custom_driver_name, jstring file_redirect_dir) {
diff --git a/src/android/app/src/main/res/drawable/ic_system_update_alt.xml b/src/android/app/src/main/res/drawable/ic_system_update_alt.xml
new file mode 100644
index 000000000..0f6adfdb8
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_system_update_alt.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M140,800q-24,0 -42,-18t-18,-42v-520q0,-24 18,-42t42,-18h250v60L140,220v520h680v-520L570,220v-60h250q24,0 42,18t18,42v520q0,24 -18,42t-42,18L140,800ZM480,615L280,415l43,-43 127,127v-339h60v339l127,-127 43,43 -200,200Z"/>
+</vector>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 6e9d47557..7dae63dcb 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -102,6 +102,15 @@
<string name="share_log">Share debug logs</string>
<string name="share_log_description">Share yuzu\'s log file to debug issues</string>
<string name="share_log_missing">No log file found</string>
+ <string name="install_game_content">Install game content</string>
+ <string name="install_game_content_description">Install game updates or DLC</string>
+ <string name="install_game_content_failure">Error installing file to NAND</string>
+ <string name="install_game_content_failure_description">Game content installation failed. Please ensure content is valid and that the prod.keys file is installed.</string>
+ <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead.</string>
+ <string name="install_game_content_failure_file_extension">The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid.</string>
+ <string name="install_game_content_success">Game content installed successfully</string>
+ <string name="install_game_content_success_overwrite">Game content was overwritten successfully</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia isn\'t real</string>