early-access version 4088

This commit is contained in:
pineappleEA
2024-01-26 01:18:56 +01:00
parent 06b2c0cf0c
commit ef251c7289
160 changed files with 7677 additions and 839 deletions
@@ -303,6 +303,11 @@ object NativeLibrary {
*/
external fun getCpuBackend(): String
/**
* Returns the current GPU Driver.
*/
external fun getGpuDriver(): String
external fun applySettings()
external fun logSettings()
@@ -614,6 +619,11 @@ object NativeLibrary {
*/
external fun clearFilesystemProvider()
/**
* Checks if all necessary keys are present for decryption
*/
external fun areKeysPresent(): Boolean
/**
* Button type for use in onTouchEvent
*/
@@ -14,15 +14,20 @@ import androidx.recyclerview.widget.RecyclerView
* Generic adapter that implements an [AsyncDifferConfig] and covers some of the basic boilerplate
* code used in every [RecyclerView].
* Type assigned to [Model] must inherit from [Object] in order to be compared properly.
* @param exact Decides whether each item will be compared by reference or by their contents
*/
abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>> :
ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>()).build()) {
abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>>(
exact: Boolean = true
) : ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>(exact)).build()) {
override fun onBindViewHolder(holder: Holder, position: Int) =
holder.bind(currentList[position])
private class DiffCallback<Model> : DiffUtil.ItemCallback<Model>() {
private class DiffCallback<Model>(val exact: Boolean) : DiffUtil.ItemCallback<Model>() {
override fun areItemsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean {
return oldItem === newItem
if (exact) {
return oldItem === newItem
}
return oldItem == newItem
}
@SuppressLint("DiffUtilEquals")
@@ -30,7 +30,7 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class GameAdapter(private val activity: AppCompatActivity) :
AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>() {
AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>(exact = false) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.also { return GameViewHolder(it) }
@@ -38,7 +38,6 @@ import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections
@@ -141,7 +140,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
// So this fragment doesn't restart on configuration changes; i.e. rotation.
retainInstance = true
emulationState = EmulationState(game.path)
emulationState = EmulationState(game.path) {
return@EmulationState driverViewModel.isInteractionAllowed.value
}
}
/**
@@ -370,6 +371,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.isInteractionAllowed.collect {
if (it) {
startEmulation()
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.emulationStarted.collectLatest {
@@ -398,19 +408,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.isInteractionAllowed.collect {
if (it) {
onEmulationStart()
}
}
}
}
}
}
private fun onEmulationStart() {
private fun startEmulation() {
if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start()
@@ -485,12 +486,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val FRAMETIME = 2
val SPEED = 3
perfStatsUpdater = {
if (emulationViewModel.emulationStarted.value) {
if (emulationViewModel.emulationStarted.value &&
!emulationViewModel.isEmulationStopping.value
) {
val perfStats = NativeLibrary.getPerfStats()
val cpuBackend = NativeLibrary.getCpuBackend()
val gpuDriver = NativeLibrary.getGpuDriver()
if (_binding != null) {
binding.showFpsText.text =
String.format("FPS: %.1f\n%s", perfStats[FPS], cpuBackend)
String.format("FPS: %.1f\n%s/%s", perfStats[FPS], cpuBackend, gpuDriver)
}
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
}
@@ -807,7 +811,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
private class EmulationState(private val gamePath: String) {
private class EmulationState(
private val gamePath: String,
private val emulationCanStart: () -> Boolean
) {
private var state: State
private var surface: Surface? = null
@@ -901,6 +908,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
State.PAUSED -> Log.warning(
"[EmulationFragment] Surface cleared while emulation paused."
)
else -> Log.warning(
"[EmulationFragment] Surface cleared while emulation stopped."
)
@@ -910,6 +918,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private fun runWithValidSurface() {
NativeLibrary.surfaceChanged(surface)
if (!emulationCanStart.invoke()) {
return
}
when (state) {
State.STOPPED -> {
val emulationThread = Thread({
@@ -26,9 +26,15 @@ class MessageDialogFragment : DialogFragment() {
val descriptionId = requireArguments().getInt(DESCRIPTION_ID)
val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!!
val helpLinkId = requireArguments().getInt(HELP_LINK)
val dismissible = requireArguments().getBoolean(DISMISSIBLE)
val clearPositiveAction = requireArguments().getBoolean(CLEAR_POSITIVE_ACTION)
val builder = MaterialAlertDialogBuilder(requireContext())
if (clearPositiveAction) {
messageDialogViewModel.positiveAction = null
}
if (messageDialogViewModel.positiveAction == null) {
builder.setPositiveButton(R.string.close, null)
} else {
@@ -51,6 +57,8 @@ class MessageDialogFragment : DialogFragment() {
}
}
isCancelable = dismissible
return builder.show()
}
@@ -67,6 +75,8 @@ class MessageDialogFragment : DialogFragment() {
private const val DESCRIPTION_ID = "DescriptionId"
private const val DESCRIPTION_STRING = "DescriptionString"
private const val HELP_LINK = "Link"
private const val DISMISSIBLE = "Dismissible"
private const val CLEAR_POSITIVE_ACTION = "ClearPositiveAction"
fun newInstance(
activity: FragmentActivity? = null,
@@ -75,22 +85,28 @@ class MessageDialogFragment : DialogFragment() {
descriptionId: Int = 0,
descriptionString: String = "",
helpLinkId: Int = 0,
dismissible: Boolean = true,
positiveAction: (() -> Unit)? = null
): MessageDialogFragment {
val dialog = MessageDialogFragment()
val bundle = Bundle()
bundle.apply {
putInt(TITLE_ID, titleId)
putString(TITLE_STRING, titleString)
putInt(DESCRIPTION_ID, descriptionId)
putString(DESCRIPTION_STRING, descriptionString)
putInt(HELP_LINK, helpLinkId)
}
var clearPositiveAction = false
if (activity != null) {
ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply {
clear()
this.positiveAction = positiveAction
}
} else {
clearPositiveAction = true
}
val dialog = MessageDialogFragment()
val bundle = Bundle().apply {
putInt(TITLE_ID, titleId)
putString(TITLE_STRING, titleString)
putInt(DESCRIPTION_ID, descriptionId)
putString(DESCRIPTION_STRING, descriptionString)
putInt(HELP_LINK, helpLinkId)
putBoolean(DISMISSIBLE, dismissible)
putBoolean(CLEAR_POSITIVE_ACTION, clearPositiveAction)
}
dialog.arguments = bundle
return dialog
@@ -31,6 +31,7 @@ import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.transition.MaterialFadeThrough
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.NativeLibrary
import java.io.File
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@@ -162,7 +163,7 @@ class SetupFragment : Fragment() {
R.string.install_prod_keys_warning_help,
{
val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys")
if (file.exists()) {
if (file.exists() && NativeLibrary.areKeysPresent()) {
StepState.COMPLETE
} else {
StepState.INCOMPLETE
@@ -347,7 +348,8 @@ class SetupFragment : Fragment() {
val getProdKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result != null) {
if (mainActivity.processKey(result)) {
mainActivity.processKey(result)
if (NativeLibrary.areKeysPresent()) {
keyCallback.onStepCompleted()
}
}
@@ -144,6 +144,7 @@ class DriverViewModel : ViewModel() {
val selectedDriverFile = File(StringSetting.DRIVER_PATH.getString())
val selectedDriverMetadata = GpuDriverHelper.customDriverSettingData
if (GpuDriverHelper.installedCustomDriverData == selectedDriverMetadata) {
setDriverReady()
return
}
@@ -70,11 +70,19 @@ class Game(
}
override fun equals(other: Any?): Boolean {
if (other !is Game) {
return false
}
if (this === other) return true
if (javaClass != other?.javaClass) return false
return hashCode() == other.hashCode()
other as Game
if (title != other.title) return false
if (path != other.path) return false
if (programId != other.programId) return false
if (developer != other.developer) return false
if (version != other.version) return false
if (isHomebrew != other.isHomebrew) return false
return true
}
override fun hashCode(): Int {
@@ -31,6 +31,9 @@ class HomeViewModel : ViewModel() {
private val _reloadPropertiesList = MutableStateFlow(false)
val reloadPropertiesList get() = _reloadPropertiesList.asStateFlow()
private val _checkKeys = MutableStateFlow(false)
val checkKeys = _checkKeys.asStateFlow()
var navigatedToSetup = false
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
@@ -66,4 +69,8 @@ class HomeViewModel : ViewModel() {
fun reloadPropertiesList(reload: Boolean) {
_reloadPropertiesList.value = reload
}
fun setCheckKeys(value: Boolean) {
_checkKeys.value = value
}
}
@@ -64,6 +64,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
override var themeId: Int = 0
private val CHECKED_DECRYPTION = "CheckedDecryption"
private var checkedDecryption = false
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
@@ -75,6 +78,18 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
if (savedInstanceState != null) {
checkedDecryption = savedInstanceState.getBoolean(CHECKED_DECRYPTION)
}
if (!checkedDecryption) {
val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
if (!firstTimeSetup) {
checkKeys()
}
checkedDecryption = true
}
WindowCompat.setDecorFitsSystemWindows(window, false)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
@@ -150,6 +165,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.checkKeys.collect {
if (it) {
checkKeys()
homeViewModel.setCheckKeys(false)
}
}
}
}
}
// Dismiss previous notifications (should not happen unless a crash occurred)
@@ -158,6 +183,21 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
setInsets()
}
private fun checkKeys() {
if (!NativeLibrary.areKeysPresent()) {
MessageDialogFragment.newInstance(
titleId = R.string.keys_missing,
descriptionId = R.string.keys_missing_description,
helpLinkId = R.string.keys_missing_help
).show(supportFragmentManager, MessageDialogFragment.TAG)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(CHECKED_DECRYPTION, checkedDecryption)
}
fun finishSetup(navController: NavController) {
navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment)
(binding.navigationView as NavigationBarView).setupWithNavController(navController)
@@ -349,6 +389,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
R.string.install_keys_success,
Toast.LENGTH_SHORT
).show()
homeViewModel.setCheckKeys(true)
gamesViewModel.reloadGames(true)
return true
} else {
@@ -399,6 +440,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
firmwarePath.deleteRecursively()
cacheFirmwareDir.copyRecursively(firmwarePath, true)
NativeLibrary.initializeSystem(true)
homeViewModel.setCheckKeys(true)
getString(R.string.save_file_imported_success)
}
} catch (e: Exception) {
@@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <core/core.h>
#include <core/file_sys/mode.h>
#include <core/file_sys/patch_manager.h>
#include <core/loader/nro.h>
#include <jni.h>
#include "core/core.h"
#include "core/file_sys/fs_filesystem.h"
#include "core/file_sys/patch_manager.h"
#include "core/loader/loader.h"
#include "core/loader/nro.h"
#include "jni.h"
#include "jni/android_common/android_common.h"
#include "native.h"
@@ -79,7 +79,7 @@ extern "C" {
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj,
jstring jpath) {
const auto file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
GetJString(env, jpath), FileSys::Mode::Read);
GetJString(env, jpath), FileSys::OpenMode::Read);
if (!file) {
return false;
}
+28 -14
View File
@@ -35,9 +35,10 @@
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/fs_filesystem.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/vfs.h"
#include "core/file_sys/vfs/vfs_real.h"
#include "core/frontend/applets/cabinet.h"
#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/error.h"
@@ -154,7 +155,7 @@ void EmulationSession::SurfaceChanged() {
}
void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) {
const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::OpenMode::Read);
if (!file) {
return;
}
@@ -247,6 +248,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
m_system.GetCpuManager().OnGpuReady();
m_system.RegisterExitCallback([&] { HaltEmulation(); });
OnEmulationStarted();
return Core::SystemResultStatus::Success;
}
@@ -463,8 +465,8 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject
};
return static_cast<int>(
ContentManager::InstallNSP(&EmulationSession::GetInstance().System(),
EmulationSession::GetInstance().System().GetFilesystem().get(),
ContentManager::InstallNSP(EmulationSession::GetInstance().System(),
*EmulationSession::GetInstance().System().GetFilesystem(),
GetJString(env, j_file), callback));
}
@@ -474,8 +476,8 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
std::string updatePath = GetJString(env, jupdatePath);
std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>(
EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(updatePath,
FileSys::Mode::Read));
EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
updatePath, FileSys::OpenMode::Read));
for (const auto& item : nsp->GetNCAs()) {
for (const auto& nca_details : item.second) {
if (nca_details.second->GetName().ends_with(".cnmt.nca")) {
@@ -674,6 +676,11 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuBackend(JNIEnv* env, jclass
return ToJString(env, "JIT");
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuDriver(JNIEnv* env, jobject jobj) {
return ToJString(env,
EmulationSession::GetInstance().System().GPU().Renderer().GetDeviceVendor());
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_applySettings(JNIEnv* env, jobject jobj) {
EmulationSession::GetInstance().System().ApplySettings();
}
@@ -713,7 +720,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
jobject instance) {
const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
auto vfs_nand_dir = EmulationSession::GetInstance().System().GetFilesystem()->OpenDirectory(
Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read);
const auto user_id = EmulationSession::GetInstance().System().GetProfileManager().GetUser(
static_cast<std::size_t>(0));
@@ -819,7 +826,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject job
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj,
jstring jprogramId) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
ContentManager::RemoveAllDLC(&EmulationSession::GetInstance().System(), program_id);
ContentManager::RemoveAllDLC(EmulationSession::GetInstance().System(), program_id);
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId,
@@ -829,8 +836,9 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj,
program_id, GetJString(env, jname));
}
jobject Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyInstalledContents(JNIEnv* env, jobject jobj,
jobject jcallback) {
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyInstalledContents(JNIEnv* env,
jobject jobj,
jobject jcallback) {
auto jlambdaClass = env->GetObjectClass(jcallback);
auto jlambdaInvokeMethod = env->GetMethodID(
jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
@@ -842,7 +850,7 @@ jobject Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyInstalledContents(JNIEnv* en
auto& session = EmulationSession::GetInstance();
std::vector<std::string> result = ContentManager::VerifyInstalledContents(
&session.System(), session.GetContentProvider(), callback);
session.System(), *session.GetContentProvider(), callback);
jobjectArray jresult =
env->NewObjectArray(result.size(), IDCache::GetStringClass(), ToJString(env, ""));
for (size_t i = 0; i < result.size(); ++i) {
@@ -863,7 +871,7 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyGameContents(JNIEnv* env, jobje
};
auto& session = EmulationSession::GetInstance();
return static_cast<jint>(
ContentManager::VerifyGameContents(&session.System(), GetJString(env, jpath), callback));
ContentManager::VerifyGameContents(session.System(), GetJString(env, jpath), callback));
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj,
@@ -882,7 +890,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject j
const auto nandDir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
auto vfsNandDir = system.GetFilesystem()->OpenDirectory(Common::FS::PathToUTF8String(nandDir),
FileSys::Mode::Read);
FileSys::OpenMode::Read);
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
{}, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
@@ -912,4 +920,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env,
EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, jobject jobj) {
auto& system = EmulationSession::GetInstance().System();
system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
return ContentManager::AreKeysPresent();
}
} // extern "C"
@@ -144,6 +144,9 @@
<string name="no_save_data_found">No save data found</string>
<string name="verify_installed_content">Verify installed content</string>
<string name="verify_installed_content_description">Checks all installed content for corruption</string>
<string name="keys_missing">Encryption keys are missing</string>
<string name="keys_missing_description">Firmware and retail games cannot be decrypted</string>
<string name="keys_missing_help">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<!-- Applet launcher strings -->
<string name="applets">Applet launcher</string>