android: Open cheats by long pressing game in game list (#6491)

This commit is contained in:
JosJuice 2023-05-07 16:01:34 +02:00 committed by GitHub
parent ebac6b17b0
commit 2e47afd48e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 145 additions and 31 deletions

View file

@ -128,6 +128,8 @@ public final class NativeLibrary {
public static native void InitGameIni(String gameID); public static native void InitGameIni(String gameID);
public static native long GetTitleId(String filename);
public static native String GetGitRevision(); public static native String GetGitRevision();
/** /**
@ -186,6 +188,11 @@ public final class NativeLibrary {
*/ */
public static native boolean IsRunning(); 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 * Returns the performance stats for the current game
**/ **/

View file

@ -491,7 +491,7 @@ public final class EmulationActivity extends AppCompatActivity {
break; break;
case MENU_ACTION_OPEN_CHEATS: case MENU_ACTION_OPEN_CHEATS:
CheatsActivity.launch(this); CheatsActivity.launch(this, NativeLibrary.GetRunningTitleId());
break; break;
case MENU_ACTION_CLOSE_GAME: case MENU_ACTION_CLOSE_GAME:

View file

@ -1,5 +1,6 @@
package org.citra.citra_emu.adapters; package org.citra.citra_emu.adapters;
import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.os.Build; import android.os.Build;
@ -14,10 +15,13 @@ import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.color.MaterialColors; 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.CitraApplication;
import org.citra.citra_emu.NativeLibrary;
import org.citra.citra_emu.R; import org.citra.citra_emu.R;
import org.citra.citra_emu.activities.EmulationActivity; 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.model.GameDatabase;
import org.citra.citra_emu.utils.FileUtil; import org.citra.citra_emu.utils.FileUtil;
import org.citra.citra_emu.utils.Log; 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) * ContentProviders and Loaders, allows for efficient display of a limited view into a (possibly)
* large dataset. * large dataset.
*/ */
public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> implements public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> {
View.OnClickListener {
private Cursor mCursor; private Cursor mCursor;
private GameDataSetObserver mObserver; private GameDataSetObserver mObserver;
@ -61,7 +64,8 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl
View gameCard = LayoutInflater.from(parent.getContext()) View gameCard = LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_game, parent, false); .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. // Use that view to create a ViewHolder.
return new GameViewHolder(gameCard); return new GameViewHolder(gameCard);
@ -193,10 +197,9 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl
/** /**
* Launches the game that was clicked on. * 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 private void onClick(View view) {
public void onClick(View view) {
// Double-click prevention, using threshold of 1000 ms // Double-click prevention, using threshold of 1000 ms
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) { if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
return; return;
@ -208,6 +211,31 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl
EmulationActivity.launch((FragmentActivity) view.getContext(), holder.path, holder.title); 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) { private boolean isValidGame(String path) {
return Stream.of( return Stream.of(
".rar", ".zip", ".7z", ".torrent", ".tar", ".gz").noneMatch(suffix -> path.toLowerCase().endsWith(suffix)); ".rar", ".zip", ".7z", ".torrent", ".tar", ".gz").noneMatch(suffix -> path.toLowerCase().endsWith(suffix));

View file

@ -1,13 +1,28 @@
package org.citra.citra_emu.features.cheats.model; package org.citra.citra_emu.features.cheats.model;
import androidx.annotation.Keep;
public class CheatEngine { 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();
} }

View file

@ -19,11 +19,17 @@ public class CheatsViewModel extends ViewModel {
private final MutableLiveData<Integer> mCheatDeletedEvent = new MutableLiveData<>(null); private final MutableLiveData<Integer> mCheatDeletedEvent = new MutableLiveData<>(null);
private final MutableLiveData<Boolean> mOpenDetailsViewEvent = new MutableLiveData<>(false); private final MutableLiveData<Boolean> mOpenDetailsViewEvent = new MutableLiveData<>(false);
private CheatEngine mCheatEngine;
private Cheat[] mCheats; private Cheat[] mCheats;
private boolean mCheatsNeedSaving = false; private boolean mCheatsNeedSaving = false;
public void load() { public void initialize(long titleId) {
mCheats = CheatEngine.getCheats(); mCheatEngine = new CheatEngine(titleId);
load();
}
private void load() {
mCheats = mCheatEngine.getCheats();
for (int i = 0; i < mCheats.length; i++) { for (int i = 0; i < mCheats.length; i++) {
int position = i; int position = i;
@ -36,7 +42,7 @@ public class CheatsViewModel extends ViewModel {
public void saveIfNeeded() { public void saveIfNeeded() {
if (mCheatsNeedSaving) { if (mCheatsNeedSaving) {
CheatEngine.saveCheatFile(); mCheatEngine.saveCheatFile();
mCheatsNeedSaving = false; mCheatsNeedSaving = false;
} }
} }
@ -106,7 +112,7 @@ public class CheatsViewModel extends ViewModel {
int position = mCheats.length; int position = mCheats.length;
CheatEngine.addCheat(cheat); mCheatEngine.addCheat(cheat);
mCheatsNeedSaving = true; mCheatsNeedSaving = true;
load(); load();
@ -132,7 +138,7 @@ public class CheatsViewModel extends ViewModel {
} }
public void updateSelectedCheat(Cheat newCheat) { public void updateSelectedCheat(Cheat newCheat) {
CheatEngine.updateCheat(mSelectedCheatPosition, newCheat); mCheatEngine.updateCheat(mSelectedCheatPosition, newCheat);
mCheatsNeedSaving = true; mCheatsNeedSaving = true;
load(); load();
@ -162,7 +168,7 @@ public class CheatsViewModel extends ViewModel {
setSelectedCheat(null, -1); setSelectedCheat(null, -1);
CheatEngine.removeCheat(position); mCheatEngine.removeCheat(position);
mCheatsNeedSaving = true; mCheatsNeedSaving = true;
load(); load();

View file

@ -32,6 +32,8 @@ import java.util.List;
public class CheatsActivity extends AppCompatActivity public class CheatsActivity extends AppCompatActivity
implements SlidingPaneLayout.PanelSlideListener { implements SlidingPaneLayout.PanelSlideListener {
private static String ARG_TITLE_ID = "title_id";
private CheatsViewModel mViewModel; private CheatsViewModel mViewModel;
private SlidingPaneLayout mSlidingPaneLayout; private SlidingPaneLayout mSlidingPaneLayout;
@ -41,8 +43,9 @@ public class CheatsActivity extends AppCompatActivity
private View mCheatListLastFocus; private View mCheatListLastFocus;
private View mCheatDetailsLastFocus; 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 intent = new Intent(context, CheatsActivity.class);
intent.putExtra(ARG_TITLE_ID, titleId);
context.startActivity(intent); context.startActivity(intent);
} }
@ -54,8 +57,10 @@ public class CheatsActivity extends AppCompatActivity
WindowCompat.setDecorFitsSystemWindows(getWindow(), false); WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
long titleId = getIntent().getLongExtra(ARG_TITLE_ID, -1);
mViewModel = new ViewModelProvider(this).get(CheatsViewModel.class); mViewModel = new ViewModelProvider(this).get(CheatsViewModel.class);
mViewModel.load(); mViewModel.initialize(titleId);
setContentView(R.layout.activity_cheats); setContentView(R.layout.activity_cheats);

View file

@ -15,9 +15,24 @@
extern "C" { 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 JNIEXPORT jobjectArray JNICALL
Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_getCheats(JNIEnv* env, jclass) { Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_getCheats(JNIEnv* env, jobject obj) {
auto cheats = Core::System::GetInstance().CheatEngine().GetCheats(); auto cheats = GetPointer(env, obj)->GetCheats();
const jobjectArray array = const jobjectArray array =
env->NewObjectArray(static_cast<jsize>(cheats.size()), IDCache::GetCheatClass(), nullptr); 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( JNIEXPORT void JNICALL Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_addCheat(
JNIEnv* env, jclass, jobject j_cheat) { JNIEnv* env, jobject obj, jobject j_cheat) {
Core::System::GetInstance().CheatEngine().AddCheat(*CheatFromJava(env, j_cheat)); GetPointer(env, obj)->AddCheat(*CheatFromJava(env, j_cheat));
} }
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_removeCheat( JNIEXPORT void JNICALL Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_removeCheat(
JNIEnv* env, jclass, jint index) { JNIEnv* env, jobject obj, jint index) {
Core::System::GetInstance().CheatEngine().RemoveCheat(index); GetPointer(env, obj)->RemoveCheat(index);
} }
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_updateCheat( JNIEXPORT void JNICALL Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_updateCheat(
JNIEnv* env, jclass, jint index, jobject j_new_cheat) { JNIEnv* env, jobject obj, jint index, jobject j_new_cheat) {
Core::System::GetInstance().CheatEngine().UpdateCheat(index, *CheatFromJava(env, j_new_cheat)); GetPointer(env, obj)->UpdateCheat(index, *CheatFromJava(env, j_new_cheat));
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_saveCheatFile(
Java_org_citra_citra_1emu_features_cheats_model_CheatEngine_saveCheatFile(JNIEnv* env, jclass) { JNIEnv* env, jobject obj) {
Core::System::GetInstance().CheatEngine().SaveCheatFile(); GetPointer(env, obj)->SaveCheatFile();
} }
} }

View file

@ -40,6 +40,8 @@ static jclass s_cheat_class;
static jfieldID s_cheat_pointer; static jfieldID s_cheat_pointer;
static jmethodID s_cheat_constructor; static jmethodID s_cheat_constructor;
static jfieldID s_cheat_engine_pointer;
static jfieldID s_game_info_pointer; static jfieldID s_game_info_pointer;
static std::unordered_map<VideoCore::LoadCallbackStage, jobject> s_java_load_callback_stages; static std::unordered_map<VideoCore::LoadCallbackStage, jobject> s_java_load_callback_stages;
@ -137,6 +139,10 @@ jmethodID GetCheatConstructor() {
return s_cheat_constructor; return s_cheat_constructor;
} }
jfieldID GetCheatEnginePointer() {
return s_cheat_engine_pointer;
}
jfieldID GetGameInfoPointer() { jfieldID GetGameInfoPointer() {
return s_game_info_pointer; 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"); s_cheat_constructor = env->GetMethodID(cheat_class, "<init>", "(J)V");
env->DeleteLocalRef(cheat_class); 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 // Initialize GameInfo
const jclass game_info_class = env->FindClass("org/citra/citra_emu/model/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"); s_game_info_pointer = env->GetFieldID(game_info_class, "mPointer", "J");

View file

@ -34,6 +34,8 @@ jclass GetCheatClass();
jfieldID GetCheatPointer(); jfieldID GetCheatPointer();
jmethodID GetCheatConstructor(); jmethodID GetCheatConstructor();
jfieldID GetCheatEnginePointer();
jfieldID GetGameInfoPointer(); jfieldID GetGameInfoPointer();
jobject GetJavaLoadCallbackStage(VideoCore::LoadCallbackStage stage); jobject GetJavaLoadCallbackStage(VideoCore::LoadCallbackStage stage);

View file

@ -24,6 +24,7 @@
#include "core/frontend/camera/factory.h" #include "core/frontend/camera/factory.h"
#include "core/hle/service/am/am.h" #include "core/hle/service/am/am.h"
#include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc.h"
#include "core/loader/loader.h"
#include "core/savestate.h" #include "core/savestate.h"
#include "jni/android_common/android_common.h" #include "jni/android_common/android_common.h"
#include "jni/applets/mii_selector.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); 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, jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadEvent(JNIEnv* env,
[[maybe_unused]] jclass clazz, [[maybe_unused]] jclass clazz,
jstring j_device, jint j_button, 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); 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, jstring Java_org_citra_citra_1emu_NativeLibrary_GetGitRevision(JNIEnv* env,
[[maybe_unused]] jclass clazz) { [[maybe_unused]] jclass clazz) {
return nullptr; return nullptr;

View file

@ -146,6 +146,10 @@
<string name="select_game_folder">Select Game Folder</string> <string name="select_game_folder">Select Game Folder</string>
<string name="install_cia_title">Install CIA</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 --> <!-- Preferences Screen -->
<string name="preferences_settings">Settings</string> <string name="preferences_settings">Settings</string>
<string name="preferences_premium">Premium</string> <string name="preferences_premium">Premium</string>