@@ -20,10 +20,10 @@
#include "Core/GeckoCodeConfig.h"
#include "DolphinQt2/Config/CheatCodeEditor.h"
#include "DolphinQt2/Config/CheatWarningWidget.h"
#include "DolphinQt2/GameList/GameFile.h"
#include "UICommon/GameFile.h"

GeckoCodeWidget::GeckoCodeWidget(const GameFile& game)
: m_game(game), m_game_id(game.GetGameID().toStdString()), m_game_revision(game.GetRevision())
GeckoCodeWidget::GeckoCodeWidget(const UICommon::GameFile& game)
: m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision())
{
CreateWidgets();
ConnectWidgets();
@@ -13,18 +13,22 @@
#include "Core/GeckoCode.h"

class CheatWarningWidget;
class GameFile;
class QLabel;
class QListWidget;
class QListWidgetItem;
class QTextEdit;
class QPushButton;

namespace UICommon
{
class GameFile;
}

class GeckoCodeWidget : public QWidget
{
Q_OBJECT
public:
explicit GeckoCodeWidget(const GameFile& game);
explicit GeckoCodeWidget(const UICommon::GameFile& game);

signals:
void OpenGeneralSettings();
@@ -43,7 +47,7 @@ class GeckoCodeWidget : public QWidget
void DownloadCodes();
void SaveCodes();

const GameFile& m_game;
const UICommon::GameFile& m_game;
std::string m_game_id;
u16 m_game_revision;

@@ -16,8 +16,10 @@
#include "DiscIO/Blob.h"
#include "DiscIO/Enums.h"
#include "DolphinQt2/Config/InfoWidget.h"
#include "DolphinQt2/QtUtils/ImageConverter.h"
#include "UICommon/UICommon.h"

InfoWidget::InfoWidget(const GameFile& game) : m_game(game)
InfoWidget::InfoWidget(const UICommon::GameFile& game) : m_game(game)
{
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(CreateISODetails());
@@ -32,17 +34,19 @@ QGroupBox* InfoWidget::CreateISODetails()

QLineEdit* file_path = CreateValueDisplay(m_game.GetFilePath());
QLineEdit* internal_name = CreateValueDisplay(m_game.GetInternalName());
QString game_id_string = m_game.GetGameID();

QString game_id_string = QString::fromStdString(m_game.GetGameID());
if (const u64 title_id = m_game.GetTitleID())
game_id_string += QStringLiteral(" (%1)").arg(title_id, 16, 16, QLatin1Char('0'));
QLineEdit* game_id = CreateValueDisplay(game_id_string);
QLineEdit* country = CreateValueDisplay(m_game.GetCountry());

QLineEdit* country = CreateValueDisplay(DiscIO::GetName(m_game.GetCountry(), true));
QLineEdit* maker = CreateValueDisplay(m_game.GetMaker());
QLineEdit* maker_id = CreateValueDisplay(QStringLiteral("0x") + m_game.GetMakerID());
QLineEdit* maker_id = CreateValueDisplay("0x" + m_game.GetMakerID());
QLineEdit* disc_number = CreateValueDisplay(QString::number(m_game.GetDiscNumber()));
QLineEdit* revision = CreateValueDisplay(QString::number(m_game.GetRevision()));
QLineEdit* apploader_date = CreateValueDisplay(m_game.GetApploaderDate());
QLineEdit* iso_size = CreateValueDisplay(FormatSize(m_game.GetFileSize()));
QLineEdit* iso_size = CreateValueDisplay(UICommon::FormatSize(m_game.GetFileSize()));
QWidget* checksum = CreateChecksumComputer();

layout->addRow(tr("File Path:"), file_path);
@@ -75,35 +79,34 @@ QGroupBox* InfoWidget::CreateBannerDetails()
CreateLanguageSelector();

layout->addRow(tr("Show Language:"), m_language_selector);
if (m_game.GetPlatformID() == DiscIO::Platform::GAMECUBE_DISC)
if (m_game.GetPlatform() == DiscIO::Platform::GAMECUBE_DISC)
{
layout->addRow(tr("Short Name:"), m_short_name);
layout->addRow(tr("Short Maker:"), m_short_maker);
layout->addRow(tr("Long Name:"), m_long_name);
layout->addRow(tr("Long Maker:"), m_long_maker);
layout->addRow(tr("Description:"), m_description);
}
else if (DiscIO::IsWii(m_game.GetPlatformID()))
else if (DiscIO::IsWii(m_game.GetPlatform()))
{
layout->addRow(tr("Name:"), m_long_name);
}

if (!m_game.GetBanner().isNull())
{
layout->addRow(tr("Banner:"), CreateBannerGraphic());
}
QPixmap banner = ToQPixmap(m_game.GetBannerImage());
if (!banner.isNull())
layout->addRow(tr("Banner:"), CreateBannerGraphic(banner));

group->setLayout(layout);
return group;
}

QWidget* InfoWidget::CreateBannerGraphic()
QWidget* InfoWidget::CreateBannerGraphic(const QPixmap& image)
{
QWidget* widget = new QWidget();
QHBoxLayout* layout = new QHBoxLayout();

QLabel* banner = new QLabel();
banner->setPixmap(m_game.GetBanner());
banner->setPixmap(image);
QPushButton* save = new QPushButton(tr("Save as..."));
connect(save, &QPushButton::clicked, this, &InfoWidget::SaveBanner);

@@ -117,7 +120,7 @@ void InfoWidget::SaveBanner()
{
QString path = QFileDialog::getSaveFileName(this, tr("Select a File"), QDir::currentPath(),
tr("PNG image file (*.png);; All Files (*)"));
m_game.GetBanner().save(path, "PNG");
ToQPixmap(m_game.GetBannerImage()).save(path, "PNG");
}

QLineEdit* InfoWidget::CreateValueDisplay(const QString& value)
@@ -128,14 +131,18 @@ QLineEdit* InfoWidget::CreateValueDisplay(const QString& value)
return value_display;
}

QLineEdit* InfoWidget::CreateValueDisplay(const std::string& value)
{
return CreateValueDisplay(QString::fromStdString(value));
}

void InfoWidget::CreateLanguageSelector()
{
m_language_selector = new QComboBox();
QList<DiscIO::Language> languages = m_game.GetAvailableLanguages();
for (int i = 0; i < languages.count(); i++)
for (DiscIO::Language language : m_game.GetLanguages())
{
DiscIO::Language language = languages.at(i);
m_language_selector->addItem(m_game.GetLanguage(language), static_cast<int>(language));
m_language_selector->addItem(QString::fromStdString(DiscIO::GetName(language, true)),
static_cast<int>(language));
}
if (m_language_selector->count() == 1)
m_language_selector->setDisabled(true);
@@ -149,11 +156,11 @@ void InfoWidget::ChangeLanguage()
{
DiscIO::Language language =
static_cast<DiscIO::Language>(m_language_selector->currentData().toInt());
m_short_name->setText(m_game.GetShortName(language));
m_short_maker->setText(m_game.GetShortMaker(language));
m_long_name->setText(m_game.GetLongName(language));
m_long_maker->setText(m_game.GetLongMaker(language));
m_description->setText(m_game.GetDescription(language));
m_short_name->setText(QString::fromStdString(m_game.GetShortName(language)));
m_short_maker->setText(QString::fromStdString(m_game.GetShortMaker(language)));
m_long_name->setText(QString::fromStdString(m_game.GetLongName(language)));
m_long_maker->setText(QString::fromStdString(m_game.GetLongMaker(language)));
m_description->setText(QString::fromStdString(m_game.GetDescription(language)));
}

QWidget* InfoWidget::CreateChecksumComputer()
@@ -176,8 +183,7 @@ void InfoWidget::ComputeChecksum()
{
QCryptographicHash hash(QCryptographicHash::Md5);
hash.reset();
std::unique_ptr<DiscIO::BlobReader> file(
DiscIO::CreateBlobReader(m_game.GetFilePath().toStdString()));
std::unique_ptr<DiscIO::BlobReader> file(DiscIO::CreateBlobReader(m_game.GetFilePath()));
std::vector<u8> file_data(8 * 1080 * 1080); // read 1MB at a time
u64 game_size = file->GetDataSize();
u64 read_offset = 0;
@@ -4,20 +4,23 @@

#pragma once

#include <string>

#include <QWidget>

#include "DolphinQt2/GameList/GameFile.h"
#include "UICommon/GameFile.h"

class QComboBox;
class QGroupBox;
class QTextEdit;
class QLineEdit;
class QPixmap;
class QTextEdit;

class InfoWidget final : public QWidget
{
Q_OBJECT
public:
explicit InfoWidget(const GameFile& game);
explicit InfoWidget(const UICommon::GameFile& game);

private:
void ComputeChecksum();
@@ -26,13 +29,13 @@ class InfoWidget final : public QWidget

QGroupBox* CreateBannerDetails();
QGroupBox* CreateISODetails();
QLineEdit* CreateValueDisplay() { return CreateValueDisplay(QStringLiteral("")); };
QLineEdit* CreateValueDisplay(const QString& value);
QLineEdit* CreateValueDisplay(const std::string& value = "");
QWidget* CreateChecksumComputer();
void CreateLanguageSelector();
QWidget* CreateBannerGraphic();
QWidget* CreateBannerGraphic(const QPixmap& image);

GameFile m_game;
UICommon::GameFile m_game;
QLineEdit* m_checksum_result;
QComboBox* m_language_selector;
QLineEdit* m_long_name;
@@ -10,7 +10,6 @@
#include <QWidget>

#include "Core/PatchEngine.h"
#include "DolphinQt2/GameList/GameFile.h"

class QDialogButtonBox;
class QGroupBox;
@@ -14,8 +14,8 @@
#include "Core/ConfigManager.h"
#include "DolphinQt2/Config/NewPatchDialog.h"

PatchesWidget::PatchesWidget(const GameFile& game)
: m_game(game), m_game_id(game.GetGameID().toStdString()), m_game_revision(game.GetRevision())
PatchesWidget::PatchesWidget(const UICommon::GameFile& game)
: m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision())
{
IniFile game_ini_local;
game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
@@ -10,7 +10,7 @@
#include <QWidget>

#include "Core/PatchEngine.h"
#include "DolphinQt2/GameList/GameFile.h"
#include "UICommon/GameFile.h"

class QListWidget;
class QListWidgetItem;
@@ -19,7 +19,7 @@ class QPushButton;
class PatchesWidget : public QWidget
{
public:
explicit PatchesWidget(const GameFile& game);
explicit PatchesWidget(const UICommon::GameFile& game);

private:
void CreateWidgets();
@@ -39,7 +39,7 @@ class PatchesWidget : public QWidget
QPushButton* m_remove_button;

std::vector<PatchEngine::Patch> m_patches;
const GameFile& m_game;
const UICommon::GameFile& m_game;
std::string m_game_id;
u16 m_game_revision;
};
@@ -13,11 +13,15 @@
#include "DolphinQt2/Config/InfoWidget.h"
#include "DolphinQt2/Config/PatchesWidget.h"
#include "DolphinQt2/Config/PropertiesDialog.h"
#include "UICommon/GameFile.h"

PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDialog(parent)
PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& game)
: QDialog(parent)
{
setWindowTitle(
QStringLiteral("%1: %2 - %3").arg(game.GetFileName(), game.GetGameID(), game.GetLongName()));
setWindowTitle(QStringLiteral("%1: %2 - %3")
.arg(QString::fromStdString(game.GetFileName()),
QString::fromStdString(game.GetGameID()),
QString::fromStdString(game.GetLongName())));
QVBoxLayout* layout = new QVBoxLayout();

QTabWidget* tab_widget = new QTabWidget(this);
@@ -39,7 +43,7 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDia
tab_widget->addTab(gecko, tr("Gecko Codes"));
tab_widget->addTab(info, tr("Info"));

if (DiscIO::IsDisc(game.GetPlatformID()))
if (DiscIO::IsDisc(game.GetPlatform()))
{
FilesystemWidget* filesystem = new FilesystemWidget(game);
tab_widget->addTab(filesystem, tr("Filesystem"));
@@ -6,13 +6,16 @@

#include <QDialog>

#include "DolphinQt2/GameList/GameFile.h"
namespace UICommon
{
class GameFile;
}

class PropertiesDialog final : public QDialog
{
Q_OBJECT
public:
explicit PropertiesDialog(QWidget* parent, const GameFile& game);
explicit PropertiesDialog(QWidget* parent, const UICommon::GameFile& game);

signals:
void OpenGeneralSettings();
@@ -97,7 +97,6 @@
<QtMoc Include="Debugger\NewBreakpointDialog.h" />
<QtMoc Include="Debugger\RegisterWidget.h" />
<QtMoc Include="Debugger\WatchWidget.h" />
<QtMoc Include="GameList\GameFile.h" />
<QtMoc Include="GameList\GameList.h" />
<QtMoc Include="GameList\GameListModel.h" />
<QtMoc Include="GameList\GameTracker.h" />
@@ -250,8 +249,6 @@
<ClCompile Include="Debugger\RegisterWidget.cpp" />
<ClCompile Include="Debugger\WatchWidget.cpp" />
<ClCompile Include="GCMemcardManager.cpp" />
<ClCompile Include="GameList\GameFile.cpp" />
<ClCompile Include="GameList\GameFileCache.cpp" />
<ClCompile Include="GameList\GameList.cpp" />
<ClCompile Include="GameList\GameListModel.cpp" />
<ClCompile Include="GameList\GameTracker.cpp" />
@@ -271,6 +268,7 @@
<ClCompile Include="NetPlay\PadMappingDialog.cpp" />
<ClCompile Include="QtUtils\DoubleClickEventFilter.cpp" />
<ClCompile Include="QtUtils\ElidedButton.cpp" />
<ClCompile Include="QtUtils\ImageConverter.cpp" />
<ClCompile Include="QtUtils\ListTabWidget.cpp" />
<ClCompile Include="QtUtils\WindowActivationEventFilter.cpp" />
<ClCompile Include="QtUtils\AspectRatioWidget.cpp" />
@@ -311,9 +309,9 @@
<ClInclude Include="Config\Mapping\WiimoteEmuMotionControl.h" />
<ClInclude Include="Config\RegisterColumn.h" />
<ClInclude Include="Config\RegisterWidget.h" />
<ClInclude Include="GameList\GameFileCache.h" />
<ClInclude Include="QtUtils\BlockUserInputFilter.h" />
<ClInclude Include="QtUtils\ElidedButton.h" />
<ClInclude Include="QtUtils\ImageConverter.h" />
<ClInclude Include="QtUtils\ListTabWidget.h" />
<ClInclude Include="Resources.h" />
<ClInclude Include="Settings\GameCubePane.h" />

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

@@ -21,6 +21,8 @@
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/WiiSaveCrypted.h"
#include "Core/WiiUtils.h"
#include "DiscIO/Blob.h"
#include "DiscIO/Enums.h"

@@ -157,11 +159,11 @@ void GameList::MakeGridView()
void GameList::ShowContextMenu(const QPoint&)
{
const auto game = GetSelectedGame();
if (game.isNull())
if (!game)
return;

QMenu* menu = new QMenu(this);
DiscIO::Platform platform = game->GetPlatformID();
DiscIO::Platform platform = game->GetPlatform();
AddAction(menu, tr("&Properties"), this, &GameList::OpenProperties);
AddAction(menu, tr("&Wiki"), this, &GameList::OpenWiki);
menu->addSeparator();
@@ -188,7 +190,7 @@ void GameList::ShowContextMenu(const QPoint&)
if (platform == DiscIO::Platform::WII_DISC)
{
auto* perform_disc_update = AddAction(menu, tr("Perform System Update"), this, [this] {
WiiUpdate::PerformDiscUpdate(GetSelectedGame()->GetFilePath().toStdString(), this);
WiiUpdate::PerformDiscUpdate(GetSelectedGame()->GetFilePath(), this);
});
perform_disc_update->setEnabled(!Core::IsRunning() || !SConfig::GetInstance().bWii);
}
@@ -209,7 +211,8 @@ void GameList::ShowContextMenu(const QPoint&)

connect(&Settings::Instance(), &Settings::EmulationStateChanged, menu, [=](Core::State state) {
wad_install_action->setEnabled(state == Core::State::Uninitialized);
wad_uninstall_action->setEnabled(state == Core::State::Uninitialized && game->IsInstalled());
wad_uninstall_action->setEnabled(state == Core::State::Uninitialized &&
WiiUtils::IsTitleInstalled(game->GetTitleID()));
});

menu->addSeparator();
@@ -228,7 +231,7 @@ void GameList::ShowContextMenu(const QPoint&)
QAction* netplay_host = new QAction(tr("Host with NetPlay"), menu);

connect(netplay_host, &QAction::triggered,
[this, game] { emit NetPlayHost(game->GetUniqueID()); });
[this, game] { emit NetPlayHost(QString::fromStdString(game->GetUniqueIdentifier())); });

connect(&Settings::Instance(), &Settings::EmulationStateChanged, menu, [=](Core::State state) {
netplay_host->setEnabled(state == Core::State::Uninitialized);
@@ -253,7 +256,7 @@ void GameList::ExportWiiSave()
{
QMessageBox result_dialog(this);

const bool success = GetSelectedGame()->ExportWiiSave();
const bool success = CWiiSaveCrypted::ExportWiiSave(GetSelectedGame()->GetTitleID());

result_dialog.setIcon(success ? QMessageBox::Information : QMessageBox::Critical);
result_dialog.setText(success ? tr("Successfully exported save files") :
@@ -263,7 +266,7 @@ void GameList::ExportWiiSave()

void GameList::OpenWiki()
{
QString game_id = GetSelectedGame()->GetGameID();
QString game_id = QString::fromStdString(GetSelectedGame()->GetGameID());
QString url = QStringLiteral("https://wiki.dolphin-emu.org/index.php?title=").append(game_id);
QDesktopServices::openUrl(QUrl(url));
}
@@ -275,7 +278,7 @@ void GameList::CompressISO()

const bool compressed = (file->GetBlobType() == DiscIO::BlobType::GCZ);

if (!compressed && file->GetPlatformID() == DiscIO::Platform::WII_DISC)
if (!compressed && file->GetPlatform() == DiscIO::Platform::WII_DISC)
{
QMessageBox wii_warning(this);
wii_warning.setIcon(QMessageBox::Warning);
@@ -292,9 +295,9 @@ void GameList::CompressISO()
QString dst_path = QFileDialog::getSaveFileName(
this, compressed ? tr("Select where you want to save the decompressed image") :
tr("Select where you want to save the compressed image"),
QFileInfo(GetSelectedGame()->GetFilePath())
QFileInfo(QString::fromStdString(GetSelectedGame()->GetFilePath()))
.dir()
.absoluteFilePath(file->GetGameID())
.absoluteFilePath(QString::fromStdString(file->GetGameID()))
.append(compressed ? QStringLiteral(".gcm") : QStringLiteral(".gcz")),
compressed ? tr("Uncompressed GC/Wii images (*.iso *.gcm)") :
tr("Compressed GC/Wii images (*.gcz)"));
@@ -310,13 +313,13 @@ void GameList::CompressISO()

if (compressed)
{
good = DiscIO::DecompressBlobToFile(original_path.toStdString(), dst_path.toStdString(),
&CompressCB, &progress_dialog);
good = DiscIO::DecompressBlobToFile(original_path, dst_path.toStdString(), &CompressCB,
&progress_dialog);
}
else
{
good = DiscIO::CompressFileToBlob(original_path.toStdString(), dst_path.toStdString(),
file->GetPlatformID() == DiscIO::Platform::WII_DISC ? 1 : 0,
good = DiscIO::CompressFileToBlob(original_path, dst_path.toStdString(),
file->GetPlatform() == DiscIO::Platform::WII_DISC ? 1 : 0,
16384, &CompressCB, &progress_dialog);
}

@@ -336,7 +339,7 @@ void GameList::InstallWAD()
{
QMessageBox result_dialog(this);

const bool success = GetSelectedGame()->Install();
const bool success = WiiUtils::InstallWAD(GetSelectedGame()->GetFilePath());

result_dialog.setIcon(success ? QMessageBox::Information : QMessageBox::Critical);
result_dialog.setText(success ? tr("Successfully installed this title to the NAND.") :
@@ -358,7 +361,7 @@ void GameList::UninstallWAD()

QMessageBox result_dialog(this);

const bool success = GetSelectedGame()->Uninstall();
const bool success = WiiUtils::UninstallTitle(GetSelectedGame()->GetTitleID());

result_dialog.setIcon(success ? QMessageBox::Information : QMessageBox::Critical);
result_dialog.setText(success ? tr("Successfully removed this title from the NAND.") :
@@ -368,24 +371,25 @@ void GameList::UninstallWAD()

void GameList::SetDefaultISO()
{
Settings::Instance().SetDefaultGame(GetSelectedGame()->GetFilePath());
SConfig::GetInstance().m_strDefaultISO = GetSelectedGame()->GetFilePath();
}

void GameList::OpenContainingFolder()
{
QUrl url = QUrl::fromLocalFile(QFileInfo(GetSelectedGame()->GetFilePath()).dir().absolutePath());
QUrl url = QUrl::fromLocalFile(
QFileInfo(QString::fromStdString(GetSelectedGame()->GetFilePath())).dir().absolutePath());
QDesktopServices::openUrl(url);
}

void GameList::OpenSaveFolder()
{
QUrl url = QUrl::fromLocalFile(GetSelectedGame()->GetWiiFSPath());
QUrl url = QUrl::fromLocalFile(QString::fromStdString(GetSelectedGame()->GetWiiFSPath()));
QDesktopServices::openUrl(url);
}

void GameList::DeleteFile()
{
const auto game = GetSelectedGame()->GetFilePath();
const std::string game = GetSelectedGame()->GetFilePath();
QMessageBox confirm_dialog(this);

confirm_dialog.setIcon(QMessageBox::Warning);
@@ -399,7 +403,7 @@ void GameList::DeleteFile()

while (!deletion_successful)
{
deletion_successful = File::Delete(game.toStdString());
deletion_successful = File::Delete(game);

if (deletion_successful)
{
@@ -424,11 +428,10 @@ void GameList::DeleteFile()

void GameList::ChangeDisc()
{
Core::RunAsCPUThread(
[this] { DVDInterface::ChangeDisc(GetSelectedGame()->GetFilePath().toStdString()); });
Core::RunAsCPUThread([this] { DVDInterface::ChangeDisc(GetSelectedGame()->GetFilePath()); });
}

QSharedPointer<GameFile> GameList::GetSelectedGame() const
std::shared_ptr<const UICommon::GameFile> GameList::GetSelectedGame() const
{
QAbstractItemView* view;
QSortFilterProxyModel* proxy;
@@ -4,22 +4,25 @@

#pragma once

#include <memory>

#include <QLabel>
#include <QListView>
#include <QSortFilterProxyModel>
#include <QStackedWidget>
#include <QTableView>

#include "DolphinQt2/GameList/GameFile.h"
#include "DolphinQt2/GameList/GameListModel.h"

#include "UICommon/GameFile.h"

class GameList final : public QStackedWidget
{
Q_OBJECT

public:
explicit GameList(QWidget* parent = nullptr);
QSharedPointer<GameFile> GetSelectedGame() const;
std::shared_ptr<const UICommon::GameFile> GetSelectedGame() const;

void SetListView() { SetPreferredView(true); }
void SetGridView() { SetPreferredView(false); }
@@ -30,7 +33,7 @@ class GameList final : public QStackedWidget
signals:
void GameSelected();
void NetPlayHost(const QString& game_id);
void SelectionChanged(QSharedPointer<GameFile> game_file);
void SelectionChanged(std::shared_ptr<const UICommon::GameFile> game_file);
void OpenGeneralSettings();

private:
@@ -3,17 +3,23 @@
// Refer to the license.txt file included.

#include "DolphinQt2/GameList/GameListModel.h"

#include <QPixmap>

#include "Core/ConfigManager.h"
#include "DiscIO/Enums.h"
#include "DolphinQt2/QtUtils/ImageConverter.h"
#include "DolphinQt2/Resources.h"
#include "DolphinQt2/Settings.h"
#include "UICommon/UICommon.h"

const QSize GAMECUBE_BANNER_SIZE(96, 32);

GameListModel::GameListModel(QObject* parent) : QAbstractTableModel(parent)
{
connect(&m_tracker, &GameTracker::GameLoaded, this, &GameListModel::UpdateGame);
connect(&m_tracker, &GameTracker::GameRemoved, this, &GameListModel::RemoveGame);
connect(&m_tracker, &GameTracker::GameRemoved, this,
[this](const QString& path) { RemoveGame(path.toStdString()); });
connect(&Settings::Instance(), &Settings::PathAdded, &m_tracker, &GameTracker::AddDirectory);
connect(&Settings::Instance(), &Settings::PathRemoved, &m_tracker, &GameTracker::RemoveDirectory);

@@ -26,87 +32,75 @@ GameListModel::GameListModel(QObject* parent) : QAbstractTableModel(parent)
emit layoutAboutToBeChanged();
emit layoutChanged();
});

// TODO: Reload m_title_database when the language changes
}

QVariant GameListModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();

QSharedPointer<GameFile> game = m_games[index.row()];
const UICommon::GameFile& game = *m_games[index.row()];

switch (index.column())
{
case COL_PLATFORM:
if (role == Qt::DecorationRole)
return Resources::GetPlatform(static_cast<int>(game->GetPlatformID()));
return Resources::GetPlatform(static_cast<int>(game.GetPlatform()));
if (role == Qt::InitialSortOrderRole)
return static_cast<int>(game->GetPlatformID());
return static_cast<int>(game.GetPlatform());
break;
case COL_COUNTRY:
if (role == Qt::DecorationRole)
return Resources::GetCountry(static_cast<int>(game->GetCountryID()));
return Resources::GetCountry(static_cast<int>(game.GetCountry()));
if (role == Qt::InitialSortOrderRole)
return static_cast<int>(game->GetCountryID());
return static_cast<int>(game.GetCountry());
break;
case COL_RATING:
if (role == Qt::DecorationRole)
return Resources::GetRating(game->GetRating());
return Resources::GetRating(game.GetEmuState());
if (role == Qt::InitialSortOrderRole)
return game->GetRating();
return game.GetEmuState();
break;
case COL_BANNER:
if (role == Qt::DecorationRole)
{
// GameCube banners are 96x32, but Wii banners are 192x64.
// TODO: use custom banners from rom directory like DolphinWX?
QPixmap banner = game->GetBanner();
QPixmap banner = ToQPixmap(game.GetBannerImage());
if (banner.isNull())
banner = Resources::GetMisc(Resources::BANNER_MISSING);
banner.setDevicePixelRatio(std::max(banner.width() / GAMECUBE_BANNER_SIZE.width(),
banner.height() / GAMECUBE_BANNER_SIZE.height()));

banner.setDevicePixelRatio(
std::max(static_cast<qreal>(banner.width()) / GAMECUBE_BANNER_SIZE.width(),
static_cast<qreal>(banner.height()) / GAMECUBE_BANNER_SIZE.height()));

return banner;
}
break;
case COL_TITLE:
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
{
QString display_name = QString::fromStdString(m_title_database.GetTitleName(
game->GetGameID().toStdString(), game->GetPlatformID() == DiscIO::Platform::WII_WAD ?
Core::TitleDatabase::TitleType::Channel :
Core::TitleDatabase::TitleType::Other));
if (display_name.isEmpty())
display_name = game->GetLongName();

if (display_name.isEmpty())
display_name = game->GetFileName();

return display_name;
}
return QString::fromStdString(game.GetName());
break;
case COL_ID:
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
return game->GetGameID();
return QString::fromStdString(game.GetGameID());
break;
case COL_DESCRIPTION:
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
return game->GetDescription();
return QString::fromStdString(game.GetDescription());
break;
case COL_MAKER:
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
return game->GetMaker();
return QString::fromStdString(game.GetMaker());
break;
case COL_FILE_NAME:
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
return game->GetFileName();
return QString::fromStdString(game.GetFileName());
break;
case COL_SIZE:
if (role == Qt::DisplayRole)
return FormatSize(game->GetFileSize());
return QString::fromStdString(UICommon::FormatSize(game.GetFileSize()));
if (role == Qt::InitialSortOrderRole)
return game->GetFileSize();
return static_cast<quint64>(game.GetFileSize());
break;
}

@@ -154,10 +148,10 @@ int GameListModel::columnCount(const QModelIndex& parent) const

bool GameListModel::ShouldDisplayGameListItem(int index) const
{
QSharedPointer<GameFile> game = m_games[index];
const UICommon::GameFile& game = *m_games[index];

const bool show_platform = [&game] {
switch (game->GetPlatformID())
switch (game.GetPlatform())
{
case DiscIO::Platform::GAMECUBE_DISC:
return SConfig::GetInstance().m_ListGC;
@@ -175,7 +169,7 @@ bool GameListModel::ShouldDisplayGameListItem(int index) const
if (!show_platform)
return false;

switch (game->GetCountryID())
switch (game.GetCountry())
{
case DiscIO::Country::COUNTRY_AUSTRALIA:
return SConfig::GetInstance().m_ListAustralia;
@@ -209,16 +203,14 @@ bool GameListModel::ShouldDisplayGameListItem(int index) const
}
}

QSharedPointer<GameFile> GameListModel::GetGameFile(int index) const
std::shared_ptr<const UICommon::GameFile> GameListModel::GetGameFile(int index) const
{
return m_games[index];
}

void GameListModel::UpdateGame(const QSharedPointer<GameFile>& game)
void GameListModel::UpdateGame(const std::shared_ptr<const UICommon::GameFile>& game)
{
QString path = game->GetFilePath();

int index = FindGame(path);
int index = FindGame(game->GetFilePath());
if (index < 0)
{
beginInsertRows(QModelIndex(), m_games.size(), m_games.size());
@@ -232,7 +224,7 @@ void GameListModel::UpdateGame(const QSharedPointer<GameFile>& game)
}
}

void GameListModel::RemoveGame(const QString& path)
void GameListModel::RemoveGame(const std::string& path)
{
int entry = FindGame(path);
if (entry < 0)
@@ -243,7 +235,7 @@ void GameListModel::RemoveGame(const QString& path)
endRemoveRows();
}

int GameListModel::FindGame(const QString& path) const
int GameListModel::FindGame(const std::string& path) const
{
for (int i = 0; i < m_games.size(); i++)
{
@@ -4,12 +4,14 @@

#pragma once

#include <memory>
#include <string>

#include <QAbstractTableModel>
#include <QString>

#include "Core/TitleDatabase.h"
#include "DolphinQt2/GameList/GameFile.h"
#include "DolphinQt2/GameList/GameTracker.h"
#include "UICommon/GameFile.h"

class GameListModel final : public QAbstractTableModel
{
@@ -25,11 +27,14 @@ class GameListModel final : public QAbstractTableModel
int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex& parent) const override;

QSharedPointer<GameFile> GetGameFile(int index) const;
// Path of the Game at the specified index.
QString GetPath(int index) const { return m_games[index]->GetFilePath(); }
// Unique ID of the Game at the specified index
QString GetUniqueID(int index) const { return m_games[index]->GetUniqueID(); }
std::shared_ptr<const UICommon::GameFile> GetGameFile(int index) const;
// Path of the game at the specified index.
QString GetPath(int index) const { return QString::fromStdString(m_games[index]->GetFilePath()); }
// Unique identifier of the game at the specified index.
QString GetUniqueIdentifier(int index) const
{
return QString::fromStdString(m_games[index]->GetUniqueIdentifier());
}
bool ShouldDisplayGameListItem(int index) const;
enum
{
@@ -46,14 +51,13 @@ class GameListModel final : public QAbstractTableModel
NUM_COLS
};

void UpdateGame(const QSharedPointer<GameFile>& game);
void RemoveGame(const QString& path);
void UpdateGame(const std::shared_ptr<const UICommon::GameFile>& game);
void RemoveGame(const std::string& path);

private:
// Index in m_games, or -1 if it isn't found
int FindGame(const QString& path) const;
int FindGame(const std::string& path) const;

GameTracker m_tracker;
QList<QSharedPointer<GameFile>> m_games;
Core::TitleDatabase m_title_database;
QList<std::shared_ptr<const UICommon::GameFile>> m_games;
};
@@ -7,7 +7,6 @@
#include <QFile>

#include "DiscIO/DirectoryBlob.h"
#include "DolphinQt2/GameList/GameFileCache.h"
#include "DolphinQt2/GameList/GameTracker.h"
#include "DolphinQt2/QtUtils/QueueOnObject.h"
#include "DolphinQt2/Settings.h"
@@ -19,13 +18,15 @@ static const QStringList game_filters{

GameTracker::GameTracker(QObject* parent) : QFileSystemWatcher(parent)
{
qRegisterMetaType<QSharedPointer<GameFile>>();
qRegisterMetaType<std::shared_ptr<const UICommon::GameFile>>();
connect(this, &QFileSystemWatcher::directoryChanged, this, &GameTracker::UpdateDirectory);
connect(this, &QFileSystemWatcher::fileChanged, this, &GameTracker::UpdateFile);

cache.Load();
m_cache.Load();

m_load_thread.Reset([this](const QString& path) { LoadGame(path); });

// TODO: When language changes, reload m_title_database and call m_cache.UpdateAdditionalMetadata
}

void GameTracker::AddDirectory(const QString& dir)
@@ -130,25 +131,12 @@ void GameTracker::UpdateFile(const QString& file)

void GameTracker::LoadGame(const QString& path)
{
if (!DiscIO::ShouldHideFromGameList(path.toStdString()))
const std::string converted_path = path.toStdString();
if (!DiscIO::ShouldHideFromGameList(converted_path))
{
if (cache.IsCached(path))
{
const QDateTime last_modified = QFileInfo(path).lastModified();
auto cached_file = cache.GetFile(path);
if (cached_file.GetLastModified() >= last_modified)
{
emit GameLoaded(QSharedPointer<GameFile>::create(cached_file));
return;
}
}

auto game = QSharedPointer<GameFile>::create(path);
if (game->IsValid())
{
emit GameLoaded(game);
cache.Update(*game);
cache.Save();
}
bool cache_changed = false;
emit GameLoaded(m_cache.AddOrGet(converted_path, &cache_changed, m_title_database));
if (cache_changed)
m_cache.Save();
}
}
@@ -4,15 +4,17 @@

#pragma once

#include <memory>

#include <QFileSystemWatcher>
#include <QMap>
#include <QSet>
#include <QSharedPointer>
#include <QString>

#include "Common/WorkQueueThread.h"
#include "DolphinQt2/GameList/GameFile.h"
#include "DolphinQt2/GameList/GameFileCache.h"
#include "Core/TitleDatabase.h"
#include "UICommon/GameFile.h"
#include "UICommon/GameFileCache.h"

// Watches directories and loads GameFiles in a separate thread.
// To use this, just add directories using AddDirectory, and listen for the
@@ -28,7 +30,7 @@ class GameTracker final : public QFileSystemWatcher
void RemoveDirectory(const QString& dir);

signals:
void GameLoaded(QSharedPointer<GameFile> game);
void GameLoaded(std::shared_ptr<const UICommon::GameFile> game);
void GameRemoved(const QString& path);

private:
@@ -40,7 +42,8 @@ class GameTracker final : public QFileSystemWatcher
// game path -> directories that track it
QMap<QString, QSet<QString>> m_tracked_files;
Common::WorkQueueThread<QString> m_load_thread;
GameFileCache cache;
UICommon::GameFileCache m_cache;
Core::TitleDatabase m_title_database;
};

Q_DECLARE_METATYPE(QSharedPointer<GameFile>)
Q_DECLARE_METATYPE(std::shared_ptr<const UICommon::GameFile>)
@@ -2,6 +2,7 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <QPixmap>
#include <QSize>

#include "DolphinQt2/GameList/GameListModel.h"
@@ -5,6 +5,7 @@

#include <QApplication>
#include <QCloseEvent>
#include <QDateTime>
#include <QDir>
#include <QDragEnterEvent>
#include <QDropEvent>
@@ -386,15 +387,15 @@ void MainWindow::Play(const std::optional<std::string>& savestate_path)
}
else
{
QSharedPointer<GameFile> selection = m_game_list->GetSelectedGame();
std::shared_ptr<const UICommon::GameFile> selection = m_game_list->GetSelectedGame();
if (selection)
{
StartGame(selection->GetFilePath(), savestate_path);
EnableScreenSaver(false);
}
else
{
auto default_path = QString::fromStdString(SConfig::GetInstance().m_strDefaultISO);
QString default_path = QString::fromStdString(SConfig::GetInstance().m_strDefaultISO);
if (!default_path.isEmpty() && QFile::exists(default_path))
{
StartGame(default_path, savestate_path);
@@ -536,7 +537,13 @@ void MainWindow::ScreenShot()

void MainWindow::StartGame(const QString& path, const std::optional<std::string>& savestate_path)
{
StartGame(BootParameters::GenerateFromFile(path.toStdString(), savestate_path));
StartGame(path.toStdString(), savestate_path);
}

void MainWindow::StartGame(const std::string& path,
const std::optional<std::string>& savestate_path)
{
StartGame(BootParameters::GenerateFromFile(path, savestate_path));
}

void MainWindow::StartGame(std::unique_ptr<BootParameters>&& parameters)
@@ -11,6 +11,7 @@

#include <memory>
#include <optional>
#include <string>

#include "DolphinQt2/GameList/GameList.h"
#include "DolphinQt2/MenuBar.h"
@@ -94,6 +95,7 @@ class MainWindow final : public QMainWindow
void InitCoreCallbacks();

void StartGame(const QString& path, const std::optional<std::string>& savestate_path = {});
void StartGame(const std::string& path, const std::optional<std::string>& savestate_path = {});
void StartGame(std::unique_ptr<BootParameters>&& parameters);
void ShowRenderWidget();
void HideRenderWidget();
@@ -34,10 +34,11 @@
#include "DiscIO/WiiSaveBanner.h"

#include "DolphinQt2/AboutDialog.h"
#include "DolphinQt2/GameList/GameFile.h"
#include "DolphinQt2/QtUtils/ActionHelper.h"
#include "DolphinQt2/Settings.h"

#include "UICommon/GameFile.h"

MenuBar::MenuBar(QWidget* parent) : QMenuBar(parent)
{
AddFileMenu();
@@ -593,8 +594,9 @@ void MenuBar::InstallWAD()

QMessageBox result_dialog(this);

if (GameFile(wad_file).Install())
if (WiiUtils::InstallWAD(wad_file.toStdString()))
{
Settings::Instance().NANDRefresh();
result_dialog.setIcon(QMessageBox::Information);
result_dialog.setText(tr("Successfully installed this title to the NAND."));
}
@@ -698,12 +700,12 @@ void MenuBar::NANDExtractCertificates()
}
}

void MenuBar::OnSelectionChanged(QSharedPointer<GameFile> game_file)
void MenuBar::OnSelectionChanged(std::shared_ptr<const UICommon::GameFile> game_file)
{
bool is_null = game_file.isNull();
const bool game_selected = !!game_file;

m_recording_play->setEnabled(!Core::IsRunning() && !is_null);
m_recording_start->setEnabled(!Movie::IsPlayingInput() && !is_null);
m_recording_play->setEnabled(game_selected && !Core::IsRunning());
m_recording_start->setEnabled(game_selected && !Movie::IsPlayingInput());
}

void MenuBar::OnRecordingStatusChanged(bool recording)
@@ -5,13 +5,12 @@
#pragma once

#include <array>
#include <memory>
#include <string>

#include <QMenu>
#include <QMenuBar>

#include "DolphinQt2/GameList/GameFile.h"

namespace Core
{
enum class State;
@@ -22,6 +21,11 @@ namespace DiscIO
enum class Region;
};

namespace UICommon
{
class GameFile;
}

class MenuBar final : public QMenuBar
{
Q_OBJECT
@@ -89,7 +93,7 @@ class MenuBar final : public QMenuBar
void ExportRecording();
void ShowTASInput();

void SelectionChanged(QSharedPointer<GameFile> game_file);
void SelectionChanged(std::shared_ptr<const UICommon::GameFile> game_file);
void RecordingStatusChanged(bool recording);
void ReadOnlyModeChanged(bool read_only);

@@ -120,7 +124,7 @@ class MenuBar final : public QMenuBar
void CheckNAND();
void NANDExtractCertificates();

void OnSelectionChanged(QSharedPointer<GameFile> game_file);
void OnSelectionChanged(std::shared_ptr<const UICommon::GameFile> game_file);
void OnRecordingStatusChanged(bool recording);
void OnReadOnlyModeChanged(bool read_only);
void OnDebugModeToggled(bool enabled);
@@ -51,7 +51,7 @@ void GameListDialog::PopulateGameList()

for (int i = 0; i < game_list_model->rowCount(QModelIndex()); i++)
{
auto* item = new QListWidgetItem(game_list_model->GetUniqueID(i));
auto* item = new QListWidgetItem(game_list_model->GetUniqueIdentifier(i));
m_game_list->addItem(item);
}

@@ -532,7 +532,7 @@ std::string NetPlayDialog::FindGame(const std::string& game)
return RunOnObject(this, [this, game] {
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
{
if (m_game_list_model->GetUniqueID(i).toStdString() == game)
if (m_game_list_model->GetUniqueIdentifier(i).toStdString() == game)
return m_game_list_model->GetPath(i).toStdString();
}
return std::string("");
@@ -245,7 +245,7 @@ void NetPlaySetupDialog::PopulateGameList()
m_host_games->clear();
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
{
auto title = m_game_list_model->GetUniqueID(i);
auto title = m_game_list_model->GetUniqueIdentifier(i);
auto path = m_game_list_model->GetPath(i);

auto* item = new QListWidgetItem(title);
@@ -0,0 +1,33 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "DolphinQt2/QtUtils/ImageConverter.h"

#include <vector>

#include <QPixmap>

#include "Common/CommonTypes.h"
#include "UICommon/GameFile.h"

QPixmap ToQPixmap(const UICommon::GameBanner& banner)
{
return ToQPixmap(banner.buffer, banner.width, banner.height);
}

QPixmap ToQPixmap(const std::vector<u32>& buffer, int width, int height)
{
QImage image(width, height, QImage::Format_RGB888);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
const u32 color = buffer[y * width + x];
image.setPixel(
x, y, qRgb((color & 0xFF0000) >> 16, (color & 0x00FF00) >> 8, (color & 0x0000FF) >> 0));
}
}

return QPixmap::fromImage(image);
}
@@ -0,0 +1,19 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#pragma once

#include <vector>

#include "Common/CommonTypes.h"

class QPixmap;

namespace UICommon
{
struct GameBanner;
}

QPixmap ToQPixmap(const UICommon::GameBanner& banner);
QPixmap ToQPixmap(const std::vector<u32>& buffer, int width, int height);
@@ -5,6 +5,7 @@
#pragma once

#include <QList>
#include <QPixmap>

// Store for various QPixmaps that will be used repeatedly.
class Resources final
@@ -67,7 +67,6 @@ set(SRCS
FrameAui.cpp
FrameTools.cpp
GameListCtrl.cpp
ISOFile.cpp
LogConfigWindow.cpp
LogWindow.cpp
Main.cpp
@@ -111,7 +111,6 @@
<ClCompile Include="Input\GuitarInputConfigDiag.cpp" />
<ClCompile Include="Input\DrumsInputConfigDiag.cpp" />
<ClCompile Include="Input\TurntableInputConfigDiag.cpp" />
<ClCompile Include="ISOFile.cpp" />
<ClCompile Include="LogConfigWindow.cpp" />
<ClCompile Include="LogWindow.cpp" />
<ClCompile Include="Main.cpp" />
@@ -194,7 +193,6 @@
<ClInclude Include="Input\GuitarInputConfigDiag.h" />
<ClInclude Include="Input\DrumsInputConfigDiag.h" />
<ClInclude Include="Input\TurntableInputConfigDiag.h" />
<ClInclude Include="ISOFile.h" />
<ClInclude Include="LogConfigWindow.h" />
<ClInclude Include="LogWindow.h" />
<ClInclude Include="Main.h" />
@@ -299,4 +297,4 @@
<Message Text="Copy: @(BinaryFiles) -&gt; $(BinaryOutputDir)" Importance="High" />
<Copy SourceFiles="@(BinaryFiles)" DestinationFolder="$(BinaryOutputDir)" />
</Target>
</Project>
</Project>
@@ -19,9 +19,6 @@
<Filter Include="GUI\Video">
<UniqueIdentifier>{80626e3b-e13b-41c3-bd63-4ef1faf92924}</UniqueIdentifier>
</Filter>
<Filter Include="Misc">
<UniqueIdentifier>{4352dc64-398e-4a96-ba4a-824dffa2004c}</UniqueIdentifier>
</Filter>
<Filter Include="Resources">
<UniqueIdentifier>{d6bc4dd6-06ed-46ad-b327-04afb26e10ec}</UniqueIdentifier>
</Filter>
@@ -154,9 +151,6 @@
<ClCompile Include="VideoConfigDiag.cpp">
<Filter>GUI\Video</Filter>
</ClCompile>
<ClCompile Include="ISOFile.cpp">
<Filter>Misc</Filter>
</ClCompile>
<ClCompile Include="AboutDolphin.cpp">
<Filter>GUI</Filter>
</ClCompile>
@@ -381,9 +375,6 @@
<ClInclude Include="VideoConfigDiag.h">
<Filter>GUI\Video</Filter>
</ClInclude>
<ClInclude Include="ISOFile.h">
<Filter>Misc</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Resources</Filter>
</ClInclude>
@@ -507,4 +498,4 @@
<ItemGroup>
<Image Include="$(CoreDir)..\..\Installer\Dolphin.ico" />
</ItemGroup>
</Project>
</Project>
@@ -77,7 +77,6 @@
#include "DolphinWX/Frame.h"
#include "DolphinWX/GameListCtrl.h"
#include "DolphinWX/Globals.h"
#include "DolphinWX/ISOFile.h"
#include "DolphinWX/Input/HotkeyInputConfigDiag.h"
#include "DolphinWX/Input/InputConfigDiag.h"
#include "DolphinWX/LogWindow.h"
@@ -92,6 +91,7 @@

#include "InputCommon/ControllerInterface/ControllerInterface.h"

#include "UICommon/GameFile.h"
#include "UICommon/UICommon.h"

#include "VideoCommon/RenderBase.h"
@@ -318,7 +318,7 @@ void CFrame::BootGame(const std::string& filename, const std::optional<std::stri
if (m_game_list_ctrl->GetSelectedISO() != nullptr)
{
if (m_game_list_ctrl->GetSelectedISO()->IsValid())
bootfile = m_game_list_ctrl->GetSelectedISO()->GetFileName();
bootfile = m_game_list_ctrl->GetSelectedISO()->GetFilePath();
}
else if (!StartUp.m_strDefaultISO.empty() && File::Exists(StartUp.m_strDefaultISO))
{
@@ -1229,10 +1229,10 @@ void CFrame::OnInstallWAD(wxCommandEvent& event)
{
case IDM_LIST_INSTALL_WAD:
{
const GameListItem* iso = m_game_list_ctrl->GetSelectedISO();
const UICommon::GameFile* iso = m_game_list_ctrl->GetSelectedISO();
if (!iso)
return;
fileName = iso->GetFileName();
fileName = iso->GetFilePath();
break;
}
case IDM_MENU_INSTALL_WAD:
@@ -1258,7 +1258,7 @@ void CFrame::OnInstallWAD(wxCommandEvent& event)

void CFrame::OnUninstallWAD(wxCommandEvent&)
{
const GameListItem* file = m_game_list_ctrl->GetSelectedISO();
const UICommon::GameFile* file = m_game_list_ctrl->GetSelectedISO();
if (!file)
return;

@@ -1268,9 +1268,8 @@ void CFrame::OnUninstallWAD(wxCommandEvent&)
return;
}

u64 title_id = file->GetTitleID();
IOS::HLE::Kernel ios;
if (ios.GetES()->DeleteTitleContent(title_id) < 0)
const u64 title_id = file->GetTitleID();
if (!WiiUtils::UninstallTitle(title_id))
{
PanicAlertT("Failed to remove this title from the NAND.");
return;
@@ -1490,11 +1489,11 @@ void CFrame::OnPerformOnlineWiiUpdate(wxCommandEvent& event)

void CFrame::OnPerformDiscWiiUpdate(wxCommandEvent&)
{
const GameListItem* iso = m_game_list_ctrl->GetSelectedISO();
const UICommon::GameFile* iso = m_game_list_ctrl->GetSelectedISO();
if (!iso)
return;

const std::string file_name = iso->GetFileName();
const std::string file_name = iso->GetFilePath();

const WiiUtils::UpdateResult result = ShowUpdateProgress(this, WiiUtils::DoDiscUpdate, file_name);
ShowUpdateResult(result);

Large diffs are not rendered by default.

@@ -16,7 +16,12 @@
#include "Common/ChunkFile.h"
#include "Common/Event.h"
#include "Common/Flag.h"
#include "DolphinWX/ISOFile.h"
#include "UICommon/GameFileCache.h"

namespace UICommon
{
class GameFile;
}

class wxEmuStateTip : public wxTipWindow
{
@@ -46,8 +51,8 @@ class GameListCtrl : public wxListCtrl
~GameListCtrl();

void BrowseForDirectory();
const GameListItem* GetISO(size_t index) const;
const GameListItem* GetSelectedISO() const;
const UICommon::GameFile* GetISO(size_t index) const;
const UICommon::GameFile* GetSelectedISO() const;

static bool IsHidingItems();

@@ -80,9 +85,7 @@ class GameListCtrl : public wxListCtrl
void SetColors();
void RefreshList();
void RescanList();
void DoState(PointerWrap* p, u32 size = 0);
bool SyncCacheFile(bool write);
std::vector<const GameListItem*> GetAllSelectedISOs() const;
std::vector<const UICommon::GameFile*> GetAllSelectedISOs() const;

// events
void OnRefreshGameList(wxCommandEvent& event);
@@ -124,17 +127,17 @@ class GameListCtrl : public wxListCtrl
std::vector<int> emu_state;
} m_image_indexes;

// Actual backing GameListItems are maintained in a background thread and cached to file
std::vector<std::shared_ptr<GameListItem>> m_cached_files;
// Locks the list, not the contents
// Actual backing GameFiles are maintained in a background thread and cached to file
UICommon::GameFileCache m_cache;
// Locks the cache object, not the shared_ptr<GameFile>s obtained from it
std::mutex m_cache_mutex;
Core::TitleDatabase m_title_database;
std::mutex m_title_database_mutex;
std::thread m_scan_thread;
Common::Event m_scan_trigger;
Common::Flag m_scan_exiting;
// UI thread's view into the cache
std::vector<std::shared_ptr<GameListItem>> m_shown_files;
std::vector<std::shared_ptr<const UICommon::GameFile>> m_shown_files;

int m_last_column;
int m_last_sort;

This file was deleted.

This file was deleted.

@@ -28,7 +28,6 @@
#include "DiscIO/Enums.h"
#include "DiscIO/Filesystem.h"
#include "DiscIO/Volume.h"
#include "DolphinWX/ISOFile.h"
#include "DolphinWX/WxUtils.h"

namespace
@@ -54,12 +54,12 @@
#include "DolphinWX/DolphinSlider.h"
#include "DolphinWX/Frame.h"
#include "DolphinWX/Globals.h"
#include "DolphinWX/ISOFile.h"
#include "DolphinWX/ISOProperties/FilesystemPanel.h"
#include "DolphinWX/ISOProperties/InfoPanel.h"
#include "DolphinWX/Main.h"
#include "DolphinWX/PatchAddEdit.h"
#include "DolphinWX/WxUtils.h"
#include "UICommon/GameFile.h"

// A warning message displayed on the ARCodes and GeckoCodes pages when cheats are
// disabled globally to explain why turning cheats on does not work.
@@ -190,15 +190,15 @@ EVT_BUTTON(ID_ADDPATCH, CISOProperties::PatchButtonClicked)
EVT_BUTTON(ID_REMOVEPATCH, CISOProperties::PatchButtonClicked)
END_EVENT_TABLE()

CISOProperties::CISOProperties(const GameListItem& game_list_item, wxWindow* parent, wxWindowID id,
const wxString& title, const wxPoint& position, const wxSize& size,
long style)
CISOProperties::CISOProperties(const UICommon::GameFile& game_list_item, wxWindow* parent,
wxWindowID id, const wxString& title, const wxPoint& position,
const wxSize& size, long style)
: wxDialog(parent, id, title, position, size, style), m_open_gamelist_item(game_list_item)
{
Bind(DOLPHIN_EVT_CHANGE_ISO_PROPERTIES_TITLE, &CISOProperties::OnChangeTitle, this);

// Load ISO data
m_open_iso = DiscIO::CreateVolumeFromFilename(m_open_gamelist_item.GetFileName());
m_open_iso = DiscIO::CreateVolumeFromFilename(m_open_gamelist_item.GetFilePath());

m_game_id = m_open_iso->GetGameID();

@@ -15,8 +15,8 @@
#include <wx/treebase.h>

#include "Common/IniFile.h"
#include "DolphinWX/ISOFile.h"
#include "DolphinWX/PatchAddEdit.h"
#include "UICommon/GameFile.h"

class ActionReplayCodesPanel;
class CheatWarningMessage;
@@ -52,9 +52,9 @@ wxDECLARE_EVENT(DOLPHIN_EVT_CHANGE_ISO_PROPERTIES_TITLE, wxCommandEvent);
class CISOProperties : public wxDialog
{
public:
CISOProperties(const GameListItem& game_list_item, wxWindow* parent, wxWindowID id = wxID_ANY,
const wxString& title = _("Properties"), const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
CISOProperties(const UICommon::GameFile& game_list_item, wxWindow* parent,
wxWindowID id = wxID_ANY, const wxString& title = _("Properties"),
const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
long style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
virtual ~CISOProperties();

@@ -141,7 +141,7 @@ class CISOProperties : public wxDialog
void OnCheatCodeToggled(wxCommandEvent& event);
void OnChangeTitle(wxCommandEvent& event);

const GameListItem m_open_gamelist_item;
const UICommon::GameFile m_open_gamelist_item;

IniFile m_gameini_default;
IniFile m_gameini_local;
@@ -32,9 +32,9 @@
#include "Core/IOS/ES/Formats.h"
#include "DiscIO/Enums.h"
#include "DiscIO/Volume.h"
#include "DolphinWX/ISOFile.h"
#include "DolphinWX/ISOProperties/ISOProperties.h"
#include "DolphinWX/WxUtils.h"
#include "UICommon/GameFile.h"

namespace
{
@@ -142,7 +142,7 @@ int FindPreferredLanguageIndex(DiscIO::Language preferred_language,
}
} // Anonymous namespace

InfoPanel::InfoPanel(wxWindow* parent, wxWindowID id, const GameListItem& item,
InfoPanel::InfoPanel(wxWindow* parent, wxWindowID id, const UICommon::GameFile& item,
const std::unique_ptr<DiscIO::Volume>& opened_iso)
: wxPanel{parent, id}, m_game_list_item{item}, m_opened_iso{opened_iso}
{
@@ -209,8 +209,8 @@ void InfoPanel::LoadBannerDetails()

void InfoPanel::LoadBannerImage()
{
const auto& banner_image = m_game_list_item.GetBannerImage();
const auto banner_min_size = m_banner->GetMinSize();
const wxImage banner_image = WxUtils::ToWxImage(m_game_list_item.GetBannerImage());
const wxSize banner_min_size = m_banner->GetMinSize();

if (banner_image.IsOk())
{
@@ -337,7 +337,7 @@ void InfoPanel::OnComputeMD5(wxCommandEvent& WXUNUSED(event))
wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME |
wxPD_REMAINING_TIME | wxPD_SMOOTH);

const auto result = MD5::MD5Sum(m_game_list_item.GetFileName(), [&progress_dialog](int progress) {
const auto result = MD5::MD5Sum(m_game_list_item.GetFilePath(), [&progress_dialog](int progress) {
return progress_dialog.Update(progress);
});

@@ -367,24 +367,24 @@ void InfoPanel::OnSaveBannerImage(wxCommandEvent& WXUNUSED(event))

if (dialog.ShowModal() == wxID_OK)
{
m_game_list_item.GetBannerImage().SaveFile(dialog.GetPath());
WxUtils::ToWxImage(m_game_list_item.GetBannerImage()).SaveFile(dialog.GetPath());
}

Raise();
}

void InfoPanel::ChangeBannerDetails(DiscIO::Language language)
{
const auto name = StrToWxStr(m_game_list_item.GetName(language));
const auto name = StrToWxStr(m_game_list_item.GetLongName(language));
const auto comment = StrToWxStr(m_game_list_item.GetDescription(language));
const auto maker = StrToWxStr(m_game_list_item.GetCompany());
const auto maker = StrToWxStr(m_game_list_item.GetLongMaker(language));

m_name->SetValue(name);
m_comment->SetValue(comment);
m_maker->SetValue(maker);

std::string path, filename, extension;
SplitPath(m_game_list_item.GetFileName(), &path, &filename, &extension);
SplitPath(m_game_list_item.GetFilePath(), &path, &filename, &extension);

// Real disk drives don't have filenames on Windows
if (filename.empty() && extension.empty())
@@ -7,7 +7,6 @@
#include <memory>
#include <wx/panel.h>

class GameListItem;
class wxButton;
class wxChoice;
class wxStaticBitmap;
@@ -16,14 +15,19 @@ class wxTextCtrl;

namespace DiscIO
{
class Volume;
enum class Language;
class Volume;
}

namespace UICommon
{
class GameFile;
}

class InfoPanel final : public wxPanel
{
public:
InfoPanel(wxWindow* parent, wxWindowID id, const GameListItem& item,
InfoPanel(wxWindow* parent, wxWindowID id, const UICommon::GameFile& item,
const std::unique_ptr<DiscIO::Volume>& opened_iso);

private:
@@ -37,6 +41,7 @@ class InfoPanel final : public wxPanel
void LoadGUIData();
void LoadISODetails();
void LoadBannerDetails();
wxImage ConvertBannerImage();
void LoadBannerImage();

wxStaticBoxSizer* CreateISODetailsSizer();
@@ -52,7 +57,7 @@ class InfoPanel final : public wxPanel

void EmitTitleChangeEvent(const wxString& new_title);

const GameListItem& m_game_list_item;
const UICommon::GameFile& m_game_list_item;
const std::unique_ptr<DiscIO::Volume>& m_opened_iso;

wxTextCtrl* m_internal_name;
@@ -47,11 +47,12 @@

#include "DolphinWX/Frame.h"
#include "DolphinWX/GameListCtrl.h"
#include "DolphinWX/ISOFile.h"
#include "DolphinWX/NetPlay/ChangeGameDialog.h"
#include "DolphinWX/NetPlay/MD5Dialog.h"
#include "DolphinWX/NetPlay/PadMapDialog.h"
#include "DolphinWX/WxUtils.h"
#include "MD5Dialog.h"

#include "UICommon/GameFile.h"

#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/VideoConfig.h"
@@ -335,7 +336,7 @@ std::string NetPlayDialog::FindGame(const std::string& target_game)
// find path for selected game, sloppy..
for (u32 i = 0; auto game = m_game_list->GetISO(i); ++i)
if (target_game == game->GetUniqueIdentifier())
return game->GetFileName();
return game->GetFilePath();

return "";
}
@@ -28,6 +28,8 @@

#include "DolphinWX/WxUtils.h"

#include "UICommon/GameFile.h"

#ifdef _WIN32
#include <Windows.h>
#endif
@@ -285,6 +287,27 @@ wxSize GetTextWidgetMinSize(const wxSpinCtrl* spinner)
return size;
}

wxImage ToWxImage(const UICommon::GameBanner& banner)
{
return ToWxImage(banner.buffer, banner.width, banner.height);
}

wxImage ToWxImage(const std::vector<u32>& buffer, int width, int height)
{
wxImage image(width, height, false);
if (buffer.empty())
return image;

unsigned char* data = image.GetData();
for (int i = 0; i < width * height; i++)
{
data[i * 3 + 0] = (buffer[i] & 0xFF0000) >> 16;
data[i * 3 + 1] = (buffer[i] & 0x00FF00) >> 8;
data[i * 3 + 2] = (buffer[i] & 0x0000FF) >> 0;
}
return image;
}

static wxImage LoadScaledImage(const std::string& file_path, const wxWindow* context,
const wxSize& output_size, const wxRect& usable_rect, LSIFlags flags,
const wxColour& fill_color)
@@ -5,10 +5,14 @@
#pragma once

#include <string>
#include <vector>

#include <wx/colour.h>
#include <wx/gdicmn.h>
#include <wx/string.h>

#include "Common/CommonTypes.h"

class wxControl;
class wxBitmap;
class wxImage;
@@ -18,6 +22,11 @@ class wxToolBar;
class wxTopLevelWindow;
class wxWindow;

namespace UICommon
{
struct GameBanner;
}

namespace WxUtils
{
// Launch a file according to its mime type
@@ -95,6 +104,9 @@ constexpr LSIFlags operator&(LSIFlags left, LSIFlags right)
return static_cast<LSIFlags>(static_cast<unsigned int>(left) & right);
}

wxImage ToWxImage(const UICommon::GameBanner& banner);
wxImage ToWxImage(const std::vector<u32>& buffer, int width, int height);

// Swiss army knife loader function for preparing a scaled resource image file.
// Only the path and context are mandatory, other parameters can be ignored.
// NOTE: All size parameters are in window pixels, not DIPs or framebuffer pixels.
@@ -1,6 +1,8 @@
set(SRCS
CommandLineParse.cpp
Disassembler.cpp
GameFile.cpp
GameFileCache.cpp
UICommon.cpp
USBUtils.cpp
VideoUtils.cpp
@@ -0,0 +1,312 @@
// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "UICommon/GameFile.h"

#include <algorithm>
#include <cinttypes>
#include <cstdio>
#include <cstring>
#include <iterator>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "Common/ChunkFile.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/Hash.h"
#include "Common/IniFile.h"
#include "Common/NandPaths.h"
#include "Common/StringUtil.h"

#include "Core/Boot/Boot.h"
#include "Core/ConfigManager.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/TitleDatabase.h"

#include "DiscIO/Blob.h"
#include "DiscIO/Enums.h"
#include "DiscIO/Volume.h"
#include "DiscIO/WiiSaveBanner.h"

namespace UICommon
{
static const std::string EMPTY_STRING;

const std::string& GameFile::Lookup(DiscIO::Language language,
const std::map<DiscIO::Language, std::string>& strings)
{
auto end = strings.end();
auto it = strings.find(language);
if (it != end)
return it->second;

// English tends to be a good fallback when the requested language isn't available
if (language != DiscIO::Language::LANGUAGE_ENGLISH)
{
it = strings.find(DiscIO::Language::LANGUAGE_ENGLISH);
if (it != end)
return it->second;
}

// If English isn't available either, just pick something
if (!strings.empty())
return strings.cbegin()->second;

return EMPTY_STRING;
}

const std::string&
GameFile::LookupUsingConfigLanguage(const std::map<DiscIO::Language, std::string>& strings) const
{
const bool wii = DiscIO::IsWii(m_platform);
return Lookup(SConfig::GetInstance().GetCurrentLanguage(wii), strings);
}

GameFile::GameFile(const std::string& path)
: m_file_path(path), m_region(DiscIO::Region::UNKNOWN_REGION),
m_country(DiscIO::Country::COUNTRY_UNKNOWN)
{
{
std::string name, extension;
SplitPath(m_file_path, nullptr, &name, &extension);
m_file_name = name + extension;

std::unique_ptr<DiscIO::Volume> volume(DiscIO::CreateVolumeFromFilename(m_file_path));
if (volume != nullptr)
{
m_platform = volume->GetVolumeType();

m_short_names = volume->GetShortNames();
m_long_names = volume->GetLongNames();
m_short_makers = volume->GetShortMakers();
m_long_makers = volume->GetLongMakers();
m_descriptions = volume->GetDescriptions();

m_region = volume->GetRegion();
m_country = volume->GetCountry();
m_blob_type = volume->GetBlobType();
m_file_size = volume->GetRawSize();
m_volume_size = volume->GetSize();

m_internal_name = volume->GetInternalName();
m_game_id = volume->GetGameID();
m_title_id = volume->GetTitleID().value_or(0);
m_maker_id = volume->GetMakerID();
m_revision = volume->GetRevision().value_or(0);
m_disc_number = volume->GetDiscNumber().value_or(0);
m_apploader_date = volume->GetApploaderDate();

m_volume_banner.buffer = volume->GetBanner(&m_volume_banner.width, &m_volume_banner.height);

m_valid = true;
}
}

if (!IsValid() && IsElfOrDol())
{
m_valid = true;
m_file_size = File::GetSize(m_file_path);
m_platform = DiscIO::Platform::ELF_DOL;
m_blob_type = DiscIO::BlobType::DIRECTORY;
}
}

bool GameFile::IsValid() const
{
if (!m_valid)
return false;

if (m_platform == DiscIO::Platform::WII_WAD && !IOS::ES::IsChannel(m_title_id))
return false;

return true;
}

bool GameFile::CustomNameChanged(const Core::TitleDatabase& title_database)
{
const auto type = m_platform == DiscIO::Platform::WII_WAD ?
Core::TitleDatabase::TitleType::Channel :
Core::TitleDatabase::TitleType::Other;
m_pending.custom_name = title_database.GetTitleName(m_game_id, type);
return m_custom_name != m_pending.custom_name;
}

void GameFile::CustomNameCommit()
{
m_custom_name = std::move(m_pending.custom_name);
}

bool GameFile::EmuStateChanged()
{
IniFile ini = SConfig::LoadGameIni(m_game_id, m_revision);
ini.GetIfExists("EmuState", "EmulationStateId", &m_pending.emu_state.rating, 0);
ini.GetIfExists("EmuState", "EmulationIssues", &m_pending.emu_state.issues, std::string());
return m_emu_state != m_pending.emu_state;
}

void GameFile::EmuStateCommit()
{
m_emu_state = std::move(m_pending.emu_state);
}

void GameFile::EmuState::DoState(PointerWrap& p)
{
p.Do(rating);
p.Do(issues);
}

void GameBanner::DoState(PointerWrap& p)
{
p.Do(buffer);
p.Do(width);
p.Do(height);
}

void GameFile::DoState(PointerWrap& p)
{
p.Do(m_valid);
p.Do(m_file_path);
p.Do(m_file_name);

p.Do(m_file_size);
p.Do(m_volume_size);

p.Do(m_short_names);
p.Do(m_long_names);
p.Do(m_short_makers);
p.Do(m_long_makers);
p.Do(m_descriptions);
p.Do(m_internal_name);
p.Do(m_game_id);
p.Do(m_title_id);
p.Do(m_maker_id);

p.Do(m_region);
p.Do(m_country);
p.Do(m_platform);
p.Do(m_blob_type);
p.Do(m_revision);
p.Do(m_disc_number);
p.Do(m_apploader_date);

m_volume_banner.DoState(p);
m_emu_state.DoState(p);
p.Do(m_custom_name);
}

bool GameFile::IsElfOrDol() const
{
if (m_file_path.size() < 4)
return false;

std::string name_end = m_file_path.substr(m_file_path.size() - 4);
std::transform(name_end.begin(), name_end.end(), name_end.begin(), ::tolower);
return name_end == ".elf" || name_end == ".dol";
}

bool GameFile::BannerChanged()
{
// Wii banners can only be read if there is a save file.
// In case the cache was created without a save file existing,
// let's try reading the save file again, because it might exist now.

if (!m_volume_banner.empty())
return false;
if (!DiscIO::IsWii(m_platform))
return false;

m_volume_banner.buffer =
DiscIO::WiiSaveBanner(m_title_id).GetBanner(&m_volume_banner.width, &m_volume_banner.height);
if (m_volume_banner.buffer.empty())
return false;

// We only reach here if m_volume_banner was empty, so we can always return true
// without needing any extra check to know whether the banners are different
return true;
}

void GameFile::BannerCommit()
{
m_volume_banner = std::move(m_pending.volume_banner);
}

const std::string& GameFile::GetName(bool long_name) const
{
if (!m_custom_name.empty())
return m_custom_name;

const std::string& name = long_name ? GetLongName() : GetShortName();
if (!name.empty())
return name;

// No usable name, return filename (better than nothing)
return m_file_name;
}

const std::string& GameFile::GetMaker(bool long_maker) const
{
const std::string& maker = long_maker ? GetLongMaker() : GetShortMaker();
if (!maker.empty())
return maker;

if (m_game_id.size() >= 6)
return DiscIO::GetCompanyFromID(m_maker_id);

return EMPTY_STRING;
}

std::vector<DiscIO::Language> GameFile::GetLanguages() const
{
std::vector<DiscIO::Language> languages;
// TODO: What if some languages don't have long names but have other strings?
for (std::pair<DiscIO::Language, std::string> name : m_long_names)
languages.push_back(name.first);
return languages;
}

std::string GameFile::GetUniqueIdentifier() const
{
const DiscIO::Language lang = DiscIO::Language::LANGUAGE_ENGLISH;
std::vector<std::string> info;
if (!GetGameID().empty())
info.push_back(GetGameID());
if (GetRevision() != 0)
info.push_back("Revision " + std::to_string(GetRevision()));

std::string name(GetLongName(lang));
if (name.empty())
name = GetName();

int disc_number = GetDiscNumber() + 1;

std::string lower_name = name;
std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(), ::tolower);
if (disc_number > 1 &&
lower_name.find(StringFromFormat("disc %i", disc_number)) == std::string::npos &&
lower_name.find(StringFromFormat("disc%i", disc_number)) == std::string::npos)
{
std::string disc_text = "Disc ";
info.push_back(disc_text + std::to_string(disc_number));
}
if (info.empty())
return name;
std::ostringstream ss;
std::copy(info.begin(), info.end() - 1, std::ostream_iterator<std::string>(ss, ", "));
ss << info.back();
return name + " (" + ss.str() + ")";
}

std::string GameFile::GetWiiFSPath() const
{
_assert_(DiscIO::IsWii(m_platform));
return Common::GetTitleDataPath(m_title_id, Common::FROM_CONFIGURED_ROOT);
}

} // namespace UICommon
@@ -0,0 +1,153 @@
// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#pragma once

#include <map>
#include <string>
#include <vector>

#include "Common/CommonTypes.h"

class PointerWrap;

namespace Core
{
class TitleDatabase;
}

namespace DiscIO
{
enum class BlobType;
enum class Country;
enum class Language;
enum class Region;
enum class Platform;
}

namespace UICommon
{
struct GameBanner
{
std::vector<u32> buffer{};
int width{};
int height{};
bool empty() const { return buffer.empty(); }
void DoState(PointerWrap& p);
};

// This class caches the metadata of a DiscIO::Volume (or a DOL/ELF file).
class GameFile final
{
public:
GameFile() = default;
explicit GameFile(const std::string& path);
~GameFile() = default;

bool IsValid() const;
const std::string& GetFilePath() const { return m_file_path; }
const std::string& GetFileName() const { return m_file_name; }
const std::string& GetName(bool long_name = true) const;
const std::string& GetMaker(bool long_maker = true) const;
const std::string& GetShortName(DiscIO::Language l) const { return Lookup(l, m_short_names); }
const std::string& GetShortName() const { return LookupUsingConfigLanguage(m_short_names); }
const std::string& GetLongName(DiscIO::Language l) const { return Lookup(l, m_long_names); }
const std::string& GetLongName() const { return LookupUsingConfigLanguage(m_long_names); }
const std::string& GetShortMaker(DiscIO::Language l) const { return Lookup(l, m_short_makers); }
const std::string& GetShortMaker() const { return LookupUsingConfigLanguage(m_short_makers); }
const std::string& GetLongMaker(DiscIO::Language l) const { return Lookup(l, m_long_makers); }
const std::string& GetLongMaker() const { return LookupUsingConfigLanguage(m_long_makers); }
const std::string& GetDescription(DiscIO::Language l) const { return Lookup(l, m_descriptions); }
const std::string& GetDescription() const { return LookupUsingConfigLanguage(m_descriptions); }
std::vector<DiscIO::Language> GetLanguages() const;
const std::string& GetInternalName() const { return m_internal_name; }
const std::string& GetGameID() const { return m_game_id; }
u64 GetTitleID() const { return m_title_id; }
const std::string& GetMakerID() const { return m_maker_id; }
u16 GetRevision() const { return m_revision; }
// 0 is the first disc, 1 is the second disc
u8 GetDiscNumber() const { return m_disc_number; }
std::string GetUniqueIdentifier() const;
std::string GetWiiFSPath() const;
DiscIO::Region GetRegion() const { return m_region; }
DiscIO::Country GetCountry() const { return m_country; }
DiscIO::Platform GetPlatform() const { return m_platform; }
DiscIO::BlobType GetBlobType() const { return m_blob_type; }
const std::string& GetApploaderDate() const { return m_apploader_date; }
const std::string& GetIssues() const { return m_emu_state.issues; }
int GetEmuState() const { return m_emu_state.rating; }
u64 GetFileSize() const { return m_file_size; }
u64 GetVolumeSize() const { return m_volume_size; }
const GameBanner& GetBannerImage() const { return m_volume_banner; }
void DoState(PointerWrap& p);
bool BannerChanged();
void BannerCommit();
bool EmuStateChanged();
void EmuStateCommit();
bool CustomNameChanged(const Core::TitleDatabase& title_database);
void CustomNameCommit();

private:
struct EmuState
{
int rating{};
std::string issues{};
bool operator!=(const EmuState& rhs) const
{
return rating != rhs.rating || issues != rhs.issues;
}
void DoState(PointerWrap& p);
};

static const std::string& Lookup(DiscIO::Language language,
const std::map<DiscIO::Language, std::string>& strings);
const std::string&
LookupUsingConfigLanguage(const std::map<DiscIO::Language, std::string>& strings) const;
bool IsElfOrDol() const;

// IMPORTANT: Nearly all data members must be save/restored in DoState.
// If anything is changed, make sure DoState handles it properly and
// CACHE_REVISION in GameFileCache.cpp is incremented.

bool m_valid{};
std::string m_file_path{};
std::string m_file_name{};

u64 m_file_size{};
u64 m_volume_size{};

std::map<DiscIO::Language, std::string> m_short_names{};
std::map<DiscIO::Language, std::string> m_long_names{};
std::map<DiscIO::Language, std::string> m_short_makers{};
std::map<DiscIO::Language, std::string> m_long_makers{};
std::map<DiscIO::Language, std::string> m_descriptions{};
std::string m_internal_name{};
std::string m_game_id{};
u64 m_title_id{};
std::string m_maker_id{};

DiscIO::Region m_region{};
DiscIO::Country m_country{};
DiscIO::Platform m_platform{};
DiscIO::BlobType m_blob_type{};
u16 m_revision{};
u8 m_disc_number{};
std::string m_apploader_date{};

GameBanner m_volume_banner{};
EmuState m_emu_state{};
// Overridden name from TitleDatabase
std::string m_custom_name{};

// The following data members allow GameFileCache to construct updated versions
// of GameFiles in a threadsafe way. They should not be handled in DoState.
struct
{
EmuState emu_state;
GameBanner volume_banner;
std::string custom_name;
} m_pending{};
};

} // namespace UICommon