Cheats: Support importing native format

Compared to only replacing the .cht file.
This commit is contained in:
Stenzek 2024-11-29 13:10:58 +10:00
parent 208e6c4b35
commit eeee1e691a
No known key found for this signature in database
2 changed files with 106 additions and 23 deletions

View File

@ -198,7 +198,8 @@ using EnableCodeList = std::vector<std::string>;
static std::string GetChtTemplate(const std::string_view serial, std::optional<GameHash> hash, bool add_wildcard); static std::string GetChtTemplate(const std::string_view serial, std::optional<GameHash> hash, bool add_wildcard);
static std::vector<std::string> FindChtFilesOnDisk(const std::string_view serial, std::optional<GameHash> hash, static std::vector<std::string> FindChtFilesOnDisk(const std::string_view serial, std::optional<GameHash> hash,
bool cheats); 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 void AppendCheatToList(CodeInfoList* dst, CodeInfo code);
static std::string FormatCodeForFile(const 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, EnumerateChtFiles(serial, hash, cheats, true, true, load_from_database,
[&ret](const std::string& filename, const std::string& data, bool 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) 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()) if (!file_contents.empty() && !name.empty())
{ {
CodeInfoList existing_codes_in_file; 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); const CodeInfo* existing_code = FindCodeInInfoList(existing_codes_in_file, name);
if (existing_code) if (existing_code)
@ -664,7 +665,7 @@ bool Cheats::SaveCodesToFile(const char* path, const CodeInfoList& codes, Error*
if (!file_contents.empty()) if (!file_contents.empty())
{ {
CodeInfoList existing_codes_in_file; 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); const CodeInfo* existing_code = FindCodeInInfoList(existing_codes_in_file, code.name);
if (existing_code) if (existing_code)
@ -980,7 +981,8 @@ u32 Cheats::GetActiveCheatCount()
// File Parsing // 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; CodeInfo current_code;
@ -988,17 +990,24 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
std::optional<CodeType> legacy_type; std::optional<CodeType> legacy_type;
std::optional<CodeActivation> legacy_activation; std::optional<CodeActivation> legacy_activation;
const auto finish_code = [&dst, &file_data, &current_code]() { CheatFileReader reader(file_data);
const auto finish_code = [&dst, &file_data, &stop_on_error, &error, &current_code, &reader]() {
if (current_code.file_offset_end > current_code.file_offset_body_start) if (current_code.file_offset_end > current_code.file_offset_body_start)
{ {
current_code.body = std::string_view(file_data).substr( current_code.body = file_data.substr(current_code.file_offset_body_start,
current_code.file_offset_body_start, current_code.file_offset_end - 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)); AppendCheatToList(dst, std::move(current_code));
return true;
}; };
CheatFileReader reader(file_data);
std::string_view line; std::string_view line;
while (reader.GetLine(&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))); legacy_type = ParseTypeName(StringUtil::StripWhitespace(linev.substr(6)));
if (!legacy_type.has_value()) [[unlikely]] 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=")) else if (linev.starts_with("#activation="))
{ {
legacy_activation = ParseActivationName(StringUtil::StripWhitespace(linev.substr(12))); legacy_activation = ParseActivationName(StringUtil::StripWhitespace(linev.substr(12)));
if (!legacy_activation.has_value()) [[unlikely]] 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 // skip comments
@ -1035,7 +1052,24 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
{ {
if (linev.size() < 3 || linev.back() != ']') 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; continue;
} }
@ -1047,7 +1081,6 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
current_code = CodeInfo(); current_code = CodeInfo();
} }
const std::string_view name = linev.substr(1, linev.length() - 2);
current_code.name = current_code.name =
legacy_group.has_value() ? fmt::format("{}\\{}", legacy_group.value(), name) : std::string(name); legacy_group.has_value() ? fmt::format("{}\\{}", legacy_group.value(), name) : std::string(name);
current_code.type = legacy_type.value_or(CodeType::Gameshark); 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; std::string_view key, value;
if (!StringUtil::ParseAssignmentString(linev, &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; continue;
} }
@ -1090,29 +1128,57 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
{ {
const std::optional<CodeType> type = ParseTypeName(value); const std::optional<CodeType> type = ParseTypeName(value);
if (type.has_value()) [[unlikely]] if (type.has_value()) [[unlikely]]
{
current_code.type = type.value(); current_code.type = type.value();
}
else 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") else if (key == "Activation")
{ {
const std::optional<CodeActivation> activation = ParseActivationName(value); const std::optional<CodeActivation> activation = ParseActivationName(value);
if (activation.has_value()) [[unlikely]] if (activation.has_value()) [[unlikely]]
{
current_code.activation = activation.value(); current_code.activation = activation.value();
}
else 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") else if (key == "Option")
{ {
if (std::optional<Cheats::CodeOption> opt = ParseOption(value)) if (std::optional<Cheats::CodeOption> opt = ParseOption(value))
{
current_code.options.push_back(std::move(opt.value())); current_code.options.push_back(std::move(opt.value()));
}
else 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") else if (key == "OptionRange")
{ {
if (!ParseOptionRange(value, &current_code.option_range_start, &current_code.option_range_end)) if (!ParseOptionRange(value, &current_code.option_range_start, &current_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 // 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()) 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; continue;
} }
@ -1134,7 +1205,9 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
// last code. // last code.
if (!current_code.name.empty()) if (!current_code.name.empty())
finish_code(); return finish_code();
else
return true;
} }
void Cheats::AppendCheatToList(CodeInfoList* dst, CodeInfo code) 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) if (file_format == FileFormat::Unknown)
file_format = DetectFileFormat(file_contents); 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)) if (!ImportPCSXFile(dst, file_contents, stop_on_error, error))
return false; return false;
@ -1468,6 +1546,10 @@ Cheats::FileFormat Cheats::DetectFileFormat(const std::string_view file_contents
if (linev.starts_with("cheats")) if (linev.starts_with("cheats"))
return FileFormat::Libretro; 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 // pcsxr if we see brackets
if (linev[0] == '[') if (linev[0] == '[')
return FileFormat::PCSX; return FileFormat::PCSX;

View File

@ -34,6 +34,7 @@ enum class CodeActivation : u8
enum class FileFormat : u8 enum class FileFormat : u8
{ {
Unknown, Unknown,
DuckStation,
PCSX, PCSX,
Libretro, Libretro,
EPSXe, EPSXe,