From cf84342e3567c3349dfbbc3f52fdfded5bf26138 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Wed, 12 Aug 2020 23:48:57 +0800 Subject: [PATCH] android: Add savestates UI A simple menu with savestates info. --- .../org/citra/citra_emu/NativeLibrary.java | 15 +++++++ .../activities/EmulationActivity.java | 40 +++++++++++++++++ src/android/app/src/main/jni/config.cpp | 2 +- src/android/app/src/main/jni/id_cache.cpp | 8 ++++ src/android/app/src/main/jni/id_cache.h | 1 + src/android/app/src/main/jni/native.cpp | 44 +++++++++++++++++++ src/android/app/src/main/jni/native.h | 9 ++++ .../app/src/main/res/menu/menu_emulation.xml | 12 +++++ .../app/src/main/res/values/strings.xml | 4 ++ 9 files changed, 134 insertions(+), 1 deletion(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java index 9baeeb5e4..62685eb9f 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java @@ -16,6 +16,7 @@ import android.widget.EditText; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; @@ -25,6 +26,7 @@ import org.citra.citra_emu.utils.Log; import org.citra.citra_emu.utils.PermissionsHandler; import java.lang.ref.WeakReference; +import java.util.Date; import static android.Manifest.permission.CAMERA; import static android.Manifest.permission.RECORD_AUDIO; @@ -487,6 +489,19 @@ public final class NativeLibrary { public static native void InstallCIAS(String[] path); + public static final int SAVESTATE_SLOT_COUNT = 10; + + public static final class SavestateInfo { + public int slot; + public Date time; + } + + @Nullable + public static native SavestateInfo[] GetSavestateInfo(); + + public static native void SaveState(int slot); + public static native void LoadState(int slot); + /** * Button type for use in onTouchEvent */ diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java index 4f058a76a..9837c2ec7 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java @@ -14,6 +14,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.SubMenu; import android.view.View; import android.widget.SeekBar; import android.widget.TextView; @@ -303,6 +304,45 @@ public final class EmulationActivity extends AppCompatActivity { return true; } + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + final NativeLibrary.SavestateInfo[] savestates = NativeLibrary.GetSavestateInfo(); + if (savestates == null) { + menu.findItem(R.id.menu_emulation_save_state).setVisible(false); + menu.findItem(R.id.menu_emulation_load_state).setVisible(false); + return true; + } + menu.findItem(R.id.menu_emulation_save_state).setVisible(true); + menu.findItem(R.id.menu_emulation_load_state).setVisible(true); + + final SubMenu saveStateMenu = menu.findItem(R.id.menu_emulation_save_state).getSubMenu(); + final SubMenu loadStateMenu = menu.findItem(R.id.menu_emulation_load_state).getSubMenu(); + saveStateMenu.clear(); + loadStateMenu.clear(); + + // Update savestates information + for (int i = 0; i < NativeLibrary.SAVESTATE_SLOT_COUNT; ++i) { + final int slot = i + 1; + final String text = getString(R.string.emulation_empty_state_slot, slot); + saveStateMenu.add(text).setEnabled(true).setOnMenuItemClickListener((item) -> { + NativeLibrary.SaveState(slot); + return true; + }); + loadStateMenu.add(text).setEnabled(false).setOnMenuItemClickListener((item) -> { + NativeLibrary.LoadState(slot); + return true; + }); + } + for (final NativeLibrary.SavestateInfo info : savestates) { + final String text = getString(R.string.emulation_occupied_state_slot, info.slot, info.time); + saveStateMenu.getItem(info.slot - 1).setTitle(text); + loadStateMenu.getItem(info.slot - 1).setTitle(text).setEnabled(true); + } + return true; + } + @SuppressWarnings("WrongConstant") @Override public boolean onOptionsItemSelected(MenuItem item) { diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index ac81e49d1..aa9966c13 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -128,7 +128,7 @@ void Config::ReadValues() { // Work around to map Android setting for enabling the frame limiter to the format Citra expects if (sdl2_config->GetBoolean("Renderer", "use_frame_limit", true)) { Settings::values.frame_limit = - static_cast(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); + static_cast(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); } else { Settings::values.frame_limit = 0; } diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index 6c5c31acf..b51b11846 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -19,6 +19,7 @@ static constexpr jint JNI_VERSION = JNI_VERSION_1_6; static JavaVM* s_java_vm; static jclass s_native_library_class; +static jclass s_savestate_info_class; static jmethodID s_display_alert_msg; static jmethodID s_display_alert_prompt; static jmethodID s_alert_prompt_button; @@ -53,6 +54,10 @@ jclass GetNativeLibraryClass() { return s_native_library_class; } +jclass GetSavestateInfoClass() { + return s_savestate_info_class; +} + jmethodID GetDisplayAlertMsg() { return s_display_alert_msg; } @@ -111,6 +116,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { // Initialize Java methods const jclass native_library_class = env->FindClass("org/citra/citra_emu/NativeLibrary"); s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); + s_savestate_info_class = reinterpret_cast( + env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$SavestateInfo"))); s_display_alert_msg = env->GetStaticMethodID(s_native_library_class, "displayAlertMsg", "(Ljava/lang/String;Ljava/lang/String;Z)Z"); s_display_alert_prompt = @@ -142,6 +149,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) { } env->DeleteGlobalRef(s_native_library_class); + env->DeleteGlobalRef(s_savestate_info_class); MiiSelector::CleanupJNI(env); SoftwareKeyboard::CleanupJNI(env); Camera::StillImage::CleanupJNI(env); diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index 64c185e80..ee3fe6be8 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -12,6 +12,7 @@ namespace IDCache { JNIEnv* GetEnvForThread(); jclass GetNativeLibraryClass(); +jclass GetSavestateInfoClass(); jmethodID GetDisplayAlertMsg(); jmethodID GetDisplayAlertPrompt(); jmethodID GetAlertPromptButton(); diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 8262b9f38..7a6f89e90 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -23,6 +23,7 @@ #include "core/frontend/scope_acquire_context.h" #include "core/hle/service/am/am.h" #include "core/hle/service/nfc/nfc.h" +#include "core/savestate.h" #include "core/settings.h" #include "jni/applets/mii_selector.h" #include "jni/applets/swkbd.h" @@ -620,4 +621,47 @@ void Java_org_citra_citra_1emu_NativeLibrary_InstallCIAS(JNIEnv* env, [[maybe_un thread.join(); } +jobjectArray Java_org_citra_citra_1emu_NativeLibrary_GetSavestateInfo( + JNIEnv* env, [[maybe_unused]] jclass clazz) { + const jclass date_class = env->FindClass("java/util/Date"); + const auto date_constructor = env->GetMethodID(date_class, "", "(J)V"); + + const jclass savestate_info_class = IDCache::GetSavestateInfoClass(); + const auto slot_field = env->GetFieldID(savestate_info_class, "slot", "I"); + const auto date_field = env->GetFieldID(savestate_info_class, "time", "Ljava/util/Date;"); + + const Core::System& system{Core::System::GetInstance()}; + if (!system.IsPoweredOn()) { + return nullptr; + } + + u64 title_id; + if (system.GetAppLoader().ReadProgramId(title_id) != Loader::ResultStatus::Success) { + return nullptr; + } + + const auto savestates = Core::ListSaveStates(title_id); + const jobjectArray array = + env->NewObjectArray(static_cast(savestates.size()), savestate_info_class, nullptr); + for (std::size_t i = 0; i < savestates.size(); ++i) { + const jobject object = env->AllocObject(savestate_info_class); + env->SetIntField(object, slot_field, static_cast(savestates[i].slot)); + env->SetObjectField(object, date_field, + env->NewObject(date_class, date_constructor, + static_cast(savestates[i].time * 1000))); + + env->SetObjectArrayElement(array, i, object); + } + LOG_CRITICAL(Frontend, "Called"); + return array; +} + +void Java_org_citra_citra_1emu_NativeLibrary_SaveState(JNIEnv* env, jclass clazz, jint slot) { + Core::System::GetInstance().SendSignal(Core::System::Signal::Save, slot); +} + +void Java_org_citra_citra_1emu_NativeLibrary_LoadState(JNIEnv* env, jclass clazz, jint slot) { + Core::System::GetInstance().SendSignal(Core::System::Signal::Load, slot); +} + } // extern "C" diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 582b9a4f2..f02b15b35 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -146,6 +146,15 @@ JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_InstallCIAS(JNIEn jclass clazz, jobjectArray path); +JNIEXPORT jobjectArray JNICALL +Java_org_citra_citra_1emu_NativeLibrary_GetSavestateInfo(JNIEnv* env, jclass clazz); + +JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SaveState(JNIEnv* env, jclass clazz, + jint slot); + +JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_LoadState(JNIEnv* env, jclass clazz, + jint slot); + #ifdef __cplusplus } #endif diff --git a/src/android/app/src/main/res/menu/menu_emulation.xml b/src/android/app/src/main/res/menu/menu_emulation.xml index a3ecb80d7..ea3301d37 100644 --- a/src/android/app/src/main/res/menu/menu_emulation.xml +++ b/src/android/app/src/main/res/menu/menu_emulation.xml @@ -3,6 +3,18 @@ xmlns:app="http://schemas.android.com/apk/res-auto" tools:context="org.citra.citra_emu.activities.EmulationActivity"> + + + + + + + + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 0e9799dfe..afe96d16e 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -143,6 +143,10 @@ Invalid ROM format + Save State + Load State + Slot %1$d + Slot %1$d - %2$tF %2$tR Show FPS Configure Controls Edit Layout