mirror of
https://github.com/LNH-team/pico-launcher.git
synced 2026-06-02 09:06:54 +02:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
127de36b1c | ||
|
|
5442c02341 | ||
|
|
cf9ce63db5 | ||
|
|
53727e5fdd | ||
|
|
9ca3e38668 | ||
|
|
3f780fdd69 | ||
|
|
7c06abf224 | ||
|
|
2c142caa98 | ||
|
|
b7d7f9f352 | ||
|
|
601fd6371e | ||
|
|
a4ecea6802 | ||
|
|
6c34d9324d | ||
|
|
43b1bf7afa | ||
|
|
12ebd482d4 | ||
|
|
4d9318b0b9 | ||
|
|
10431c4615 | ||
|
|
e2e42115e7 | ||
|
|
a9425eea7c | ||
|
|
7f35d524ae | ||
|
|
126b898ac4 | ||
|
|
f54a379ff2 | ||
|
|
dddee0bb94 | ||
|
|
f73c8b0547 | ||
|
|
a102068433 |
1
.github/workflows/nightly.yml
vendored
1
.github/workflows/nightly.yml
vendored
@@ -2,7 +2,6 @@ name: Build Pico Launcher
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["develop"]
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
pull_request:
|
||||
|
||||
27
CHANGELOG.md
Normal file
27
CHANGELOG.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.2.0] - 29 Mar 2026
|
||||
|
||||
### Added
|
||||
- Support for cheats with Pico Loader API v3
|
||||
- Hide files/dirs with hidden attribute, or with a name starting with a period
|
||||
- New customization options for custom themes
|
||||
- Position of elements on the top screen
|
||||
- Text colors
|
||||
- Blend colors
|
||||
|
||||
### Changed
|
||||
- File name on the top screen now uses marquee when too long
|
||||
|
||||
### Fixed
|
||||
- Improve error handling for banners to better detect if a rom has a valid banner
|
||||
|
||||
## [v1.1.0] - 11 Jan 2026
|
||||
|
||||
### Added
|
||||
- Support for Pico Loader API v2. This makes it possible to return to Pico Launcher from supported homebrew applications.
|
||||
|
||||
## [v1.0.0] - 25 Nov 2025
|
||||
- Initial release
|
||||
@@ -105,17 +105,17 @@ INCLUDEFLAGS := $(foreach path,$(INCLUDEDIRS),-I$(path)) \
|
||||
LIBDIRSFLAGS := $(foreach path,$(LIBDIRS),-L$(path)/lib)
|
||||
|
||||
ASFLAGS += -x assembler-with-cpp $(DEFINES) $(INCLUDEFLAGS) \
|
||||
$(ARCH) -ffunction-sections -fdata-sections \
|
||||
$(ARCH) -g -ffunction-sections -fdata-sections \
|
||||
-specs=$(SPECS)
|
||||
|
||||
CFLAGS += -std=gnu17 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \
|
||||
$(ARCH) -O2 -ffunction-sections -fdata-sections \
|
||||
$(ARCH) -g -O2 -ffunction-sections -fdata-sections \
|
||||
-fno-devirtualize-speculatively \
|
||||
-Werror=return-type \
|
||||
-specs=$(SPECS)
|
||||
|
||||
CXXFLAGS += -std=gnu++23 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \
|
||||
$(ARCH) -O2 -ffunction-sections -fdata-sections \
|
||||
$(ARCH) -g -O2 -ffunction-sections -fdata-sections \
|
||||
-fno-exceptions -fno-rtti \
|
||||
-fno-devirtualize-speculatively \
|
||||
-Werror=return-type \
|
||||
|
||||
@@ -15,6 +15,7 @@ This repository contains Pico Launcher, which is a front-end for [Pico Loader](h
|
||||
- [Covers](docs/Covers.md)
|
||||
- [Material Design 3 and custom themes](docs/Themes.md)
|
||||
- Support for background music (see [Themes](docs/Themes.md))
|
||||
- Support for cheats (See [Cheats](docs/Cheats.md))
|
||||
|
||||
General usage documentation can be found here: [Usage](docs/Usage.md).
|
||||
|
||||
|
||||
@@ -8,5 +8,119 @@
|
||||
"g": 217,
|
||||
"b": 255
|
||||
},
|
||||
"darkTheme": false
|
||||
"darkTheme": false,
|
||||
"topIcon": {
|
||||
"position": {
|
||||
"x": 24,
|
||||
"y": 132
|
||||
},
|
||||
"blendColor": {
|
||||
"r": 200,
|
||||
"g": 200,
|
||||
"b": 200
|
||||
}
|
||||
},
|
||||
"topBannerTextLine0": {
|
||||
"position": {
|
||||
"x": 70,
|
||||
"y": 126
|
||||
},
|
||||
"width": 176,
|
||||
"textColor": {
|
||||
"r": 30,
|
||||
"g": 30,
|
||||
"b": 30
|
||||
},
|
||||
"blendColor": {
|
||||
"r": 200,
|
||||
"g": 200,
|
||||
"b": 200
|
||||
}
|
||||
},
|
||||
"topBannerTextLine1": {
|
||||
"position": {
|
||||
"x": 70,
|
||||
"y": 141
|
||||
},
|
||||
"width": 176,
|
||||
"textColor": {
|
||||
"r": 30,
|
||||
"g": 30,
|
||||
"b": 30
|
||||
},
|
||||
"blendColor": {
|
||||
"r": 200,
|
||||
"g": 200,
|
||||
"b": 200
|
||||
}
|
||||
},
|
||||
"topBannerTextLine2": {
|
||||
"position": {
|
||||
"x": 70,
|
||||
"y": 155
|
||||
},
|
||||
"width": 176,
|
||||
"textColor": {
|
||||
"r": 30,
|
||||
"g": 30,
|
||||
"b": 30
|
||||
},
|
||||
"blendColor": {
|
||||
"r": 200,
|
||||
"g": 200,
|
||||
"b": 200
|
||||
}
|
||||
},
|
||||
"topFileNameText": {
|
||||
"position": {
|
||||
"x": 18,
|
||||
"y": 170
|
||||
},
|
||||
"width": 220,
|
||||
"textColor": {
|
||||
"r": 30,
|
||||
"g": 30,
|
||||
"b": 30
|
||||
},
|
||||
"blendColor": {
|
||||
"r": 200,
|
||||
"g": 200,
|
||||
"b": 200
|
||||
}
|
||||
},
|
||||
"gridIcon": {
|
||||
"blendColor": {
|
||||
"r": 200,
|
||||
"g": 200,
|
||||
"b": 200
|
||||
}
|
||||
},
|
||||
"bannerListIcon": {
|
||||
"blendColor": {
|
||||
"r": 200,
|
||||
"g": 200,
|
||||
"b": 200
|
||||
}
|
||||
},
|
||||
"bannerListTextLine0": {
|
||||
"textColor": {
|
||||
"r": 30,
|
||||
"g": 30,
|
||||
"b": 30
|
||||
}
|
||||
},
|
||||
"bannerListTextLine1": {
|
||||
"textColor": {
|
||||
"r": 30,
|
||||
"g": 30,
|
||||
"b": 30
|
||||
}
|
||||
},
|
||||
"bannerListTextLine2": {
|
||||
"textColor": {
|
||||
"r": 30,
|
||||
"g": 30,
|
||||
"b": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
7
arm9/gfx/cheatSelector.grit
Normal file
7
arm9/gfx/cheatSelector.grit
Normal file
@@ -0,0 +1,7 @@
|
||||
# tile format
|
||||
-gt
|
||||
|
||||
# graphics bit depth is 4 (16 color)
|
||||
-gB4
|
||||
|
||||
-p!
|
||||
BIN
arm9/gfx/cheatSelector.png
Normal file
BIN
arm9/gfx/cheatSelector.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 155 B |
7
arm9/gfx/checkboxChecked.grit
Normal file
7
arm9/gfx/checkboxChecked.grit
Normal file
@@ -0,0 +1,7 @@
|
||||
# tile format
|
||||
-gt
|
||||
|
||||
# graphics bit depth is 4 (16 color)
|
||||
-gB4
|
||||
|
||||
-p!
|
||||
BIN
arm9/gfx/checkboxChecked.png
Normal file
BIN
arm9/gfx/checkboxChecked.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 B |
7
arm9/gfx/checkboxUnchecked.grit
Normal file
7
arm9/gfx/checkboxUnchecked.grit
Normal file
@@ -0,0 +1,7 @@
|
||||
# tile format
|
||||
-gt
|
||||
|
||||
# graphics bit depth is 4 (16 color)
|
||||
-gB4
|
||||
|
||||
-p!
|
||||
BIN
arm9/gfx/checkboxUnchecked.png
Normal file
BIN
arm9/gfx/checkboxUnchecked.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 167 B |
7
arm9/gfx/folderIcon.grit
Normal file
7
arm9/gfx/folderIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
||||
# tile format
|
||||
-gt
|
||||
|
||||
# graphics bit depth is 4 (16 color)
|
||||
-gB4
|
||||
|
||||
-p!
|
||||
BIN
arm9/gfx/folderIcon.png
Normal file
BIN
arm9/gfx/folderIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 177 B |
@@ -20,6 +20,7 @@
|
||||
#include "romBrowser/DisplayMode/RomBrowserDisplayModeFactory.h"
|
||||
#include "romBrowser/Theme/Material/MaterialThemeFileIconFactory.h"
|
||||
#include "romBrowser/views/NdsGameDetailsBottomSheetView.h"
|
||||
#include "romBrowser/views/cheats/CheatsBottomSheetView.h"
|
||||
#include "romBrowser/views/DisplaySettingsBottomSheetView.h"
|
||||
#include "bgm/AudioStreamPlayer.h"
|
||||
#include "bgm/BgmService.h"
|
||||
@@ -44,7 +45,7 @@ App::App(IAppSettingsService& appSettingsService, IBgmService& bgmService)
|
||||
, _bgmService(bgmService)
|
||||
, _inputProvider(&_inputSource)
|
||||
, _inputRepeater(&_inputProvider,
|
||||
InputKey::DpadLeft | InputKey::DpadRight | InputKey::DpadUp | InputKey::DpadDown,
|
||||
InputKey::DpadLeft | InputKey::DpadRight | InputKey::DpadUp | InputKey::DpadDown | InputKey::L | InputKey::R,
|
||||
25, 8)
|
||||
, _romBrowserController(&appSettingsService, &_ioTaskQueue, &_bgTaskQueue)
|
||||
, _displaySettingsBottomSheetViewModel(&_romBrowserController)
|
||||
@@ -295,10 +296,15 @@ void App::HandleTrigger(RomBrowserStateTrigger trigger, RomBrowserState newState
|
||||
|
||||
void App::HandleShowGameInfoTrigger()
|
||||
{
|
||||
auto gameInfoDialog = std::make_unique<NdsGameDetailsBottomSheetView>(
|
||||
&_romBrowserController, &_theme->GetMaterialColorScheme(), _theme->GetFontRepository());
|
||||
gameInfoDialog->SetGraphics(_chipViewVram);
|
||||
_dialogPresenter.ShowDialog(std::move(gameInfoDialog));
|
||||
// auto gameInfoDialog = std::make_unique<NdsGameDetailsBottomSheetView>(
|
||||
// &_romBrowserController, &_theme->GetMaterialColorScheme(), _theme->GetFontRepository());
|
||||
// gameInfoDialog->SetGraphics(_chipViewVram);
|
||||
// _dialogPresenter.ShowDialog(std::move(gameInfoDialog));
|
||||
|
||||
auto cheatsViewModel = std::make_unique<CheatsViewModel>(_romBrowserController.GetTriggerFileInfo(), &_romBrowserController);
|
||||
auto cheatsDialog = std::make_unique<CheatsBottomSheetView>(
|
||||
std::move(cheatsViewModel), &_theme->GetMaterialColorScheme(), _theme->GetFontRepository(), &_focusManager);
|
||||
_dialogPresenter.ShowDialog(std::move(cheatsDialog));
|
||||
}
|
||||
|
||||
void App::HandleHideGameInfoTrigger()
|
||||
|
||||
190
arm9/source/cheats/CheatEntry.h
Normal file
190
arm9/source/cheats/CheatEntry.h
Normal file
@@ -0,0 +1,190 @@
|
||||
#pragma once
|
||||
|
||||
/// @brief Class representing a cheat or a cheat category.
|
||||
class CheatEntry
|
||||
{
|
||||
public:
|
||||
/// @brief Dummy empty constructor.
|
||||
CheatEntry()
|
||||
: _isCheatCategory(false), _flagsPointer(nullptr), _cheatData(nullptr), _cheatDataLength(0) { }
|
||||
|
||||
/// @brief Constructor for a cheat.
|
||||
CheatEntry(const char* name, const char* description, u32* flagsPointer, const void* cheatData, u32 cheatDataLength)
|
||||
: _name(name), _description(description), _isCheatCategory(false), _flagsPointer(flagsPointer)
|
||||
, _cheatData(cheatData), _cheatDataLength(cheatDataLength) { }
|
||||
|
||||
/// @brief Constructor for a category.
|
||||
CheatEntry(const char* name, const char* description, bool isMaxOneCheatActive, CheatEntry* subEntries, u32 numberOfSubEntries)
|
||||
: _name(name), _description(description), _isCheatCategory(true), _isMaxOneCheatActive(isMaxOneCheatActive)
|
||||
, _subEntries(subEntries), _numberOfSubEntries(numberOfSubEntries) { }
|
||||
|
||||
CheatEntry(const CheatEntry& other) = delete;
|
||||
|
||||
CheatEntry(CheatEntry&& other)
|
||||
: _isCheatCategory(false), _flagsPointer(nullptr), _cheatData(nullptr), _cheatDataLength(0)
|
||||
{
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
~CheatEntry()
|
||||
{
|
||||
if (_isCheatCategory && _subEntries != nullptr)
|
||||
{
|
||||
delete[] _subEntries;
|
||||
}
|
||||
}
|
||||
|
||||
CheatEntry& operator=(const CheatEntry& other) = delete;
|
||||
|
||||
CheatEntry& operator=(CheatEntry&& other)
|
||||
{
|
||||
if (_isCheatCategory && _subEntries != nullptr)
|
||||
{
|
||||
delete[] _subEntries;
|
||||
_subEntries = nullptr;
|
||||
}
|
||||
|
||||
_name = other._name;
|
||||
other._name = nullptr;
|
||||
_description = other._description;
|
||||
other._description = nullptr;
|
||||
_isCheatCategory = other.IsCheatCategory();
|
||||
if (_isCheatCategory)
|
||||
{
|
||||
_isMaxOneCheatActive = other._isMaxOneCheatActive;
|
||||
_subEntries = other._subEntries;
|
||||
other._subEntries = nullptr;
|
||||
_numberOfSubEntries = other._numberOfSubEntries;
|
||||
other._numberOfSubEntries = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_flagsPointer = other._flagsPointer;
|
||||
other._flagsPointer = nullptr;
|
||||
_cheatData = other._cheatData;
|
||||
other._cheatData = nullptr;
|
||||
_cheatDataLength = other._cheatDataLength;
|
||||
other._cheatDataLength = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Gets the name of this cheat entry.
|
||||
/// @return A pointer to the name of this cheat entry.
|
||||
const char* GetName() const
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
/// @brief Gets the description of this cheat entry.
|
||||
/// @return A pointer to the description of this cheat entry.
|
||||
const char* GetDescription() const
|
||||
{
|
||||
return _description;
|
||||
}
|
||||
|
||||
/// @brief When this entry is a cheat, gets a pointer to the cheat data.
|
||||
/// @param cheatDataLength The length of the cheat data is returned in this reference.
|
||||
/// @return A pointer to the cheat data.
|
||||
/// This pointer is only valid for the lifetime of the \see GameCheats instance this cheat belongs to.
|
||||
/// If this entry is not a cheat, \c nullptr is returned.
|
||||
const void* GetCheatData(u32& cheatDataLength) const
|
||||
{
|
||||
if (_isCheatCategory)
|
||||
{
|
||||
cheatDataLength = 0;
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
cheatDataLength = _cheatDataLength;
|
||||
return _cheatData;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Gets whether this entry is an active (enabled) cheat or not.
|
||||
/// @return \c true when this entry is an active cheat, or \c false otherwise.
|
||||
bool GetIsCheatActive() const
|
||||
{
|
||||
if (_isCheatCategory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((*_flagsPointer >> 24) & 1) == 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief If this entry is a cheat, sets whether this cheat is active (enabled) or not.
|
||||
/// @param isCheatActive \c true to enable this cheat, or \c false to disable this cheat.
|
||||
void SetIsCheatActive(bool isCheatActive) const
|
||||
{
|
||||
if (!_isCheatCategory)
|
||||
{
|
||||
u32 flags = *_flagsPointer;
|
||||
flags &= ~(1 << 24);
|
||||
if (isCheatActive)
|
||||
{
|
||||
flags |= 1 << 24;
|
||||
}
|
||||
*_flagsPointer = flags;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Indicates if this entry is a cheat category or not.
|
||||
/// @return \c true when this entry is a cheat category, or \c false when this entry is a cheat.
|
||||
bool IsCheatCategory() const
|
||||
{
|
||||
return _isCheatCategory;
|
||||
}
|
||||
|
||||
/// @brief Indicates if this entry is a cheat category in which only one cheat is allowed to be on at a time or not.
|
||||
/// @return \c true when this entry is a cheat category in which only one cheat is allowed
|
||||
/// to be active at a time, or \c false otherwise.
|
||||
bool GetIsMaxOneCheatActive() const
|
||||
{
|
||||
return _isCheatCategory && _isMaxOneCheatActive;
|
||||
}
|
||||
|
||||
/// @brief Gets the sub-entries of this entry.
|
||||
/// @param numberOfSubEntries The number of sub-entries is returned through this reference.
|
||||
/// @return A pointer to an array of entries. This may be \c nullptr when \p numberOfSubEntries is 0.
|
||||
const CheatEntry* GetSubEntries(u32& numberOfSubEntries) const
|
||||
{
|
||||
if (_isCheatCategory)
|
||||
{
|
||||
numberOfSubEntries = _numberOfSubEntries;
|
||||
return _subEntries;
|
||||
}
|
||||
else
|
||||
{
|
||||
numberOfSubEntries = 0;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const char* _name = nullptr;
|
||||
const char* _description = nullptr;
|
||||
bool _isCheatCategory;
|
||||
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
// For cheat
|
||||
u32* _flagsPointer;
|
||||
const void* _cheatData;
|
||||
u32 _cheatDataLength;
|
||||
};
|
||||
struct
|
||||
{
|
||||
// For category
|
||||
bool _isMaxOneCheatActive;
|
||||
CheatEntry* _subEntries;
|
||||
u32 _numberOfSubEntries;
|
||||
};
|
||||
};
|
||||
};
|
||||
17
arm9/source/cheats/EmptyCheatRepository.h
Normal file
17
arm9/source/cheats/EmptyCheatRepository.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "ICheatRepository.h"
|
||||
|
||||
/// @brief Class implementing an empty cheat repository.
|
||||
class EmptyCheatRepository : public ICheatRepository
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<GameCheats> GetCheatsForGame(const FastFileRef& romFile) const override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UpdateEnabledCheatsForGame(const std::unique_ptr<GameCheats>& cheats) const override
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
32
arm9/source/cheats/GameCheats.h
Normal file
32
arm9/source/cheats/GameCheats.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include "CheatEntry.h"
|
||||
|
||||
/// @brief Class holding the cheats for a game.
|
||||
class GameCheats : public CheatEntry
|
||||
{
|
||||
public:
|
||||
GameCheats(std::unique_ptr<u8[]> cheatData, u32 cheatDataLength, u32 fileOffset, const char* gameName,
|
||||
CheatEntry* subEntries, u32 numberOfSubEntries)
|
||||
: CheatEntry(gameName, "", false, subEntries, numberOfSubEntries)
|
||||
, _cheatData(std::move(cheatData)), _cheatDataLength(cheatDataLength)
|
||||
, _fileOffset(fileOffset) { }
|
||||
|
||||
/// @brief Gets a pointer to the cheat data.
|
||||
/// @param cheatDataLength The length of the cheat data is returned in this reference.
|
||||
/// @return A pointer to the cheat data. This pointer is only valid for the lifetime of this \see GameCheats instance.
|
||||
u8* GetCheatData(u32& cheatDataLength) const
|
||||
{
|
||||
cheatDataLength = _cheatDataLength;
|
||||
return _cheatData.get();
|
||||
}
|
||||
|
||||
u32 GetFileOffset() const
|
||||
{
|
||||
return _fileOffset;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<u8[]> _cheatData;
|
||||
u32 _cheatDataLength;
|
||||
u32 _fileOffset;
|
||||
};
|
||||
22
arm9/source/cheats/ICheatRepository.h
Normal file
22
arm9/source/cheats/ICheatRepository.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "GameCheats.h"
|
||||
#include "fat/FastFileRef.h"
|
||||
|
||||
/// @brief Interface for a repository providing access to cheats.
|
||||
class ICheatRepository
|
||||
{
|
||||
public:
|
||||
virtual ~ICheatRepository() { }
|
||||
|
||||
/// @brief Gets the available cheats for the given \p romFile.
|
||||
/// @param romFile Reference to the rom file.
|
||||
/// @return A unique pointer to the found cheats, or an empty unique pointer when no cheats were found.
|
||||
virtual std::unique_ptr<GameCheats> GetCheatsForGame(const FastFileRef& romFile) const = 0;
|
||||
|
||||
/// @brief Writes the enable/disabled status of the given \p cheats.
|
||||
/// @param cheats The cheats to update.
|
||||
virtual void UpdateEnabledCheatsForGame(const std::unique_ptr<GameCheats>& cheats) const = 0;
|
||||
|
||||
protected:
|
||||
ICheatRepository() { }
|
||||
};
|
||||
80
arm9/source/cheats/PicoLoaderCheatDataFactory.cpp
Normal file
80
arm9/source/cheats/PicoLoaderCheatDataFactory.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include "PicoLoaderCheatDataFactory.h"
|
||||
|
||||
pload_cheats_t* PicoLoaderCheatDataFactory::CreateCheatData(const std::unique_ptr<GameCheats>& gameCheats) const
|
||||
{
|
||||
pload_cheats_t* cheatData = nullptr;
|
||||
if (gameCheats)
|
||||
{
|
||||
u32 totalNumberOfCheats = 0;
|
||||
u32 requiredSize = GetCheatEntryRequiredSize(gameCheats.get(), totalNumberOfCheats);
|
||||
if (totalNumberOfCheats != 0)
|
||||
{
|
||||
requiredSize += sizeof(u32) * 2;
|
||||
cheatData = (pload_cheats_t*)new u8[requiredSize];
|
||||
cheatData->length = requiredSize;
|
||||
cheatData->numberOfCheats = totalNumberOfCheats;
|
||||
u8* buffer = (u8*)&cheatData->firstCheat;
|
||||
GetCheatEntryData(gameCheats.get(), buffer);
|
||||
}
|
||||
}
|
||||
|
||||
return cheatData;
|
||||
}
|
||||
|
||||
u32 PicoLoaderCheatDataFactory::GetCheatEntryRequiredSize(const CheatEntry* cheatEntry, u32& totalNumberOfCheats) const
|
||||
{
|
||||
u32 requiredSize = 0;
|
||||
if (cheatEntry->IsCheatCategory())
|
||||
{
|
||||
u32 numberOfSubEntries = 0;
|
||||
auto subEntries = cheatEntry->GetSubEntries(numberOfSubEntries);
|
||||
for (u32 i = 0; i < numberOfSubEntries; i++)
|
||||
{
|
||||
requiredSize += GetCheatEntryRequiredSize(&subEntries[i], totalNumberOfCheats);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cheatEntry->GetIsCheatActive())
|
||||
{
|
||||
u32 cheatDataLength = 0;
|
||||
cheatEntry->GetCheatData(cheatDataLength);
|
||||
requiredSize += sizeof(u32) + ((cheatDataLength + 7) & ~7);
|
||||
totalNumberOfCheats++;
|
||||
}
|
||||
}
|
||||
|
||||
return requiredSize;
|
||||
}
|
||||
|
||||
void PicoLoaderCheatDataFactory::GetCheatEntryData(const CheatEntry* cheatEntry, u8*& buffer) const
|
||||
{
|
||||
if (cheatEntry->IsCheatCategory())
|
||||
{
|
||||
u32 numberOfSubEntries = 0;
|
||||
auto subEntries = cheatEntry->GetSubEntries(numberOfSubEntries);
|
||||
for (u32 i = 0; i < numberOfSubEntries; i++)
|
||||
{
|
||||
GetCheatEntryData(&subEntries[i], buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cheatEntry->GetIsCheatActive())
|
||||
{
|
||||
u32 cheatDataLength = 0;
|
||||
auto cheatData = cheatEntry->GetCheatData(cheatDataLength);
|
||||
u32 paddedCheatDataLength = (cheatDataLength + 7) & ~7;
|
||||
*(u32*)buffer = paddedCheatDataLength;
|
||||
buffer += sizeof(u32);
|
||||
memcpy(buffer, cheatData, cheatDataLength);
|
||||
if (cheatDataLength != paddedCheatDataLength)
|
||||
{
|
||||
memset(buffer + cheatDataLength, 0, paddedCheatDataLength - cheatDataLength);
|
||||
}
|
||||
buffer += paddedCheatDataLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
arm9/source/cheats/PicoLoaderCheatDataFactory.h
Normal file
18
arm9/source/cheats/PicoLoaderCheatDataFactory.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "GameCheats.h"
|
||||
#include "picoLoader7.h"
|
||||
|
||||
/// @brief Factory for creating Pico Loader compatible cheat data.
|
||||
class PicoLoaderCheatDataFactory
|
||||
{
|
||||
public:
|
||||
/// @brief Converts the given \p gameCheats to Pico Loader format. Only the enabled cheats will be included.
|
||||
/// @param gameCheats The cheats to convert.
|
||||
/// @return Pointer to the created cheat data.
|
||||
pload_cheats_t* CreateCheatData(const std::unique_ptr<GameCheats>& gameCheats) const;
|
||||
|
||||
private:
|
||||
u32 GetCheatEntryRequiredSize(const CheatEntry* cheatEntry, u32& totalNumberOfCheats) const;
|
||||
void GetCheatEntryData(const CheatEntry* cheatEntry, u8*& buffer) const;
|
||||
};
|
||||
12
arm9/source/cheats/UsrCheatDat.h
Normal file
12
arm9/source/cheats/UsrCheatDat.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
/// @brief Struct representing an index entry of usrcheat.dat.
|
||||
struct usr_cheat_index_entry_t
|
||||
{
|
||||
u32 gameCode;
|
||||
u32 headerCrc32;
|
||||
u32 offset;
|
||||
u32 padding;
|
||||
};
|
||||
|
||||
static_assert(sizeof(usr_cheat_index_entry_t) == 16);
|
||||
225
arm9/source/cheats/UsrCheatRepository.cpp
Normal file
225
arm9/source/cheats/UsrCheatRepository.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
#include "common.h"
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
#include "UsrCheatRepository.h"
|
||||
|
||||
#define CRCPOLY 0xEDB88320
|
||||
|
||||
static u32 crc32(const void* buffer, u32 length)
|
||||
{
|
||||
u32 crc = ~0u;
|
||||
const u8* p = (u8*)buffer;
|
||||
while (length--)
|
||||
{
|
||||
crc ^= *p++;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY : 0);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
std::unique_ptr<GameCheats> UsrCheatRepository::GetCheatsForGame(const FastFileRef& romFile) const
|
||||
{
|
||||
auto file = std::make_unique<File>();
|
||||
file->Open(romFile, FA_READ);
|
||||
auto headerBuffer = std::make_unique_for_overwrite<u8[]>(512);
|
||||
if (!file->ReadExact(headerBuffer.get(), 512))
|
||||
{
|
||||
LOG_ERROR("Could not read rom header\n");
|
||||
return nullptr;
|
||||
}
|
||||
file->Close();
|
||||
|
||||
u32 gameCode = *(u32*)(headerBuffer.get() + 0xC);
|
||||
u32 crc = crc32(headerBuffer.get(), 512);
|
||||
|
||||
headerBuffer.reset();
|
||||
|
||||
return GetCheatsForGame(gameCode, crc);
|
||||
}
|
||||
|
||||
void UsrCheatRepository::UpdateEnabledCheatsForGame(const std::unique_ptr<GameCheats>& cheats) const
|
||||
{
|
||||
u32 cheatDataLength = 0;
|
||||
auto cheatData = cheats->GetCheatData(cheatDataLength);
|
||||
if (_usrCheatFile->Seek(cheats->GetFileOffset()) != FR_OK)
|
||||
{
|
||||
LOG_ERROR("Failed to seek to cheat data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
u32 bytesWritten = 0;
|
||||
if (_usrCheatFile->Write(cheatData, cheatDataLength, bytesWritten) != FR_OK ||
|
||||
bytesWritten != cheatDataLength)
|
||||
{
|
||||
LOG_ERROR("Failed to write cheat data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_usrCheatFile->Sync() != FR_OK)
|
||||
{
|
||||
LOG_ERROR("Failed to flush cheat data\n");
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<GameCheats> UsrCheatRepository::GetCheatsForGame(u32 gameCode, u32 headerCrc32) const
|
||||
{
|
||||
auto index = FindIndex(gameCode, headerCrc32);
|
||||
if (index == nullptr)
|
||||
{
|
||||
LOG_DEBUG("Cheats not found for %c%c%c%c - 0x%X\n",
|
||||
gameCode & 0xFF, (gameCode >> 8) & 0xFF, (gameCode >> 16) & 0xFF, gameCode >> 24,
|
||||
headerCrc32);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const u32 cheatDataLength = index->padding; // padding was set to the size in UsrCheatRepositoryFactory
|
||||
auto cheatData = std::make_unique_for_overwrite<u8[]>(cheatDataLength);
|
||||
if (_usrCheatFile->Seek(index->offset) != FR_OK)
|
||||
{
|
||||
LOG_ERROR("Failed to seek to cheat data\n");
|
||||
return nullptr;
|
||||
}
|
||||
if (!_usrCheatFile->ReadExact(cheatData.get(), cheatDataLength))
|
||||
{
|
||||
LOG_ERROR("Failed to read cheat data\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u8* ptr = cheatData.get();
|
||||
|
||||
// game name
|
||||
const char* gameName = (const char*)ptr;
|
||||
ptr += strlen(gameName) + 1;
|
||||
|
||||
// padding
|
||||
ptr = (u8*)(((u32)ptr + 3) & ~3); // 32-bit align
|
||||
|
||||
// flags
|
||||
u32 flags = *(u32*)ptr;
|
||||
u32 totalNumberOfItems = flags & 0x0FFFFFFF;
|
||||
// u32 gameActive = flags >> 28;
|
||||
ptr += 4;
|
||||
|
||||
// master codes
|
||||
ptr += 8 * 4;
|
||||
|
||||
auto entries = new CheatEntry[totalNumberOfItems];
|
||||
u32 entryCount = 0;
|
||||
|
||||
while (ptr < cheatData.get() + cheatDataLength)
|
||||
{
|
||||
u32 itemFlags = *(u32*)ptr;
|
||||
bool isCategory = ((itemFlags >> 28) & 1) == 1;
|
||||
if (isCategory)
|
||||
{
|
||||
entries[entryCount++] = ParseCategory(ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
entries[entryCount++] = ParseCheat(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto actualEntries = new CheatEntry[entryCount];
|
||||
std::move(entries, entries + entryCount, actualEntries);
|
||||
delete[] entries;
|
||||
|
||||
return std::make_unique<GameCheats>(
|
||||
std::move(cheatData), cheatDataLength, index->offset, gameName, actualEntries, entryCount);
|
||||
}
|
||||
|
||||
const usr_cheat_index_entry_t* UsrCheatRepository::FindIndex(u32 gameCode, u32 headerCrc32) const
|
||||
{
|
||||
if (_numberOfIndices != 0)
|
||||
{
|
||||
const auto index = std::lower_bound(_sortedIndices.get(), _sortedIndices.get() + _numberOfIndices, gameCode,
|
||||
[headerCrc32] (const usr_cheat_index_entry_t& entry, u32 value)
|
||||
{
|
||||
if (entry.gameCode != value)
|
||||
{
|
||||
return entry.gameCode < value;
|
||||
}
|
||||
|
||||
return entry.headerCrc32 < headerCrc32;
|
||||
});
|
||||
|
||||
if (index != _sortedIndices.get() + _numberOfIndices &&
|
||||
index->gameCode == gameCode &&
|
||||
index->headerCrc32 == headerCrc32)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CheatEntry UsrCheatRepository::ParseCategory(u8*& ptr) const
|
||||
{
|
||||
// flags
|
||||
u32 itemFlags = *(u32*)ptr;
|
||||
ptr += 4;
|
||||
u32 numberOfItems = itemFlags & 0x00FFFFFF;
|
||||
bool isMaxOneCheatActive = ((itemFlags >> 24) & 1) == 1;
|
||||
|
||||
// item name
|
||||
const char* itemName = (const char*)ptr;
|
||||
ptr += strlen(itemName) + 1;
|
||||
|
||||
// item description
|
||||
const char* itemDescription = (const char*)ptr;
|
||||
ptr += strlen(itemDescription) + 1;
|
||||
|
||||
// padding
|
||||
ptr = (u8*)(((u32)ptr + 3) & ~3); // 32-bit align
|
||||
|
||||
auto entries = new CheatEntry[numberOfItems];
|
||||
for (u32 i = 0; i < numberOfItems; i++)
|
||||
{
|
||||
u32 itemFlags = *(u32*)ptr;
|
||||
bool isCategory = ((itemFlags >> 28) & 1) == 1;
|
||||
if (isCategory)
|
||||
{
|
||||
entries[i] = ParseCategory(ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
entries[i] = ParseCheat(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
return CheatEntry(itemName, itemDescription, isMaxOneCheatActive, entries, numberOfItems);
|
||||
}
|
||||
|
||||
CheatEntry UsrCheatRepository::ParseCheat(u8*& ptr) const
|
||||
{
|
||||
// flags
|
||||
u32* flagsPtr = (u32*)ptr;
|
||||
ptr += 4;
|
||||
|
||||
// item name
|
||||
const char* itemName = (const char*)ptr;
|
||||
ptr += strlen(itemName) + 1;
|
||||
|
||||
// item description
|
||||
const char* itemDescription = (const char*)ptr;
|
||||
ptr += strlen(itemDescription) + 1;
|
||||
|
||||
// padding
|
||||
ptr = (u8*)(((u32)ptr + 3) & ~3); // 32-bit align
|
||||
|
||||
// number of code words
|
||||
u32 numberOfCodeWords = *(u32*)ptr;
|
||||
ptr += 4;
|
||||
|
||||
const void* cheatData = ptr;
|
||||
|
||||
// code
|
||||
ptr += numberOfCodeWords * 4;
|
||||
|
||||
return CheatEntry(itemName, itemDescription, flagsPtr, cheatData, numberOfCodeWords * 4);
|
||||
}
|
||||
28
arm9/source/cheats/UsrCheatRepository.h
Normal file
28
arm9/source/cheats/UsrCheatRepository.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "fat/File.h"
|
||||
#include "ICheatRepository.h"
|
||||
#include "UsrCheatDat.h"
|
||||
|
||||
/// @brief Class implementing a cheat repository for usrcheat.dat.
|
||||
class UsrCheatRepository : public ICheatRepository
|
||||
{
|
||||
public:
|
||||
UsrCheatRepository(std::unique_ptr<File> usrCheatDatFile,
|
||||
std::unique_ptr<usr_cheat_index_entry_t[]> sortedIndices, u32 numberOfIndices)
|
||||
: _usrCheatFile(std::move(usrCheatDatFile))
|
||||
, _sortedIndices(std::move(sortedIndices)), _numberOfIndices(numberOfIndices) { }
|
||||
|
||||
std::unique_ptr<GameCheats> GetCheatsForGame(const FastFileRef& romFile) const override;
|
||||
void UpdateEnabledCheatsForGame(const std::unique_ptr<GameCheats>& cheats) const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<File> _usrCheatFile;
|
||||
std::unique_ptr<usr_cheat_index_entry_t[]> _sortedIndices;
|
||||
u32 _numberOfIndices;
|
||||
|
||||
std::unique_ptr<GameCheats> GetCheatsForGame(u32 gameCode, u32 headerCrc32) const;
|
||||
const usr_cheat_index_entry_t* FindIndex(u32 gameCode, u32 headerCrc32) const;
|
||||
CheatEntry ParseCategory(u8*& ptr) const;
|
||||
CheatEntry ParseCheat(u8*& ptr) const;
|
||||
};
|
||||
87
arm9/source/cheats/UsrCheatRepositoryFactory.cpp
Normal file
87
arm9/source/cheats/UsrCheatRepositoryFactory.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "common.h"
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
#include "fat/File.h"
|
||||
#include "UsrCheatDat.h"
|
||||
#include "UsrCheatRepositoryFactory.h"
|
||||
|
||||
std::unique_ptr<UsrCheatRepository> UsrCheatRepositoryFactory::FromUsrCheatDat(const TCHAR* usrCheatDatPath)
|
||||
{
|
||||
auto file = std::make_unique<File>();
|
||||
if (file->Open(usrCheatDatPath, FA_READ | FA_WRITE) != FR_OK)
|
||||
{
|
||||
LOG_ERROR("Failed to open %s\n", usrCheatDatPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char header[12];
|
||||
if (!file->ReadExact(header, sizeof(header)))
|
||||
{
|
||||
LOG_ERROR("Failed to read usr cheat header\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (memcmp(header, "R4 CheatCode", sizeof(header)))
|
||||
{
|
||||
LOG_ERROR("Invalid usr cheat header\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Read index
|
||||
if (file->Seek(0x100) != FR_OK)
|
||||
{
|
||||
LOG_ERROR("Failed to seek to usr cheat index\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
usr_cheat_index_entry_t firstEntry;
|
||||
if (!file->ReadExact(&firstEntry, sizeof(firstEntry)))
|
||||
{
|
||||
LOG_ERROR("Failed to read first index\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 numberOfIndices = 0;
|
||||
if (firstEntry.offset != 0)
|
||||
{
|
||||
numberOfIndices = (firstEntry.offset - 0x100 - sizeof(usr_cheat_index_entry_t)) / sizeof(usr_cheat_index_entry_t);
|
||||
}
|
||||
|
||||
if (file->Seek(0x100) != FR_OK)
|
||||
{
|
||||
LOG_ERROR("Failed to seek to usr cheat index\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto indices = std::make_unique_for_overwrite<usr_cheat_index_entry_t[]>(numberOfIndices);
|
||||
if (!file->ReadExact(indices.get(), numberOfIndices * sizeof(usr_cheat_index_entry_t)))
|
||||
{
|
||||
LOG_ERROR("Failed to read first index\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Set padding field to the size of the cheat data
|
||||
if (numberOfIndices > 0)
|
||||
{
|
||||
for (u32 i = 0; i < numberOfIndices - 1; i++)
|
||||
{
|
||||
indices[i].padding = indices[i + 1].offset - indices[i].offset;
|
||||
}
|
||||
|
||||
indices[numberOfIndices - 1].padding = file->GetSize() - indices[numberOfIndices - 1].offset;
|
||||
}
|
||||
|
||||
// sort by gameCode, then by headerCrc32
|
||||
std::sort(indices.get(), indices.get() + numberOfIndices,
|
||||
[] (const usr_cheat_index_entry_t& a, const usr_cheat_index_entry_t& b)
|
||||
{
|
||||
if (a.gameCode != b.gameCode)
|
||||
{
|
||||
return a.gameCode < b.gameCode;
|
||||
}
|
||||
|
||||
return a.headerCrc32 < b.headerCrc32;
|
||||
});
|
||||
|
||||
return std::make_unique<UsrCheatRepository>(std::move(file), std::move(indices), numberOfIndices);
|
||||
}
|
||||
13
arm9/source/cheats/UsrCheatRepositoryFactory.h
Normal file
13
arm9/source/cheats/UsrCheatRepositoryFactory.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "UsrCheatRepository.h"
|
||||
|
||||
/// @brief Factory for constructing a \see UsrCheatRepository for a usrcheat.dat file.
|
||||
class UsrCheatRepositoryFactory
|
||||
{
|
||||
public:
|
||||
/// @brief Constructs a \see UsrCheatRepository for the given \p usrCheatDatPath.
|
||||
/// @param usrCheatDatPath The path to the usrcheat.dat file.
|
||||
/// @return A unique pointer to the constructed repository, or an empty unique pointer when construction failed.
|
||||
std::unique_ptr<UsrCheatRepository> FromUsrCheatDat(const TCHAR* usrCheatDatPath);
|
||||
};
|
||||
@@ -75,3 +75,5 @@ public:
|
||||
return Rgb(r + other.r, g + other.g, b + other.b);
|
||||
}
|
||||
};
|
||||
|
||||
using Rgb8 = Rgb<8, 8, 8>;
|
||||
|
||||
@@ -65,7 +65,7 @@ template <>
|
||||
class Task<void> : public TaskBase
|
||||
{
|
||||
protected:
|
||||
virtual TaskResult<void> ExecuteFunc() const = 0;
|
||||
virtual TaskResult<void> ExecuteFunc() = 0;
|
||||
|
||||
private:
|
||||
TaskState ExecuteDirect() override
|
||||
@@ -79,11 +79,11 @@ template <class T, typename FuncType>
|
||||
class FuncTask : public Task<T>
|
||||
{
|
||||
public:
|
||||
FuncTask(const FuncType& function)
|
||||
: _function(function) { }
|
||||
FuncTask(FuncType&& function)
|
||||
: _function(std::move(function)) { }
|
||||
|
||||
private:
|
||||
const FuncType _function;
|
||||
FuncType _function;
|
||||
|
||||
TaskResult<T> ExecuteFunc() const override { return _function((const volatile u8&)this->_cancelRequested); }
|
||||
TaskResult<T> ExecuteFunc() override { return _function((const volatile u8&)this->_cancelRequested); }
|
||||
};
|
||||
|
||||
@@ -79,12 +79,12 @@ public:
|
||||
|
||||
template <typename FuncType>
|
||||
[[gnu::noinline]]
|
||||
auto Enqueue(const FuncType& function) -> QueueTask<decltype(TaskResultToResultType(function(*new vu8())))>
|
||||
auto Enqueue(FuncType&& function) -> QueueTask<decltype(TaskResultToResultType(function(*new vu8())))>
|
||||
{
|
||||
using TaskType = FuncTask<decltype(TaskResultToResultType(function(*new vu8()))), FuncType>;
|
||||
// static_assert(sizeof(TaskType) <= MaxTaskSize, "Task is too big for this pool");
|
||||
void* slot = GetSlot();
|
||||
auto task = new (slot) TaskType(function);
|
||||
auto task = new (slot) TaskType(std::move(function));
|
||||
Enqueue(task);
|
||||
return QueueTask(task, this);
|
||||
}
|
||||
|
||||
@@ -89,14 +89,8 @@ u32 AdvancedPaletteManagerBase::TryMerge(PaletteRow* rows, const PaletteRow& new
|
||||
|
||||
u32 AdvancedPaletteManagerBase::AllocRowInternal(PaletteRow* rows, const IPalette& palette, int yStart, int yEnd)
|
||||
{
|
||||
if (yStart < 0)
|
||||
{
|
||||
yStart = 0;
|
||||
}
|
||||
if (yEnd > 192)
|
||||
{
|
||||
yEnd = 192;
|
||||
}
|
||||
yStart = std::clamp(yStart, 0, 192);
|
||||
yEnd = std::clamp(yEnd, 0, 192);
|
||||
|
||||
u32 newIdx = _usedRows;
|
||||
PaletteRow& newRow = rows[newIdx];
|
||||
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
|
||||
u32 AllocRow(const IPalette& palette, int yStart, int yEnd) override
|
||||
{
|
||||
yEnd++; // avoid back to back allocation
|
||||
return AllocRowInternal(_rows[_curSet], palette, yStart, yEnd);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,24 +28,28 @@ int nft2_findGlyphIdxForCharacter(const nft2_header_t* font, u16 character)
|
||||
}
|
||||
|
||||
static inline void renderGlyph(const nft2_header_t* font, const nft2_glyph_t* glyph,
|
||||
int xPos, int yPos, int width, int height, u8* dst, u32 stride, bool a5i3)
|
||||
int xPos, int yPos, const nft2_string_render_params_t* renderParams, u8* dst, u32 stride, bool a5i3)
|
||||
{
|
||||
int yOffset = glyph->spacingTop;
|
||||
u32 xStart = xPos < 0 ? -xPos : 0;
|
||||
u32 yStart = yPos + yOffset < 0 ? -(yPos + yOffset) : 0;
|
||||
|
||||
int xEnd = glyph->glyphWidth;
|
||||
if (xPos + xEnd > width)
|
||||
if (xPos + xEnd > (int)renderParams->width)
|
||||
{
|
||||
if (renderParams->onlyRenderWholeGlyphs)
|
||||
{
|
||||
// by returning we only render complete glyphs
|
||||
return;
|
||||
// old code for rendering partial glyphs
|
||||
// xEnd = width - xPos;
|
||||
}
|
||||
|
||||
xEnd = renderParams->width - xPos;
|
||||
}
|
||||
|
||||
int yEnd = glyph->glyphHeight;
|
||||
if (yPos + yOffset + yEnd > height)
|
||||
yEnd = height - (yPos + yOffset); // allow partial glyphs in the vertical direction
|
||||
if (yPos + yOffset + yEnd > (int)renderParams->height)
|
||||
{
|
||||
yEnd = renderParams->height - (yPos + yOffset); // allow partial glyphs in the vertical direction
|
||||
}
|
||||
|
||||
const u8* glyphData = &font->glyphDataPtr[glyph->dataOffset];
|
||||
glyphData += yStart * ((glyph->glyphWidth + 1) >> 1);
|
||||
@@ -97,15 +101,15 @@ static inline void renderGlyph(const nft2_header_t* font, const nft2_glyph_t* gl
|
||||
}
|
||||
|
||||
static ITCM_CODE void renderGlyphTiled(const nft2_header_t* font, const nft2_glyph_t* glyph,
|
||||
int xPos, int yPos, int width, int height, u8* dst, u32 stride)
|
||||
int xPos, int yPos, const nft2_string_render_params_t* renderParams, u8* dst, u32 stride)
|
||||
{
|
||||
renderGlyph(font, glyph, xPos, yPos, width, height, dst, stride, false);
|
||||
renderGlyph(font, glyph, xPos, yPos, renderParams, dst, stride, false);
|
||||
}
|
||||
|
||||
static ITCM_CODE void renderGlyphA5I3(const nft2_header_t* font, const nft2_glyph_t* glyph,
|
||||
int xPos, int yPos, int width, int height, u8* dst, u32 stride)
|
||||
int xPos, int yPos, const nft2_string_render_params_t* renderParams, u8* dst, u32 stride)
|
||||
{
|
||||
renderGlyph(font, glyph, xPos, yPos, width, height, dst, stride, true);
|
||||
renderGlyph(font, glyph, xPos, yPos, renderParams, dst, stride, true);
|
||||
}
|
||||
|
||||
ITCM_CODE void nft2_renderString(const nft2_header_t* font, const char16_t* string, u8* dst, u32 stride,
|
||||
@@ -134,18 +138,18 @@ ITCM_CODE void nft2_renderString(const nft2_header_t* font, const char16_t* stri
|
||||
xPos += glyph->spacingLeft;
|
||||
if (a5i3)
|
||||
{
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams, dst, stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams, dst, stride);
|
||||
}
|
||||
xPos += glyph->glyphWidth;
|
||||
if (xPos > (int)textWidth)
|
||||
textWidth = xPos;
|
||||
xPos += glyph->spacingRight;
|
||||
}
|
||||
renderParams->textWidth = textWidth;
|
||||
renderParams->textWidth = textWidth - renderParams->x;
|
||||
}
|
||||
|
||||
ITCM_CODE void nft2_measureString(const nft2_header_t* font, const char16_t* string, u32& width, u32& height)
|
||||
@@ -265,11 +269,11 @@ ITCM_CODE void nft2_renderStringEllipsis(const nft2_header_t* font, const char16
|
||||
xPos += glyph->spacingLeft;
|
||||
if (a5i3)
|
||||
{
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams, dst, stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams, dst, stride);
|
||||
}
|
||||
xPos += glyph->glyphWidth;
|
||||
if (xPos > (int)textWidth)
|
||||
@@ -287,11 +291,11 @@ ITCM_CODE void nft2_renderStringEllipsis(const nft2_header_t* font, const char16
|
||||
xPos += glyph->spacingLeft;
|
||||
if (a5i3)
|
||||
{
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams, dst, stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams, dst, stride);
|
||||
}
|
||||
xPos += glyph->glyphWidth;
|
||||
if (xPos > (int)textWidth)
|
||||
@@ -309,16 +313,16 @@ ITCM_CODE void nft2_renderStringEllipsis(const nft2_header_t* font, const char16
|
||||
xPos += glyph->spacingLeft;
|
||||
if (a5i3)
|
||||
{
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams, dst, stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams, dst, stride);
|
||||
}
|
||||
xPos += glyph->glyphWidth;
|
||||
if (xPos > (int)textWidth)
|
||||
textWidth = xPos;
|
||||
xPos += glyph->spacingRight;
|
||||
}
|
||||
renderParams->textWidth = textWidth;
|
||||
renderParams->textWidth = textWidth - renderParams->x;
|
||||
}
|
||||
@@ -39,6 +39,7 @@ struct nft2_string_render_params_t
|
||||
u32 textWidth;
|
||||
// u32 textHeight;
|
||||
bool a5i3;
|
||||
bool onlyRenderWholeGlyphs = true;
|
||||
};
|
||||
|
||||
/// @brief Prepares the ntf2 data of the given \p font for runtime use.
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#include "gui/palette/GradientPalette.h"
|
||||
#include "LabelView.h"
|
||||
|
||||
#define MARQUEE_START_FRAMES 60
|
||||
#define MARQUEE_STEP_FRAMES 3
|
||||
#define MARQUEE_END_FRAMES 90
|
||||
|
||||
LabelView::LabelView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font, bool a5i3)
|
||||
: _width(width), _height(height)
|
||||
, _maxStringLength(maxStringLength), _font(font)
|
||||
@@ -34,14 +38,30 @@ LabelView::LabelView(u32 width, u32 height, u32 maxStringLength, const nft2_head
|
||||
SetText(u"");
|
||||
}
|
||||
|
||||
void LabelView::Update()
|
||||
{
|
||||
if (_ellipsisStyleChanged)
|
||||
{
|
||||
RestartMarquee();
|
||||
UpdateTileBuffer();
|
||||
_ellipsisStyleChanged = false;
|
||||
}
|
||||
else if (_ellipsisStyle == EllipsisStyle::Marquee)
|
||||
{
|
||||
UpdateMarquee();
|
||||
}
|
||||
}
|
||||
|
||||
void LabelView::SetTextBuffer(const char* text)
|
||||
{
|
||||
StringUtil::Copy(_textBuffer.get(), text, _maxStringLength + 1);
|
||||
RestartMarquee();
|
||||
}
|
||||
|
||||
void LabelView::SetTextBuffer(const char16_t* text)
|
||||
{
|
||||
StringUtil::Copy(_textBuffer.get(), text, _maxStringLength + 1);
|
||||
RestartMarquee();
|
||||
}
|
||||
|
||||
void LabelView::SetTextBuffer(const char16_t* text, u32 length)
|
||||
@@ -50,6 +70,7 @@ void LabelView::SetTextBuffer(const char16_t* text, u32 length)
|
||||
if (copyLength > 0)
|
||||
memcpy(_textBuffer.get(), text, copyLength * 2);
|
||||
_textBuffer[copyLength] = 0;
|
||||
RestartMarquee();
|
||||
}
|
||||
|
||||
void LabelView::UpdateTileBuffer()
|
||||
@@ -63,10 +84,19 @@ void LabelView::UpdateTileBuffer()
|
||||
renderParams.width = _width;
|
||||
renderParams.height = _height;
|
||||
renderParams.a5i3 = _a5i3;
|
||||
if (_ellipsis)
|
||||
if (_ellipsisStyle == EllipsisStyle::Ellipsis)
|
||||
{
|
||||
nft2_renderStringEllipsis(_font, _textBuffer.get(), _tileBuffer.get(), _actualWidth, &renderParams, u" ... ");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_ellipsisStyle == EllipsisStyle::Marquee)
|
||||
{
|
||||
renderParams.x = -_marqueeOffset;
|
||||
renderParams.onlyRenderWholeGlyphs = false;
|
||||
}
|
||||
nft2_renderString(_font, _textBuffer.get(), _tileBuffer.get(), _actualWidth, &renderParams);
|
||||
}
|
||||
_newStringWidth = renderParams.textWidth;
|
||||
}
|
||||
else
|
||||
@@ -119,3 +149,59 @@ QueueTask<void> LabelView::SetTextAsync(TaskQueueBase* taskQueue, const char16_t
|
||||
SetTextBuffer(text, length);
|
||||
return UpdateTileBufferAsync(taskQueue);
|
||||
}
|
||||
|
||||
void LabelView::RestartMarquee()
|
||||
{
|
||||
_marqueeOffset = 0;
|
||||
_marqueeCounter = MARQUEE_START_FRAMES;
|
||||
_marqueeState = MarqueeState::StartWait;
|
||||
}
|
||||
|
||||
void LabelView::UpdateMarquee()
|
||||
{
|
||||
UpdateTileBuffer();
|
||||
if (_newStringWidth <= _width)
|
||||
{
|
||||
_marqueeOffset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (_marqueeState)
|
||||
{
|
||||
case MarqueeState::StartWait:
|
||||
{
|
||||
if (--_marqueeCounter <= 0)
|
||||
{
|
||||
_marqueeState = MarqueeState::Moving;
|
||||
_marqueeOffset = 0;
|
||||
_marqueeCounter = MARQUEE_STEP_FRAMES;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MarqueeState::Moving:
|
||||
{
|
||||
if (--_marqueeCounter == 0)
|
||||
{
|
||||
_marqueeCounter = MARQUEE_STEP_FRAMES;
|
||||
_marqueeOffset++;
|
||||
if (_newStringWidth - _marqueeOffset < _width)
|
||||
{
|
||||
_marqueeState = MarqueeState::EndWait;
|
||||
_marqueeCounter = MARQUEE_END_FRAMES;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MarqueeState::EndWait:
|
||||
{
|
||||
if (--_marqueeCounter <= 0)
|
||||
{
|
||||
_marqueeState = MarqueeState::StartWait;
|
||||
_marqueeCounter = MARQUEE_START_FRAMES;
|
||||
_marqueeOffset = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,15 @@ class MaterialGraphicsContext;
|
||||
class LabelView : public View
|
||||
{
|
||||
public:
|
||||
enum class EllipsisStyle
|
||||
{
|
||||
None,
|
||||
Ellipsis,
|
||||
Marquee
|
||||
};
|
||||
|
||||
void Update() override;
|
||||
|
||||
void SetText(const char* text);
|
||||
void SetText(const char16_t* text);
|
||||
void SetText(const char16_t* text, u32 length);
|
||||
@@ -43,7 +52,14 @@ public:
|
||||
return Rectangle(_position, _width, _height);
|
||||
}
|
||||
|
||||
void SetEllipsis(bool ellipsis) { _ellipsis = ellipsis; }
|
||||
void SetEllipsisStyle(EllipsisStyle ellipsisStyle)
|
||||
{
|
||||
if (_ellipsisStyle != ellipsisStyle)
|
||||
{
|
||||
_ellipsisStyle = ellipsisStyle;
|
||||
_ellipsisStyleChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
u32 _width;
|
||||
@@ -61,14 +77,30 @@ protected:
|
||||
Rgb<8, 8, 8> _backgroundColor;
|
||||
Rgb<8, 8, 8> _foregroundColor;
|
||||
int _paletteRow = -1;
|
||||
bool _ellipsis = false;
|
||||
EllipsisStyle _ellipsisStyle = EllipsisStyle::None;
|
||||
bool _a5i3;
|
||||
|
||||
LabelView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font, bool a5i3);
|
||||
|
||||
void SetTextBuffer(const char* text);
|
||||
void SetTextBuffer(const char16_t* text);
|
||||
void SetTextBuffer(const char16_t* text, u32 length);
|
||||
virtual void UpdateTileBuffer();
|
||||
QueueTask<void> UpdateTileBufferAsync(TaskQueueBase* taskQueue);
|
||||
|
||||
LabelView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font, bool a5i3);
|
||||
private:
|
||||
enum class MarqueeState
|
||||
{
|
||||
StartWait,
|
||||
Moving,
|
||||
EndWait
|
||||
};
|
||||
|
||||
MarqueeState _marqueeState = MarqueeState::StartWait;
|
||||
int _marqueeOffset = 0;
|
||||
int _marqueeCounter = 0;
|
||||
bool _ellipsisStyleChanged = false;
|
||||
|
||||
void RestartMarquee();
|
||||
void UpdateMarquee();
|
||||
};
|
||||
@@ -39,6 +39,10 @@ void RecyclerView::SetAdapter(const RecyclerAdapter* adapter, int initialSelecte
|
||||
_viewPool.reset();
|
||||
_viewPoolFreeCount = 0;
|
||||
_viewPoolTotalCount = 0;
|
||||
_xOffset = 0;
|
||||
_yOffset = 0;
|
||||
_curRangeStart = 0;
|
||||
_curRangeLength = 0;
|
||||
}
|
||||
_adapter = adapter;
|
||||
_adapter->GetViewSize(_itemWidth, _itemHeight);
|
||||
@@ -305,7 +309,7 @@ View* RecyclerView::MoveFocusVertical(View* currentFocus, FocusMoveDirection dir
|
||||
|
||||
bool RecyclerView::HandleInput(const InputProvider& inputProvider, FocusManager& focusManager)
|
||||
{
|
||||
if (inputProvider.Triggered(InputKey::L | InputKey::R))
|
||||
if (_itemCount != 0 && inputProvider.Triggered(InputKey::L | InputKey::R))
|
||||
{
|
||||
int direction = inputProvider.Triggered(InputKey::L) ? 1 : -1;
|
||||
int selected = _selectedItem->itemIdx;
|
||||
|
||||
@@ -21,6 +21,7 @@ typedef void (*pico_loader_9_func_t)(void);
|
||||
static pload_params_t sLoadParams;
|
||||
static char sLauncherPath[256] alignas(32);
|
||||
static PicoLoaderBootDrive sBootDrive;
|
||||
static const pload_cheats_t* sCheatData = nullptr;
|
||||
|
||||
pload_params_t* pload_getLoadParams()
|
||||
{
|
||||
@@ -37,6 +38,11 @@ void pload_setLauncherPath(const char* launcherPath)
|
||||
StringUtil::Copy(sLauncherPath, launcherPath, sizeof(sLauncherPath));
|
||||
}
|
||||
|
||||
void pload_setCheatData(const pload_cheats_t* cheatData)
|
||||
{
|
||||
sCheatData = cheatData;
|
||||
}
|
||||
|
||||
void pload_start()
|
||||
{
|
||||
mem_setVramAMapping(MEM_VRAM_AB_LCDC);
|
||||
@@ -92,6 +98,10 @@ void pload_start()
|
||||
{
|
||||
dma_ntrCopy16(3, &sLauncherPath, &header->v2.launcherPath, sizeof(header->v2.launcherPath));
|
||||
}
|
||||
if (header->apiVersion >= 3)
|
||||
{
|
||||
header->v3.cheats = sCheatData;
|
||||
}
|
||||
mem_setVramCMapping(MEM_VRAM_C_ARM7_00000);
|
||||
mem_setVramDMapping(MEM_VRAM_D_ARM7_20000);
|
||||
ipc_sendFifoMessage(IPC_CHANNEL_LOADER, 1);
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
pload_params_t* pload_getLoadParams();
|
||||
void pload_setBootDrive(PicoLoaderBootDrive bootDrive);
|
||||
void pload_setLauncherPath(const char* launcherPath);
|
||||
void pload_setCheatData(const pload_cheats_t* cheatData);
|
||||
void pload_start();
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
#include "FileInfo.h"
|
||||
|
||||
FileInfo::FileInfo(const FileInfo& fileInfo)
|
||||
: _type(fileInfo._type), _fastFileRef(fileInfo._fastFileRef)
|
||||
: _type(fileInfo._type), _fastFileRef(fileInfo._fastFileRef), _attributes(fileInfo._attributes)
|
||||
{
|
||||
u32 bufferLength = strlen(fileInfo.GetFileName()) + 1;
|
||||
_name = std::make_unique_for_overwrite<TCHAR[]>(bufferLength);
|
||||
StringUtil::Copy(_name.get(), fileInfo.GetFileName(), bufferLength);
|
||||
}
|
||||
|
||||
FileInfo::FileInfo(const TCHAR* fileName, const FileType* type, const FastFileRef& fastFileRef)
|
||||
: _type(type), _fastFileRef(fastFileRef)
|
||||
FileInfo::FileInfo(const TCHAR* fileName, const FileType* type, const FastFileRef& fastFileRef, u8 attributes)
|
||||
: _type(type), _fastFileRef(fastFileRef), _attributes(attributes)
|
||||
{
|
||||
u32 bufferLength = strlen(fileName) + 1;
|
||||
_name = std::make_unique_for_overwrite<TCHAR[]>(bufferLength);
|
||||
|
||||
@@ -10,7 +10,7 @@ class FileInfo
|
||||
public:
|
||||
FileInfo() { }
|
||||
FileInfo(const FileInfo& fileInfo);
|
||||
FileInfo(const TCHAR* fileName, const FileType* type, const FastFileRef& fastFileRef);
|
||||
FileInfo(const TCHAR* fileName, const FileType* type, const FastFileRef& fastFileRef, u8 attributes);
|
||||
|
||||
FileInfo &operator=(FileInfo&& rhs)
|
||||
{
|
||||
@@ -35,8 +35,13 @@ public:
|
||||
|
||||
const FastFileRef& GetFastFileRef() const { return _fastFileRef; }
|
||||
|
||||
bool IsReadOnly() const { return _attributes & AM_RDO; }
|
||||
bool IsHidden() const { return _attributes & AM_HID; }
|
||||
bool IsSystem() const { return _attributes & AM_SYS; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<TCHAR[]> _name;
|
||||
const FileType* _type;
|
||||
FastFileRef _fastFileRef;
|
||||
u8 _attributes;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class PaletteManager;
|
||||
class GraphicsContext;
|
||||
|
||||
#define FILE_ICON_VRAM_SIZE 4096
|
||||
#define FILE_ICON_VRAM_SIZE 1024
|
||||
|
||||
/// @brief Abstract base class representing a file icon.
|
||||
class FileIcon
|
||||
@@ -13,9 +13,17 @@ class FileIcon
|
||||
public:
|
||||
virtual ~FileIcon() = 0;
|
||||
|
||||
/// @brief Uploads the graphics of this icon to the specified \p vram address.
|
||||
/// @param vram The vram address to load the graphics to.
|
||||
virtual void UploadGraphics(vu16* vram) const = 0;
|
||||
/// @brief Sets the OBJ vram address and offset to use.
|
||||
/// @param objVramAddress The OBJ vram address to use.
|
||||
/// @param objVramOffset The OBJ vram offset to use.
|
||||
virtual void SetVramAddress(vu16* objVramAddress, u32 objVramOffset)
|
||||
{
|
||||
_vramAddress = objVramAddress;
|
||||
_vramOffset = objVramOffset;
|
||||
}
|
||||
|
||||
/// @brief Uploads the graphics of this icon to the vram address specified by SetVramAddress.
|
||||
virtual void UploadGraphics() = 0;
|
||||
|
||||
/// @brief Updates this icon.
|
||||
virtual void Update() { }
|
||||
@@ -25,10 +33,6 @@ public:
|
||||
/// @param backgroundColor The color on which the icon is drawn.
|
||||
virtual void Draw(GraphicsContext& graphicsContext, const Rgb<8, 8, 8>& backgroundColor) = 0;
|
||||
|
||||
/// @brief Sets the OBJ vram offset of this icon.
|
||||
/// @param offset The OBJ vram offset.
|
||||
void SetObjVramOffset(u32 offset) { _vramOffset = offset; }
|
||||
|
||||
/// @brief Sets the icon animation frame.
|
||||
/// @param frame The animation frame.
|
||||
void SetAnimFrame(u32 frame)
|
||||
@@ -50,6 +54,7 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
vu16* _vramAddress = nullptr;
|
||||
u32 _vramOffset = 0;
|
||||
u32 _frame = 0;
|
||||
Point _position;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "common.h"
|
||||
#include <libtwl/math/mathDiv.h>
|
||||
#include <libtwl/dma/dmaNitro.h>
|
||||
#include "gui/PaletteManager.h"
|
||||
#include "gui/OamManager.h"
|
||||
@@ -30,25 +29,36 @@ NdsFileIcon::NdsFileIcon(const nds_banner_t* banner)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
length += token.duration;
|
||||
}
|
||||
}
|
||||
_animLength = length;
|
||||
_tokenStartTimes[NDS_BANNER_ANIM_TOKEN_COUNT] = _animLength;
|
||||
}
|
||||
}
|
||||
|
||||
void NdsFileIcon::UploadGraphics(vu16* vram) const
|
||||
void NdsFileIcon::SetVramAddress(vu16* objVramAddress, u32 objVramOffset)
|
||||
{
|
||||
if (_animated)
|
||||
dma_ntrCopy32(3, _banner->animation.iconGfx, vram, sizeof(_banner->animation.iconGfx));
|
||||
else
|
||||
dma_ntrCopy32(3, _banner->iconGfx, vram, sizeof(_banner->iconGfx));
|
||||
FileIcon::SetVramAddress(objVramAddress, objVramOffset);
|
||||
_currentVramSlot = 0;
|
||||
_currentGfxIdx = -1;
|
||||
}
|
||||
|
||||
void NdsFileIcon::UploadGraphics()
|
||||
{
|
||||
if (_vramAddress != nullptr && !_animated)
|
||||
{
|
||||
dma_ntrCopy32(3, _banner->iconGfx, _vramAddress, sizeof(_banner->iconGfx));
|
||||
}
|
||||
}
|
||||
|
||||
void NdsFileIcon::Update()
|
||||
{
|
||||
if (!_animated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_frame %= _animLength;
|
||||
|
||||
@@ -71,48 +81,31 @@ void NdsFileIcon::Update()
|
||||
break;
|
||||
}
|
||||
else if (_frame < midTime)
|
||||
{
|
||||
end = mid - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
_animTokenIdx = start;
|
||||
}
|
||||
|
||||
if (++_frame == _animLength)
|
||||
{
|
||||
_frame = 0;
|
||||
|
||||
|
||||
// if (++_durationCounter < _banner->animation.animTokens[_animTokenIdx].duration)
|
||||
// return;
|
||||
|
||||
// _durationCounter = 0;
|
||||
|
||||
// if (++_animTokenIdx >= NDS_BANNER_ANIM_TOKEN_COUNT)
|
||||
// {
|
||||
// _animTokenIdx = 0;
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (_banner->animation.animTokens[_animTokenIdx].duration == NDS_BANNER_ANIM_DURATION_CONTROL_FRAME)
|
||||
// {
|
||||
// switch (_banner->animation.animTokens[_animTokenIdx].control)
|
||||
// {
|
||||
// case NDS_BANNER_ANIM_CONTROL_LOOP:
|
||||
// _animTokenIdx = 0;
|
||||
// break;
|
||||
|
||||
// case NDS_BANNER_ANIM_CONTROL_STOP:
|
||||
// _animTokenIdx--;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void NdsFileIcon::Draw(GraphicsContext& graphicsContext, const Rgb<8, 8, 8>& backgroundColor)
|
||||
{
|
||||
if (!graphicsContext.IsVisible(Rectangle(_position, 32, 32)))
|
||||
if (!graphicsContext.IsVisible(Rectangle(_position, 32, 32)) ||
|
||||
_vramAddress == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const u16* palette = _animated
|
||||
? _banner->animation.iconPltt[_banner->animation.animTokens[_animTokenIdx].plttIdx]
|
||||
@@ -123,7 +116,17 @@ void NdsFileIcon::Draw(GraphicsContext& graphicsContext, const Rgb<8, 8, 8>& bac
|
||||
|
||||
u32 vramOffset = _vramOffset;
|
||||
if (_animated)
|
||||
vramOffset += _banner->animation.animTokens[_animTokenIdx].gfxIdx * 512;
|
||||
{
|
||||
int gfxIdx = _banner->animation.animTokens[_animTokenIdx].gfxIdx;
|
||||
if (gfxIdx != _currentGfxIdx)
|
||||
{
|
||||
_currentVramSlot = 1 - _currentVramSlot;
|
||||
_currentGfxIdx = gfxIdx;
|
||||
dma_ntrCopy32(3, &_banner->animation.iconGfx[gfxIdx][0], (u8*)_vramAddress + (_currentVramSlot * NDS_BANNER_ICON_SIZE), NDS_BANNER_ICON_SIZE);
|
||||
}
|
||||
|
||||
vramOffset += _currentVramSlot * NDS_BANNER_ICON_SIZE;
|
||||
}
|
||||
|
||||
auto builder = OamBuilder::OamWithSize<32, 32>(
|
||||
_position, vramOffset >> 7)
|
||||
|
||||
@@ -8,7 +8,8 @@ class NdsFileIcon : public FileIcon
|
||||
public:
|
||||
explicit NdsFileIcon(const nds_banner_t* banner);
|
||||
|
||||
void UploadGraphics(vu16* vram) const override;
|
||||
void SetVramAddress(vu16* objVramAddress, u32 objVramOffset) override;
|
||||
void UploadGraphics() override;
|
||||
void Update() override;
|
||||
void Draw(GraphicsContext& graphicsContext, const Rgb<8, 8, 8>& backgroundColor) override;
|
||||
|
||||
@@ -16,9 +17,11 @@ private:
|
||||
const nds_banner_t* _banner;
|
||||
bool _animated;
|
||||
int _animTokenIdx;
|
||||
// int _durationCounter;
|
||||
u32 _lastAnimToken;
|
||||
u32 _animLength;
|
||||
u16 _tokenStartTimes[65];
|
||||
bool _loop;
|
||||
|
||||
int _currentVramSlot = 0;
|
||||
int _currentGfxIdx = -1;
|
||||
};
|
||||
@@ -2,52 +2,60 @@
|
||||
#include <string.h>
|
||||
#include <nds/arm9/cache.h>
|
||||
#include "fat/File.h"
|
||||
#include "romBrowser/ICoverRepository.h"
|
||||
#include "NdsInternalFileInfo.h"
|
||||
|
||||
NdsInternalFileInfo::NdsInternalFileInfo(const FastFileRef& fastFileRef)
|
||||
{
|
||||
const auto file = std::make_unique<File>();
|
||||
_hasBanner = false;
|
||||
memset(_gameCode, 0, sizeof(_gameCode));
|
||||
|
||||
file->Open(fastFileRef, FA_READ);
|
||||
|
||||
if (file->Seek(0xC) != FR_OK)
|
||||
return;
|
||||
|
||||
u32 bytesRead;
|
||||
if (file->Read(_gameCode, 4, bytesRead) != FR_OK)
|
||||
return;
|
||||
|
||||
if (file->Seek(0x68) != FR_OK)
|
||||
return;
|
||||
|
||||
u32 bannerOffset;
|
||||
if (file->Read(&bannerOffset, 4, bytesRead) != FR_OK)
|
||||
return;
|
||||
|
||||
if (bannerOffset == 0)
|
||||
return;
|
||||
|
||||
if (file->Seek(bannerOffset) != FR_OK)
|
||||
return;
|
||||
|
||||
if (file->Read(&_banner, 0xA00, bytesRead) != FR_OK)
|
||||
return;
|
||||
|
||||
if (_banner.header.version >= NDS_BANNER_VERSION_103)
|
||||
if (file->Seek(0xC) != FR_OK ||
|
||||
!file->ReadExact(_gameCode, 4) ||
|
||||
file->Seek(0x68) != FR_OK ||
|
||||
!file->ReadExact(&bannerOffset, 4) ||
|
||||
bannerOffset == 0 ||
|
||||
bannerOffset >= file->GetSize() ||
|
||||
file->Seek(bannerOffset) != FR_OK ||
|
||||
!file->ReadExact(&_banner, 0x840))
|
||||
{
|
||||
if (file->Read(((u8*)&_banner) + 0xA00, 0x19C0, bytesRead) != 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));
|
||||
}
|
||||
|
||||
const char16_t* NdsInternalFileInfo::GetGameTitle() const
|
||||
{
|
||||
if (!_hasBanner)
|
||||
return nullptr;
|
||||
return _banner.title[NDS_BANNER_TITLE_LANGUAGE_ENGLISH];
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -12,18 +12,11 @@ public:
|
||||
|
||||
constexpr const char* GetGameCode() const override { return _gameCode; }
|
||||
const char16_t* GetGameTitle() const override;
|
||||
|
||||
std::unique_ptr<FileIcon> CreateGameIcon() const override
|
||||
{
|
||||
if (!_hasBanner)
|
||||
return nullptr;
|
||||
return std::make_unique<NdsFileIcon>(&_banner);
|
||||
}
|
||||
|
||||
std::unique_ptr<FileIcon> CreateGameIcon() const override;
|
||||
const nds_banner_t& GetBanner() const { return _banner; }
|
||||
|
||||
private:
|
||||
nds_banner_t _banner alignas(32);
|
||||
bool _hasBanner;
|
||||
bool _hasBanner = false;
|
||||
char _gameCode[5];
|
||||
};
|
||||
@@ -46,10 +46,11 @@ typedef struct
|
||||
} nds_banner_anim_token_t;
|
||||
|
||||
#define NDS_BANNER_ANIM_TOKEN_COUNT 64
|
||||
#define NDS_BANNER_ICON_SIZE 512
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u8 iconGfx[8][0x200];
|
||||
u8 iconGfx[8][NDS_BANNER_ICON_SIZE];
|
||||
u16 iconPltt[8][16];
|
||||
nds_banner_anim_token_t animTokens[NDS_BANNER_ANIM_TOKEN_COUNT];
|
||||
} nds_banner_anim_t;
|
||||
@@ -57,7 +58,7 @@ typedef struct
|
||||
typedef struct
|
||||
{
|
||||
nds_banner_header_t header;
|
||||
u8 iconGfx[0x200];
|
||||
u8 iconGfx[NDS_BANNER_ICON_SIZE];
|
||||
u16 iconPltt[16];
|
||||
char16_t title[16][128];
|
||||
nds_banner_anim_t animation;
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
#include "gui/palette/DirectPalette.h"
|
||||
#include "StaticIcon.h"
|
||||
|
||||
void StaticIcon::UploadGraphics(vu16* vram) const
|
||||
void StaticIcon::UploadGraphics()
|
||||
{
|
||||
dma_ntrCopy32(3, _tileData, vram, 512);
|
||||
if (_vramAddress != nullptr)
|
||||
{
|
||||
dma_ntrCopy32(3, _tileData, _vramAddress, 512);
|
||||
}
|
||||
}
|
||||
|
||||
void StaticIcon::Update()
|
||||
|
||||
@@ -7,7 +7,7 @@ public:
|
||||
StaticIcon(const u8* tileData, const u16* paletteData)
|
||||
: _tileData(tileData), _paletteData(paletteData) { }
|
||||
|
||||
void UploadGraphics(vu16* vram) const override;
|
||||
void UploadGraphics() override;
|
||||
void Update() override;
|
||||
void Draw(GraphicsContext& graphicsContext, const Rgb<8, 8, 8>& backgroundColor) override;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ class RomBrowserViewModel;
|
||||
class FileInfo;
|
||||
class TaskQueueBase;
|
||||
class ICoverRepository;
|
||||
class ICheatRepository;
|
||||
|
||||
class IRomBrowserController
|
||||
{
|
||||
@@ -17,7 +18,7 @@ public:
|
||||
virtual void NavigateUp() = 0;
|
||||
virtual void NavigateToPath(const TCHAR* name) = 0;
|
||||
virtual void LaunchFile(const FileInfo& fileInfo) = 0;
|
||||
virtual void ShowGameInfo() = 0;
|
||||
virtual void ShowGameInfo(const FileInfo& fileInfo) = 0;
|
||||
virtual void HideGameInfo() = 0;
|
||||
virtual void ShowDisplaySettings() = 0;
|
||||
virtual void HideDisplaySettings() = 0;
|
||||
@@ -33,11 +34,14 @@ public:
|
||||
virtual TaskQueueBase* GetIoTaskQueue() const = 0;
|
||||
virtual TaskQueueBase* GetBgTaskQueue() const = 0;
|
||||
virtual const ICoverRepository& GetCoverRepository() const = 0;
|
||||
virtual const ICheatRepository& GetCheatRepository() const = 0;
|
||||
|
||||
virtual const RomBrowserDisplaySettings& GetRomBrowserDisplaySettings() const = 0;
|
||||
|
||||
virtual void SetRomBrowserDisplaySettings(
|
||||
const RomBrowserDisplaySettings& romBrowserDisplaySettings) = 0;
|
||||
|
||||
virtual const FileInfo& GetTriggerFileInfo() const = 0;
|
||||
};
|
||||
|
||||
inline IRomBrowserController::~IRomBrowserController() { }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "FileType/FileType.h"
|
||||
#include "SdFolderFactory.h"
|
||||
#include "services/settings/IAppSettingsService.h"
|
||||
#include "cheats/UsrCheatRepositoryFactory.h"
|
||||
#include "cheats/EmptyCheatRepository.h"
|
||||
#include "cheats/PicoLoaderCheatDataFactory.h"
|
||||
#include "RomBrowserController.h"
|
||||
|
||||
RomBrowserController::RomBrowserController(
|
||||
@@ -23,14 +26,14 @@ void RomBrowserController::NavigateToPath(const TCHAR* name)
|
||||
|
||||
void RomBrowserController::LaunchFile(const FileInfo& fileInfo)
|
||||
{
|
||||
_launchFileInfo = FileInfo(fileInfo);
|
||||
_triggerFileInfo = FileInfo(fileInfo);
|
||||
_stateMachine.Fire(RomBrowserStateTrigger::Launch);
|
||||
}
|
||||
|
||||
void RomBrowserController::ShowGameInfo()
|
||||
void RomBrowserController::ShowGameInfo(const FileInfo& fileInfo)
|
||||
{
|
||||
// Currently disabled, as the game info panel is not complete
|
||||
// _stateMachine.Fire(RomBrowserStateTrigger::ShowGameInfo);
|
||||
_triggerFileInfo = FileInfo(fileInfo);
|
||||
_stateMachine.Fire(RomBrowserStateTrigger::ShowGameInfo);
|
||||
}
|
||||
|
||||
void RomBrowserController::HideGameInfo()
|
||||
@@ -140,6 +143,15 @@ void RomBrowserController::HandleNavigateTrigger()
|
||||
_coverRepository = std::make_unique<CoverRepository>();
|
||||
_coverRepository->Initialize();
|
||||
}
|
||||
if (!_cheatRepository)
|
||||
{
|
||||
_cheatRepository = UsrCheatRepositoryFactory().FromUsrCheatDat("/_pico/usrcheat.dat");
|
||||
if (!_cheatRepository)
|
||||
{
|
||||
// When usrcheat.dat is not found or cannot be read use a dummy empty cheat repository
|
||||
_cheatRepository = std::make_unique<EmptyCheatRepository>();
|
||||
}
|
||||
}
|
||||
|
||||
u64 startTick = gTickCounter.GetValue();
|
||||
_navigateFileName = nullptr;
|
||||
@@ -178,26 +190,9 @@ void RomBrowserController::HandleLaunchTrigger()
|
||||
LOG_DEBUG("RomBrowserStateTrigger::Launch\n");
|
||||
_ioTaskQueue->Enqueue([this] (const vu8& cancelRequested)
|
||||
{
|
||||
f_getcwd(_navigatePath, sizeof(_navigatePath) / sizeof(_navigatePath[0]));
|
||||
int idx = strlcat(_navigatePath, "/", sizeof(_navigatePath));
|
||||
if (_navigatePath[idx - 2] == '/')
|
||||
_navigatePath[idx - 1] = 0;
|
||||
strlcat(_navigatePath, _launchFileInfo.GetFileName(), sizeof(_navigatePath));
|
||||
_appSettingsService->GetAppSettings().lastUsedFilePath = _navigatePath;
|
||||
_appSettingsService->Save();
|
||||
|
||||
auto loadParams = pload_getLoadParams();
|
||||
loadParams->savePath[0] = 0;
|
||||
loadParams->arguments[0] = 0;
|
||||
loadParams->argumentsLength = 0;
|
||||
if (_launchFileInfo.GetFileType()->TrySetLaunchParameters(loadParams, _navigatePath))
|
||||
{
|
||||
gProcessManager.Goto<PicoLoaderProcess>();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_FATAL("Failed to set launch parameters.\n");
|
||||
}
|
||||
UpdateLastUsedFilepath();
|
||||
SetPicoLoaderParams();
|
||||
LoadCheats();
|
||||
return TaskResult<void>::Completed();
|
||||
});
|
||||
}
|
||||
@@ -207,3 +202,39 @@ void RomBrowserController::HandleChangeDisplayModeTrigger()
|
||||
LOG_DEBUG("RomBrowserStateTrigger::ChangeDisplayMode\n");
|
||||
_romBrowserViewModel = SharedPtr(new RomBrowserViewModel(this));
|
||||
}
|
||||
|
||||
void RomBrowserController::UpdateLastUsedFilepath()
|
||||
{
|
||||
f_getcwd(_navigatePath, sizeof(_navigatePath) / sizeof(_navigatePath[0]));
|
||||
int idx = strlcat(_navigatePath, "/", sizeof(_navigatePath));
|
||||
if (_navigatePath[idx - 2] == '/')
|
||||
{
|
||||
_navigatePath[idx - 1] = 0;
|
||||
}
|
||||
strlcat(_navigatePath, _triggerFileInfo.GetFileName(), sizeof(_navigatePath));
|
||||
_appSettingsService->GetAppSettings().lastUsedFilePath = _navigatePath;
|
||||
_appSettingsService->Save();
|
||||
}
|
||||
|
||||
void RomBrowserController::SetPicoLoaderParams() const
|
||||
{
|
||||
auto loadParams = pload_getLoadParams();
|
||||
loadParams->savePath[0] = 0;
|
||||
loadParams->arguments[0] = 0;
|
||||
loadParams->argumentsLength = 0;
|
||||
if (_triggerFileInfo.GetFileType()->TrySetLaunchParameters(loadParams, _navigatePath))
|
||||
{
|
||||
gProcessManager.Goto<PicoLoaderProcess>();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_FATAL("Failed to set launch parameters.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void RomBrowserController::LoadCheats() const
|
||||
{
|
||||
auto cheats = _cheatRepository->GetCheatsForGame(_triggerFileInfo.GetFastFileRef());
|
||||
auto cheatData = PicoLoaderCheatDataFactory().CreateCheatData(cheats);
|
||||
pload_setCheatData(cheatData);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "CoverRepository.h"
|
||||
#include "FileType/ExtensionFileTypeProvider.h"
|
||||
#include "services/settings/IAppSettingsService.h"
|
||||
#include "cheats/ICheatRepository.h"
|
||||
|
||||
class RomBrowserController : public IRomBrowserController
|
||||
{
|
||||
@@ -23,7 +24,7 @@ public:
|
||||
|
||||
void NavigateToPath(const TCHAR* name) override;
|
||||
void LaunchFile(const FileInfo& fileInfo) override;
|
||||
void ShowGameInfo() override;
|
||||
void ShowGameInfo(const FileInfo& fileInfo) override;
|
||||
void HideGameInfo() override;
|
||||
void ShowDisplaySettings() override;
|
||||
void HideDisplaySettings() override;
|
||||
@@ -39,6 +40,7 @@ public:
|
||||
TaskQueueBase* GetIoTaskQueue() const override { return _ioTaskQueue; }
|
||||
TaskQueueBase* GetBgTaskQueue() const override { return _bgTaskQueue; }
|
||||
const ICoverRepository& GetCoverRepository() const override { return *_coverRepository; }
|
||||
const ICheatRepository& GetCheatRepository() const override { return *_cheatRepository; }
|
||||
|
||||
void SetRomBrowserDisplaySettings(const RomBrowserDisplaySettings& romBrowserDisplaySettings) override;
|
||||
|
||||
@@ -47,6 +49,8 @@ public:
|
||||
return _appSettingsService->GetAppSettings().romBrowserDisplaySettings;
|
||||
}
|
||||
|
||||
virtual const FileInfo& GetTriggerFileInfo() const override { return _triggerFileInfo; }
|
||||
|
||||
private:
|
||||
IAppSettingsService* _appSettingsService;
|
||||
TaskQueueBase* _ioTaskQueue;
|
||||
@@ -58,15 +62,19 @@ private:
|
||||
RomBrowserStateMachine _stateMachine;
|
||||
TCHAR _navigatePath[256];
|
||||
TCHAR* _navigateFileName;
|
||||
FileInfo _launchFileInfo;
|
||||
FileInfo _triggerFileInfo;
|
||||
QueueTask<void> _navigateTask;
|
||||
bool _saveSettingsPending = false;
|
||||
std::unique_ptr<CoverRepository> _coverRepository;
|
||||
ExtensionFileTypeProvider _fileTypeProvider;
|
||||
std::unique_ptr<ICheatRepository> _cheatRepository;
|
||||
|
||||
void HandleTrigger();
|
||||
void HandleNavigateTrigger();
|
||||
void HandleFolderLoadDoneTrigger();
|
||||
void HandleLaunchTrigger();
|
||||
void HandleChangeDisplayModeTrigger();
|
||||
void UpdateLastUsedFilepath();
|
||||
void SetPicoLoaderParams() const;
|
||||
void LoadCheats() const;
|
||||
};
|
||||
|
||||
@@ -21,8 +21,10 @@ std::unique_ptr<const FileInfo*[]> SdFolder::FilterAndSort(
|
||||
for (int i = 0; i < _fileCount; i++)
|
||||
{
|
||||
const FileInfo* file = _files[i];
|
||||
bool isHidden = file->GetFileName()[0] == '.' || file->IsHidden();
|
||||
auto classification = file->GetFileType()->GetClassification();
|
||||
if (classification != FileTypeClassification::Unknown)
|
||||
if (classification != FileTypeClassification::Unknown &&
|
||||
(!isHidden || filterSortParams.includeHiddenFiles))
|
||||
{
|
||||
sortedFilteredFiles[filteredCount++] = file;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ std::unique_ptr<SdFolder> SdFolderFactory::CreateFromPath(const char* path) cons
|
||||
? &FolderFileType::sInstance
|
||||
: _fileTypeProvider->GetFileType(sdFileInfo->fname);
|
||||
fileInfos[count++] = new FileInfo(sdFileInfo->fname, fileType,
|
||||
FastFileRef(directory.GetFatFsDirectory(), sdFileInfo.get()));
|
||||
FastFileRef(directory.GetFatFsDirectory(), sdFileInfo.get()), sdFileInfo->fattrib);
|
||||
}
|
||||
|
||||
return std::make_unique<SdFolder>(fileInfos, count);
|
||||
|
||||
@@ -7,9 +7,10 @@ class SdFolderFilterSortParams
|
||||
public:
|
||||
SdFolderSortType sortType = SdFolderSortType::Name;
|
||||
SdFolderSortDirection sortDirection = SdFolderSortDirection::Ascending;
|
||||
bool includeHiddenFiles = false;
|
||||
|
||||
SdFolderFilterSortParams() { }
|
||||
|
||||
SdFolderFilterSortParams(SdFolderSortType sortType, SdFolderSortDirection sortDirection)
|
||||
: sortType(sortType), sortDirection(sortDirection) { }
|
||||
SdFolderFilterSortParams(SdFolderSortType sortType, SdFolderSortDirection sortDirection, bool includeHiddenFiles)
|
||||
: sortType(sortType), sortDirection(sortDirection), includeHiddenFiles(includeHiddenFiles) { }
|
||||
};
|
||||
|
||||
@@ -119,7 +119,6 @@ void MaterialBannerListItemView::Draw(GraphicsContext& graphicsContext)
|
||||
|
||||
if (_icon)
|
||||
{
|
||||
_icon->SetObjVramOffset(_iconVramOffset);
|
||||
_icon->SetPosition(6 + _position.x, 6 + _position.y);
|
||||
_icon->Draw(graphicsContext, frontColor);
|
||||
}
|
||||
|
||||
@@ -24,9 +24,11 @@ MaterialFileIcon::MaterialFileIcon(const TCHAR* name, const MaterialColorScheme*
|
||||
_displayName[i] = 0;
|
||||
}
|
||||
|
||||
void MaterialFileIcon::UploadGraphics(vu16* vram) const
|
||||
void MaterialFileIcon::UploadGraphics()
|
||||
{
|
||||
dma_ntrCopy32(3, GetIconTiles(), vram, 32 * 32 / 2);
|
||||
if (_vramAddress != nullptr)
|
||||
{
|
||||
dma_ntrCopy32(3, GetIconTiles(), _vramAddress, 32 * 32 / 2);
|
||||
|
||||
auto font = _fontRepository->GetFont(FontType::Medium11);
|
||||
u8 tileBuffer[32 * 16 / 2];
|
||||
@@ -40,7 +42,8 @@ void MaterialFileIcon::UploadGraphics(vu16* vram) const
|
||||
renderParams.height = 16;
|
||||
renderParams.a5i3 = false;
|
||||
nft2_renderString(font, _displayName, tileBuffer, 32, &renderParams);
|
||||
memcpy((u8*)vram + largeFolderIconTilesLen, tileBuffer, sizeof(tileBuffer));
|
||||
memcpy((u8*)_vramAddress + largeFolderIconTilesLen, tileBuffer, sizeof(tileBuffer));
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialFileIcon::Draw(GraphicsContext& graphicsContext, const Rgb<8, 8, 8>& backgroundColor)
|
||||
|
||||
@@ -11,7 +11,7 @@ public:
|
||||
MaterialFileIcon(const TCHAR* name, const MaterialColorScheme* materialColorScheme,
|
||||
const IFontRepository* fontRepository);
|
||||
|
||||
void UploadGraphics(vu16* vram) const override;
|
||||
void UploadGraphics() override;
|
||||
void Draw(GraphicsContext& graphicsContext, const Rgb<8, 8, 8>& backgroundColor) override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -22,7 +22,7 @@ MaterialFileInfoCardView::MaterialFileInfoCardView(const MaterialColorScheme* ma
|
||||
AddChildTail(&_firstLine);
|
||||
AddChildTail(&_secondLine);
|
||||
AddChildTail(&_thirdLine);
|
||||
_filenameLabelView.SetEllipsis(true);
|
||||
_filenameLabelView.SetEllipsisStyle(LabelView::EllipsisStyle::Marquee);
|
||||
AddChildTail(&_filenameLabelView);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ void MaterialFileInfoCardView::Draw(GraphicsContext& graphicsContext)
|
||||
|
||||
if (_icon)
|
||||
{
|
||||
_icon->SetObjVramOffset(_iconVramOffset);
|
||||
_icon->Draw(graphicsContext, fgColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
|
||||
void SetFirstLineAsync(TaskQueueBase* taskQueue, const char* firstLine, bool ellipsis) override
|
||||
{
|
||||
_firstLine.SetEllipsis(ellipsis);
|
||||
_firstLine.SetEllipsisStyle(ellipsis ? LabelView::EllipsisStyle::Ellipsis : LabelView::EllipsisStyle::None);
|
||||
if (taskQueue)
|
||||
_firstLine.SetTextAsync(taskQueue, firstLine);
|
||||
else
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
|
||||
void SetFirstLineAsync(TaskQueueBase* taskQueue, const char16_t* firstLine, u32 length, bool ellipsis) override
|
||||
{
|
||||
_firstLine.SetEllipsis(ellipsis);
|
||||
_firstLine.SetEllipsisStyle(ellipsis ? LabelView::EllipsisStyle::Ellipsis : LabelView::EllipsisStyle::None);
|
||||
if (taskQueue)
|
||||
_firstLine.SetTextAsync(taskQueue, firstLine, length);
|
||||
else
|
||||
|
||||
@@ -47,7 +47,6 @@ void MaterialIconGridItemView::Draw(GraphicsContext& graphicsContext)
|
||||
|
||||
if (_icon)
|
||||
{
|
||||
_icon->SetObjVramOffset(_iconVramOffset);
|
||||
_icon->SetPosition(6 + _position.x, 6 + _position.y);
|
||||
_icon->Draw(graphicsContext, frontColor);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "core/math/RgbMixer.h"
|
||||
#include "themes/material/MaterialColorScheme.h"
|
||||
#include "themes/IFontRepository.h"
|
||||
#include "themes/custom/CustomThemeInfo.h"
|
||||
#include "bannerListItemBg0.h"
|
||||
#include "bannerListItemBg1.h"
|
||||
#include "bannerListItemBg2.h"
|
||||
@@ -30,9 +31,10 @@
|
||||
#define LINE_HEIGHT 16
|
||||
#define MAX_LINE_STRING_LENGTH 50
|
||||
|
||||
CustomBannerListItemView::CustomBannerListItemView(const MaterialColorScheme* materialColorScheme,
|
||||
const IFontRepository* fontRepository, u32 texVramOffset, u32 plttVramOffset,
|
||||
u32 selectedTexVramOffset, u32 selectedPlttVramOffset, VBlankTextureLoader* vblankTextureLoader)
|
||||
CustomBannerListItemView::CustomBannerListItemView(const CustomThemeInfo* customThemeInfo,
|
||||
const MaterialColorScheme* materialColorScheme, const IFontRepository* fontRepository,
|
||||
u32 texVramOffset, u32 plttVramOffset, u32 selectedTexVramOffset, u32 selectedPlttVramOffset,
|
||||
VBlankTextureLoader* vblankTextureLoader)
|
||||
: BannerListItemView(
|
||||
std::make_unique<Label3DView>(LINE_WIDTH, LINE_HEIGHT, MAX_LINE_STRING_LENGTH,
|
||||
fontRepository->GetFont(FontType::Medium10), vblankTextureLoader),
|
||||
@@ -40,6 +42,7 @@ CustomBannerListItemView::CustomBannerListItemView(const MaterialColorScheme* ma
|
||||
fontRepository->GetFont(FontType::Regular10), vblankTextureLoader),
|
||||
std::make_unique<Label3DView>(LINE_WIDTH, LINE_HEIGHT, MAX_LINE_STRING_LENGTH,
|
||||
fontRepository->GetFont(FontType::Regular10), vblankTextureLoader))
|
||||
, _customThemeInfo(customThemeInfo)
|
||||
, _materialColorScheme(materialColorScheme)
|
||||
, _texVramOffset(texVramOffset)
|
||||
, _plttVramOffset(plttVramOffset)
|
||||
@@ -53,8 +56,6 @@ void CustomBannerListItemView::Draw(GraphicsContext& graphicsContext)
|
||||
return;
|
||||
}
|
||||
|
||||
auto backgroundColor = Rgb<8, 8, 8>(200, 200, 200);
|
||||
|
||||
Gx::MtxIdentity();
|
||||
Gx::PolygonAttr(GX_LIGHTMASK_NONE, GX_POLYGON_MODE_MODULATE, GX_DISPLAY_MODE_FRONT,
|
||||
false, false, false, GX_DEPTH_FUNC_LESS, false, 31, 0);
|
||||
@@ -82,9 +83,9 @@ void CustomBannerListItemView::Draw(GraphicsContext& graphicsContext)
|
||||
|
||||
graphicsContext.SetPolygonId(1);
|
||||
|
||||
_firstLine->SetForegroundColor(Rgb<8, 8, 8>(30, 30, 30));
|
||||
_secondLine->SetForegroundColor(Rgb<8, 8, 8>(30, 30, 30));
|
||||
_thirdLine->SetForegroundColor(Rgb<8, 8, 8>(30, 30, 30));
|
||||
_firstLine->SetForegroundColor(_customThemeInfo->bannerListTextLine0Info.GetTextColor());
|
||||
_secondLine->SetForegroundColor(_customThemeInfo->bannerListTextLine1Info.GetTextColor());
|
||||
_thirdLine->SetForegroundColor(_customThemeInfo->bannerListTextLine2Info.GetTextColor());
|
||||
|
||||
if (_lines == 1)
|
||||
{
|
||||
@@ -117,8 +118,7 @@ void CustomBannerListItemView::Draw(GraphicsContext& graphicsContext)
|
||||
|
||||
if (_icon)
|
||||
{
|
||||
_icon->SetObjVramOffset(_iconVramOffset);
|
||||
_icon->SetPosition(6 + _position.x, 6 + _position.y);
|
||||
_icon->Draw(graphicsContext, backgroundColor);
|
||||
_icon->Draw(graphicsContext, _customThemeInfo->bannerListIconInfo.GetBlendColor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
class MaterialColorScheme;
|
||||
class IFontRepository;
|
||||
class CustomThemeInfo;
|
||||
|
||||
class CustomBannerListItemView : public BannerListItemView
|
||||
{
|
||||
public:
|
||||
CustomBannerListItemView(const MaterialColorScheme* materialColorScheme,
|
||||
CustomBannerListItemView(const CustomThemeInfo* customThemeInfo, const MaterialColorScheme* materialColorScheme,
|
||||
const IFontRepository* fontRepository, u32 texVramOffset, u32 plttVramOffset,
|
||||
u32 selectedTexVramOffset, u32 selectedPlttVramOffset, VBlankTextureLoader* vblankTextureLoader);
|
||||
|
||||
@@ -20,6 +21,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
const CustomThemeInfo* _customThemeInfo;
|
||||
const MaterialColorScheme* _materialColorScheme;
|
||||
u32 _texVramOffset = 0;
|
||||
u32 _plttVramOffset = 0;
|
||||
|
||||
@@ -2,52 +2,52 @@
|
||||
#include "gui/GraphicsContext.h"
|
||||
#include "gui/IVramManager.h"
|
||||
#include "themes/IFontRepository.h"
|
||||
#include "themes/custom/CustomThemeInfo.h"
|
||||
#include "CustomFileInfoView.h"
|
||||
|
||||
CustomFileInfoView::CustomFileInfoView(const IFontRepository* fontRepository)
|
||||
: _firstLine(176, 16, 50, fontRepository->GetFont(FontType::Medium11))
|
||||
, _secondLine(176, 16, 50, fontRepository->GetFont(FontType::Regular10))
|
||||
, _thirdLine(176, 16, 50, fontRepository->GetFont(FontType::Regular10))
|
||||
, _filenameLabelView(220, 16, 200, fontRepository->GetFont(FontType::Medium7_5))
|
||||
, _backgroundColor(200, 200, 200), _textColor(30, 30, 30)
|
||||
CustomFileInfoView::CustomFileInfoView(const CustomThemeInfo* customThemeInfo, const IFontRepository* fontRepository)
|
||||
: _firstLine(customThemeInfo->topBannerTextLine0Info.GetWidth(), 16, 50, fontRepository->GetFont(FontType::Medium11))
|
||||
, _secondLine(customThemeInfo->topBannerTextLine1Info.GetWidth(), 16, 50, fontRepository->GetFont(FontType::Regular10))
|
||||
, _thirdLine(customThemeInfo->topBannerTextLine2Info.GetWidth(), 16, 50, fontRepository->GetFont(FontType::Regular10))
|
||||
, _filenameLabelView(customThemeInfo->topFileNameTextInfo.GetWidth(), 16, 256, fontRepository->GetFont(FontType::Medium7_5))
|
||||
, _customThemeInfo(customThemeInfo)
|
||||
{
|
||||
AddChildTail(&_firstLine);
|
||||
AddChildTail(&_secondLine);
|
||||
AddChildTail(&_thirdLine);
|
||||
_filenameLabelView.SetEllipsis(true);
|
||||
_filenameLabelView.SetEllipsisStyle(LabelView::EllipsisStyle::Marquee);
|
||||
AddChildTail(&_filenameLabelView);
|
||||
}
|
||||
|
||||
void CustomFileInfoView::Update()
|
||||
{
|
||||
BannerView::Update();
|
||||
_firstLine.SetPosition(_position.x + 70, _position.y + 130 - 4);
|
||||
_secondLine.SetPosition(_position.x + 70, _position.y + 145 - 4);
|
||||
_thirdLine.SetPosition(_position.x + 70, _position.y + 159 - 4);
|
||||
_filenameLabelView.SetPosition(_position.x + 18, _position.y + 170);
|
||||
_firstLine.SetPosition(_customThemeInfo->topBannerTextLine0Info.GetPosition());
|
||||
_secondLine.SetPosition(_customThemeInfo->topBannerTextLine1Info.GetPosition());
|
||||
_thirdLine.SetPosition(_customThemeInfo->topBannerTextLine2Info.GetPosition());
|
||||
_filenameLabelView.SetPosition(_customThemeInfo->topFileNameTextInfo.GetPosition());
|
||||
if (_icon)
|
||||
{
|
||||
_icon->SetPosition(_position.x + 24, _position.y + 136 - 4);
|
||||
_icon->SetPosition(_customThemeInfo->topIconInfo.GetPosition());
|
||||
_icon->Update();
|
||||
}
|
||||
}
|
||||
|
||||
void CustomFileInfoView::Draw(GraphicsContext& graphicsContext)
|
||||
{
|
||||
_firstLine.SetBackgroundColor(_backgroundColor);
|
||||
_firstLine.SetForegroundColor(_textColor);
|
||||
_secondLine.SetBackgroundColor(_backgroundColor);
|
||||
_secondLine.SetForegroundColor(_textColor);
|
||||
_thirdLine.SetBackgroundColor(_backgroundColor);
|
||||
_thirdLine.SetForegroundColor(_textColor);
|
||||
_filenameLabelView.SetBackgroundColor(_backgroundColor);
|
||||
_filenameLabelView.SetForegroundColor(_textColor);
|
||||
_firstLine.SetBackgroundColor(_customThemeInfo->topBannerTextLine0Info.GetBlendColor());
|
||||
_firstLine.SetForegroundColor(_customThemeInfo->topBannerTextLine0Info.GetTextColor());
|
||||
_secondLine.SetBackgroundColor(_customThemeInfo->topBannerTextLine1Info.GetBlendColor());
|
||||
_secondLine.SetForegroundColor(_customThemeInfo->topBannerTextLine1Info.GetTextColor());
|
||||
_thirdLine.SetBackgroundColor(_customThemeInfo->topBannerTextLine2Info.GetBlendColor());
|
||||
_thirdLine.SetForegroundColor(_customThemeInfo->topBannerTextLine2Info.GetTextColor());
|
||||
_filenameLabelView.SetBackgroundColor(_customThemeInfo->topFileNameTextInfo.GetBlendColor());
|
||||
_filenameLabelView.SetForegroundColor(_customThemeInfo->topFileNameTextInfo.GetTextColor());
|
||||
|
||||
BannerView::Draw(graphicsContext);
|
||||
|
||||
if (_icon)
|
||||
{
|
||||
_icon->SetObjVramOffset(_iconVramOffset);
|
||||
_icon->Draw(graphicsContext, _backgroundColor);
|
||||
_icon->Draw(graphicsContext, _customThemeInfo->topIconInfo.GetBlendColor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,19 @@
|
||||
|
||||
class FileIcon;
|
||||
class IFontRepository;
|
||||
class CustomThemeInfo;
|
||||
|
||||
class CustomFileInfoView : public BannerView
|
||||
{
|
||||
public:
|
||||
explicit CustomFileInfoView(const IFontRepository* fontRepository);
|
||||
CustomFileInfoView(const CustomThemeInfo* customThemeInfo, const IFontRepository* fontRepository);
|
||||
|
||||
void Update() override;
|
||||
void Draw(GraphicsContext& graphicsContext) override;
|
||||
|
||||
void SetFirstLineAsync(TaskQueueBase* taskQueue, const char* firstLine, bool ellipsis) override
|
||||
{
|
||||
_firstLine.SetEllipsis(ellipsis);
|
||||
_firstLine.SetEllipsisStyle(ellipsis ? LabelView::EllipsisStyle::Ellipsis : LabelView::EllipsisStyle::None);
|
||||
if (taskQueue)
|
||||
_firstLine.SetTextAsync(taskQueue, firstLine);
|
||||
else
|
||||
@@ -25,7 +26,7 @@ public:
|
||||
|
||||
void SetFirstLineAsync(TaskQueueBase* taskQueue, const char16_t* firstLine, u32 length, bool ellipsis) override
|
||||
{
|
||||
_firstLine.SetEllipsis(ellipsis);
|
||||
_firstLine.SetEllipsisStyle(ellipsis ? LabelView::EllipsisStyle::Ellipsis : LabelView::EllipsisStyle::None);
|
||||
if (taskQueue)
|
||||
_firstLine.SetTextAsync(taskQueue, firstLine, length);
|
||||
else
|
||||
@@ -67,6 +68,5 @@ private:
|
||||
Label2DView _secondLine;
|
||||
Label2DView _thirdLine;
|
||||
Label2DView _filenameLabelView;
|
||||
Rgb<8, 8, 8> _backgroundColor;
|
||||
Rgb<8, 8, 8> _textColor;
|
||||
const CustomThemeInfo* _customThemeInfo;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "gui/OamBuilder.h"
|
||||
#include "gui/IVramManager.h"
|
||||
#include "gui/GraphicsContext.h"
|
||||
#include "themes/custom/CustomThemeInfo.h"
|
||||
#include "CustomIconGridItemView.h"
|
||||
|
||||
#define X_OFFSET (-2)
|
||||
@@ -47,8 +48,7 @@ void CustomIconGridItemView::Draw(GraphicsContext& graphicsContext)
|
||||
|
||||
if (_icon)
|
||||
{
|
||||
_icon->SetObjVramOffset(_iconVramOffset);
|
||||
_icon->SetPosition(6 + _position.x, 6 + _position.y);
|
||||
_icon->Draw(graphicsContext, Rgb<8, 8, 8>(200, 200, 200));
|
||||
_icon->Draw(graphicsContext, _customThemeInfo->gridIconInfo.GetBlendColor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#pragma once
|
||||
#include "../../views/IconGridItemView.h"
|
||||
|
||||
class CustomThemeInfo;
|
||||
|
||||
class CustomIconGridItemView : public IconGridItemView
|
||||
{
|
||||
public:
|
||||
CustomIconGridItemView(u32 texVramOffset, u32 plttVramOffset,
|
||||
CustomIconGridItemView(const CustomThemeInfo* customThemeInfo, u32 texVramOffset, u32 plttVramOffset,
|
||||
u32 selectedTexVramOffset, u32 selectedPlttVramOffset)
|
||||
: _texVramOffset(texVramOffset), _plttVramOffset(plttVramOffset)
|
||||
: _customThemeInfo(customThemeInfo), _texVramOffset(texVramOffset), _plttVramOffset(plttVramOffset)
|
||||
, _selectedTexVramOffset(selectedTexVramOffset), _selectedPlttVramOffset(selectedPlttVramOffset) { }
|
||||
|
||||
void Draw(GraphicsContext& graphicsContext) override;
|
||||
@@ -17,8 +19,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
u32 _texVramOffset = 0;
|
||||
u32 _plttVramOffset = 0;
|
||||
u32 _selectedTexVramOffset = 0;
|
||||
u32 _selectedPlttVramOffset = 0;
|
||||
const CustomThemeInfo* _customThemeInfo;
|
||||
u32 _texVramOffset;
|
||||
u32 _plttVramOffset;
|
||||
u32 _selectedTexVramOffset;
|
||||
u32 _selectedPlttVramOffset;
|
||||
};
|
||||
@@ -12,23 +12,24 @@ class MaterialColorScheme;
|
||||
class ITheme;
|
||||
class VramContext;
|
||||
class IFontRepository;
|
||||
class CustomThemeInfo;
|
||||
|
||||
class CustomRomBrowserViewFactory : public IRomBrowserViewFactory
|
||||
{
|
||||
public:
|
||||
CustomRomBrowserViewFactory(const MaterialColorScheme* materialColorScheme,
|
||||
CustomRomBrowserViewFactory(const CustomThemeInfo* customThemeInfo, const MaterialColorScheme* materialColorScheme,
|
||||
const IFontRepository* fontRepository)
|
||||
: _materialColorScheme(materialColorScheme), _fontRepository(fontRepository) { }
|
||||
: _customThemeInfo(customThemeInfo), _materialColorScheme(materialColorScheme), _fontRepository(fontRepository) { }
|
||||
|
||||
IconGridItemView* CreateIconGridItemView() const override
|
||||
{
|
||||
return new CustomIconGridItemView(_gridCellTexVramOffset, _gridCellPlttVramOffset,
|
||||
return new CustomIconGridItemView(_customThemeInfo, _gridCellTexVramOffset, _gridCellPlttVramOffset,
|
||||
_gridCellSelectedTexVramOffset, _gridCellSelectedPlttVramOffset);
|
||||
}
|
||||
|
||||
BannerListItemView* CreateBannerListItemView(VBlankTextureLoader* vblankTextureLoader) const override
|
||||
{
|
||||
return new CustomBannerListItemView(_materialColorScheme, _fontRepository,
|
||||
return new CustomBannerListItemView(_customThemeInfo, _materialColorScheme, _fontRepository,
|
||||
_bannerListCellTexVramOffset, _bannerListCellPlttVramOffset,
|
||||
_bannerListCellSelectedTexVramOffset, _bannerListCellSelectedPlttVramOffset, vblankTextureLoader);
|
||||
}
|
||||
@@ -47,7 +48,7 @@ public:
|
||||
|
||||
std::unique_ptr<BannerView> CreateFileInfoView() const override
|
||||
{
|
||||
return std::make_unique<CustomFileInfoView>(_fontRepository);
|
||||
return std::make_unique<CustomFileInfoView>(_customThemeInfo, _fontRepository);
|
||||
}
|
||||
|
||||
std::unique_ptr<RecyclerViewBase> CreateCoverFlowRecyclerView() const override
|
||||
@@ -77,6 +78,7 @@ private:
|
||||
u32 _bannerListCellSelectedPlttVramOffset;
|
||||
u32 _scrimTexVramOffset;
|
||||
u32 _scrimPlttVramOffset;
|
||||
const CustomThemeInfo* _customThemeInfo;
|
||||
const MaterialColorScheme* _materialColorScheme;
|
||||
const IFontRepository* _fontRepository;
|
||||
};
|
||||
|
||||
135
arm9/source/romBrowser/viewModels/CheatsViewModel.cpp
Normal file
135
arm9/source/romBrowser/viewModels/CheatsViewModel.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "common.h"
|
||||
#include "cheats/ICheatRepository.h"
|
||||
#include "fat/File.h"
|
||||
#include "CheatsViewModel.h"
|
||||
|
||||
CheatsViewModel::CheatsViewModel(const FileInfo& romFileInfo, IRomBrowserController* romBrowserController)
|
||||
: _romFileInfo(romFileInfo), _romBrowserController(romBrowserController)
|
||||
{
|
||||
_categoryStack.fill({ nullptr, 0 });
|
||||
_loadCheatsTask = _romBrowserController->GetIoTaskQueue()->Enqueue([this] (const vu8& cancelRequested)
|
||||
{
|
||||
_cheats = _romBrowserController->GetCheatRepository().GetCheatsForGame(_romFileInfo.GetFastFileRef());
|
||||
if (_cheats)
|
||||
{
|
||||
_categoryStack[0] = { _cheats.get(), 0 };
|
||||
_state = State::DisplayCheats;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = State::NoCheats;
|
||||
}
|
||||
|
||||
return TaskResult<void>::Completed();
|
||||
});
|
||||
}
|
||||
|
||||
void CheatsViewModel::ActivateSelectedItem()
|
||||
{
|
||||
if (_state != State::DisplayCheats)
|
||||
{
|
||||
// No cheats or not yet loaded
|
||||
return;
|
||||
}
|
||||
|
||||
auto cheatCategory = GetCurrentCheatCategory();
|
||||
u32 numberOfSubEntries = 0;
|
||||
auto subEntries = cheatCategory->GetSubEntries(numberOfSubEntries);
|
||||
|
||||
if (numberOfSubEntries == 0)
|
||||
{
|
||||
// There is nothing to activate
|
||||
return;
|
||||
}
|
||||
|
||||
auto& entry = subEntries[_selectedItem];
|
||||
if (entry.IsCheatCategory())
|
||||
{
|
||||
// Category activated
|
||||
if (_categoryStackLevel + 1 != _categoryStack.size())
|
||||
{
|
||||
_categoryStack[++_categoryStackLevel] = { &entry, (u32)_selectedItem };
|
||||
_selectedItem = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Toggle cheat on/off
|
||||
bool isEnabled = !entry.GetIsCheatActive();
|
||||
if (isEnabled && cheatCategory->GetIsMaxOneCheatActive())
|
||||
{
|
||||
for (u32 i = 0; i < numberOfSubEntries; i++)
|
||||
{
|
||||
if (!subEntries[i].IsCheatCategory())
|
||||
{
|
||||
subEntries[i].SetIsCheatActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
entry.SetIsCheatActive(isEnabled);
|
||||
_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CheatsViewModel::NavigateUp()
|
||||
{
|
||||
if (_categoryStackLevel == 0)
|
||||
{
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedItem = _categoryStack[_categoryStackLevel].index;
|
||||
_categoryStack[_categoryStackLevel--] = { nullptr, 0 };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void CheatsViewModel::Close()
|
||||
{
|
||||
_categoryStack.fill({ nullptr, 0 });
|
||||
|
||||
if (_changed)
|
||||
{
|
||||
// Save which cheats are enabled/disabled
|
||||
_romBrowserController->GetIoTaskQueue()->Enqueue(
|
||||
[romBrowserController = _romBrowserController, cheats = move(_cheats)] (const vu8& cancelRequested)
|
||||
{
|
||||
romBrowserController->GetCheatRepository().UpdateEnabledCheatsForGame(cheats);
|
||||
return TaskResult<void>::Completed();
|
||||
});
|
||||
}
|
||||
|
||||
_romBrowserController->HideGameInfo();
|
||||
}
|
||||
|
||||
void CheatsViewModel::DisableAllCheats()
|
||||
{
|
||||
if (_state == State::DisplayCheats)
|
||||
{
|
||||
DisableAllCheats(_cheats.get());
|
||||
}
|
||||
}
|
||||
|
||||
void CheatsViewModel::DisableAllCheats(const CheatEntry* cheatCategory)
|
||||
{
|
||||
u32 numberOfSubEntries = 0;
|
||||
auto subEntries = cheatCategory->GetSubEntries(numberOfSubEntries);
|
||||
for (u32 i = 0; i < numberOfSubEntries; i++)
|
||||
{
|
||||
auto& entry = subEntries[i];
|
||||
if (entry.IsCheatCategory())
|
||||
{
|
||||
DisableAllCheats(&entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entry.GetIsCheatActive())
|
||||
{
|
||||
entry.SetIsCheatActive(false);
|
||||
_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
82
arm9/source/romBrowser/viewModels/CheatsViewModel.h
Normal file
82
arm9/source/romBrowser/viewModels/CheatsViewModel.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include "core/task/TaskQueue.h"
|
||||
#include "cheats/GameCheats.h"
|
||||
#include "romBrowser/FileInfo.h"
|
||||
#include "romBrowser/IRomBrowserController.h"
|
||||
|
||||
/// @brief View model for the cheats screen.
|
||||
class CheatsViewModel
|
||||
{
|
||||
public:
|
||||
/// @brief Enum representing the state of the cheats panel.
|
||||
enum class State
|
||||
{
|
||||
/// @brief Cheats are being loaded.
|
||||
Loading,
|
||||
|
||||
/// @brief No cheats were found.
|
||||
NoCheats,
|
||||
|
||||
/// @brief Cheats are being displayed.
|
||||
DisplayCheats
|
||||
};
|
||||
|
||||
CheatsViewModel(const FileInfo& romFileInfo, IRomBrowserController* romBrowserController);
|
||||
|
||||
/// @brief Activates the selected cheat or category.
|
||||
void ActivateSelectedItem();
|
||||
|
||||
/// @brief Navigates up in the cheat hierachy, or closes the cheats panel when at the root.
|
||||
/// @return \c true when navigation happened in the cheats tree, or \c false when the cheats panel was closed.
|
||||
bool NavigateUp();
|
||||
|
||||
/// @brief Closes the cheats panel.
|
||||
void Close();
|
||||
|
||||
/// @brief Disables all cheats.
|
||||
void DisableAllCheats();
|
||||
|
||||
/// @brief Gets the current state of the cheats panel.
|
||||
/// @return The current state of the cheats panel.
|
||||
State GetState() const { return _state; }
|
||||
|
||||
/// @brief Gets the current cheat category.
|
||||
/// @return The current cheat category.
|
||||
const CheatEntry* GetCurrentCheatCategory() const { return _categoryStack[_categoryStackLevel].cheatCategory; }
|
||||
|
||||
/// @brief Gets the index of the selected item.
|
||||
/// @return The index of the selected item.
|
||||
constexpr int GetSelectedItem() const { return _selectedItem; }
|
||||
|
||||
/// @brief Sets the index of the selected item.
|
||||
/// @param selectedItem The index of the selected item to set.
|
||||
void SetSelectedItem(int selectedItem) { _selectedItem = selectedItem; }
|
||||
|
||||
/// @brief Returns whether the category name should be displayed.
|
||||
/// @return \c true when the category name should be displayed, or \c false otherwise.
|
||||
bool ShouldShowCategoryName() const
|
||||
{
|
||||
return _categoryStackLevel > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
struct CategoryStackEntry
|
||||
{
|
||||
const CheatEntry* cheatCategory;
|
||||
u32 index;
|
||||
};
|
||||
|
||||
FileInfo _romFileInfo;
|
||||
IRomBrowserController* _romBrowserController;
|
||||
QueueTask<void> _loadCheatsTask;
|
||||
std::unique_ptr<GameCheats> _cheats;
|
||||
State _state = State::Loading;
|
||||
int _selectedItem = -1;
|
||||
bool _changed = false;
|
||||
u32 _categoryStackLevel = 0;
|
||||
std::array<CategoryStackEntry, 8> _categoryStack;
|
||||
|
||||
void DisableAllCheats(const CheatEntry* cheatCategory);
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "common.h"
|
||||
#include <algorithm>
|
||||
#include "romBrowser/FileType/Nds/NdsFileType.h"
|
||||
#include "RomBrowserViewModel.h"
|
||||
|
||||
RomBrowserViewModel::RomBrowserViewModel(IRomBrowserController* romBrowserController, const char* initialSelectedFileName)
|
||||
@@ -12,19 +13,19 @@ RomBrowserViewModel::RomBrowserViewModel(IRomBrowserController* romBrowserContro
|
||||
default:
|
||||
{
|
||||
filterSortParams = SdFolderFilterSortParams(
|
||||
SdFolderSortType::Name, SdFolderSortDirection::Ascending);
|
||||
SdFolderSortType::Name, SdFolderSortDirection::Ascending, false);
|
||||
break;
|
||||
}
|
||||
case RomBrowserSortMode::NameDescending:
|
||||
{
|
||||
filterSortParams = SdFolderFilterSortParams(
|
||||
SdFolderSortType::Name, SdFolderSortDirection::Descending);
|
||||
SdFolderSortType::Name, SdFolderSortDirection::Descending, false);
|
||||
break;
|
||||
}
|
||||
case RomBrowserSortMode::LastModified:
|
||||
{
|
||||
filterSortParams = SdFolderFilterSortParams(
|
||||
SdFolderSortType::LastModified, SdFolderSortDirection::Descending);
|
||||
SdFolderSortType::LastModified, SdFolderSortDirection::Descending, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -34,7 +35,8 @@ RomBrowserViewModel::RomBrowserViewModel(IRomBrowserController* romBrowserContro
|
||||
auto sortedFilteredFiles = sdFolder.FilterAndSort(filterSortParams, filteredCount);
|
||||
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());
|
||||
_fileInfoManager = std::make_unique<FileInfoManager>(std::move(sortedFilteredFiles),
|
||||
filteredCount, _romBrowserController->GetCoverRepository());
|
||||
_selectedItem = _fileInfoManager->GetItemIndex(initialSelectedFileName);
|
||||
}
|
||||
|
||||
@@ -59,7 +61,8 @@ void RomBrowserViewModel::NavigateUp()
|
||||
void RomBrowserViewModel::ShowGameInfo()
|
||||
{
|
||||
const auto& item = _fileInfoManager->GetItem(_selectedItem);
|
||||
if (item.GetFileType()->GetClassification() == FileTypeClassification::Folder)
|
||||
return;
|
||||
_romBrowserController->ShowGameInfo();
|
||||
if (item.GetFileType() == &NdsFileType::sInstance)
|
||||
{
|
||||
_romBrowserController->ShowGameInfo(item);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
|
||||
void SetFirstLineAsync(TaskQueueBase* taskQueue, const char* firstLine, bool ellipsis) override
|
||||
{
|
||||
_firstLine->SetEllipsis(ellipsis);
|
||||
_firstLine->SetEllipsisStyle(ellipsis ? LabelView::EllipsisStyle::Ellipsis : LabelView::EllipsisStyle::None);
|
||||
if (taskQueue)
|
||||
_firstLine->SetTextAsync(taskQueue, firstLine);
|
||||
else
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
|
||||
void SetFirstLineAsync(TaskQueueBase* taskQueue, const char16_t* firstLine, u32 length, bool ellipsis) override
|
||||
{
|
||||
_firstLine->SetEllipsis(ellipsis);
|
||||
_firstLine->SetEllipsisStyle(ellipsis ? LabelView::EllipsisStyle::Ellipsis : LabelView::EllipsisStyle::None);
|
||||
if (taskQueue)
|
||||
_firstLine->SetTextAsync(taskQueue, firstLine, length);
|
||||
else
|
||||
|
||||
@@ -26,13 +26,17 @@ public:
|
||||
void SetIcon(std::unique_ptr<FileIcon> icon)
|
||||
{
|
||||
_icon = std::move(icon);
|
||||
if (_icon)
|
||||
{
|
||||
_icon->SetVramAddress(_iconVram, _iconVramOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void UploadIconGraphics() const
|
||||
{
|
||||
if (_icon)
|
||||
{
|
||||
_icon->UploadGraphics(_iconVram);
|
||||
_icon->UploadGraphics();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,13 +27,17 @@ public:
|
||||
void SetIcon(std::unique_ptr<FileIcon> icon)
|
||||
{
|
||||
_icon = std::move(icon);
|
||||
if (_icon)
|
||||
{
|
||||
_icon->SetVramAddress(_iconVram, _iconVramOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void UploadIconGraphics() const
|
||||
{
|
||||
if (_icon)
|
||||
{
|
||||
_icon->UploadGraphics(_iconVram);
|
||||
_icon->UploadGraphics();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
99
arm9/source/romBrowser/views/cheats/CheatListItemView.cpp
Normal file
99
arm9/source/romBrowser/views/cheats/CheatListItemView.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "common.h"
|
||||
#include "themes/material/MaterialColorScheme.h"
|
||||
#include "themes/IFontRepository.h"
|
||||
#include "gui/palette/GradientPalette.h"
|
||||
#include "gui/GraphicsContext.h"
|
||||
#include "gui/OamBuilder.h"
|
||||
#include "CheatListItemView.h"
|
||||
|
||||
#define ICON_X 4
|
||||
#define ICON_Y 4
|
||||
|
||||
#define NAME_LABEL_X 24
|
||||
#define NAME_LABEL_Y 5
|
||||
|
||||
CheatListItemView::CheatListItemView(const VramOffsets& vramOffsets,
|
||||
const MaterialColorScheme* materialColorScheme, const IFontRepository* fontRepository)
|
||||
: _nameLabel(196, 16, 256, fontRepository->GetFont(FontType::Regular10))
|
||||
, _vramOffsets(vramOffsets)
|
||||
, _materialColorScheme(materialColorScheme)
|
||||
{
|
||||
_nameLabel.SetEllipsisStyle(LabelView::EllipsisStyle::Ellipsis);
|
||||
AddChildTail(&_nameLabel);
|
||||
}
|
||||
|
||||
void CheatListItemView::Update()
|
||||
{
|
||||
_nameLabel.SetPosition(_position.x + NAME_LABEL_X, _position.y + NAME_LABEL_Y);
|
||||
if (_cheatEntry != nullptr && !_cheatEntry->IsCheatCategory())
|
||||
{
|
||||
_iconVramOffset = _cheatEntry->GetIsCheatActive()
|
||||
? _vramOffsets.checkboxCheckedIconVramOffset
|
||||
: _vramOffsets.checkboxUncheckedIconVramOffset;
|
||||
}
|
||||
if (IsFocused())
|
||||
{
|
||||
_nameLabel.SetEllipsisStyle(LabelView::EllipsisStyle::Marquee);
|
||||
}
|
||||
else
|
||||
{
|
||||
_nameLabel.SetEllipsisStyle(LabelView::EllipsisStyle::Ellipsis);
|
||||
}
|
||||
ViewContainer::Update();
|
||||
}
|
||||
|
||||
void CheatListItemView::Draw(GraphicsContext& graphicsContext)
|
||||
{
|
||||
if (!graphicsContext.IsVisible(GetBounds()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto backColor = _materialColorScheme->GetColor(md::sys::color::surfaceContainerLow);
|
||||
if (IsFocused())
|
||||
{
|
||||
auto selectorFullColor = _materialColorScheme->GetColor(md::sys::color::onSurface);
|
||||
backColor = RgbMixer::Lerp(backColor, selectorFullColor, 10, 100);
|
||||
}
|
||||
|
||||
_nameLabel.SetBackgroundColor(backColor);
|
||||
_nameLabel.SetForegroundColor(_materialColorScheme->onSurface);
|
||||
|
||||
if (IsFocused())
|
||||
{
|
||||
u32 selectorPaletteRow = graphicsContext.GetPaletteManager().AllocRow(
|
||||
GradientPalette(backColor, backColor),
|
||||
_position.y, _position.y + 24);
|
||||
auto selectorOam = graphicsContext.GetOamManager().AllocOams(4);
|
||||
OamBuilder::OamWithSize<64, 32>(_position.x, _position.y, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(selectorPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(selectorOam[0]);
|
||||
OamBuilder::OamWithSize<64, 32>(_position.x + 64, _position.y, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(selectorPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(selectorOam[1]);
|
||||
OamBuilder::OamWithSize<64, 32>(_position.x + 2 * 64, _position.y, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(selectorPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(selectorOam[2]);
|
||||
OamBuilder::OamWithSize<64, 32>(_position.x + 2 * 64 + 32, _position.y, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(selectorPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(selectorOam[3]);
|
||||
}
|
||||
|
||||
ViewContainer::Draw(graphicsContext);
|
||||
|
||||
if (graphicsContext.IsVisible(Rectangle(_position.x + ICON_X, _position.y + ICON_Y, 16, 16)))
|
||||
{
|
||||
u32 iconPaletteRow = graphicsContext.GetPaletteManager().AllocRow(
|
||||
GradientPalette(backColor, _materialColorScheme->primary),
|
||||
_position.y + ICON_Y, _position.y + ICON_Y + 16);
|
||||
auto iconOam = graphicsContext.GetOamManager().AllocOams(1);
|
||||
OamBuilder::OamWithSize<16, 16>(_position.x + ICON_X, _position.y + ICON_Y, _iconVramOffset >> 7)
|
||||
.WithPalette16(iconPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(iconOam[0]);
|
||||
}
|
||||
}
|
||||
52
arm9/source/romBrowser/views/cheats/CheatListItemView.h
Normal file
52
arm9/source/romBrowser/views/cheats/CheatListItemView.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include "gui/views/ViewContainer.h"
|
||||
#include "gui/views/Label2DView.h"
|
||||
#include "cheats/CheatEntry.h"
|
||||
|
||||
class MaterialColorScheme;
|
||||
class IFontRepository;
|
||||
|
||||
/// @brief List item view for the cheats panel, representing a single cheat or cheat category.
|
||||
class CheatListItemView : public ViewContainer
|
||||
{
|
||||
public:
|
||||
struct VramOffsets
|
||||
{
|
||||
u32 folderIconVramOffset = 0;
|
||||
u32 checkboxUncheckedIconVramOffset = 0;
|
||||
u32 checkboxCheckedIconVramOffset = 0;
|
||||
u32 cheatSelectorVramOffset = 0;
|
||||
};
|
||||
|
||||
CheatListItemView(const VramOffsets& vramOffsets, const MaterialColorScheme* materialColorScheme, const IFontRepository* fontRepository);
|
||||
|
||||
void Update() override;
|
||||
void Draw(GraphicsContext& graphicsContext) override;
|
||||
|
||||
Rectangle GetBounds() const override
|
||||
{
|
||||
return Rectangle(_position.x, _position.y, 224, 24);
|
||||
}
|
||||
|
||||
void SetName(const char* name)
|
||||
{
|
||||
_nameLabel.SetText(name);
|
||||
}
|
||||
|
||||
void SetEntry(const CheatEntry* cheatEntry)
|
||||
{
|
||||
_cheatEntry = cheatEntry;
|
||||
_nameLabel.SetText(cheatEntry->GetName());
|
||||
if (cheatEntry->IsCheatCategory())
|
||||
{
|
||||
_iconVramOffset = _vramOffsets.folderIconVramOffset;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Label2DView _nameLabel;
|
||||
VramOffsets _vramOffsets;
|
||||
const MaterialColorScheme* _materialColorScheme;
|
||||
u32 _iconVramOffset = 0;
|
||||
const CheatEntry* _cheatEntry = nullptr;
|
||||
};
|
||||
56
arm9/source/romBrowser/views/cheats/CheatsAdapter.h
Normal file
56
arm9/source/romBrowser/views/cheats/CheatsAdapter.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
#include "gui/views/RecyclerAdapter.h"
|
||||
#include "cheats/CheatEntry.h"
|
||||
#include "CheatListItemView.h"
|
||||
|
||||
/// @brief Recycler adapter for cheats.
|
||||
class CheatsAdapter : public RecyclerAdapter
|
||||
{
|
||||
public:
|
||||
CheatsAdapter(const CheatEntry* cheatCategory, const MaterialColorScheme* materialColorScheme,
|
||||
const IFontRepository* fontRepository, const CheatListItemView::VramOffsets& vramOffsets)
|
||||
: _cheatCategory(cheatCategory), _materialColorScheme(materialColorScheme)
|
||||
, _fontRepository(fontRepository), _vramOffsets(vramOffsets) { }
|
||||
|
||||
u32 GetItemCount() const override
|
||||
{
|
||||
u32 numberOfSubEntries = 0;
|
||||
_cheatCategory->GetSubEntries(numberOfSubEntries);
|
||||
return numberOfSubEntries;
|
||||
}
|
||||
|
||||
void GetViewSize(int& width, int& height) const override
|
||||
{
|
||||
width = 224;
|
||||
height = 24;
|
||||
}
|
||||
|
||||
View* CreateView() const override
|
||||
{
|
||||
return new CheatListItemView(_vramOffsets, _materialColorScheme, _fontRepository);
|
||||
}
|
||||
|
||||
void DestroyView(View* view) const override
|
||||
{
|
||||
delete (CheatListItemView*)view;
|
||||
}
|
||||
|
||||
void BindView(View* view, int index) const override
|
||||
{
|
||||
auto listItemView = static_cast<CheatListItemView*>(view);
|
||||
u32 numberOfSubEntries = 0;
|
||||
auto subEntries = _cheatCategory->GetSubEntries(numberOfSubEntries);
|
||||
listItemView->SetEntry(&subEntries[index]);
|
||||
}
|
||||
|
||||
void ReleaseView(View* view, int index) const override
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
private:
|
||||
const CheatEntry* _cheatCategory;
|
||||
const MaterialColorScheme* _materialColorScheme;
|
||||
const IFontRepository* _fontRepository;
|
||||
CheatListItemView::VramOffsets _vramOffsets;
|
||||
};
|
||||
271
arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.cpp
Normal file
271
arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.cpp
Normal file
@@ -0,0 +1,271 @@
|
||||
#include "common.h"
|
||||
#include "gui/GraphicsContext.h"
|
||||
#include "themes/material/MaterialColorScheme.h"
|
||||
#include "themes/IFontRepository.h"
|
||||
#include "gui/input/InputProvider.h"
|
||||
#include "gui/VramContext.h"
|
||||
#include "gui/palette/GradientPalette.h"
|
||||
#include "gui/OamBuilder.h"
|
||||
#include "folderIcon.h"
|
||||
#include "checkboxChecked.h"
|
||||
#include "checkboxUnchecked.h"
|
||||
#include "cheatSelector.h"
|
||||
#include "gui/DescendingStackVramManager.h"
|
||||
#include "CheatsBottomSheetView.h"
|
||||
|
||||
#define TITLE_LABEL_X 20
|
||||
#define TITLE_LABEL_Y 16
|
||||
|
||||
#define CATEGORY_NAME_LABEL_X (TITLE_LABEL_X + 43)
|
||||
#define CATEGORY_NAME_LABEL_Y (TITLE_LABEL_Y + 1)
|
||||
|
||||
#define NO_CHEATS_FOUND_LABEL_X 20
|
||||
#define NO_CHEATS_FOUND_LABEL_Y 36
|
||||
|
||||
#define DESCRIPTION_LABEL_X 16
|
||||
#define DESCRIPTION_LABEL_Y 147
|
||||
|
||||
#define LIST_X 16
|
||||
#define LIST_Y 36
|
||||
#define LIST_WIDTH 224
|
||||
#define LIST_HEIGHT 108
|
||||
|
||||
CheatsBottomSheetView::CheatsBottomSheetView(std::unique_ptr<CheatsViewModel> viewModel,
|
||||
const MaterialColorScheme* materialColorScheme, const IFontRepository* fontRepository,
|
||||
FocusManager* focusManager)
|
||||
: _viewModel(std::move(viewModel))
|
||||
, _titleLabel(64, 16, 25, fontRepository->GetFont(FontType::Medium11))
|
||||
, _secondaryLabel(177, 16, 64, fontRepository->GetFont(FontType::Regular10))
|
||||
, _descriptionLabel(224, 16, 256, fontRepository->GetFont(FontType::Medium7_5))
|
||||
, _cheatListRecycler(std::make_unique<RecyclerView>(
|
||||
LIST_X, LIST_Y, LIST_WIDTH, LIST_HEIGHT, RecyclerView::Mode::VerticalList))
|
||||
, _materialColorScheme(materialColorScheme)
|
||||
, _fontRepository(fontRepository)
|
||||
, _focusManager(focusManager)
|
||||
{
|
||||
_titleLabel.SetText(u"Cheats");
|
||||
_secondaryLabel.SetText(u"No cheats found.");
|
||||
_secondaryLabel.SetEllipsisStyle(LabelView::EllipsisStyle::Ellipsis);
|
||||
_descriptionLabel.SetEllipsisStyle(LabelView::EllipsisStyle::Marquee);
|
||||
_descriptionLabel.SetText("");
|
||||
AddChildTail(&_titleLabel);
|
||||
AddChildTail(&_secondaryLabel);
|
||||
AddChildTail(&_descriptionLabel);
|
||||
AddChildTail(_cheatListRecycler.get());
|
||||
}
|
||||
|
||||
void CheatsBottomSheetView::InitVram(const VramContext& vramContext)
|
||||
{
|
||||
BottomSheetView::InitVram(vramContext);
|
||||
|
||||
const auto objVramManager = vramContext.GetObjVramManager();
|
||||
if (objVramManager)
|
||||
{
|
||||
_vramOffsets.folderIconVramOffset = objVramManager->Alloc(folderIconTilesLen);
|
||||
dma_ntrCopy32(3, folderIconTiles,
|
||||
objVramManager->GetVramAddress(_vramOffsets.folderIconVramOffset), folderIconTilesLen);
|
||||
|
||||
_vramOffsets.checkboxUncheckedIconVramOffset = objVramManager->Alloc(checkboxUncheckedTilesLen);
|
||||
dma_ntrCopy32(3, checkboxUncheckedTiles,
|
||||
objVramManager->GetVramAddress(_vramOffsets.checkboxUncheckedIconVramOffset), checkboxUncheckedTilesLen);
|
||||
|
||||
_vramOffsets.checkboxCheckedIconVramOffset = objVramManager->Alloc(checkboxCheckedTilesLen);
|
||||
dma_ntrCopy32(3, checkboxCheckedTiles,
|
||||
objVramManager->GetVramAddress(_vramOffsets.checkboxCheckedIconVramOffset), checkboxCheckedTilesLen);
|
||||
|
||||
_vramOffsets.cheatSelectorVramOffset = objVramManager->Alloc(cheatSelectorTilesLen);
|
||||
dma_ntrCopy32(3, cheatSelectorTiles,
|
||||
objVramManager->GetVramAddress(_vramOffsets.cheatSelectorVramOffset), cheatSelectorTilesLen);
|
||||
}
|
||||
|
||||
_objVramManager = vramContext.GetObjVramManager();
|
||||
}
|
||||
|
||||
void CheatsBottomSheetView::Update()
|
||||
{
|
||||
_titleLabel.SetPosition(TITLE_LABEL_X, _position.y + TITLE_LABEL_Y);
|
||||
if (_viewModel->GetState() == CheatsViewModel::State::DisplayCheats)
|
||||
{
|
||||
_secondaryLabel.SetPosition(CATEGORY_NAME_LABEL_X, _position.y + CATEGORY_NAME_LABEL_Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
_secondaryLabel.SetPosition(NO_CHEATS_FOUND_LABEL_X, _position.y + NO_CHEATS_FOUND_LABEL_Y);
|
||||
}
|
||||
_descriptionLabel.SetPosition(DESCRIPTION_LABEL_X, _position.y + DESCRIPTION_LABEL_Y);
|
||||
_cheatListRecycler->SetPosition(LIST_X, _position.y + LIST_Y);
|
||||
if (_viewModel->GetState() == CheatsViewModel::State::DisplayCheats)
|
||||
{
|
||||
if (_cheatsAdapter == nullptr && _objVramManager != nullptr)
|
||||
{
|
||||
_cheatsAdapter = new CheatsAdapter(
|
||||
_viewModel->GetCurrentCheatCategory(), _materialColorScheme, _fontRepository, _vramOffsets);
|
||||
_cheatListRecycler->SetAdapter(_cheatsAdapter);
|
||||
|
||||
// Ugly hack
|
||||
_savedVramState = ((DescendingStackVramManager*)_objVramManager)->GetState();
|
||||
|
||||
_cheatListRecycler->InitVram(VramContext(nullptr, _objVramManager, nullptr, nullptr));
|
||||
_cheatListRecycler->Focus(*_focusManager);
|
||||
}
|
||||
}
|
||||
BottomSheetView::Update();
|
||||
int selectedItem = _cheatListRecycler->GetSelectedItem();
|
||||
if (selectedItem != _viewModel->GetSelectedItem())
|
||||
{
|
||||
_viewModel->SetSelectedItem(selectedItem);
|
||||
UpdateDescriptionText();
|
||||
}
|
||||
}
|
||||
|
||||
void CheatsBottomSheetView::Draw(GraphicsContext& graphicsContext)
|
||||
{
|
||||
graphicsContext.SetClipArea(GetBounds());
|
||||
u32 oldPrio = graphicsContext.SetPriority(1);
|
||||
{
|
||||
graphicsContext.SetClipArea(_cheatListRecycler->GetBounds());
|
||||
_cheatListRecycler->Draw(graphicsContext);
|
||||
|
||||
graphicsContext.SetClipArea(GetBounds());
|
||||
|
||||
auto backColor = _materialColorScheme->GetColor(md::sys::color::surfaceContainerLow);
|
||||
auto maskOam = graphicsContext.GetOamManager().AllocOams(8);
|
||||
// Top
|
||||
u32 maskPaletteRow = graphicsContext.GetPaletteManager().AllocRow(
|
||||
GradientPalette(backColor, backColor),
|
||||
_position.y + LIST_Y - 24, _position.y + LIST_Y);
|
||||
OamBuilder::OamWithSize<64, 32>(LIST_X, _position.y + LIST_Y - 24, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(maskPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(maskOam[0]);
|
||||
OamBuilder::OamWithSize<64, 32>(LIST_X + 64, _position.y + LIST_Y - 24, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(maskPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(maskOam[1]);
|
||||
OamBuilder::OamWithSize<64, 32>(LIST_X + 2 * 64, _position.y + LIST_Y - 24, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(maskPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(maskOam[2]);
|
||||
OamBuilder::OamWithSize<64, 32>(LIST_X + 2 * 64 + 32, _position.y + LIST_Y - 24, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(maskPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(maskOam[3]);
|
||||
|
||||
// Bottom
|
||||
if (graphicsContext.IsVisible(Rectangle(LIST_X, _position.y + LIST_Y + LIST_HEIGHT, 224, 24)))
|
||||
{
|
||||
maskPaletteRow = graphicsContext.GetPaletteManager().AllocRow(
|
||||
GradientPalette(backColor, backColor),
|
||||
_position.y + LIST_Y + LIST_HEIGHT, 192);
|
||||
OamBuilder::OamWithSize<64, 32>(LIST_X, _position.y + LIST_Y + LIST_HEIGHT, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(maskPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(maskOam[4]);
|
||||
OamBuilder::OamWithSize<64, 32>(LIST_X + 64, _position.y + LIST_Y + LIST_HEIGHT, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(maskPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(maskOam[5]);
|
||||
OamBuilder::OamWithSize<64, 32>(LIST_X + 2 * 64, _position.y + LIST_Y + LIST_HEIGHT, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(maskPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(maskOam[6]);
|
||||
OamBuilder::OamWithSize<64, 32>(LIST_X + 2 * 64 + 32, _position.y + LIST_Y + LIST_HEIGHT, _vramOffsets.cheatSelectorVramOffset >> 7)
|
||||
.WithPalette16(maskPaletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(maskOam[7]);
|
||||
}
|
||||
|
||||
_titleLabel.SetBackgroundColor(backColor);
|
||||
_titleLabel.SetForegroundColor(_materialColorScheme->onSurface);
|
||||
_titleLabel.Draw(graphicsContext);
|
||||
|
||||
if (_viewModel->GetState() == CheatsViewModel::State::NoCheats ||
|
||||
_viewModel->ShouldShowCategoryName())
|
||||
{
|
||||
_secondaryLabel.SetBackgroundColor(backColor);
|
||||
_secondaryLabel.SetForegroundColor(_materialColorScheme->onSurfaceVariant);
|
||||
_secondaryLabel.Draw(graphicsContext);
|
||||
}
|
||||
|
||||
_descriptionLabel.SetBackgroundColor(backColor);
|
||||
_descriptionLabel.SetForegroundColor(_materialColorScheme->onSurfaceVariant);
|
||||
_descriptionLabel.Draw(graphicsContext);
|
||||
}
|
||||
graphicsContext.SetPriority(oldPrio);
|
||||
graphicsContext.ResetClipArea();
|
||||
}
|
||||
|
||||
bool CheatsBottomSheetView::HandleInput(const InputProvider& inputProvider, FocusManager& focusManager)
|
||||
{
|
||||
if (inputProvider.Triggered(InputKey::A))
|
||||
{
|
||||
if (focusManager.IsFocusInside(_cheatListRecycler.get()))
|
||||
{
|
||||
auto oldCategory = _viewModel->GetCurrentCheatCategory();
|
||||
_viewModel->ActivateSelectedItem();
|
||||
if (oldCategory != _viewModel->GetCurrentCheatCategory())
|
||||
{
|
||||
_secondaryLabel.SetText(_viewModel->GetCurrentCheatCategory()->GetName());
|
||||
UpdateCheatList();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (inputProvider.Triggered(InputKey::B))
|
||||
{
|
||||
auto oldCategory = _viewModel->GetCurrentCheatCategory();
|
||||
if (_viewModel->NavigateUp() &&
|
||||
oldCategory != _viewModel->GetCurrentCheatCategory())
|
||||
{
|
||||
UpdateCheatList();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (inputProvider.Triggered(InputKey::Y))
|
||||
{
|
||||
_viewModel->Close();
|
||||
return true;
|
||||
}
|
||||
else if (inputProvider.Triggered(InputKey::X))
|
||||
{
|
||||
_viewModel->DisableAllCheats();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CheatsBottomSheetView::UpdateCheatList()
|
||||
{
|
||||
// Need to unfocus first, otherwise the focus manager still contains a pointer to a view that is going to be destroyed
|
||||
_focusManager->Unfocus();
|
||||
|
||||
auto oldAdapter = _cheatsAdapter;
|
||||
_cheatsAdapter = new CheatsAdapter(
|
||||
_viewModel->GetCurrentCheatCategory(), _materialColorScheme, _fontRepository, _vramOffsets);
|
||||
_cheatListRecycler->SetAdapter(_cheatsAdapter, _viewModel->GetSelectedItem());
|
||||
delete oldAdapter;
|
||||
|
||||
// Ugly hack
|
||||
((DescendingStackVramManager*)_objVramManager)->SetState(_savedVramState);
|
||||
|
||||
_cheatListRecycler->InitVram(VramContext(nullptr, _objVramManager, nullptr, nullptr));
|
||||
_cheatListRecycler->Focus(*_focusManager);
|
||||
UpdateDescriptionText();
|
||||
}
|
||||
|
||||
void CheatsBottomSheetView::UpdateDescriptionText()
|
||||
{
|
||||
int selectedItem = _viewModel->GetSelectedItem();
|
||||
if (selectedItem < 0)
|
||||
{
|
||||
_descriptionLabel.SetText("");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto cheatCategory = _viewModel->GetCurrentCheatCategory();
|
||||
u32 numberOfSubEntries = 0;
|
||||
auto subEntries = cheatCategory->GetSubEntries(numberOfSubEntries);
|
||||
_descriptionLabel.SetText(subEntries[selectedItem].GetDescription());
|
||||
}
|
||||
}
|
||||
57
arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.h
Normal file
57
arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "romBrowser/views/BottomSheetView.h"
|
||||
#include "gui/views/Label2DView.h"
|
||||
#include "gui/views/RecyclerView.h"
|
||||
#include "romBrowser/viewModels/CheatsViewModel.h"
|
||||
#include "CheatsAdapter.h"
|
||||
#include "CheatListItemView.h"
|
||||
|
||||
class MaterialColorScheme;
|
||||
class IFontRepository;
|
||||
class IVramManager;
|
||||
|
||||
/// @brief Bottom sheet for browsing and enabling/disabling cheats.
|
||||
class CheatsBottomSheetView : public BottomSheetView
|
||||
{
|
||||
public:
|
||||
CheatsBottomSheetView(std::unique_ptr<CheatsViewModel> viewModel,
|
||||
const MaterialColorScheme* materialColorScheme, const IFontRepository* fontRepository,
|
||||
FocusManager* focusManager);
|
||||
|
||||
~CheatsBottomSheetView() override
|
||||
{
|
||||
_cheatListRecycler.reset();
|
||||
if (_cheatsAdapter != nullptr)
|
||||
{
|
||||
delete _cheatsAdapter;
|
||||
}
|
||||
}
|
||||
|
||||
void InitVram(const VramContext& vramContext) override;
|
||||
void Update() override;
|
||||
void Draw(GraphicsContext& graphicsContext) override;
|
||||
bool HandleInput(const InputProvider& inputProvider, FocusManager& focusManager) override;
|
||||
|
||||
void Focus(FocusManager& focusManager) override
|
||||
{
|
||||
_cheatListRecycler->Focus(focusManager);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<CheatsViewModel> _viewModel;
|
||||
Label2DView _titleLabel;
|
||||
Label2DView _secondaryLabel;
|
||||
Label2DView _descriptionLabel;
|
||||
std::unique_ptr<RecyclerView> _cheatListRecycler;
|
||||
CheatsAdapter* _cheatsAdapter = nullptr;
|
||||
const MaterialColorScheme* _materialColorScheme;
|
||||
const IFontRepository* _fontRepository;
|
||||
IVramManager* _objVramManager = nullptr;
|
||||
FocusManager* _focusManager;
|
||||
CheatListItemView::VramOffsets _vramOffsets;
|
||||
u32 _savedVramState = 0;
|
||||
|
||||
void UpdateCheatList();
|
||||
void UpdateDescriptionText();
|
||||
};
|
||||
15
arm9/source/themes/custom/CustomBannerListTextElementInfo.h
Normal file
15
arm9/source/themes/custom/CustomBannerListTextElementInfo.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include "core/math/Point.h"
|
||||
#include "core/math/Rgb.h"
|
||||
|
||||
class CustomBannerListTextElementInfo
|
||||
{
|
||||
public:
|
||||
CustomBannerListTextElementInfo(const Rgb8& textColor)
|
||||
: _textColor(textColor) { }
|
||||
|
||||
const Rgb8& GetTextColor() const { return _textColor; }
|
||||
|
||||
private:
|
||||
Rgb8 _textColor;
|
||||
};
|
||||
15
arm9/source/themes/custom/CustomBottomIconInfo.h
Normal file
15
arm9/source/themes/custom/CustomBottomIconInfo.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include "core/math/Point.h"
|
||||
#include "core/math/Rgb.h"
|
||||
|
||||
class CustomBottomIconInfo
|
||||
{
|
||||
public:
|
||||
CustomBottomIconInfo(const Rgb8& blendColor)
|
||||
: _blendColor(blendColor) { }
|
||||
|
||||
const Rgb8& GetBlendColor() const { return _blendColor; }
|
||||
|
||||
private:
|
||||
Rgb8 _blendColor;
|
||||
};
|
||||
21
arm9/source/themes/custom/CustomTextElementInfo.h
Normal file
21
arm9/source/themes/custom/CustomTextElementInfo.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "core/math/Point.h"
|
||||
#include "core/math/Rgb.h"
|
||||
|
||||
class CustomTopTextElementInfo
|
||||
{
|
||||
public:
|
||||
CustomTopTextElementInfo(const Point& position, u32 width, const Rgb8& textColor, const Rgb8& blendColor)
|
||||
: _position(position), _width(width), _textColor(textColor), _blendColor(blendColor) { }
|
||||
|
||||
const Point& GetPosition() const { return _position; }
|
||||
const u32 GetWidth() const { return _width; }
|
||||
const Rgb8& GetTextColor() const { return _textColor; }
|
||||
const Rgb8& GetBlendColor() const { return _blendColor; }
|
||||
|
||||
private:
|
||||
Point _position;
|
||||
u32 _width;
|
||||
Rgb8 _textColor;
|
||||
Rgb8 _blendColor;
|
||||
};
|
||||
@@ -11,13 +11,164 @@
|
||||
#include "romBrowser/views/IconButton3DView.h"
|
||||
#include "CustomTheme.h"
|
||||
|
||||
#define JSON_RESERVED_SIZE 2048
|
||||
#define JSON_RESERVED_SIZE 4096
|
||||
|
||||
#define KEY_COLOR_R "r"
|
||||
#define KEY_COLOR_G "g"
|
||||
#define KEY_COLOR_B "b"
|
||||
|
||||
#define KEY_POINT_X "x"
|
||||
#define KEY_POINT_Y "y"
|
||||
|
||||
#define KEY_TOP_ICON "topIcon"
|
||||
#define KEY_TOP_BANNER_TEXT_LINE_0 "topBannerTextLine0"
|
||||
#define KEY_TOP_BANNER_TEXT_LINE_1 "topBannerTextLine1"
|
||||
#define KEY_TOP_BANNER_TEXT_LINE_2 "topBannerTextLine2"
|
||||
#define KEY_TOP_FILE_NAME_TEXT "topFileNameText"
|
||||
#define KEY_GRID_ICON "gridIcon"
|
||||
#define KEY_BANNER_LIST_ICON "bannerListIcon"
|
||||
#define KEY_BANNER_LIST_TEXT_LINE_0 "bannerListTextLine0"
|
||||
#define KEY_BANNER_LIST_TEXT_LINE_1 "bannerListTextLine1"
|
||||
#define KEY_BANNER_LIST_TEXT_LINE_2 "bannerListTextLine2"
|
||||
|
||||
#define KEY_ELEMENT_POSITION "position"
|
||||
#define KEY_ELEMENT_WIDTH "width"
|
||||
#define KEY_ELEMENT_TEXT_COLOR "textColor"
|
||||
#define KEY_ELEMENT_BLEND_COLOR "blendColor"
|
||||
|
||||
static const CustomThemeInfo sDefaultCustomThemeInfo
|
||||
{
|
||||
.topIconInfo = CustomTopIconInfo(Point(24, 132), Rgb8(200, 200, 200)),
|
||||
.topBannerTextLine0Info = CustomTopTextElementInfo(Point(70, 126), 176, Rgb8(30, 30, 30), Rgb8(200, 200, 200)),
|
||||
.topBannerTextLine1Info = CustomTopTextElementInfo(Point(70, 141), 176, Rgb8(30, 30, 30), Rgb8(200, 200, 200)),
|
||||
.topBannerTextLine2Info = CustomTopTextElementInfo(Point(70, 155), 176, Rgb8(30, 30, 30), Rgb8(200, 200, 200)),
|
||||
.topFileNameTextInfo = CustomTopTextElementInfo(Point(18, 170), 220, Rgb8(30, 30, 30), Rgb8(200, 200, 200)),
|
||||
|
||||
.gridIconInfo = CustomBottomIconInfo(Rgb8(200, 200, 200)),
|
||||
|
||||
.bannerListIconInfo = CustomBottomIconInfo(Rgb8(200, 200, 200)),
|
||||
.bannerListTextLine0Info = CustomBannerListTextElementInfo(Rgb8(30, 30, 30)),
|
||||
.bannerListTextLine1Info = CustomBannerListTextElementInfo(Rgb8(30, 30, 30)),
|
||||
.bannerListTextLine2Info = CustomBannerListTextElementInfo(Rgb8(30, 30, 30))
|
||||
};
|
||||
|
||||
static CustomTopBackgroundType parseTopBackgroundType(const char* topBackgroundTypeString)
|
||||
{
|
||||
return CustomTopBackgroundType::Bitmap;
|
||||
}
|
||||
|
||||
static Rgb8 parseColor(const JsonObjectConst& json, const Rgb8& defaultColor)
|
||||
{
|
||||
if (json.isNull())
|
||||
{
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
return Rgb8(
|
||||
json[KEY_COLOR_R] | 0,
|
||||
json[KEY_COLOR_G] | 0,
|
||||
json[KEY_COLOR_B] | 0
|
||||
);
|
||||
}
|
||||
|
||||
static Point parsePoint(const JsonObjectConst& json, const Point& defaultPoint)
|
||||
{
|
||||
if (json.isNull())
|
||||
{
|
||||
return defaultPoint;
|
||||
}
|
||||
|
||||
return Point(
|
||||
json[KEY_POINT_X] | 0,
|
||||
json[KEY_POINT_Y] | 0
|
||||
);
|
||||
}
|
||||
|
||||
static CustomBannerListTextElementInfo parseCustomBannerListTextElementInfo(
|
||||
const JsonObjectConst& json, const CustomBannerListTextElementInfo& defaultInfo)
|
||||
{
|
||||
if (json.isNull())
|
||||
{
|
||||
return defaultInfo;
|
||||
}
|
||||
|
||||
return CustomBannerListTextElementInfo(
|
||||
parseColor(json[KEY_ELEMENT_TEXT_COLOR], defaultInfo.GetTextColor())
|
||||
);
|
||||
}
|
||||
|
||||
static CustomBottomIconInfo parseCustomBottomIconInfo(const JsonObjectConst& json, const CustomBottomIconInfo& defaultInfo)
|
||||
{
|
||||
if (json.isNull())
|
||||
{
|
||||
return defaultInfo;
|
||||
}
|
||||
|
||||
return CustomBottomIconInfo(
|
||||
parseColor(json[KEY_ELEMENT_BLEND_COLOR], defaultInfo.GetBlendColor())
|
||||
);
|
||||
}
|
||||
|
||||
static CustomTopIconInfo parseCustomTopIconInfo(const JsonObjectConst& json, const CustomTopIconInfo& defaultInfo)
|
||||
{
|
||||
if (json.isNull())
|
||||
{
|
||||
return defaultInfo;
|
||||
}
|
||||
|
||||
return CustomTopIconInfo(
|
||||
parsePoint(json[KEY_ELEMENT_POSITION], defaultInfo.GetPosition()),
|
||||
parseColor(json[KEY_ELEMENT_BLEND_COLOR], defaultInfo.GetBlendColor())
|
||||
);
|
||||
}
|
||||
|
||||
static CustomTopTextElementInfo parseCustomTextElementInfo(
|
||||
const JsonObjectConst& json, const CustomTopTextElementInfo& defaultInfo)
|
||||
{
|
||||
if (json.isNull())
|
||||
{
|
||||
return defaultInfo;
|
||||
}
|
||||
|
||||
return CustomTopTextElementInfo(
|
||||
parsePoint(json[KEY_ELEMENT_POSITION], defaultInfo.GetPosition()),
|
||||
json[KEY_ELEMENT_WIDTH] | defaultInfo.GetWidth(),
|
||||
parseColor(json[KEY_ELEMENT_TEXT_COLOR], defaultInfo.GetTextColor()),
|
||||
parseColor(json[KEY_ELEMENT_BLEND_COLOR], defaultInfo.GetBlendColor())
|
||||
);
|
||||
}
|
||||
|
||||
static CustomThemeInfo parseCustomThemeInfo(const JsonDocument& json)
|
||||
{
|
||||
return CustomThemeInfo
|
||||
{
|
||||
.topIconInfo = parseCustomTopIconInfo(json[KEY_TOP_ICON], sDefaultCustomThemeInfo.topIconInfo),
|
||||
.topBannerTextLine0Info = parseCustomTextElementInfo(
|
||||
json[KEY_TOP_BANNER_TEXT_LINE_0], sDefaultCustomThemeInfo.topBannerTextLine0Info),
|
||||
.topBannerTextLine1Info = parseCustomTextElementInfo(
|
||||
json[KEY_TOP_BANNER_TEXT_LINE_1], sDefaultCustomThemeInfo.topBannerTextLine1Info),
|
||||
.topBannerTextLine2Info = parseCustomTextElementInfo(
|
||||
json[KEY_TOP_BANNER_TEXT_LINE_2], sDefaultCustomThemeInfo.topBannerTextLine2Info),
|
||||
.topFileNameTextInfo = parseCustomTextElementInfo(
|
||||
json[KEY_TOP_FILE_NAME_TEXT], sDefaultCustomThemeInfo.topFileNameTextInfo),
|
||||
|
||||
.gridIconInfo = parseCustomBottomIconInfo(json[KEY_GRID_ICON], sDefaultCustomThemeInfo.gridIconInfo),
|
||||
|
||||
.bannerListIconInfo = parseCustomBottomIconInfo(json[KEY_BANNER_LIST_ICON], sDefaultCustomThemeInfo.bannerListIconInfo),
|
||||
.bannerListTextLine0Info = parseCustomBannerListTextElementInfo(
|
||||
json[KEY_BANNER_LIST_TEXT_LINE_0], sDefaultCustomThemeInfo.bannerListTextLine0Info),
|
||||
.bannerListTextLine1Info = parseCustomBannerListTextElementInfo(
|
||||
json[KEY_BANNER_LIST_TEXT_LINE_1], sDefaultCustomThemeInfo.bannerListTextLine1Info),
|
||||
.bannerListTextLine2Info = parseCustomBannerListTextElementInfo(
|
||||
json[KEY_BANNER_LIST_TEXT_LINE_2], sDefaultCustomThemeInfo.bannerListTextLine2Info)
|
||||
};
|
||||
}
|
||||
|
||||
CustomTheme::CustomTheme(const TCHAR* folderName, const Rgb<8, 8, 8>& primaryColor, bool darkMode)
|
||||
: Theme(folderName, primaryColor, darkMode)
|
||||
, _customThemeInfo(sDefaultCustomThemeInfo)
|
||||
, _romBrowserViewFactory(&_customThemeInfo, &_materialColorScheme, &_fontRepository) { }
|
||||
|
||||
void CustomTheme::LoadRomBrowserResources(const VramContext& mainVramContext, const VramContext& subVramContext)
|
||||
{
|
||||
const auto file = std::make_unique<File>();
|
||||
@@ -39,6 +190,7 @@ void CustomTheme::LoadRomBrowserResources(const VramContext& mainVramContext, co
|
||||
return;
|
||||
|
||||
_topBackgroundType = parseTopBackgroundType(json["topBackgroundType"].as<const char*>());
|
||||
_customThemeInfo = parseCustomThemeInfo(json);
|
||||
|
||||
mem_setVramDMapping(MEM_VRAM_D_LCDC);
|
||||
mem_setVramEMapping(MEM_VRAM_E_LCDC);
|
||||
|
||||
@@ -7,19 +7,13 @@
|
||||
#include "romBrowser/Theme/custom/CustomRomBrowserViewFactory.h"
|
||||
#include "CustomTopBackgroundType.h"
|
||||
#include "../DefaultFontRepository.h"
|
||||
#include "CustomThemeInfo.h"
|
||||
#include "../Theme.h"
|
||||
|
||||
class CustomTheme : public Theme
|
||||
{
|
||||
String<TCHAR, 64> _folderName;
|
||||
CustomRomBrowserViewFactory _romBrowserViewFactory;
|
||||
CustomTopBackgroundType _topBackgroundType;
|
||||
DefaultFontRepository _fontRepository;
|
||||
|
||||
public:
|
||||
CustomTheme(const TCHAR* folderName, const Rgb<8, 8, 8>& primaryColor, bool darkMode)
|
||||
: Theme(folderName, primaryColor, darkMode)
|
||||
, _romBrowserViewFactory(&_materialColorScheme, &_fontRepository) { }
|
||||
CustomTheme(const TCHAR* folderName, const Rgb<8, 8, 8>& primaryColor, bool darkMode);
|
||||
|
||||
const IFontRepository* GetFontRepository() const override
|
||||
{
|
||||
@@ -47,4 +41,11 @@ public:
|
||||
}
|
||||
|
||||
void LoadRomBrowserResources(const VramContext& mainVramContext, const VramContext& subVramContext) override;
|
||||
|
||||
private:
|
||||
String<TCHAR, 64> _folderName;
|
||||
CustomThemeInfo _customThemeInfo;
|
||||
CustomRomBrowserViewFactory _romBrowserViewFactory;
|
||||
CustomTopBackgroundType _topBackgroundType;
|
||||
DefaultFontRepository _fontRepository;
|
||||
};
|
||||
|
||||
21
arm9/source/themes/custom/CustomThemeInfo.h
Normal file
21
arm9/source/themes/custom/CustomThemeInfo.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "CustomBannerListTextElementInfo.h"
|
||||
#include "CustomBottomIconInfo.h"
|
||||
#include "CustomTopIconInfo.h"
|
||||
#include "CustomTextElementInfo.h"
|
||||
|
||||
struct CustomThemeInfo
|
||||
{
|
||||
CustomTopIconInfo topIconInfo;
|
||||
CustomTopTextElementInfo topBannerTextLine0Info;
|
||||
CustomTopTextElementInfo topBannerTextLine1Info;
|
||||
CustomTopTextElementInfo topBannerTextLine2Info;
|
||||
CustomTopTextElementInfo topFileNameTextInfo;
|
||||
|
||||
CustomBottomIconInfo gridIconInfo;
|
||||
|
||||
CustomBottomIconInfo bannerListIconInfo;
|
||||
CustomBannerListTextElementInfo bannerListTextLine0Info;
|
||||
CustomBannerListTextElementInfo bannerListTextLine1Info;
|
||||
CustomBannerListTextElementInfo bannerListTextLine2Info;
|
||||
};
|
||||
17
arm9/source/themes/custom/CustomTopIconInfo.h
Normal file
17
arm9/source/themes/custom/CustomTopIconInfo.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "core/math/Point.h"
|
||||
#include "core/math/Rgb.h"
|
||||
|
||||
class CustomTopIconInfo
|
||||
{
|
||||
public:
|
||||
CustomTopIconInfo(const Point& position, const Rgb8& blendColor)
|
||||
: _position(position), _blendColor(blendColor) { }
|
||||
|
||||
const Point& GetPosition() const { return _position; }
|
||||
const Rgb8& GetBlendColor() const { return _blendColor; }
|
||||
|
||||
private:
|
||||
Point _position;
|
||||
Rgb8 _blendColor;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
/// @brief The Pico Loader API version supported by this header file.
|
||||
#define PICO_LOADER_API_VERSION 2
|
||||
#define PICO_LOADER_API_VERSION 3
|
||||
|
||||
/// @brief Enum to specify the drive to boot from.
|
||||
typedef enum
|
||||
@@ -43,6 +43,46 @@ typedef struct
|
||||
char launcherPath[256];
|
||||
} pload_header7_v2_t;
|
||||
|
||||
/// @brief Struct representing a single Action Replay cheat opcode.
|
||||
typedef struct
|
||||
{
|
||||
/// @brief The first part of the opcode.
|
||||
u32 a;
|
||||
|
||||
/// @brief The second part of the opcode.
|
||||
u32 b;
|
||||
} pload_cheat_opcode_t;
|
||||
|
||||
/// @brief Struct representing a single Action Replay cheat.
|
||||
typedef struct
|
||||
{
|
||||
/// @brief Length of \see opcodes in bytes.
|
||||
u32 length;
|
||||
|
||||
/// @brief The cheat opcodes.
|
||||
pload_cheat_opcode_t opcodes[1];
|
||||
} pload_cheat_t;
|
||||
|
||||
/// @brief Struct representing one or more cheats. Cheats are adjacent starting from the firstCheat field.
|
||||
typedef struct
|
||||
{
|
||||
/// @brief Length of this stucture (length field + numberOfCheats field + all cheats).
|
||||
u32 length;
|
||||
|
||||
/// @brief The number of cheats.
|
||||
u32 numberOfCheats;
|
||||
|
||||
/// @brief The first cheat.
|
||||
pload_cheat_t firstCheat;
|
||||
} pload_cheats_t;
|
||||
|
||||
/// @brief Struct representing the API version 3 part of the header of picoLoader7.bin.
|
||||
typedef struct
|
||||
{
|
||||
/// @brief Pointer to the cheats, or \c nullptr when there are no cheats.
|
||||
const pload_cheats_t* cheats;
|
||||
} pload_header7_v3_t;
|
||||
|
||||
/// @brief Struct representing the header of picoLoader7.bin.
|
||||
typedef struct
|
||||
{
|
||||
@@ -63,4 +103,7 @@ typedef struct
|
||||
|
||||
/// @brief The API version 2 part of the header. Only access this when \see apiVersion >= 2.
|
||||
pload_header7_v2_t v2;
|
||||
|
||||
/// @brief The API version 3 part of the header. Only access this when \see apiVersion >= 3.
|
||||
pload_header7_v3_t v3;
|
||||
} pload_header7_t;
|
||||
|
||||
15
docs/Cheats.md
Normal file
15
docs/Cheats.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Cheats
|
||||
Pico Launcher supports cheats from a `usrcheat.dat` file placed at `/_pico/usrcheat.dat`. The file stores the cheat codes, as well as which cheats are enabled. When a game is started which has cheats enabled, the enabled cheats are passed to Pico Loader.
|
||||
|
||||
## Usage
|
||||
To display the available cheats highlight a game in the rom browser and press Y to access the cheats panel.
|
||||
|
||||

|
||||
|
||||
### Controls
|
||||
- DPAD up/down: Scroll through the list of cheats.
|
||||
- L and R: Scroll quickly when there are many cheats.
|
||||
- A: Toggle a cheat on/off, or go into a cheat category.
|
||||
- B: Go up in the cheat hierarchy, or close the cheats panel when at the root.
|
||||
- Y: Close the cheats panel.
|
||||
- X: Disable all cheats.
|
||||
@@ -53,5 +53,75 @@ These files can be created, for example, using [NitroPaint](https://github.com/G
|
||||
|
||||
The top screen background should include a box in which the banner text and icon of the selected item will be shown.
|
||||
|
||||
### Additional JSON properties
|
||||
Custom themes support additional properties in the `theme.json` file to allow for more customization.
|
||||
|
||||
- **topIcon** - Properties of the icon displayed on the top screen.
|
||||
- **topBannerTextLine0** - Properties of the first banner text line displayed on the top screen.
|
||||
- **topBannerTextLine1** - Properties of the second banner text line displayed on the top screen.
|
||||
- **topBannerTextLine2** - Properties of the third banner text line displayed on the top screen.
|
||||
- **topFileNameText** - Properties of the file name text displayed on the top screen.
|
||||
- **gridIcon** - Properties of the icons displayed on the bottom screen in grid display modes.
|
||||
- **bannerListIcon** - Properties of the icons displayed on the bottom screen in banner list display mode.
|
||||
- **bannerListTextLine0** - Properties of the first banner text line displayed on the bottom screen in banner list display mode.
|
||||
- **bannerListTextLine1** - Properties of the second banner text line displayed on the bottom screen in banner list display mode.
|
||||
- **bannerListTextLine2** - Properties of the third banner text line displayed on the bottom screen in banner list display mode.
|
||||
|
||||
Blend colors are used to fake translucency. They should be set to an approximation of the background color.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "custom",
|
||||
"name": "Raspberry",
|
||||
"description": "Theme based on raspberries.",
|
||||
"author": "Gericom",
|
||||
"primaryColor": { "r": 138, "g": 217, "b": 255 },
|
||||
"darkTheme": false,
|
||||
"topIcon": {
|
||||
"position": { "x": 24, "y": 132 },
|
||||
"blendColor": { "r": 200, "g": 200, "b": 200 }
|
||||
},
|
||||
"topBannerTextLine0": {
|
||||
"position": { "x": 70, "y": 126 },
|
||||
"width": 176,
|
||||
"textColor": { "r": 30, "g": 30, "b": 30 },
|
||||
"blendColor": { "r": 200, "g": 200, "b": 200 }
|
||||
},
|
||||
"topBannerTextLine1": {
|
||||
"position": { "x": 70, "y": 141 },
|
||||
"width": 176,
|
||||
"textColor": { "r": 30, "g": 30, "b": 30 },
|
||||
"blendColor": { "r": 200, "g": 200, "b": 200 }
|
||||
},
|
||||
"topBannerTextLine2": {
|
||||
"position": { "x": 70, "y": 155 },
|
||||
"width": 176,
|
||||
"textColor": { "r": 30, "g": 30, "b": 30 },
|
||||
"blendColor": { "r": 200, "g": 200, "b": 200 }
|
||||
},
|
||||
"topFileNameText": {
|
||||
"position": { "x": 18, "y": 170 },
|
||||
"width": 220,
|
||||
"textColor": { "r": 30, "g": 30, "b": 30 },
|
||||
"blendColor": { "r": 200, "g": 200, "b": 200 }
|
||||
},
|
||||
"gridIcon": {
|
||||
"blendColor": { "r": 200, "g": 200, "b": 200 }
|
||||
},
|
||||
"bannerListIcon": {
|
||||
"blendColor": { "r": 200, "g": 200, "b": 200 }
|
||||
},
|
||||
"bannerListTextLine0": {
|
||||
"textColor": { "r": 30, "g": 30, "b": 30 }
|
||||
},
|
||||
"bannerListTextLine1": {
|
||||
"textColor": { "r": 30, "g": 30, "b": 30 }
|
||||
},
|
||||
"bannerListTextLine2": {
|
||||
"textColor": { "r": 30, "g": 30, "b": 30 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Background music
|
||||
All themes support background music by placing DSP-ADPCM encoded `.bcstm` files in a `bgm` folder inside the theme folder. Looping is supported. When multiple `.bcstm` files are provided, the background music will be selected at random each time Pico Launcher is started.
|
||||
|
||||
@@ -12,6 +12,7 @@ From here you can browse your SD card to launch homebrew and games.
|
||||
- A: Open a folder, or to launch a homebrew or game.
|
||||
- B: Go to the parent folder or close a menu.
|
||||
- L and R: Scroll quickly when there are many items in a folder.
|
||||
- Y: Open the cheats panel (see [Cheats](Cheats.md)).
|
||||
|
||||
The back arrow on the top left of the bottom screen can also be used to go up to the parent folder.
|
||||
|
||||
@@ -46,4 +47,4 @@ Settings are stored on your SD card in `/_pico/settings.json`. They can be edite
|
||||
- `romBrowserSortMode` - Specified if folder contents should be sorted from A to Z (`NameAscending`), or from Z to A (`NameDescending`). This setting can be changed from within Pico Launcher.
|
||||
- `theme`: Specifies the folder name of the theme to use. If the theme cannot be found, a default fallback theme will be used.
|
||||
- `lastUsedFilePath` - Specifies the path of the most recently launched homebrew or game, such that it can be selected the next time Pico Launcher is started. It is automatically updated by Pico Launcher.
|
||||
- `fileAssociations` - See [FileAssociations.md](/LAUNCHER/docs/FileAssociations.md) for information about how to use this setting.
|
||||
- `fileAssociations` - See [FileAssociations.md](/docs/FileAssociations.md) for information about how to use this setting.
|
||||
BIN
docs/images/Cheats.png
Normal file
BIN
docs/images/Cheats.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Reference in New Issue
Block a user