Cheats: Support parsing built-in database

This commit is contained in:
Connor McLaughlin 2020-11-29 16:42:49 +10:00
parent d1399fe6a3
commit eaafd0a00c
6 changed files with 165 additions and 13 deletions

View File

@ -80,7 +80,7 @@ std::string JStringToString(JNIEnv* env, jstring str)
return ret; return ret;
} }
std::unique_ptr<GrowableMemoryByteStream> ReadInputStreamToMemory(JNIEnv* env, jobject obj, u32 chunk_size/* = 65536*/) std::unique_ptr<GrowableMemoryByteStream> ReadInputStreamToMemory(JNIEnv* env, jobject obj, u32 chunk_size /* = 65536*/)
{ {
std::unique_ptr<GrowableMemoryByteStream> bs = std::make_unique<GrowableMemoryByteStream>(nullptr, 0); std::unique_ptr<GrowableMemoryByteStream> bs = std::make_unique<GrowableMemoryByteStream>(nullptr, 0);
u32 position = 0; u32 position = 0;
@ -190,14 +190,15 @@ float AndroidHostInterface::GetFloatSettingValue(const char* section, const char
return m_settings_interface.GetFloatValue(section, key, default_value); return m_settings_interface.GetFloatValue(section, key, default_value);
} }
std::unique_ptr<ByteStream> AndroidHostInterface::OpenPackageFile(const char *path, u32 flags) std::unique_ptr<ByteStream> AndroidHostInterface::OpenPackageFile(const char* path, u32 flags)
{ {
Log_DevPrintf("OpenPackageFile(%s, %x)", path, flags); Log_DevPrintf("OpenPackageFile(%s, %x)", path, flags);
if (flags & (BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE)) if (flags & (BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE))
return {}; return {};
JNIEnv* env = AndroidHelpers::GetJNIEnv(); JNIEnv* env = AndroidHelpers::GetJNIEnv();
jobject stream = env->CallObjectMethod(m_java_object, s_AndroidHostInterface_method_openAssetStream, env->NewStringUTF(path)); jobject stream =
env->CallObjectMethod(m_java_object, s_AndroidHostInterface_method_openAssetStream, env->NewStringUTF(path));
if (!stream) if (!stream)
{ {
Log_ErrorPrintf("Package file '%s' not found", path); Log_ErrorPrintf("Package file '%s' not found", path);
@ -695,7 +696,8 @@ void AndroidHostInterface::SetVibration(bool enabled)
m_last_vibration_update_time = current_time; m_last_vibration_update_time = current_time;
JNIEnv* env = AndroidHelpers::GetJNIEnv(); JNIEnv* env = AndroidHelpers::GetJNIEnv();
if (m_emulation_activity_object) { if (m_emulation_activity_object)
{
env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_setVibration, env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_setVibration,
static_cast<jboolean>(enabled)); static_cast<jboolean>(enabled));
} }
@ -735,8 +737,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
// Create global reference so it doesn't get cleaned up. // Create global reference so it doesn't get cleaned up.
JNIEnv* env = AndroidHelpers::GetJNIEnv(); JNIEnv* env = AndroidHelpers::GetJNIEnv();
if ((s_String_class = env->FindClass("java/lang/String")) == nullptr || if ((s_String_class = env->FindClass("java/lang/String")) == nullptr ||
(s_String_class = static_cast<jclass>(env->NewGlobalRef(s_String_class))) == (s_String_class = static_cast<jclass>(env->NewGlobalRef(s_String_class))) == nullptr ||
nullptr ||
(s_AndroidHostInterface_class = env->FindClass("com/github/stenzek/duckstation/AndroidHostInterface")) == (s_AndroidHostInterface_class = env->FindClass("com/github/stenzek/duckstation/AndroidHostInterface")) ==
nullptr || nullptr ||
(s_AndroidHostInterface_class = static_cast<jclass>(env->NewGlobalRef(s_AndroidHostInterface_class))) == (s_AndroidHostInterface_class = static_cast<jclass>(env->NewGlobalRef(s_AndroidHostInterface_class))) ==
@ -757,8 +758,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
env->GetMethodID(s_AndroidHostInterface_class, "reportError", "(Ljava/lang/String;)V")) == nullptr || env->GetMethodID(s_AndroidHostInterface_class, "reportError", "(Ljava/lang/String;)V")) == nullptr ||
(s_AndroidHostInterface_method_reportMessage = (s_AndroidHostInterface_method_reportMessage =
env->GetMethodID(s_AndroidHostInterface_class, "reportMessage", "(Ljava/lang/String;)V")) == nullptr || env->GetMethodID(s_AndroidHostInterface_class, "reportMessage", "(Ljava/lang/String;)V")) == nullptr ||
(s_AndroidHostInterface_method_openAssetStream = (s_AndroidHostInterface_method_openAssetStream = env->GetMethodID(
env->GetMethodID(s_AndroidHostInterface_class, "openAssetStream", "(Ljava/lang/String;)Ljava/io/InputStream;")) == nullptr || s_AndroidHostInterface_class, "openAssetStream", "(Ljava/lang/String;)Ljava/io/InputStream;")) == nullptr ||
(emulation_activity_class = env->FindClass("com/github/stenzek/duckstation/EmulationActivity")) == nullptr || (emulation_activity_class = env->FindClass("com/github/stenzek/duckstation/EmulationActivity")) == nullptr ||
(s_EmulationActivity_method_reportError = (s_EmulationActivity_method_reportError =
env->GetMethodID(emulation_activity_class, "reportError", "(Ljava/lang/String;)V")) == nullptr || env->GetMethodID(emulation_activity_class, "reportError", "(Ljava/lang/String;)V")) == nullptr ||
@ -770,8 +771,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
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_EmulationActivity_method_setVibration = (s_EmulationActivity_method_setVibration = env->GetMethodID(emulation_activity_class, "setVibration", "(Z)V")) ==
env->GetMethodID(emulation_activity_class, "setVibration", "(Z)V")) == nullptr || nullptr ||
(s_PatchCode_constructor = env->GetMethodID(s_PatchCode_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"); Log_ErrorPrint("AndroidHostInterface lookups failed");
@ -1040,10 +1041,15 @@ DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_getPatchCodeList, jobject o
return nullptr; return nullptr;
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
if (!System::HasCheatList() && !g_settings.auto_load_cheats) if (!System::HasCheatList())
{ {
// Hopefully this won't deadlock... // Hopefully this won't deadlock...
hi->RunOnEmulationThread([hi]() { hi->LoadCheatListFromGameTitle(); }, true); hi->RunOnEmulationThread(
[hi]() {
if (!hi->LoadCheatListFromGameTitle())
hi->LoadCheatListFromDatabase();
},
true);
} }
if (!System::HasCheatList()) if (!System::HasCheatList())
@ -1171,7 +1177,8 @@ DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_setMediaPlaylistIndex, job
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
hi->RunOnEmulationThread([index, hi]() { hi->RunOnEmulationThread([index, hi]() {
if (System::IsValid()) { if (System::IsValid())
{
if (!System::SwitchMediaFromPlaylist(index)) if (!System::SwitchMediaFromPlaylist(index))
hi->AddOSDMessage("Disc switch failed. Please make sure the file exists."); hi->AddOSDMessage("Disc switch failed. Please make sure the file exists.");
} }

View File

@ -1,6 +1,7 @@
#include "cheats.h" #include "cheats.h"
#include "bus.h" #include "bus.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/byte_stream.h"
#include "common/file_system.h" #include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
#include "common/string.h" #include "common/string.h"
@ -505,6 +506,123 @@ bool CheatList::SaveToPCSXRFile(const char* filename)
return (std::ferror(fp.get()) == 0); return (std::ferror(fp.get()) == 0);
} }
bool CheatList::LoadFromPackage(const std::string& game_code)
{
std::unique_ptr<ByteStream> stream =
g_host_interface->OpenPackageFile("database/chtdb.txt", BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED);
if (!stream)
return false;
std::string db_string = FileSystem::ReadStreamToString(stream.get());
stream.reset();
if (db_string.empty())
return false;
std::istringstream iss(db_string);
std::string line;
while (std::getline(iss, line))
{
char* start = line.data();
while (*start != '\0' && std::isspace(*start))
start++;
// skip empty lines
if (*start == '\0' || *start == ';')
continue;
char* end = start + std::strlen(start) - 1;
while (end > start && std::isspace(*end))
{
*end = '\0';
end--;
}
if (start == end)
continue;
if (start[0] != ':' || std::strcmp(&start[1], game_code.c_str()) != 0)
continue;
// game code match
CheatCode current_code;
while (std::getline(iss, line))
{
start = line.data();
while (*start != '\0' && std::isspace(*start))
start++;
// skip empty lines
if (*start == '\0' || *start == ';')
continue;
end = start + std::strlen(start) - 1;
while (end > start && std::isspace(*end))
{
*end = '\0';
end--;
}
if (start == end)
continue;
if (start[0] == ':')
break;
if (start[0] == '#')
{
start++;
if (current_code.Valid())
{
m_codes.push_back(std::move(current_code));
current_code = CheatCode();
}
// new code
char* slash = std::strrchr(start, '\\');
if (slash)
{
*slash = '\0';
current_code.group = start;
start = slash + 1;
}
if (current_code.group.empty())
current_code.group = "Ungrouped";
current_code.description = start;
continue;
}
while (!IsHexCharacter(*start) && start != end)
start++;
if (start == end)
continue;
char* end_ptr;
CheatCode::Instruction inst;
inst.first = static_cast<u32>(std::strtoul(start, &end_ptr, 16));
inst.second = 0;
if (end_ptr)
{
while (!IsHexCharacter(*end_ptr) && end_ptr != end)
end_ptr++;
if (end_ptr != end)
inst.second = static_cast<u32>(std::strtoul(end_ptr, nullptr, 16));
}
current_code.instructions.push_back(inst);
}
if (current_code.Valid())
m_codes.push_back(std::move(current_code));
Log_InfoPrintf("Loaded %zu codes from package for %s", m_codes.size(), game_code.c_str());
return !m_codes.empty();
}
Log_WarningPrintf("No codes found in package for %s", game_code.c_str());
return false;
}
u32 CheatList::GetEnabledCodeCount() const u32 CheatList::GetEnabledCodeCount() const
{ {
u32 count = 0; u32 count = 0;

View File

@ -129,6 +129,8 @@ public:
bool SaveToPCSXRFile(const char* filename); bool SaveToPCSXRFile(const char* filename);
bool LoadFromPackage(const std::string& game_code);
void Apply(); void Apply();
void ApplyCode(u32 index); void ApplyCode(u32 index);

View File

@ -263,7 +263,15 @@ CheatList* CheatManagerDialog::getCheatList() const
CheatList* list = System::GetCheatList(); CheatList* list = System::GetCheatList();
if (!list) if (!list)
{
QtHostInterface::GetInstance()->LoadCheatListFromGameTitle(); QtHostInterface::GetInstance()->LoadCheatListFromGameTitle();
list = System::GetCheatList();
}
if (!list)
{
QtHostInterface::GetInstance()->LoadCheatListFromDatabase();
list = System::GetCheatList();
}
if (!list) if (!list)
{ {
QtHostInterface::GetInstance()->executeOnEmulationThread( QtHostInterface::GetInstance()->executeOnEmulationThread(

View File

@ -2436,6 +2436,20 @@ bool CommonHostInterface::LoadCheatListFromGameTitle()
return LoadCheatList(filename.c_str()); return LoadCheatList(filename.c_str());
} }
bool CommonHostInterface::LoadCheatListFromDatabase()
{
if (System::GetRunningCode().empty())
return false;
std::unique_ptr<CheatList> cl = std::make_unique<CheatList>();
if (!cl->LoadFromPackage(System::GetRunningCode()))
return false;
AddFormattedOSDMessage(10.0f, TranslateString("OSDMessage", "Loaded %u cheats from database."), cl->GetCodeCount());
System::SetCheatList(std::move(cl));
return true;
}
bool CommonHostInterface::SaveCheatList() bool CommonHostInterface::SaveCheatList()
{ {
if (!System::IsValid() || !System::HasCheatList()) if (!System::IsValid() || !System::HasCheatList())

View File

@ -160,6 +160,9 @@ public:
/// Loads the cheat list for the current game title from the user directory. /// Loads the cheat list for the current game title from the user directory.
bool LoadCheatListFromGameTitle(); bool LoadCheatListFromGameTitle();
/// Loads the cheat list for the current game code from the built-in code database.
bool LoadCheatListFromDatabase();
/// Saves the current cheat list to the game title's file. /// Saves the current cheat list to the game title's file.
bool SaveCheatList(); bool SaveCheatList();