diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java
index f17ef89859..ebb4ee9416 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java
@@ -5,7 +5,7 @@ package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
-public class ARCheat implements Cheat
+public class ARCheat extends AbstractCheat
{
@Keep
private final long mPointer;
@@ -22,6 +22,13 @@ public class ARCheat implements Cheat
@NonNull
public native String getName();
+ public native boolean getEnabled();
+
+ @Override
+ protected native void setEnabledImpl(boolean enabled);
+
@NonNull
public static native ARCheat[] loadCodes(String gameId, int revision);
+
+ public static native void saveCodes(String gameId, int revision, ARCheat[] codes);
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/AbstractCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/AbstractCheat.java
new file mode 100644
index 0000000000..8f6121808a
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/AbstractCheat.java
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.cheats.model;
+
+import androidx.annotation.Nullable;
+
+public abstract class AbstractCheat implements Cheat
+{
+ private Runnable mChangedCallback = null;
+
+ public void setEnabled(boolean enabled)
+ {
+ setEnabledImpl(enabled);
+ onChanged();
+ }
+
+ public void setChangedCallback(@Nullable Runnable callback)
+ {
+ mChangedCallback = callback;
+ }
+
+ protected void onChanged()
+ {
+ if (mChangedCallback != null)
+ mChangedCallback.run();
+ }
+
+ protected abstract void setEnabledImpl(boolean enabled);
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.java
index 3c50325edf..97bd57d885 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.java
@@ -8,4 +8,8 @@ public interface Cheat
{
@NonNull
String getName();
+
+ boolean getEnabled();
+
+ void setEnabled(boolean enabled);
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java
index 24eb4eff7c..c2cadaf8c0 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java
@@ -12,6 +12,10 @@ public class CheatsViewModel extends ViewModel
private ARCheat[] mARCheats;
private GeckoCheat[] mGeckoCheats;
+ private boolean mPatchCheatsNeedSaving = false;
+ private boolean mARCheatsNeedSaving = false;
+ private boolean mGeckoCheatsNeedSaving = false;
+
public void load(String gameID, int revision)
{
if (mLoaded)
@@ -21,9 +25,43 @@ public class CheatsViewModel extends ViewModel
mARCheats = ARCheat.loadCodes(gameID, revision);
mGeckoCheats = GeckoCheat.loadCodes(gameID, revision);
+ for (PatchCheat cheat : mPatchCheats)
+ {
+ cheat.setChangedCallback(() -> mPatchCheatsNeedSaving = true);
+ }
+ for (ARCheat cheat : mARCheats)
+ {
+ cheat.setChangedCallback(() -> mARCheatsNeedSaving = true);
+ }
+ for (GeckoCheat cheat : mGeckoCheats)
+ {
+ cheat.setChangedCallback(() -> mGeckoCheatsNeedSaving = true);
+ }
+
mLoaded = true;
}
+ public void saveIfNeeded(String gameID, int revision)
+ {
+ if (mPatchCheatsNeedSaving)
+ {
+ PatchCheat.saveCodes(gameID, revision, mPatchCheats);
+ mPatchCheatsNeedSaving = false;
+ }
+
+ if (mARCheatsNeedSaving)
+ {
+ ARCheat.saveCodes(gameID, revision, mARCheats);
+ mARCheatsNeedSaving = false;
+ }
+
+ if (mGeckoCheatsNeedSaving)
+ {
+ GeckoCheat.saveCodes(gameID, revision, mGeckoCheats);
+ mGeckoCheatsNeedSaving = false;
+ }
+ }
+
public Cheat[] getPatchCheats()
{
return mPatchCheats;
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java
index 5f9c7029ec..f6ea493ac3 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java
@@ -5,7 +5,7 @@ package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
-public class GeckoCheat implements Cheat
+public class GeckoCheat extends AbstractCheat
{
@Keep
private final long mPointer;
@@ -22,6 +22,13 @@ public class GeckoCheat implements Cheat
@NonNull
public native String getName();
+ public native boolean getEnabled();
+
+ @Override
+ protected native void setEnabledImpl(boolean enabled);
+
@NonNull
public static native GeckoCheat[] loadCodes(String gameId, int revision);
+
+ public static native void saveCodes(String gameId, int revision, GeckoCheat[] codes);
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java
index 5b2027807e..411df3cdc0 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java
@@ -5,7 +5,7 @@ package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
-public class PatchCheat implements Cheat
+public class PatchCheat extends AbstractCheat
{
@Keep
private final long mPointer;
@@ -22,6 +22,13 @@ public class PatchCheat implements Cheat
@NonNull
public native String getName();
+ public native boolean getEnabled();
+
+ @Override
+ protected native void setEnabledImpl(boolean enabled);
+
@NonNull
public static native PatchCheat[] loadCodes(String gameId, int revision);
+
+ public static native void saveCodes(String gameId, int revision, PatchCheat[] codes);
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.java
index e99f75f6b2..d3fa67905b 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.java
@@ -3,6 +3,8 @@
package org.dolphinemu.dolphinemu.features.cheats.ui;
import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -11,19 +13,35 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat;
-public class CheatViewHolder extends ViewHolder
+public class CheatViewHolder extends ViewHolder implements CompoundButton.OnCheckedChangeListener
{
- private TextView mName;
+ private final TextView mName;
+ private final CheckBox mCheckbox;
+
+ private Cheat mCheat;
public CheatViewHolder(@NonNull View itemView)
{
super(itemView);
mName = itemView.findViewById(R.id.text_name);
+ mCheckbox = itemView.findViewById(R.id.checkbox);
}
public void bind(Cheat item)
{
+ mCheckbox.setOnCheckedChangeListener(null);
+
mName.setText(item.getName());
+ mCheckbox.setChecked(item.getEnabled());
+
+ mCheat = item;
+
+ mCheckbox.setOnCheckedChangeListener(this);
+ }
+
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
+ {
+ mCheat.setEnabled(isChecked);
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.java
index 80a875e026..21fd9a04e9 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.java
@@ -18,6 +18,10 @@ public class CheatsActivity extends AppCompatActivity
private static final String ARG_GAME_ID = "game_id";
private static final String ARG_REVISION = "revision";
+ private String mGameId;
+ private int mRevision;
+ private CheatsViewModel mViewModel;
+
public static void launch(Context context, String gameId, int revision)
{
Intent intent = new Intent(context, CheatsActivity.class);
@@ -34,14 +38,22 @@ public class CheatsActivity extends AppCompatActivity
MainPresenter.skipRescanningLibrary();
Intent intent = getIntent();
- String gameId = intent.getStringExtra(ARG_GAME_ID);
- int revision = intent.getIntExtra(ARG_REVISION, 0);
+ mGameId = intent.getStringExtra(ARG_GAME_ID);
+ mRevision = intent.getIntExtra(ARG_REVISION, 0);
- setTitle(getString(R.string.cheats_with_game_id, gameId));
+ setTitle(getString(R.string.cheats_with_game_id, mGameId));
- CheatsViewModel viewModel = new ViewModelProvider(this).get(CheatsViewModel.class);
- viewModel.load(gameId, revision);
+ mViewModel = new ViewModelProvider(this).get(CheatsViewModel.class);
+ mViewModel.load(mGameId, mRevision);
setContentView(R.layout.activity_cheats);
}
+
+ @Override
+ protected void onStop()
+ {
+ super.onStop();
+
+ mViewModel.saveIfNeeded(mGameId, mRevision);
+ }
}
diff --git a/Source/Android/app/src/main/res/layout/list_item_cheat.xml b/Source/Android/app/src/main/res/layout/list_item_cheat.xml
index f0b568fe12..46bd84196a 100644
--- a/Source/Android/app/src/main/res/layout/list_item_cheat.xml
+++ b/Source/Android/app/src/main/res/layout/list_item_cheat.xml
@@ -16,8 +16,19 @@
tools:text="Hyrule Field Speed Hack"
android:layout_margin="@dimen/spacing_large"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/checkbox"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
+
+
diff --git a/Source/Android/jni/Cheats/ARCheat.cpp b/Source/Android/jni/Cheats/ARCheat.cpp
index d1cb9410a8..f93804f5c6 100644
--- a/Source/Android/jni/Cheats/ARCheat.cpp
+++ b/Source/Android/jni/Cheats/ARCheat.cpp
@@ -39,6 +39,18 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getName(JNIEnv* env
return ToJString(env, GetPointer(env, obj)->name);
}
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getEnabled(JNIEnv* env, jobject obj)
+{
+ return static_cast(GetPointer(env, obj)->enabled);
+}
+
+JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_setEnabledImpl(
+ JNIEnv* env, jobject obj, jboolean enabled)
+{
+ GetPointer(env, obj)->enabled = static_cast(enabled);
+}
+
JNIEXPORT jobjectArray JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_loadCodes(JNIEnv* env, jclass,
jstring jGameID,
@@ -64,4 +76,27 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_loadCodes(JNIEnv* e
return array;
}
+
+JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_saveCodes(
+ JNIEnv* env, jclass, jstring jGameID, jint revision, jobjectArray jCodes)
+{
+ const jsize size = env->GetArrayLength(jCodes);
+ std::vector vector;
+ vector.reserve(size);
+
+ for (jsize i = 0; i < size; ++i)
+ {
+ jobject code = reinterpret_cast(env->GetObjectArrayElement(jCodes, i));
+ vector.emplace_back(*GetPointer(env, code));
+ env->DeleteLocalRef(code);
+ }
+
+ const std::string game_id = GetJString(env, jGameID);
+ const std::string ini_path = File::GetUserPath(D_GAMESETTINGS_IDX) + game_id + ".ini";
+
+ IniFile game_ini_local;
+ game_ini_local.Load(ini_path);
+ ActionReplay::SaveCodes(&game_ini_local, vector);
+ game_ini_local.Save(ini_path);
+}
}
diff --git a/Source/Android/jni/Cheats/GeckoCheat.cpp b/Source/Android/jni/Cheats/GeckoCheat.cpp
index 6718eafac6..0393ff67cc 100644
--- a/Source/Android/jni/Cheats/GeckoCheat.cpp
+++ b/Source/Android/jni/Cheats/GeckoCheat.cpp
@@ -40,6 +40,20 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getName(JNIEnv*
return ToJString(env, GetPointer(env, obj)->name);
}
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getEnabled(JNIEnv* env, jobject obj)
+{
+ return static_cast(GetPointer(env, obj)->enabled);
+}
+
+JNIEXPORT void JNICALL
+Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_setEnabledImpl(JNIEnv* env,
+ jobject obj,
+ jboolean enabled)
+{
+ GetPointer(env, obj)->enabled = static_cast(enabled);
+}
+
JNIEXPORT jobjectArray JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_loadCodes(JNIEnv* env, jclass,
jstring jGameID,
@@ -64,4 +78,27 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_loadCodes(JNIEnv
return array;
}
+
+JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_saveCodes(
+ JNIEnv* env, jclass, jstring jGameID, jint revision, jobjectArray jCodes)
+{
+ const jsize size = env->GetArrayLength(jCodes);
+ std::vector vector;
+ vector.reserve(size);
+
+ for (jsize i = 0; i < size; ++i)
+ {
+ jobject code = reinterpret_cast(env->GetObjectArrayElement(jCodes, i));
+ vector.emplace_back(*GetPointer(env, code));
+ env->DeleteLocalRef(code);
+ }
+
+ const std::string game_id = GetJString(env, jGameID);
+ const std::string ini_path = File::GetUserPath(D_GAMESETTINGS_IDX) + game_id + ".ini";
+
+ IniFile game_ini_local;
+ game_ini_local.Load(ini_path);
+ Gecko::SaveCodes(game_ini_local, vector);
+ game_ini_local.Save(ini_path);
+}
}
diff --git a/Source/Android/jni/Cheats/PatchCheat.cpp b/Source/Android/jni/Cheats/PatchCheat.cpp
index bfe51eae94..f93a9a279b 100644
--- a/Source/Android/jni/Cheats/PatchCheat.cpp
+++ b/Source/Android/jni/Cheats/PatchCheat.cpp
@@ -39,6 +39,20 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getName(JNIEnv*
return ToJString(env, GetPointer(env, obj)->name);
}
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getEnabled(JNIEnv* env, jobject obj)
+{
+ return static_cast(GetPointer(env, obj)->enabled);
+}
+
+JNIEXPORT void JNICALL
+Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_setEnabledImpl(JNIEnv* env,
+ jobject obj,
+ jboolean enabled)
+{
+ GetPointer(env, obj)->enabled = static_cast(enabled);
+}
+
JNIEXPORT jobjectArray JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_loadCodes(JNIEnv* env, jclass,
jstring jGameID,
@@ -64,4 +78,27 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_loadCodes(JNIEnv
return array;
}
+
+JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_saveCodes(
+ JNIEnv* env, jclass, jstring jGameID, jint revision, jobjectArray jCodes)
+{
+ const jsize size = env->GetArrayLength(jCodes);
+ std::vector vector;
+ vector.reserve(size);
+
+ for (jsize i = 0; i < size; ++i)
+ {
+ jobject code = reinterpret_cast(env->GetObjectArrayElement(jCodes, i));
+ vector.emplace_back(*GetPointer(env, code));
+ env->DeleteLocalRef(code);
+ }
+
+ const std::string game_id = GetJString(env, jGameID);
+ const std::string ini_path = File::GetUserPath(D_GAMESETTINGS_IDX) + game_id + ".ini";
+
+ IniFile game_ini_local;
+ game_ini_local.Load(ini_path);
+ PatchEngine::SavePatchSection(&game_ini_local, vector);
+ game_ini_local.Save(ini_path);
+}
}