Cheats: Support importing native format
Compared to only replacing the .cht file.
This commit is contained in:
parent
208e6c4b35
commit
eeee1e691a
|
@ -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, ¤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)
|
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, ¤t_code.option_range_start, ¤t_code.option_range_end))
|
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
|
// 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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue