|
|
@ -6,13 +6,85 @@ |
|
|
|
|
|
|
|
|
#include <atomic> |
|
|
#include <atomic> |
|
|
|
|
|
|
|
|
|
|
|
#include <QImage> |
|
|
#include <QRunnable> |
|
|
#include <QRunnable> |
|
|
#include <QStandardItem> |
|
|
#include <QStandardItem> |
|
|
#include <QString> |
|
|
#include <QString> |
|
|
|
|
|
|
|
|
#include "citra_qt/util/util.h" |
|
|
#include "citra_qt/util/util.h" |
|
|
#include "common/string_util.h" |
|
|
#include "common/string_util.h" |
|
|
|
|
|
#include "common/color.h" |
|
|
|
|
|
|
|
|
|
|
|
#include "core/loader/loader.h" |
|
|
|
|
|
|
|
|
|
|
|
#include "video_core/utils.h" |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Tests if data is a valid SMDH by its length and magic number. |
|
|
|
|
|
* @param smdh_data data buffer to test |
|
|
|
|
|
* @return bool test result |
|
|
|
|
|
*/ |
|
|
|
|
|
static bool IsValidSMDH(const std::vector<u8>& smdh_data) { |
|
|
|
|
|
if (smdh_data.size() < sizeof(Loader::SMDH)) |
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
|
|
u32 magic; |
|
|
|
|
|
memcpy(&magic, smdh_data.data(), 4); |
|
|
|
|
|
|
|
|
|
|
|
return Loader::MakeMagic('S', 'M', 'D', 'H') == magic; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Gets game icon from SMDH |
|
|
|
|
|
* @param sdmh SMDH data |
|
|
|
|
|
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) |
|
|
|
|
|
* @return QPixmap game icon |
|
|
|
|
|
*/ |
|
|
|
|
|
static QPixmap GetIconFromSMDH(const Loader::SMDH& smdh, bool large) { |
|
|
|
|
|
u32 size; |
|
|
|
|
|
const u8* icon_data; |
|
|
|
|
|
|
|
|
|
|
|
if (large) { |
|
|
|
|
|
size = 48; |
|
|
|
|
|
icon_data = smdh.large_icon.data(); |
|
|
|
|
|
} else { |
|
|
|
|
|
size = 24; |
|
|
|
|
|
icon_data = smdh.small_icon.data(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
QImage icon(size, size, QImage::Format::Format_RGB888); |
|
|
|
|
|
for (u32 x = 0; x < size; ++x) { |
|
|
|
|
|
for (u32 y = 0; y < size; ++y) { |
|
|
|
|
|
u32 coarse_y = y & ~7; |
|
|
|
|
|
auto v = Color::DecodeRGB565( |
|
|
|
|
|
icon_data + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * size * 2); |
|
|
|
|
|
icon.setPixel(x, y, qRgb(v.r(), v.g(), v.b())); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return QPixmap::fromImage(icon); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Gets the default icon (for games without valid SMDH) |
|
|
|
|
|
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) |
|
|
|
|
|
* @return QPixmap default icon |
|
|
|
|
|
*/ |
|
|
|
|
|
static QPixmap GetDefaultIcon(bool large) { |
|
|
|
|
|
int size = large ? 48 : 24; |
|
|
|
|
|
QPixmap icon(size, size); |
|
|
|
|
|
icon.fill(Qt::transparent); |
|
|
|
|
|
return icon; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Gets the short game title fromn SMDH |
|
|
|
|
|
* @param sdmh SMDH data |
|
|
|
|
|
* @param language title language |
|
|
|
|
|
* @return QString short title |
|
|
|
|
|
*/ |
|
|
|
|
|
static QString GetShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) { |
|
|
|
|
|
return QString::fromUtf16(smdh.titles[static_cast<int>(language)].short_title.data()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
class GameListItem : public QStandardItem { |
|
|
class GameListItem : public QStandardItem { |
|
|
|
|
|
|
|
|
@ -27,29 +99,43 @@ public: |
|
|
* A specialization of GameListItem for path values. |
|
|
* A specialization of GameListItem for path values. |
|
|
* This class ensures that for every full path value it holds, a correct string representation |
|
|
* This class ensures that for every full path value it holds, a correct string representation |
|
|
* of just the filename (with no extension) will be displayed to the user. |
|
|
* of just the filename (with no extension) will be displayed to the user. |
|
|
|
|
|
* If this class recieves valid SMDH data, it will also display game icons and titles. |
|
|
*/ |
|
|
*/ |
|
|
class GameListItemPath : public GameListItem { |
|
|
class GameListItemPath : public GameListItem { |
|
|
|
|
|
|
|
|
public: |
|
|
public: |
|
|
static const int FullPathRole = Qt::UserRole + 1; |
|
|
static const int FullPathRole = Qt::UserRole + 1; |
|
|
|
|
|
static const int TitleRole = Qt::UserRole + 2; |
|
|
|
|
|
|
|
|
GameListItemPath(): GameListItem() {} |
|
|
GameListItemPath(): GameListItem() {} |
|
|
GameListItemPath(const QString& game_path): GameListItem() |
|
|
|
|
|
|
|
|
GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data): GameListItem() |
|
|
{ |
|
|
{ |
|
|
setData(game_path, FullPathRole); |
|
|
setData(game_path, FullPathRole); |
|
|
|
|
|
|
|
|
|
|
|
if (!IsValidSMDH(smdh_data)) { |
|
|
|
|
|
// SMDH is not valid, set a default icon |
|
|
|
|
|
setData(GetDefaultIcon(true), Qt::DecorationRole); |
|
|
|
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void setData(const QVariant& value, int role) override |
|
|
|
|
|
{ |
|
|
|
|
|
// By specializing setData for FullPathRole, we can ensure that the two string |
|
|
|
|
|
// representations of the data are always accurate and in the correct format. |
|
|
|
|
|
if (role == FullPathRole) { |
|
|
|
|
|
|
|
|
Loader::SMDH smdh; |
|
|
|
|
|
memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH)); |
|
|
|
|
|
|
|
|
|
|
|
// Get icon from SMDH |
|
|
|
|
|
setData(GetIconFromSMDH(smdh, true), Qt::DecorationRole); |
|
|
|
|
|
|
|
|
|
|
|
// Get title form SMDH |
|
|
|
|
|
setData(GetShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
QVariant data(int role) const override { |
|
|
|
|
|
if (role == Qt::DisplayRole) { |
|
|
std::string filename; |
|
|
std::string filename; |
|
|
Common::SplitPath(value.toString().toStdString(), nullptr, &filename, nullptr); |
|
|
|
|
|
GameListItem::setData(QString::fromStdString(filename), Qt::DisplayRole); |
|
|
|
|
|
GameListItem::setData(value, FullPathRole); |
|
|
|
|
|
|
|
|
Common::SplitPath(data(FullPathRole).toString().toStdString(), nullptr, &filename, nullptr); |
|
|
|
|
|
QString title = data(TitleRole).toString(); |
|
|
|
|
|
return QString::fromStdString(filename) + (title.isEmpty() ? "" : "\n " + title); |
|
|
} else { |
|
|
} else { |
|
|
GameListItem::setData(value, role); |
|
|
|
|
|
|
|
|
return GameListItem::data(role); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|