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