mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-01-31 11:55:37 +00:00
Android: Implement cheats
This commit is contained in:
parent
4b8ae472ab
commit
de33c7fa2b
|
@ -4,6 +4,7 @@
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/string.h"
|
#include "common/string.h"
|
||||||
#include "common/timestamp.h"
|
#include "common/timestamp.h"
|
||||||
|
#include "core/cheats.h"
|
||||||
#include "core/controller.h"
|
#include "core/controller.h"
|
||||||
#include "core/gpu.h"
|
#include "core/gpu.h"
|
||||||
#include "core/host_display.h"
|
#include "core/host_display.h"
|
||||||
|
@ -28,6 +29,8 @@ static jmethodID s_EmulationActivity_method_reportMessage;
|
||||||
static jmethodID s_EmulationActivity_method_onEmulationStarted;
|
static jmethodID s_EmulationActivity_method_onEmulationStarted;
|
||||||
static jmethodID s_EmulationActivity_method_onEmulationStopped;
|
static jmethodID s_EmulationActivity_method_onEmulationStopped;
|
||||||
static jmethodID s_EmulationActivity_method_onGameTitleChanged;
|
static jmethodID s_EmulationActivity_method_onGameTitleChanged;
|
||||||
|
static jclass s_CheatCode_class;
|
||||||
|
static jmethodID s_CheatCode_constructor;
|
||||||
|
|
||||||
namespace AndroidHelpers {
|
namespace AndroidHelpers {
|
||||||
// helper for retrieving the current per-thread jni environment
|
// helper for retrieving the current per-thread jni environment
|
||||||
|
@ -186,9 +189,7 @@ bool AndroidHostInterface::StartEmulationThread(jobject emulation_activity, ANat
|
||||||
void AndroidHostInterface::PauseEmulationThread(bool paused)
|
void AndroidHostInterface::PauseEmulationThread(bool paused)
|
||||||
{
|
{
|
||||||
Assert(IsEmulationThreadRunning());
|
Assert(IsEmulationThreadRunning());
|
||||||
RunOnEmulationThread([this, paused]() {
|
RunOnEmulationThread([this, paused]() { PauseSystem(paused); });
|
||||||
PauseSystem(paused);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidHostInterface::StopEmulationThread()
|
void AndroidHostInterface::StopEmulationThread()
|
||||||
|
@ -291,8 +292,10 @@ void AndroidHostInterface::EmulationThreadLoop()
|
||||||
// run any events
|
// run any events
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(m_mutex);
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||||||
for (;;) {
|
for (;;)
|
||||||
while (!m_callback_queue.empty()) {
|
{
|
||||||
|
while (!m_callback_queue.empty())
|
||||||
|
{
|
||||||
auto callback = std::move(m_callback_queue.front());
|
auto callback = std::move(m_callback_queue.front());
|
||||||
m_callback_queue.pop_front();
|
m_callback_queue.pop_front();
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
@ -303,10 +306,13 @@ void AndroidHostInterface::EmulationThreadLoop()
|
||||||
if (m_emulation_thread_stop_request.load())
|
if (m_emulation_thread_stop_request.load())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (System::IsPaused()) {
|
if (System::IsPaused())
|
||||||
|
{
|
||||||
// paused, wait for us to resume
|
// paused, wait for us to resume
|
||||||
m_sleep_cv.wait(lock);
|
m_sleep_cv.wait(lock);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// done with callbacks, run the frame
|
// done with callbacks, run the frame
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -511,21 +517,19 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||||
Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV);
|
Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV);
|
||||||
s_jvm = vm;
|
s_jvm = vm;
|
||||||
|
|
||||||
|
// Create global reference so it doesn't get cleaned up.
|
||||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||||
if ((s_AndroidHostInterface_class = env->FindClass("com/github/stenzek/duckstation/AndroidHostInterface")) == nullptr)
|
if ((s_AndroidHostInterface_class = env->FindClass("com/github/stenzek/duckstation/AndroidHostInterface")) ==
|
||||||
|
nullptr ||
|
||||||
|
(s_AndroidHostInterface_class = static_cast<jclass>(env->NewGlobalRef(s_AndroidHostInterface_class))) ==
|
||||||
|
nullptr ||
|
||||||
|
(s_CheatCode_class = env->FindClass("com/github/stenzek/duckstation/CheatCode")) == nullptr ||
|
||||||
|
(s_CheatCode_class = static_cast<jclass>(env->NewGlobalRef(s_CheatCode_class))) == nullptr)
|
||||||
{
|
{
|
||||||
Log_ErrorPrint("AndroidHostInterface class lookup failed");
|
Log_ErrorPrint("AndroidHostInterface class lookup failed");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create global reference so it doesn't get cleaned up.
|
|
||||||
s_AndroidHostInterface_class = static_cast<jclass>(env->NewGlobalRef(s_AndroidHostInterface_class));
|
|
||||||
if (!s_AndroidHostInterface_class)
|
|
||||||
{
|
|
||||||
Log_ErrorPrint("Failed to get reference to AndroidHostInterface");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass emulation_activity_class;
|
jclass emulation_activity_class;
|
||||||
if ((s_AndroidHostInterface_constructor =
|
if ((s_AndroidHostInterface_constructor =
|
||||||
env->GetMethodID(s_AndroidHostInterface_class, "<init>", "(Landroid/content/Context;)V")) == nullptr ||
|
env->GetMethodID(s_AndroidHostInterface_class, "<init>", "(Landroid/content/Context;)V")) == nullptr ||
|
||||||
|
@ -545,7 +549,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||||
(s_EmulationActivity_method_onEmulationStopped =
|
(s_EmulationActivity_method_onEmulationStopped =
|
||||||
env->GetMethodID(emulation_activity_class, "onEmulationStopped", "()V")) == nullptr ||
|
env->GetMethodID(emulation_activity_class, "onEmulationStopped", "()V")) == nullptr ||
|
||||||
(s_EmulationActivity_method_onGameTitleChanged =
|
(s_EmulationActivity_method_onGameTitleChanged =
|
||||||
env->GetMethodID(emulation_activity_class, "onGameTitleChanged", "(Ljava/lang/String;)V")) == nullptr)
|
env->GetMethodID(emulation_activity_class, "onGameTitleChanged", "(Ljava/lang/String;)V")) == nullptr ||
|
||||||
|
(s_CheatCode_constructor = env->GetMethodID(s_CheatCode_class, "<init>", "(ILjava/lang/String;Z)V")) == nullptr)
|
||||||
{
|
{
|
||||||
Log_ErrorPrint("AndroidHostInterface lookups failed");
|
Log_ErrorPrint("AndroidHostInterface lookups failed");
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -770,7 +775,8 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_saveResumeState, jobject obj,
|
||||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setDisplayAlignment, jobject obj, jint alignment)
|
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setDisplayAlignment, jobject obj, jint alignment)
|
||||||
{
|
{
|
||||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||||
hi->RunOnEmulationThread([hi, alignment]() { hi->GetDisplay()->SetDisplayAlignment(static_cast<HostDisplay::Alignment>(alignment)); });
|
hi->RunOnEmulationThread(
|
||||||
|
[hi, alignment]() { hi->GetDisplay()->SetDisplayAlignment(static_cast<HostDisplay::Alignment>(alignment)); });
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_JNI_ARGS_METHOD(bool, AndroidHostInterface_hasSurface, jobject obj)
|
DEFINE_JNI_ARGS_METHOD(bool, AndroidHostInterface_hasSurface, jobject obj)
|
||||||
|
@ -795,5 +801,32 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_pauseEmulationThread, jobject
|
||||||
hi->PauseEmulationThread(paused);
|
hi->PauseEmulationThread(paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_getCheatList, jobject obj)
|
||||||
|
{
|
||||||
|
if (!System::IsValid() || !System::HasCheatList())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
CheatList* cl = System::GetCheatList();
|
||||||
|
const u32 count = cl->GetCodeCount();
|
||||||
|
|
||||||
|
jobjectArray arr = env->NewObjectArray(count, s_CheatCode_class, nullptr);
|
||||||
|
for (u32 i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
const CheatCode& cc = cl->GetCode(i);
|
||||||
|
|
||||||
|
jobject java_cc = env->NewObject(s_CheatCode_class, s_CheatCode_constructor, static_cast<jint>(i),
|
||||||
|
env->NewStringUTF(cc.description.c_str()), cc.enabled);
|
||||||
|
env->SetObjectArrayElement(arr, i, java_cc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setCheatEnabled, jobject obj, jint index, jboolean enabled)
|
||||||
|
{
|
||||||
|
if (!System::IsValid() || !System::HasCheatList())
|
||||||
|
return;
|
||||||
|
|
||||||
|
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||||
|
hi->RunOnEmulationThread([index, enabled, hi]() { hi->SetCheatCodeState(static_cast<u32>(index), enabled, true); });
|
||||||
|
}
|
||||||
|
|
|
@ -141,7 +141,8 @@ void AndroidSettingsInterface::DeleteValue(const char* section, const char* key)
|
||||||
std::vector<std::string> AndroidSettingsInterface::GetStringList(const char* section, const char* key)
|
std::vector<std::string> AndroidSettingsInterface::GetStringList(const char* section, const char* key)
|
||||||
{
|
{
|
||||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||||
jobject values_set = env->CallObjectMethod(m_java_shared_preferences, m_get_string_set, env->NewStringUTF(GetSettingKey(section, key)), nullptr);
|
jobject values_set = env->CallObjectMethod(m_java_shared_preferences, m_get_string_set,
|
||||||
|
env->NewStringUTF(GetSettingKey(section, key)), nullptr);
|
||||||
if (!values_set)
|
if (!values_set)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,9 @@ public class AndroidHostInterface {
|
||||||
|
|
||||||
public native void setDisplayAlignment(int alignment);
|
public native void setDisplayAlignment(int alignment);
|
||||||
|
|
||||||
|
public native CheatCode[] getCheatList();
|
||||||
|
public native void setCheatEnabled(int index, boolean enabled);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
System.loadLibrary("duckstation-native");
|
System.loadLibrary("duckstation-native");
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
public class CheatCode {
|
||||||
|
private int mIndex;
|
||||||
|
private String mName;
|
||||||
|
private boolean mEnabled;
|
||||||
|
|
||||||
|
public CheatCode(int index, String name, boolean enabled) {
|
||||||
|
mIndex = index;
|
||||||
|
mName = name;
|
||||||
|
mEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndex() { return mIndex; }
|
||||||
|
public String getName() { return mName; }
|
||||||
|
public boolean isEnabled() { return mEnabled; }
|
||||||
|
}
|
|
@ -280,18 +280,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 2: // Reset
|
case 2: // Toggle Speed Limiter
|
||||||
{
|
|
||||||
AndroidHostInterface.getInstance().resetSystem();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 3: // Change Disc
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 4: // Toggle Speed Limiter
|
|
||||||
{
|
{
|
||||||
boolean newSetting = !getBooleanSetting("Main/SpeedLimiterEnabled", true);
|
boolean newSetting = !getBooleanSetting("Main/SpeedLimiterEnabled", true);
|
||||||
setBooleanSetting("Main/SpeedLimiterEnabled", newSetting);
|
setBooleanSetting("Main/SpeedLimiterEnabled", newSetting);
|
||||||
|
@ -299,13 +288,63 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 5: // Toggle Touchscreen Controller
|
case 3: // More Options
|
||||||
|
{
|
||||||
|
showMoreMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4: // Quit
|
||||||
|
{
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss(DialogInterface dialogInterface) {
|
||||||
|
enableFullscreenImmersive();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showMoreMenu() {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
if (mGameTitle != null && !mGameTitle.isEmpty())
|
||||||
|
builder.setTitle(mGameTitle);
|
||||||
|
|
||||||
|
builder.setItems(R.array.emulation_more_menu, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 0: // Reset
|
||||||
|
{
|
||||||
|
AndroidHostInterface.getInstance().resetSystem();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: // Cheats
|
||||||
|
{
|
||||||
|
showCheatsMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: // Change Disc
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3: // Toggle Touchscreen Controller
|
||||||
{
|
{
|
||||||
setTouchscreenControllerVisibility(!mTouchscreenControllerVisible);
|
setTouchscreenControllerVisibility(!mTouchscreenControllerVisible);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 6: // Settings
|
case 4: // Settings
|
||||||
{
|
{
|
||||||
Intent intent = new Intent(EmulationActivity.this, SettingsActivity.class);
|
Intent intent = new Intent(EmulationActivity.this, SettingsActivity.class);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
@ -313,7 +352,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 7: // Quit
|
case 5: // Quit
|
||||||
{
|
{
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
|
@ -330,6 +369,36 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showCheatsMenu() {
|
||||||
|
final CheatCode[] cheats = AndroidHostInterface.getInstance().getCheatList();
|
||||||
|
if (cheats == null) {
|
||||||
|
Toast.makeText(this, "No cheats are loaded.", Toast.LENGTH_LONG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
CharSequence[] items = new CharSequence[cheats.length];
|
||||||
|
for (int i = 0; i < cheats.length; i++) {
|
||||||
|
final CheatCode cc = cheats[i];
|
||||||
|
items[i] = String.format("%s %s", cc.isEnabled() ? "(ON)" : "(OFF)", cc.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setItems(items, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
AndroidHostInterface.getInstance().setCheatEnabled(i, !cheats[i].isEnabled());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss(DialogInterface dialogInterface) {
|
||||||
|
enableFullscreenImmersive();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Touchscreen controller overlay
|
* Touchscreen controller overlay
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -128,9 +128,14 @@
|
||||||
<string-array name="emulation_menu">
|
<string-array name="emulation_menu">
|
||||||
<item>Quick Load</item>
|
<item>Quick Load</item>
|
||||||
<item>Quick Save</item>
|
<item>Quick Save</item>
|
||||||
<item>Reset</item>
|
|
||||||
<item>Change Disc</item>
|
|
||||||
<item>Toggle Speed Limiter</item>
|
<item>Toggle Speed Limiter</item>
|
||||||
|
<item>More Options</item>
|
||||||
|
<item>Quit</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="emulation_more_menu">
|
||||||
|
<item>Reset</item>
|
||||||
|
<item>Cheats</item>
|
||||||
|
<item>Change Disc</item>
|
||||||
<item>Toggle Touch Controller</item>
|
<item>Toggle Touch Controller</item>
|
||||||
<item>Settings</item>
|
<item>Settings</item>
|
||||||
<item>Quit</item>
|
<item>Quit</item>
|
||||||
|
|
|
@ -30,6 +30,12 @@
|
||||||
app:defaultValue="true"
|
app:defaultValue="true"
|
||||||
app:summary="Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time."
|
app:summary="Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time."
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="Main/AutoLoadCheats"
|
||||||
|
app:title="Load Cheats"
|
||||||
|
app:defaultValue="false"
|
||||||
|
app:summary="Loads cheats from cheats/<game name>.cht in PCSXR format. Cheats can be toggled while ingame."
|
||||||
|
app:iconSpaceReserved="false" />
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
app:key="Display/VSync"
|
app:key="Display/VSync"
|
||||||
app:title="Video Sync"
|
app:title="Video Sync"
|
||||||
|
|
Loading…
Reference in a new issue