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 bed39c2dbd..432da43ce1 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,12 +22,15 @@ public class ARCheat extends AbstractCheat @NonNull public native String getName(); + @NonNull + public native String getCode(); + public native boolean getUserDefined(); public native boolean getEnabled(); @Override - protected native int trySetImpl(@NonNull String name); + protected native int trySetImpl(@NonNull String name, @NonNull String code); @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 60391895c7..cca4b041d9 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 @@ -9,12 +9,12 @@ public abstract class AbstractCheat implements Cheat { private Runnable mChangedCallback = null; - public int trySet(@NonNull String name) + public int trySet(@NonNull String name, @NonNull String code) { if (name.isEmpty()) return TRY_SET_FAIL_NO_NAME; - int result = trySetImpl(name); + int result = trySetImpl(name, code); if (result == TRY_SET_SUCCESS) onChanged(); @@ -39,7 +39,7 @@ public abstract class AbstractCheat implements Cheat mChangedCallback.run(); } - protected abstract int trySetImpl(@NonNull String name); + protected abstract int trySetImpl(@NonNull String name, @NonNull String code); 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 baf8ebb5be..505bd5c026 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 @@ -7,13 +7,19 @@ import androidx.annotation.Nullable; public interface Cheat { + int TRY_SET_FAIL_CODE_MIXED_ENCRYPTION = -3; + int TRY_SET_FAIL_NO_CODE_LINES = -2; int TRY_SET_FAIL_NO_NAME = -1; int TRY_SET_SUCCESS = 0; + // Result codes greater than 0 represent an error on the corresponding code line (one-indexed) @NonNull String getName(); - int trySet(@NonNull String name); + @NonNull + String getCode(); + + int trySet(@NonNull String name, @NonNull String code); boolean getUserDefined(); 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 06815d1fe9..6ef6a9431e 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,12 +22,15 @@ public class GeckoCheat extends AbstractCheat @NonNull public native String getName(); + @NonNull + public native String getCode(); + public native boolean getUserDefined(); public native boolean getEnabled(); @Override - protected native int trySetImpl(@NonNull String name); + protected native int trySetImpl(@NonNull String name, @NonNull String code); @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 b4e0ee3e53..8514ae00cc 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,12 +22,15 @@ public class PatchCheat extends AbstractCheat @NonNull public native String getName(); + @NonNull + public native String getCode(); + public native boolean getUserDefined(); public native boolean getEnabled(); @Override - protected native int trySetImpl(@NonNull String name); + protected native int trySetImpl(@NonNull String name, @NonNull String code); @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 3a61b2e4b7..ffe86fc702 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 @@ -23,6 +23,7 @@ public class CheatDetailsFragment extends Fragment { private View mRoot; private EditText mEditName; + private EditText mEditCode; private Button mButtonEdit; private Button mButtonCancel; private Button mButtonOk; @@ -43,6 +44,7 @@ public class CheatDetailsFragment extends Fragment { mRoot = view.findViewById(R.id.root); mEditName = view.findViewById(R.id.edit_name); + mEditCode = view.findViewById(R.id.edit_code); mButtonEdit = view.findViewById(R.id.button_edit); mButtonCancel = view.findViewById(R.id.button_cancel); mButtonOk = view.findViewById(R.id.button_ok); @@ -65,13 +67,14 @@ public class CheatDetailsFragment extends Fragment private void clearEditErrors() { mEditName.setError(null); + mEditCode.setError(null); } private void onOkClicked(View view) { clearEditErrors(); - int result = mCheat.trySet(mEditName.getText().toString()); + int result = mCheat.trySet(mEditName.getText().toString(), mEditCode.getText().toString()); switch (result) { @@ -80,7 +83,16 @@ public class CheatDetailsFragment extends Fragment mViewModel.setIsEditing(false); break; case Cheat.TRY_SET_FAIL_NO_NAME: - mEditName.setError(getText(R.string.cheats_error_no_name)); + mEditName.setError(getString(R.string.cheats_error_no_name)); + break; + case Cheat.TRY_SET_FAIL_NO_CODE_LINES: + mEditCode.setError(getString(R.string.cheats_error_no_code_lines)); + break; + case Cheat.TRY_SET_FAIL_CODE_MIXED_ENCRYPTION: + mEditCode.setError(getString(R.string.cheats_error_mixed_encryption)); + break; + default: + mEditCode.setError(getString(R.string.cheats_error_on_line, result)); break; } } @@ -101,6 +113,7 @@ public class CheatDetailsFragment extends Fragment if (!isEditing && cheat != null) { mEditName.setText(cheat.getName()); + mEditCode.setText(cheat.getCode()); } mCheat = cheat; @@ -109,6 +122,7 @@ public class CheatDetailsFragment extends Fragment private void onIsEditingUpdated(boolean isEditing) { mEditName.setEnabled(isEditing); + mEditCode.setEnabled(isEditing); mButtonEdit.setVisibility(isEditing ? View.GONE : View.VISIBLE); mButtonCancel.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 4afb27f344..69cb87c82e 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 @@ -45,9 +45,39 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/label_name" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/label_code" tools:text="Hyrule Field Speed Hack" /> + + + + diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 669bec3f1e..925070e5e4 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -390,8 +390,12 @@ Cheats Cheats: %1$s Name + Code Edit Name can\'t be empty + Code can\'t be empty + Error on line %1$d + Lines must either be all encrypted or all decrypted Format diff --git a/Source/Android/jni/Cheats/ARCheat.cpp b/Source/Android/jni/Cheats/ARCheat.cpp index 866015b9a8..7c418ec579 100644 --- a/Source/Android/jni/Cheats/ARCheat.cpp +++ b/Source/Android/jni/Cheats/ARCheat.cpp @@ -8,6 +8,8 @@ #include "Common/FileUtil.h" #include "Common/IniFile.h" +#include "Common/StringUtil.h" +#include "Core/ARDecrypt.h" #include "Core/ActionReplay.h" #include "Core/ConfigManager.h" #include "jni/AndroidCommon/AndroidCommon.h" @@ -40,6 +42,24 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getName(JNIEnv* env return ToJString(env, GetPointer(env, obj)->name); } +JNIEXPORT jstring JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getCode(JNIEnv* env, jobject obj) +{ + ActionReplay::ARCode* code = GetPointer(env, obj); + + std::string code_string; + + for (size_t i = 0; i < code->ops.size(); ++i) + { + if (i != 0) + code_string += '\n'; + + code_string += ActionReplay::SerializeLine(code->ops[i]); + } + + return ToJString(env, code_string); +} + JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getUserDefined(JNIEnv* env, jobject obj) @@ -54,12 +74,47 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getEnabled(JNIEnv* } JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_trySetImpl( - JNIEnv* env, jobject obj, jstring name) + JNIEnv* env, jobject obj, jstring name, jstring code_string) { ActionReplay::ARCode* code = GetPointer(env, obj); - code->name = GetJString(env, name); - return TRY_SET_SUCCESS; + std::vector entries; + std::vector encrypted_lines; + + std::vector lines = SplitString(GetJString(env, code_string), '\n'); + + for (size_t i = 0; i < lines.size(); i++) + { + const std::string& line = lines[i]; + + if (line.empty()) + continue; + + auto parse_result = ActionReplay::DeserializeLine(line); + + if (std::holds_alternative(parse_result)) + entries.emplace_back(std::get(std::move(parse_result))); + else if (std::holds_alternative(parse_result)) + encrypted_lines.emplace_back(std::get(std::move(parse_result))); + else + return i + 1; // Parse error on line i + } + + if (!encrypted_lines.empty()) + { + if (!entries.empty()) + return Cheats::TRY_SET_FAIL_CODE_MIXED_ENCRYPTION; + + ActionReplay::DecryptARCode(encrypted_lines, &entries); + } + + if (entries.empty()) + return Cheats::TRY_SET_FAIL_NO_CODE_LINES; + + code->name = GetJString(env, name); + code->ops = std::move(entries); + + return Cheats::TRY_SET_SUCCESS; } JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_setEnabledImpl( diff --git a/Source/Android/jni/Cheats/Cheats.h b/Source/Android/jni/Cheats/Cheats.h index a79722c817..e554a3ed24 100644 --- a/Source/Android/jni/Cheats/Cheats.h +++ b/Source/Android/jni/Cheats/Cheats.h @@ -3,5 +3,11 @@ #pragma once +namespace Cheats +{ +constexpr int TRY_SET_FAIL_CODE_MIXED_ENCRYPTION = -3; +constexpr int TRY_SET_FAIL_NO_CODE_LINES = -2; constexpr int TRY_SET_FAIL_NO_NAME = -1; constexpr int TRY_SET_SUCCESS = 0; +// Result codes greater than 0 represent an error on the corresponding code line (one-indexed) +} // namespace Cheats diff --git a/Source/Android/jni/Cheats/GeckoCheat.cpp b/Source/Android/jni/Cheats/GeckoCheat.cpp index b3aca0074f..a5d2ce7c65 100644 --- a/Source/Android/jni/Cheats/GeckoCheat.cpp +++ b/Source/Android/jni/Cheats/GeckoCheat.cpp @@ -41,6 +41,24 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getName(JNIEnv* return ToJString(env, GetPointer(env, obj)->name); } +JNIEXPORT jstring JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getCode(JNIEnv* env, jobject obj) +{ + Gecko::GeckoCode* code = GetPointer(env, obj); + + std::string code_string; + + for (size_t i = 0; i < code->codes.size(); ++i) + { + if (i != 0) + code_string += '\n'; + + code_string += code->codes[i].original_line; + } + + return ToJString(env, code_string); +} + JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getUserDefined(JNIEnv* env, jobject obj) @@ -55,12 +73,34 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getEnabled(JNIEn } JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_trySetImpl( - JNIEnv* env, jobject obj, jstring name) + JNIEnv* env, jobject obj, jstring name, jstring code_string) { Gecko::GeckoCode* code = GetPointer(env, obj); - code->name = GetJString(env, name); - return TRY_SET_SUCCESS; + std::vector entries; + + std::vector lines = SplitString(GetJString(env, code_string), '\n'); + + for (size_t i = 0; i < lines.size(); i++) + { + const std::string& line = lines[i]; + + if (line.empty()) + continue; + + if (std::optional c = Gecko::DeserializeLine(line)) + entries.emplace_back(*std::move(c)); + else + return i + 1; // Parse error on line i + } + + if (entries.empty()) + return Cheats::TRY_SET_FAIL_NO_CODE_LINES; + + code->name = GetJString(env, name); + code->codes = std::move(entries); + + return Cheats::TRY_SET_SUCCESS; } JNIEXPORT void JNICALL diff --git a/Source/Android/jni/Cheats/PatchCheat.cpp b/Source/Android/jni/Cheats/PatchCheat.cpp index 84cbea745b..c41f0a599d 100644 --- a/Source/Android/jni/Cheats/PatchCheat.cpp +++ b/Source/Android/jni/Cheats/PatchCheat.cpp @@ -40,6 +40,24 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getName(JNIEnv* return ToJString(env, GetPointer(env, obj)->name); } +JNIEXPORT jstring JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getCode(JNIEnv* env, jobject obj) +{ + PatchEngine::Patch* patch = GetPointer(env, obj); + + std::string code_string; + + for (size_t i = 0; i < patch->entries.size(); ++i) + { + if (i != 0) + code_string += '\n'; + + code_string += PatchEngine::SerializeLine(patch->entries[i]); + } + + return ToJString(env, code_string); +} + JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getUserDefined(JNIEnv* env, jobject obj) @@ -54,12 +72,34 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getEnabled(JNIEn } JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_trySetImpl( - JNIEnv* env, jobject obj, jstring name) + JNIEnv* env, jobject obj, jstring name, jstring code_string) { PatchEngine::Patch* patch = GetPointer(env, obj); - patch->name = GetJString(env, name); - return TRY_SET_SUCCESS; + std::vector entries; + + std::vector lines = SplitString(GetJString(env, code_string), '\n'); + + for (size_t i = 0; i < lines.size(); i++) + { + const std::string& line = lines[i]; + + if (line.empty()) + continue; + + if (std::optional entry = PatchEngine::DeserializeLine(line)) + entries.emplace_back(*std::move(entry)); + else + return i + 1; // Parse error on line i + } + + if (entries.empty()) + return Cheats::TRY_SET_FAIL_NO_CODE_LINES; + + patch->name = GetJString(env, name); + patch->entries = std::move(entries); + + return Cheats::TRY_SET_SUCCESS; } JNIEXPORT void JNICALL