mirror of
https://github.com/stenzek/duckstation.git
synced 2026-04-20 04:53:27 +00:00
GameList: Fix memcard icon extraction for non-database games
Also fix "Edit Memory Cards" menu option.
This commit is contained in:
@@ -1065,12 +1065,12 @@ GPUTexture* FullscreenUI::GetGameListCover(const GameList::Entry* entry, bool fa
|
||||
}
|
||||
|
||||
// because memcard icons are crap res
|
||||
if (fallback_to_icon && cover_it->second.empty())
|
||||
if (fallback_to_icon && cover_it->second.empty() && !entry->serial.empty() && entry->IsDiscOrDiscSet())
|
||||
{
|
||||
cover_it = s_game_list_locals.icon_image_map.find(entry->serial);
|
||||
if (cover_it == s_game_list_locals.icon_image_map.end())
|
||||
{
|
||||
std::string icon_path = GameList::GetGameIconPath(entry->serial, entry->path, entry->achievements_game_id);
|
||||
std::string icon_path = GameList::GetGameIconPath(entry);
|
||||
cover_it = s_game_list_locals.icon_image_map.emplace(entry->serial, std::move(icon_path)).first;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2026,28 +2026,29 @@ void GameList::ReloadMemcardTimestampCache()
|
||||
entry.serial[sizeof(entry.serial) - 1] = 0;
|
||||
}
|
||||
|
||||
std::string GameList::GetGameIconPath(std::string_view serial, std::string_view path, u32 achievements_game_id)
|
||||
std::string GameList::GetGameIconPath(const GameList::Entry* entry)
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
std::string fallback_path;
|
||||
if (achievements_game_id != 0)
|
||||
if (entry->achievements_game_id != 0)
|
||||
{
|
||||
fallback_path = GetAchievementGameBadgePath(achievements_game_id);
|
||||
fallback_path = GetAchievementGameBadgePath(entry->achievements_game_id);
|
||||
if (!fallback_path.empty() && PreferAchievementGameBadgesForIcons())
|
||||
return (ret = std::move(fallback_path));
|
||||
}
|
||||
|
||||
if (serial.empty())
|
||||
if (entry->serial.empty())
|
||||
return (ret = std::move(fallback_path));
|
||||
|
||||
// might exist already, or the user used a custom icon
|
||||
ret = Path::Combine(EmuFolders::GameIcons, TinyString::from_format("{}.png", serial));
|
||||
ret = Path::Combine(EmuFolders::GameIcons, TinyString::from_format("{}.png", entry->serial));
|
||||
if (FileSystem::FileExists(ret.c_str()))
|
||||
return ret;
|
||||
|
||||
MemoryCardType type;
|
||||
std::string memcard_path = System::GetGameMemoryCardPath(serial, path, 0, &type);
|
||||
std::string memcard_path =
|
||||
System::GetGameMemoryCardPath(entry->title, entry->has_custom_title, entry->serial, entry->path, 0, &type);
|
||||
FILESYSTEM_STAT_DATA memcard_sd;
|
||||
if (memcard_path.empty() || type == MemoryCardType::Shared ||
|
||||
!FileSystem::StatFile(memcard_path.c_str(), &memcard_sd))
|
||||
@@ -2057,33 +2058,33 @@ std::string GameList::GetGameIconPath(std::string_view serial, std::string_view
|
||||
|
||||
const s64 timestamp = memcard_sd.ModificationTime;
|
||||
TinyString index_serial;
|
||||
index_serial.assign(
|
||||
serial.substr(0, std::min<size_t>(serial.length(), MemcardTimestampCacheEntry::MAX_SERIAL_LENGTH - 1)));
|
||||
index_serial.assign(entry->serial.substr(
|
||||
0, std::min<size_t>(entry->serial.length(), MemcardTimestampCacheEntry::MAX_SERIAL_LENGTH - 1)));
|
||||
|
||||
MemcardTimestampCacheEntry* serial_entry = nullptr;
|
||||
for (MemcardTimestampCacheEntry& entry : s_state.memcard_timestamp_cache_entries)
|
||||
MemcardTimestampCacheEntry* cache_entry = nullptr;
|
||||
for (MemcardTimestampCacheEntry& it : s_state.memcard_timestamp_cache_entries)
|
||||
{
|
||||
if (StringUtil::EqualNoCase(index_serial, entry.serial))
|
||||
if (StringUtil::EqualNoCase(index_serial, it.serial))
|
||||
{
|
||||
// user might've deleted the file, so re-extract it if so
|
||||
// otherwise, card hasn't changed, still no icon
|
||||
if (entry.memcard_timestamp == timestamp && !entry.icon_was_extracted)
|
||||
if (it.memcard_timestamp == timestamp && !it.icon_was_extracted)
|
||||
return (ret = std::move(fallback_path));
|
||||
|
||||
serial_entry = &entry;
|
||||
cache_entry = ⁢
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!serial_entry)
|
||||
if (!cache_entry)
|
||||
{
|
||||
serial_entry = &s_state.memcard_timestamp_cache_entries.emplace_back();
|
||||
std::memset(serial_entry, 0, sizeof(MemcardTimestampCacheEntry));
|
||||
cache_entry = &s_state.memcard_timestamp_cache_entries.emplace_back();
|
||||
std::memset(cache_entry, 0, sizeof(MemcardTimestampCacheEntry));
|
||||
}
|
||||
|
||||
serial_entry->memcard_timestamp = timestamp;
|
||||
serial_entry->icon_was_extracted = false;
|
||||
StringUtil::Strlcpy(serial_entry->serial, index_serial.view(), sizeof(serial_entry->serial));
|
||||
cache_entry->memcard_timestamp = timestamp;
|
||||
cache_entry->icon_was_extracted = false;
|
||||
StringUtil::Strlcpy(cache_entry->serial, index_serial.view(), sizeof(cache_entry->serial));
|
||||
|
||||
// Try extracting an icon.
|
||||
Error error;
|
||||
@@ -2105,8 +2106,8 @@ std::string GameList::GetGameIconPath(std::string_view serial, std::string_view
|
||||
for (size_t i = 0; i < fi.icon_frames.size(); i++)
|
||||
image.SetPixels(static_cast<u32>(i), fi.icon_frames[i].pixels, MemoryCardImage::ICON_WIDTH * sizeof(u32));
|
||||
|
||||
serial_entry->icon_was_extracted = image.SaveToFile(ret.c_str(), AnimatedImage::DEFAULT_SAVE_QUALITY, &error);
|
||||
if (serial_entry->icon_was_extracted)
|
||||
cache_entry->icon_was_extracted = image.SaveToFile(ret.c_str(), AnimatedImage::DEFAULT_SAVE_QUALITY, &error);
|
||||
if (cache_entry->icon_was_extracted)
|
||||
return ret;
|
||||
else
|
||||
ERROR_LOG("Failed to save memory card icon to {}: {}", Path::GetFileName(ret), error.GetDescription());
|
||||
@@ -2118,7 +2119,7 @@ std::string GameList::GetGameIconPath(std::string_view serial, std::string_view
|
||||
ERROR_LOG("Failed to load memory card '{}': {}", Path::GetFileName(memcard_path), error.GetDescription());
|
||||
}
|
||||
|
||||
UpdateMemcardTimestampCache(*serial_entry);
|
||||
UpdateMemcardTimestampCache(*cache_entry);
|
||||
return (ret = std::move(fallback_path));
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ struct Entry
|
||||
ALWAYS_INLINE bool IsValid() const { return (type < EntryType::MaxCount); }
|
||||
ALWAYS_INLINE bool IsDisc() const { return (type == EntryType::Disc); }
|
||||
ALWAYS_INLINE bool IsDiscSet() const { return (type == EntryType::DiscSet); }
|
||||
ALWAYS_INLINE bool IsDiscOrDiscSet() const { return (type == EntryType::Disc || type == EntryType::DiscSet); }
|
||||
ALWAYS_INLINE bool HasCustomLanguage() const { return (custom_language != GameDatabase::Language::MaxCount); }
|
||||
ALWAYS_INLINE EntryType GetSortType() const { return (type == EntryType::DiscSet) ? EntryType::Disc : type; }
|
||||
ALWAYS_INLINE const GameDatabase::DiscSetEntry* GetDiscSetEntry() const
|
||||
@@ -170,7 +171,7 @@ std::optional<DiscRegion> GetCustomRegionForPath(const std::string_view path);
|
||||
/// The purpose of this cache is to stop us trying to constantly extract memory card icons, when we know a game
|
||||
/// doesn't have any saves yet. It caches the serial:memcard_timestamp pair, and only tries extraction when the
|
||||
/// timestamp of the memory card has changed.
|
||||
std::string GetGameIconPath(std::string_view serial, std::string_view path, u32 achievements_game_id);
|
||||
std::string GetGameIconPath(const GameList::Entry* entry);
|
||||
void ReloadMemcardTimestampCache();
|
||||
|
||||
/// Updates game list with new achievement unlocks.
|
||||
|
||||
@@ -5761,8 +5761,8 @@ void System::DeleteSaveStates(std::string_view serial, bool resume)
|
||||
}
|
||||
}
|
||||
|
||||
std::string System::GetGameMemoryCardPath(std::string_view serial, std::string_view path, u32 slot,
|
||||
MemoryCardType* out_type)
|
||||
std::string System::GetGameMemoryCardPath(std::string_view title, bool is_custom_title, std::string_view serial,
|
||||
std::string_view path, u32 slot, MemoryCardType* out_type /* = nullptr */)
|
||||
{
|
||||
const char* section = "MemoryCards";
|
||||
const TinyString type_key = TinyString::from_format("Card{}Type", slot + 1);
|
||||
@@ -5821,10 +5821,8 @@ std::string System::GetGameMemoryCardPath(std::string_view serial, std::string_v
|
||||
|
||||
case MemoryCardType::PerGameTitle:
|
||||
{
|
||||
const std::string custom_title = GameList::GetCustomTitleForPath(path);
|
||||
const GameDatabase::Entry* entry = GameDatabase::GetEntryForSerial(serial);
|
||||
const std::string_view game_title =
|
||||
(!custom_title.empty() || !entry) ? std::string_view(custom_title) : entry->GetSaveTitle();
|
||||
const std::string_view game_title = (is_custom_title || !entry) ? title : entry->GetSaveTitle();
|
||||
|
||||
// Multi-disc game - use disc set name.
|
||||
if (entry && entry->disc_set)
|
||||
|
||||
@@ -404,8 +404,8 @@ std::optional<ExtendedSaveStateInfo> GetExtendedSaveStateInfo(const char* path);
|
||||
void DeleteSaveStates(std::string_view serial, bool resume);
|
||||
|
||||
/// Returns the path to the memory card for the specified game, considering game settings.
|
||||
std::string GetGameMemoryCardPath(std::string_view serial, std::string_view path, u32 slot,
|
||||
MemoryCardType* out_type = nullptr);
|
||||
std::string GetGameMemoryCardPath(std::string_view title, bool is_custom_title, std::string_view serial,
|
||||
std::string_view path, u32 slot, MemoryCardType* out_type = nullptr);
|
||||
|
||||
/// Returns intended output volume considering fast forwarding.
|
||||
u8 GetAudioOutputVolume();
|
||||
|
||||
@@ -610,7 +610,7 @@ const QPixmap& GameListModel::getCoverForEntry(const GameList::Entry* ge) const
|
||||
const QPixmap* GameListModel::lookupIconPixmapForEntry(const GameList::Entry* ge) const
|
||||
{
|
||||
// We only do this for discs/disc sets for now.
|
||||
if (m_show_game_icons && (!ge->serial.empty() && (ge->IsDisc() || ge->IsDiscSet())))
|
||||
if (m_show_game_icons && !ge->serial.empty() && ge->IsDiscOrDiscSet())
|
||||
{
|
||||
QPixmap* item = m_icon_pixmap_cache.Lookup(ge->serial);
|
||||
if (item)
|
||||
@@ -621,7 +621,7 @@ const QPixmap* GameListModel::lookupIconPixmapForEntry(const GameList::Entry* ge
|
||||
else
|
||||
{
|
||||
// Assumes game list lock is held.
|
||||
const std::string path = GameList::GetGameIconPath(ge->serial, ge->path, ge->achievements_game_id);
|
||||
const std::string path = GameList::GetGameIconPath(ge);
|
||||
QPixmap pm;
|
||||
if (!path.empty() && pm.load(QString::fromStdString(path)))
|
||||
{
|
||||
@@ -682,7 +682,7 @@ QIcon GameListModel::getIconForGame(const QString& path)
|
||||
|
||||
const auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = GameList::GetEntryForPath(path.toStdString());
|
||||
if (!entry || entry->serial.empty() || (!entry->IsDisc() && !entry->IsDiscSet()))
|
||||
if (!entry || entry->serial.empty() || !entry->IsDiscOrDiscSet())
|
||||
return ret;
|
||||
|
||||
// Only use the cache if we're not using larger icons. Otherwise they'll get double scaled.
|
||||
@@ -697,7 +697,7 @@ QIcon GameListModel::getIconForGame(const QString& path)
|
||||
}
|
||||
}
|
||||
|
||||
const std::string icon_path = GameList::GetGameIconPath(entry->serial, entry->path, entry->achievements_game_id);
|
||||
const std::string icon_path = GameList::GetGameIconPath(entry);
|
||||
if (!icon_path.empty())
|
||||
ret = QIcon(QString::fromStdString(icon_path));
|
||||
|
||||
@@ -1535,7 +1535,7 @@ public:
|
||||
{
|
||||
DebugAssert(source_row >= 0);
|
||||
|
||||
const std::string icon_path = GameList::GetGameIconPath(entry->serial, entry->path, entry->achievements_game_id);
|
||||
const std::string icon_path = GameList::GetGameIconPath(entry);
|
||||
if (icon_path.empty())
|
||||
{
|
||||
clearEntry();
|
||||
|
||||
@@ -531,6 +531,7 @@ void MainWindow::updateGameListRelatedActions()
|
||||
m_ui.actionViewZoomIn->setDisabled(disable);
|
||||
m_ui.actionViewZoomOut->setDisabled(disable);
|
||||
m_ui.actionGridViewRefreshCovers->setDisabled(disable || !game_grid);
|
||||
m_ui.actionPreferAchievementGameIcons->setDisabled(disable || !game_list);
|
||||
m_ui.actionChangeGameListBackground->setDisabled(disable);
|
||||
m_ui.actionClearGameListBackground->setDisabled(disable || !has_background);
|
||||
}
|
||||
@@ -909,7 +910,10 @@ void MainWindow::populateGameListContextMenu(const GameList::Entry* entry, QWidg
|
||||
|
||||
QString paths[2];
|
||||
for (u32 i = 0; i < 2; i++)
|
||||
paths[i] = QString::fromStdString(System::GetGameMemoryCardPath(entry->serial, entry->path, i));
|
||||
{
|
||||
paths[i] = QString::fromStdString(
|
||||
System::GetGameMemoryCardPath(entry->title, entry->has_custom_title, entry->serial, entry->path, i));
|
||||
}
|
||||
|
||||
g_main_window->openMemoryCardEditor(paths[0], paths[1]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user