ppsspp/Core/Util/GameDB.cpp
Henrik Rydgård e01ca5b057
Logging API change (refactor) (#19324)
* Rename LogType to Log

* Explicitly use the Log:: enum when logging. Allows for autocomplete when editing.

* Mac/ARM64 buildfix

* Do the same with the hle result log macros

* Rename the log names to mixed case while at it.

* iOS buildfix

* Qt buildfix attempt, ARM32 buildfix
2024-07-14 14:42:59 +02:00

168 lines
4.4 KiB
C++

#include <cstdint>
#include "Core/Util/GameDB.h"
#include "Common/Log.h"
#include "Common/File/VFS/VFS.h"
#include "Common/StringUtils.h"
GameDB g_gameDB;
static const char *const g_gameDBFilename = "redump.csv";
static void SplitCSVLine(const std::string_view str, std::vector<std::string_view> &result) {
result.clear();
int indexCommaToLeftOfColumn = 0;
int indexCommaToRightOfColumn = -1;
bool inQuote = false;
for (int i = 0; i < static_cast<int>(str.size()); i++) {
if (str[i] == '\"') {
inQuote = !inQuote;
} else if (str[i] == ',' && !inQuote) {
indexCommaToLeftOfColumn = indexCommaToRightOfColumn;
indexCommaToRightOfColumn = i;
int index = indexCommaToLeftOfColumn + 1;
int length = indexCommaToRightOfColumn - index;
std::string_view column(str.data() + index, length);
// Remove quotes if possible
column = StripQuotes(column);
result.push_back(column);
}
}
const std::string_view finalColumn(str.data() + indexCommaToRightOfColumn + 1, str.size() - indexCommaToRightOfColumn - 1);
result.push_back(finalColumn);
}
static void SplitSV(std::string_view strv, char delim, bool removeWhiteSpace, std::vector<std::string_view> *output) {
size_t first = 0;
while (first < strv.size()) {
const auto second = strv.find(delim, first);
if (first != second) {
std::string_view line = strv.substr(first, second - first);
_dbg_assert_(!line.empty());
if (line.back() == '\r') {
line.remove_suffix(1);
}
if (removeWhiteSpace) {
line = StripSpaces(line);
}
if (!line.empty()) {
output->emplace_back(line);
}
}
if (second == std::string_view::npos)
break;
first = second + 1;
}
}
void GameDB::LoadIfNeeded() {
if (loaded_) {
// Already loaded
return;
}
loaded_ = true;
size_t size;
uint8_t *data = g_VFS.ReadFile(g_gameDBFilename, &size);
if (!data)
return;
contents_ = std::string((const char *)data, size);
delete[] data;
const size_t RESERVE_COUNT = 2820; // ~ known current line count
// Split the string into views of each line, keeping the original.
std::vector<std::string_view> lines;
lines.reserve(RESERVE_COUNT);
SplitSV(contents_, '\n', false, &lines);
SplitCSVLine(lines[0], columns_);
const size_t titleColumn = GetColumnIndex("Title");
const size_t foreignTitleColumn = GetColumnIndex("Foreign Title");
const size_t serialColumn = GetColumnIndex("Serial");
const size_t crcColumn = GetColumnIndex("CRC32");
const size_t sizeColumn = GetColumnIndex("Size");
std::vector<std::string_view> items;
items.reserve(8);
lines_.reserve(RESERVE_COUNT);
for (size_t i = 1; i < lines.size(); i++) {
auto &lineString = lines[i];
SplitCSVLine(lineString, items);
if (items.size() != columns_.size()) {
// Bad line
ERROR_LOG(Log::System, "Bad line in CSV file: %s", std::string(lineString).c_str());
continue;
}
Line line;
line.title = items[titleColumn];
line.foreignTitle = items[foreignTitleColumn];
SplitSV(items[serialColumn], ',', true, &line.serials);
line.crc = items[crcColumn];
line.size = items[sizeColumn];
lines_.push_back(line);
}
}
size_t GameDB::GetColumnIndex(std::string_view name) const {
for (size_t i = 0; i < columns_.size(); i++) {
if (name == columns_[i]) {
return i;
}
}
return (size_t)-1;
}
// Our IDs are ULUS12345, while the DB has them in some different forms, with a space or dash as separator.
// TODO: report to redump
static bool IDMatches(std::string_view id, std::string_view dbId) {
if (id.size() < 9 || dbId.size() < 10)
return false;
if (id.substr(0, 4) != dbId.substr(0, 4))
return false;
if (id.substr(4, 5) != dbId.substr(5, 5))
return false;
return true;
}
bool GameDB::GetGameInfos(std::string_view id, std::vector<GameDBInfo> *infos) {
if (id.size() < 9) {
// Not a game.
return false;
}
std::lock_guard<std::mutex> guard(loadMutex_);
LoadIfNeeded();
for (auto &line : lines_) {
for (auto &serial : line.serials) {
// Ignore version and stuff for now
if (IDMatches(id, serial)) {
GameDBInfo info;
// zero-terminate before sscanf
std::string crc(line.crc);
if (1 != sscanf(crc.c_str(), "%08x", &info.crc)) {
continue;
}
std::string size(line.size);
if (1 != sscanf(size.c_str(), "%llu", (long long *)&info.size)) {
continue;
}
info.title = line.title;
info.foreignTitle = line.foreignTitle;
infos->push_back(info);
}
}
}
return !infos->empty();
}