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