Android: Add patch code (cheat) import
This commit is contained in:
parent
fcca1fa3f7
commit
8014e66c6f
|
@ -38,8 +38,8 @@ static jmethodID s_EmulationActivity_method_reportMessage;
|
|||
static jmethodID s_EmulationActivity_method_onEmulationStarted;
|
||||
static jmethodID s_EmulationActivity_method_onEmulationStopped;
|
||||
static jmethodID s_EmulationActivity_method_onGameTitleChanged;
|
||||
static jclass s_CheatCode_class;
|
||||
static jmethodID s_CheatCode_constructor;
|
||||
static jclass s_PatchCode_class;
|
||||
static jmethodID s_PatchCode_constructor;
|
||||
|
||||
namespace AndroidHelpers {
|
||||
// helper for retrieving the current per-thread jni environment
|
||||
|
@ -178,7 +178,8 @@ void AndroidHostInterface::LoadAndConvertSettings()
|
|||
g_settings.gpu_per_sample_shading = StringUtil::EndsWith(msaa_str, "-ssaa");
|
||||
|
||||
// turn percentage into fraction for overclock
|
||||
const u32 overclock_percent = static_cast<u32>(std::max(m_settings_interface.GetIntValue("CPU", "Overclock", 100), 1));
|
||||
const u32 overclock_percent =
|
||||
static_cast<u32>(std::max(m_settings_interface.GetIntValue("CPU", "Overclock", 100), 1));
|
||||
Settings::CPUOverclockPercentToFraction(overclock_percent, &g_settings.cpu_overclock_numerator,
|
||||
&g_settings.cpu_overclock_denominator);
|
||||
g_settings.cpu_overclock_enable = (overclock_percent != 100);
|
||||
|
@ -329,8 +330,7 @@ void AndroidHostInterface::EmulationThreadLoop()
|
|||
lock.unlock();
|
||||
callback();
|
||||
lock.lock();
|
||||
}
|
||||
while (!m_callback_queue.empty());
|
||||
} while (!m_callback_queue.empty());
|
||||
m_callbacks_outstanding.store(false);
|
||||
}
|
||||
|
||||
|
@ -583,6 +583,34 @@ void AndroidHostInterface::ApplySettings(bool display_osd_messages)
|
|||
CheckForSettingsChanges(old_settings);
|
||||
}
|
||||
|
||||
bool AndroidHostInterface::ImportPatchCodesFromString(const std::string& str)
|
||||
{
|
||||
CheatList* cl = new CheatList();
|
||||
if (!cl->LoadFromString(str, CheatList::Format::Autodetect) || cl->GetCodeCount() == 0)
|
||||
return false;
|
||||
|
||||
RunOnEmulationThread([this, cl]() {
|
||||
u32 imported_count;
|
||||
if (!System::HasCheatList())
|
||||
{
|
||||
imported_count = cl->GetCodeCount();
|
||||
System::SetCheatList(std::unique_ptr<CheatList>(cl));
|
||||
}
|
||||
else
|
||||
{
|
||||
const u32 old_count = System::GetCheatList()->GetCodeCount();
|
||||
System::GetCheatList()->MergeList(*cl);
|
||||
imported_count = System::GetCheatList()->GetCodeCount() - old_count;
|
||||
delete cl;
|
||||
}
|
||||
|
||||
AddFormattedOSDMessage(20.0f, "Imported %u patch codes.", imported_count);
|
||||
CommonHostInterface::SaveCheatList();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||
{
|
||||
Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV);
|
||||
|
@ -594,8 +622,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|||
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)
|
||||
(s_PatchCode_class = env->FindClass("com/github/stenzek/duckstation/PatchCode")) == nullptr ||
|
||||
(s_PatchCode_class = static_cast<jclass>(env->NewGlobalRef(s_PatchCode_class))) == nullptr)
|
||||
{
|
||||
Log_ErrorPrint("AndroidHostInterface class lookup failed");
|
||||
return -1;
|
||||
|
@ -621,7 +649,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|||
env->GetMethodID(emulation_activity_class, "onEmulationStopped", "()V")) == nullptr ||
|
||||
(s_EmulationActivity_method_onGameTitleChanged =
|
||||
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)
|
||||
(s_PatchCode_constructor = env->GetMethodID(s_PatchCode_class, "<init>", "(ILjava/lang/String;Z)V")) == nullptr)
|
||||
{
|
||||
Log_ErrorPrint("AndroidHostInterface lookups failed");
|
||||
return -1;
|
||||
|
@ -878,20 +906,30 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_pauseEmulationThread, jobject
|
|||
hi->PauseEmulationThread(paused);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_getCheatList, jobject obj)
|
||||
DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_getPatchCodeList, jobject obj)
|
||||
{
|
||||
if (!System::IsValid() || !System::HasCheatList())
|
||||
if (!System::IsValid())
|
||||
return nullptr;
|
||||
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
if (!System::HasCheatList() && !g_settings.auto_load_cheats)
|
||||
{
|
||||
// Hopefully this won't deadlock...
|
||||
hi->RunOnEmulationThread([hi]() { hi->LoadCheatListFromGameTitle(); }, true);
|
||||
}
|
||||
|
||||
if (!System::HasCheatList())
|
||||
return nullptr;
|
||||
|
||||
CheatList* cl = System::GetCheatList();
|
||||
const u32 count = cl->GetCodeCount();
|
||||
|
||||
jobjectArray arr = env->NewObjectArray(count, s_CheatCode_class, nullptr);
|
||||
jobjectArray arr = env->NewObjectArray(count, s_PatchCode_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),
|
||||
jobject java_cc = env->NewObject(s_PatchCode_class, s_PatchCode_constructor, static_cast<jint>(i),
|
||||
env->NewStringUTF(cc.description.c_str()), cc.enabled);
|
||||
env->SetObjectArrayElement(arr, i, java_cc);
|
||||
}
|
||||
|
@ -899,7 +937,16 @@ DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_getCheatList, jobject obj)
|
|||
return arr;
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setCheatEnabled, jobject obj, jint index, jboolean enabled)
|
||||
DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_importPatchCodesFromString, jobject obj, jstring str)
|
||||
{
|
||||
if (!System::IsValid())
|
||||
return false;
|
||||
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
return hi->ImportPatchCodesFromString(AndroidHelpers::JStringToString(env, str));
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setPatchCodeEnabled, jobject obj, jint index, jboolean enabled)
|
||||
{
|
||||
if (!System::IsValid() || !System::HasCheatList())
|
||||
return;
|
||||
|
|
|
@ -54,6 +54,8 @@ public:
|
|||
void RefreshGameList(bool invalidate_cache, bool invalidate_database, ProgressCallback* progress_callback);
|
||||
void ApplySettings(bool display_osd_messages);
|
||||
|
||||
bool ImportPatchCodesFromString(const std::string& str);
|
||||
|
||||
protected:
|
||||
void SetUserDirectory() override;
|
||||
void LoadSettings() override;
|
||||
|
|
|
@ -70,9 +70,9 @@ public class AndroidHostInterface {
|
|||
|
||||
public native void setDisplayAlignment(int alignment);
|
||||
|
||||
public native CheatCode[] getCheatList();
|
||||
|
||||
public native void setCheatEnabled(int index, boolean enabled);
|
||||
public native PatchCode[] getPatchCodeList();
|
||||
public native void setPatchCodeEnabled(int index, boolean enabled);
|
||||
public native boolean importPatchCodesFromString(String str);
|
||||
|
||||
public native void addOSDMessage(String message, float duration);
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceHolder;
|
||||
|
@ -54,6 +55,19 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
|||
editor.apply();
|
||||
}
|
||||
|
||||
private void reportErrorOnUIThread(String message) {
|
||||
// Toast.makeText(this, message, Toast.LENGTH_LONG);
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Error")
|
||||
.setMessage(message)
|
||||
.setPositiveButton("OK", (dialog, button) -> {
|
||||
dialog.dismiss();
|
||||
enableFullscreenImmersive();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
public void reportError(String message) {
|
||||
Log.e("EmulationActivity", message);
|
||||
|
||||
|
@ -65,6 +79,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
|||
.setMessage(message)
|
||||
.setPositiveButton("OK", (dialog, button) -> {
|
||||
dialog.dismiss();
|
||||
enableFullscreenImmersive();
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
|
@ -133,7 +148,6 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
|||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
// Once we get a surface, we can boot.
|
||||
if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) {
|
||||
final boolean hadSurface = AndroidHostInterface.getInstance().hasSurface();
|
||||
AndroidHostInterface.getInstance().surfaceChanged(holder.getSurface(), format, width, height);
|
||||
updateOrientation();
|
||||
|
||||
|
@ -217,6 +231,9 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
|||
if (requestCode == REQUEST_CODE_SETTINGS) {
|
||||
if (AndroidHostInterface.getInstance().isEmulationThreadRunning())
|
||||
applySettings();
|
||||
} else if (requestCode == REQUEST_IMPORT_PATCH_CODES) {
|
||||
if (data != null)
|
||||
importPatchesFromFile(data.getData());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,6 +278,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
|||
}
|
||||
|
||||
private static final int REQUEST_CODE_SETTINGS = 0;
|
||||
private static final int REQUEST_IMPORT_PATCH_CODES = 1;
|
||||
|
||||
private void showMenu() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
|
@ -331,7 +349,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
|||
return;
|
||||
}
|
||||
|
||||
case 1: // Patches
|
||||
case 1: // Patch Codes
|
||||
{
|
||||
showPatchesMenu();
|
||||
return;
|
||||
|
@ -373,25 +391,42 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
|||
}
|
||||
|
||||
private void showPatchesMenu() {
|
||||
final CheatCode[] cheats = AndroidHostInterface.getInstance().getCheatList();
|
||||
if (cheats == null) {
|
||||
AndroidHostInterface.getInstance().addOSDMessage("No patches are loaded.", 5.0f);
|
||||
return;
|
||||
}
|
||||
final PatchCode[] codes = AndroidHostInterface.getInstance().getPatchCodeList();
|
||||
|
||||
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());
|
||||
CharSequence[] items = new CharSequence[(codes != null) ? (codes.length + 1) : 1];
|
||||
items[0] = "Import Patch Codes...";
|
||||
if (codes != null) {
|
||||
for (int i = 0; i < codes.length; i++) {
|
||||
final PatchCode cc = codes[i];
|
||||
items[i + 1] = String.format("%s %s", cc.isEnabled() ? "(ON)" : "(OFF)", cc.getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
builder.setItems(items, (dialogInterface, i) -> AndroidHostInterface.getInstance().setCheatEnabled(i, !cheats[i].isEnabled()));
|
||||
builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive());
|
||||
builder.setItems(items, (dialogInterface, i) -> {
|
||||
if (i > 0) {
|
||||
AndroidHostInterface.getInstance().setPatchCodeEnabled(i - 1, !codes[i - 1].isEnabled());
|
||||
enableFullscreenImmersive();
|
||||
} else {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.setType("*/*");
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
startActivityForResult(Intent.createChooser(intent, "Choose Patch Code File"), REQUEST_IMPORT_PATCH_CODES);
|
||||
}
|
||||
});
|
||||
builder.setOnCancelListener(dialogInterface -> enableFullscreenImmersive());
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void importPatchesFromFile(Uri uri) {
|
||||
String str = FileUtil.readFileFromUri(this, uri, 512 * 1024);
|
||||
if (str == null || !AndroidHostInterface.getInstance().importPatchCodesFromString(str)) {
|
||||
reportErrorOnUIThread("Failed to import patch codes. Make sure you selected a PCSXR or Libretro format file.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Touchscreen controller overlay
|
||||
*/
|
||||
|
|
|
@ -9,12 +9,23 @@ import android.net.Uri;
|
|||
import android.os.Build;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class FileUtil {
|
||||
static String TAG = "TAG";
|
||||
|
@ -138,4 +149,32 @@ public final class FileUtil {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String readFileFromUri(final Context context, final Uri uri, int maxSize) {
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = context.getContentResolver().openInputStream(uri);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder os = new StringBuilder();
|
||||
try {
|
||||
char[] buffer = new char[1024];
|
||||
InputStreamReader reader = new InputStreamReader(stream, Charset.forName(StandardCharsets.UTF_8.name()));
|
||||
int len;
|
||||
while ((len = reader.read(buffer)) > 0) {
|
||||
os.append(buffer, 0, len);
|
||||
if (os.length() > maxSize)
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (os.length() == 0)
|
||||
return null;
|
||||
|
||||
return os.toString();
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
package com.github.stenzek.duckstation;
|
||||
|
||||
public class CheatCode {
|
||||
public class PatchCode {
|
||||
private int mIndex;
|
||||
private String mName;
|
||||
private String mDescription;
|
||||
private boolean mEnabled;
|
||||
|
||||
public CheatCode(int index, String name, boolean enabled) {
|
||||
public PatchCode(int index, String description, boolean enabled) {
|
||||
mIndex = index;
|
||||
mName = name;
|
||||
mDescription = description;
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,8 @@ public class CheatCode {
|
|||
return mIndex;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
public String getDescription() {
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
|
@ -143,7 +143,7 @@
|
|||
</string-array>
|
||||
<string-array name="emulation_more_menu">
|
||||
<item>Reset</item>
|
||||
<item>Patches</item>
|
||||
<item>Patch Codes</item>
|
||||
<item>Change Disc</item>
|
||||
<item>Change Touchscreen Controller</item>
|
||||
<item>Settings</item>
|
||||
|
|
|
@ -41,9 +41,9 @@
|
|||
app:iconSpaceReserved="false" />
|
||||
<SwitchPreferenceCompat
|
||||
app:key="Main/AutoLoadCheats"
|
||||
app:title="Load Cheats"
|
||||
app:title="Load Patch Codes"
|
||||
app:defaultValue="false"
|
||||
app:summary="Loads cheats from cheats/<game name>.cht in PCSXR format. Cheats can be toggled while ingame."
|
||||
app:summary="Loads patch codes from cheats/<game name>.cht in PCSXR format. Codes can be toggled while ingame."
|
||||
app:iconSpaceReserved="false" />
|
||||
<SwitchPreferenceCompat
|
||||
app:key="Display/VSync"
|
||||
|
|
Loading…
Reference in New Issue