From ee3a5a4a81f56aeeda8c2aa1c0800847ab652bed Mon Sep 17 00:00:00 2001 From: JosJuice Date: Tue, 22 Jun 2021 13:43:56 +0200 Subject: [PATCH 01/19] Android: Create CheatsActivity --- .../Android/app/src/main/AndroidManifest.xml | 6 +++++ .../dialogs/GamePropertiesDialog.java | 4 ++++ .../features/cheats/ui/CheatsActivity.java | 22 +++++++++++++++++++ .../app/src/main/res/values/strings.xml | 9 +++++++- 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.java diff --git a/Source/Android/app/src/main/AndroidManifest.xml b/Source/Android/app/src/main/AndroidManifest.xml index 6c49619945..9074dfb3da 100644 --- a/Source/Android/app/src/main/AndroidManifest.xml +++ b/Source/Android/app/src/main/AndroidManifest.xml @@ -76,6 +76,12 @@ android:theme="@style/DolphinSettingsBase" android:label="@string/settings"/> + + SettingsActivity.launch(getContext(), MenuTag.SETTINGS, gameId, revision, isWii)); + itemsBuilder.add(R.string.properties_edit_cheats, (dialog, i) -> + CheatsActivity.launch(getContext(), gameId, revision)); + itemsBuilder.add(R.string.properties_clear_game_settings, (dialog, i) -> clearGameSettings(gameId)); 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 new file mode 100644 index 0000000000..4090507408 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.java @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.cheats.ui; + +import android.content.Context; +import android.content.Intent; + +import androidx.appcompat.app.AppCompatActivity; + +public class CheatsActivity extends AppCompatActivity +{ + private static final String ARG_GAME_ID = "game_id"; + private static final String ARG_REVISION = "revision"; + + public static void launch(Context context, String gameId, int revision) + { + Intent intent = new Intent(context, CheatsActivity.class); + intent.putExtra(ARG_GAME_ID, gameId); + intent.putExtra(ARG_REVISION, revision); + context.startActivity(intent); + } +} diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index e21e942e64..99abca7e6c 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -361,7 +361,8 @@ Convert File Set as Default ISO Edit Game Settings - Clear Game Settings + Edit Cheats + Clear Game Settings and Cheats Cleared settings for %1$s Unable to clear settings for %1$s No game settings to delete @@ -371,6 +372,8 @@ Extension Bindings Junk Data Found The settings file for this game contains extraneous data added by an old version of Dolphin. This will likely prevent global settings from working as intended.\n\nWould you like to fix this by deleting the settings file for this game? All game-specific settings and cheats that you have added will be removed. This cannot be undone. + + Country Company Game ID @@ -382,6 +385,10 @@ No Compression %1$s (%2$s) + + Cheats + Cheats: %1$s + Format Block Size From 4d609c769f7b8eba12da931c0936c5950509e165 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 17 Jul 2021 19:47:11 +0200 Subject: [PATCH 02/19] Android: Implement basic read-only cheats list --- Source/Android/app/build.gradle | 1 + .../features/cheats/model/ARCheat.java | 27 +++++++ .../features/cheats/model/Cheat.java | 11 +++ .../cheats/model/CheatsViewModel.java | 41 ++++++++++ .../features/cheats/model/GeckoCheat.java | 27 +++++++ .../features/cheats/model/PatchCheat.java | 27 +++++++ .../features/cheats/ui/CheatListFragment.java | 43 ++++++++++ .../features/cheats/ui/CheatViewHolder.java | 29 +++++++ .../features/cheats/ui/CheatsActivity.java | 25 ++++++ .../features/cheats/ui/CheatsAdapter.java | 72 +++++++++++++++++ .../src/main/res/layout/activity_cheats.xml | 7 ++ .../main/res/layout/fragment_cheat_list.xml | 6 ++ .../src/main/res/layout/list_item_cheat.xml | 23 ++++++ Source/Android/jni/AndroidCommon/IDCache.cpp | 81 +++++++++++++++++++ Source/Android/jni/AndroidCommon/IDCache.h | 12 +++ Source/Android/jni/CMakeLists.txt | 3 + Source/Android/jni/Cheats/ARCheat.cpp | 67 +++++++++++++++ Source/Android/jni/Cheats/GeckoCheat.cpp | 67 +++++++++++++++ Source/Android/jni/Cheats/PatchCheat.cpp | 67 +++++++++++++++ 19 files changed, 636 insertions(+) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatListFragment.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java create mode 100644 Source/Android/app/src/main/res/layout/activity_cheats.xml create mode 100644 Source/Android/app/src/main/res/layout/fragment_cheat_list.xml create mode 100644 Source/Android/app/src/main/res/layout/list_item_cheat.xml create mode 100644 Source/Android/jni/Cheats/ARCheat.cpp create mode 100644 Source/Android/jni/Cheats/GeckoCheat.cpp create mode 100644 Source/Android/jni/Cheats/PatchCheat.cpp diff --git a/Source/Android/app/build.gradle b/Source/Android/app/build.gradle index 369cc46e79..ebcee3d5cd 100644 --- a/Source/Android/app/build.gradle +++ b/Source/Android/app/build.gradle @@ -90,6 +90,7 @@ dependencies { implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.lifecycle:lifecycle-viewmodel:2.3.1' + implementation 'androidx.fragment:fragment:1.3.6' implementation 'com.google.android.material:material:1.4.0' // Android TV UI libraries. 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 new file mode 100644 index 0000000000..f17ef89859 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.cheats.model; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; + +public class ARCheat implements Cheat +{ + @Keep + private final long mPointer; + + @Keep + private ARCheat(long pointer) + { + mPointer = pointer; + } + + @Override + public native void finalize(); + + @NonNull + public native String getName(); + + @NonNull + public static native ARCheat[] loadCodes(String gameId, int revision); +} 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 new file mode 100644 index 0000000000..3c50325edf --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.java @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.cheats.model; + +import androidx.annotation.NonNull; + +public interface Cheat +{ + @NonNull + String getName(); +} 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 new file mode 100644 index 0000000000..24eb4eff7c --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.cheats.model; + +import androidx.lifecycle.ViewModel; + +public class CheatsViewModel extends ViewModel +{ + private boolean mLoaded = false; + + private PatchCheat[] mPatchCheats; + private ARCheat[] mARCheats; + private GeckoCheat[] mGeckoCheats; + + public void load(String gameID, int revision) + { + if (mLoaded) + return; + + mPatchCheats = PatchCheat.loadCodes(gameID, revision); + mARCheats = ARCheat.loadCodes(gameID, revision); + mGeckoCheats = GeckoCheat.loadCodes(gameID, revision); + + mLoaded = true; + } + + public Cheat[] getPatchCheats() + { + return mPatchCheats; + } + + public ARCheat[] getARCheats() + { + return mARCheats; + } + + public Cheat[] getGeckoCheats() + { + return mGeckoCheats; + } +} 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 new file mode 100644 index 0000000000..5f9c7029ec --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.cheats.model; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; + +public class GeckoCheat implements Cheat +{ + @Keep + private final long mPointer; + + @Keep + private GeckoCheat(long pointer) + { + mPointer = pointer; + } + + @Override + public native void finalize(); + + @NonNull + public native String getName(); + + @NonNull + public static native GeckoCheat[] loadCodes(String gameId, int revision); +} 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 new file mode 100644 index 0000000000..5b2027807e --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.cheats.model; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; + +public class PatchCheat implements Cheat +{ + @Keep + private final long mPointer; + + @Keep + private PatchCheat(long pointer) + { + mPointer = pointer; + } + + @Override + public native void finalize(); + + @NonNull + public native String getName(); + + @NonNull + public static native PatchCheat[] loadCodes(String gameId, int revision); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatListFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatListFragment.java new file mode 100644 index 0000000000..82c5b549bb --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatListFragment.java @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.cheats.ui; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel; +import org.dolphinemu.dolphinemu.ui.DividerItemDecoration; + +public class CheatListFragment extends Fragment +{ + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) + { + return inflater.inflate(R.layout.fragment_cheat_list, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) + { + RecyclerView recyclerView = view.findViewById(R.id.cheat_list); + + CheatsActivity activity = (CheatsActivity) requireActivity(); + CheatsViewModel viewModel = new ViewModelProvider(activity).get(CheatsViewModel.class); + + recyclerView.setAdapter(new CheatsAdapter(viewModel)); + recyclerView.setLayoutManager(new LinearLayoutManager(activity)); + recyclerView.addItemDecoration(new DividerItemDecoration(activity, null)); + } +} 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 new file mode 100644 index 0000000000..e99f75f6b2 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.java @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.cheats.ui; + +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.features.cheats.model.Cheat; + +public class CheatViewHolder extends ViewHolder +{ + private TextView mName; + + public CheatViewHolder(@NonNull View itemView) + { + super(itemView); + + mName = itemView.findViewById(R.id.text_name); + } + + public void bind(Cheat item) + { + mName.setText(item.getName()); + } +} 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 4090507408..80a875e026 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 @@ -4,8 +4,14 @@ package org.dolphinemu.dolphinemu.features.cheats.ui; import android.content.Context; import android.content.Intent; +import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.ViewModelProvider; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel; +import org.dolphinemu.dolphinemu.ui.main.MainPresenter; public class CheatsActivity extends AppCompatActivity { @@ -19,4 +25,23 @@ public class CheatsActivity extends AppCompatActivity intent.putExtra(ARG_REVISION, revision); context.startActivity(intent); } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + MainPresenter.skipRescanningLibrary(); + + Intent intent = getIntent(); + String gameId = intent.getStringExtra(ARG_GAME_ID); + int revision = intent.getIntExtra(ARG_REVISION, 0); + + setTitle(getString(R.string.cheats_with_game_id, gameId)); + + CheatsViewModel viewModel = new ViewModelProvider(this).get(CheatsViewModel.class); + viewModel.load(gameId, revision); + + setContentView(R.layout.activity_cheats); + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java new file mode 100644 index 0000000000..6193b6c52f --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.cheats.ui; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.features.cheats.model.Cheat; +import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel; + +public class CheatsAdapter extends RecyclerView.Adapter +{ + private final CheatsViewModel mViewModel; + + public CheatsAdapter(CheatsViewModel viewModel) + { + mViewModel = viewModel; + } + + @NonNull + @Override + public CheatViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) + { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.list_item_cheat, parent, false); + return new CheatViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull CheatViewHolder holder, int position) + { + holder.bind(getItemAt(position)); + } + + @Override + public int getItemCount() + { + return mViewModel.getARCheats().length + mViewModel.getGeckoCheats().length + + mViewModel.getPatchCheats().length; + } + + private Cheat getItemAt(int position) + { + Cheat[] patchCheats = mViewModel.getPatchCheats(); + if (position < patchCheats.length) + { + return patchCheats[position]; + } + position -= patchCheats.length; + + Cheat[] arCheats = mViewModel.getARCheats(); + if (position < arCheats.length) + { + return arCheats[position]; + } + position -= arCheats.length; + + Cheat[] geckoCheats = mViewModel.getGeckoCheats(); + if (position < geckoCheats.length) + { + return geckoCheats[position]; + } + position -= geckoCheats.length; + + throw new IndexOutOfBoundsException(); + } +} diff --git a/Source/Android/app/src/main/res/layout/activity_cheats.xml b/Source/Android/app/src/main/res/layout/activity_cheats.xml new file mode 100644 index 0000000000..21279342a7 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/activity_cheats.xml @@ -0,0 +1,7 @@ + + diff --git a/Source/Android/app/src/main/res/layout/fragment_cheat_list.xml b/Source/Android/app/src/main/res/layout/fragment_cheat_list.xml new file mode 100644 index 0000000000..8120f24443 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/fragment_cheat_list.xml @@ -0,0 +1,6 @@ + + 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 new file mode 100644 index 0000000000..f0b568fe12 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/list_item_cheat.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 7c261337b5..d0a7710e1a 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -58,6 +58,18 @@ static jmethodID s_network_helper_get_network_gateway; static jclass s_boolean_supplier_class; static jmethodID s_boolean_supplier_get; +static jclass s_ar_cheat_class; +static jfieldID s_ar_cheat_pointer; +static jmethodID s_ar_cheat_constructor; + +static jclass s_gecko_cheat_class; +static jfieldID s_gecko_cheat_pointer; +static jmethodID s_gecko_cheat_constructor; + +static jclass s_patch_cheat_class; +static jfieldID s_patch_cheat_pointer; +static jmethodID s_patch_cheat_constructor; + namespace IDCache { JNIEnv* GetEnvForThread() @@ -268,6 +280,51 @@ jmethodID GetBooleanSupplierGet() return s_boolean_supplier_get; } +jclass GetARCheatClass() +{ + return s_ar_cheat_class; +} + +jfieldID GetARCheatPointer() +{ + return s_ar_cheat_pointer; +} + +jmethodID GetARCheatConstructor() +{ + return s_ar_cheat_constructor; +} + +jclass GetGeckoCheatClass() +{ + return s_gecko_cheat_class; +} + +jfieldID GetGeckoCheatPointer() +{ + return s_gecko_cheat_pointer; +} + +jmethodID GetGeckoCheatConstructor() +{ + return s_gecko_cheat_constructor; +} + +jclass GetPatchCheatClass() +{ + return s_patch_cheat_class; +} + +jfieldID GetPatchCheatPointer() +{ + return s_patch_cheat_pointer; +} + +jmethodID GetPatchCheatConstructor() +{ + return s_patch_cheat_constructor; +} + } // namespace IDCache extern "C" { @@ -376,6 +433,27 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) s_boolean_supplier_get = env->GetMethodID(s_boolean_supplier_class, "get", "()Z"); env->DeleteLocalRef(boolean_supplier_class); + const jclass ar_cheat_class = + env->FindClass("org/dolphinemu/dolphinemu/features/cheats/model/ARCheat"); + s_ar_cheat_class = reinterpret_cast(env->NewGlobalRef(ar_cheat_class)); + s_ar_cheat_pointer = env->GetFieldID(ar_cheat_class, "mPointer", "J"); + s_ar_cheat_constructor = env->GetMethodID(ar_cheat_class, "", "(J)V"); + env->DeleteLocalRef(ar_cheat_class); + + const jclass gecko_cheat_class = + env->FindClass("org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat"); + s_gecko_cheat_class = reinterpret_cast(env->NewGlobalRef(gecko_cheat_class)); + s_gecko_cheat_pointer = env->GetFieldID(gecko_cheat_class, "mPointer", "J"); + s_gecko_cheat_constructor = env->GetMethodID(gecko_cheat_class, "", "(J)V"); + env->DeleteLocalRef(gecko_cheat_class); + + const jclass patch_cheat_class = + env->FindClass("org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat"); + s_patch_cheat_class = reinterpret_cast(env->NewGlobalRef(patch_cheat_class)); + s_patch_cheat_pointer = env->GetFieldID(patch_cheat_class, "mPointer", "J"); + s_patch_cheat_constructor = env->GetMethodID(patch_cheat_class, "", "(J)V"); + env->DeleteLocalRef(patch_cheat_class); + return JNI_VERSION; } @@ -396,5 +474,8 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) env->DeleteGlobalRef(s_content_handler_class); env->DeleteGlobalRef(s_network_helper_class); env->DeleteGlobalRef(s_boolean_supplier_class); + env->DeleteGlobalRef(s_ar_cheat_class); + env->DeleteGlobalRef(s_gecko_cheat_class); + env->DeleteGlobalRef(s_patch_cheat_class); } } diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h index e67ef43243..3268c842c3 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.h +++ b/Source/Android/jni/AndroidCommon/IDCache.h @@ -57,4 +57,16 @@ jmethodID GetNetworkHelperGetNetworkGateway(); jmethodID GetBooleanSupplierGet(); +jclass GetARCheatClass(); +jfieldID GetARCheatPointer(); +jmethodID GetARCheatConstructor(); + +jclass GetGeckoCheatClass(); +jfieldID GetGeckoCheatPointer(); +jmethodID GetGeckoCheatConstructor(); + +jclass GetPatchCheatClass(); +jfieldID GetPatchCheatPointer(); +jmethodID GetPatchCheatConstructor(); + } // namespace IDCache diff --git a/Source/Android/jni/CMakeLists.txt b/Source/Android/jni/CMakeLists.txt index d358ed46d1..41ac4cbbd8 100644 --- a/Source/Android/jni/CMakeLists.txt +++ b/Source/Android/jni/CMakeLists.txt @@ -1,4 +1,7 @@ add_library(main SHARED + Cheats/ARCheat.cpp + Cheats/GeckoCheat.cpp + Cheats/PatchCheat.cpp Config/NativeConfig.cpp Config/PostProcessing.cpp GameList/GameFile.cpp diff --git a/Source/Android/jni/Cheats/ARCheat.cpp b/Source/Android/jni/Cheats/ARCheat.cpp new file mode 100644 index 0000000000..d1cb9410a8 --- /dev/null +++ b/Source/Android/jni/Cheats/ARCheat.cpp @@ -0,0 +1,67 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include + +#include "Common/FileUtil.h" +#include "Common/IniFile.h" +#include "Core/ActionReplay.h" +#include "Core/ConfigManager.h" +#include "jni/AndroidCommon/AndroidCommon.h" +#include "jni/AndroidCommon/IDCache.h" + +static ActionReplay::ARCode* GetPointer(JNIEnv* env, jobject obj) +{ + return reinterpret_cast( + env->GetLongField(obj, IDCache::GetARCheatPointer())); +} + +jobject ARCheatToJava(JNIEnv* env, const ActionReplay::ARCode& code) +{ + return env->NewObject(IDCache::GetARCheatClass(), IDCache::GetARCheatConstructor(), + reinterpret_cast(new ActionReplay::ARCode(code))); +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_finalize(JNIEnv* env, jobject obj) +{ + delete GetPointer(env, obj); +} + +JNIEXPORT jstring JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getName(JNIEnv* env, jobject obj) +{ + return ToJString(env, GetPointer(env, obj)->name); +} + +JNIEXPORT jobjectArray JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_loadCodes(JNIEnv* env, jclass, + jstring jGameID, + jint revision) +{ + const std::string game_id = GetJString(env, jGameID); + IniFile game_ini_local; + + // We don't use LoadLocalGameIni() here because user cheat codes that are installed via the UI + // will always be stored in GS/${GAMEID}.ini + game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + game_id + ".ini"); + const IniFile game_ini_default = SConfig::LoadDefaultGameIni(game_id, revision); + + const std::vector codes = + ActionReplay::LoadCodes(game_ini_default, game_ini_local); + + const jobjectArray array = + env->NewObjectArray(static_cast(codes.size()), IDCache::GetARCheatClass(), nullptr); + + jsize i = 0; + for (const ActionReplay::ARCode& code : codes) + env->SetObjectArrayElement(array, i++, ARCheatToJava(env, code)); + + return array; +} +} diff --git a/Source/Android/jni/Cheats/GeckoCheat.cpp b/Source/Android/jni/Cheats/GeckoCheat.cpp new file mode 100644 index 0000000000..6718eafac6 --- /dev/null +++ b/Source/Android/jni/Cheats/GeckoCheat.cpp @@ -0,0 +1,67 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include + +#include "Common/FileUtil.h" +#include "Common/IniFile.h" +#include "Core/ConfigManager.h" +#include "Core/GeckoCode.h" +#include "Core/GeckoCodeConfig.h" +#include "jni/AndroidCommon/AndroidCommon.h" +#include "jni/AndroidCommon/IDCache.h" + +static Gecko::GeckoCode* GetPointer(JNIEnv* env, jobject obj) +{ + return reinterpret_cast( + env->GetLongField(obj, IDCache::GetGeckoCheatPointer())); +} + +jobject GeckoCheatToJava(JNIEnv* env, const Gecko::GeckoCode& code) +{ + return env->NewObject(IDCache::GetGeckoCheatClass(), IDCache::GetGeckoCheatConstructor(), + reinterpret_cast(new Gecko::GeckoCode(code))); +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_finalize(JNIEnv* env, jobject obj) +{ + delete GetPointer(env, obj); +} + +JNIEXPORT jstring JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getName(JNIEnv* env, jobject obj) +{ + return ToJString(env, GetPointer(env, obj)->name); +} + +JNIEXPORT jobjectArray JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_loadCodes(JNIEnv* env, jclass, + jstring jGameID, + jint revision) +{ + const std::string game_id = GetJString(env, jGameID); + IniFile game_ini_local; + + // We don't use LoadLocalGameIni() here because user cheat codes that are installed via the UI + // will always be stored in GS/${GAMEID}.ini + game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + game_id + ".ini"); + const IniFile game_ini_default = SConfig::LoadDefaultGameIni(game_id, revision); + + const std::vector codes = Gecko::LoadCodes(game_ini_default, game_ini_local); + + const jobjectArray array = + env->NewObjectArray(static_cast(codes.size()), IDCache::GetGeckoCheatClass(), nullptr); + + jsize i = 0; + for (const Gecko::GeckoCode& code : codes) + env->SetObjectArrayElement(array, i++, GeckoCheatToJava(env, code)); + + return array; +} +} diff --git a/Source/Android/jni/Cheats/PatchCheat.cpp b/Source/Android/jni/Cheats/PatchCheat.cpp new file mode 100644 index 0000000000..bfe51eae94 --- /dev/null +++ b/Source/Android/jni/Cheats/PatchCheat.cpp @@ -0,0 +1,67 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include + +#include "Common/FileUtil.h" +#include "Common/IniFile.h" +#include "Core/ConfigManager.h" +#include "Core/PatchEngine.h" +#include "jni/AndroidCommon/AndroidCommon.h" +#include "jni/AndroidCommon/IDCache.h" + +static PatchEngine::Patch* GetPointer(JNIEnv* env, jobject obj) +{ + return reinterpret_cast( + env->GetLongField(obj, IDCache::GetPatchCheatPointer())); +} + +jobject PatchCheatToJava(JNIEnv* env, const PatchEngine::Patch& patch) +{ + return env->NewObject(IDCache::GetPatchCheatClass(), IDCache::GetPatchCheatConstructor(), + reinterpret_cast(new PatchEngine::Patch(patch))); +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_finalize(JNIEnv* env, jobject obj) +{ + delete GetPointer(env, obj); +} + +JNIEXPORT jstring JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getName(JNIEnv* env, jobject obj) +{ + return ToJString(env, GetPointer(env, obj)->name); +} + +JNIEXPORT jobjectArray JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_loadCodes(JNIEnv* env, jclass, + jstring jGameID, + jint revision) +{ + const std::string game_id = GetJString(env, jGameID); + IniFile game_ini_local; + + // We don't use LoadLocalGameIni() here because user cheat codes that are installed via the UI + // will always be stored in GS/${GAMEID}.ini + game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + game_id + ".ini"); + const IniFile game_ini_default = SConfig::LoadDefaultGameIni(game_id, revision); + + std::vector patches; + PatchEngine::LoadPatchSection("OnFrame", &patches, game_ini_default, game_ini_local); + + const jobjectArray array = env->NewObjectArray(static_cast(patches.size()), + IDCache::GetPatchCheatClass(), nullptr); + + jsize i = 0; + for (const PatchEngine::Patch& patch : patches) + env->SetObjectArrayElement(array, i++, PatchCheatToJava(env, patch)); + + return array; +} +} From 67a8855d9a5ec6e6c23617d72beb29db002424c7 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 17 Jul 2021 22:15:26 +0200 Subject: [PATCH 03/19] Android: Add "Enable Cheats" setting to GUI --- .../dolphinemu/features/settings/model/BooleanSetting.java | 2 ++ .../features/settings/ui/SettingsFragmentPresenter.java | 2 ++ Source/Android/app/src/main/res/values/strings.xml | 1 + 3 files changed, 5 insertions(+) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java index bd71683c1f..480f69937f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java @@ -14,6 +14,7 @@ public enum BooleanSetting implements AbstractBooleanSetting MAIN_FASTMEM(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "Fastmem", true), MAIN_CPU_THREAD(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "CPUThread", true), MAIN_SYNC_ON_SKIP_IDLE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "SyncOnSkipIdle", true), + MAIN_ENABLE_CHEATS(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "EnableCheats", false), MAIN_OVERRIDE_REGION_SETTINGS(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "OverrideRegionSettings", false), MAIN_AUDIO_STRETCH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "AudioStretch", false), @@ -198,6 +199,7 @@ public enum BooleanSetting implements AbstractBooleanSetting private static final BooleanSetting[] NOT_RUNTIME_EDITABLE_ARRAY = new BooleanSetting[]{ MAIN_DSP_HLE, MAIN_CPU_THREAD, + MAIN_ENABLE_CHEATS, MAIN_OVERRIDE_REGION_SETTINGS, MAIN_WII_SD_CARD, // Can actually be changed, but specific code is required MAIN_DSP_JIT diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java index 0450a7cedf..d91faa775e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java @@ -258,6 +258,8 @@ public final class SettingsFragmentPresenter { sl.add(new CheckBoxSetting(mContext, BooleanSetting.MAIN_CPU_THREAD, R.string.dual_core, R.string.dual_core_description)); + sl.add(new CheckBoxSetting(mContext, BooleanSetting.MAIN_ENABLE_CHEATS, R.string.enable_cheats, + 0)); sl.add(new CheckBoxSetting(mContext, BooleanSetting.MAIN_OVERRIDE_REGION_SETTINGS, R.string.override_region_settings, 0)); sl.add(new CheckBoxSetting(mContext, BooleanSetting.MAIN_AUTO_DISC_CHANGE, diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 99abca7e6c..43fb50fd47 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -135,6 +135,7 @@ General Dual Core Split workload to two CPU cores instead of one. Increases speed. + Enable Cheats Speed Limit (0% = Unlimited) WARNING: Changing this from the default (100%) WILL break games and cause glitches. Please do not report bugs that occur with a non-default clock. GameCube From 93a12713864b147c003cfb757b42b47ecb69676f Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 18 Jul 2021 18:54:01 +0200 Subject: [PATCH 04/19] Android: Add checkboxes for toggling cheats enabled --- .../features/cheats/model/ARCheat.java | 9 ++++- .../features/cheats/model/AbstractCheat.java | 29 ++++++++++++++ .../features/cheats/model/Cheat.java | 4 ++ .../cheats/model/CheatsViewModel.java | 38 +++++++++++++++++++ .../features/cheats/model/GeckoCheat.java | 9 ++++- .../features/cheats/model/PatchCheat.java | 9 ++++- .../features/cheats/ui/CheatViewHolder.java | 22 ++++++++++- .../features/cheats/ui/CheatsActivity.java | 22 ++++++++--- .../src/main/res/layout/list_item_cheat.xml | 13 ++++++- Source/Android/jni/Cheats/ARCheat.cpp | 35 +++++++++++++++++ Source/Android/jni/Cheats/GeckoCheat.cpp | 37 ++++++++++++++++++ Source/Android/jni/Cheats/PatchCheat.cpp | 37 ++++++++++++++++++ 12 files changed, 253 insertions(+), 11 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/AbstractCheat.java 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); +} } From 95879c2e76a68526079445b427d1f3740416c978 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Wed, 4 Aug 2021 22:38:14 +0200 Subject: [PATCH 05/19] Android: Add details view for cheats The details view only contains the name of the cheat for now. --- Source/Android/app/build.gradle | 1 + .../cheats/model/CheatsViewModel.java | 14 +++++ .../cheats/ui/CheatDetailsFragment.java | 62 +++++++++++++++++++ .../features/cheats/ui/CheatViewHolder.java | 16 ++++- .../features/cheats/ui/CheatsAdapter.java | 2 +- .../src/main/res/layout/activity_cheats.xml | 23 +++++-- .../res/layout/fragment_cheat_details.xml | 39 ++++++++++++ .../src/main/res/layout/list_item_cheat.xml | 1 + .../app/src/main/res/values/strings.xml | 1 + 9 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java create mode 100644 Source/Android/app/src/main/res/layout/fragment_cheat_details.xml diff --git a/Source/Android/app/build.gradle b/Source/Android/app/build.gradle index ebcee3d5cd..57715c02dc 100644 --- a/Source/Android/app/build.gradle +++ b/Source/Android/app/build.gradle @@ -91,6 +91,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.lifecycle:lifecycle-viewmodel:2.3.1' implementation 'androidx.fragment:fragment:1.3.6' + implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0-alpha03" implementation 'com.google.android.material:material:1.4.0' // Android TV UI libraries. 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 c2cadaf8c0..d05da7d978 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 @@ -2,12 +2,16 @@ package org.dolphinemu.dolphinemu.features.cheats.model; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; public class CheatsViewModel extends ViewModel { private boolean mLoaded = false; + private final MutableLiveData mSelectedCheat = new MutableLiveData<>(null); + private PatchCheat[] mPatchCheats; private ARCheat[] mARCheats; private GeckoCheat[] mGeckoCheats; @@ -62,6 +66,16 @@ public class CheatsViewModel extends ViewModel } } + public LiveData getSelectedCheat() + { + return mSelectedCheat; + } + + public void setSelectedCheat(Cheat cheat) + { + mSelectedCheat.setValue(cheat); + } + public Cheat[] getPatchCheats() { return mPatchCheats; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java new file mode 100644 index 0000000000..90356b63eb --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.cheats.ui; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModelProvider; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.features.cheats.model.Cheat; +import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel; + +public class CheatDetailsFragment extends Fragment +{ + private View mRoot; + private EditText mEditName; + + private CheatsViewModel mViewModel; + private Cheat mCheat; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) + { + return inflater.inflate(R.layout.fragment_cheat_details, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) + { + mRoot = view.findViewById(R.id.root); + mEditName = view.findViewById(R.id.edit_name); + + CheatsActivity activity = (CheatsActivity) requireActivity(); + mViewModel = new ViewModelProvider(activity).get(CheatsViewModel.class); + + LiveData selectedCheat = mViewModel.getSelectedCheat(); + selectedCheat.observe(getViewLifecycleOwner(), this::populateFields); + populateFields(selectedCheat.getValue()); + } + + private void populateFields(@Nullable Cheat cheat) + { + mRoot.setVisibility(cheat == null ? View.GONE : View.VISIBLE); + + if (cheat != null && cheat != mCheat) + { + mEditName.setText(cheat.getName()); + } + + mCheat = cheat; + } +} 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 d3fa67905b..adaa5949a6 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 @@ -12,34 +12,46 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.features.cheats.model.Cheat; +import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel; -public class CheatViewHolder extends ViewHolder implements CompoundButton.OnCheckedChangeListener +public class CheatViewHolder extends ViewHolder + implements View.OnClickListener, CompoundButton.OnCheckedChangeListener { + private final View mRoot; private final TextView mName; private final CheckBox mCheckbox; + private CheatsViewModel mViewModel; private Cheat mCheat; public CheatViewHolder(@NonNull View itemView) { super(itemView); + mRoot = itemView.findViewById(R.id.root); mName = itemView.findViewById(R.id.text_name); mCheckbox = itemView.findViewById(R.id.checkbox); } - public void bind(Cheat item) + public void bind(CheatsViewModel viewModel, Cheat item) { mCheckbox.setOnCheckedChangeListener(null); mName.setText(item.getName()); mCheckbox.setChecked(item.getEnabled()); + mViewModel = viewModel; mCheat = item; + mRoot.setOnClickListener(this); mCheckbox.setOnCheckedChangeListener(this); } + public void onClick(View root) + { + mViewModel.setSelectedCheat(mCheat); + } + 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/CheatsAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java index 6193b6c52f..3638ab383f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java @@ -34,7 +34,7 @@ public class CheatsAdapter extends RecyclerView.Adapter @Override public void onBindViewHolder(@NonNull CheatViewHolder holder, int position) { - holder.bind(getItemAt(position)); + holder.bind(mViewModel, getItemAt(position)); } @Override diff --git a/Source/Android/app/src/main/res/layout/activity_cheats.xml b/Source/Android/app/src/main/res/layout/activity_cheats.xml index 21279342a7..0f89b488e1 100644 --- a/Source/Android/app/src/main/res/layout/activity_cheats.xml +++ b/Source/Android/app/src/main/res/layout/activity_cheats.xml @@ -1,7 +1,22 @@ - + android:layout_height="match_parent"> + + + + + + diff --git a/Source/Android/app/src/main/res/layout/fragment_cheat_details.xml b/Source/Android/app/src/main/res/layout/fragment_cheat_details.xml new file mode 100644 index 0000000000..14c7dfeace --- /dev/null +++ b/Source/Android/app/src/main/res/layout/fragment_cheat_details.xml @@ -0,0 +1,39 @@ + + + + + + + + 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 46bd84196a..5d79260b68 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 @@ -3,6 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/root" android:layout_width="match_parent" android:layout_height="wrap_content" android:focusable="true"> diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 43fb50fd47..8582608000 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -389,6 +389,7 @@ Cheats Cheats: %1$s + Name Format From a303b4bc98af237ab708221b88c51df22d3f2ebd Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 5 Aug 2021 16:23:13 +0200 Subject: [PATCH 06/19] Android: Programmatically open/close cheat details Only has an effect when using a narrow screen. --- .../cheats/model/CheatsViewModel.java | 13 ++++++ .../features/cheats/ui/CheatViewHolder.java | 1 + .../features/cheats/ui/CheatsActivity.java | 32 +++++++++++++ .../ui/TwoPaneOnBackPressedCallback.java | 46 +++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/TwoPaneOnBackPressedCallback.java 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 d05da7d978..aac6c02ed0 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,8 @@ public class CheatsViewModel extends ViewModel private final MutableLiveData mSelectedCheat = new MutableLiveData<>(null); + private final MutableLiveData mOpenDetailsViewEvent = new MutableLiveData<>(false); + private PatchCheat[] mPatchCheats; private ARCheat[] mARCheats; private GeckoCheat[] mGeckoCheats; @@ -76,6 +78,17 @@ public class CheatsViewModel extends ViewModel mSelectedCheat.setValue(cheat); } + public LiveData getOpenDetailsViewEvent() + { + return mOpenDetailsViewEvent; + } + + public void openDetailsView() + { + mOpenDetailsViewEvent.setValue(true); + mOpenDetailsViewEvent.setValue(false); + } + public Cheat[] getPatchCheats() { return mPatchCheats; 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 adaa5949a6..c48c7c27be 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 @@ -50,6 +50,7 @@ public class CheatViewHolder extends ViewHolder public void onClick(View root) { mViewModel.setSelectedCheat(mCheat); + mViewModel.openDetailsView(); } public void onCheckedChanged(CompoundButton buttonView, boolean 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 21fd9a04e9..911c54ab0e 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 @@ -8,9 +8,12 @@ import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider; +import androidx.slidingpanelayout.widget.SlidingPaneLayout; import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.features.cheats.model.Cheat; import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel; +import org.dolphinemu.dolphinemu.ui.TwoPaneOnBackPressedCallback; import org.dolphinemu.dolphinemu.ui.main.MainPresenter; public class CheatsActivity extends AppCompatActivity @@ -22,6 +25,8 @@ public class CheatsActivity extends AppCompatActivity private int mRevision; private CheatsViewModel mViewModel; + private SlidingPaneLayout mSlidingPaneLayout; + public static void launch(Context context, String gameId, int revision) { Intent intent = new Intent(context, CheatsActivity.class); @@ -47,6 +52,16 @@ public class CheatsActivity extends AppCompatActivity mViewModel.load(mGameId, mRevision); setContentView(R.layout.activity_cheats); + + mSlidingPaneLayout = findViewById(R.id.sliding_pane_layout); + + getOnBackPressedDispatcher().addCallback(this, + new TwoPaneOnBackPressedCallback(mSlidingPaneLayout)); + + mViewModel.getSelectedCheat().observe(this, this::onSelectedCheatChanged); + onSelectedCheatChanged(mViewModel.getSelectedCheat().getValue()); + + mViewModel.getOpenDetailsViewEvent().observe(this, this::openDetailsView); } @Override @@ -56,4 +71,21 @@ public class CheatsActivity extends AppCompatActivity mViewModel.saveIfNeeded(mGameId, mRevision); } + + private void onSelectedCheatChanged(Cheat selectedCheat) + { + boolean cheatSelected = selectedCheat != null; + + if (!cheatSelected && mSlidingPaneLayout.isOpen()) + mSlidingPaneLayout.close(); + + mSlidingPaneLayout.setLockMode(cheatSelected ? + SlidingPaneLayout.LOCK_MODE_UNLOCKED : SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED); + } + + private void openDetailsView(boolean open) + { + if (open) + mSlidingPaneLayout.open(); + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/TwoPaneOnBackPressedCallback.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/TwoPaneOnBackPressedCallback.java new file mode 100644 index 0000000000..a6a0da5f97 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/TwoPaneOnBackPressedCallback.java @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.ui; + +import android.view.View; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.slidingpanelayout.widget.SlidingPaneLayout; + +public class TwoPaneOnBackPressedCallback extends OnBackPressedCallback + implements SlidingPaneLayout.PanelSlideListener +{ + private final SlidingPaneLayout mSlidingPaneLayout; + + public TwoPaneOnBackPressedCallback(@NonNull SlidingPaneLayout slidingPaneLayout) + { + super(slidingPaneLayout.isSlideable() && slidingPaneLayout.isOpen()); + mSlidingPaneLayout = slidingPaneLayout; + slidingPaneLayout.addPanelSlideListener(this); + } + + @Override + public void handleOnBackPressed() + { + mSlidingPaneLayout.close(); + } + + @Override + public void onPanelSlide(@NonNull View panel, float slideOffset) + { + } + + @Override + public void onPanelOpened(@NonNull View panel) + { + setEnabled(true); + } + + @Override + public void onPanelClosed(@NonNull View panel) + { + setEnabled(false); + } +} + From 43dcbf33adc72e935c518c3a7a0e621fe5694b69 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 7 Aug 2021 16:08:07 +0200 Subject: [PATCH 07/19] Android: Add edit button for cheats --- .../features/cheats/model/ARCheat.java | 5 + .../features/cheats/model/AbstractCheat.java | 16 +++ .../features/cheats/model/Cheat.java | 10 ++ .../cheats/model/CheatsViewModel.java | 14 +++ .../features/cheats/model/GeckoCheat.java | 5 + .../features/cheats/model/PatchCheat.java | 5 + .../cheats/ui/CheatDetailsFragment.java | 64 ++++++++++- .../res/layout/fragment_cheat_details.xml | 100 ++++++++++++++---- .../app/src/main/res/values/strings.xml | 2 + Source/Android/jni/CMakeLists.txt | 1 + Source/Android/jni/Cheats/ARCheat.cpp | 17 +++ Source/Android/jni/Cheats/Cheats.h | 7 ++ Source/Android/jni/Cheats/GeckoCheat.cpp | 17 +++ Source/Android/jni/Cheats/PatchCheat.cpp | 17 +++ 14 files changed, 253 insertions(+), 27 deletions(-) create mode 100644 Source/Android/jni/Cheats/Cheats.h 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 ebb4ee9416..bed39c2dbd 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 @@ -22,8 +22,13 @@ public class ARCheat extends AbstractCheat @NonNull public native String getName(); + public native boolean getUserDefined(); + public native boolean getEnabled(); + @Override + protected native int trySetImpl(@NonNull String name); + @Override protected native void setEnabledImpl(boolean enabled); 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 index 8f6121808a..60391895c7 100644 --- 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 @@ -2,12 +2,26 @@ package org.dolphinemu.dolphinemu.features.cheats.model; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; public abstract class AbstractCheat implements Cheat { private Runnable mChangedCallback = null; + public int trySet(@NonNull String name) + { + if (name.isEmpty()) + return TRY_SET_FAIL_NO_NAME; + + int result = trySetImpl(name); + + if (result == TRY_SET_SUCCESS) + onChanged(); + + return result; + } + public void setEnabled(boolean enabled) { setEnabledImpl(enabled); @@ -25,5 +39,7 @@ public abstract class AbstractCheat implements Cheat mChangedCallback.run(); } + protected abstract int trySetImpl(@NonNull String name); + 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 97bd57d885..baf8ebb5be 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 @@ -3,13 +3,23 @@ package org.dolphinemu.dolphinemu.features.cheats.model; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; public interface Cheat { + int TRY_SET_FAIL_NO_NAME = -1; + int TRY_SET_SUCCESS = 0; + @NonNull String getName(); + int trySet(@NonNull String name); + + boolean getUserDefined(); + boolean getEnabled(); void setEnabled(boolean enabled); + + void setChangedCallback(@Nullable Runnable callback); } 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 aac6c02ed0..e4e5298104 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 @@ -11,6 +11,7 @@ public class CheatsViewModel extends ViewModel private boolean mLoaded = false; private final MutableLiveData mSelectedCheat = new MutableLiveData<>(null); + private final MutableLiveData mIsEditing = new MutableLiveData<>(false); private final MutableLiveData mOpenDetailsViewEvent = new MutableLiveData<>(false); @@ -75,9 +76,22 @@ public class CheatsViewModel extends ViewModel public void setSelectedCheat(Cheat cheat) { + if (mIsEditing.getValue()) + setIsEditing(false); + mSelectedCheat.setValue(cheat); } + public LiveData getIsEditing() + { + return mIsEditing; + } + + public void setIsEditing(boolean isEditing) + { + mIsEditing.setValue(isEditing); + } + public LiveData getOpenDetailsViewEvent() { return mOpenDetailsViewEvent; 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 f6ea493ac3..06815d1fe9 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 @@ -22,8 +22,13 @@ public class GeckoCheat extends AbstractCheat @NonNull public native String getName(); + public native boolean getUserDefined(); + public native boolean getEnabled(); + @Override + protected native int trySetImpl(@NonNull String name); + @Override protected native void setEnabledImpl(boolean enabled); 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 411df3cdc0..b4e0ee3e53 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 @@ -22,8 +22,13 @@ public class PatchCheat extends AbstractCheat @NonNull public native String getName(); + public native boolean getUserDefined(); + public native boolean getEnabled(); + @Override + protected native int trySetImpl(@NonNull String name); + @Override protected native void setEnabledImpl(boolean enabled); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java index 90356b63eb..0b7ce49785 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java @@ -6,6 +6,7 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import android.widget.EditText; import androidx.annotation.NonNull; @@ -22,6 +23,9 @@ public class CheatDetailsFragment extends Fragment { private View mRoot; private EditText mEditName; + private Button mButtonEdit; + private Button mButtonCancel; + private Button mButtonOk; private CheatsViewModel mViewModel; private Cheat mCheat; @@ -39,24 +43,74 @@ public class CheatDetailsFragment extends Fragment { mRoot = view.findViewById(R.id.root); mEditName = view.findViewById(R.id.edit_name); + mButtonEdit = view.findViewById(R.id.button_edit); + mButtonCancel = view.findViewById(R.id.button_cancel); + mButtonOk = view.findViewById(R.id.button_ok); CheatsActivity activity = (CheatsActivity) requireActivity(); mViewModel = new ViewModelProvider(activity).get(CheatsViewModel.class); - LiveData selectedCheat = mViewModel.getSelectedCheat(); - selectedCheat.observe(getViewLifecycleOwner(), this::populateFields); - populateFields(selectedCheat.getValue()); + mViewModel.getSelectedCheat().observe(getViewLifecycleOwner(), this::onSelectedCheatUpdated); + mViewModel.getIsEditing().observe(getViewLifecycleOwner(), this::onIsEditingUpdated); + + mButtonEdit.setOnClickListener((v) -> mViewModel.setIsEditing(true)); + mButtonCancel.setOnClickListener((v) -> + { + mViewModel.setIsEditing(false); + onSelectedCheatUpdated(mCheat); + }); + mButtonOk.setOnClickListener(this::onOkClicked); } - private void populateFields(@Nullable Cheat cheat) + private void clearEditErrors() { + mEditName.setError(null); + } + + private void onOkClicked(View view) + { + clearEditErrors(); + + int result = mCheat.trySet(mEditName.getText().toString()); + + switch (result) + { + case Cheat.TRY_SET_SUCCESS: + mViewModel.setIsEditing(false); + break; + case Cheat.TRY_SET_FAIL_NO_NAME: + mEditName.setError(getText(R.string.cheats_error_no_name)); + break; + } + } + + private void onSelectedCheatUpdated(@Nullable Cheat cheat) + { + clearEditErrors(); + mRoot.setVisibility(cheat == null ? View.GONE : View.VISIBLE); - if (cheat != null && cheat != mCheat) + boolean userDefined = cheat != null && cheat.getUserDefined(); + mButtonEdit.setEnabled(userDefined); + + // If the fragment was recreated while editing a cheat, it's vital that we + // don't repopulate the fields, otherwise the user's changes will be lost + boolean isEditing = mViewModel.getIsEditing().getValue(); + + if (!isEditing && cheat != null) { mEditName.setText(cheat.getName()); } mCheat = cheat; } + + private void onIsEditingUpdated(boolean isEditing) + { + mEditName.setEnabled(isEditing); + + mButtonEdit.setVisibility(isEditing ? View.GONE : View.VISIBLE); + mButtonCancel.setVisibility(isEditing ? View.VISIBLE : View.GONE); + mButtonOk.setVisibility(isEditing ? View.VISIBLE : View.GONE); + } } diff --git a/Source/Android/app/src/main/res/layout/fragment_cheat_details.xml b/Source/Android/app/src/main/res/layout/fragment_cheat_details.xml index 14c7dfeace..4afb27f344 100644 --- a/Source/Android/app/src/main/res/layout/fragment_cheat_details.xml +++ b/Source/Android/app/src/main/res/layout/fragment_cheat_details.xml @@ -5,35 +5,91 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="match_parent"> - + app:layout_constraintBottom_toTopOf="@id/barrier"> - + + + + + + + + + + + +