diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index 638f95243..449d5c149 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -80,7 +80,7 @@ std::string JStringToString(JNIEnv* env, jstring str) return ret; } -std::unique_ptr ReadInputStreamToMemory(JNIEnv* env, jobject obj, u32 chunk_size/* = 65536*/) +std::unique_ptr ReadInputStreamToMemory(JNIEnv* env, jobject obj, u32 chunk_size /* = 65536*/) { std::unique_ptr bs = std::make_unique(nullptr, 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); } -std::unique_ptr AndroidHostInterface::OpenPackageFile(const char *path, u32 flags) +std::unique_ptr AndroidHostInterface::OpenPackageFile(const char* path, u32 flags) { Log_DevPrintf("OpenPackageFile(%s, %x)", path, flags); if (flags & (BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE)) return {}; 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) { Log_ErrorPrintf("Package file '%s' not found", path); @@ -695,7 +696,8 @@ void AndroidHostInterface::SetVibration(bool enabled) m_last_vibration_update_time = current_time; JNIEnv* env = AndroidHelpers::GetJNIEnv(); - if (m_emulation_activity_object) { + if (m_emulation_activity_object) + { env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_setVibration, static_cast(enabled)); } @@ -735,8 +737,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) // Create global reference so it doesn't get cleaned up. JNIEnv* env = AndroidHelpers::GetJNIEnv(); if ((s_String_class = env->FindClass("java/lang/String")) == nullptr || - (s_String_class = static_cast(env->NewGlobalRef(s_String_class))) == - nullptr || + (s_String_class = static_cast(env->NewGlobalRef(s_String_class))) == nullptr || (s_AndroidHostInterface_class = env->FindClass("com/github/stenzek/duckstation/AndroidHostInterface")) == nullptr || (s_AndroidHostInterface_class = static_cast(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 || (s_AndroidHostInterface_method_reportMessage = env->GetMethodID(s_AndroidHostInterface_class, "reportMessage", "(Ljava/lang/String;)V")) == nullptr || - (s_AndroidHostInterface_method_openAssetStream = - env->GetMethodID(s_AndroidHostInterface_class, "openAssetStream", "(Ljava/lang/String;)Ljava/io/InputStream;")) == nullptr || + (s_AndroidHostInterface_method_openAssetStream = env->GetMethodID( + s_AndroidHostInterface_class, "openAssetStream", "(Ljava/lang/String;)Ljava/io/InputStream;")) == nullptr || (emulation_activity_class = env->FindClass("com/github/stenzek/duckstation/EmulationActivity")) == nullptr || (s_EmulationActivity_method_reportError = 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 || (s_EmulationActivity_method_onGameTitleChanged = env->GetMethodID(emulation_activity_class, "onGameTitleChanged", "(Ljava/lang/String;)V")) == nullptr || - (s_EmulationActivity_method_setVibration = - env->GetMethodID(emulation_activity_class, "setVibration", "(Z)V")) == nullptr || + (s_EmulationActivity_method_setVibration = env->GetMethodID(emulation_activity_class, "setVibration", "(Z)V")) == + nullptr || (s_PatchCode_constructor = env->GetMethodID(s_PatchCode_class, "", "(ILjava/lang/String;Z)V")) == nullptr) { Log_ErrorPrint("AndroidHostInterface lookups failed"); @@ -1040,10 +1041,15 @@ DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_getPatchCodeList, jobject o return nullptr; AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - if (!System::HasCheatList() && !g_settings.auto_load_cheats) + if (!System::HasCheatList()) { // Hopefully this won't deadlock... - hi->RunOnEmulationThread([hi]() { hi->LoadCheatListFromGameTitle(); }, true); + hi->RunOnEmulationThread( + [hi]() { + if (!hi->LoadCheatListFromGameTitle()) + hi->LoadCheatListFromDatabase(); + }, + true); } if (!System::HasCheatList()) @@ -1171,7 +1177,8 @@ DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_setMediaPlaylistIndex, job AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); hi->RunOnEmulationThread([index, hi]() { - if (System::IsValid()) { + if (System::IsValid()) + { if (!System::SwitchMediaFromPlaylist(index)) hi->AddOSDMessage("Disc switch failed. Please make sure the file exists."); } diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index 0431fcd9f..7eecff54a 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -1,6 +1,7 @@ #include "cheats.h" #include "bus.h" #include "common/assert.h" +#include "common/byte_stream.h" #include "common/file_system.h" #include "common/log.h" #include "common/string.h" @@ -505,6 +506,123 @@ bool CheatList::SaveToPCSXRFile(const char* filename) return (std::ferror(fp.get()) == 0); } +bool CheatList::LoadFromPackage(const std::string& game_code) +{ + std::unique_ptr 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(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(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 count = 0; diff --git a/src/core/cheats.h b/src/core/cheats.h index e536e292f..de0b0af37 100644 --- a/src/core/cheats.h +++ b/src/core/cheats.h @@ -129,6 +129,8 @@ public: bool SaveToPCSXRFile(const char* filename); + bool LoadFromPackage(const std::string& game_code); + void Apply(); void ApplyCode(u32 index); diff --git a/src/duckstation-qt/cheatmanagerdialog.cpp b/src/duckstation-qt/cheatmanagerdialog.cpp index 3392b3860..c8bc91a17 100644 --- a/src/duckstation-qt/cheatmanagerdialog.cpp +++ b/src/duckstation-qt/cheatmanagerdialog.cpp @@ -263,7 +263,15 @@ CheatList* CheatManagerDialog::getCheatList() const CheatList* list = System::GetCheatList(); if (!list) + { QtHostInterface::GetInstance()->LoadCheatListFromGameTitle(); + list = System::GetCheatList(); + } + if (!list) + { + QtHostInterface::GetInstance()->LoadCheatListFromDatabase(); + list = System::GetCheatList(); + } if (!list) { QtHostInterface::GetInstance()->executeOnEmulationThread( diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index aa94d61c5..7e0e4d214 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -2436,6 +2436,20 @@ bool CommonHostInterface::LoadCheatListFromGameTitle() return LoadCheatList(filename.c_str()); } +bool CommonHostInterface::LoadCheatListFromDatabase() +{ + if (System::GetRunningCode().empty()) + return false; + + std::unique_ptr cl = std::make_unique(); + 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() { if (!System::IsValid() || !System::HasCheatList()) diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index f79c27d10..ce0694a99 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -160,6 +160,9 @@ public: /// Loads the cheat list for the current game title from the user directory. 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. bool SaveCheatList();