diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index eca343be85..47a653d927 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -193,7 +193,7 @@ static bool LoadBanner(std::string filename, u32* Banner) if (pVolume != nullptr) { - int Width, Height; + u32 Width, Height; std::vector BannerVec = pVolume->GetBanner(&Width, &Height); // This code (along with above inlines) is moved from // elsewhere. Someone who knows anything about Android diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h index f7172db6cc..f1625a9681 100644 --- a/Source/Core/DiscIO/Volume.h +++ b/Source/Core/DiscIO/Volume.h @@ -83,7 +83,7 @@ public: virtual std::map GetShortMakers() const { return {}; } virtual std::map GetLongMakers() const { return {}; } virtual std::map GetDescriptions() const { return {}; } - virtual std::vector GetBanner(int* width, int* height) const = 0; + virtual std::vector GetBanner(u32* width, u32* height) const = 0; std::string GetApploaderDate() const { return GetApploaderDate(GetGamePartition()); } virtual std::string GetApploaderDate(const Partition& partition) const = 0; // 0 is the first disc, 1 is the second disc diff --git a/Source/Core/DiscIO/VolumeGC.cpp b/Source/Core/DiscIO/VolumeGC.cpp index 12c16e10ad..eda59fff08 100644 --- a/Source/Core/DiscIO/VolumeGC.cpp +++ b/Source/Core/DiscIO/VolumeGC.cpp @@ -150,7 +150,7 @@ std::map VolumeGC::GetDescriptions() const return m_converted_banner->descriptions; } -std::vector VolumeGC::GetBanner(int* width, int* height) const +std::vector VolumeGC::GetBanner(u32* width, u32* height) const { *width = m_converted_banner->image_width; *height = m_converted_banner->image_height; diff --git a/Source/Core/DiscIO/VolumeGC.h b/Source/Core/DiscIO/VolumeGC.h index 4f878c3bc9..3977623f23 100644 --- a/Source/Core/DiscIO/VolumeGC.h +++ b/Source/Core/DiscIO/VolumeGC.h @@ -42,7 +42,7 @@ public: std::map GetShortMakers() const override; std::map GetLongMakers() const override; std::map GetDescriptions() const override; - std::vector GetBanner(int* width, int* height) const override; + std::vector GetBanner(u32* width, u32* height) const override; std::string GetApploaderDate(const Partition& partition = PARTITION_NONE) const override; std::optional GetDiscNumber(const Partition& partition = PARTITION_NONE) const override; @@ -54,8 +54,8 @@ public: u64 GetRawSize() const override; private: - static const int GC_BANNER_WIDTH = 96; - static const int GC_BANNER_HEIGHT = 32; + static const u32 GC_BANNER_WIDTH = 96; + static const u32 GC_BANNER_HEIGHT = 32; struct GCBannerInformation { @@ -89,8 +89,8 @@ private: std::map descriptions; std::vector image_buffer; - int image_height = 0; - int image_width = 0; + u32 image_height = 0; + u32 image_width = 0; }; ConvertedGCBanner LoadBannerFile() const; diff --git a/Source/Core/DiscIO/VolumeWad.cpp b/Source/Core/DiscIO/VolumeWad.cpp index 5b601e4fb9..7a1df3e0d5 100644 --- a/Source/Core/DiscIO/VolumeWad.cpp +++ b/Source/Core/DiscIO/VolumeWad.cpp @@ -144,7 +144,7 @@ std::map VolumeWAD::GetLongNames() const return ReadWiiNames(names); } -std::vector VolumeWAD::GetBanner(int* width, int* height) const +std::vector VolumeWAD::GetBanner(u32* width, u32* height) const { *width = 0; *height = 0; diff --git a/Source/Core/DiscIO/VolumeWad.h b/Source/Core/DiscIO/VolumeWad.h index 9fd39db093..cd41593134 100644 --- a/Source/Core/DiscIO/VolumeWad.h +++ b/Source/Core/DiscIO/VolumeWad.h @@ -42,7 +42,7 @@ public: return ""; } std::map GetLongNames() const override; - std::vector GetBanner(int* width, int* height) const override; + std::vector GetBanner(u32* width, u32* height) const override; std::string GetApploaderDate(const Partition& partition = PARTITION_NONE) const override { return ""; diff --git a/Source/Core/DiscIO/VolumeWii.cpp b/Source/Core/DiscIO/VolumeWii.cpp index fc44e29e68..d4558c0c99 100644 --- a/Source/Core/DiscIO/VolumeWii.cpp +++ b/Source/Core/DiscIO/VolumeWii.cpp @@ -308,7 +308,7 @@ std::map VolumeWii::GetLongNames() const return ReadWiiNames(names); } -std::vector VolumeWii::GetBanner(int* width, int* height) const +std::vector VolumeWii::GetBanner(u32* width, u32* height) const { *width = 0; *height = 0; diff --git a/Source/Core/DiscIO/VolumeWii.h b/Source/Core/DiscIO/VolumeWii.h index b854ae5917..a173d17aa7 100644 --- a/Source/Core/DiscIO/VolumeWii.h +++ b/Source/Core/DiscIO/VolumeWii.h @@ -45,7 +45,7 @@ public: std::optional GetRevision(const Partition& partition) const override; std::string GetInternalName(const Partition& partition) const override; std::map GetLongNames() const override; - std::vector GetBanner(int* width, int* height) const override; + std::vector GetBanner(u32* width, u32* height) const override; std::string GetApploaderDate(const Partition& partition) const override; std::optional GetDiscNumber(const Partition& partition) const override; diff --git a/Source/Core/DiscIO/WiiSaveBanner.cpp b/Source/Core/DiscIO/WiiSaveBanner.cpp index 444cb0590d..5bfb87c273 100644 --- a/Source/Core/DiscIO/WiiSaveBanner.cpp +++ b/Source/Core/DiscIO/WiiSaveBanner.cpp @@ -16,13 +16,13 @@ namespace DiscIO { -constexpr unsigned int BANNER_WIDTH = 192; -constexpr unsigned int BANNER_HEIGHT = 64; -constexpr unsigned int BANNER_SIZE = BANNER_WIDTH * BANNER_HEIGHT * 2; +constexpr u32 BANNER_WIDTH = 192; +constexpr u32 BANNER_HEIGHT = 64; +constexpr u32 BANNER_SIZE = BANNER_WIDTH * BANNER_HEIGHT * 2; -constexpr unsigned int ICON_WIDTH = 48; -constexpr unsigned int ICON_HEIGHT = 48; -constexpr unsigned int ICON_SIZE = ICON_WIDTH * ICON_HEIGHT * 2; +constexpr u32 ICON_WIDTH = 48; +constexpr u32 ICON_HEIGHT = 48; +constexpr u32 ICON_SIZE = ICON_WIDTH * ICON_HEIGHT * 2; WiiSaveBanner::WiiSaveBanner(u64 title_id) : WiiSaveBanner(Common::GetTitleDataPath(title_id, Common::FROM_CONFIGURED_ROOT) + @@ -55,7 +55,7 @@ std::string WiiSaveBanner::GetDescription() const return UTF16BEToUTF8(m_header.description, ArraySize(m_header.description)); } -std::vector WiiSaveBanner::GetBanner(int* width, int* height) const +std::vector WiiSaveBanner::GetBanner(u32* width, u32* height) const { *width = 0; *height = 0; diff --git a/Source/Core/DiscIO/WiiSaveBanner.h b/Source/Core/DiscIO/WiiSaveBanner.h index eb199e5ec4..aa0c47393d 100644 --- a/Source/Core/DiscIO/WiiSaveBanner.h +++ b/Source/Core/DiscIO/WiiSaveBanner.h @@ -22,7 +22,7 @@ public: std::string GetName() const; std::string GetDescription() const; - std::vector GetBanner(int* width, int* height) const; + std::vector GetBanner(u32* width, u32* height) const; private: struct Header diff --git a/Source/Core/UICommon/GameFile.cpp b/Source/Core/UICommon/GameFile.cpp index cbd7077987..90f87bb016 100644 --- a/Source/Core/UICommon/GameFile.cpp +++ b/Source/Core/UICommon/GameFile.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -21,9 +22,11 @@ #include "Common/CommonTypes.h" #include "Common/FileUtil.h" #include "Common/Hash.h" +#include "Common/Image.h" #include "Common/IniFile.h" #include "Common/NandPaths.h" #include "Common/StringUtil.h" +#include "Common/Swap.h" #include "Core/Boot/Boot.h" #include "Core/ConfigManager.h" @@ -39,6 +42,16 @@ namespace UICommon { static const std::string EMPTY_STRING; +bool operator==(const GameBanner& lhs, const GameBanner& rhs) +{ + return std::tie(lhs.buffer, lhs.width, lhs.height) == std::tie(rhs.buffer, rhs.width, rhs.height); +} + +bool operator!=(const GameBanner& lhs, const GameBanner& rhs) +{ + return !operator==(lhs, rhs); +} + const std::string& GameFile::Lookup(DiscIO::Language language, const std::map& strings) { @@ -177,6 +190,7 @@ void GameFile::DoState(PointerWrap& p) p.Do(m_apploader_date); m_volume_banner.DoState(p); + m_custom_banner.DoState(p); p.Do(m_custom_name); } @@ -190,7 +204,7 @@ bool GameFile::IsElfOrDol() const return name_end == ".elf" || name_end == ".dol"; } -bool GameFile::BannerChanged() +bool GameFile::WiiBannerChanged() { // Wii banners can only be read if there is a save file. // In case the cache was created without a save file existing, @@ -210,11 +224,62 @@ bool GameFile::BannerChanged() return !m_pending.volume_banner.buffer.empty(); } -void GameFile::BannerCommit() +void GameFile::WiiBannerCommit() { m_volume_banner = std::move(m_pending.volume_banner); } +bool GameFile::ReadPNGBanner(const std::string& path) +{ + File::IOFile file(path, "rb"); + if (!file) + return false; + + std::vector png_data(file.GetSize()); + if (!file.ReadBytes(png_data.data(), png_data.size())) + return false; + + GameBanner& banner = m_pending.custom_banner; + std::vector data_out; + if (!Common::LoadPNG(png_data, &data_out, &banner.width, &banner.height)) + return false; + + // Make an ARGB copy of the RGBA data + banner.buffer.resize(data_out.size() / sizeof(u32)); + for (size_t i = 0; i < banner.buffer.size(); i++) + { + const size_t j = i * sizeof(u32); + banner.buffer[i] = (Common::swap32(data_out.data() + j) >> 8) + (data_out[j] << 24); + } + + return true; +} + +bool GameFile::CustomBannerChanged() +{ + std::string path, name; + SplitPath(m_file_path, &path, &name, nullptr); + + // This icon naming format is intended as an alternative to Homebrew Channel icons + // for those who don't want to have a Homebrew Channel style folder structure. + if (!ReadPNGBanner(path + name + ".png")) + { + // Homebrew Channel icon naming. Typical for DOLs and ELFs, but we also support it for volumes. + if (!ReadPNGBanner(path + "icon.png")) + { + // If no custom icon is found, go back to the non-custom one. + m_pending.custom_banner = {}; + } + } + + return m_pending.custom_banner != m_custom_banner; +} + +void GameFile::CustomBannerCommit() +{ + m_custom_banner = std::move(m_pending.custom_banner); +} + const std::string& GameFile::GetName(bool long_name) const { if (!m_custom_name.empty()) @@ -287,4 +352,9 @@ std::string GameFile::GetWiiFSPath() const return Common::GetTitleDataPath(m_title_id, Common::FROM_CONFIGURED_ROOT); } +const GameBanner& GameFile::GetBannerImage() const +{ + return m_custom_banner.empty() ? m_volume_banner : m_custom_banner; +} + } // namespace UICommon diff --git a/Source/Core/UICommon/GameFile.h b/Source/Core/UICommon/GameFile.h index aa33cd7c65..df5845ab83 100644 --- a/Source/Core/UICommon/GameFile.h +++ b/Source/Core/UICommon/GameFile.h @@ -31,12 +31,15 @@ namespace UICommon struct GameBanner { std::vector buffer{}; - int width{}; - int height{}; + u32 width{}; + u32 height{}; bool empty() const { return buffer.empty(); } void DoState(PointerWrap& p); }; +bool operator==(const GameBanner& lhs, const GameBanner& rhs); +bool operator!=(const GameBanner& lhs, const GameBanner& rhs); + // This class caches the metadata of a DiscIO::Volume (or a DOL/ELF file). class GameFile final { @@ -77,10 +80,12 @@ public: const std::string& GetApploaderDate() const { return m_apploader_date; } u64 GetFileSize() const { return m_file_size; } u64 GetVolumeSize() const { return m_volume_size; } - const GameBanner& GetBannerImage() const { return m_volume_banner; } + const GameBanner& GetBannerImage() const; void DoState(PointerWrap& p); - bool BannerChanged(); - void BannerCommit(); + bool WiiBannerChanged(); + void WiiBannerCommit(); + bool CustomBannerChanged(); + void CustomBannerCommit(); bool CustomNameChanged(const Core::TitleDatabase& title_database); void CustomNameCommit(); @@ -90,6 +95,7 @@ private: const std::string& LookupUsingConfigLanguage(const std::map& strings) const; bool IsElfOrDol() const; + bool ReadPNGBanner(const std::string& path); // IMPORTANT: Nearly all data members must be save/restored in DoState. // If anything is changed, make sure DoState handles it properly and @@ -121,6 +127,7 @@ private: std::string m_apploader_date{}; GameBanner m_volume_banner{}; + GameBanner m_custom_banner{}; // Overridden name from TitleDatabase std::string m_custom_name{}; @@ -129,6 +136,7 @@ private: struct { GameBanner volume_banner; + GameBanner custom_banner; std::string custom_name; } m_pending{}; }; diff --git a/Source/Core/UICommon/GameFileCache.cpp b/Source/Core/UICommon/GameFileCache.cpp index 9c18b78e81..9dc25740f9 100644 --- a/Source/Core/UICommon/GameFileCache.cpp +++ b/Source/Core/UICommon/GameFileCache.cpp @@ -28,7 +28,7 @@ namespace UICommon { -static constexpr u32 CACHE_REVISION = 9; // Last changed in PR 6569 +static constexpr u32 CACHE_REVISION = 10; // Last changed in PR 6429 std::vector FindAllGamePaths(const std::vector& directories_to_scan, bool recursive_scan) @@ -138,17 +138,20 @@ bool GameFileCache::UpdateAdditionalMetadata(const Core::TitleDatabase& title_da bool GameFileCache::UpdateAdditionalMetadata(std::shared_ptr* game_file, const Core::TitleDatabase& title_database) { - const bool banner_changed = (*game_file)->BannerChanged(); + const bool wii_banner_changed = (*game_file)->WiiBannerChanged(); + const bool custom_banner_changed = (*game_file)->CustomBannerChanged(); const bool custom_title_changed = (*game_file)->CustomNameChanged(title_database); - if (!banner_changed && !custom_title_changed) + if (!wii_banner_changed && !custom_banner_changed && !custom_title_changed) return false; // If a cached file needs an update, apply the updates to a copy and delete the original. // This makes the usage of cached files in other threads safe. std::shared_ptr copy = std::make_shared(**game_file); - if (banner_changed) - copy->BannerCommit(); + if (wii_banner_changed) + copy->WiiBannerCommit(); + if (custom_banner_changed) + copy->CustomBannerCommit(); if (custom_title_changed) copy->CustomNameCommit(); *game_file = std::move(copy);