mirror of
https://github.com/LNH-team/pico-launcher.git
synced 2026-09-17 20:52:38 +02:00
Add support for custom BMP icons and custom NDS banners for games and folders. Closes #53 and Closes #62
This commit is contained in:
80
arm9/source/romBrowser/BannerRepository.cpp
Normal file
80
arm9/source/romBrowser/BannerRepository.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include "core/mini-printf.h"
|
||||
#include "fat/Directory.h"
|
||||
#include "FileType/NullFileTypeProvider.h"
|
||||
#include "FileType/Bnr/BnrInternalFileInfo.h"
|
||||
#include "SdFolderFactory.h"
|
||||
#include "fat/File.h"
|
||||
#include "BannerRepository.h"
|
||||
|
||||
void BannerRepository::Initialize()
|
||||
{
|
||||
InitializeFolders("/_pico/banners/");
|
||||
}
|
||||
|
||||
InternalFileInfo* BannerRepository::GetBannerForFile(const FileInfo& fileInfo, const char* gameCode) const
|
||||
{
|
||||
char nameBuffer[270];
|
||||
const auto& fileType = fileInfo.GetFileType();
|
||||
|
||||
if (fileType->GetClassification() == FileTypeClassification::Folder)
|
||||
{
|
||||
// Look for banner.bnr inside the folder (path relative to FatFs CWD = current browse dir).
|
||||
// Scan with the already-open directory handle so the match can be turned directly into a
|
||||
// FastFileRef, instead of stat'ing then re-opening the same path by name.
|
||||
Directory folderDir;
|
||||
if (folderDir.Open(fileInfo.GetFileName()) == FR_OK)
|
||||
{
|
||||
FILINFO folderFileInfo;
|
||||
while (folderDir.Read(&folderFileInfo) == FR_OK && folderFileInfo.fname[0] != 0)
|
||||
{
|
||||
if (!(folderFileInfo.fattrib & AM_DIR) && !strcasecmp(folderFileInfo.fname, "banner.bnr"))
|
||||
{
|
||||
auto* bnr = new BnrInternalFileInfo(
|
||||
FastFileRef(folderDir.GetFatFsDirectory(), &folderFileInfo), nullptr);
|
||||
if (bnr->HasBanner())
|
||||
{
|
||||
return bnr;
|
||||
}
|
||||
delete bnr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FileInfo* bnrFile = nullptr;
|
||||
|
||||
// Try to get a banner based on the filename in the user folder
|
||||
if (_userFolder)
|
||||
{
|
||||
mini_snprintf(nameBuffer, sizeof(nameBuffer), "%s.bnr", fileInfo.GetFileName());
|
||||
bnrFile = _userFolder->BinarySearch(nameBuffer);
|
||||
}
|
||||
|
||||
// Try to get a banner based on an internal game code
|
||||
if (!bnrFile && gameCode)
|
||||
{
|
||||
const auto* bannerFolder = GetFileTypeFolder(fileType->GetShortName());
|
||||
if (bannerFolder)
|
||||
{
|
||||
mini_snprintf(nameBuffer, sizeof(nameBuffer), "%s.bnr", gameCode);
|
||||
bnrFile = bannerFolder->BinarySearch(nameBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (bnrFile)
|
||||
{
|
||||
auto* bnr = new BnrInternalFileInfo(bnrFile->GetFastFileRef(), gameCode);
|
||||
if (bnr->HasBanner())
|
||||
{
|
||||
return bnr;
|
||||
}
|
||||
delete bnr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
10
arm9/source/romBrowser/BannerRepository.h
Normal file
10
arm9/source/romBrowser/BannerRepository.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "IBannerRepository.h"
|
||||
#include "RepositoryBase.h"
|
||||
|
||||
class BannerRepository : public RepositoryBase, public IBannerRepository
|
||||
{
|
||||
public:
|
||||
void Initialize() override;
|
||||
InternalFileInfo* GetBannerForFile(const FileInfo& fileInfo, const char* gameCode) const override;
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include "core/StringUtil.h"
|
||||
#include "core/mini-printf.h"
|
||||
#include "FileType/NullFileTypeProvider.h"
|
||||
#include "FileType/BmpFileCover.h"
|
||||
#include "FileType/InternalFileInfo.h"
|
||||
@@ -9,22 +8,7 @@
|
||||
|
||||
void CoverRepository::Initialize()
|
||||
{
|
||||
NullFileTypeProvider fileTypeProvider;
|
||||
_ndsCoversFolder = SdFolderFactory(&fileTypeProvider).CreateFromPath("/_pico/covers/nds");
|
||||
if (_ndsCoversFolder)
|
||||
{
|
||||
_ndsCoversFolder->SortByNameInPlace();
|
||||
}
|
||||
_gbaCoversFolder = SdFolderFactory(&fileTypeProvider).CreateFromPath("/_pico/covers/gba");
|
||||
if (_gbaCoversFolder)
|
||||
{
|
||||
_gbaCoversFolder->SortByNameInPlace();
|
||||
}
|
||||
_userCoversFolder = SdFolderFactory(&fileTypeProvider).CreateFromPath("/_pico/covers/user");
|
||||
if (_userCoversFolder)
|
||||
{
|
||||
_userCoversFolder->SortByNameInPlace();
|
||||
}
|
||||
InitializeFolders("/_pico/covers/");
|
||||
}
|
||||
|
||||
FileCover* CoverRepository::GetCoverForFile(const FileInfo& fileInfo, const InternalFileInfo* internalFileInfo) const
|
||||
@@ -37,32 +21,22 @@ FileCover* CoverRepository::GetCoverForFile(const FileInfo& fileInfo, const Inte
|
||||
const FileInfo* coverFile = nullptr;
|
||||
|
||||
// Try to get a cover based on the filename in the user folder
|
||||
if (_userCoversFolder)
|
||||
if (_userFolder)
|
||||
{
|
||||
u32 length = StringUtil::Copy(nameBuffer, fileInfo.GetFileName(), sizeof(nameBuffer) - 5);
|
||||
nameBuffer[length + 0] = '.';
|
||||
nameBuffer[length + 1] = 'b';
|
||||
nameBuffer[length + 2] = 'm';
|
||||
nameBuffer[length + 3] = 'p';
|
||||
nameBuffer[length + 4] = 0;
|
||||
coverFile = _userCoversFolder->BinarySearch(nameBuffer);
|
||||
mini_snprintf(nameBuffer, sizeof(nameBuffer), "%s.bmp", fileInfo.GetFileName());
|
||||
coverFile = _userFolder->BinarySearch(nameBuffer);
|
||||
}
|
||||
|
||||
// Try to get a cover based on an internal game code
|
||||
if (!coverFile && internalFileInfo)
|
||||
{
|
||||
const auto* coverFolder = GetCoverFolder(fileType->GetShortName());
|
||||
const auto* coverFolder = GetFileTypeFolder(fileType->GetShortName());
|
||||
if (coverFolder)
|
||||
{
|
||||
const char* gameCode = internalFileInfo->GetGameCode();
|
||||
if (gameCode)
|
||||
{
|
||||
u32 length = StringUtil::Copy(nameBuffer, gameCode, sizeof(nameBuffer) - 5);
|
||||
nameBuffer[length + 0] = '.';
|
||||
nameBuffer[length + 1] = 'b';
|
||||
nameBuffer[length + 2] = 'm';
|
||||
nameBuffer[length + 3] = 'p';
|
||||
nameBuffer[length + 4] = 0;
|
||||
mini_snprintf(nameBuffer, sizeof(nameBuffer), "%s.bmp", gameCode);
|
||||
}
|
||||
|
||||
coverFile = coverFolder->BinarySearch(nameBuffer);
|
||||
@@ -86,19 +60,3 @@ FileCover* CoverRepository::GetCoverForFile(const FileInfo& fileInfo, const Inte
|
||||
|
||||
return fileType->CreateFileCover(fileInfo.GetFileName());
|
||||
}
|
||||
|
||||
const SdFolder* CoverRepository::GetCoverFolder(const char* coverFolderName) const
|
||||
{
|
||||
if (!strcmp(coverFolderName, "nds"))
|
||||
{
|
||||
return _ndsCoversFolder.get();
|
||||
}
|
||||
else if (!strcmp(coverFolderName, "gba"))
|
||||
{
|
||||
return _gbaCoversFolder.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
#pragma once
|
||||
#include "ICoverRepository.h"
|
||||
#include "SdFolder.h"
|
||||
#include "RepositoryBase.h"
|
||||
|
||||
class CoverRepository : public ICoverRepository
|
||||
class CoverRepository : public RepositoryBase, public ICoverRepository
|
||||
{
|
||||
public:
|
||||
void Initialize() override;
|
||||
FileCover* GetCoverForFile(
|
||||
const FileInfo& fileInfo, const InternalFileInfo* internalFileInfo) const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<SdFolder> _ndsCoversFolder;
|
||||
std::unique_ptr<SdFolder> _gbaCoversFolder;
|
||||
std::unique_ptr<SdFolder> _userCoversFolder;
|
||||
|
||||
const SdFolder* GetCoverFolder(const char* coverFolderName) const;
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ TaskResult<void> BannerListFileRecyclerAdapter::BindView(SharedPtr<View> view, i
|
||||
if (internalFileInfo)
|
||||
{
|
||||
const char16_t* gameTitle = internalFileInfo->GetGameTitle();
|
||||
if (gameTitle)
|
||||
if (gameTitle && gameTitle[0] != 0)
|
||||
{
|
||||
listItemView->SetGameTitle(gameTitle);
|
||||
fileNameAsTitle = false;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include "FileType/CustomIconInternalFileInfo.h"
|
||||
#include "FileInfoManager.h"
|
||||
|
||||
FileInfoManager::FileInfoManager(std::unique_ptr<const FileInfo*[]> items, u32 itemCount, const ICoverRepository& coverRepository)
|
||||
FileInfoManager::FileInfoManager(std::unique_ptr<const FileInfo*[]> items, u32 itemCount, const ICoverRepository& coverRepository,
|
||||
const IIconRepository& iconRepository, const IBannerRepository& bannerRepository)
|
||||
: _items(std::move(items)), _itemCount(itemCount)
|
||||
, _extraFileInfo(std::make_unique<ExtraFileInfo[]>(itemCount))
|
||||
, _coverRepository(coverRepository) { }
|
||||
, _coverRepository(coverRepository)
|
||||
, _iconRepository(iconRepository)
|
||||
, _bannerRepository(bannerRepository) { }
|
||||
|
||||
FileInfoManager::~FileInfoManager()
|
||||
{
|
||||
@@ -17,10 +21,29 @@ FileInfoManager::~FileInfoManager()
|
||||
|
||||
void FileInfoManager::LoadFileInfo(int index)
|
||||
{
|
||||
auto internalFileInfo = _extraFileInfo[index].internalFileInfo;
|
||||
if (!internalFileInfo)
|
||||
if (_extraFileInfo[index].loaded)
|
||||
{
|
||||
internalFileInfo = _items[index]->CreateInternalFileInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
const InternalFileInfo* internalFileInfo = _items[index]->CreateInternalFileInfo();
|
||||
const char* gameCode = internalFileInfo ? internalFileInfo->GetGameCode() : nullptr;
|
||||
|
||||
// A custom banner (.bnr) takes priority and replaces the internal file info entirely.
|
||||
auto customBanner = _bannerRepository.GetBannerForFile(*_items[index], gameCode);
|
||||
if (customBanner)
|
||||
{
|
||||
delete internalFileInfo;
|
||||
internalFileInfo = customBanner;
|
||||
}
|
||||
else
|
||||
{
|
||||
// A custom icon (.bmp) wraps the existing internal file info, overriding only the icon.
|
||||
auto iconData = _iconRepository.GetIconForFile(*_items[index], gameCode);
|
||||
if (iconData)
|
||||
{
|
||||
internalFileInfo = new CustomIconInternalFileInfo(std::move(iconData), std::unique_ptr<const InternalFileInfo>(internalFileInfo));
|
||||
}
|
||||
}
|
||||
|
||||
if (!_extraFileInfo[index].fileCover.Lock())
|
||||
@@ -29,10 +52,13 @@ void FileInfoManager::LoadFileInfo(int index)
|
||||
}
|
||||
|
||||
_extraFileInfo[index].internalFileInfo = internalFileInfo;
|
||||
_extraFileInfo[index].loaded = true;
|
||||
}
|
||||
|
||||
void FileInfoManager::ReleaseFileInfo(int index)
|
||||
{
|
||||
_extraFileInfo[index].loaded = false;
|
||||
|
||||
auto internalFileInfo = _extraFileInfo[index].internalFileInfo;
|
||||
if (internalFileInfo)
|
||||
{
|
||||
@@ -57,4 +83,4 @@ int FileInfoManager::GetItemIndex(const char* fileName)
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
#include "FileInfo.h"
|
||||
#include "FileType/FileCover.h"
|
||||
#include "ICoverRepository.h"
|
||||
#include "IIconRepository.h"
|
||||
#include "IBannerRepository.h"
|
||||
#include "core/AtomicSharedPtr.h"
|
||||
#include "FileType/InternalFileInfo.h"
|
||||
|
||||
class FileInfoManager
|
||||
{
|
||||
public:
|
||||
FileInfoManager(std::unique_ptr<const FileInfo*[]> items, u32 itemCount, const ICoverRepository& coverRepository);
|
||||
FileInfoManager(std::unique_ptr<const FileInfo*[]> items, u32 itemCount, const ICoverRepository& coverRepository,
|
||||
const IIconRepository& iconRepository, const IBannerRepository& bannerRepository);
|
||||
~FileInfoManager();
|
||||
|
||||
const InternalFileInfo* GetInternalFileInfo(int index)
|
||||
@@ -18,13 +21,20 @@ public:
|
||||
return _extraFileInfo[index].internalFileInfo;
|
||||
}
|
||||
|
||||
/// @brief Whether LoadFileInfo() has finished for this item. Unlike checking
|
||||
/// GetInternalFileInfo() for null, this distinguishes "still loading" from
|
||||
/// "loaded, and there's legitimately nothing" (e.g. a folder with no custom icon).
|
||||
bool IsFileInfoLoaded(int index) const
|
||||
{
|
||||
return _extraFileInfo[index].loaded;
|
||||
}
|
||||
|
||||
SharedPtr<FileCover> GetFileCover(int index)
|
||||
{
|
||||
return _extraFileInfo[index].fileCover.Lock();
|
||||
}
|
||||
|
||||
void LoadFileInfo(int index);
|
||||
|
||||
void ReleaseFileInfo(int index);
|
||||
|
||||
int GetItemIndex(const char* fileName);
|
||||
@@ -35,7 +45,8 @@ public:
|
||||
private:
|
||||
struct ExtraFileInfo
|
||||
{
|
||||
const InternalFileInfo* internalFileInfo;
|
||||
bool loaded = false;
|
||||
const InternalFileInfo* internalFileInfo = nullptr;
|
||||
AtomicSharedPtr<FileCover> fileCover;
|
||||
};
|
||||
|
||||
@@ -43,4 +54,6 @@ private:
|
||||
u32 _itemCount;
|
||||
std::unique_ptr<ExtraFileInfo[]> _extraFileInfo;
|
||||
const ICoverRepository& _coverRepository;
|
||||
};
|
||||
const IIconRepository& _iconRepository;
|
||||
const IBannerRepository& _bannerRepository;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <libtwl/dma/dmaNitro.h>
|
||||
#include "fat/File.h"
|
||||
#include "core/math/ColorConverter.h"
|
||||
#include "BmpHeader.h"
|
||||
#include "BmpFileCover.h"
|
||||
|
||||
BmpFileCover::BmpFileCover(const FastFileRef& coverFileRef)
|
||||
@@ -12,8 +13,11 @@ BmpFileCover::BmpFileCover(const FastFileRef& coverFileRef)
|
||||
const auto file = std::make_unique<File>();
|
||||
file->Open(coverFileRef, FA_READ);
|
||||
|
||||
if (!file->ReadExact(_coverBuffer, 0x436))
|
||||
if (!file->ReadExact(_coverBuffer, 0x436) ||
|
||||
!BmpHeader::Validate(_coverBuffer, 128, 96, 8))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
u32 dataOffset = _coverBuffer[0xA] | (_coverBuffer[0xB] << 8) | (_coverBuffer[0xC] << 16) | (_coverBuffer[0xD] << 24);
|
||||
|
||||
@@ -24,12 +28,14 @@ BmpFileCover::BmpFileCover(const FastFileRef& coverFileRef)
|
||||
u32 g = *paletteData32++;
|
||||
u32 r = *paletteData32++;
|
||||
paletteData32++;
|
||||
_palette[i] = ColorConverter::ToXBGR555(Rgb<5, 5, 5>(Rgb<8, 8, 8>(r, g, b)));
|
||||
_palette[i] = ColorConverter::ToXBGR555(Rgb<5, 5, 5>(Rgb8(r, g, b)));
|
||||
}
|
||||
|
||||
if (file->Seek(dataOffset) != FR_OK ||
|
||||
!file->ReadExact(_coverBuffer, sizeof(_coverBuffer)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
file->Close();
|
||||
|
||||
|
||||
35
arm9/source/romBrowser/FileType/BmpFileIcon.cpp
Normal file
35
arm9/source/romBrowser/FileType/BmpFileIcon.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "common.h"
|
||||
#include <libtwl/dma/dmaNitro.h>
|
||||
#include "gui/OamBuilder.h"
|
||||
#include "gui/palette/DirectPalette.h"
|
||||
#include "gui/GraphicsContext.h"
|
||||
#include "BmpFileIcon.h"
|
||||
|
||||
void BmpFileIcon::UploadGraphics()
|
||||
{
|
||||
if (_vramAddress != nullptr)
|
||||
{
|
||||
dma_ntrCopy32(3, _iconData->GetGfx(), _vramAddress, BmpFileIconData::GfxSize);
|
||||
}
|
||||
}
|
||||
|
||||
void BmpFileIcon::Draw(GraphicsContext& graphicsContext, const Rgb<8, 8, 8>& backgroundColor)
|
||||
{
|
||||
if (!graphicsContext.IsVisible(Rectangle(_position, 32, 32)) ||
|
||||
_vramAddress == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
u32 paletteRowIdx = graphicsContext.GetPaletteManager().AllocRow(
|
||||
DirectPalette(_iconData->GetPltt()), _position.y, _position.y + 32);
|
||||
|
||||
// Character offset in OBJ VRAM is in 128-byte units for 32x32 4 bpp sprites
|
||||
auto builder = OamBuilder::OamWithSize<32, 32>(
|
||||
_position, _vramOffset >> 7)
|
||||
.WithPalette16(paletteRowIdx)
|
||||
.WithPriority(graphicsContext.GetPriority());
|
||||
|
||||
gfx_oam_entry_t* oam = graphicsContext.GetOamManager().AllocOams(1);
|
||||
builder.Build(oam[0]);
|
||||
}
|
||||
19
arm9/source/romBrowser/FileType/BmpFileIcon.h
Normal file
19
arm9/source/romBrowser/FileType/BmpFileIcon.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include "FileIcon.h"
|
||||
#include "BmpFileIconData.h"
|
||||
#include "core/SharedPtr.h"
|
||||
|
||||
/// @brief Icon of a file loaded from an external 32x32 4 bpp 16-color BMP file.
|
||||
class BmpFileIcon : public FileIcon
|
||||
{
|
||||
public:
|
||||
explicit BmpFileIcon(SharedPtr<BmpFileIconData> iconData)
|
||||
: _iconData(std::move(iconData)) { }
|
||||
|
||||
void UploadGraphics() override;
|
||||
|
||||
void Draw(GraphicsContext& graphicsContext, const Rgb<8, 8, 8>& backgroundColor) override;
|
||||
|
||||
private:
|
||||
SharedPtr<BmpFileIconData> _iconData;
|
||||
};
|
||||
83
arm9/source/romBrowser/FileType/BmpFileIconData.cpp
Normal file
83
arm9/source/romBrowser/FileType/BmpFileIconData.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "common.h"
|
||||
#include <memory>
|
||||
#include <string.h>
|
||||
#include <nds/arm9/cache.h>
|
||||
#include "fat/File.h"
|
||||
#include "core/math/ColorConverter.h"
|
||||
#include "BmpHeader.h"
|
||||
#include "BmpFileIconData.h"
|
||||
|
||||
BmpFileIconData::BmpFileIconData(const FastFileRef& iconFileRef)
|
||||
{
|
||||
auto file = std::make_unique<File>();
|
||||
file->Open(iconFileRef, FA_READ);
|
||||
|
||||
memset(_iconGfx, 0, sizeof(_iconGfx));
|
||||
memset(_iconPltt, 0, sizeof(_iconPltt));
|
||||
Load(std::move(file));
|
||||
DC_FlushRange(_iconGfx, sizeof(_iconGfx));
|
||||
DC_FlushRange(_iconPltt, sizeof(_iconPltt));
|
||||
}
|
||||
|
||||
void BmpFileIconData::Load(std::unique_ptr<File> file)
|
||||
{
|
||||
// BMP file header (14) + DIB header (40) + 16-color palette (64)
|
||||
u8 headerAndPalette[118];
|
||||
if (!file->ReadExact(headerAndPalette, sizeof(headerAndPalette)) ||
|
||||
!BmpHeader::Validate(headerAndPalette, 32, 32, 4))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
u32 dataOffset = headerAndPalette[0xA] | (headerAndPalette[0xB] << 8) |
|
||||
(headerAndPalette[0xC] << 16) | (headerAndPalette[0xD] << 24);
|
||||
|
||||
if (dataOffset < sizeof(headerAndPalette))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool topDown = BmpHeader::IsTopDown(headerAndPalette);
|
||||
|
||||
const u8* paletteData = &headerAndPalette[0x36];
|
||||
for (u32 i = 0; i < 16; i++)
|
||||
{
|
||||
u32 b = *paletteData++;
|
||||
u32 g = *paletteData++;
|
||||
u32 r = *paletteData++;
|
||||
paletteData++;
|
||||
_iconPltt[i] = ColorConverter::ToGBGR565(Rgb<8, 8, 8>(r, g, b));
|
||||
}
|
||||
|
||||
// Heap-allocate the staging buffer so it doesn't live on the task thread stack.
|
||||
auto rawPixelData = std::make_unique<u8[]>(GfxSize);
|
||||
if (!rawPixelData ||
|
||||
file->Seek(dataOffset) != FR_OK ||
|
||||
!file->ReadExact(rawPixelData.get(), GfxSize))
|
||||
{
|
||||
memset(_iconPltt, 0, sizeof(_iconPltt));
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert BMP rows (bottom-up or top-down) to the DS tiled 4 bpp sprite format.
|
||||
// BMP is high-nibble-first; DS tiles are low-nibble-first -- swap nibbles per 4-byte group.
|
||||
for (int y = 0; y < 32; y++)
|
||||
{
|
||||
// Bottom-up BMP (normal, positive height): row 0 is the bottom of the image.
|
||||
// Top-down BMP (negative height): row 0 is the top of the image.
|
||||
const u8* srcRowPtr = topDown
|
||||
? rawPixelData.get() + y * 16
|
||||
: rawPixelData.get() + (31 - y) * 16;
|
||||
|
||||
int ty = y / 8;
|
||||
int py = y % 8;
|
||||
|
||||
for (int tx = 0; tx < 4; tx++)
|
||||
{
|
||||
u32 val;
|
||||
memcpy(&val, srcRowPtr + tx * 4, 4);
|
||||
val = ((val >> 4) & 0x0F0F0F0F) | ((val & 0x0F0F0F0F) << 4);
|
||||
memcpy(&_iconGfx[(ty * 4 + tx) * 32 + py * 4], &val, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
arm9/source/romBrowser/FileType/BmpFileIconData.h
Normal file
24
arm9/source/romBrowser/FileType/BmpFileIconData.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "fat/FastFileRef.h"
|
||||
|
||||
class File;
|
||||
|
||||
/// @brief Decoded graphics and palette of a 32x32 4 bpp 16-color BMP file icon,
|
||||
/// loaded once and shared by BmpFileIcon instances.
|
||||
class alignas(32) BmpFileIconData
|
||||
{
|
||||
public:
|
||||
static constexpr u32 GfxSize = 512;
|
||||
|
||||
explicit BmpFileIconData(const FastFileRef& iconFileRef);
|
||||
|
||||
const u8* GetGfx() const { return _iconGfx; }
|
||||
const u16* GetPltt() const { return _iconPltt; }
|
||||
|
||||
private:
|
||||
u8 _iconGfx[GfxSize] alignas(32);
|
||||
u16 _iconPltt[16] alignas(32);
|
||||
|
||||
void Load(std::unique_ptr<File> file);
|
||||
};
|
||||
52
arm9/source/romBrowser/FileType/BmpHeader.h
Normal file
52
arm9/source/romBrowser/FileType/BmpHeader.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include <cstdlib>
|
||||
#include <nds/ndstypes.h>
|
||||
|
||||
/// @brief Static helpers for reading and validating BITMAPFILEHEADER + BITMAPINFOHEADER
|
||||
/// fields from a raw BMP buffer.
|
||||
struct BmpHeader
|
||||
{
|
||||
/// @brief Reads the (possibly negative) biHeight field of a BMP's DIB header.
|
||||
/// @param bmpHeader Buffer containing BMP data. Must be at least 50 bytes.
|
||||
/// @return The image height. Negative when the BMP is stored top-down.
|
||||
static s32 GetHeight(const u8* bmpHeader)
|
||||
{
|
||||
return (s32)(bmpHeader[0x16] | (bmpHeader[0x17] << 8) | (bmpHeader[0x18] << 16) | (bmpHeader[0x19] << 24));
|
||||
}
|
||||
|
||||
/// @brief Validates BITMAPFILEHEADER + BITMAPINFOHEADER fields from a raw BMP buffer.
|
||||
/// @param bmpHeader Buffer containing BMP data. Must be at least 50 bytes.
|
||||
/// @param expectedWidth Expected width of the image.
|
||||
/// @param expectedHeight Expected height of the image.
|
||||
/// @param expectedBpp Expected bits per pixel.
|
||||
/// @return \c true when valid, or \c false otherwise.
|
||||
static bool Validate(const u8* bmpHeader, u32 expectedWidth, u32 expectedHeight, u32 expectedBpp)
|
||||
{
|
||||
if (bmpHeader[0] != 'B' || bmpHeader[1] != 'M')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 dibSize = bmpHeader[0x0E] | (bmpHeader[0x0F] << 8) | (bmpHeader[0x10] << 16) | (bmpHeader[0x11] << 24);
|
||||
u32 width = bmpHeader[0x12] | (bmpHeader[0x13] << 8) | (bmpHeader[0x14] << 16) | (bmpHeader[0x15] << 24);
|
||||
u32 bpp = bmpHeader[0x1C] | (bmpHeader[0x1D] << 8);
|
||||
u32 comp = bmpHeader[0x1E] | (bmpHeader[0x1F] << 8) | (bmpHeader[0x20] << 16) | (bmpHeader[0x21] << 24);
|
||||
u32 clrUsed = bmpHeader[0x2E] | (bmpHeader[0x2F] << 8) | (bmpHeader[0x30] << 16) | (bmpHeader[0x31] << 24);
|
||||
|
||||
return dibSize == 40
|
||||
&& width == expectedWidth
|
||||
&& (u32)std::abs(GetHeight(bmpHeader)) == expectedHeight
|
||||
&& bpp == expectedBpp
|
||||
&& comp == 0
|
||||
&& (clrUsed == 0 || clrUsed == (1u << expectedBpp));
|
||||
}
|
||||
|
||||
/// @brief Returns \c true if the BMP stores rows top-to-bottom (negative biHeight).
|
||||
/// @param bmpHeader The raw BMP buffer.
|
||||
/// @return \c true when top-down, or \c false otherwise.
|
||||
/// @note Call only after Validate() succeeds.
|
||||
static bool IsTopDown(const u8* bmpHeader)
|
||||
{
|
||||
return GetHeight(bmpHeader) < 0;
|
||||
}
|
||||
};
|
||||
19
arm9/source/romBrowser/FileType/Bnr/BnrInternalFileInfo.cpp
Normal file
19
arm9/source/romBrowser/FileType/Bnr/BnrInternalFileInfo.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include <memory>
|
||||
#include "fat/File.h"
|
||||
#include "BnrInternalFileInfo.h"
|
||||
|
||||
BnrInternalFileInfo::BnrInternalFileInfo(const FastFileRef& bnrFileRef, const char* gameCode)
|
||||
{
|
||||
auto file = std::make_unique<File>();
|
||||
file->Open(bnrFileRef, FA_READ);
|
||||
|
||||
if (gameCode)
|
||||
{
|
||||
strncpy(_gameCode, gameCode, 4);
|
||||
}
|
||||
|
||||
_hasBanner = ReadBannerChunks(*file, file->GetSize());
|
||||
}
|
||||
|
||||
10
arm9/source/romBrowser/FileType/Bnr/BnrInternalFileInfo.h
Normal file
10
arm9/source/romBrowser/FileType/Bnr/BnrInternalFileInfo.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "../NdsBannerInternalFileInfo.h"
|
||||
#include "fat/FastFileRef.h"
|
||||
|
||||
/// @brief Internal file info loaded from an external .bnr file.
|
||||
class alignas(32) BnrInternalFileInfo : public NdsBannerInternalFileInfo
|
||||
{
|
||||
public:
|
||||
BnrInternalFileInfo(const FastFileRef& bnrFileRef, const char* gameCode);
|
||||
};
|
||||
@@ -34,12 +34,6 @@ public:
|
||||
: FileType::CreateFileCover(fileName);
|
||||
};
|
||||
|
||||
bool HasInternalFileInfo() const override
|
||||
{
|
||||
return _baseFileType != nullptr
|
||||
&& _baseFileType->HasInternalFileInfo();
|
||||
}
|
||||
|
||||
InternalFileInfo* CreateInternalFileInfo(const FastFileRef& fastFileRef) const override
|
||||
{
|
||||
return _baseFileType != nullptr
|
||||
|
||||
29
arm9/source/romBrowser/FileType/CustomIconInternalFileInfo.h
Normal file
29
arm9/source/romBrowser/FileType/CustomIconInternalFileInfo.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "InternalFileInfo.h"
|
||||
#include "BmpFileIcon.h"
|
||||
#include "BmpFileIconData.h"
|
||||
#include "core/SharedPtr.h"
|
||||
|
||||
/// @brief Wraps any InternalFileInfo and overrides its icon with a custom BMP icon.
|
||||
class CustomIconInternalFileInfo : public InternalFileInfo
|
||||
{
|
||||
public:
|
||||
CustomIconInternalFileInfo(SharedPtr<BmpFileIconData> iconData, std::unique_ptr<const InternalFileInfo> wrapped)
|
||||
: _iconData(std::move(iconData)), _wrapped(std::move(wrapped)) {}
|
||||
|
||||
~CustomIconInternalFileInfo() override = default;
|
||||
|
||||
const char* GetGameCode() const override { return _wrapped ? _wrapped->GetGameCode() : nullptr; }
|
||||
const char16_t* GetGameTitle() const override { return _wrapped ? _wrapped->GetGameTitle() : nullptr; }
|
||||
FileCover* CreateGameCover() const override { return _wrapped ? _wrapped->CreateGameCover() : nullptr; }
|
||||
|
||||
std::unique_ptr<FileIcon> CreateGameIcon() const override
|
||||
{
|
||||
return std::make_unique<BmpFileIcon>(_iconData);
|
||||
}
|
||||
|
||||
private:
|
||||
SharedPtr<BmpFileIconData> _iconData;
|
||||
std::unique_ptr<const InternalFileInfo> _wrapped;
|
||||
};
|
||||
@@ -45,10 +45,6 @@ public:
|
||||
return new UnknownFileCover();
|
||||
};
|
||||
|
||||
/// @brief Returns if this file format has internal file info or not.
|
||||
/// @return \c true if this file format has internal file info, or \c false otherwise.
|
||||
virtual bool HasInternalFileInfo() const { return false; }
|
||||
|
||||
/// @brief Reads the internal file info of the file specified by \p fastFileRef.
|
||||
/// @param fastFileRef The file to get the internal file info of.
|
||||
/// @return The internal file info of the specified file when successful, or \c nullptr otherwise.
|
||||
|
||||
@@ -16,8 +16,6 @@ public:
|
||||
return themeFileIconFactory->CreateGenericFileIcon(fileName);
|
||||
}
|
||||
|
||||
bool HasInternalFileInfo() const override { return true; }
|
||||
|
||||
InternalFileInfo* CreateInternalFileInfo(const FastFileRef& fastFileRef) const override
|
||||
{
|
||||
return new GbaInternalFileInfo(fastFileRef);
|
||||
|
||||
@@ -14,7 +14,8 @@ GbaInternalFileInfo::GbaInternalFileInfo(const FastFileRef& fastFileRef)
|
||||
if (file->Seek(0xAC) != FR_OK)
|
||||
return;
|
||||
|
||||
u32 bytesRead;
|
||||
if (file->Read(_gameCode, 4, bytesRead) != FR_OK)
|
||||
return;
|
||||
if (!file->ReadExact(_gameCode, 4))
|
||||
{
|
||||
memset(_gameCode, 0, sizeof(_gameCode));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,4 +12,4 @@ public:
|
||||
|
||||
private:
|
||||
char _gameCode[5];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -16,8 +16,6 @@ public:
|
||||
return themeFileIconFactory->CreateNdsFileIcon(fileName);
|
||||
}
|
||||
|
||||
bool HasInternalFileInfo() const override { return true; }
|
||||
|
||||
InternalFileInfo* CreateInternalFileInfo(const FastFileRef& fastFileRef) const override
|
||||
{
|
||||
return new NdsInternalFileInfo(fastFileRef);
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include <nds/arm9/cache.h>
|
||||
#include "fat/File.h"
|
||||
#include "NdsInternalFileInfo.h"
|
||||
|
||||
NdsInternalFileInfo::NdsInternalFileInfo(const FastFileRef& fastFileRef)
|
||||
{
|
||||
const auto file = std::make_unique<File>();
|
||||
memset(_gameCode, 0, sizeof(_gameCode));
|
||||
|
||||
file->Open(fastFileRef, FA_READ);
|
||||
|
||||
u32 bannerOffset;
|
||||
@@ -18,44 +15,11 @@ NdsInternalFileInfo::NdsInternalFileInfo(const FastFileRef& fastFileRef)
|
||||
!file->ReadExact(&bannerOffset, 4) ||
|
||||
bannerOffset == 0 ||
|
||||
bannerOffset >= file->GetSize() ||
|
||||
file->Seek(bannerOffset) != FR_OK ||
|
||||
!file->ReadExact(&_banner, 0x840))
|
||||
file->Seek(bannerOffset) != FR_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_banner.header.version >= NDS_BANNER_VERSION_2 &&
|
||||
!file->ReadExact(((u8*)&_banner) + 0x840, 0x100))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_banner.header.version >= NDS_BANNER_VERSION_3 &&
|
||||
!file->ReadExact(((u8*)&_banner) + 0x940, 0x100))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_banner.header.version >= NDS_BANNER_VERSION_103 &&
|
||||
!file->ReadExact(((u8*)&_banner) + 0xA40, 0x1980))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_hasBanner = true;
|
||||
DC_FlushRange(&_banner, sizeof(_banner));
|
||||
_hasBanner = ReadBannerChunks(*file, file->GetSize() - bannerOffset);
|
||||
}
|
||||
|
||||
const char16_t* NdsInternalFileInfo::GetGameTitle() const
|
||||
{
|
||||
return _hasBanner
|
||||
? _banner.title[NDS_BANNER_TITLE_LANGUAGE_ENGLISH]
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<FileIcon> NdsInternalFileInfo::CreateGameIcon() const
|
||||
{
|
||||
return _hasBanner
|
||||
? std::make_unique<NdsFileIcon>(&_banner)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
#pragma once
|
||||
#include "../InternalFileInfo.h"
|
||||
#include "../NdsBannerInternalFileInfo.h"
|
||||
#include "ndsBanner.h"
|
||||
#include "NdsFileIcon.h"
|
||||
#include "fat/FastFileRef.h"
|
||||
|
||||
/// @brief Internal file info for nds roms.
|
||||
class alignas(32) NdsInternalFileInfo : public InternalFileInfo
|
||||
class alignas(32) NdsInternalFileInfo : public NdsBannerInternalFileInfo
|
||||
{
|
||||
public:
|
||||
explicit NdsInternalFileInfo(const FastFileRef& fastFileRef);
|
||||
|
||||
constexpr const char* GetGameCode() const override { return _gameCode; }
|
||||
const char16_t* GetGameTitle() const override;
|
||||
std::unique_ptr<FileIcon> CreateGameIcon() const override;
|
||||
const nds_banner_t& GetBanner() const { return _banner; }
|
||||
|
||||
private:
|
||||
nds_banner_t _banner alignas(32);
|
||||
bool _hasBanner = false;
|
||||
char _gameCode[5];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include <nds/arm9/cache.h>
|
||||
#include "fat/File.h"
|
||||
#include "Nds/NdsFileIcon.h"
|
||||
#include "NdsBannerInternalFileInfo.h"
|
||||
|
||||
bool NdsBannerInternalFileInfo::ReadBannerChunks(File& file, u32 availableSize)
|
||||
{
|
||||
memset(&_banner, 0, sizeof(_banner));
|
||||
|
||||
if (availableSize < 0x240) // Must have at least header + icon + palette (576 bytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 toRead = availableSize < 0x840 ? availableSize : 0x840;
|
||||
if (!file.ReadExact(&_banner, toRead))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read Version 2 Chinese Title override (0x100 bytes)
|
||||
if (_banner.header.version >= NDS_BANNER_VERSION_2 && availableSize >= 0x940 &&
|
||||
!file.ReadExact(((u8*)&_banner) + 0x840, 0x100))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read Version 3 Korean Title override (0x100 bytes)
|
||||
if (_banner.header.version >= NDS_BANNER_VERSION_3 && availableSize >= 0xA40 &&
|
||||
!file.ReadExact(((u8*)&_banner) + 0x940, 0x100))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read Version 103 (DSi) Animation Frames, Palettes, and Sequence (0x1980 bytes)
|
||||
if (_banner.header.version >= NDS_BANNER_VERSION_103 && availableSize >= 0x23C0 &&
|
||||
!file.ReadExact(((u8*)&_banner) + 0xA40, 0x1980))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DC_FlushRange(&_banner, sizeof(_banner));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<FileIcon> NdsBannerInternalFileInfo::CreateGameIcon() const
|
||||
{
|
||||
return _hasBanner
|
||||
? std::make_unique<NdsFileIcon>(&_banner)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
const char* NdsBannerInternalFileInfo::GetGameCode() const
|
||||
{
|
||||
return _gameCode[0] != 0 ? _gameCode : nullptr;
|
||||
}
|
||||
|
||||
const char16_t* NdsBannerInternalFileInfo::GetGameTitle() const
|
||||
{
|
||||
if (!_hasBanner)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char16_t* title = _banner.title[NDS_BANNER_TITLE_LANGUAGE_ENGLISH];
|
||||
if (title && title[0] != 0)
|
||||
{
|
||||
return title;
|
||||
}
|
||||
|
||||
title = _banner.title[NDS_BANNER_TITLE_LANGUAGE_JAPANESE];
|
||||
if (title && title[0] != 0)
|
||||
{
|
||||
return title;
|
||||
}
|
||||
|
||||
for (int i = NDS_BANNER_TITLE_LANGUAGE_FRENCH; i <= NDS_BANNER_TITLE_LANGUAGE_KOREAN; i++)
|
||||
{
|
||||
title = _banner.title[i];
|
||||
if (title && title[0] != 0)
|
||||
{
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
26
arm9/source/romBrowser/FileType/NdsBannerInternalFileInfo.h
Normal file
26
arm9/source/romBrowser/FileType/NdsBannerInternalFileInfo.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "InternalFileInfo.h"
|
||||
#include "Nds/ndsBanner.h"
|
||||
|
||||
class File;
|
||||
|
||||
class alignas(32) NdsBannerInternalFileInfo : public InternalFileInfo
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<FileIcon> CreateGameIcon() const override;
|
||||
const char* GetGameCode() const override;
|
||||
const char16_t* GetGameTitle() const override;
|
||||
bool HasBanner() const { return _hasBanner; }
|
||||
|
||||
protected:
|
||||
bool _hasBanner = false;
|
||||
char _gameCode[5] = {};
|
||||
nds_banner_t _banner alignas(32);
|
||||
|
||||
/// @brief Reads banner chunks from the current file position.
|
||||
/// @param file The file to read from.
|
||||
/// @param availableSize The number of bytes remaining from that position.
|
||||
/// @return True on success, or false otherwise.
|
||||
bool ReadBannerChunks(File& file, u32 availableSize);
|
||||
};
|
||||
16
arm9/source/romBrowser/IBannerRepository.h
Normal file
16
arm9/source/romBrowser/IBannerRepository.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
class FileInfo;
|
||||
class InternalFileInfo;
|
||||
|
||||
class IBannerRepository
|
||||
{
|
||||
public:
|
||||
virtual ~IBannerRepository() = default;
|
||||
|
||||
virtual void Initialize() = 0;
|
||||
virtual InternalFileInfo* GetBannerForFile(const FileInfo& fileInfo, const char* gameCode) const = 0;
|
||||
|
||||
protected:
|
||||
IBannerRepository() = default;
|
||||
};
|
||||
17
arm9/source/romBrowser/IIconRepository.h
Normal file
17
arm9/source/romBrowser/IIconRepository.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "core/SharedPtr.h"
|
||||
#include "FileType/BmpFileIconData.h"
|
||||
|
||||
class FileInfo;
|
||||
|
||||
class IIconRepository
|
||||
{
|
||||
public:
|
||||
virtual ~IIconRepository() = default;
|
||||
|
||||
virtual void Initialize() = 0;
|
||||
virtual SharedPtr<BmpFileIconData> GetIconForFile(const FileInfo& fileInfo, const char* gameCode) const = 0;
|
||||
|
||||
protected:
|
||||
IIconRepository() = default;
|
||||
};
|
||||
@@ -8,6 +8,8 @@ class RomBrowserViewModel;
|
||||
class FileInfo;
|
||||
class TaskQueueBase;
|
||||
class ICoverRepository;
|
||||
class IIconRepository;
|
||||
class IBannerRepository;
|
||||
class ICheatRepository;
|
||||
|
||||
class IRomBrowserController
|
||||
@@ -34,6 +36,8 @@ public:
|
||||
virtual TaskQueueBase* GetIoTaskQueue() const = 0;
|
||||
virtual TaskQueueBase* GetBgTaskQueue() const = 0;
|
||||
virtual const ICoverRepository& GetCoverRepository() const = 0;
|
||||
virtual const IIconRepository& GetIconRepository() const = 0;
|
||||
virtual const IBannerRepository& GetBannerRepository() const = 0;
|
||||
virtual const ICheatRepository& GetCheatRepository() const = 0;
|
||||
|
||||
virtual const RomBrowserDisplaySettings& GetRomBrowserDisplaySettings() const = 0;
|
||||
|
||||
65
arm9/source/romBrowser/IconRepository.cpp
Normal file
65
arm9/source/romBrowser/IconRepository.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include "core/mini-printf.h"
|
||||
#include "fat/Directory.h"
|
||||
#include "FileType/NullFileTypeProvider.h"
|
||||
#include "FileType/BmpFileIconData.h"
|
||||
#include "SdFolderFactory.h"
|
||||
#include "IconRepository.h"
|
||||
|
||||
void IconRepository::Initialize()
|
||||
{
|
||||
InitializeFolders("/_pico/icons/");
|
||||
}
|
||||
|
||||
SharedPtr<BmpFileIconData> IconRepository::GetIconForFile(const FileInfo& fileInfo, const char* gameCode) const
|
||||
{
|
||||
char nameBuffer[256];
|
||||
const auto& fileType = fileInfo.GetFileType();
|
||||
|
||||
if (fileType->GetClassification() == FileTypeClassification::Folder)
|
||||
{
|
||||
// Look for icon.bmp inside the folder (path relative to FatFs CWD = current browse dir).
|
||||
// Scan with the already-open directory handle so the match can be turned directly into a
|
||||
// FastFileRef, instead of stat'ing then re-opening the same path by name.
|
||||
Directory folderDir;
|
||||
if (folderDir.Open(fileInfo.GetFileName()) == FR_OK)
|
||||
{
|
||||
FILINFO folderFileInfo;
|
||||
while (folderDir.Read(&folderFileInfo) == FR_OK && folderFileInfo.fname[0] != 0)
|
||||
{
|
||||
if (!(folderFileInfo.fattrib & AM_DIR) && !strcasecmp(folderFileInfo.fname, "icon.bmp"))
|
||||
{
|
||||
return SharedPtr<BmpFileIconData>::MakeShared(
|
||||
FastFileRef(folderDir.GetFatFsDirectory(), &folderFileInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FileInfo* iconFile = nullptr;
|
||||
|
||||
// Try to get an icon based on the filename in the user folder
|
||||
if (_userFolder)
|
||||
{
|
||||
mini_snprintf(nameBuffer, sizeof(nameBuffer), "%s.bmp", fileInfo.GetFileName());
|
||||
iconFile = _userFolder->BinarySearch(nameBuffer);
|
||||
}
|
||||
|
||||
// Try to get an icon based on an internal game code
|
||||
if (!iconFile && gameCode)
|
||||
{
|
||||
const auto* iconFolder = GetFileTypeFolder(fileType->GetShortName());
|
||||
if (iconFolder)
|
||||
{
|
||||
mini_snprintf(nameBuffer, sizeof(nameBuffer), "%s.bmp", gameCode);
|
||||
iconFile = iconFolder->BinarySearch(nameBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
return iconFile
|
||||
? SharedPtr<BmpFileIconData>::MakeShared(iconFile->GetFastFileRef())
|
||||
: nullptr;
|
||||
}
|
||||
10
arm9/source/romBrowser/IconRepository.h
Normal file
10
arm9/source/romBrowser/IconRepository.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "IIconRepository.h"
|
||||
#include "RepositoryBase.h"
|
||||
|
||||
class IconRepository : public RepositoryBase, public IIconRepository
|
||||
{
|
||||
public:
|
||||
void Initialize() override;
|
||||
SharedPtr<BmpFileIconData> GetIconForFile(const FileInfo& fileInfo, const char* gameCode) const override;
|
||||
};
|
||||
49
arm9/source/romBrowser/RepositoryBase.cpp
Normal file
49
arm9/source/romBrowser/RepositoryBase.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include "core/StringUtil.h"
|
||||
#include "FileType/NullFileTypeProvider.h"
|
||||
#include "SdFolderFactory.h"
|
||||
#include "RepositoryBase.h"
|
||||
|
||||
void RepositoryBase::InitializeFolders(const char* basePath)
|
||||
{
|
||||
NullFileTypeProvider fileTypeProvider;
|
||||
SdFolderFactory folderFactory(&fileTypeProvider);
|
||||
char path[64];
|
||||
|
||||
u32 len = StringUtil::Copy(path, basePath, sizeof(path));
|
||||
|
||||
StringUtil::Copy(path + len, "nds", sizeof(path) - len);
|
||||
_ndsFolder = folderFactory.CreateFromPath(path);
|
||||
if (_ndsFolder)
|
||||
{
|
||||
_ndsFolder->SortByNameInPlace();
|
||||
}
|
||||
|
||||
StringUtil::Copy(path + len, "gba", sizeof(path) - len);
|
||||
_gbaFolder = folderFactory.CreateFromPath(path);
|
||||
if (_gbaFolder)
|
||||
{
|
||||
_gbaFolder->SortByNameInPlace();
|
||||
}
|
||||
|
||||
StringUtil::Copy(path + len, "user", sizeof(path) - len);
|
||||
_userFolder = folderFactory.CreateFromPath(path);
|
||||
if (_userFolder)
|
||||
{
|
||||
_userFolder->SortByNameInPlace();
|
||||
}
|
||||
}
|
||||
|
||||
const SdFolder* RepositoryBase::GetFileTypeFolder(const char* shortName) const
|
||||
{
|
||||
if (!strcmp(shortName, "nds"))
|
||||
{
|
||||
return _ndsFolder.get();
|
||||
}
|
||||
if (!strcmp(shortName, "gba"))
|
||||
{
|
||||
return _gbaFolder.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
20
arm9/source/romBrowser/RepositoryBase.h
Normal file
20
arm9/source/romBrowser/RepositoryBase.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "SdFolder.h"
|
||||
|
||||
class RepositoryBase
|
||||
{
|
||||
protected:
|
||||
std::unique_ptr<SdFolder> _ndsFolder;
|
||||
std::unique_ptr<SdFolder> _gbaFolder;
|
||||
std::unique_ptr<SdFolder> _userFolder;
|
||||
|
||||
/// @brief Scans directories and populates the folder cache under the given base path.
|
||||
/// @param basePath The base path of the folders.
|
||||
void InitializeFolders(const char* basePath);
|
||||
|
||||
/// @brief Gets the cached folder corresponding to the file type suffix name.
|
||||
/// @param shortName Suffix folder name (e.g. "nds" or "gba").
|
||||
/// @return A pointer to the cached \see SdFolder, or \c nullptr otherwise.
|
||||
const SdFolder* GetFileTypeFolder(const char* shortName) const;
|
||||
};
|
||||
@@ -143,6 +143,16 @@ void RomBrowserController::HandleNavigateTrigger()
|
||||
_coverRepository = std::make_unique<CoverRepository>();
|
||||
_coverRepository->Initialize();
|
||||
}
|
||||
if (!_iconRepository)
|
||||
{
|
||||
_iconRepository = std::make_unique<IconRepository>();
|
||||
_iconRepository->Initialize();
|
||||
}
|
||||
if (!_bannerRepository)
|
||||
{
|
||||
_bannerRepository = std::make_unique<BannerRepository>();
|
||||
_bannerRepository->Initialize();
|
||||
}
|
||||
if (!_cheatRepository)
|
||||
{
|
||||
_cheatRepository = UsrCheatRepositoryFactory().FromUsrCheatDat("/_pico/usrcheat.dat");
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "core/task/TaskQueue.h"
|
||||
#include "IRomBrowserController.h"
|
||||
#include "CoverRepository.h"
|
||||
#include "IconRepository.h"
|
||||
#include "BannerRepository.h"
|
||||
#include "FileType/ExtensionFileTypeProvider.h"
|
||||
#include "services/settings/IAppSettingsService.h"
|
||||
#include "cheats/ICheatRepository.h"
|
||||
@@ -40,6 +42,8 @@ public:
|
||||
TaskQueueBase* GetIoTaskQueue() const override { return _ioTaskQueue; }
|
||||
TaskQueueBase* GetBgTaskQueue() const override { return _bgTaskQueue; }
|
||||
const ICoverRepository& GetCoverRepository() const override { return *_coverRepository; }
|
||||
const IIconRepository& GetIconRepository() const override { return *_iconRepository; }
|
||||
const IBannerRepository& GetBannerRepository() const override { return *_bannerRepository; }
|
||||
const ICheatRepository& GetCheatRepository() const override { return *_cheatRepository; }
|
||||
|
||||
void SetRomBrowserDisplaySettings(const RomBrowserDisplaySettings& romBrowserDisplaySettings) override;
|
||||
@@ -66,6 +70,8 @@ private:
|
||||
QueueTask<void> _navigateTask;
|
||||
bool _saveSettingsPending = false;
|
||||
std::unique_ptr<CoverRepository> _coverRepository;
|
||||
std::unique_ptr<IconRepository> _iconRepository;
|
||||
std::unique_ptr<BannerRepository> _bannerRepository;
|
||||
ExtensionFileTypeProvider _fileTypeProvider;
|
||||
std::unique_ptr<ICheatRepository> _cheatRepository;
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ RomBrowserViewModel::RomBrowserViewModel(IRomBrowserController* romBrowserContro
|
||||
u64 endTick = gTickCounter.GetValue();
|
||||
LOG_DEBUG("Filter + sort took: %d us\n", (u32)TickCounter::TicksToMicroSeconds(endTick - startTick));
|
||||
_fileInfoManager = std::make_unique<FileInfoManager>(std::move(sortedFilteredFiles),
|
||||
filteredCount, _romBrowserController->GetCoverRepository());
|
||||
filteredCount, _romBrowserController->GetCoverRepository(), _romBrowserController->GetIconRepository(),
|
||||
_romBrowserController->GetBannerRepository());
|
||||
_selectedItem = _fileInfoManager->GetItemIndex(initialSelectedFileName);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,64 +44,48 @@ void RomBrowserTopScreenView::InitVram(const VramContext& vramContext)
|
||||
void RomBrowserTopScreenView::Update()
|
||||
{
|
||||
int selectedItem = _viewModel->GetSelectedItem();
|
||||
if (selectedItem != _lastSelectedItem)
|
||||
if (selectedItem != _lastSelectedItem && selectedItem >= 0)
|
||||
{
|
||||
auto& fileInfoManager = _viewModel->GetFileInfoManager();
|
||||
const auto& item = fileInfoManager.GetItem(selectedItem);
|
||||
if (item.GetFileType()->HasInternalFileInfo())
|
||||
// GetInternalFileInfo() covers both game banners and custom icon overrides (the
|
||||
// latter apply to any file type, including folders), so check it directly instead
|
||||
// of branching on FileType::HasInternalFileInfo() - that's a static per-type property
|
||||
// and knows nothing about a per-item custom icon. IsFileInfoLoaded() distinguishes
|
||||
// "still loading" from "loaded, and there's legitimately nothing" so this waits for
|
||||
// the io thread instead of flashing the previous item's icon while undecided.
|
||||
if (fileInfoManager.IsFileInfoLoaded(selectedItem))
|
||||
{
|
||||
const auto& item = fileInfoManager.GetItem(selectedItem);
|
||||
auto info = fileInfoManager.GetInternalFileInfo(selectedItem);
|
||||
if (info)
|
||||
|
||||
bool fileNameAsTitle = true;
|
||||
const char16_t* gameTitle = info ? info->GetGameTitle() : nullptr;
|
||||
if (gameTitle && gameTitle[0] != 0)
|
||||
{
|
||||
bool fileNameAsTitle = true;
|
||||
const char16_t* gameTitle = info->GetGameTitle();
|
||||
if (gameTitle)
|
||||
{
|
||||
_fileInfoView->SetGameTitleAsync(_viewModel->GetBgTaskQueue(), gameTitle);
|
||||
fileNameAsTitle = false;
|
||||
}
|
||||
|
||||
_selectedFileIcon = info->CreateGameIcon();
|
||||
if (!_selectedFileIcon)
|
||||
{
|
||||
_selectedFileIcon = item.GetFileType()->CreateFileIcon("", _themeFileIconFactory);
|
||||
}
|
||||
if (_selectedFileIcon)
|
||||
{
|
||||
_selectedFileIcon->SetAnimFrame(_viewModel->GetIconFrameCounter());
|
||||
_iconGraphicsUploaded = false;
|
||||
}
|
||||
_fileInfoView->SetIcon(std::move(_selectedFileIcon));
|
||||
_fileInfoView->SetFileNameAsync(_viewModel->GetBgTaskQueue(), item.GetFileName(), fileNameAsTitle);
|
||||
|
||||
_lastSelectedItem = selectedItem;
|
||||
|
||||
auto cover = fileInfoManager.GetFileCover(selectedItem);
|
||||
if (cover.IsValid())
|
||||
{
|
||||
_selectedFileCover = std::move(cover);
|
||||
_coverGraphicsUploaded = false;
|
||||
}
|
||||
_fileInfoView->SetGameTitleAsync(_viewModel->GetBgTaskQueue(), gameTitle);
|
||||
fileNameAsTitle = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
_selectedFileIcon = info ? info->CreateGameIcon() : nullptr;
|
||||
if (!_selectedFileIcon)
|
||||
{
|
||||
_selectedFileIcon = item.GetFileType()->CreateFileIcon("", _themeFileIconFactory);
|
||||
}
|
||||
if (_selectedFileIcon)
|
||||
{
|
||||
_selectedFileIcon->SetAnimFrame(_viewModel->GetIconFrameCounter());
|
||||
_iconGraphicsUploaded = false;
|
||||
}
|
||||
_fileInfoView->SetIcon(std::move(_selectedFileIcon));
|
||||
_fileInfoView->SetFileNameAsync(_viewModel->GetBgTaskQueue(), item.GetFileName(), fileNameAsTitle);
|
||||
|
||||
_lastSelectedItem = selectedItem;
|
||||
|
||||
auto cover = fileInfoManager.GetFileCover(selectedItem);
|
||||
if (cover.IsValid())
|
||||
{
|
||||
_selectedFileCover = std::move(cover);
|
||||
_coverGraphicsUploaded = false;
|
||||
|
||||
_selectedFileIcon = item.GetFileType()->CreateFileIcon("", _themeFileIconFactory);
|
||||
if (_selectedFileIcon)
|
||||
{
|
||||
_selectedFileIcon->SetAnimFrame(_viewModel->GetIconFrameCounter());
|
||||
_iconGraphicsUploaded = false;
|
||||
}
|
||||
_fileInfoView->SetIcon(std::move(_selectedFileIcon));
|
||||
_fileInfoView->SetFileNameAsync(_viewModel->GetBgTaskQueue(), item.GetFileName(), true);
|
||||
|
||||
_lastSelectedItem = selectedItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user