From 36643fe78b2b0cae658f156a5f31e3884e9be8f0 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Wed, 20 Dec 2023 23:40:24 +1000 Subject: [PATCH] GameList: Add "File Size" field --- src/core/fullscreen_ui.cpp | 34 ++++++++++++++++++++------ src/core/game_list.cpp | 16 +++++++----- src/core/game_list.h | 3 ++- src/duckstation-qt/gamelistmodel.cpp | 35 ++++++++++++++++++++------- src/duckstation-qt/gamelistmodel.h | 3 ++- src/duckstation-qt/gamelistwidget.cpp | 4 ++- src/util/cd_image.cpp | 5 ++++ src/util/cd_image.h | 4 +++ src/util/cd_image_bin.cpp | 7 ++++++ src/util/cd_image_chd.cpp | 6 +++++ src/util/cd_image_cue.cpp | 10 ++++++++ src/util/cd_image_ecm.cpp | 6 +++++ src/util/cd_image_mds.cpp | 6 +++++ src/util/cd_image_pbp.cpp | 6 +++++ src/util/cd_image_ppf.cpp | 9 +++++++ 15 files changed, 128 insertions(+), 26 deletions(-) diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 3d064422c..4dd0ec3eb 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -5717,11 +5717,21 @@ void FullscreenUI::PopulateGameListEntryList() } break; - case 6: // Size + case 6: // File Size { - if (lhs->total_size != rhs->total_size) + if (lhs->file_size != rhs->file_size) { - return reverse ? (lhs->total_size > rhs->total_size) : (lhs->total_size < rhs->total_size); + return reverse ? (lhs->file_size > rhs->file_size) : (lhs->file_size < rhs->file_size); + } + } + break; + + case 7: // Uncompressed Size + { + if (lhs->uncompressed_size != rhs->uncompressed_size) + { + return reverse ? (lhs->uncompressed_size > rhs->uncompressed_size) : + (lhs->uncompressed_size < rhs->uncompressed_size); } } break; @@ -5975,7 +5985,12 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) ImGui::Text(FSUI_CSTR("Last Played: %s"), GameList::FormatTimestamp(selected_entry->last_played_time).c_str()); // size - ImGui::Text(FSUI_CSTR("Size: %.2f MB"), static_cast(selected_entry->total_size) / 1048576.0f); + if (selected_entry->file_size >= 0) + ImGui::Text(FSUI_CSTR("File Size: %.2f MB"), static_cast(selected_entry->file_size) / 1048576.0f); + else + ImGui::TextUnformatted(FSUI_CSTR("Unknown File Size")); + ImGui::Text(FSUI_CSTR("Uncompressed Size: %.2f MB"), + static_cast(selected_entry->uncompressed_size) / 1048576.0f); ImGui::PopFont(); } @@ -6275,8 +6290,8 @@ void FullscreenUI::DrawGameListSettingsPage(const ImVec2& heading_size) { static constexpr const char* view_types[] = {FSUI_NSTR("Game Grid"), FSUI_NSTR("Game List")}; static constexpr const char* sort_types[] = { - FSUI_NSTR("Type"), FSUI_NSTR("Serial"), FSUI_NSTR("Title"), FSUI_NSTR("File Title"), - FSUI_NSTR("Time Played"), FSUI_NSTR("Last Played"), FSUI_NSTR("Size")}; + FSUI_NSTR("Type"), FSUI_NSTR("Serial"), FSUI_NSTR("Title"), FSUI_NSTR("File Title"), + FSUI_NSTR("Time Played"), FSUI_NSTR("Last Played"), FSUI_NSTR("File Size"), FSUI_NSTR("Uncompressed Size")}; DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_BORDER_ALL, "Default View"), FSUI_CSTR("Selects the view that the game list will open to."), "Main", @@ -6969,6 +6984,8 @@ TRANSLATE_NOOP("FullscreenUI", "Failed to save input profile '{}'."); TRANSLATE_NOOP("FullscreenUI", "Fast Boot"); TRANSLATE_NOOP("FullscreenUI", "Fast Forward Speed"); TRANSLATE_NOOP("FullscreenUI", "Fast Forward Volume"); +TRANSLATE_NOOP("FullscreenUI", "File Size"); +TRANSLATE_NOOP("FullscreenUI", "File Size: %.2f MB"); TRANSLATE_NOOP("FullscreenUI", "File Title"); TRANSLATE_NOOP("FullscreenUI", "Force 4:3 For 24-Bit Display"); TRANSLATE_NOOP("FullscreenUI", "Force NTSC Timings"); @@ -7231,8 +7248,6 @@ TRANSLATE_NOOP("FullscreenUI", "Shows the number of frames (or v-syncs) displaye TRANSLATE_NOOP("FullscreenUI", "Simulates the CPU's instruction cache in the recompiler. Can help with games running too fast."); TRANSLATE_NOOP("FullscreenUI", "Simulates the region check present in original, unmodified consoles."); TRANSLATE_NOOP("FullscreenUI", "Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements."); -TRANSLATE_NOOP("FullscreenUI", "Size"); -TRANSLATE_NOOP("FullscreenUI", "Size: %.2f MB"); TRANSLATE_NOOP("FullscreenUI", "Slow Boot"); TRANSLATE_NOOP("FullscreenUI", "Smooths out blockyness between colour transitions in 24-bit content, usually FMVs. Only applies to the hardware renderers."); TRANSLATE_NOOP("FullscreenUI", "Smooths out the blockiness of magnified textures on 3D objects."); @@ -7283,8 +7298,11 @@ TRANSLATE_NOOP("FullscreenUI", "True Color Rendering"); TRANSLATE_NOOP("FullscreenUI", "Turbo Speed"); TRANSLATE_NOOP("FullscreenUI", "Type"); TRANSLATE_NOOP("FullscreenUI", "UI Language"); +TRANSLATE_NOOP("FullscreenUI", "Uncompressed Size"); +TRANSLATE_NOOP("FullscreenUI", "Uncompressed Size: %.2f MB"); TRANSLATE_NOOP("FullscreenUI", "Undo Load State"); TRANSLATE_NOOP("FullscreenUI", "Unknown"); +TRANSLATE_NOOP("FullscreenUI", "Unknown File Size"); TRANSLATE_NOOP("FullscreenUI", "Unlimited"); TRANSLATE_NOOP("FullscreenUI", "Use Blit Swap Chain"); TRANSLATE_NOOP("FullscreenUI", "Use Debug GPU Device"); diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 1c5112717..598c7c6c6 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -155,7 +155,8 @@ bool GameList::GetExeListEntry(const std::string& path, GameList::Entry* entry) entry->serial.clear(); entry->title = Path::GetFileTitle(display_name); entry->region = BIOS::GetPSExeDiscRegion(header); - entry->total_size = ZeroExtend64(file_size); + entry->file_size = ZeroExtend64(file_size); + entry->uncompressed_size = entry->file_size; entry->type = EntryType::PSExe; entry->compatibility = GameDatabase::CompatibilityRating::Unknown; @@ -171,7 +172,8 @@ bool GameList::GetPsfListEntry(const std::string& path, Entry* entry) entry->serial.clear(); entry->region = file.GetRegion(); - entry->total_size = static_cast(file.GetProgramData().size()); + entry->file_size = static_cast(file.GetProgramData().size()); + entry->uncompressed_size = entry->file_size; entry->type = EntryType::PSF; entry->compatibility = GameDatabase::CompatibilityRating::Unknown; @@ -208,7 +210,8 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry) return false; entry->path = path; - entry->total_size = static_cast(CDImage::RAW_SECTOR_SIZE) * static_cast(cdi->GetLBACount()); + entry->file_size = cdi->GetSizeOnDisk(); + entry->uncompressed_size = static_cast(CDImage::RAW_SECTOR_SIZE) * static_cast(cdi->GetLBACount()); entry->type = EntryType::Disc; entry->compatibility = GameDatabase::CompatibilityRating::Unknown; @@ -283,7 +286,7 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry) continue; } - entry->total_size += static_cast(CDImage::RAW_SECTOR_SIZE) * static_cast(cdi->GetLBACount()); + entry->uncompressed_size += static_cast(CDImage::RAW_SECTOR_SIZE) * static_cast(cdi->GetLBACount()); } } @@ -333,7 +336,7 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream) !stream->ReadSizePrefixedString(&ge.serial) || !stream->ReadSizePrefixedString(&ge.title) || !stream->ReadSizePrefixedString(&ge.disc_set_name) || !stream->ReadSizePrefixedString(&ge.genre) || !stream->ReadSizePrefixedString(&ge.publisher) || !stream->ReadSizePrefixedString(&ge.developer) || - !stream->ReadU64(&ge.hash) || !stream->ReadU64(&ge.total_size) || + !stream->ReadU64(&ge.hash) || !stream->ReadS64(&ge.file_size) || !stream->ReadU64(&ge.uncompressed_size) || !stream->ReadU64(reinterpret_cast(&ge.last_modified_time)) || !stream->ReadU64(&ge.release_date) || !stream->ReadU16(&ge.supported_controllers) || !stream->ReadU8(&ge.min_players) || !stream->ReadU8(&ge.max_players) || !stream->ReadU8(&ge.min_blocks) || !stream->ReadU8(&ge.max_blocks) || @@ -373,7 +376,8 @@ bool GameList::WriteEntryToCache(const Entry* entry) result &= s_cache_write_stream->WriteSizePrefixedString(entry->publisher); result &= s_cache_write_stream->WriteSizePrefixedString(entry->developer); result &= s_cache_write_stream->WriteU64(entry->hash); - result &= s_cache_write_stream->WriteU64(entry->total_size); + result &= s_cache_write_stream->WriteS64(entry->file_size); + result &= s_cache_write_stream->WriteU64(entry->uncompressed_size); result &= s_cache_write_stream->WriteU64(entry->last_modified_time); result &= s_cache_write_stream->WriteU64(entry->release_date); result &= s_cache_write_stream->WriteU16(entry->supported_controllers); diff --git a/src/core/game_list.h b/src/core/game_list.h index e5e56c9c0..e3a95536f 100644 --- a/src/core/game_list.h +++ b/src/core/game_list.h @@ -44,7 +44,8 @@ struct Entry std::string publisher; std::string developer; u64 hash = 0; - u64 total_size = 0; + s64 file_size = 0; + u64 uncompressed_size = 0; std::time_t last_modified_time = 0; std::time_t last_played_time = 0; std::time_t total_played_time = 0; diff --git a/src/duckstation-qt/gamelistmodel.cpp b/src/duckstation-qt/gamelistmodel.cpp index a87973257..fa732698e 100644 --- a/src/duckstation-qt/gamelistmodel.cpp +++ b/src/duckstation-qt/gamelistmodel.cpp @@ -19,7 +19,7 @@ static constexpr std::array s_column_names = { {"Type", "Serial", "Title", "File Title", "Developer", "Publisher", "Genre", "Year", "Players", "Time Played", - "Last Played", "Size", "Region", "Compatibility", "Cover"}}; + "Last Played", "Size", "File Size", "Region", "Compatibility", "Cover"}}; static constexpr int COVER_ART_WIDTH = 512; static constexpr int COVER_ART_HEIGHT = 512; @@ -303,8 +303,13 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const return QStringLiteral("%1-%2").arg(ge->min_players).arg(ge->max_players); } - case Column_Size: - return QString("%1 MB").arg(static_cast(ge->total_size) / 1048576.0, 0, 'f', 2); + case Column_FileSize: + return (ge->file_size >= 0) ? + QString("%1 MB").arg(static_cast(ge->file_size) / 1048576.0, 0, 'f', 2) : + tr("Unknown"); + + case Column_UncompressedSize: + return QString("%1 MB").arg(static_cast(ge->uncompressed_size) / 1048576.0, 0, 'f', 2); case Column_TimePlayed: { @@ -374,8 +379,11 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const case Column_LastPlayed: return static_cast(ge->last_played_time); - case Column_Size: - return static_cast(ge->total_size); + case Column_FileSize: + return static_cast(ge->file_size); + + case Column_UncompressedSize: + return static_cast(ge->uncompressed_size); default: return {}; @@ -519,12 +527,20 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r return (static_cast(left->compatibility) < static_cast(right->compatibility)); } - case Column_Size: + case Column_FileSize: + { + if (left->file_size == right->file_size) + return titlesLessThan(left_row, right_row); + + return (left->file_size < right->file_size); + } + + case Column_UncompressedSize: { - if (left->total_size == right->total_size) + if (left->uncompressed_size == right->uncompressed_size) return titlesLessThan(left_row, right_row); - return (left->total_size < right->total_size); + return (left->uncompressed_size < right->uncompressed_size); } case Column_Genre: @@ -622,7 +638,8 @@ void GameListModel::setColumnDisplayNames() m_column_display_names[Column_Players] = tr("Players"); m_column_display_names[Column_TimePlayed] = tr("Time Played"); m_column_display_names[Column_LastPlayed] = tr("Last Played"); - m_column_display_names[Column_Size] = tr("Size"); + m_column_display_names[Column_FileSize] = tr("Size"); + m_column_display_names[Column_UncompressedSize] = tr("Raw Size"); m_column_display_names[Column_Region] = tr("Region"); m_column_display_names[Column_Compatibility] = tr("Compatibility"); } diff --git a/src/duckstation-qt/gamelistmodel.h b/src/duckstation-qt/gamelistmodel.h index ac5ef1fd3..c3e8c51d0 100644 --- a/src/duckstation-qt/gamelistmodel.h +++ b/src/duckstation-qt/gamelistmodel.h @@ -34,7 +34,8 @@ public: Column_Players, Column_TimePlayed, Column_LastPlayed, - Column_Size, + Column_FileSize, + Column_UncompressedSize, Column_Region, Column_Compatibility, Column_Cover, diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp index 8990fdfd6..9987fe265 100644 --- a/src/duckstation-qt/gamelistwidget.cpp +++ b/src/duckstation-qt/gamelistwidget.cpp @@ -473,6 +473,7 @@ void GameListWidget::resizeTableViewColumnsToFit() 100, // players 80, // time played 80, // last played + 80, // file size 80, // size 50, // region 100 // compatibility @@ -498,7 +499,8 @@ void GameListWidget::loadTableViewColumnVisibilitySettings() false, // players true, // time played true, // last played - true, // size + true, // file size + false, // size true, // region true // compatibility }}; diff --git a/src/util/cd_image.cpp b/src/util/cd_image.cpp index b28c41cb6..4dc5247c2 100644 --- a/src/util/cd_image.cpp +++ b/src/util/cd_image.cpp @@ -386,6 +386,11 @@ bool CDImage::IsPrecached() const return false; } +s64 CDImage::GetSizeOnDisk() const +{ + return -1; +} + void CDImage::ClearTOC() { m_lba_count = 0; diff --git a/src/util/cd_image.h b/src/util/cd_image.h index 824bdba6b..f73b57e3f 100644 --- a/src/util/cd_image.h +++ b/src/util/cd_image.h @@ -328,6 +328,10 @@ public: virtual PrecacheResult Precache(ProgressCallback* progress = ProgressCallback::NullProgressCallback); virtual bool IsPrecached() const; + // Returns the size on disk of the image. This could be multiple files. + // If this function returns -1, it means the size could not be computed. + virtual s64 GetSizeOnDisk() const; + protected: void ClearTOC(); void CopyTOC(const CDImage* image); diff --git a/src/util/cd_image_bin.cpp b/src/util/cd_image_bin.cpp index f0f55c2d0..32616b880 100644 --- a/src/util/cd_image_bin.cpp +++ b/src/util/cd_image_bin.cpp @@ -25,6 +25,8 @@ public: bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; + s64 GetSizeOnDisk() const override; + protected: bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; @@ -145,6 +147,11 @@ bool CDImageBin::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_i return true; } +s64 CDImageBin::GetSizeOnDisk() const +{ + return FileSystem::FSize64(m_fp); +} + std::unique_ptr CDImage::OpenBinImage(const char* filename, Error* error) { std::unique_ptr image = std::make_unique(); diff --git a/src/util/cd_image_chd.cpp b/src/util/cd_image_chd.cpp index 952401f88..c715f51f5 100644 --- a/src/util/cd_image_chd.cpp +++ b/src/util/cd_image_chd.cpp @@ -68,6 +68,7 @@ public: bool HasNonStandardSubchannel() const override; PrecacheResult Precache(ProgressCallback* progress) override; bool IsPrecached() const override; + s64 GetSizeOnDisk() const override; protected: bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; @@ -571,6 +572,11 @@ ALWAYS_INLINE_RELEASE bool CDImageCHD::UpdateHunkBuffer(const Index& index, LBA return true; } +s64 CDImageCHD::GetSizeOnDisk() const +{ + return static_cast(chd_get_compressed_size(m_chd)); +} + std::unique_ptr CDImage::OpenCHDImage(const char* filename, Error* error) { std::unique_ptr image = std::make_unique(); diff --git a/src/util/cd_image_cue.cpp b/src/util/cd_image_cue.cpp index 037dfc14a..111c722f4 100644 --- a/src/util/cd_image_cue.cpp +++ b/src/util/cd_image_cue.cpp @@ -32,6 +32,7 @@ public: bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; + s64 GetSizeOnDisk() const override; protected: bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; @@ -339,6 +340,15 @@ bool CDImageCueSheet::ReadSectorFromIndex(void* buffer, const Index& index, LBA return true; } +s64 CDImageCueSheet::GetSizeOnDisk() const +{ + // Doesn't include the cue.. but they're tiny anyway, whatever. + u64 size = 0; + for (const TrackFile& tf : m_files) + size += FileSystem::FSize64(tf.file); + return size; +} + std::unique_ptr CDImage::OpenCueSheetImage(const char* filename, Error* error) { std::unique_ptr image = std::make_unique(); diff --git a/src/util/cd_image_ecm.cpp b/src/util/cd_image_ecm.cpp index 238f52f05..2a448e751 100644 --- a/src/util/cd_image_ecm.cpp +++ b/src/util/cd_image_ecm.cpp @@ -170,6 +170,7 @@ public: bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; + s64 GetSizeOnDisk() const override; protected: bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; @@ -542,6 +543,11 @@ bool CDImageEcm::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_i return true; } +s64 CDImageEcm::GetSizeOnDisk() const +{ + return FileSystem::FSize64(m_fp); +} + std::unique_ptr CDImage::OpenEcmImage(const char* filename, Error* error) { std::unique_ptr image = std::make_unique(); diff --git a/src/util/cd_image_mds.cpp b/src/util/cd_image_mds.cpp index 85177d7a0..89e7d9e58 100644 --- a/src/util/cd_image_mds.cpp +++ b/src/util/cd_image_mds.cpp @@ -48,6 +48,7 @@ public: bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; + s64 GetSizeOnDisk() const override; protected: bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; @@ -285,6 +286,11 @@ bool CDImageMds::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_i return true; } +s64 CDImageMds::GetSizeOnDisk() const +{ + return FileSystem::FSize64(m_mdf_file); +} + std::unique_ptr CDImage::OpenMdsImage(const char* filename, Error* error) { std::unique_ptr image = std::make_unique(); diff --git a/src/util/cd_image_pbp.cpp b/src/util/cd_image_pbp.cpp index fa5b82ed7..1a92bf6cb 100644 --- a/src/util/cd_image_pbp.cpp +++ b/src/util/cd_image_pbp.cpp @@ -135,6 +135,7 @@ public: bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; + s64 GetSizeOnDisk() const override; bool HasSubImages() const override; u32 GetSubImageCount() const override; @@ -960,6 +961,11 @@ std::string CDImagePBP::GetSubImageMetadata(u32 index, const std::string_view& t return CDImage::GetSubImageMetadata(index, type); } +s64 CDImagePBP::GetSizeOnDisk() const +{ + return FileSystem::FSize64(m_file); +} + std::unique_ptr CDImage::OpenPBPImage(const char* filename, Error* error) { std::unique_ptr image = std::make_unique(); diff --git a/src/util/cd_image_ppf.cpp b/src/util/cd_image_ppf.cpp index 7bd35f505..a80b97ce4 100644 --- a/src/util/cd_image_ppf.cpp +++ b/src/util/cd_image_ppf.cpp @@ -33,6 +33,7 @@ public: bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; + s64 GetSizeOnDisk() const override; std::string GetMetadata(const std::string_view& type) const override; std::string GetSubImageMetadata(u32 index, const std::string_view& type) const override; @@ -53,6 +54,7 @@ private: std::unique_ptr m_parent_image; std::vector m_replacement_data; std::unordered_map m_replacement_map; + s64 m_patch_size = 0; u32 m_replacement_offset = 0; }; @@ -71,6 +73,8 @@ bool CDImagePPF::Open(const char* filename, std::unique_ptr parent_imag return false; } + m_patch_size = FileSystem::FSize64(fp.get()); + u32 magic; if (std::fread(&magic, sizeof(magic), 1, fp.get()) != 1) { @@ -445,6 +449,11 @@ bool CDImagePPF::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_i return true; } +s64 CDImagePPF::GetSizeOnDisk() const +{ + return m_patch_size + m_parent_image->GetSizeOnDisk(); +} + std::unique_ptr CDImage::OverlayPPFPatch(const char* filename, std::unique_ptr parent_image, ProgressCallback* progress /* = ProgressCallback::NullProgressCallback */)