mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	android: Open cheats by long pressing game in game list (#6491)
This commit is contained in:
		
							parent
							
								
									ebac6b17b0
								
							
						
					
					
						commit
						2e47afd48e
					
				
					 11 changed files with 145 additions and 31 deletions
				
			
		|  | @ -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 | ||||
|      **/ | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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<GameViewHolder> implements | ||||
|         View.OnClickListener { | ||||
| public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> { | ||||
|     private Cursor mCursor; | ||||
|     private GameDataSetObserver mObserver; | ||||
| 
 | ||||
|  | @ -61,7 +64,8 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> 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<GameViewHolder> 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<GameViewHolder> 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)); | ||||
|  |  | |||
|  | @ -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(); | ||||
| } | ||||
|  |  | |||
|  | @ -19,11 +19,17 @@ public class CheatsViewModel extends ViewModel { | |||
|     private final MutableLiveData<Integer> mCheatDeletedEvent = new MutableLiveData<>(null); | ||||
|     private final MutableLiveData<Boolean> 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(); | ||||
|  |  | |||
|  | @ -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); | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,9 +15,24 @@ | |||
| 
 | ||||
| extern "C" { | ||||
| 
 | ||||
| static Cheats::CheatEngine* GetPointer(JNIEnv* env, jobject obj) { | ||||
|     return reinterpret_cast<Cheats::CheatEngine*>( | ||||
|         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<jlong>(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<jsize>(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(); | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -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<VideoCore::LoadCallbackStage, jobject> 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, "<init>", "(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"); | ||||
|  |  | |||
|  | @ -34,6 +34,8 @@ jclass GetCheatClass(); | |||
| jfieldID GetCheatPointer(); | ||||
| jmethodID GetCheatConstructor(); | ||||
| 
 | ||||
| jfieldID GetCheatEnginePointer(); | ||||
| 
 | ||||
| jfieldID GetGameInfoPointer(); | ||||
| 
 | ||||
| jobject GetJavaLoadCallbackStage(VideoCore::LoadCallbackStage stage); | ||||
|  |  | |||
|  | @ -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<jboolean>(!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<jlong>(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<jlong>(title_id); | ||||
| } | ||||
| 
 | ||||
| jstring Java_org_citra_citra_1emu_NativeLibrary_GetGitRevision(JNIEnv* env, | ||||
|                                                                [[maybe_unused]] jclass clazz) { | ||||
|     return nullptr; | ||||
|  |  | |||
|  | @ -146,6 +146,10 @@ | |||
|     <string name="select_game_folder">Select Game Folder</string> | ||||
|     <string name="install_cia_title">Install CIA</string> | ||||
| 
 | ||||
|     <!-- Game Properties --> | ||||
|     <string name="properties">Properties</string> | ||||
|     <string name="properties_not_loaded">The game properties could not be loaded.</string> | ||||
| 
 | ||||
|     <!-- Preferences Screen --> | ||||
|     <string name="preferences_settings">Settings</string> | ||||
|     <string name="preferences_premium">Premium</string> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue