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); +} }