Properly get PARAM.SFO and icons for C00 games (#5370)

* Added a helper function for fetching game's PARAM.SFO path

This should properly get SFO path for unlocked C00 games

* Normalized line endings

* Refresh game list after installing a RAP file
This commit is contained in:
NicknineTheEagle 2018-12-04 01:46:01 +03:00 committed by Ivan
parent afdf0b74a0
commit 32059bfaa2
5 changed files with 216 additions and 195 deletions

View File

@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "overlay_controls.h"
#include "../../../Utilities/date_time.h"
@ -813,14 +813,11 @@ namespace rsx
if (use_custom_background)
{
std::string root_path = Emu.GetBoot();
root_path = root_path.substr(0, root_path.find_last_of("/"));
auto icon_path = root_path + "/../PIC1.PNG";
auto icon_path = Emu.GetSfoDir() + "/PIC1.PNG";
if (!fs::exists(icon_path))
{
// Fallback path
icon_path = root_path + "/../ICON0.PNG";
icon_path = Emu.GetSfoDir() + "/ICON0.PNG";
}
if (fs::exists(icon_path))

View File

@ -1,4 +1,4 @@
#include "stdafx.h"
#include "stdafx.h"
#include "Utilities/bin_patch.h"
#include "Emu/Memory/vm.h"
#include "Emu/System.h"
@ -571,6 +571,32 @@ std::string Emulator::GetHddDir()
return fmt::replace_all(g_cfg.vfs.dev_hdd0, "$(EmulatorDir)", GetEmuDir());
}
std::string Emulator::GetSfoDirFromGamePath(const std::string& game_path, const std::string& user)
{
if (fs::is_file(game_path + "/PS3_DISC.SFB"))
{
// This is a disc game.
return game_path + "/PS3_GAME";
}
const auto psf = psf::load_object(fs::file(game_path + "/PARAM.SFO"));
const std::string category = get_string(psf, "CATEGORY");
const std::string content_id = get_string(psf, "CONTENT_ID");
if (category == "HG" && !content_id.empty())
{
// This is a trial game. Check if the user has a RAP file to unlock it.
const std::string rap_path = GetHddDir() + "home/" + user + "/exdata/" + content_id + ".rap";
if (fs::is_file(rap_path) && fs::is_file(game_path + "/C00/PARAM.SFO"))
{
// Load full game data.
return game_path + "/C00";
}
}
return game_path;
}
void Emulator::SetForceBoot(bool force_boot)
{
m_force_boot = force_boot;
@ -600,42 +626,43 @@ void Emulator::Load(bool add_only)
const std::string elf_dir = fs::get_parent_dir(m_path);
// Load PARAM.SFO (TODO)
const auto _psf = psf::load_object([&]() -> fs::file
psf::registry _psf;
if (fs::file sfov{elf_dir + "/sce_sys/param.sfo"})
{
m_sfo_dir = elf_dir;
_psf = psf::load_object(sfov);
}
else
{
if (fs::file sfov{elf_dir + "/sce_sys/param.sfo"})
{
return sfov;
}
if (fs::is_dir(m_path))
{
// Special case (directory scan)
if (fs::file sfo{m_path + "/PS3_GAME/PARAM.SFO"})
{
return sfo;
}
return fs::file{m_path + "/PARAM.SFO"};
m_sfo_dir = GetSfoDirFromGamePath(m_path, GetUsr());
}
if (disc.size())
else if (disc.size())
{
// Check previously used category before it's overwritten
if (m_cat == "DG")
{
return fs::file{disc + "/PS3_GAME/PARAM.SFO"};
m_sfo_dir = disc + "/PS3_GAME";
}
if (m_cat == "GD")
else if (m_cat == "GD")
{
return fs::file{GetHddDir() + "game/" + m_title_id + "/PARAM.SFO"};
m_sfo_dir = GetHddDir() + "game/" + m_title_id;
}
return fs::file{disc + "/PARAM.SFO"};
else
{
m_sfo_dir = GetSfoDirFromGamePath(disc, GetUsr());
}
}
else
{
m_sfo_dir = GetSfoDirFromGamePath(fs::get_parent_dir(elf_dir), GetUsr());
}
return fs::file{elf_dir + "/../PARAM.SFO"};
}());
_psf = psf::load_object(fs::file(m_sfo_dir + "/PARAM.SFO"));
}
m_title = psf::get_string(_psf, "TITLE", m_path.substr(m_path.find_last_of('/') + 1));
m_title_id = psf::get_string(_psf, "TITLE_ID");
m_cat = psf::get_string(_psf, "CATEGORY");

View File

@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "VFS.h"
#include "Utilities/Atomic.h"
@ -215,6 +215,7 @@ class Emulator final
std::string m_title;
std::string m_cat;
std::string m_dir;
std::string m_sfo_dir;
std::string m_usr{"00000001"};
u32 m_usrid{1};
@ -285,6 +286,11 @@ public:
return m_dir;
}
const std::string& GetSfoDir() const
{
return m_sfo_dir;
}
// String for GUI dialogs.
const std::string& GetUsr() const
{
@ -312,6 +318,7 @@ private:
static std::string GetEmuDir();
public:
static std::string GetHddDir();
static std::string GetSfoDirFromGamePath(const std::string& game_path, const std::string& user);
void SetForceBoot(bool force_boot);

View File

@ -1,4 +1,4 @@
#include "game_list_frame.h"
#include "game_list_frame.h"
#include "qt_utils.h"
#include "settings_dialog.h"
#include "table_item_delegate.h"
@ -229,9 +229,9 @@ void game_list_frame::FixNarrowColumns()
void game_list_frame::ResizeColumnsToContents(int spacing)
{
if (!m_gameList)
{
return;
if (!m_gameList)
{
return;
}
m_gameList->verticalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents);
@ -240,9 +240,9 @@ void game_list_frame::ResizeColumnsToContents(int spacing)
// Make non-icon columns slighty bigger for better visuals
for (int i = 1; i < m_gameList->columnCount(); i++)
{
if (m_gameList->isColumnHidden(i))
{
continue;
if (m_gameList->isColumnHidden(i))
{
continue;
}
int size = m_gameList->horizontalHeader()->sectionSize(i) + spacing;
@ -293,42 +293,42 @@ void game_list_frame::SortGameList()
int old_row_count = m_gameList->rowCount();
int old_game_count = m_game_data.count();
for (int i = 0; i < m_gameList->columnCount(); i++)
{
column_widths.append(m_gameList->columnWidth(i));
for (int i = 0; i < m_gameList->columnCount(); i++)
{
column_widths.append(m_gameList->columnWidth(i));
}
// Sorting resizes hidden columns, so unhide them as a workaround
QList<int> columns_to_hide;
for (int i = 0; i < m_gameList->columnCount(); i++)
{
if (m_gameList->isColumnHidden(i))
{
m_gameList->setColumnHidden(i, false);
columns_to_hide << i;
}
for (int i = 0; i < m_gameList->columnCount(); i++)
{
if (m_gameList->isColumnHidden(i))
{
m_gameList->setColumnHidden(i, false);
columns_to_hide << i;
}
}
// Sort the list by column and sort order
m_gameList->sortByColumn(m_sortColumn, m_colSortOrder);
// Hide columns again
for (auto i : columns_to_hide)
{
m_gameList->setColumnHidden(i, true);
for (auto i : columns_to_hide)
{
m_gameList->setColumnHidden(i, true);
}
// Don't resize the columns if no game is shown to preserve the header settings
if (!m_gameList->rowCount())
{
for (int i = 0; i < m_gameList->columnCount(); i++)
{
m_gameList->setColumnWidth(i, column_widths[i]);
if (!m_gameList->rowCount())
{
for (int i = 0; i < m_gameList->columnCount(); i++)
{
m_gameList->setColumnWidth(i, column_widths[i]);
}
m_gameList->horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed);
return;
m_gameList->horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed);
return;
}
// Fixate vertical header and row height
@ -337,13 +337,13 @@ void game_list_frame::SortGameList()
m_gameList->resizeRowsToContents();
// Resize columns if the game list was empty before
if (!old_row_count && !old_game_count)
{
ResizeColumnsToContents();
if (!old_row_count && !old_game_count)
{
ResizeColumnsToContents();
}
else
{
m_gameList->resizeColumnToContents(gui::column_icon);
else
{
m_gameList->resizeColumnToContents(gui::column_icon);
}
// Fixate icon column
@ -393,10 +393,8 @@ void game_list_frame::Refresh(const bool fromDrive, const bool scrollAfter)
for (const auto& dir : path_list) { try
{
const std::string sfb = dir + "/PS3_DISC.SFB";
const std::string sfo = dir + (fs::is_file(sfb) ? "/PS3_GAME/PARAM.SFO" : "/PARAM.SFO");
const fs::file sfo_file(sfo);
const std::string sfo_dir = Emu.GetSfoDirFromGamePath(dir, Emu.GetUsr());
const fs::file sfo_file(sfo_dir + "/PARAM.SFO");
if (!sfo_file)
{
continue;
@ -430,29 +428,21 @@ void game_list_frame::Refresh(const bool fromDrive, const bool scrollAfter)
auto cat = category::cat_boot.find(game.category);
if (cat != category::cat_boot.end())
{
if (game.category == "DG")
{
game.icon_path = dir + "/PS3_GAME/ICON0.PNG";
}
else
{
game.icon_path = dir + "/ICON0.PNG";
}
game.icon_path = sfo_dir + "/ICON0.PNG";
game.category = sstr(cat->second);
}
else if ((cat = category::cat_data.find(game.category)) != category::cat_data.end())
{
game.icon_path = dir + "/ICON0.PNG";
game.icon_path = sfo_dir + "/ICON0.PNG";
game.category = sstr(cat->second);
}
else if (game.category == sstr(category::unknown))
{
game.icon_path = dir + "/ICON0.PNG";
game.icon_path = sfo_dir + "/ICON0.PNG";
}
else
{
game.icon_path = dir + "/ICON0.PNG";
game.icon_path = sfo_dir + "/ICON0.PNG";
game.category = sstr(category::other);
}
@ -685,10 +675,10 @@ void game_list_frame::ShowContextMenu(const QPoint &pos)
});
connect(removeGame, &QAction::triggered, [=]
{
if (currGame.path.empty())
{
LOG_FATAL(GENERAL, "Cannot remove game. Path is empty");
return;
if (currGame.path.empty())
{
LOG_FATAL(GENERAL, "Cannot remove game. Path is empty");
return;
}
QMessageBox* mb = new QMessageBox(QMessageBox::Question, tr("Confirm %1 Removal").arg(qstr(currGame.category)), tr("Permanently remove %0 from drive?\nPath: %1").arg(name).arg(qstr(currGame.path)), QMessageBox::Yes | QMessageBox::No, this);
@ -755,14 +745,14 @@ void game_list_frame::ShowContextMenu(const QPoint &pos)
connect(editNotes, &QAction::triggered, [=]
{
bool accepted;
const QString old_notes = m_gui_settings->GetValue(gui::notes, serial, "").toString();
const QString new_notes = QInputDialog::getMultiLineText(this, tr("Edit Tooltip Notes"), QString("%0\n%1").arg(name).arg(serial), old_notes, &accepted);
if (accepted)
{
m_notes[serial] = new_notes;
m_gui_settings->SetValue(gui::notes, serial, new_notes);
Refresh();
const QString old_notes = m_gui_settings->GetValue(gui::notes, serial, "").toString();
const QString new_notes = QInputDialog::getMultiLineText(this, tr("Edit Tooltip Notes"), QString("%0\n%1").arg(name).arg(serial), old_notes, &accepted);
if (accepted)
{
m_notes[serial] = new_notes;
m_gui_settings->SetValue(gui::notes, serial, new_notes);
Refresh();
}
});
connect(copy_info, &QAction::triggered, [=]
@ -843,18 +833,18 @@ bool game_list_frame::RemoveShadersCache(const std::string& base_dir, bool is_in
if (is_interactive && QMessageBox::question(this, tr("Confirm Removal"), tr("Remove shaders cache?")) != QMessageBox::Yes)
return false;
QDirIterator dir_iter(qstr(base_dir), QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (dir_iter.hasNext())
{
const QString filepath = dir_iter.next();
QDirIterator dir_iter(qstr(base_dir), QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (dir_iter.hasNext())
{
const QString filepath = dir_iter.next();
if (dir_iter.fileName() == "shaders_cache")
{
if (QDir(filepath).removeRecursively())
LOG_NOTICE(GENERAL, "Removed shaders cache dir: %s", sstr(filepath));
else
else
LOG_WARNING(GENERAL, "Could not remove shaders cache file: %s", sstr(filepath));
}
}
}
LOG_SUCCESS(GENERAL, "Removed shaders cache in %s", base_dir);
@ -869,18 +859,18 @@ bool game_list_frame::RemovePPUCache(const std::string& base_dir, bool is_intera
if (is_interactive && QMessageBox::question(this, tr("Confirm Removal"), tr("Remove PPU cache?")) != QMessageBox::Yes)
return false;
QDirIterator dir_iter(qstr(base_dir), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (dir_iter.hasNext())
{
const QString filepath = dir_iter.next();
QDirIterator dir_iter(qstr(base_dir), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (dir_iter.hasNext())
{
const QString filepath = dir_iter.next();
if (dir_iter.fileInfo().absoluteFilePath().endsWith(".obj", Qt::CaseInsensitive))
{
{
if (QFile::remove(filepath))
LOG_NOTICE(GENERAL, "Removed PPU cache file: %s", sstr(filepath));
else
LOG_WARNING(GENERAL, "Could not remove PPU cache file: %s", sstr(filepath));
}
else
LOG_WARNING(GENERAL, "Could not remove PPU cache file: %s", sstr(filepath));
}
}
LOG_SUCCESS(GENERAL, "Removed PPU cache in %s", base_dir);
@ -895,18 +885,18 @@ bool game_list_frame::RemoveSPUCache(const std::string& base_dir, bool is_intera
if (is_interactive && QMessageBox::question(this, tr("Confirm Removal"), tr("Remove SPU cache?")) != QMessageBox::Yes)
return false;
QDirIterator dir_iter(qstr(base_dir), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (dir_iter.hasNext())
{
const QString filepath = dir_iter.next();
QDirIterator dir_iter(qstr(base_dir), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (dir_iter.hasNext())
{
const QString filepath = dir_iter.next();
if (dir_iter.fileInfo().absoluteFilePath().endsWith(".dat", Qt::CaseInsensitive))
{
if (QFile::remove(filepath))
LOG_NOTICE(GENERAL, "Removed SPU cache file: %s", sstr(filepath));
else
else
LOG_WARNING(GENERAL, "Could not remove SPU cache file: %s", sstr(filepath));
}
}
}
LOG_SUCCESS(GENERAL, "Removed SPU cache in %s", base_dir);
@ -1078,57 +1068,57 @@ bool game_list_frame::eventFilter(QObject *object, QEvent *event)
return true;
}
}
else
{
if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return)
{
QTableWidgetItem* item;
if (object == m_gameList)
item = m_gameList->item(m_gameList->currentRow(), gui::column_icon);
else
item = m_xgrid->currentItem();
if (!item || !item->isSelected())
return false;
else
{
if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return)
{
QTableWidgetItem* item;
if (object == m_gameList)
item = m_gameList->item(m_gameList->currentRow(), gui::column_icon);
else
item = m_xgrid->currentItem();
if (!item || !item->isSelected())
return false;
game_info gameinfo = GetGameInfoFromItem(item);
if (gameinfo.get() == nullptr)
return false;
return false;
LOG_NOTICE(LOADER, "Booting from gamelist by pressing %s...", keyEvent->key() == Qt::Key_Enter ? "Enter" : "Return");
Q_EMIT RequestBoot(gameinfo->info.path);
return true;
}
Q_EMIT RequestBoot(gameinfo->info.path);
return true;
}
}
}
else if (event->type() == QEvent::ToolTip)
{
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
QTableWidgetItem* item;
if (m_isListLayout)
{
item = m_gameList->itemAt(helpEvent->globalPos());
}
else
{
item = m_xgrid->itemAt(helpEvent->globalPos());
}
if (item && !item->toolTip().isEmpty() && (!m_isListLayout || item->column() == gui::column_name || item->column() == gui::column_serial))
{
QToolTip::showText(helpEvent->globalPos(), item->toolTip());
}
else
{
QToolTip::hideText();
event->ignore();
}
return true;
else if (event->type() == QEvent::ToolTip)
{
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
QTableWidgetItem* item;
if (m_isListLayout)
{
item = m_gameList->itemAt(helpEvent->globalPos());
}
else
{
item = m_xgrid->itemAt(helpEvent->globalPos());
}
if (item && !item->toolTip().isEmpty() && (!m_isListLayout || item->column() == gui::column_name || item->column() == gui::column_serial))
{
QToolTip::showText(helpEvent->globalPos(), item->toolTip());
}
else
{
QToolTip::hideText();
event->ignore();
}
return true;
}
return QDockWidget::eventFilter(object, event);
@ -1174,11 +1164,11 @@ int game_list_frame::PopulateGameList()
// Serial
custom_table_widget_item* serial_item = new custom_table_widget_item(game->info.serial);
if (!notes.isEmpty())
{
const QString tool_tip = tr("%0 [%1]\n\nNotes:\n%2").arg(name).arg(serial).arg(notes);
title_item->setToolTip(tool_tip);
serial_item->setToolTip(tool_tip);
if (!notes.isEmpty())
{
const QString tool_tip = tr("%0 [%1]\n\nNotes:\n%2").arg(name).arg(serial).arg(notes);
title_item->setToolTip(tool_tip);
serial_item->setToolTip(tool_tip);
}
// Move Support (http://www.psdevwiki.com/ps3/PARAM.SFO#ATTRIBUTE)
@ -1277,13 +1267,13 @@ void game_list_frame::PopulateGameGrid(int maxCols, const QSize& image_size, con
m_xgrid->addItem(app->pxmap, title, r, c);
m_xgrid->item(r, c)->setData(gui::game_role, QVariant::fromValue(app));
if (!notes.isEmpty())
{
m_xgrid->item(r, c)->setToolTip(tr("%0 [%1]\n\nNotes:\n%2").arg(title).arg(serial).arg(notes));
if (!notes.isEmpty())
{
m_xgrid->item(r, c)->setToolTip(tr("%0 [%1]\n\nNotes:\n%2").arg(title).arg(serial).arg(notes));
}
else
{
m_xgrid->item(r, c)->setToolTip(tr("%0 [%1]").arg(title).arg(serial));
else
{
m_xgrid->item(r, c)->setToolTip(tr("%0 [%1]").arg(title).arg(serial));
}
if (selected_item == app->info.icon_path)

View File

@ -189,39 +189,35 @@ void main_window::SetAppIconFromPath(const std::string& path)
{
// get Icon for the gs_frame from path. this handles presumably all possible use cases
QString qpath = qstr(path);
std::string icon_list[] = { "/ICON0.PNG", "/PS3_GAME/ICON0.PNG" };
std::string path_list[] = { path, sstr(qpath.section("/", 0, -2)), sstr(qpath.section("/", 0, -3)) };
for (std::string pth : path_list)
{
if (!fs::is_dir(pth)) continue;
for (std::string ico : icon_list)
const std::string sfo_dir = Emu.GetSfoDirFromGamePath(path, Emu.GetUsr());
const std::string ico = sfo_dir + "/ICON0.PNG";
if (fs::is_file(ico))
{
ico = pth + ico;
if (fs::is_file(ico))
{
// load the image from path. It will most likely be a rectangle
QImage source = QImage(qstr(ico));
int edgeMax = std::max(source.width(), source.height());
// load the image from path. It will most likely be a rectangle
QImage source = QImage(qstr(ico));
int edgeMax = std::max(source.width(), source.height());
// create a new transparent image with square size and same format as source (maybe handle other formats than RGB32 as well?)
QImage::Format format = source.format() == QImage::Format_RGB32 ? QImage::Format_ARGB32 : source.format();
QImage dest = QImage(edgeMax, edgeMax, format);
dest.fill(QColor("transparent"));
// create a new transparent image with square size and same format as source (maybe handle other formats than RGB32 as well?)
QImage::Format format = source.format() == QImage::Format_RGB32 ? QImage::Format_ARGB32 : source.format();
QImage dest = QImage(edgeMax, edgeMax, format);
dest.fill(QColor("transparent"));
// get the location to draw the source image centered within the dest image.
QPoint destPos = source.width() > source.height() ? QPoint(0, (source.width() - source.height()) / 2)
: QPoint((source.height() - source.width()) / 2, 0);
// get the location to draw the source image centered within the dest image.
QPoint destPos = source.width() > source.height() ? QPoint(0, (source.width() - source.height()) / 2) : QPoint((source.height() - source.width()) / 2, 0);
// Paint the source into/over the dest
QPainter painter(&dest);
painter.drawImage(destPos, source);
painter.end();
// Paint the source into/over the dest
QPainter painter(&dest);
painter.drawImage(destPos, source);
painter.end();
// set Icon
m_appIcon = QIcon(QPixmap::fromImage(dest));
return;
}
// set Icon
m_appIcon = QIcon(QPixmap::fromImage(dest));
return;
}
}
// if nothing was found reset the icon to default
@ -1290,8 +1286,9 @@ void main_window::CreateConnects()
connect(ui->actionManage_Users, &QAction::triggered, [=]
{
user_manager_dialog* user_manager = new user_manager_dialog(guiSettings, this);
user_manager->show();
user_manager_dialog user_manager(guiSettings, this);
user_manager.exec();
m_gameListFrame->Refresh(true); // New user may have different games unlocked.
});
connect(ui->toolsCgDisasmAct, &QAction::triggered, [=]
@ -1832,6 +1829,9 @@ void main_window::dropEvent(QDropEvent* event)
LOG_SUCCESS(GENERAL, "Successfully copied rap file by drop: %s", rapname);
}
}
// Refresh game list since we probably unlocked some games now.
m_gameListFrame->Refresh(true);
break;
case drop_type::drop_dir: // import valid games to gamelist (games.yaml)
for (const auto& path : dropPaths)