Merge pull request #10705 from german77/updates
android: Add update and DLC support
This commit is contained in:
commit
9bee930045
|
@ -227,6 +227,8 @@ object NativeLibrary {
|
||||||
|
|
||||||
external fun setAppDirectory(directory: String)
|
external fun setAppDirectory(directory: String)
|
||||||
|
|
||||||
|
external fun installFileToNand(filename: String): Int
|
||||||
|
|
||||||
external fun initializeGpuDriver(
|
external fun initializeGpuDriver(
|
||||||
hookLibDir: String?,
|
hookLibDir: String?,
|
||||||
customDriverDir: String?,
|
customDriverDir: String?,
|
||||||
|
@ -507,4 +509,15 @@ object NativeLibrary {
|
||||||
const val RELEASED = 0
|
const val RELEASED = 0
|
||||||
const val PRESSED = 1
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,11 @@ class HomeSettingsFragment : Fragment() {
|
||||||
R.string.install_amiibo_keys_description,
|
R.string.install_amiibo_keys_description,
|
||||||
R.drawable.ic_nfc
|
R.drawable.ic_nfc
|
||||||
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) },
|
) { 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(
|
HomeSetting(
|
||||||
R.string.select_games_folder,
|
R.string.select_games_folder,
|
||||||
R.string.select_games_folder_description,
|
R.string.select_games_folder_description,
|
||||||
|
@ -103,7 +108,12 @@ class HomeSettingsFragment : Fragment() {
|
||||||
R.string.manage_save_data,
|
R.string.manage_save_data,
|
||||||
R.string.import_export_saves_description,
|
R.string.import_export_saves_description,
|
||||||
R.drawable.ic_save
|
R.drawable.ic_save
|
||||||
) { ImportExportSavesFragment().show(parentFragmentManager, ImportExportSavesFragment.TAG) },
|
) {
|
||||||
|
ImportExportSavesFragment().show(
|
||||||
|
parentFragmentManager,
|
||||||
|
ImportExportSavesFragment.TAG
|
||||||
|
)
|
||||||
|
},
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
R.string.install_prod_keys,
|
R.string.install_prod_keys,
|
||||||
R.string.install_prod_keys_description,
|
R.string.install_prod_keys_description,
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,10 @@
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/cpu_manager.h"
|
#include "core/cpu_manager.h"
|
||||||
#include "core/crypto/key_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/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/file_sys/vfs_real.h"
|
||||||
#include "core/frontend/applets/cabinet.h"
|
#include "core/frontend/applets/cabinet.h"
|
||||||
#include "core/frontend/applets/controller.h"
|
#include "core/frontend/applets/controller.h"
|
||||||
|
@ -94,6 +97,74 @@ public:
|
||||||
m_native_window = native_window;
|
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,
|
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
|
||||||
const std::string& custom_driver_name,
|
const std::string& custom_driver_name,
|
||||||
const std::string& file_redirect_dir) {
|
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_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
|
||||||
m_vulkan_library);
|
m_vulkan_library);
|
||||||
|
|
||||||
|
m_system.SetFilesystem(m_vfs);
|
||||||
|
|
||||||
// Initialize system.
|
// Initialize system.
|
||||||
auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
|
auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
|
||||||
m_software_keyboard = android_keyboard.get();
|
m_software_keyboard = android_keyboard.get();
|
||||||
m_system.SetShuttingDown(false);
|
m_system.SetShuttingDown(false);
|
||||||
m_system.ApplySettings();
|
m_system.ApplySettings();
|
||||||
m_system.HIDCore().ReloadInputDevices();
|
m_system.HIDCore().ReloadInputDevices();
|
||||||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
|
||||||
m_system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
|
||||||
m_system.SetAppletFrontendSet({
|
m_system.SetAppletFrontendSet({
|
||||||
nullptr, // Amiibo Settings
|
nullptr, // Amiibo Settings
|
||||||
nullptr, // Controller Selector
|
nullptr, // Controller Selector
|
||||||
|
@ -173,7 +244,8 @@ public:
|
||||||
std::move(android_keyboard), // Software Keyboard
|
std::move(android_keyboard), // Software Keyboard
|
||||||
nullptr, // Web Browser
|
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
|
// Initialize account manager
|
||||||
m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
|
m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
|
||||||
|
@ -398,7 +470,7 @@ private:
|
||||||
InputCommon::InputSubsystem m_input_subsystem;
|
InputCommon::InputSubsystem m_input_subsystem;
|
||||||
Common::DetachedTasks m_detached_tasks;
|
Common::DetachedTasks m_detached_tasks;
|
||||||
Core::PerfStatsResults m_perf_stats{};
|
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};
|
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
|
||||||
bool m_is_running{};
|
bool m_is_running{};
|
||||||
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
|
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));
|
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(
|
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
|
||||||
JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
|
JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
|
||||||
jstring custom_driver_name, jstring file_redirect_dir) {
|
jstring custom_driver_name, jstring file_redirect_dir) {
|
||||||
|
|
|
@ -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>
|
|
@ -102,6 +102,15 @@
|
||||||
<string name="share_log">Share debug logs</string>
|
<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_description">Share yuzu\'s log file to debug issues</string>
|
||||||
<string name="share_log_missing">No log file found</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 -->
|
<!-- About screen strings -->
|
||||||
<string name="gaia_is_not_real">Gaia isn\'t real</string>
|
<string name="gaia_is_not_real">Gaia isn\'t real</string>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "core/file_sys/nca_metadata.h"
|
||||||
#include "core/file_sys/vfs.h"
|
#include "core/file_sys/vfs.h"
|
||||||
|
|
||||||
namespace Core::Crypto {
|
namespace Core::Crypto {
|
||||||
|
|
Reference in New Issue