Skip to content

Commit

Permalink
Merge pull request #18377 from hrydgard/redump-verification
Browse files Browse the repository at this point in the history
Game info screen: Add checks against the Redump database
  • Loading branch information
hrydgard committed Oct 26, 2023
2 parents 42dd373 + f3c2f22 commit a9d2ff2
Show file tree
Hide file tree
Showing 61 changed files with 3,406 additions and 46 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Expand Up @@ -2265,6 +2265,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/Util/AudioFormat.h
Core/Util/GameManager.cpp
Core/Util/GameManager.h
Core/Util/GameDB.cpp
Core/Util/GameDB.h
Core/Util/PortManager.cpp
Core/Util/PortManager.h
Core/Util/BlockAllocator.cpp
Expand Down
34 changes: 14 additions & 20 deletions Common/StringUtils.h
Expand Up @@ -37,18 +37,10 @@ std::string IndentString(const std::string &str, const std::string &sep, bool sk

// Other simple string utilities.

// Optimized for string constants.
inline bool startsWith(const std::string &str, const char *key) {
size_t keyLen = strlen(key);
if (str.size() < keyLen)
inline bool startsWith(std::string_view str, std::string_view key) {
if (str.size() < key.size())
return false;
return !memcmp(str.data(), key, keyLen);
}

inline bool startsWith(const std::string &str, const std::string &what) {
if (str.size() < what.size())
return false;
return str.substr(0, what.size()) == what;
return !memcmp(str.data(), key.data(), key.size());
}

inline bool endsWith(const std::string &str, const std::string &what) {
Expand All @@ -58,21 +50,23 @@ inline bool endsWith(const std::string &str, const std::string &what) {
}

// Only use on strings where you're only concerned about ASCII.
inline bool startsWithNoCase(const std::string &str, const std::string &what) {
if (str.size() < what.size())
inline bool startsWithNoCase(std::string_view str, std::string_view key) {
if (str.size() < key.size())
return false;
return strncasecmp(str.c_str(), what.c_str(), what.size()) == 0;
return strncasecmp(str.data(), key.data(), key.size()) == 0;
}

inline bool endsWithNoCase(const std::string &str, const std::string &what) {
if (str.size() < what.size())
inline bool endsWithNoCase(std::string_view str, std::string_view key) {
if (str.size() < key.size())
return false;
const size_t offset = str.size() - what.size();
return strncasecmp(str.c_str() + offset, what.c_str(), what.size()) == 0;
const size_t offset = str.size() - key.size();
return strncasecmp(str.data() + offset, key.data(), key.size()) == 0;
}

inline bool equalsNoCase(const std::string &str, const char *what) {
return strcasecmp(str.c_str(), what) == 0;
inline bool equalsNoCase(std::string_view str, std::string_view key) {
if (str.size() != key.size())
return false;
return strncasecmp(str.data(), key.data(), key.size()) == 0;
}

void DataToHexString(const uint8_t *data, size_t size, std::string *output);
Expand Down
2 changes: 2 additions & 0 deletions Core/Core.vcxproj
Expand Up @@ -1076,6 +1076,7 @@
<ClCompile Include="Util\AudioFormat.cpp" />
<ClCompile Include="Util\BlockAllocator.cpp" />
<ClCompile Include="Util\DisArm64.cpp" />
<ClCompile Include="Util\GameDB.cpp" />
<ClCompile Include="Util\GameManager.cpp" />
<ClCompile Include="Util\PortManager.cpp" />
<ClCompile Include="Util\PPGeDraw.cpp" />
Expand Down Expand Up @@ -1444,6 +1445,7 @@
<ClInclude Include="Util\AudioFormat.h" />
<ClInclude Include="Util\BlockAllocator.h" />
<ClInclude Include="Util\DisArm64.h" />
<ClInclude Include="Util\GameDB.h" />
<ClInclude Include="Util\GameManager.h" />
<ClInclude Include="Util\PortManager.h" />
<ClInclude Include="Util\PPGeDraw.h" />
Expand Down
6 changes: 6 additions & 0 deletions Core/Core.vcxproj.filters
Expand Up @@ -1297,6 +1297,9 @@
<ClCompile Include="MIPS\ARM64\Arm64IRCompFPU.cpp">
<Filter>MIPS\ARM64</Filter>
</ClCompile>
<ClCompile Include="Util\GameDB.cpp">
<Filter>Util</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ELF\ElfReader.h">
Expand Down Expand Up @@ -2070,6 +2073,9 @@
<ClInclude Include="MIPS\ARM64\Arm64IRRegCache.h">
<Filter>MIPS\ARM64</Filter>
</ClInclude>
<ClInclude Include="Util\GameDB.h">
<Filter>Util</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE.TXT" />
Expand Down
23 changes: 13 additions & 10 deletions Core/FileSystems/BlockDevices.h
Expand Up @@ -45,8 +45,11 @@ class BlockDevice {
return true;
}
int GetBlockSize() const { return 2048;} // forced, it cannot be changed by subclasses
virtual u32 GetNumBlocks() = 0;
virtual bool IsDisc() = 0;
virtual u32 GetNumBlocks() const = 0;
u64 GetUncompressedSize() const {
return (u64)GetNumBlocks() * (u64)GetBlockSize();
}
virtual bool IsDisc() const = 0;

u32 CalculateCRC(volatile bool *cancel = nullptr);
void NotifyReadError();
Expand All @@ -62,8 +65,8 @@ class CISOFileBlockDevice : public BlockDevice {
~CISOFileBlockDevice();
bool ReadBlock(int blockNumber, u8 *outPtr, bool uncached = false) override;
bool ReadBlocks(u32 minBlock, int count, u8 *outPtr) override;
u32 GetNumBlocks() override { return numBlocks; }
bool IsDisc() override { return true; }
u32 GetNumBlocks() const override { return numBlocks; }
bool IsDisc() const override { return true; }

private:
u32 *index;
Expand All @@ -85,8 +88,8 @@ class FileBlockDevice : public BlockDevice {
~FileBlockDevice();
bool ReadBlock(int blockNumber, u8 *outPtr, bool uncached = false) override;
bool ReadBlocks(u32 minBlock, int count, u8 *outPtr) override;
u32 GetNumBlocks() override {return (u32)(filesize_ / GetBlockSize());}
bool IsDisc() override { return true; }
u32 GetNumBlocks() const override {return (u32)(filesize_ / GetBlockSize());}
bool IsDisc() const override { return true; }

private:
u64 filesize_;
Expand All @@ -109,8 +112,8 @@ class NPDRMDemoBlockDevice : public BlockDevice {
~NPDRMDemoBlockDevice();

bool ReadBlock(int blockNumber, u8 *outPtr, bool uncached = false) override;
u32 GetNumBlocks() override {return (u32)lbaSize;}
bool IsDisc() override { return false; }
u32 GetNumBlocks() const override {return (u32)lbaSize;}
bool IsDisc() const override { return false; }

private:
static std::mutex mutex_;
Expand Down Expand Up @@ -138,8 +141,8 @@ class CHDFileBlockDevice : public BlockDevice {
~CHDFileBlockDevice();
bool ReadBlock(int blockNumber, u8 *outPtr, bool uncached = false) override;
bool ReadBlocks(u32 minBlock, int count, u8 *outPtr) override;
u32 GetNumBlocks() override { return numBlocks; }
bool IsDisc() override { return true; }
u32 GetNumBlocks() const override { return numBlocks; }
bool IsDisc() const override { return true; }

private:
std::unique_ptr<CHDImpl> impl_;
Expand Down
140 changes: 140 additions & 0 deletions Core/Util/GameDB.cpp
@@ -0,0 +1,140 @@
#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 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 std::vector<std::string_view> 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);
if (line.back() == '\r') {
line = strv.substr(first, second - first - 1);
}
if (removeWhiteSpace) {
line = StripSpaces(line);
}
output.emplace_back(line);
}
if (second == std::string_view::npos)
break;
first = second + 1;
}
return output;
}

bool GameDB::LoadFromVFS(VFSInterface &vfs, const char *filename) {
size_t size;
uint8_t *data = vfs.ReadFile(filename, &size);
if (!data)
return false;
contents_ = std::string((const char *)data, size);
delete[] data;

// Split the string into views of each line, keeping the original.
std::vector<std::string_view> lines = splitSV(contents_, '\n', false);

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;
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(SYSTEM, "Bad line in CSV file: %s", std::string(lineString).c_str());
continue;
}

Line line;
line.title = items[titleColumn];
line.foreignTitle = items[foreignTitleColumn];
line.serials = splitSV(items[serialColumn], ',', true);
line.crc = items[crcColumn];
line.size = items[sizeColumn];
lines_.push_back(line);
}
return true;
}

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;
}

for (auto &line : lines_) {
for (auto serial : line.serials) {
// Ignore version and stuff for now
if (IDMatches(id, serial)) {
GameDBInfo info;
sscanf(line.crc.data(), "%08x", &info.crc);
sscanf(line.size.data(), "%llu", &info.size);
info.title = line.title;
info.foreignTitle = line.foreignTitle;
infos->push_back(info);
}
}
}
return !infos->empty();
}
41 changes: 41 additions & 0 deletions Core/Util/GameDB.h
@@ -0,0 +1,41 @@
#pragma once

#include <cstdint>
#include <string>
#include <vector>
#include <string_view>

class VFSInterface;

// Serial/id doesn't need including here since we look up by it.
struct GameDBInfo {
std::string title;
std::string foreignTitle;
uint32_t crc;
uint64_t size;
};

class GameDB {
public:
bool LoadFromVFS(VFSInterface &vfs, const char *filename);
bool GetGameInfos(std::string_view id, std::vector<GameDBInfo> *infos);

private:
size_t GetColumnIndex(std::string_view name) const;

struct Line {
// The exact same ISO can have multiple serials.
std::vector<std::string_view> serials;
// The below fields should match GameDBInfo.
std::string_view title;
std::string_view foreignTitle;
std::string_view size;
std::string_view crc;
};

std::string contents_;
std::vector<Line> lines_;
std::vector<std::string_view> columns_;
};

extern GameDB g_gameDB;
23 changes: 21 additions & 2 deletions UI/GameInfoCache.cpp
Expand Up @@ -111,7 +111,7 @@ bool GameInfo::Delete() {
}
}

u64 GameInfo::GetGameSizeInBytes() {
u64 GameInfo::GetGameSizeOnDiskInBytes() {
switch (fileType) {
case IdentifiedFileType::PSP_PBP_DIRECTORY:
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
Expand All @@ -122,6 +122,22 @@ u64 GameInfo::GetGameSizeInBytes() {
}
}

u64 GameInfo::GetGameSizeUncompressedInBytes() {
switch (fileType) {
case IdentifiedFileType::PSP_PBP_DIRECTORY:
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
return File::ComputeRecursiveDirectorySize(ResolvePBPDirectory(filePath_));

default:
{
BlockDevice *blockDevice = constructBlockDevice(GetFileLoader().get());
u64 size = blockDevice->GetUncompressedSize();
delete blockDevice;
return size;
}
}
}

// Not too meaningful if the object itself is a savedata directory...
std::vector<Path> GameInfo::GetSaveDataDirectories() {
Path memc = GetSysDirectory(DIRECTORY_SAVEDATA);
Expand Down Expand Up @@ -659,10 +675,13 @@ class GameInfoWorkItem : public Task {

if (info_->wantFlags & GAMEINFO_WANTSIZE) {
std::lock_guard<std::mutex> lock(info_->lock);
info_->gameSize = info_->GetGameSizeInBytes();
info_->gameSizeOnDisk = info_->GetGameSizeOnDiskInBytes();
info_->saveDataSize = info_->GetSaveDataSizeInBytes();
info_->installDataSize = info_->GetInstallDataSizeInBytes();
}
if (info_->wantFlags & GAMEINFO_WANTUNCOMPRESSEDSIZE) {
info_->gameSizeUncompressed = info_->GetGameSizeUncompressedInBytes();
}

// INFO_LOG(SYSTEM, "Completed writing info for %s", info_->GetTitle().c_str());
}
Expand Down

0 comments on commit a9d2ff2

Please sign in to comment.