Merge pull request #6429 from JosJuice/png-banners-uicommon
Reimplement custom PNG banners in game list
This commit is contained in:
commit
56e91bfdc1
|
@ -193,7 +193,7 @@ static bool LoadBanner(std::string filename, u32* Banner)
|
||||||
|
|
||||||
if (pVolume != nullptr)
|
if (pVolume != nullptr)
|
||||||
{
|
{
|
||||||
int Width, Height;
|
u32 Width, Height;
|
||||||
std::vector<u32> BannerVec = pVolume->GetBanner(&Width, &Height);
|
std::vector<u32> BannerVec = pVolume->GetBanner(&Width, &Height);
|
||||||
// This code (along with above inlines) is moved from
|
// This code (along with above inlines) is moved from
|
||||||
// elsewhere. Someone who knows anything about Android
|
// elsewhere. Someone who knows anything about Android
|
||||||
|
|
|
@ -83,7 +83,7 @@ public:
|
||||||
virtual std::map<Language, std::string> GetShortMakers() const { return {}; }
|
virtual std::map<Language, std::string> GetShortMakers() const { return {}; }
|
||||||
virtual std::map<Language, std::string> GetLongMakers() const { return {}; }
|
virtual std::map<Language, std::string> GetLongMakers() const { return {}; }
|
||||||
virtual std::map<Language, std::string> GetDescriptions() const { return {}; }
|
virtual std::map<Language, std::string> GetDescriptions() const { return {}; }
|
||||||
virtual std::vector<u32> GetBanner(int* width, int* height) const = 0;
|
virtual std::vector<u32> GetBanner(u32* width, u32* height) const = 0;
|
||||||
std::string GetApploaderDate() const { return GetApploaderDate(GetGamePartition()); }
|
std::string GetApploaderDate() const { return GetApploaderDate(GetGamePartition()); }
|
||||||
virtual std::string GetApploaderDate(const Partition& partition) const = 0;
|
virtual std::string GetApploaderDate(const Partition& partition) const = 0;
|
||||||
// 0 is the first disc, 1 is the second disc
|
// 0 is the first disc, 1 is the second disc
|
||||||
|
|
|
@ -150,7 +150,7 @@ std::map<Language, std::string> VolumeGC::GetDescriptions() const
|
||||||
return m_converted_banner->descriptions;
|
return m_converted_banner->descriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u32> VolumeGC::GetBanner(int* width, int* height) const
|
std::vector<u32> VolumeGC::GetBanner(u32* width, u32* height) const
|
||||||
{
|
{
|
||||||
*width = m_converted_banner->image_width;
|
*width = m_converted_banner->image_width;
|
||||||
*height = m_converted_banner->image_height;
|
*height = m_converted_banner->image_height;
|
||||||
|
|
|
@ -42,7 +42,7 @@ public:
|
||||||
std::map<Language, std::string> GetShortMakers() const override;
|
std::map<Language, std::string> GetShortMakers() const override;
|
||||||
std::map<Language, std::string> GetLongMakers() const override;
|
std::map<Language, std::string> GetLongMakers() const override;
|
||||||
std::map<Language, std::string> GetDescriptions() const override;
|
std::map<Language, std::string> GetDescriptions() const override;
|
||||||
std::vector<u32> GetBanner(int* width, int* height) const override;
|
std::vector<u32> GetBanner(u32* width, u32* height) const override;
|
||||||
std::string GetApploaderDate(const Partition& partition = PARTITION_NONE) const override;
|
std::string GetApploaderDate(const Partition& partition = PARTITION_NONE) const override;
|
||||||
std::optional<u8> GetDiscNumber(const Partition& partition = PARTITION_NONE) const override;
|
std::optional<u8> GetDiscNumber(const Partition& partition = PARTITION_NONE) const override;
|
||||||
|
|
||||||
|
@ -54,8 +54,8 @@ public:
|
||||||
u64 GetRawSize() const override;
|
u64 GetRawSize() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int GC_BANNER_WIDTH = 96;
|
static const u32 GC_BANNER_WIDTH = 96;
|
||||||
static const int GC_BANNER_HEIGHT = 32;
|
static const u32 GC_BANNER_HEIGHT = 32;
|
||||||
|
|
||||||
struct GCBannerInformation
|
struct GCBannerInformation
|
||||||
{
|
{
|
||||||
|
@ -89,8 +89,8 @@ private:
|
||||||
std::map<Language, std::string> descriptions;
|
std::map<Language, std::string> descriptions;
|
||||||
|
|
||||||
std::vector<u32> image_buffer;
|
std::vector<u32> image_buffer;
|
||||||
int image_height = 0;
|
u32 image_height = 0;
|
||||||
int image_width = 0;
|
u32 image_width = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
ConvertedGCBanner LoadBannerFile() const;
|
ConvertedGCBanner LoadBannerFile() const;
|
||||||
|
|
|
@ -144,7 +144,7 @@ std::map<Language, std::string> VolumeWAD::GetLongNames() const
|
||||||
return ReadWiiNames(names);
|
return ReadWiiNames(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u32> VolumeWAD::GetBanner(int* width, int* height) const
|
std::vector<u32> VolumeWAD::GetBanner(u32* width, u32* height) const
|
||||||
{
|
{
|
||||||
*width = 0;
|
*width = 0;
|
||||||
*height = 0;
|
*height = 0;
|
||||||
|
|
|
@ -42,7 +42,7 @@ public:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
std::map<Language, std::string> GetLongNames() const override;
|
std::map<Language, std::string> GetLongNames() const override;
|
||||||
std::vector<u32> GetBanner(int* width, int* height) const override;
|
std::vector<u32> GetBanner(u32* width, u32* height) const override;
|
||||||
std::string GetApploaderDate(const Partition& partition = PARTITION_NONE) const override
|
std::string GetApploaderDate(const Partition& partition = PARTITION_NONE) const override
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
|
|
|
@ -308,7 +308,7 @@ std::map<Language, std::string> VolumeWii::GetLongNames() const
|
||||||
return ReadWiiNames(names);
|
return ReadWiiNames(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u32> VolumeWii::GetBanner(int* width, int* height) const
|
std::vector<u32> VolumeWii::GetBanner(u32* width, u32* height) const
|
||||||
{
|
{
|
||||||
*width = 0;
|
*width = 0;
|
||||||
*height = 0;
|
*height = 0;
|
||||||
|
|
|
@ -45,7 +45,7 @@ public:
|
||||||
std::optional<u16> GetRevision(const Partition& partition) const override;
|
std::optional<u16> GetRevision(const Partition& partition) const override;
|
||||||
std::string GetInternalName(const Partition& partition) const override;
|
std::string GetInternalName(const Partition& partition) const override;
|
||||||
std::map<Language, std::string> GetLongNames() const override;
|
std::map<Language, std::string> GetLongNames() const override;
|
||||||
std::vector<u32> GetBanner(int* width, int* height) const override;
|
std::vector<u32> GetBanner(u32* width, u32* height) const override;
|
||||||
std::string GetApploaderDate(const Partition& partition) const override;
|
std::string GetApploaderDate(const Partition& partition) const override;
|
||||||
std::optional<u8> GetDiscNumber(const Partition& partition) const override;
|
std::optional<u8> GetDiscNumber(const Partition& partition) const override;
|
||||||
|
|
||||||
|
|
|
@ -16,13 +16,13 @@
|
||||||
|
|
||||||
namespace DiscIO
|
namespace DiscIO
|
||||||
{
|
{
|
||||||
constexpr unsigned int BANNER_WIDTH = 192;
|
constexpr u32 BANNER_WIDTH = 192;
|
||||||
constexpr unsigned int BANNER_HEIGHT = 64;
|
constexpr u32 BANNER_HEIGHT = 64;
|
||||||
constexpr unsigned int BANNER_SIZE = BANNER_WIDTH * BANNER_HEIGHT * 2;
|
constexpr u32 BANNER_SIZE = BANNER_WIDTH * BANNER_HEIGHT * 2;
|
||||||
|
|
||||||
constexpr unsigned int ICON_WIDTH = 48;
|
constexpr u32 ICON_WIDTH = 48;
|
||||||
constexpr unsigned int ICON_HEIGHT = 48;
|
constexpr u32 ICON_HEIGHT = 48;
|
||||||
constexpr unsigned int ICON_SIZE = ICON_WIDTH * ICON_HEIGHT * 2;
|
constexpr u32 ICON_SIZE = ICON_WIDTH * ICON_HEIGHT * 2;
|
||||||
|
|
||||||
WiiSaveBanner::WiiSaveBanner(u64 title_id)
|
WiiSaveBanner::WiiSaveBanner(u64 title_id)
|
||||||
: WiiSaveBanner(Common::GetTitleDataPath(title_id, Common::FROM_CONFIGURED_ROOT) +
|
: 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));
|
return UTF16BEToUTF8(m_header.description, ArraySize(m_header.description));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u32> WiiSaveBanner::GetBanner(int* width, int* height) const
|
std::vector<u32> WiiSaveBanner::GetBanner(u32* width, u32* height) const
|
||||||
{
|
{
|
||||||
*width = 0;
|
*width = 0;
|
||||||
*height = 0;
|
*height = 0;
|
||||||
|
|
|
@ -22,7 +22,7 @@ public:
|
||||||
std::string GetName() const;
|
std::string GetName() const;
|
||||||
std::string GetDescription() const;
|
std::string GetDescription() const;
|
||||||
|
|
||||||
std::vector<u32> GetBanner(int* width, int* height) const;
|
std::vector<u32> GetBanner(u32* width, u32* height) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Header
|
struct Header
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -21,9 +22,11 @@
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/FileUtil.h"
|
#include "Common/FileUtil.h"
|
||||||
#include "Common/Hash.h"
|
#include "Common/Hash.h"
|
||||||
|
#include "Common/Image.h"
|
||||||
#include "Common/IniFile.h"
|
#include "Common/IniFile.h"
|
||||||
#include "Common/NandPaths.h"
|
#include "Common/NandPaths.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
#include "Common/Swap.h"
|
||||||
|
|
||||||
#include "Core/Boot/Boot.h"
|
#include "Core/Boot/Boot.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
|
@ -39,6 +42,16 @@ namespace UICommon
|
||||||
{
|
{
|
||||||
static const std::string EMPTY_STRING;
|
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::string& GameFile::Lookup(DiscIO::Language language,
|
||||||
const std::map<DiscIO::Language, std::string>& strings)
|
const std::map<DiscIO::Language, std::string>& strings)
|
||||||
{
|
{
|
||||||
|
@ -177,6 +190,7 @@ void GameFile::DoState(PointerWrap& p)
|
||||||
p.Do(m_apploader_date);
|
p.Do(m_apploader_date);
|
||||||
|
|
||||||
m_volume_banner.DoState(p);
|
m_volume_banner.DoState(p);
|
||||||
|
m_custom_banner.DoState(p);
|
||||||
p.Do(m_custom_name);
|
p.Do(m_custom_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +204,7 @@ bool GameFile::IsElfOrDol() const
|
||||||
return name_end == ".elf" || name_end == ".dol";
|
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.
|
// Wii banners can only be read if there is a save file.
|
||||||
// In case the cache was created without a save file existing,
|
// 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();
|
return !m_pending.volume_banner.buffer.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameFile::BannerCommit()
|
void GameFile::WiiBannerCommit()
|
||||||
{
|
{
|
||||||
m_volume_banner = std::move(m_pending.volume_banner);
|
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<u8> png_data(file.GetSize());
|
||||||
|
if (!file.ReadBytes(png_data.data(), png_data.size()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
GameBanner& banner = m_pending.custom_banner;
|
||||||
|
std::vector<u8> 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
|
const std::string& GameFile::GetName(bool long_name) const
|
||||||
{
|
{
|
||||||
if (!m_custom_name.empty())
|
if (!m_custom_name.empty())
|
||||||
|
@ -287,4 +352,9 @@ std::string GameFile::GetWiiFSPath() const
|
||||||
return Common::GetTitleDataPath(m_title_id, Common::FROM_CONFIGURED_ROOT);
|
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
|
} // namespace UICommon
|
||||||
|
|
|
@ -31,12 +31,15 @@ namespace UICommon
|
||||||
struct GameBanner
|
struct GameBanner
|
||||||
{
|
{
|
||||||
std::vector<u32> buffer{};
|
std::vector<u32> buffer{};
|
||||||
int width{};
|
u32 width{};
|
||||||
int height{};
|
u32 height{};
|
||||||
bool empty() const { return buffer.empty(); }
|
bool empty() const { return buffer.empty(); }
|
||||||
void DoState(PointerWrap& p);
|
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).
|
// This class caches the metadata of a DiscIO::Volume (or a DOL/ELF file).
|
||||||
class GameFile final
|
class GameFile final
|
||||||
{
|
{
|
||||||
|
@ -77,10 +80,12 @@ public:
|
||||||
const std::string& GetApploaderDate() const { return m_apploader_date; }
|
const std::string& GetApploaderDate() const { return m_apploader_date; }
|
||||||
u64 GetFileSize() const { return m_file_size; }
|
u64 GetFileSize() const { return m_file_size; }
|
||||||
u64 GetVolumeSize() const { return m_volume_size; }
|
u64 GetVolumeSize() const { return m_volume_size; }
|
||||||
const GameBanner& GetBannerImage() const { return m_volume_banner; }
|
const GameBanner& GetBannerImage() const;
|
||||||
void DoState(PointerWrap& p);
|
void DoState(PointerWrap& p);
|
||||||
bool BannerChanged();
|
bool WiiBannerChanged();
|
||||||
void BannerCommit();
|
void WiiBannerCommit();
|
||||||
|
bool CustomBannerChanged();
|
||||||
|
void CustomBannerCommit();
|
||||||
bool CustomNameChanged(const Core::TitleDatabase& title_database);
|
bool CustomNameChanged(const Core::TitleDatabase& title_database);
|
||||||
void CustomNameCommit();
|
void CustomNameCommit();
|
||||||
|
|
||||||
|
@ -90,6 +95,7 @@ private:
|
||||||
const std::string&
|
const std::string&
|
||||||
LookupUsingConfigLanguage(const std::map<DiscIO::Language, std::string>& strings) const;
|
LookupUsingConfigLanguage(const std::map<DiscIO::Language, std::string>& strings) const;
|
||||||
bool IsElfOrDol() const;
|
bool IsElfOrDol() const;
|
||||||
|
bool ReadPNGBanner(const std::string& path);
|
||||||
|
|
||||||
// IMPORTANT: Nearly all data members must be save/restored in DoState.
|
// IMPORTANT: Nearly all data members must be save/restored in DoState.
|
||||||
// If anything is changed, make sure DoState handles it properly and
|
// If anything is changed, make sure DoState handles it properly and
|
||||||
|
@ -121,6 +127,7 @@ private:
|
||||||
std::string m_apploader_date{};
|
std::string m_apploader_date{};
|
||||||
|
|
||||||
GameBanner m_volume_banner{};
|
GameBanner m_volume_banner{};
|
||||||
|
GameBanner m_custom_banner{};
|
||||||
// Overridden name from TitleDatabase
|
// Overridden name from TitleDatabase
|
||||||
std::string m_custom_name{};
|
std::string m_custom_name{};
|
||||||
|
|
||||||
|
@ -129,6 +136,7 @@ private:
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
GameBanner volume_banner;
|
GameBanner volume_banner;
|
||||||
|
GameBanner custom_banner;
|
||||||
std::string custom_name;
|
std::string custom_name;
|
||||||
} m_pending{};
|
} m_pending{};
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
|
|
||||||
namespace UICommon
|
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<std::string> FindAllGamePaths(const std::vector<std::string>& directories_to_scan,
|
std::vector<std::string> FindAllGamePaths(const std::vector<std::string>& directories_to_scan,
|
||||||
bool recursive_scan)
|
bool recursive_scan)
|
||||||
|
@ -138,17 +138,20 @@ bool GameFileCache::UpdateAdditionalMetadata(const Core::TitleDatabase& title_da
|
||||||
bool GameFileCache::UpdateAdditionalMetadata(std::shared_ptr<GameFile>* game_file,
|
bool GameFileCache::UpdateAdditionalMetadata(std::shared_ptr<GameFile>* game_file,
|
||||||
const Core::TitleDatabase& title_database)
|
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);
|
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;
|
return false;
|
||||||
|
|
||||||
// If a cached file needs an update, apply the updates to a copy and delete the original.
|
// 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.
|
// This makes the usage of cached files in other threads safe.
|
||||||
|
|
||||||
std::shared_ptr<GameFile> copy = std::make_shared<GameFile>(**game_file);
|
std::shared_ptr<GameFile> copy = std::make_shared<GameFile>(**game_file);
|
||||||
if (banner_changed)
|
if (wii_banner_changed)
|
||||||
copy->BannerCommit();
|
copy->WiiBannerCommit();
|
||||||
|
if (custom_banner_changed)
|
||||||
|
copy->CustomBannerCommit();
|
||||||
if (custom_title_changed)
|
if (custom_title_changed)
|
||||||
copy->CustomNameCommit();
|
copy->CustomNameCommit();
|
||||||
*game_file = std::move(copy);
|
*game_file = std::move(copy);
|
||||||
|
|
Loading…
Reference in New Issue