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 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
**/

View File

@ -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:

View File

@ -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));

View File

@ -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);
public static native void removeCheat(int index);
public static native void updateCheat(int index, Cheat newCheat);
public static native void saveCheatFile();
@Keep
public CheatEngine(long titleId) {
mPointer = initialize(titleId);
}
private static native long initialize(long titleId);
@Override
protected native void finalize();
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<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();

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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");

View File

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

View File

@ -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;

View File

@ -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>