diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index 09b10a1d3..10a59564a 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -198,7 +198,8 @@ using EnableCodeList = std::vector; static std::string GetChtTemplate(const std::string_view serial, std::optional hash, bool add_wildcard); static std::vector FindChtFilesOnDisk(const std::string_view serial, std::optional hash, bool cheats); -static void ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bool from_database); +static bool ExtractCodeInfo(CodeInfoList* dst, const std::string_view file_data, bool from_database, bool stop_on_error, + Error* error); static void AppendCheatToList(CodeInfoList* dst, CodeInfo code); static std::string FormatCodeForFile(const CodeInfo& code); @@ -511,7 +512,7 @@ Cheats::CodeInfoList Cheats::GetCodeInfoList(const std::string_view serial, std: EnumerateChtFiles(serial, hash, cheats, true, true, load_from_database, [&ret](const std::string& filename, const std::string& data, bool from_database) { - ExtractCodeInfo(&ret, data, from_database); + ExtractCodeInfo(&ret, data, from_database, false, nullptr); }); if (sort_by_name) @@ -605,7 +606,7 @@ bool Cheats::UpdateCodeInFile(const char* path, const std::string_view name, con if (!file_contents.empty() && !name.empty()) { CodeInfoList existing_codes_in_file; - ExtractCodeInfo(&existing_codes_in_file, file_contents, false); + ExtractCodeInfo(&existing_codes_in_file, file_contents, false, false, nullptr); const CodeInfo* existing_code = FindCodeInInfoList(existing_codes_in_file, name); if (existing_code) @@ -664,7 +665,7 @@ bool Cheats::SaveCodesToFile(const char* path, const CodeInfoList& codes, Error* if (!file_contents.empty()) { CodeInfoList existing_codes_in_file; - ExtractCodeInfo(&existing_codes_in_file, file_contents, false); + ExtractCodeInfo(&existing_codes_in_file, file_contents, false, false, nullptr); const CodeInfo* existing_code = FindCodeInInfoList(existing_codes_in_file, code.name); if (existing_code) @@ -980,7 +981,8 @@ u32 Cheats::GetActiveCheatCount() // File Parsing ////////////////////////////////////////////////////////////////////////// -void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bool from_database) +bool Cheats::ExtractCodeInfo(CodeInfoList* dst, std::string_view file_data, bool from_database, bool stop_on_error, + Error* error) { CodeInfo current_code; @@ -988,17 +990,24 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo std::optional legacy_type; std::optional legacy_activation; - const auto finish_code = [&dst, &file_data, ¤t_code]() { + CheatFileReader reader(file_data); + + const auto finish_code = [&dst, &file_data, &stop_on_error, &error, ¤t_code, &reader]() { if (current_code.file_offset_end > current_code.file_offset_body_start) { - current_code.body = std::string_view(file_data).substr( - current_code.file_offset_body_start, current_code.file_offset_end - current_code.file_offset_body_start); + current_code.body = file_data.substr(current_code.file_offset_body_start, + current_code.file_offset_end - current_code.file_offset_body_start); + } + else + { + if (!reader.LogError(error, stop_on_error, "Empty body for cheat '{}'", current_code.name)) + return false; } AppendCheatToList(dst, std::move(current_code)); + return true; }; - CheatFileReader reader(file_data); std::string_view line; while (reader.GetLine(&line)) { @@ -1016,15 +1025,23 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo { legacy_type = ParseTypeName(StringUtil::StripWhitespace(linev.substr(6))); if (!legacy_type.has_value()) [[unlikely]] - WARNING_LOG("Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line); - continue; + { + if (!reader.LogError(error, stop_on_error, "Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line)) + return false; + + continue; + } } else if (linev.starts_with("#activation=")) { legacy_activation = ParseActivationName(StringUtil::StripWhitespace(linev.substr(12))); if (!legacy_activation.has_value()) [[unlikely]] - WARNING_LOG("Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line); - continue; + { + if (!reader.LogError(error, stop_on_error, "Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line)) + return false; + + continue; + } } // skip comments @@ -1035,7 +1052,24 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo { if (linev.size() < 3 || linev.back() != ']') { - WARNING_LOG("Malformed code at line {}: {}", reader.GetCurrentLineNumber(), line); + if (!reader.LogError(error, stop_on_error, "Malformed code at line {}: {}", reader.GetCurrentLineNumber(), + line)) + { + return false; + } + + continue; + } + + const std::string_view name = StringUtil::StripWhitespace(linev.substr(1, linev.length() - 2)); + if (name.empty()) + { + if (!reader.LogError(error, stop_on_error, "Empty code name at line {}: {}", reader.GetCurrentLineNumber(), + line)) + { + return false; + } + continue; } @@ -1047,7 +1081,6 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo current_code = CodeInfo(); } - const std::string_view name = linev.substr(1, linev.length() - 2); current_code.name = legacy_group.has_value() ? fmt::format("{}\\{}", legacy_group.value(), name) : std::string(name); current_code.type = legacy_type.value_or(CodeType::Gameshark); @@ -1074,7 +1107,12 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo std::string_view key, value; if (!StringUtil::ParseAssignmentString(linev, &key, &value)) { - WARNING_LOG("Malformed code at line {}: {}", reader.GetCurrentLineNumber(), line); + if (!reader.LogError(error, stop_on_error, "Malformed code at line {}: {}", reader.GetCurrentLineNumber(), + line)) + { + return false; + } + continue; } @@ -1090,29 +1128,57 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo { const std::optional type = ParseTypeName(value); if (type.has_value()) [[unlikely]] + { current_code.type = type.value(); + } else - WARNING_LOG("Unknown code type at line {}: {}", reader.GetCurrentLineNumber(), line); + { + if (!reader.LogError(error, stop_on_error, "Unknown code type at line {}: {}", reader.GetCurrentLineNumber(), + line)) + return false; + } } else if (key == "Activation") { const std::optional activation = ParseActivationName(value); if (activation.has_value()) [[unlikely]] + { current_code.activation = activation.value(); + } else - WARNING_LOG("Unknown code activation at line {}: {}", reader.GetCurrentLineNumber(), line); + { + if (!reader.LogError(error, stop_on_error, "Unknown code activation at line {}: {}", + reader.GetCurrentLineNumber(), line)) + { + return false; + } + } } else if (key == "Option") { if (std::optional opt = ParseOption(value)) + { current_code.options.push_back(std::move(opt.value())); + } else - WARNING_LOG("Invalid option declaration at line {}: {}", reader.GetCurrentLineNumber(), line); + { + if (!reader.LogError(error, stop_on_error, "Invalid option declaration at line {}: {}", + reader.GetCurrentLineNumber(), line)) + { + return false; + } + } } else if (key == "OptionRange") { if (!ParseOptionRange(value, ¤t_code.option_range_start, ¤t_code.option_range_end)) - WARNING_LOG("Invalid option range declaration at line {}: {}", reader.GetCurrentLineNumber(), line); + { + if (!reader.LogError(error, stop_on_error, "Invalid option range declaration at line {}: {}", + reader.GetCurrentLineNumber(), line)) + { + return false; + } + } } // ignore other keys when we're only grabbing info @@ -1121,7 +1187,12 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo if (current_code.name.empty()) { - WARNING_LOG("Code data specified without name at line {}: {}", reader.GetCurrentLineNumber(), line); + if (!reader.LogError(error, stop_on_error, "Code data specified without name at line {}: {}", + reader.GetCurrentLineNumber(), line)) + { + return false; + } + continue; } @@ -1134,7 +1205,9 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo // last code. if (!current_code.name.empty()) - finish_code(); + return finish_code(); + else + return true; } void Cheats::AppendCheatToList(CodeInfoList* dst, CodeInfo code) @@ -1424,7 +1497,12 @@ bool Cheats::ImportCodesFromString(CodeInfoList* dst, const std::string_view fil if (file_format == FileFormat::Unknown) file_format = DetectFileFormat(file_contents); - if (file_format == FileFormat::PCSX) + if (file_format == FileFormat::DuckStation) + { + if (!ExtractCodeInfo(dst, file_contents, false, stop_on_error, error)) + return false; + } + else if (file_format == FileFormat::PCSX) { if (!ImportPCSXFile(dst, file_contents, stop_on_error, error)) return false; @@ -1468,6 +1546,10 @@ Cheats::FileFormat Cheats::DetectFileFormat(const std::string_view file_contents if (linev.starts_with("cheats")) return FileFormat::Libretro; + // native if we see brackets and a type string + if (linev[0] == '[' && file_contents.find("\nType =")) + return FileFormat::DuckStation; + // pcsxr if we see brackets if (linev[0] == '[') return FileFormat::PCSX; diff --git a/src/core/cheats.h b/src/core/cheats.h index 7a83e6e98..5d7d8e307 100644 --- a/src/core/cheats.h +++ b/src/core/cheats.h @@ -34,6 +34,7 @@ enum class CodeActivation : u8 enum class FileFormat : u8 { Unknown, + DuckStation, PCSX, Libretro, EPSXe,