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 08041f790..4af8b5c8f 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 @@ -128,6 +128,8 @@ public final class NativeLibrary { public static native void InitGameIni(String gameID); + public static native long GetTitleId(String filename); + public static native String GetGitRevision(); /** @@ -186,6 +188,11 @@ public final class NativeLibrary { */ public static native boolean IsRunning(); + /** + * Returns the title ID of the currently running title, or 0 on failure. + */ + public static native long GetRunningTitleId(); + /** * Returns the performance stats for the current game **/ 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 6b4787082..88bb0f3a5 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 @@ -491,7 +491,7 @@ public final class EmulationActivity extends AppCompatActivity { break; case MENU_ACTION_OPEN_CHEATS: - CheatsActivity.launch(this); + CheatsActivity.launch(this, NativeLibrary.GetRunningTitleId()); break; case MENU_ACTION_CLOSE_GAME: diff --git a/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.java b/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.java index 1facecaa3..1c3cad9b1 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.java @@ -1,5 +1,6 @@ package org.citra.citra_emu.adapters; +import android.content.Context; import android.database.Cursor; import android.database.DataSetObserver; import android.os.Build; @@ -14,10 +15,13 @@ import androidx.fragment.app.FragmentActivity; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.color.MaterialColors; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import org.citra.citra_emu.CitraApplication; +import org.citra.citra_emu.NativeLibrary; import org.citra.citra_emu.R; import org.citra.citra_emu.activities.EmulationActivity; +import org.citra.citra_emu.features.cheats.ui.CheatsActivity; import org.citra.citra_emu.model.GameDatabase; import org.citra.citra_emu.utils.FileUtil; import org.citra.citra_emu.utils.Log; @@ -31,8 +35,7 @@ import java.util.stream.Stream; * ContentProviders and Loaders, allows for efficient display of a limited view into a (possibly) * large dataset. */ -public final class GameAdapter extends RecyclerView.Adapter implements - View.OnClickListener { +public final class GameAdapter extends RecyclerView.Adapter { private Cursor mCursor; private GameDataSetObserver mObserver; @@ -61,7 +64,8 @@ public final class GameAdapter extends RecyclerView.Adapter impl View gameCard = LayoutInflater.from(parent.getContext()) .inflate(R.layout.card_game, parent, false); - gameCard.setOnClickListener(this); + gameCard.setOnClickListener(this::onClick); + gameCard.setOnLongClickListener(this::onLongClick); // Use that view to create a ViewHolder. return new GameViewHolder(gameCard); @@ -193,10 +197,9 @@ public final class GameAdapter extends RecyclerView.Adapter impl /** * Launches the game that was clicked on. * - * @param view The card representing the game the user wants to play. + * @param view The view representing the game the user wants to play. */ - @Override - public void onClick(View view) { + private void onClick(View view) { // Double-click prevention, using threshold of 1000 ms if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) { return; @@ -208,6 +211,31 @@ public final class GameAdapter extends RecyclerView.Adapter impl EmulationActivity.launch((FragmentActivity) view.getContext(), holder.path, holder.title); } + /** + * Opens the cheats settings for the game that was clicked on. + * + * @param view The view representing the game the user wants to play. + */ + private boolean onLongClick(View view) { + Context context = view.getContext(); + GameViewHolder holder = (GameViewHolder) view.getTag(); + + final long titleId = NativeLibrary.GetTitleId(holder.path); + + if (titleId == 0) { + new MaterialAlertDialogBuilder(context) + .setIcon(R.mipmap.ic_launcher) + .setTitle(R.string.properties) + .setMessage(R.string.properties_not_loaded) + .setPositiveButton(android.R.string.ok, null) + .show(); + } else { + CheatsActivity.launch(context, titleId); + } + + return true; + } + private boolean isValidGame(String path) { return Stream.of( ".rar", ".zip", ".7z", ".torrent", ".tar", ".gz").noneMatch(suffix -> path.toLowerCase().endsWith(suffix)); diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/model/CheatEngine.java b/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/model/CheatEngine.java index 5748162bb..a1e88a3d3 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/model/CheatEngine.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/model/CheatEngine.java @@ -1,13 +1,28 @@ package org.citra.citra_emu.features.cheats.model; +import androidx.annotation.Keep; + public class CheatEngine { - public static native Cheat[] getCheats(); + @Keep + private final long mPointer; - public static native void addCheat(Cheat cheat); + @Keep + public CheatEngine(long titleId) { + mPointer = initialize(titleId); + } - public static native void removeCheat(int index); + private static native long initialize(long titleId); - public static native void updateCheat(int index, Cheat newCheat); + @Override + protected native void finalize(); - public static native void saveCheatFile(); + public native Cheat[] getCheats(); + + public native void addCheat(Cheat cheat); + + public native void removeCheat(int index); + + public native void updateCheat(int index, Cheat newCheat); + + public native void saveCheatFile(); } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/model/CheatsViewModel.java b/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/model/CheatsViewModel.java index cb4788cb8..dbeb34c21 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/model/CheatsViewModel.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/model/CheatsViewModel.java @@ -19,11 +19,17 @@ public class CheatsViewModel extends ViewModel { private final MutableLiveData mCheatDeletedEvent = new MutableLiveData<>(null); private final MutableLiveData mOpenDetailsViewEvent = new MutableLiveData<>(false); + private CheatEngine mCheatEngine; private Cheat[] mCheats; private boolean mCheatsNeedSaving = false; - public void load() { - mCheats = CheatEngine.getCheats(); + public void initialize(long titleId) { + mCheatEngine = new CheatEngine(titleId); + load(); + } + + private void load() { + mCheats = mCheatEngine.getCheats(); for (int i = 0; i < mCheats.length; i++) { int position = i; @@ -36,7 +42,7 @@ public class CheatsViewModel extends ViewModel { public void saveIfNeeded() { if (mCheatsNeedSaving) { - CheatEngine.saveCheatFile(); + mCheatEngine.saveCheatFile(); mCheatsNeedSaving = false; } } @@ -106,7 +112,7 @@ public class CheatsViewModel extends ViewModel { int position = mCheats.length; - CheatEngine.addCheat(cheat); + mCheatEngine.addCheat(cheat); mCheatsNeedSaving = true; load(); @@ -132,7 +138,7 @@ public class CheatsViewModel extends ViewModel { } public void updateSelectedCheat(Cheat newCheat) { - CheatEngine.updateCheat(mSelectedCheatPosition, newCheat); + mCheatEngine.updateCheat(mSelectedCheatPosition, newCheat); mCheatsNeedSaving = true; load(); @@ -162,7 +168,7 @@ public class CheatsViewModel extends ViewModel { setSelectedCheat(null, -1); - CheatEngine.removeCheat(position); + mCheatEngine.removeCheat(position); mCheatsNeedSaving = true; load(); diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/ui/CheatsActivity.java b/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/ui/CheatsActivity.java index 5df4bc83d..45d0daf5b 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/ui/CheatsActivity.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/cheats/ui/CheatsActivity.java @@ -32,6 +32,8 @@ import java.util.List; public class CheatsActivity extends AppCompatActivity implements SlidingPaneLayout.PanelSlideListener { + private static String ARG_TITLE_ID = "title_id"; + private CheatsViewModel mViewModel; private SlidingPaneLayout mSlidingPaneLayout; @@ -41,8 +43,9 @@ public class CheatsActivity extends AppCompatActivity private View mCheatListLastFocus; private View mCheatDetailsLastFocus; - public static void launch(Context context) { + public static void launch(Context context, long titleId) { Intent intent = new Intent(context, CheatsActivity.class); + intent.putExtra(ARG_TITLE_ID, titleId); context.startActivity(intent); } @@ -54,8 +57,10 @@ public class CheatsActivity extends AppCompatActivity WindowCompat.setDecorFitsSystemWindows(getWindow(), false); + long titleId = getIntent().getLongExtra(ARG_TITLE_ID, -1); + mViewModel = new ViewModelProvider(this).get(CheatsViewModel.class); - mViewModel.load(); + mViewModel.initialize(titleId); setContentView(R.layout.activity_cheats); diff --git a/src/android/app/src/main/jni/cheats/cheat_engine.cpp b/src/android/app/src/main/jni/cheats/cheat_engine.cpp index 61dd7354c..5010bd14f 100644 --- a/src/android/app/src/main/jni/cheats/cheat_engine.cpp +++ b/src/android/app/src/main/jni/cheats/cheat_engine.cpp @@ -15,9 +15,24 @@ extern "C" { +static Cheats::CheatEngine* GetPointer(JNIEnv* env, jobject obj) { + return reinterpret_cast( + env->GetLongField(obj, IDCache::GetCheatEnginePointer())); +} + +JNIEXPORT jlong JNICALL Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_initialize( + JNIEnv* env, jclass, jlong title_id) { + return reinterpret_cast(new Cheats::CheatEngine(title_id, Core::System::GetInstance())); +} + +JNIEXPORT void JNICALL +Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_finalize(JNIEnv* env, jobject obj) { + delete GetPointer(env, obj); +} + JNIEXPORT jobjectArray JNICALL -Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_getCheats(JNIEnv* env, jclass) { - auto cheats = Core::System::GetInstance().CheatEngine().GetCheats(); +Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_getCheats(JNIEnv* env, jobject obj) { + auto cheats = GetPointer(env, obj)->GetCheats(); const jobjectArray array = env->NewObjectArray(static_cast(cheats.size()), IDCache::GetCheatClass(), nullptr); @@ -30,22 +45,22 @@ Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_getCheats(JNIEnv* en } JNIEXPORT void JNICALL Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_addCheat( - JNIEnv* env, jclass, jobject j_cheat) { - Core::System::GetInstance().CheatEngine().AddCheat(*CheatFromJava(env, j_cheat)); + JNIEnv* env, jobject obj, jobject j_cheat) { + GetPointer(env, obj)->AddCheat(*CheatFromJava(env, j_cheat)); } JNIEXPORT void JNICALL Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_removeCheat( - JNIEnv* env, jclass, jint index) { - Core::System::GetInstance().CheatEngine().RemoveCheat(index); + JNIEnv* env, jobject obj, jint index) { + GetPointer(env, obj)->RemoveCheat(index); } JNIEXPORT void JNICALL Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_updateCheat( - JNIEnv* env, jclass, jint index, jobject j_new_cheat) { - Core::System::GetInstance().CheatEngine().UpdateCheat(index, *CheatFromJava(env, j_new_cheat)); + JNIEnv* env, jobject obj, jint index, jobject j_new_cheat) { + GetPointer(env, obj)->UpdateCheat(index, *CheatFromJava(env, j_new_cheat)); } -JNIEXPORT void JNICALL -Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_saveCheatFile(JNIEnv* env, jclass) { - Core::System::GetInstance().CheatEngine().SaveCheatFile(); +JNIEXPORT void JNICALL Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_saveCheatFile( + JNIEnv* env, jobject obj) { + GetPointer(env, obj)->SaveCheatFile(); } } diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index f90c046c2..c34b01917 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -40,6 +40,8 @@ static jclass s_cheat_class; static jfieldID s_cheat_pointer; static jmethodID s_cheat_constructor; +static jfieldID s_cheat_engine_pointer; + static jfieldID s_game_info_pointer; static std::unordered_map s_java_load_callback_stages; @@ -137,6 +139,10 @@ jmethodID GetCheatConstructor() { return s_cheat_constructor; } +jfieldID GetCheatEnginePointer() { + return s_cheat_engine_pointer; +} + jfieldID GetGameInfoPointer() { return s_game_info_pointer; } @@ -211,6 +217,12 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { s_cheat_constructor = env->GetMethodID(cheat_class, "", "(J)V"); env->DeleteLocalRef(cheat_class); + // Initialize CheatEngine + const jclass cheat_engine_class = + env->FindClass("org/citra/citra_emu/features/cheats/model/CheatEngine"); + s_cheat_engine_pointer = env->GetFieldID(cheat_engine_class, "mPointer", "J"); + env->DeleteLocalRef(cheat_engine_class); + // Initialize GameInfo const jclass game_info_class = env->FindClass("org/citra/citra_emu/model/GameInfo"); s_game_info_pointer = env->GetFieldID(game_info_class, "mPointer", "J"); diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index 0fd687666..e57496fae 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -34,6 +34,8 @@ jclass GetCheatClass(); jfieldID GetCheatPointer(); jmethodID GetCheatConstructor(); +jfieldID GetCheatEnginePointer(); + jfieldID GetGameInfoPointer(); jobject GetJavaLoadCallbackStage(VideoCore::LoadCallbackStage stage); diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 48d37666a..0c1620ec6 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -24,6 +24,7 @@ #include "core/frontend/camera/factory.h" #include "core/hle/service/am/am.h" #include "core/hle/service/nfc/nfc.h" +#include "core/loader/loader.h" #include "core/savestate.h" #include "jni/android_common/android_common.h" #include "jni/applets/mii_selector.h" @@ -379,6 +380,13 @@ jboolean Java_org_citra_citra_1emu_NativeLibrary_IsRunning(JNIEnv* env, return static_cast(!stop_run); } +jlong Java_org_citra_citra_1emu_NativeLibrary_GetRunningTitleId(JNIEnv* env, + [[maybe_unused]] jclass clazz) { + u64 title_id{}; + Core::System::GetInstance().GetAppLoader().ReadProgramId(title_id); + return static_cast(title_id); +} + jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadEvent(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring j_device, jint j_button, @@ -435,6 +443,18 @@ void Java_org_citra_citra_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, window->OnTouchMoved((int)x, (int)y); } +jlong Java_org_citra_citra_1emu_NativeLibrary_GetTitleId(JNIEnv* env, [[maybe_unused]] jclass clazz, + jstring j_filename) { + std::string filepath = GetJString(env, j_filename); + const auto loader = Loader::GetLoader(filepath); + + u64 title_id{}; + if (loader) { + loader->ReadProgramId(title_id); + } + return static_cast(title_id); +} + jstring Java_org_citra_citra_1emu_NativeLibrary_GetGitRevision(JNIEnv* env, [[maybe_unused]] jclass clazz) { return nullptr; diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index ba47472c6..03c403ece 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -146,6 +146,10 @@ Select Game Folder Install CIA + + Properties + The game properties could not be loaded. + Settings Premium