Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Game info screen: Add checks against the Redump database #18377

Merged
merged 5 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Loading