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);
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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…
Reference in a new issue