37 Commits

Author SHA1 Message Date
Gericom
b087565651 Update changelog 2026-04-18 19:12:03 +02:00
Gericom
281a781c13 Fix BlocksDS link in readme. Fixes #41 2026-04-18 13:28:29 +02:00
Gericom
5569c005a5 Add define for cheat up button position 2026-04-18 13:23:33 +02:00
Gericom
9f9f0143a2 Add ability to move to and from the up button in the cheats panel with the dpad, fix oopsie with cheat category name text, update Usage.md 2026-04-18 13:15:25 +02:00
Gericom
97762b14d3 Add touch input support, add fast scrolling support for coverflow display mode, fix use after free bug in banner list mode 2026-04-18 12:20:57 +02:00
Gericom
21a8790ebc Add new shared pointer and make use of it 2026-04-06 12:08:00 +02:00
Gericom
bec797ffe7 Add ability to set the position of the top screen cover image in custom themes 2026-04-04 10:01:15 +02:00
Gericom
127de36b1c Update changelog 2026-03-29 17:03:39 +02:00
Gericom
5442c02341 Add changelog 2026-03-29 16:20:27 +02:00
Gericom
cf9ce63db5 Add various extra customization options for custom themes. Fixes #40 2026-03-29 14:48:12 +02:00
Gericom
53727e5fdd Avoid having a single frame where the icon was not displayed on the top screen after selecting a different rom 2026-03-29 12:21:20 +02:00
Gericom
9ca3e38668 Enable marquee for file names on the top screen. Fixes #22 2026-03-29 11:56:13 +02:00
Gericom
3f780fdd69 Improve error handling for parsing banners. Fixes #18 2026-03-29 11:47:08 +02:00
Gericom
7c06abf224 Hide files/dirs with hidden attribute and files/dirs starting with a period. Fixes #13, fixes #23 2026-03-29 09:55:45 +02:00
Gericom
2c142caa98 Enable debug information 2026-03-29 09:41:51 +02:00
Gericom
b7d7f9f352 Change cheat implementation to show cheats in database order, fix some bugs
- AdvancedPaletteManager incorrectly handled negative y positions
- FocusManager still had a pointer to a view that was destroyed in the cheats panel. After changing focus, memory got corrupted.
2026-03-15 13:28:59 +01:00
Gericom
601fd6371e Do not attempt to draw NdsFileIcon when the vram address is not yet set 2026-03-15 10:22:33 +01:00
Gericom
a4ecea6802 Fix some bugs in the cheats panel 2026-03-14 10:55:10 +01:00
Gericom
6c34d9324d Add cheat documentation, enable input repeat for L and R, show cheat category name 2026-03-08 13:03:26 +01:00
Gericom
43b1bf7afa Fix vram corruption when using the cheat panel in vertical grid mode
The graphics of an animated nds icon are now not all uploaded to vram at the same time.
There are now 1024 bytes available for each icon (previously 4096).
Double buffering is used to upload the new icon frame every time it changes.
2026-03-08 11:32:48 +01:00
Gericom
12ebd482d4 Add option to disable all cheats by pressing X in the cheat panel 2026-03-07 14:51:58 +01:00
Gericom
4d9318b0b9 Fix label marquee speed, add cheat description label 2026-03-01 17:12:56 +01:00
Gericom
10431c4615 Remember selected index when returning from cheat category 2026-03-01 16:24:05 +01:00
Gericom
e2e42115e7 Add marquee for long cheat names 2026-03-01 16:18:06 +01:00
Gericom
a9425eea7c Add cheats not found label 2026-03-01 13:43:31 +01:00
Gericom
7f35d524ae Fix a few bugs related to the cheat panel 2026-02-28 17:32:40 +01:00
Gericom
126b898ac4 Run CI on all branches 2026-02-28 17:10:57 +01:00
Gericom
f54a379ff2 Further work on support for cheats
Cheats can now be enabled/disabled and games can be launched with cheats
2026-02-28 17:00:02 +01:00
Gericom
dddee0bb94 Initial work on implementing support for cheats 2026-02-22 20:28:35 +01:00
Gericom
f73c8b0547 Merge pull request #21 from oakwoodwolf/develop
Update Usage.md
2026-02-08 16:35:52 +01:00
Roy Flaherty
a102068433 Update Usage.md 2026-02-03 21:42:24 +00:00
Gericom
d76d46ea73 Added support for Pico Loader API v2. This makes it possible to return to Pico Launcher from homebrew. 2026-01-10 17:08:06 +01:00
Gericom
e4c2fafa74 Updated to latest blocksds (Fixes #14) 2026-01-04 11:00:33 +01:00
Gericom
e2a8a540e9 Merge pull request #12 from lifehackerhansol/workflow-release
workflow: add release pipeline
2025-12-04 13:41:05 +01:00
lifehackerhansol
17d8e3b4f8 workflow: add release pipeline
- Minor change in push pipeline to remove spaces from artifact name
- Create release pipeline with the same steps that will upload a zipped
  package to every published release automatically
2025-12-03 21:09:03 -08:00
Gericom
9863dbf631 Merge pull request #10 from lifehackerhansol/workflow-remove-dotnet-env
workflow: remove unnecessary dotnet environment variables
2025-11-29 14:41:52 +01:00
lifehackerhansol
c77bf774d4 workflow: remove unnecessary dotnet environment variables
- This is not needed in pico-launcher.
2025-11-29 04:43:35 -08:00
201 changed files with 5809 additions and 1340 deletions

View File

@@ -2,7 +2,6 @@ name: Build Pico Launcher
on:
push:
branches: ["develop"]
paths-ignore:
- 'README.md'
pull_request:
@@ -14,12 +13,8 @@ on:
jobs:
pico_launcher:
runs-on: ubuntu-latest
container: skylyrac/blocksds:slim-v1.13.1
container: skylyrac/blocksds:slim-v1.16.0
name: Build Pico Launcher
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
steps:
- name: Checkout repo
uses: actions/checkout@v4
@@ -34,4 +29,4 @@ jobs:
path: |
_pico/
LAUNCHER.nds
name: Pico Launcher
name: Pico_Launcher

39
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Build Pico Launcher release
on:
release:
types: [published]
jobs:
pico_launcher:
runs-on: ubuntu-latest
container: skylyrac/blocksds:slim-v1.16.0
name: Build Pico Launcher
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
submodules: true
- name: Install zip
run:
apt-get update && apt-get -y install zip
- name: Run build script
run: |
make
- name: Publish build to GH Actions
uses: actions/upload-artifact@v4
with:
path: |
_pico/
LAUNCHER.nds
name: Pico_Launcher
- name: Package for release
run: |
mkdir Pico_Launcher
cp -r _pico/ LAUNCHER.nds Pico_Launcher
cd Pico_Launcher && zip -r $PWD.zip *
- name: Release
uses: softprops/action-gh-release@v2
with:
files: |
Pico_Launcher.zip

37
CHANGELOG.md Normal file
View File

@@ -0,0 +1,37 @@
# Changelog
## [Unreleased]
## [v1.3.0] - 18 Apr 2026
### Added
- Ability to set the position of the top screen cover image in custom themes
- Support for fast scrolling with the L and R buttons in coverflow display mode
- Support for touch input
### Fixed
- Use after free bug with the texture load request in Label3DView. This occurred for example when spamming B in banner list mode.
## [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

View File

@@ -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 \

View File

@@ -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).
@@ -22,7 +23,7 @@ General usage documentation can be found here: [Usage](docs/Usage.md).
We recommend using WSL (Windows Subsystem for Linux), or MSYS2 to compile this repository.
The steps provided will assume you already have one of those environments set up.
1. Install [BlocksDS](https://blocksds.skylyrac.net/docs/setup/options/)
1. Install [BlocksDS](https://blocksds.skylyrac.net/docs/setup/)
## Compiling

View File

@@ -8,5 +8,125 @@
"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
}
},
"topCover": {
"position": {
"x": 75,
"y": 18
}
},
"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
}
}
}

View File

@@ -30,6 +30,7 @@
#include "ExitMode.h"
#include "Arm7State.h"
#include "mmc/tmio.h"
#include "touchScreen.h"
static NocashOutputStream sNocashOutputStream;
static PlainLogger sPlainLogger = PlainLogger(LogLevel::All, std::unique_ptr<IOutputStream>(&sNocashOutputStream));
@@ -42,19 +43,14 @@ static RtcIpcService sRtcIpcService;
ILogger* gLogger = &sThreadSafeLogger;
static rtos_event_t sVBlankEvent;
static rtos_event_t sVCountEvent;
static ExitMode sExitMode;
static Arm7State sState;
static volatile u8 sMcuIrqFlag = false;
static void vblankIrq(u32 irqMask)
{
rtos_signalEvent(&sVBlankEvent);
}
static void vcountIrq(u32 irqMask)
{
SHARED_KEY_XY = REG_RCNT0_H;
rtos_signalEvent(&sVCountEvent);
}
static void mcuIrq(u32 irq2Mask)
@@ -88,12 +84,13 @@ static void checkMcuIrq(void)
}
}
static void initializeVBlankIrq()
static void initializeVCountIrq()
{
rtos_createEvent(&sVBlankEvent);
rtos_setIrqFunc(RTOS_IRQ_VBLANK, vblankIrq);
rtos_enableIrqMask(RTOS_IRQ_VBLANK);
gfx_setVBlankIrqEnabled(true);
rtos_createEvent(&sVCountEvent);
gfx_setVCountMatchLine(96);
rtos_setIrqFunc(RTOS_IRQ_VCOUNT, vcountIrq);
rtos_enableIrqMask(RTOS_IRQ_VCOUNT);
gfx_setVCountMatchIrqEnabled(true);
}
static void clearSoundRegisters()
@@ -145,12 +142,7 @@ static void initializeArm7()
sSoundIpcService.Start();
sRtcIpcService.Start();
gfx_setVCountMatchLine(96);
rtos_setIrqFunc(RTOS_IRQ_VCOUNT, vcountIrq);
rtos_enableIrqMask(RTOS_IRQ_VCOUNT);
gfx_setVCountMatchIrqEnabled(true);
initializeVBlankIrq();
initializeVCountIrq();
if (isDSiMode())
{
@@ -158,6 +150,8 @@ static void initializeArm7()
rtos_enableIrq2Mask(RTOS_IRQ2_MCU);
}
touch_init();
ipc_setArm7SyncBits(7);
}
@@ -233,7 +227,16 @@ int main()
while (true)
{
rtos_waitEvent(&sVBlankEvent, true, true);
rtos_waitEvent(&sVCountEvent, true, true);
u16 keys = REG_RCNT0_H | RCNT0_H_DATA_PEN;
touchPosition touchPos;
if (touch_update(touchPos))
{
keys &= ~RCNT0_H_DATA_PEN; // pen down
SHARED_TOUCH_X = touchPos.px;
SHARED_TOUCH_Y = touchPos.py;
}
SHARED_KEY_XY = keys;
updateArm7();
}

146
arm7/source/touchScreen.cpp Normal file
View File

@@ -0,0 +1,146 @@
#include "common.h"
#include <nds/input.h>
#include <nds/arm7/touch.h>
// See: https://github.com/blocksds/libnds/blob/master/source/arm7/input.c
// === Touchscreen filter configuration ===
// Replace Z1/Z2 values with X/Y noisiness measurements.
// #define TOUCH_DEBUG_NOISINESS
// The number of frames to debounce/hold pen presses for.
// Set to 0 to disable.
#define PEN_DOWN_DEBOUNCE 1
// The shift (1 << N) used for the IIR filter to average noisy samples across
// time. Set to 0 to disable.
#define TOUCH_MAX_NOISE_PEN_UP_IIR_SHIFT 5
// The maximum value of noisiness for pressing a pen down (measurement now valid).
#define TOUCH_MAX_NOISE_PEN_DOWN 38
// The minimum value of noisiness for lifting a pen up (measurement no longer valid).
#define TOUCH_MAX_NOISE_PEN_UP 50
// === Touchscreen filter ===
// IIR filter constants.
#define TOUCH_MAX_NOISE_PEN_UP_IIR_RATIO (1 << TOUCH_MAX_NOISE_PEN_UP_IIR_SHIFT)
#define TOUCH_MAX_NOISE_PEN_UP_IIR_MIN (TOUCH_MAX_NOISE_PEN_UP - TOUCH_MAX_NOISE_PEN_UP_IIR_RATIO)
typedef struct {
u16 value; // 1..4095, 0 if invalid
u16 noisiness; // 0..4095, ~15-16 = 1 pixel
} libnds_touchMeasurementFilterResult;
/**
* @brief Perform filtering on the raw touch samples provided to return one
* averaged sample and an estimate of how noisy it is, while skipping outliers.
*
* Internal. See touchFilter.c for more information.
*/
extern "C" libnds_touchMeasurementFilterResult libnds_touchMeasurementFilter(u16 values[5]);
void touch_init()
{
touchInit();
}
bool touch_update(touchPosition& touchPos)
{
#if PEN_DOWN_DEBOUNCE > 0
static touchPosition lastTouchPosition;
static bool lastPenDown = false;
static u8 penDownDebounce = 0;
#else
touchPosition lastTouchPosition;
bool lastPenDown = false;
#endif
bool penDown = touchPenDown();
if (penDown)
{
// Set penDown to false for later fallthroughs to noPenDown.
// It will be set to true if all the touch filtering ensures the readout is valid.
penDown = false;
// Measure new touch position.
touchRawArray data;
if (!touchReadData(&data))
goto noPenDown;
libnds_touchMeasurementFilterResult rawXresult = libnds_touchMeasurementFilter(data.rawX);
if (!rawXresult.value)
goto noPenDown;
libnds_touchMeasurementFilterResult rawYresult = libnds_touchMeasurementFilter(data.rawY);
if (!rawYresult.value)
goto noPenDown;
// Valid sample read.
u16 noisiness = rawXresult.noisiness > rawYresult.noisiness ? rawXresult.noisiness : rawYresult.noisiness;
if (noisiness <= (lastPenDown ? TOUCH_MAX_NOISE_PEN_UP : TOUCH_MAX_NOISE_PEN_DOWN))
{
lastTouchPosition.z1 = libnds_touchMeasurementFilter(data.z1).value;
lastTouchPosition.z2 = libnds_touchMeasurementFilter(data.z2).value;
#if TOUCH_MAX_NOISE_PEN_UP_IIR_SHIFT > 0
// Apply an IIR filter on noisy X/Y samples.
// Skip the IIR filter if the pen was just pressed.
int n = (noisiness - TOUCH_MAX_NOISE_PEN_UP_IIR_MIN);
if (noisiness <= 0 || !lastPenDown)
{
lastTouchPosition.rawx = rawXresult.value;
lastTouchPosition.rawy = rawYresult.value;
}
else if (noisiness <= TOUCH_MAX_NOISE_PEN_UP_IIR_RATIO)
{
lastTouchPosition.rawx =
((rawXresult.value * (TOUCH_MAX_NOISE_PEN_UP_IIR_RATIO - n))
+ (lastTouchPosition.rawx * n)) >> TOUCH_MAX_NOISE_PEN_UP_IIR_SHIFT;
lastTouchPosition.rawy =
((rawYresult.value * (TOUCH_MAX_NOISE_PEN_UP_IIR_RATIO - n))
+ (lastTouchPosition.rawy * n)) >> TOUCH_MAX_NOISE_PEN_UP_IIR_SHIFT;
}
#else
lastTouchPosition.rawx = rawXresult.value;
lastTouchPosition.rawy = rawYresult.value;
#endif
touchApplyCalibration(lastTouchPosition.rawx, lastTouchPosition.rawy, &lastTouchPosition.px, &lastTouchPosition.py);
penDown = true;
}
#ifdef TOUCH_DEBUG_NOISINESS
lastTouchPosition.z1 = rawXresult.noisiness;
lastTouchPosition.z2 = rawYresult.noisiness;
#endif
}
noPenDown:
#if PEN_DOWN_DEBOUNCE > 0
// Perform simple debouncing.
// Hold new presses for PEN_DOWN_DEBOUNCE frames.
if (!penDownDebounce)
{
if (lastPenDown != penDown)
{
lastPenDown = penDown;
if (penDown)
penDownDebounce = PEN_DOWN_DEBOUNCE;
}
}
else
{
penDownDebounce--;
}
#else
lastPenDown = penDown;
#endif
// Return the touch position and pen down.
if (lastPenDown)
{
touchPos = lastTouchPosition;
}
return lastPenDown;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <nds/touch.h>
void touch_init();
bool touch_update(touchPosition& touchPos);

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/cheatSelector.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

7
arm9/gfx/folderIcon.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/folderIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

7
arm9/gfx/upIcon.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/upIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

View File

@@ -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"
@@ -42,9 +43,9 @@ App::App(IAppSettingsService& appSettingsService, IBgmService& bgmService)
, _subVramContext(nullptr, &_subObjVram, nullptr, nullptr)
, _appSettingsService(appSettingsService)
, _bgmService(bgmService)
, _inputProvider(&_inputSource)
, _inputProvider(&_keyInputSource, &_touchInputSource)
, _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)
@@ -133,7 +134,7 @@ void App::Run()
StoreVramState(_vramStateBeforeMakeBottomScreenView);
_romBrowserBottomScreenView = std::make_unique<RomBrowserBottomScreenView>(
_romBrowserBottomScreenView = RomBrowserBottomScreenView::CreateShared(
&_romBrowserBottomScreenViewModel,
RomBrowserDisplayModeFactory().GetRomBrowserDisplayMode(
_romBrowserController.GetRomBrowserDisplaySettings().layout),
@@ -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 = SharedPtr<CheatsViewModel>::MakeShared(_romBrowserController.GetTriggerFileInfo(), &_romBrowserController);
auto cheatsDialog = CheatsBottomSheetView::CreateShared(
std::move(cheatsViewModel), &_theme->GetMaterialColorScheme(), _theme->GetFontRepository(), &_focusManager);
_dialogPresenter.ShowDialog(std::move(cheatsDialog));
}
void App::HandleHideGameInfoTrigger()
@@ -310,7 +316,7 @@ void App::HandleHideGameInfoTrigger()
void App::HandleShowDisplaySettingsTrigger()
{
auto displaySettingsDialog = std::make_unique<DisplaySettingsBottomSheetView>(
auto displaySettingsDialog = DisplaySettingsBottomSheetView::CreateShared(
&_displaySettingsBottomSheetViewModel, &_theme->GetMaterialColorScheme(), _theme->GetFontRepository());
displaySettingsDialog->SetGraphics(_iconButtonViewVram);
_dialogPresenter.ShowDialog(std::move(displaySettingsDialog));
@@ -331,11 +337,11 @@ void App::HandleNavigateTrigger()
void App::HandleFolderLoadDoneTrigger()
{
_romBrowserTopScreenView.reset();
_romBrowserTopScreenView.Reset();
RestoreVramState(_vramStateAfterMakeBottomScreenView);
auto displayMode = RomBrowserDisplayModeFactory().GetRomBrowserDisplayMode(
_romBrowserController.GetRomBrowserDisplaySettings().layout);
_romBrowserTopScreenView = std::make_unique<RomBrowserTopScreenView>(
_romBrowserTopScreenView = RomBrowserTopScreenView::CreateShared(
_romBrowserController.GetRomBrowserViewModel(),
displayMode,
_materialThemeFileIconFactory.get(),
@@ -352,7 +358,7 @@ void App::HandleChangeDisplayModeTrigger(RomBrowserState newState)
RestoreVramState(_vramStateBeforeMakeBottomScreenView);
auto displayMode = RomBrowserDisplayModeFactory().GetRomBrowserDisplayMode(
_romBrowserController.GetRomBrowserDisplaySettings().layout);
_romBrowserBottomScreenView = std::make_unique<RomBrowserBottomScreenView>(
_romBrowserBottomScreenView = RomBrowserBottomScreenView::CreateShared(
&_romBrowserBottomScreenViewModel,
displayMode,
_materialThemeFileIconFactory.get(),
@@ -360,7 +366,7 @@ void App::HandleChangeDisplayModeTrigger(RomBrowserState newState)
&_vblankTextureLoader);
_romBrowserBottomScreenView->InitVram(_mainVramContext);
StoreVramState(_vramStateAfterMakeBottomScreenView);
_romBrowserTopScreenView = std::make_unique<RomBrowserTopScreenView>(
_romBrowserTopScreenView = RomBrowserTopScreenView::CreateShared(
_romBrowserController.GetRomBrowserViewModel(),
displayMode,
_materialThemeFileIconFactory.get(),
@@ -399,7 +405,7 @@ void App::Update()
bool isRomBrowserVisible = IsRomBrowserVisible();
if (isRomBrowserVisible && !_exit && curState != RomBrowserState::Launching)
{
_focusManager.Update(_inputRepeater);
HandleInput();
}
if (_topBackground)
@@ -517,3 +523,49 @@ void App::RestoreVramState(const VramState& vramState)
_texturePaletteVram.SetState(vramState._texPlttVramState);
_subObjVram.SetState(vramState._subObjVramState);
}
void App::HandleInput()
{
_focusManager.Update(_inputRepeater);
Point touchPoint;
if (_inputRepeater.Triggered(InputKey::Touch) &&
_inputRepeater.GetCurrentTouchPoint(touchPoint))
{
// pen down
if (_dialogPresenter.IsBottomSheetVisible())
{
_dialogPresenter.HandlePenDown(touchPoint, _focusManager);
}
else
{
_romBrowserBottomScreenView->HandlePenDown(touchPoint, _focusManager);
}
_lastTouchPoint = touchPoint;
}
else if (_inputRepeater.Released(InputKey::Touch))
{
// pen up
if (_dialogPresenter.IsBottomSheetVisible())
{
_dialogPresenter.HandlePenUp(_lastTouchPoint, _focusManager);
}
else
{
_romBrowserBottomScreenView->HandlePenUp(_lastTouchPoint, _focusManager);
}
}
else if (_inputRepeater.Current(InputKey::Touch)
&& _inputRepeater.GetCurrentTouchPoint(touchPoint))
{
// pen move
if (_dialogPresenter.IsBottomSheetVisible())
{
_dialogPresenter.HandlePenMove(touchPoint, _focusManager);
}
else
{
_romBrowserBottomScreenView->HandlePenMove(touchPoint, _focusManager);
}
_lastTouchPoint = touchPoint;
}
}

View File

@@ -12,6 +12,7 @@
#include "gui/DescendingStackVramManager.h"
#include "material/scheme/scheme.h"
#include "gui/input/PadInputSource.h"
#include "gui/input/TouchInputSource.h"
#include "gui/input/SampledInputProvider.h"
#include "gui/input/InputRepeater.h"
#include "gui/VBlankTextureLoader.h"
@@ -28,7 +29,6 @@
#include "romBrowser/RomBrowserController.h"
#include "DialogPresenter.h"
#include "themes/ITheme.h"
#include "core/SharedPtr.h"
#include "animation/Animator.h"
class alignas(32) App : public IProcess
@@ -63,9 +63,9 @@ private:
Rgb6Palette _rgb6Palette;
Animator<int> _fadeAnimator;
TaskQueue<32, 32> _ioTaskQueue;
TaskQueue<32, sizeof(TaskBase) + 32> _ioTaskQueue;
u32 _ioTaskThreadStack[2048 / 4];
TaskQueue<32, 32> _bgTaskQueue;
TaskQueue<32, sizeof(TaskBase) + 32> _bgTaskQueue;
u32 _bgTaskThreadStack[2048 / 4];
std::unique_ptr<ITheme> _theme;
@@ -76,12 +76,13 @@ private:
IBgmService& _bgmService;
volatile bool _exit = false;
PadInputSource _inputSource;
PadInputSource _keyInputSource;
TouchInputSource _touchInputSource;
SampledInputProvider _inputProvider;
InputRepeater _inputRepeater;
std::unique_ptr<RomBrowserBottomScreenView> _romBrowserBottomScreenView;
std::unique_ptr<RomBrowserTopScreenView> _romBrowserTopScreenView;
SharedPtr<RomBrowserBottomScreenView> _romBrowserBottomScreenView;
SharedPtr<RomBrowserTopScreenView> _romBrowserTopScreenView;
RomBrowserController _romBrowserController;
@@ -104,10 +105,13 @@ private:
bool _vcountIrqStarted = false;
Point _lastTouchPoint = Point(0, 0);
void InitVramMapping() const;
void DisplaySplashScreen() const;
void LoadTheme();
void VCountIrq();
void HandleInput();
void HandleTrigger(RomBrowserStateTrigger trigger, RomBrowserState newState);
void HandleShowGameInfoTrigger();
void HandleHideGameInfoTrigger();

View File

@@ -16,10 +16,12 @@ DialogPresenter::DialogPresenter(FocusManager* focusManager, StackVramManager* v
_baseVramState = _vramManager->GetState();
}
void DialogPresenter::ShowDialog(std::unique_ptr<DialogView> dialog)
void DialogPresenter::ShowDialog(SharedPtr<DialogView> dialog)
{
if (!_nextDialog)
{
_nextDialog = std::move(dialog);
}
}
void DialogPresenter::CloseDialog()
@@ -93,7 +95,7 @@ void DialogPresenter::Update()
else
{
_newState = State::Idle;
_currentDialog.reset();
_currentDialog.Reset();
}
break;
}
@@ -153,3 +155,27 @@ void DialogPresenter::InitVram()
REG_BLDCNT = 0x3944;
REG_BLDALPHA = (16 << 8) | 0;
}
void DialogPresenter::HandlePenDown(const Point& touchPoint, FocusManager& focusManager)
{
if (_curState == State::BottomSheetVisible && _currentDialog)
{
_currentDialog->HandlePenDown(touchPoint, focusManager);
}
}
void DialogPresenter::HandlePenMove(const Point& touchPoint, FocusManager& focusManager)
{
if (_curState == State::BottomSheetVisible && _currentDialog)
{
_currentDialog->HandlePenMove(touchPoint, focusManager);
}
}
void DialogPresenter::HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager)
{
if (_curState == State::BottomSheetVisible && _currentDialog)
{
_currentDialog->HandlePenUp(lastTouchPoint, focusManager);
}
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <memory>
#include "core/SharedPtr.h"
#include "animation/Animator.h"
#include "gui/views/DialogView.h"
@@ -14,7 +15,7 @@ public:
/// @brief Requests to show the given dialog.
/// @param dialog The dialog to show.
void ShowDialog(std::unique_ptr<DialogView> dialog);
void ShowDialog(SharedPtr<DialogView> dialog);
/// @brief Closes the current dialog.
void CloseDialog();
@@ -41,6 +42,21 @@ public:
/// @brief Initializes vram that is needed for showing dialogs.
void InitVram();
/// @brief Handles a pen down event.
/// @param touchPoint The touch point.
/// @param focusManager The focus manager.
void HandlePenDown(const Point& touchPoint, FocusManager& focusManager);
/// @brief Handles a pen move event.
/// @param touchPoint The touch point.
/// @param focusManager The focus manager.
void HandlePenMove(const Point& touchPoint, FocusManager& focusManager);
/// @brief Handles a pen up event.
/// @param lastTouchPoint The last touch point.
/// @param focusManager The focus manager.
void HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager);
/// @brief Clears the focus that was stored when a dialog was opened.
void ClearOldFocus()
{
@@ -49,11 +65,16 @@ public:
/// @brief Gets the focus that was stored when a dialog was opened.
/// @return The view that was focused when the current dialog was opened.
constexpr View* GetOldFocus() const
constexpr SharedPtr<View> GetOldFocus() const
{
return _oldFocus;
}
bool IsBottomSheetVisible() const
{
return _curState != State::Idle;
}
private:
enum class State
{
@@ -65,10 +86,10 @@ private:
FocusManager* _focusManager;
StackVramManager* _vramManager;
u32 _baseVramState;
std::unique_ptr<DialogView> _currentDialog;
std::unique_ptr<DialogView> _nextDialog;
SharedPtr<DialogView> _currentDialog;
SharedPtr<DialogView> _nextDialog;
bool _initVram = false;
View* _oldFocus = nullptr;
SharedPtr<View> _oldFocus = nullptr;
Animator<int> _scrimAnimator;
Animator<int> _yAnimator;
State _curState = State::Idle;

View 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;
};
};
};

View 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
}
};

View 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;
};

View 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() { }
};

View 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;
}
}
}

View 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;
};

View 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);

View 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);
}

View 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;
};

View 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);
}

View 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);
};

View File

@@ -0,0 +1,35 @@
#include "common.h"
#include <libtwl/rtos/rtosIrq.h>
#include "AtomicSharedPtr.h"
void AtomicSharedPtrBase::Reset(void* newObject, RefCount* newRefCount, bool increaseNewRefCount)
{
u32 irq = rtos_disableIrqs(); // 1
auto refCount = _refCount;
_object = newObject;
_refCount = newRefCount;
if (increaseNewRefCount && _refCount)
{
_refCount->refCount++;
}
if (refCount && --refCount->refCount == 0) [[gnu::unlikely]]
{
refCount->weakRefCount++; // ensure the ref count is not destructed elsewhere
rtos_restoreIrqs(irq); // 1
refCount->DestructObject();
irq = rtos_disableIrqs(); // 2
if (--refCount->weakRefCount == 0) [[gnu::unlikely]]
{
rtos_restoreIrqs(irq); // 2
delete refCount;
}
else
{
rtos_restoreIrqs(irq); // 2
}
}
else
{
rtos_restoreIrqs(irq); // 1
}
}

View File

@@ -0,0 +1,134 @@
#pragma once
#include <type_traits>
#include <libtwl/rtos/rtosIrq.h>
#include "SharedPtr.h"
class AtomicSharedPtrBase
{
public:
void Reset()
{
Reset(nullptr, nullptr, false);
}
protected:
void* volatile _object;
RefCount* volatile _refCount;
AtomicSharedPtrBase()
: _object(nullptr), _refCount(nullptr) { }
AtomicSharedPtrBase(void* object, RefCount* refCount)
: _object(object), _refCount(refCount) { }
~AtomicSharedPtrBase()
{
Reset(nullptr, nullptr, false);
}
void Reset(void* newObject, RefCount* newRefCount, bool increaseNewRefCount);
};
template <class T>
class AtomicSharedPtr : public AtomicSharedPtrBase
{
public:
AtomicSharedPtr() { }
AtomicSharedPtr(std::nullptr_t) { }
AtomicSharedPtr(const SharedPtr<T>& sharedPtr)
: AtomicSharedPtrBase(sharedPtr._object, sharedPtr._refCount)
{
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->refCount);
}
}
template <class Y> requires std::assignable_from<T*&, Y*>
AtomicSharedPtr(const SharedPtr<Y>& sharedPtr)
: AtomicSharedPtrBase(static_cast<T*>(sharedPtr.GetPointer()), sharedPtr._refCount)
{
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->refCount);
}
}
template <class Y>
explicit AtomicSharedPtr(const SharedPtr<Y>& sharedPtr)
: AtomicSharedPtrBase(static_cast<T*>(sharedPtr.GetPointer()), sharedPtr._refCount)
{
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->refCount);
}
}
AtomicSharedPtr(SharedPtr<T>&& sharedPtr)
: AtomicSharedPtrBase(sharedPtr._object, sharedPtr._refCount)
{
sharedPtr._object = nullptr;
sharedPtr._refCount = nullptr;
}
template <class Y> requires std::assignable_from<T*&, Y*>
AtomicSharedPtr(SharedPtr<Y>&& sharedPtr)
: AtomicSharedPtrBase(static_cast<T*>(sharedPtr.GetPointer()), sharedPtr._refCount)
{
sharedPtr._object = nullptr;
sharedPtr._refCount = nullptr;
}
template <class Y>
explicit AtomicSharedPtr(SharedPtr<Y>&& sharedPtr)
: AtomicSharedPtrBase(static_cast<T*>(sharedPtr.GetPointer()), sharedPtr._refCount)
{
sharedPtr._object = nullptr;
sharedPtr._refCount = nullptr;
}
AtomicSharedPtr& operator=(const SharedPtr<T>& sharedPtr)
{
Reset(sharedPtr._object, sharedPtr._refCount, true);
return *this;
}
template <class Y> requires std::assignable_from<T*&, Y*>
AtomicSharedPtr<T>& operator=(const SharedPtr<Y>& sharedPtr)
{
Reset(static_cast<T*>(sharedPtr.GetPointer()), sharedPtr._refCount, true);
return *this;
}
AtomicSharedPtr& operator=(SharedPtr<T>&& sharedPtr)
{
Reset(sharedPtr._object, sharedPtr._refCount, false);
sharedPtr._object = nullptr;
sharedPtr._refCount = nullptr;
return *this;
}
template <class Y> requires std::assignable_from<T*&, Y*>
AtomicSharedPtr<T>& operator=(SharedPtr<Y>&& sharedPtr)
{
Reset(static_cast<T*>(sharedPtr.GetPointer()), sharedPtr._refCount, false);
sharedPtr._object = nullptr;
sharedPtr._refCount = nullptr;
return *this;
}
SharedPtr<T> Lock() const
{
u32 irq = rtos_disableIrqs();
auto object = static_cast<T*>(_object);
auto refCount = _refCount;
if (refCount)
{
refCount->refCount++;
}
rtos_restoreIrqs(irq);
return SharedPtr<T>(object, refCount);
}
};

View File

@@ -0,0 +1,42 @@
#pragma once
#include "SharedPtr.h"
#include "WeakPtr.h"
class EnableSharedFromThisBase
{
template <class Y>
friend class SharedPtr;
template <class Y>
friend class EnableSharedFromThis;
private:
EnableSharedFromThisBase() = default;
};
template <class T>
class EnableSharedFromThis : public EnableSharedFromThisBase
{
template <class Y>
friend class SharedPtr;
public:
SharedPtr<T> SharedFromThis()
{
return __sharedFromThisWeakPtr.Lock();
}
WeakPtr<T> WeakFromThis()
{
return __sharedFromThisWeakPtr;
}
private:
WeakPtr<T> __sharedFromThisWeakPtr;
template <class Y>
void __SetSharedFromThisWeakPtr(const SharedPtr<Y>& sharedPtr)
{
__sharedFromThisWeakPtr = WeakPtr<Y>(sharedPtr.GetPointer(), sharedPtr._refCount);
}
};

View File

@@ -0,0 +1,58 @@
#pragma once
#include <array>
extern "C" void shared_ptr_increase_ref_count(vu32& refCount);
class RefCount
{
public:
vu32 refCount;
vu32 weakRefCount;
virtual ~RefCount() = default;
virtual void DestructObject() = 0;
protected:
explicit RefCount()
: refCount(1), weakRefCount(0) { }
};
template <class T>
class StandaloneRefCount : public RefCount
{
public:
explicit StandaloneRefCount(T* object)
: _object(object) { }
void DestructObject() final
{
delete _object;
}
private:
T* _object;
};
template <class T>
class alignas(T) MakeSharedRefCount : public RefCount
{
public:
explicit MakeSharedRefCount(auto&&... args)
{
new (_object.data()) T(std::forward<decltype(args)>(args)...);
}
T* GetObject()
{
return reinterpret_cast<T*>(_object.data());
}
void DestructObject() final
{
reinterpret_cast<T*>(_object.data())->~T();
}
private:
std::array<u8, sizeof(T)> _object alignas(T);
};

View File

@@ -0,0 +1,31 @@
#include "common.h"
#include <libtwl/rtos/rtosIrq.h>
#include "SharedPtr.h"
void SharedPtrBase::ResetIntern()
{
auto refCount = _refCount;
_object = nullptr;
_refCount = nullptr;
u32 irq = rtos_disableIrqs(); // 1
if (--refCount->refCount == 0) [[gnu::unlikely]]
{
refCount->weakRefCount++; // ensure the ref count is not destructed elsewhere
rtos_restoreIrqs(irq); // 1
refCount->DestructObject();
irq = rtos_disableIrqs(); // 2
if (--refCount->weakRefCount == 0) [[gnu::unlikely]]
{
rtos_restoreIrqs(irq); // 2
delete refCount;
}
else
{
rtos_restoreIrqs(irq); // 2
}
}
else
{
rtos_restoreIrqs(irq); // 1
}
}

View File

@@ -1,163 +1,216 @@
#pragma once
#include <type_traits>
#include "RefCount.h"
static inline u32 arm_getCpsr()
{
u32 cpsr;
asm volatile("mrs %0, cpsr" : "=r" (cpsr));
return cpsr;
}
static inline void arm_setCpsrControl(u32 cpsrControl)
{
asm volatile("msr cpsr_c, %0" :: "r" (cpsrControl) : "cc");
}
static inline u32 arm_disableIrqs(void)
{
u32 oldCpsr = arm_getCpsr();
arm_setCpsrControl(oldCpsr | 0x80);
return oldCpsr;
}
static inline void arm_restoreIrqs(u32 oldCpsr)
{
arm_setCpsrControl(oldCpsr);
}
class EnableSharedFromThisBase;
template <class T>
class SharedPtr
class EnableSharedFromThis;
class SharedPtrBase
{
T* _pointer;
vu32* _refCount;
public:
SharedPtr()
: _pointer(nullptr), _refCount(nullptr) { (void)sizeof(T); }
explicit SharedPtr(T* pointer)
: _pointer(pointer), _refCount(pointer ? new u32(1) : nullptr) { (void)sizeof(T); }
SharedPtr(const SharedPtr& other)
void Reset()
{
u32 irq = arm_disableIrqs();
_pointer = other._pointer;
_refCount = other._refCount;
if (_pointer)
if (_refCount != nullptr)
{
(*_refCount)++;
ResetIntern();
}
arm_restoreIrqs(irq);
}
SharedPtr(SharedPtr&& other)
: _pointer(other._pointer), _refCount(other._refCount)
{
other._pointer = nullptr;
other._refCount = nullptr;
}
protected:
void* _object;
RefCount* _refCount;
~SharedPtr()
SharedPtrBase()
: _object(nullptr), _refCount(nullptr) { }
SharedPtrBase(void* object, RefCount* refCount)
: _object(object), _refCount(refCount) { }
SharedPtrBase(const void* object, RefCount* refCount)
: _object((void*)object), _refCount(refCount) { }
~SharedPtrBase()
{
Reset();
}
[[gnu::noinline]]
void ResetIntern();
};
template <class T>
class SharedPtr : public SharedPtrBase
{
template <class Y> friend class WeakPtr;
template <class Y> friend class SharedPtr;
template <class Y> friend class AtomicSharedPtr;
template <class Y> friend class EnableSharedFromThis;
public:
SharedPtr() { }
SharedPtr(std::nullptr_t) { }
explicit SharedPtr(T* object)
: SharedPtrBase(object, object == nullptr ? nullptr : new StandaloneRefCount<T>(object))
{
if (_object != nullptr)
{
if constexpr (std::is_convertible<T*, EnableSharedFromThisBase*>::value)
{
_refCount->weakRefCount = 1;
GetPointer()->__SetSharedFromThisWeakPtr(*this);
}
}
}
template <class Y> requires std::assignable_from<T*&, Y*>
explicit SharedPtr(Y* object)
: SharedPtrBase(static_cast<T*>(object), object == nullptr ? nullptr : new StandaloneRefCount<Y>(object))
{
if (object != nullptr)
{
if constexpr (std::is_convertible<Y*, EnableSharedFromThisBase*>::value)
{
_refCount->weakRefCount = 1;
GetPointer()->__SetSharedFromThisWeakPtr(*this);
}
}
}
SharedPtr(const SharedPtr& other)
: SharedPtrBase(other._object, other._refCount)
{
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->refCount);
}
}
template <class Y> requires std::assignable_from<T*&, Y*>
SharedPtr(const SharedPtr<Y>& other)
: SharedPtrBase(static_cast<T*>(other.GetPointer()), other._refCount)
{
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->refCount);
}
}
template <class Y>
explicit SharedPtr(const SharedPtr<Y>& other)
: SharedPtrBase(static_cast<T*>(other.GetPointer()), other._refCount)
{
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->refCount);
}
}
SharedPtr(SharedPtr&& other)
: SharedPtrBase(other._object, other._refCount)
{
other._object = nullptr;
other._refCount = nullptr;
}
template <class Y> requires std::assignable_from<T*&, Y*>
SharedPtr(SharedPtr<Y>&& other)
: SharedPtrBase(static_cast<T*>(other.GetPointer()), other._refCount)
{
other._object = nullptr;
other._refCount = nullptr;
}
template <class Y>
explicit SharedPtr(SharedPtr<Y>&& other)
: SharedPtrBase(static_cast<T*>(other.GetPointer()), other._refCount)
{
other._object = nullptr;
other._refCount = nullptr;
}
static SharedPtr<T> MakeShared(auto&&... args)
{
auto refCount = new MakeSharedRefCount<T>(std::forward<decltype(args)>(args)...);
return SharedPtr<T>(refCount->GetObject(), refCount, true);
}
SharedPtr& operator=(const SharedPtr& other)
{
u32 irq = arm_disableIrqs();
T* pointer = _pointer;
if (pointer)
Reset();
_object = other._object;
_refCount = other._refCount;
if (_refCount)
{
vu32* refCount = _refCount;
u32 newValue = *refCount - 1;
*refCount = newValue;
_pointer = other._pointer;
_refCount = other._refCount;
if (_pointer)
{
(*_refCount)++;
}
arm_restoreIrqs(irq);
if (newValue == 0)
{
delete pointer;
delete refCount;
}
}
else
{
_pointer = other._pointer;
_refCount = other._refCount;
if (_pointer)
{
(*_refCount)++;
}
arm_restoreIrqs(irq);
shared_ptr_increase_ref_count(_refCount->refCount);
}
return *this;
}
template <class Y> requires std::assignable_from<T*&, Y*>
SharedPtr<T>& operator=(const SharedPtr<Y>& other)
{
Reset();
_object = static_cast<T*>(other.GetPointer());
_refCount = other._refCount;
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->refCount);
}
return *this;
}
[[gnu::noinline]]
SharedPtr& operator=(SharedPtr&& other)
{
u32 irq = arm_disableIrqs();
T* pointer = _pointer;
if (pointer)
{
vu32* refCount = _refCount;
u32 newValue = *refCount - 1;
*refCount = newValue;
_pointer = other._pointer;
_refCount = other._refCount;
other._pointer = nullptr;
other._refCount = nullptr;
arm_restoreIrqs(irq);
if (newValue == 0)
{
delete pointer;
delete refCount;
}
}
else
{
_pointer = other._pointer;
_refCount = other._refCount;
other._pointer = nullptr;
other._refCount = nullptr;
arm_restoreIrqs(irq);
}
Reset();
_object = other._object;
_refCount = other._refCount;
other._object = nullptr;
other._refCount = nullptr;
return *this;
}
[[gnu::noinline]]
void Reset()
template <class Y> requires std::assignable_from<T*&, Y*>
SharedPtr<T>& operator=(SharedPtr<Y>&& other)
{
u32 irq = arm_disableIrqs();
T* pointer = _pointer;
if (pointer)
{
vu32* refCount = _refCount;
u32 newValue = *refCount - 1;
*refCount = newValue;
_pointer = nullptr;
_refCount = nullptr;
arm_restoreIrqs(irq);
if (newValue == 0)
{
delete pointer;
delete refCount;
}
}
else
{
arm_restoreIrqs(irq);
}
Reset();
_object = static_cast<T*>(other.GetPointer());
_refCount = other._refCount;
other._object = nullptr;
other._refCount = nullptr;
return *this;
}
constexpr T& operator*() const { return *_pointer; }
constexpr T* operator->() const { return _pointer; }
T& operator*() const { return *static_cast<T*>(_object); }
T* operator->() const { return static_cast<T*>(_object); }
T* GetPointer() const { return static_cast<T*>(_object); }
constexpr T* GetPointer() const { return _pointer; }
constexpr u32 GetRefCount() const { return _refCount ? *_refCount : 0; }
constexpr bool IsValid() const { return _pointer; }
bool IsValid() const { return _object != nullptr; }
operator bool() const { return _object != nullptr; }
private:
SharedPtr(T* object, RefCount* refCount)
: SharedPtrBase(object, refCount) { }
SharedPtr(T* object, RefCount* refCount, bool doSharedFromThis)
: SharedPtrBase(object, refCount)
{
if (doSharedFromThis)
{
if constexpr (std::is_convertible<T*, EnableSharedFromThisBase*>::value)
{
_refCount->weakRefCount = 1;
GetPointer()->__SetSharedFromThisWeakPtr(*this);
}
}
}
};
#define SHARED_ONLY(className) \
friend class MakeSharedRefCount<className>; \
public: \
static SharedPtr<className> CreateShared(auto&&... args) \
{ return SharedPtr<className>::MakeShared(std::forward<decltype(args)>(args)...); } \
private:

View File

@@ -0,0 +1,15 @@
.section .itcm
.arm
// r0 = &refCount
.global shared_ptr_increase_ref_count
.type shared_ptr_increase_ref_count, %function
shared_ptr_increase_ref_count:
mrs r2, cpsr
orr r1, r2, #0x80
msr cpsr_c, r1
ldr r12, [r0]
add r12, r12, #1
str r12, [r0]
msr cpsr_c, r2
bx lr

View File

@@ -0,0 +1,35 @@
#include "common.h"
#include <libtwl/rtos/rtosIrq.h>
#include "WeakPtr.h"
void WeakPtrBase::ResetIntern()
{
u32 irq = rtos_disableIrqs();
auto refCount = _refCount;
if (--refCount->weakRefCount == 0)
{
_refCount = nullptr;
rtos_restoreIrqs(irq);
delete refCount;
}
else
{
rtos_restoreIrqs(irq);
}
}
bool WeakPtrBase::LockIntern() const
{
u32 irq = rtos_disableIrqs();
if (_refCount->refCount != 0)
{
_refCount->refCount++;
rtos_restoreIrqs(irq);
return true;
}
else
{
rtos_restoreIrqs(irq);
return false;
}
}

158
arm9/source/core/WeakPtr.h Normal file
View File

@@ -0,0 +1,158 @@
#pragma once
#include <type_traits>
#include "RefCount.h"
#include "SharedPtr.h"
class WeakPtrBase
{
public:
~WeakPtrBase()
{
Reset();
}
void Reset()
{
if (_refCount)
{
Reset();
}
}
protected:
RefCount* _refCount;
WeakPtrBase()
: _refCount(nullptr) { }
explicit WeakPtrBase(RefCount* refCount)
: _refCount(refCount) { }
void ResetIntern();
bool LockIntern() const;
};
template <class T>
class WeakPtr : public WeakPtrBase
{
template <class Y> friend class WeakPtr;
template <class Y> friend class EnableSharedFromThis;
public:
WeakPtr() { }
WeakPtr(std::nullptr_t) { }
WeakPtr(const WeakPtr& other)
: WeakPtrBase(other._refCount), _object(other._object)
{
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->weakRefCount);
}
}
WeakPtr(WeakPtr&& other)
: WeakPtrBase(other._refCount), _object(other._object)
{
other._refCount = nullptr;
other._object = nullptr;
}
template <class Y> requires std::assignable_from<T*&, Y*>
WeakPtr(const SharedPtr<Y>& sharedPtr)
: WeakPtrBase(sharedPtr._refCount), _object(static_cast<T*>(sharedPtr.GetPointer()))
{
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->weakRefCount);
}
}
template <class Y>
explicit WeakPtr(const SharedPtr<Y>& sharedPtr)
: WeakPtrBase(sharedPtr._refCount), _object(static_cast<T*>(sharedPtr.GetPointer()))
{
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->weakRefCount);
}
}
WeakPtr& operator=(const WeakPtr& other)
{
Reset();
_object = other._object;
_refCount = other._refCount;
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->weakRefCount);
}
return *this;
}
template <class Y> requires std::assignable_from<T*&, Y*>
WeakPtr<T>& operator=(const WeakPtr<Y>& other)
{
Reset();
_object = static_cast<T*>(static_cast<Y*>(other._object));
_refCount = other._refCount;
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->weakRefCount);
}
return *this;
}
template <class Y> requires std::assignable_from<T*&, Y*>
WeakPtr<T>& operator=(const SharedPtr<Y>& sharedPtr)
{
Reset();
_object = static_cast<T*>(sharedPtr.GetPointer());
_refCount = sharedPtr._refCount;
if (_refCount)
{
shared_ptr_increase_ref_count(_refCount->weakRefCount);
}
return *this;
}
WeakPtr& operator=(WeakPtr&& other)
{
Reset();
_object = other._object;
_refCount = other._refCount;
other._object = nullptr;
other._refCount = nullptr;
return *this;
}
template <class Y> requires std::assignable_from<T*&, Y*>
WeakPtr<T>& operator=(WeakPtr<Y>&& other)
{
Reset();
_object = static_cast<T*>(static_cast<Y*>(other._object));
_refCount = other._refCount;
other._object = nullptr;
other._refCount = nullptr;
return *this;
}
SharedPtr<T> Lock() const
{
if (_refCount && LockIntern())
{
return SharedPtr<T>(_object, _refCount);
}
else
{
return SharedPtr<T>();
}
}
private:
T* _object;
WeakPtr(T* object, RefCount* refCount)
: WeakPtrBase(refCount), _object(object) { }
};

View File

@@ -75,3 +75,5 @@ public:
return Rgb(r + other.r, g + other.g, b + other.b);
}
};
using Rgb8 = Rgb<8, 8, 8>;

View File

@@ -161,11 +161,27 @@ public:
return FromRawValue(this->_value >> rhs);
}
constexpr fix16 abs() const
constexpr fix16 Abs() const
{
return FromRawValue(std::abs(this->_value));
}
constexpr fix16 Clamp(fix16 min, fix16 max) const
{
if (this->_value < min._value)
{
return min;
}
else if (this->_value > max._value)
{
return max;
}
else
{
return *this;
}
}
template <u32 OtherFractionBits>
constexpr fix32<FractionBits + OtherFractionBits> LongMul(const fix16<OtherFractionBits>& other) const
{
@@ -265,21 +281,45 @@ public:
return this->_value < other._value;
}
template <typename TLhs>
constexpr friend bool operator<(TLhs lhs, const fix32& rhs)
{
return fix32(lhs)._value < rhs._value;
}
constexpr bool operator<=(const fix32& other) const
{
return this->_value <= other._value;
}
template <typename TLhs>
constexpr friend bool operator<=(TLhs lhs, const fix32& rhs)
{
return fix32(lhs)._value <= rhs._value;
}
constexpr bool operator>(const fix32& other) const
{
return this->_value > other._value;
}
template <typename TLhs>
constexpr friend bool operator>(TLhs lhs, const fix32& rhs)
{
return fix32(lhs)._value > rhs._value;
}
constexpr bool operator>=(const fix32& other) const
{
return this->_value >= other._value;
}
template <typename TLhs>
constexpr friend bool operator>=(TLhs lhs, const fix32& rhs)
{
return fix32(lhs)._value >= rhs._value;
}
constexpr fix32 operator-() const
{
return FromRawValue(-this->_value);
@@ -337,6 +377,22 @@ public:
return FromRawValue(std::abs(this->_value));
}
constexpr fix32 Clamp(fix32 min, fix32 max) const
{
if (this->_value < min._value)
{
return min;
}
else if (this->_value > max._value)
{
return max;
}
else
{
return *this;
}
}
template <u32 OtherFractionBits>
constexpr fix64<FractionBits + OtherFractionBits> LongMul(const fix16<OtherFractionBits>& other) const
{
@@ -354,6 +410,11 @@ public:
return fix64<FractionBits>::FromRawValue((s64)this->_value * other);
}
constexpr fix64<FractionBits> LongMul(double other) const
{
return LongMul(fix32(other));
}
template <u32 OtherFractionBits>
constexpr fix32 operator*(const fix16<OtherFractionBits>& other) const
{
@@ -371,6 +432,11 @@ public:
return FromRawValue(this->_value * other);
}
constexpr fix32 operator*(double other) const
{
return fix32(LongMul(fix32(other)));
}
constexpr friend fix32 operator*(int lhs, const fix32& rhs)
{
return FromRawValue(lhs * rhs.GetRawValue());

View File

@@ -1,9 +1,20 @@
#include "common.h"
#include "Task.h"
void TaskBase::Execute()
void TaskBase::RequestCancel()
{
u32 irqs = rtos_disableIrqs();
_cancelRequested = true;
if (_state == TaskState::NotStarted)
{
_state = TaskState::Canceled;
rtos_wakeupQueue(&_threadQueue);
}
rtos_restoreIrqs(irqs);
}
void TaskBase::Execute(u32 irqs)
{
if (_state == TaskState::NotStarted)
{
_state = TaskState::Running;
@@ -12,7 +23,9 @@ void TaskBase::Execute()
SetFinalState(finalState);
}
else
{
rtos_restoreIrqs(irqs);
}
}
void TaskBase::SetFinalState(TaskState finalState)

View File

@@ -3,14 +3,17 @@
#include <memory>
#include <libtwl/rtos/rtosIrq.h>
#include <libtwl/rtos/rtosThread.h>
#include "core/LinkedListLink.h"
#include "TaskResult.h"
class TaskBase
{
public:
LinkedListLink link;
virtual ~TaskBase() { }
void Execute();
void Execute(u32 irqs);
TaskState GetState() const { return _state; }
@@ -24,7 +27,7 @@ public:
bool GetDestroyWhenComplete() const { return _destroyWhenComplete; }
void SetDestroyWhenComplete() { _destroyWhenComplete = true; }
void RequestCancel() { _cancelRequested = true; }
void RequestCancel();
bool IsCancelRequested() const { return _cancelRequested; }
protected:
@@ -65,7 +68,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 +82,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); }
};

View File

@@ -1,23 +1,22 @@
#include "common.h"
#include "TaskQueue.h"
void TaskQueueBase::ThreadMain(TaskBase** queue, u32 queueLength)
void TaskQueueBase::ThreadMain()
{
while (true)
{
_idle = false;
u32 readPtr = _queueReadPtr;
while (readPtr != _queueWritePtr)
while (true)
{
TaskBase* task = queue[readPtr];
if (readPtr == queueLength - 1)
readPtr = 0;
else
readPtr++;
_queueReadPtr = readPtr;
u32 irqs = rtos_disableIrqs();
auto task = _taskList.GetHead();
if (!task)
continue;
task->Execute();
{
rtos_restoreIrqs(irqs);
break;
}
_taskList.Remove(task);
task->Execute(irqs);
if (task->GetDestroyWhenComplete())
{
// this will destroy the task

View File

@@ -4,6 +4,7 @@
#include <libtwl/rtos/rtosEvent.h>
#include <libtwl/rtos/rtosThread.h>
#include "core/BitVector.h"
#include "core/LinkedList.h"
#include "Task.h"
class TaskQueueBase;
@@ -11,25 +12,13 @@ class TaskQueueBase;
class QueueTaskBase
{
public:
// forbid copies
QueueTaskBase(const QueueTaskBase&) = delete;
QueueTaskBase& operator=(const QueueTaskBase&) = delete;
// move assignment
QueueTaskBase& operator=(QueueTaskBase&& other)
{
_taskQueue = other._taskQueue;
_task = other._task;
other._taskQueue = nullptr;
other._task = nullptr;
return *this;
}
~QueueTaskBase()
{
// extra check here for optimizing out the dispose call after move assignment
if (_task)
{
Dispose();
}
}
void Dispose();
@@ -47,12 +36,10 @@ public:
protected:
TaskBase* _task;
TaskQueueBase* _taskQueue;
QueueTaskBase(TaskBase* task, TaskQueueBase* taskQueue)
: _task(task), _taskQueue(taskQueue) { }
private:
TaskQueueBase* _taskQueue;
};
template <typename T>
@@ -62,6 +49,26 @@ public:
QueueTask()
: QueueTaskBase(nullptr, nullptr) { }
QueueTask(const QueueTask&) = delete;
QueueTask& operator=(const QueueTask&) = delete;
QueueTask(QueueTask&& other)
: QueueTaskBase(other._task, other._taskQueue)
{
other._task = nullptr;
other._taskQueue = nullptr;
}
QueueTask& operator=(QueueTask&& other)
{
Dispose();
_taskQueue = other._taskQueue;
_task = other._task;
other._taskQueue = nullptr;
other._task = nullptr;
return *this;
}
QueueTask(Task<T>* task, TaskQueueBase* taskQueue)
: QueueTaskBase(task, taskQueue) { }
@@ -79,24 +86,23 @@ 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);
}
protected:
rtos_event_t _event;
vu32 _queueReadPtr = 0;
vu32 _queueWritePtr = 0;
LinkedList<TaskBase, &TaskBase::link> _taskList;
volatile bool _endThreadWhenDone = false;
volatile bool _idle = true;
void ThreadMain(TaskBase** queue, u32 queueLength);
void ThreadMain();
TaskQueueBase()
{
@@ -124,6 +130,10 @@ public:
u32 irqs = rtos_disableIrqs();
if (task->IsCompleted())
{
if (task->link.prev != nullptr || task->link.next != nullptr)
{
_taskList.Remove(task);
}
task->~TaskBase();
u32 slot = ((u32)task - (u32)_taskPool) / ((MaxTaskSize + 3) & ~3);
_poolOccupation.Set(slot, 0);
@@ -159,13 +169,12 @@ public:
bool IsIdle() const
{
return _queueReadPtr == _queueWritePtr && _idle;
return _taskList.GetHead() == nullptr && _idle;
}
private:
u32 _taskPool[QueueLength][(MaxTaskSize + 3) / 4];
BitVector<QueueLength> _poolOccupation;
TaskBase* _queue[QueueLength];
rtos_thread_t _thread;
bool _threadStarted = false;
@@ -191,12 +200,11 @@ private:
[[gnu::noinline]]
void Enqueue(TaskBase* task) override
{
u32 writePtr = _queueWritePtr;
_queue[writePtr] = task;
if (writePtr == QueueLength - 1)
_queueWritePtr = 0;
else
_queueWritePtr = writePtr + 1;
u32 irqs = rtos_disableIrqs();
{
_taskList.InsertTail(task);
}
rtos_restoreIrqs(irqs);
rtos_signalEvent(&_event);
}
@@ -207,6 +215,6 @@ private:
void ThreadMain()
{
TaskQueueBase::ThreadMain(&_queue[0], QueueLength);
TaskQueueBase::ThreadMain();
}
};

View File

@@ -60,6 +60,16 @@ extern "C" void* memalign(size_t alignment, size_t size)
return result;
}
extern "C" void* calloc(size_t num, size_t size)
{
void* result = malloc(num * size);
if (result)
{
memset(result, 0, num * size);
}
return result;
}
void* operator new(std::size_t blocksize)
{
return malloc(blocksize);

View File

@@ -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];

View File

@@ -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);
}

View File

@@ -3,7 +3,7 @@
#include "input/InputProvider.h"
#include "FocusManager.h"
void FocusManager::Focus(View* newFocus)
void FocusManager::Focus(const SharedPtr<View>& newFocus)
{
if (!newFocus)
{
@@ -11,48 +11,71 @@ void FocusManager::Focus(View* newFocus)
return;
}
if (_currentFocus)
_currentFocus->SetFocused(false);
if (auto currentFocus = _currentFocus.Lock())
{
currentFocus->SetFocused(false);
}
newFocus->SetFocused(true);
_currentFocus = newFocus;
}
void FocusManager::Unfocus()
{
if (_currentFocus)
_currentFocus->SetFocused(false);
_currentFocus = nullptr;
if (auto currentFocus = _currentFocus.Lock())
{
currentFocus->SetFocused(false);
}
_currentFocus.Reset();
}
void FocusManager::Update(const InputProvider& inputProvider)
{
if (!_currentFocus || !_currentFocus->GetParent())
auto currentFocus = _currentFocus.Lock();
if (!currentFocus || !currentFocus->GetParent())
return; // todo
View* newFocus = nullptr;
SharedPtr<View> newFocus;
if (inputProvider.Triggered(InputKey::DpadUp))
newFocus = _currentFocus->GetParent()->MoveFocus(_currentFocus, FocusMoveDirection::Up, _currentFocus);
{
newFocus = currentFocus->GetParent()->MoveFocus(currentFocus, FocusMoveDirection::Up, currentFocus.GetPointer());
}
else if (inputProvider.Triggered(InputKey::DpadDown))
newFocus = _currentFocus->GetParent()->MoveFocus(_currentFocus, FocusMoveDirection::Down, _currentFocus);
{
newFocus = currentFocus->GetParent()->MoveFocus(currentFocus, FocusMoveDirection::Down, currentFocus.GetPointer());
}
else if (inputProvider.Triggered(InputKey::DpadLeft))
newFocus = _currentFocus->GetParent()->MoveFocus(_currentFocus, FocusMoveDirection::Left, _currentFocus);
{
newFocus = currentFocus->GetParent()->MoveFocus(currentFocus, FocusMoveDirection::Left, currentFocus.GetPointer());
}
else if (inputProvider.Triggered(InputKey::DpadRight))
newFocus = _currentFocus->GetParent()->MoveFocus(_currentFocus, FocusMoveDirection::Right, _currentFocus);
{
newFocus = currentFocus->GetParent()->MoveFocus(currentFocus, FocusMoveDirection::Right, currentFocus.GetPointer());
}
else
_currentFocus->HandleInput(inputProvider, *this);
{
currentFocus->HandleInput(inputProvider, *this);
}
if (newFocus)
{
Focus(newFocus);
}
}
bool FocusManager::IsFocusInside(const View* view) const
{
auto focusView = _currentFocus;
while (focusView)
if (auto currentFocus = _currentFocus.Lock())
{
if (view == focusView)
return true;
focusView = focusView->GetParent();
auto focusView = currentFocus.GetPointer();
while (focusView)
{
if (view == focusView)
{
return true;
}
focusView = focusView->GetParent();
}
}
return false;
}

View File

@@ -1,4 +1,6 @@
#pragma once
#include <core/SharedPtr.h>
#include <core/WeakPtr.h>
class View;
class InputProvider;
@@ -9,7 +11,7 @@ class FocusManager
public:
/// @brief Focuses the given view.
/// @param newFocus The view to focus.
void Focus(View* newFocus);
void Focus(const SharedPtr<View>& newFocus);
/// @brief Clears the current focus.
void Unfocus();
@@ -20,7 +22,7 @@ public:
/// @brief Gets the currently focused view.
/// @return A pointer to the view that is currently focused, or null if none.
constexpr View* GetCurrentFocus() const { return _currentFocus; }
constexpr SharedPtr<View> GetCurrentFocus() const { return _currentFocus.Lock(); }
/// @brief Checks whether the current focus lies inside the given view.
/// @param view The view to check for.
@@ -28,5 +30,5 @@ public:
bool IsFocusInside(const View* view) const;
private:
View* _currentFocus = nullptr;
WeakPtr<View> _currentFocus;
};

View File

@@ -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)
{
// by returning we only render complete glyphs
return;
// old code for rendering partial glyphs
// xEnd = width - xPos;
if (renderParams->onlyRenderWholeGlyphs)
{
return;
}
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;
}

View File

@@ -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.

View File

@@ -2,10 +2,10 @@
#include "InputKey.h"
/// @brief Interface for a source of key input.
class IInputSource
class IKeyInputSource
{
public:
virtual ~IInputSource() { }
virtual ~IKeyInputSource() { }
virtual InputKey Sample() const = 0;
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include "core/math/Point.h"
/// @brief Interface for a source of touch input.
class ITouchInputSource
{
public:
virtual ~ITouchInputSource() { }
/// @brief Samples the touch input.
/// @param touchPosition When the pen is down, the current touch position.
/// @return \c true when the pen is down, or \c false otherwise.
virtual bool Sample(Point& touchPosition) const = 0;
};

View File

@@ -15,7 +15,9 @@ enum class InputKey : u16
L = 1 << 9,
X = 1 << 10,
Y = 1 << 11,
Debug = 1 << 13
Debug = 1 << 13,
Touch = 1 << 14,
Lid = 1 << 15
};
inline InputKey operator&(InputKey lhs, InputKey rhs)

View File

@@ -1,5 +1,5 @@
#pragma once
#include "core/math/Point.h"
#include "InputKey.h"
class InputProvider
@@ -14,6 +14,23 @@ public:
/// @return \c true if any of the keys in the \p mask is being held, or \c false otherwise.
bool Current(InputKey mask) const { return static_cast<bool>(_currentKeys & mask); }
/// @brief Returns the current touch point if the screen is being touched.
/// @param touchPoint The current touch point if the screen is being touched.
/// @return \c true if the screen is being touched, or \c false otherwise.
bool GetCurrentTouchPoint(Point& touchPoint)
{
if (Current(InputKey::Touch))
{
touchPoint = _currentTouchPoint;
return true;
}
else
{
touchPoint = Point(0, 0);
return false;
}
}
/// @brief Returns a bitmask of the keys that went from unpressed to pressed in the latest update.
/// @return A bitmask of the keys that went from unpressed to pressed in the latest update.
InputKey GetTriggeredKeys() const
@@ -46,13 +63,16 @@ public:
_currentKeys = InputKey::None;
_triggeredKeys = InputKey::None;
_releasedKeys = InputKey::None;
_currentTouchPoint = Point(0, 0);
}
protected:
InputKey _currentKeys;
InputKey _triggeredKeys;
InputKey _releasedKeys;
Point _currentTouchPoint;
InputProvider()
: _currentKeys(InputKey::None), _triggeredKeys(InputKey::None), _releasedKeys(InputKey::None) { }
: _currentKeys(InputKey::None), _triggeredKeys(InputKey::None), _releasedKeys(InputKey::None)
, _currentTouchPoint(0, 0) { }
};

View File

@@ -27,7 +27,9 @@ void InputRepeater::Update()
}
}
else
{
_state = State::Idle;
}
}
else if (_state == State::NextRepeat)
{
@@ -40,13 +42,24 @@ void InputRepeater::Update()
}
}
else
{
_state = State::Idle;
}
}
InputKey lastRepKeys = _currentKeys & _repeatMask;
_currentKeys = curKeys | repKeys;
_triggeredKeys = _inputProvider->GetTriggeredKeys() | repKeys;
_releasedKeys = _inputProvider->GetReleasedKeys() | (lastRepKeys & (lastRepKeys ^ repKeys));
Point touchPoint;
if (_inputProvider->GetCurrentTouchPoint(touchPoint))
{
_currentTouchPoint = touchPoint;
}
else
{
_currentTouchPoint = Point(0, 0);
}
}
void InputRepeater::Reset()

View File

@@ -7,7 +7,7 @@ class InputRepeater : public InputProvider
public:
InputRepeater(InputProvider* inputProvider, InputKey repeatMask, u16 firstRepeatDelay, u16 nextRepeatDelay)
: _inputProvider(inputProvider), _state(State::Idle)
, _frameCounter(0), _repeatMask(repeatMask)
, _frameCounter(0), _repeatMask(repeatMask & ~InputKey::Touch)
, _firstRepeatDelayFrames(firstRepeatDelay)
, _nextRepeatDelayFrames(nextRepeatDelay) { }

View File

@@ -1,17 +1,18 @@
#pragma once
#include "common.h"
#include <nds/input.h>
#include "IInputSource.h"
#include "IKeyInputSource.h"
#include "sharedMemory.h"
/// @brief Input source from the physical DS buttons.
class PadInputSource : public IInputSource
class PadInputSource : public IKeyInputSource
{
public:
InputKey Sample() const override
{
u16 arm9Mask = (~REG_KEYINPUT & 0x3FF);
u16 arm7Mask = (~SHARED_KEY_XY & 0xB);
u16 arm7Mask = (~SHARED_KEY_XY & 0xCB);
arm7Mask = (arm7Mask & 0xB) | ((arm7Mask & 0xC0) >> 2);
return static_cast<InputKey>((arm9Mask | (arm7Mask << 10)));
}
};

View File

@@ -7,16 +7,44 @@ void SampledInputProvider::Update()
InputKey trig = InputKey::None;
InputKey rel = InputKey::None;
Point touchPoint(0, 0);
int touchPointCount = 0;
while (_inputBufferReadPtr != _inputBufferWritePtr)
{
InputKey nextKeys = _inputBuffer[_inputBufferReadPtr];
InputKey nextKeys = _keyInputBuffer[_inputBufferReadPtr];
trig |= (nextKeys ^ curKeys) & nextKeys;
rel |= (nextKeys ^ curKeys) & curKeys;
curKeys = nextKeys;
if ((nextKeys & InputKey::Touch) != InputKey::None)
{
touchPoint.x += _touchInputBuffer[_inputBufferReadPtr].x;
touchPoint.y += _touchInputBuffer[_inputBufferReadPtr].y;
touchPointCount++;
}
else
{
touchPoint = Point(0, 0);
touchPointCount = 0;
}
_inputBufferReadPtr = (_inputBufferReadPtr + 1) & 3;
}
_triggeredKeys = trig;
_releasedKeys = rel;
_currentKeys = curKeys;
if (touchPointCount == 0)
{
if ((_triggeredKeys & InputKey::Touch) != InputKey::None ||
(_currentKeys & InputKey::Touch) != InputKey::None)
{
_releasedKeys = _releasedKeys | InputKey::Touch;
}
_triggeredKeys = _triggeredKeys & ~InputKey::Touch;
_currentKeys = _currentKeys & ~InputKey::Touch;
}
else
{
_currentTouchPoint.x = touchPoint.x / touchPointCount;
_currentTouchPoint.y = touchPoint.y / touchPointCount;
}
}

View File

@@ -1,20 +1,23 @@
#pragma once
#include "InputProvider.h"
#include "IInputSource.h"
#include "IKeyInputSource.h"
#include "ITouchInputSource.h"
/// @brief Input provider providing input from an \see IInputSource.
/// @brief Input provider providing input from an \see IKeyInputSource.
class SampledInputProvider : public InputProvider
{
public:
explicit SampledInputProvider(const IInputSource* inputSource)
: _inputSource(inputSource), _inputBufferReadPtr(0), _inputBufferWritePtr(0) { }
explicit SampledInputProvider(const IKeyInputSource* keyInputSource, const ITouchInputSource* touchInputSource)
: _keyInputSource(keyInputSource), _touchInputSource(touchInputSource)
, _inputBufferReadPtr(0), _inputBufferWritePtr(0) { }
void Update() override;
/// @brief Samples the input source.
void Sample()
{
_inputBuffer[_inputBufferWritePtr] = _inputSource->Sample();
_keyInputBuffer[_inputBufferWritePtr] = _keyInputSource->Sample();
_touchInputSource->Sample(_touchInputBuffer[_inputBufferWritePtr]);
_inputBufferWritePtr = (_inputBufferWritePtr + 1) & 3;
}
@@ -26,9 +29,11 @@ public:
}
private:
const IInputSource* _inputSource;
const IKeyInputSource* _keyInputSource;
const ITouchInputSource* _touchInputSource;
InputKey _inputBuffer[4];
InputKey _keyInputBuffer[4];
Point _touchInputBuffer[4];
u8 _inputBufferReadPtr;
u8 _inputBufferWritePtr;
};

View File

@@ -0,0 +1,24 @@
#pragma once
#include "common.h"
#include <nds/input.h>
#include "ITouchInputSource.h"
#include "sharedMemory.h"
/// @brief Input source from the physical DS touch screen.
class TouchInputSource : public ITouchInputSource
{
public:
bool Sample(Point& touchPosition) const override
{
if (SHARED_KEY_XY & (1 << 6))
{
touchPosition = Point(0, 0);
return false;
}
else
{
touchPosition = Point(SHARED_TOUCH_X, SHARED_TOUCH_Y);
return true;
}
}
};

View File

@@ -3,15 +3,17 @@
class Label2DView : public LabelView
{
public:
Label2DView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font)
: LabelView(width, height, maxStringLength, font, false) { }
SHARED_ONLY(Label2DView)
public:
void InitVram(const VramContext& vramContext) override;
void Draw(GraphicsContext& graphicsContext) override;
void VBlank() override;
private:
Label2DView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font)
: LabelView(width, height, maxStringLength, font, false) { }
void UpdateTileBuffer() override;
bool _tileBufferUpdated = false;

View File

@@ -12,6 +12,11 @@ Label3DView::Label3DView(u32 width, u32 height, u32 maxStringLength, const nft2_
: LabelView(width, height, maxStringLength, font, true)
, _vblankTextureLoader(vblankTextureLoader) { }
Label3DView::~Label3DView()
{
_vblankTextureLoader->CancelLoad(_textureLoadRequest);
}
void Label3DView::InitVram(const VramContext& vramContext)
{
const auto texVramManager = vramContext.GetTexVramManager();

View File

@@ -4,14 +4,18 @@
class alignas(32) Label3DView : public LabelView
{
SHARED_ONLY(Label3DView)
public:
Label3DView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font,
VBlankTextureLoader* vblankTextureLoader);
~Label3DView() override;
void InitVram(const VramContext& vramContext) override;
void Draw(GraphicsContext& graphicsContext) override;
private:
Label3DView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font,
VBlankTextureLoader* vblankTextureLoader);
void UpdateTileBuffer() override;
u32 _texVramOffset = 0;

View File

@@ -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;
}
}
}
}

View File

@@ -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();
};

View File

@@ -20,19 +20,15 @@ public:
/// @brief Creates and returns a view for this adapter.
/// @return The created view.
virtual View* CreateView() const = 0;
/// @brief Destroys a \p view for this adapter that was previously created with CreateView.
/// @param view The view to destroy.
virtual void DestroyView(View* view) const = 0;
virtual SharedPtr<View> CreateView() const = 0;
/// @brief Binds the given \p view to the item at the given \p index.
/// @param view The view to bind.
/// @param index The item index to bind to.
virtual void BindView(View* view, int index) const = 0;
virtual void BindView(SharedPtr<View> view, int index) const = 0;
/// @brief Releases a \p view that was previously bound with BindView, such that it can be reused.
/// @param view The view to release.
/// @param index The item index that was bound to the view.
virtual void ReleaseView(View* view, int index) const = 0;
virtual void ReleaseView(SharedPtr<View> view, int index) const = 0;
};

View File

@@ -20,27 +20,31 @@ RecyclerView::~RecyclerView()
{
if (_adapter)
{
for (u32 i = 0; i < _viewPoolTotalCount; i++)
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
{
_adapter->DestroyView(_viewPool[i].view);
_adapter->ReleaseView(_viewPool[i].view, _viewPool[i].itemIdx);
}
}
}
void RecyclerView::SetAdapter(const RecyclerAdapter* adapter, int initialSelectedIndex)
void RecyclerView::SetAdapter(SharedPtr<const RecyclerAdapter> adapter, int initialSelectedIndex)
{
if (_adapter)
{
_selectedItem = nullptr;
for (u32 i = 0; i < _viewPoolTotalCount; i++)
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
{
_adapter->DestroyView(_viewPool[i].view);
_adapter->ReleaseView(_viewPool[i].view, _viewPool[i].itemIdx);
}
_viewPool.reset();
_viewPoolFreeCount = 0;
_viewPoolTotalCount = 0;
_xOffset = 0;
_yOffset = 0;
_curRangeStart = 0;
_curRangeLength = 0;
}
_adapter = adapter;
_adapter = std::move(adapter);
_adapter->GetViewSize(_itemWidth, _itemHeight);
_itemCount = _adapter->GetItemCount();
if (_mode == Mode::HorizontalList || _mode == Mode::HorizontalGrid)
@@ -174,7 +178,7 @@ void RecyclerView::VBlank()
}
}
View* RecyclerView::MoveFocus(View* currentFocus, FocusMoveDirection direction, View* source)
SharedPtr<View> RecyclerView::MoveFocus(const SharedPtr<View>& currentFocus, FocusMoveDirection direction, View* source)
{
if (_itemCount == 0)
{
@@ -191,9 +195,9 @@ View* RecyclerView::MoveFocus(View* currentFocus, FocusMoveDirection direction,
}
}
View* RecyclerView::MoveFocusHorizontal(View* currentFocus, FocusMoveDirection direction, View* source)
SharedPtr<View> RecyclerView::MoveFocusHorizontal(const SharedPtr<View>& currentFocus, FocusMoveDirection direction, View* source)
{
if (!_selectedItem || currentFocus != _selectedItem->view)
if (!_selectedItem || currentFocus.GetPointer() != _selectedItem->view.GetPointer())
{
// incoming focus
if (direction != FocusMoveDirection::Down)
@@ -203,7 +207,7 @@ View* RecyclerView::MoveFocusHorizontal(View* currentFocus, FocusMoveDirection d
int idx = (-_xOffset + currentFocus->GetPosition().x - _xPadding + ((_xSpacing + _itemWidth) >> 1)) / (_xSpacing + _itemWidth) * _rows;
SetSelectedItem(std::clamp(idx, 0, ((int)_itemCount - 1) / _rows * _rows));
return _selectedItem != nullptr ? _selectedItem->view : this;
return _selectedItem != nullptr ? _selectedItem->view : SharedFromThis();
}
int row = _selectedItem->itemIdx % _rows;
@@ -244,22 +248,28 @@ View* RecyclerView::MoveFocusHorizontal(View* currentFocus, FocusMoveDirection d
SetSelectedItem(std::clamp(idx, 0, (int)_itemCount - 1));
}
return _selectedItem != nullptr ? _selectedItem->view : this;
return _selectedItem != nullptr ? _selectedItem->view : SharedFromThis();
}
View* RecyclerView::MoveFocusVertical(View* currentFocus, FocusMoveDirection direction, View* source)
SharedPtr<View> RecyclerView::MoveFocusVertical(const SharedPtr<View>& currentFocus, FocusMoveDirection direction, View* source)
{
if (!_selectedItem || currentFocus != _selectedItem->view)
if (!_selectedItem || currentFocus.GetPointer() != _selectedItem->view.GetPointer())
{
// incoming focus
if (direction != FocusMoveDirection::Right)
if (direction == FocusMoveDirection::Right)
{
return nullptr;
int idx = (-_yOffset + currentFocus->GetPosition().y - _yPadding + ((_ySpacing + _itemHeight) >> 1)) / (_ySpacing + _itemHeight) * _columns;
SetSelectedItem(std::clamp(idx, 0, ((int)_itemCount - 1) / _columns * _columns));
return _selectedItem != nullptr ? _selectedItem->view : SharedFromThis();
}
else if (direction == FocusMoveDirection::Down)
{
int idx = (-_xOffset + currentFocus->GetPosition().x - _xPadding + ((_xSpacing + _itemWidth) >> 1)) / (_xSpacing + _itemWidth);
SetSelectedItem(std::clamp(idx, 0, _columns - 1));
return _selectedItem != nullptr ? _selectedItem->view : SharedFromThis();
}
int idx = (-_yOffset + currentFocus->GetPosition().y - _yPadding + ((_ySpacing + _itemHeight) >> 1)) / (_ySpacing + _itemHeight) * _columns;
SetSelectedItem(std::clamp(idx, 0, ((int)_itemCount - 1) / _columns * _columns));
return _selectedItem != nullptr ? _selectedItem->view : this;
return nullptr;
}
int column = _selectedItem->itemIdx % _columns;
@@ -300,12 +310,12 @@ View* RecyclerView::MoveFocusVertical(View* currentFocus, FocusMoveDirection dir
SetSelectedItem(std::clamp(idx, 0, (int)_itemCount - 1));
}
return _selectedItem != nullptr ? _selectedItem->view : this;
return _selectedItem != nullptr ? _selectedItem->view : SharedFromThis();
}
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;
@@ -337,6 +347,117 @@ bool RecyclerView::HandleInput(const InputProvider& inputProvider, FocusManager&
return View::HandleInput(inputProvider, focusManager);
}
void RecyclerView::HandlePenDown(const Point& touchPoint, FocusManager& focusManager)
{
if (GetBounds().Contains(touchPoint))
{
_penDown = true;
_penDownPosition = touchPoint;
_hasScrollStarted = false;
_penDownScrollOffset = _scrollOffsetAnimator.GetValue();
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
{
_viewPool[i].view->HandlePenDown(touchPoint, focusManager);
}
}
}
void RecyclerView::HandlePenMove(const Point& touchPoint, FocusManager& focusManager)
{
if (!_penDown)
{
return;
}
if (!_hasScrollStarted)
{
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
{
_viewPool[i].view->HandlePenMove(touchPoint, focusManager);
if (focusManager.GetCurrentFocus().GetPointer() == _viewPool[i].view.GetPointer())
{
SetSelectedItem(_viewPool[i].itemIdx);
}
}
int dx = touchPoint.x - _penDownPosition.x;
int dy = touchPoint.y - _penDownPosition.y;
if (dx * dx + dy * dy > 7 * 7)
{
bool shouldScrollStart = (_mode == Mode::HorizontalGrid || _mode == Mode::HorizontalList)
? (std::abs(touchPoint.x - _penDownPosition.x) > std::abs(touchPoint.y - _penDownPosition.y))
: (std::abs(touchPoint.x - _penDownPosition.x) < std::abs(touchPoint.y - _penDownPosition.y));
if (shouldScrollStart)
{
_hasScrollStarted = true;
}
else
{
_penDown = false; //wrong direction drag, so cancel it
}
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
{
_viewPool[i].view->HandlePenUp(Point(-1, -1), focusManager);
}
}
}
else
{
int newScrollOffset;
if (_mode == Mode::HorizontalGrid || _mode == Mode::HorizontalList)
{
newScrollOffset = _penDownScrollOffset + touchPoint.x - _penDownPosition.x;
if (-newScrollOffset < 0)
{
newScrollOffset = 0;
_penDownScrollOffset = 0;
_penDownPosition.x = touchPoint.x;
}
else if (newScrollOffset < GetMaxScrollOffset())
{
newScrollOffset = GetMaxScrollOffset();
_penDownScrollOffset = newScrollOffset;
_penDownPosition.x = touchPoint.x;
}
}
else
{
newScrollOffset = _penDownScrollOffset + touchPoint.y - _penDownPosition.y;
if (-newScrollOffset < 0)
{
newScrollOffset = 0;
_penDownScrollOffset = 0;
_penDownPosition.y = touchPoint.y;
}
else if (newScrollOffset < GetMaxScrollOffset())
{
newScrollOffset = GetMaxScrollOffset();
_penDownScrollOffset = newScrollOffset;
_penDownPosition.y = touchPoint.y;
}
}
SetScrollOffset(newScrollOffset, false);
}
}
void RecyclerView::HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager)
{
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
{
_viewPool[i].view->HandlePenUp(lastTouchPoint, focusManager);
if (focusManager.GetCurrentFocus().GetPointer() == _viewPool[i].view.GetPointer())
{
SetSelectedItem(_viewPool[i].itemIdx);
}
}
_penDown = false;
}
Point RecyclerView::GetItemPosition(int itemIdx)
{
int x = 0;

View File

@@ -8,6 +8,8 @@
class RecyclerView : public RecyclerViewBase
{
SHARED_ONLY(RecyclerView)
public:
enum class Mode
{
@@ -21,10 +23,9 @@ public:
VerticalGrid
};
RecyclerView(int x, int y, int width, int height, Mode mode);
~RecyclerView();
void SetAdapter(const RecyclerAdapter* adapter, int initialSelectedIndex = 0) override;
void SetAdapter(SharedPtr<const RecyclerAdapter> adapter, int initialSelectedIndex = 0) override;
void InitVram(const VramContext& vramContext) override;
void Update() override;
void Draw(GraphicsContext& graphicsContext) override;
@@ -35,16 +36,23 @@ public:
return Rectangle(_position, _width, _height);
}
View* MoveFocus(View* currentFocus, FocusMoveDirection direction, View* source) override;
SharedPtr<View> MoveFocus(const SharedPtr<View>& currentFocus, FocusMoveDirection direction, View* source) override;
bool HandleInput(const InputProvider& inputProvider, FocusManager& focusManager) override;
void HandlePenDown(const Point& touchPoint, FocusManager& focusManager) override;
void HandlePenMove(const Point& touchPoint, FocusManager& focusManager) override;
void HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager) override;
void Focus(FocusManager& focusManager) override
{
if (_selectedItem)
{
focusManager.Focus(_selectedItem->view);
}
else
focusManager.Focus(this);
{
focusManager.Focus(SharedFromThis());
}
}
int GetSelectedItem() const override
@@ -69,7 +77,7 @@ public:
private:
struct ViewPoolEntry
{
View* view;
SharedPtr<View> view;
int itemIdx;
};
@@ -94,6 +102,12 @@ private:
int _curRangeStart;
int _curRangeLength;
Animator<int> _scrollOffsetAnimator;
bool _penDown = false;
Point _penDownPosition = Point(0, 0);
bool _hasScrollStarted = false;
int _penDownScrollOffset = 0;
RecyclerView(int x, int y, int width, int height, Mode mode);
void UpdatePosition(ViewPoolEntry& viewPoolEntry);
ViewPoolEntry* GetViewPoolEntryByItemIndex(int itemIdx);
@@ -107,6 +121,6 @@ private:
void EnsureVisible(int itemIdx, bool animate);
Point GetItemPosition(int itemIdx);
View* MoveFocusHorizontal(View* currentFocus, FocusMoveDirection direction, View* source);
View* MoveFocusVertical(View* currentFocus, FocusMoveDirection direction, View* source);
SharedPtr<View> MoveFocusHorizontal(const SharedPtr<View>& currentFocus, FocusMoveDirection direction, View* source);
SharedPtr<View> MoveFocusVertical(const SharedPtr<View>& currentFocus, FocusMoveDirection direction, View* source);
};

View File

@@ -2,16 +2,17 @@
#include "View.h"
#include "RecyclerAdapter.h"
#include "gui/FocusManager.h"
#include "core/SharedPtr.h"
/// @brief Abstract base class for a recycler view that displays a possibly large collection of items
/// provided by an adapter in an efficient way.
class RecyclerViewBase : public View
{
public:
virtual void SetAdapter(const RecyclerAdapter* adapter, int initialSelectedIndex = 0) = 0;
virtual void SetAdapter(SharedPtr<const RecyclerAdapter> adapter, int initialSelectedIndex = 0) = 0;
virtual void Focus(FocusManager& focusManager) = 0;
virtual int GetSelectedItem() const = 0;
protected:
const RecyclerAdapter* _adapter = nullptr;
SharedPtr<const RecyclerAdapter> _adapter;
};

View File

@@ -2,6 +2,8 @@
#include "core/LinkedListLink.h"
#include "core/math/Point.h"
#include "core/math/Rectangle.h"
#include "core/SharedPtr.h"
#include "core/EnableSharedFromThis.h"
#include "../FocusManager.h"
#include "../FocusMoveDirection.h"
@@ -10,7 +12,7 @@ class VramContext;
class InputProvider;
/// @brief Base class for views.
class View
class View : public EnableSharedFromThis<View>
{
public:
/// @brief Link used for views that contain other views.
@@ -37,7 +39,7 @@ public:
/// @param direction The direction to move the focus in.
/// @param source The view that requested this view to move focus.
/// @return The newly focused view, or null if the focus didn't change.
virtual View* MoveFocus(View* currentFocus, FocusMoveDirection direction, View* source)
virtual SharedPtr<View> MoveFocus(const SharedPtr<View>& currentFocus, FocusMoveDirection direction, View* source)
{
if (_parent && _parent != source)
return _parent->MoveFocus(currentFocus, direction, this);
@@ -57,6 +59,21 @@ public:
return false;
}
/// @brief Handles a pen down event.
/// @param touchPoint The touch point.
/// @param focusManager The focus manager.
virtual void HandlePenDown(const Point& touchPoint, FocusManager& focusManager) { }
/// @brief Handles a pen move event.
/// @param touchPoint The touch point.
/// @param focusManager The focus manager.
virtual void HandlePenMove(const Point& touchPoint, FocusManager& focusManager) { }
/// @brief Handles a pen up event.
/// @param lastTouchPoint The last touch point.
/// @param focusManager The focus manager.
virtual void HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager) { }
/// @brief Gets the bounds of the view.
/// @return The bounds of the view.
virtual Rectangle GetBounds() const = 0;

View File

@@ -0,0 +1,58 @@
#include "common.h"
#include "ViewContainer.h"
void ViewContainer::InitVram(const VramContext& vramContext)
{
for (auto& view : _children)
{
view.InitVram(vramContext);
}
}
void ViewContainer::Update()
{
for (auto& view : _children)
{
view.Update();
}
}
void ViewContainer::Draw(GraphicsContext& graphicsContext)
{
for (auto& view : _children)
{
view.Draw(graphicsContext);
}
}
void ViewContainer::VBlank()
{
for (auto& view : _children)
{
view.VBlank();
}
}
void ViewContainer::HandlePenDown(const Point& touchPoint, FocusManager& focusManager)
{
for (auto& view : _children)
{
view.HandlePenDown(touchPoint, focusManager);
}
}
void ViewContainer::HandlePenMove(const Point& touchPoint, FocusManager& focusManager)
{
for (auto& view : _children)
{
view.HandlePenMove(touchPoint, focusManager);
}
}
void ViewContainer::HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager)
{
for (auto& view : _children)
{
view.HandlePenUp(lastTouchPoint, focusManager);
}
}

View File

@@ -6,37 +6,13 @@
class ViewContainer : public View
{
public:
void InitVram(const VramContext& vramContext) override
{
for (auto& view : _children)
{
view.InitVram(vramContext);
}
}
void Update() override
{
for (auto& view : _children)
{
view.Update();
}
}
void Draw(GraphicsContext& graphicsContext) override
{
for (auto& view : _children)
{
view.Draw(graphicsContext);
}
}
void VBlank() override
{
for (auto& view : _children)
{
view.VBlank();
}
}
void InitVram(const VramContext& vramContext) override;
void Update() override;
void Draw(GraphicsContext& graphicsContext) override;
void VBlank() override;
void HandlePenDown(const Point& touchPoint, FocusManager& focusManager) override;
void HandlePenMove(const Point& touchPoint, FocusManager& focusManager) override;
void HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager) override;
protected:
/// @brief Adds a child to the head of the list.
@@ -55,6 +31,14 @@ protected:
view->SetParent(this);
}
/// @brief Removes a child from the list.
/// @param view The child to remove.
void RemoveChild(View* view)
{
_children.Remove(view);
view->SetParent(nullptr);
}
private:
LinkedList<View, &View::listLink> _children;
};

View File

@@ -167,6 +167,11 @@ int main(int argc, char* argv[])
rtc_init();
if (argc >= 1)
{
pload_setLauncherPath(argv[0]);
}
memset(&gFatFs, 0, sizeof(gFatFs));
if (dldi_init())
{

View File

@@ -10,6 +10,7 @@
#include <libtwl/ipc/ipcFifoSystem.h>
#include "ipcChannels.h"
#include "fat/File.h"
#include "core/StringUtil.h"
#include "picoLoaderBootstrap.h"
#define PICO_LOADER_9_PATH "/_pico/picoLoader9.bin"
@@ -18,7 +19,9 @@
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()
{
@@ -30,6 +33,16 @@ void pload_setBootDrive(PicoLoaderBootDrive bootDrive)
sBootDrive = bootDrive;
}
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);
@@ -78,8 +91,17 @@ void pload_start()
DC_InvalidateAll();
IC_InvalidateAll();
((pload_header7_t*)0x06840000)->bootDrive = sBootDrive;
dma_ntrCopy16(3, &sLoadParams, &((pload_header7_t*)0x06840000)->loadParams, sizeof(pload_params_t));
auto header = (pload_header7_t*)0x06840000;
header->bootDrive = sBootDrive;
dma_ntrCopy16(3, &sLoadParams, &header->loadParams, sizeof(pload_params_t));
if (header->apiVersion >= 2)
{
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);

View File

@@ -3,4 +3,6 @@
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();

View File

@@ -3,6 +3,7 @@
#include "core/task/TaskQueue.h"
#include "../views/BannerListItemView.h"
#include "../Theme/IRomBrowserViewFactory.h"
#include "romBrowser/viewModels/RomBrowserItemViewModel.h"
#include "BannerListFileRecyclerAdapter.h"
void BannerListFileRecyclerAdapter::GetViewSize(int& width, int& height) const
@@ -11,27 +12,24 @@ void BannerListFileRecyclerAdapter::GetViewSize(int& width, int& height) const
height = 44;
}
View* BannerListFileRecyclerAdapter::CreateView() const
SharedPtr<View> BannerListFileRecyclerAdapter::CreateView() const
{
return _romBrowserViewFactory->CreateBannerListItemView(_vblankTextureLoader);
return _romBrowserViewFactory->CreateBannerListItemView(
std::make_unique<RomBrowserItemViewModel>(_romBrowserController), _vblankTextureLoader);
}
void BannerListFileRecyclerAdapter::DestroyView(View* view) const
void BannerListFileRecyclerAdapter::BindView(SharedPtr<View> view, int index) const
{
delete static_cast<BannerListItemView*>(view);
}
void BannerListFileRecyclerAdapter::BindView(View* view, int index) const
{
auto listItemView = static_cast<BannerListItemView*>(view);
auto listItemView = static_cast<BannerListItemView*>(view.GetPointer());
listItemView->SetGraphics(_bannerListItemViewGraphics);
FileRecyclerAdapter::BindView(view, index);
}
TaskResult<void> BannerListFileRecyclerAdapter::BindView(View* view, int index,
TaskResult<void> BannerListFileRecyclerAdapter::BindView(SharedPtr<View> view, int index,
const InternalFileInfo* internalFileInfo, const vu8& cancelRequested) const
{
auto listItemView = static_cast<BannerListItemView*>(view);
auto listItemView = static_cast<BannerListItemView*>(view.GetPointer());
listItemView->GetViewModel().SetIndex(index);
const auto& fileInfo = _fileInfoManager->GetItem(index);
bool fileNameAsTitle = true;
if (internalFileInfo)
@@ -70,12 +68,20 @@ TaskResult<void> BannerListFileRecyclerAdapter::BindView(View* view, int index,
return TaskResult<void>::Completed();
}
void BannerListFileRecyclerAdapter::ReleaseView(View* view, int index) const
void BannerListFileRecyclerAdapter::SetQueueTask(const SharedPtr<View>& view, QueueTask<void> queueTask) const
{
auto listItemView = static_cast<BannerListItemView*>(view.GetPointer());
listItemView->GetViewModel().SetQueueTask(std::move(queueTask));
}
void BannerListFileRecyclerAdapter::ReleaseView(SharedPtr<View> view, int index) const
{
LOG_DEBUG("Releasing %d\n", index);
auto listItemView = static_cast<BannerListItemView*>(view);
auto listItemView = static_cast<BannerListItemView*>(view.GetPointer());
listItemView->SetIcon(nullptr);
listItemView->SetGameTitle(u"");
listItemView->GetViewModel().SetIndex(-1);
listItemView->GetViewModel().CancelQueueTask();
_fileInfoManager->ReleaseFileInfo(index);
}

View File

@@ -8,19 +8,18 @@ class IRomBrowserViewFactory;
class BannerListFileRecyclerAdapter : public FileRecyclerAdapter
{
public:
BannerListFileRecyclerAdapter(FileInfoManager* fileInfoManager,
BannerListFileRecyclerAdapter(IRomBrowserController* romBrowserController, FileInfoManager* fileInfoManager,
TaskQueueBase* taskQueue, const IThemeFileIconFactory* themeFileIconFactory,
const IRomBrowserViewFactory* romBrowserViewFactory,
VBlankTextureLoader* vblankTextureLoader)
: FileRecyclerAdapter(fileInfoManager, taskQueue, themeFileIconFactory)
: FileRecyclerAdapter(romBrowserController, fileInfoManager, taskQueue, themeFileIconFactory)
, _romBrowserViewFactory(romBrowserViewFactory)
, _vblankTextureLoader(vblankTextureLoader) { }
void GetViewSize(int& width, int& height) const override;
View* CreateView() const override;
void DestroyView(View* view) const override;
void BindView(View* view, int index) const override;
void ReleaseView(View* view, int index) const override;
SharedPtr<View> CreateView() const override;
void BindView(SharedPtr<View> view, int index) const override;
void ReleaseView(SharedPtr<View> view, int index) const override;
void InitVram(const VramContext& vramContext) override;
@@ -29,6 +28,7 @@ private:
BannerListItemView::VramToken _bannerListItemViewGraphics;
VBlankTextureLoader* _vblankTextureLoader;
TaskResult<void> BindView(View* view, int index,
TaskResult<void> BindView(SharedPtr<View> view, int index,
const InternalFileInfo* internalFileInfo, const vu8& cancelRequested) const override;
void SetQueueTask(const SharedPtr<View>& view, QueueTask<void> queueTask) const override;
};

View File

@@ -13,21 +13,17 @@ void CoverFlowFileRecyclerAdapter::GetViewSize(int& width, int& height) const
height = 44;
}
View* CoverFlowFileRecyclerAdapter::CreateView() const
SharedPtr<View> CoverFlowFileRecyclerAdapter::CreateView() const
{
return new CoverView(_vblankTextureLoader);
return CoverView::CreateShared(
std::make_unique<RomBrowserItemViewModel>(_romBrowserController), _vblankTextureLoader);
}
void CoverFlowFileRecyclerAdapter::DestroyView(View* view) const
{
auto coverView = static_cast<CoverView*>(view);
delete coverView;
}
TaskResult<void> CoverFlowFileRecyclerAdapter::BindView(View* view, int index,
TaskResult<void> CoverFlowFileRecyclerAdapter::BindView(SharedPtr<View> view, int index,
const InternalFileInfo* internalFileInfo, const vu8& cancelRequested) const
{
auto coverView = static_cast<CoverView*>(view);
auto coverView = static_cast<CoverView*>(view.GetPointer());
coverView->GetViewModel().SetIndex(index);
auto cover = _fileInfoManager->GetFileCover(index);
if (cancelRequested)
{
@@ -45,11 +41,19 @@ TaskResult<void> CoverFlowFileRecyclerAdapter::BindView(View* view, int index,
return TaskResult<void>::Completed();
}
void CoverFlowFileRecyclerAdapter::ReleaseView(View* view, int index) const
void CoverFlowFileRecyclerAdapter::SetQueueTask(const SharedPtr<View>& view, QueueTask<void> queueTask) const
{
auto coverView = static_cast<CoverView*>(view.GetPointer());
coverView->GetViewModel().SetQueueTask(std::move(queueTask));
}
void CoverFlowFileRecyclerAdapter::ReleaseView(SharedPtr<View> view, int index) const
{
LOG_DEBUG("Releasing %d\n", index);
auto coverView = static_cast<CoverView*>(view);
auto coverView = static_cast<CoverView*>(view.GetPointer());
coverView->ClearCover();
coverView->GetViewModel().SetIndex(-1);
coverView->GetViewModel().CancelQueueTask();
_fileInfoManager->ReleaseFileInfo(index);
}

View File

@@ -8,20 +8,19 @@ class ICoverRepository;
class CoverFlowFileRecyclerAdapter : public FileRecyclerAdapter
{
public:
CoverFlowFileRecyclerAdapter(FileInfoManager* fileInfoManager,
CoverFlowFileRecyclerAdapter(IRomBrowserController* romBrowserController, FileInfoManager* fileInfoManager,
TaskQueueBase* taskQueue, const IThemeFileIconFactory* themeFileIconFactory,
const IRomBrowserViewFactory* romBrowserViewFactory,
VBlankTextureLoader* vblankTextureLoader,
const ICoverRepository* coverRepository)
: FileRecyclerAdapter(fileInfoManager, taskQueue, themeFileIconFactory)
: FileRecyclerAdapter(romBrowserController, fileInfoManager, taskQueue, themeFileIconFactory)
, _romBrowserViewFactory(romBrowserViewFactory)
, _vblankTextureLoader(vblankTextureLoader)
, _coverRepository(coverRepository) { }
void GetViewSize(int& width, int& height) const override;
View* CreateView() const override;
void DestroyView(View* view) const override;
void ReleaseView(View* view, int index) const override;
SharedPtr<View> CreateView() const override;
void ReleaseView(SharedPtr<View> view, int index) const override;
void InitVram(const VramContext& vramContext) override;
@@ -30,6 +29,7 @@ private:
VBlankTextureLoader* _vblankTextureLoader;
const ICoverRepository* _coverRepository;
TaskResult<void> BindView(View* view, int index,
TaskResult<void> BindView(SharedPtr<View> view, int index,
const InternalFileInfo* internalFileInfo, const vu8& cancelRequested) const override;
void SetQueueTask(const SharedPtr<View>& view, QueueTask<void> queueTask) const override;
};

View File

@@ -3,6 +3,7 @@
#include "core/task/TaskQueue.h"
#include "../views/IconGridItemView.h"
#include "../Theme/IRomBrowserViewFactory.h"
#include "romBrowser/viewModels/RomBrowserItemViewModel.h"
#include "IconGridFileRecyclerAdapter.h"
void IconGridFileRecyclerAdapter::GetViewSize(int& width, int& height) const
@@ -11,27 +12,23 @@ void IconGridFileRecyclerAdapter::GetViewSize(int& width, int& height) const
height = 44;
}
View* IconGridFileRecyclerAdapter::CreateView() const
SharedPtr<View> IconGridFileRecyclerAdapter::CreateView() const
{
return _romBrowserViewFactory->CreateIconGridItemView();
return _romBrowserViewFactory->CreateIconGridItemView(std::make_unique<RomBrowserItemViewModel>(_romBrowserController));
}
void IconGridFileRecyclerAdapter::DestroyView(View* view) const
void IconGridFileRecyclerAdapter::BindView(SharedPtr<View> view, int index) const
{
delete static_cast<IconGridItemView*>(view);
}
void IconGridFileRecyclerAdapter::BindView(View* view, int index) const
{
auto iconGridItemView = static_cast<IconGridItemView*>(view);
auto iconGridItemView = static_cast<IconGridItemView*>(view.GetPointer());
iconGridItemView->SetGraphics(_iconGridItemViewGraphics);
FileRecyclerAdapter::BindView(view, index);
}
TaskResult<void> IconGridFileRecyclerAdapter::BindView(View* view, int index,
TaskResult<void> IconGridFileRecyclerAdapter::BindView(SharedPtr<View> view, int index,
const InternalFileInfo* internalFileInfo, const vu8& cancelRequested) const
{
auto iconGridItemView = static_cast<IconGridItemView*>(view);
auto iconGridItemView = static_cast<IconGridItemView*>(view.GetPointer());
iconGridItemView->GetViewModel().SetIndex(index);
auto icon = internalFileInfo ? internalFileInfo->CreateGameIcon() : nullptr;
if (!icon)
{
@@ -59,11 +56,19 @@ TaskResult<void> IconGridFileRecyclerAdapter::BindView(View* view, int index,
return TaskResult<void>::Completed();
}
void IconGridFileRecyclerAdapter::ReleaseView(View* view, int index) const
void IconGridFileRecyclerAdapter::SetQueueTask(const SharedPtr<View>& view, QueueTask<void> queueTask) const
{
auto iconGridItemView = static_cast<IconGridItemView*>(view.GetPointer());
iconGridItemView->GetViewModel().SetQueueTask(std::move(queueTask));
}
void IconGridFileRecyclerAdapter::ReleaseView(SharedPtr<View> view, int index) const
{
LOG_DEBUG("Releasing %d\n", index);
auto iconGridItemView = static_cast<IconGridItemView*>(view);
auto iconGridItemView = static_cast<IconGridItemView*>(view.GetPointer());
iconGridItemView->SetIcon(nullptr);
iconGridItemView->GetViewModel().SetIndex(-1);
iconGridItemView->GetViewModel().CancelQueueTask();
_fileInfoManager->ReleaseFileInfo(index);
}

View File

@@ -7,17 +7,16 @@ class IRomBrowserViewFactory;
class IconGridFileRecyclerAdapter : public FileRecyclerAdapter
{
public:
IconGridFileRecyclerAdapter(FileInfoManager* fileInfoManager,
IconGridFileRecyclerAdapter(IRomBrowserController* romBrowserController, FileInfoManager* fileInfoManager,
TaskQueueBase* taskQueue, const IThemeFileIconFactory* themeFileIconFactory,
const IRomBrowserViewFactory* romBrowserViewFactory)
: FileRecyclerAdapter(fileInfoManager, taskQueue, themeFileIconFactory)
: FileRecyclerAdapter(romBrowserController, fileInfoManager, taskQueue, themeFileIconFactory)
, _romBrowserViewFactory(romBrowserViewFactory) { }
void GetViewSize(int& width, int& height) const override;
View* CreateView() const override;
void DestroyView(View* view) const override;
void BindView(View* view, int index) const override;
void ReleaseView(View* view, int index) const override;
SharedPtr<View> CreateView() const override;
void BindView(SharedPtr<View> view, int index) const override;
void ReleaseView(SharedPtr<View> view, int index) const override;
void InitVram(const VramContext& vramContext) override;
@@ -25,6 +24,7 @@ private:
const IRomBrowserViewFactory* _romBrowserViewFactory;
IconGridItemView::VramToken _iconGridItemViewGraphics;
TaskResult<void> BindView(View* view, int index,
TaskResult<void> BindView(SharedPtr<View> view, int index,
const InternalFileInfo* internalFileInfo, const vu8& cancelRequested) const override;
void SetQueueTask(const SharedPtr<View>& view, QueueTask<void> queueTask) const override;
};

View File

@@ -10,26 +10,26 @@ public:
bool IsVertical() const override { return true; }
std::unique_ptr<AppBarView> CreateAppBarView(const IRomBrowserViewFactory* romBrowserViewFactory,
SharedPtr<AppBarView> CreateAppBarView(const IRomBrowserViewFactory* romBrowserViewFactory,
int startButtonCount, int endButtonCount) const override
{
return romBrowserViewFactory->CreateAppBarView(0, 0,
AppBarView::Orientation::Vertical, startButtonCount, endButtonCount);
}
std::unique_ptr<RecyclerViewBase> CreateRecyclerView(const IRomBrowserViewFactory* romBrowserViewFactory) const override
SharedPtr<RecyclerViewBase> CreateRecyclerView(const IRomBrowserViewFactory* romBrowserViewFactory) const override
{
auto recyclerView = std::make_unique<RecyclerView>(42, 0, 256 - 42, 192, RecyclerView::Mode::VerticalList);
auto recyclerView = RecyclerView::CreateShared(42, 0, 256 - 42, 192, RecyclerView::Mode::VerticalList);
recyclerView->SetPadding(0, 3);
recyclerView->SetItemSpacing(0, 3);
return recyclerView;
}
FileRecyclerAdapter* CreateRecyclerAdapter(
SharedPtr<FileRecyclerAdapter> CreateRecyclerAdapter(
RomBrowserViewModel* viewModel, const IThemeFileIconFactory* themeFileIconFactory,
const IRomBrowserViewFactory* romBrowserViewFactory, VBlankTextureLoader* vblankTextureLoader) const override
{
return new BannerListFileRecyclerAdapter(
return SharedPtr<BannerListFileRecyclerAdapter>::MakeShared(viewModel->GetRomBrowserController(),
&viewModel->GetFileInfoManager(), viewModel->GetIoTaskQueue(), themeFileIconFactory,
romBrowserViewFactory, vblankTextureLoader);
}

View File

@@ -14,10 +14,10 @@ class RomBrowserDisplayMode
public:
virtual bool IsVertical() const = 0;
virtual bool ShowCoverOnTopScreen() const { return true; }
virtual std::unique_ptr<AppBarView> CreateAppBarView(const IRomBrowserViewFactory* romBrowserViewFactory,
virtual SharedPtr<AppBarView> CreateAppBarView(const IRomBrowserViewFactory* romBrowserViewFactory,
int startButtonCount, int endButtonCount) const = 0;
virtual std::unique_ptr<RecyclerViewBase> CreateRecyclerView(const IRomBrowserViewFactory* romBrowserViewFactory) const = 0;
virtual FileRecyclerAdapter* CreateRecyclerAdapter(
virtual SharedPtr<RecyclerViewBase> CreateRecyclerView(const IRomBrowserViewFactory* romBrowserViewFactory) const = 0;
virtual SharedPtr<FileRecyclerAdapter> CreateRecyclerAdapter(
RomBrowserViewModel* viewModel, const IThemeFileIconFactory* themeFileIconFactory,
const IRomBrowserViewFactory* romBrowserViewFactory, VBlankTextureLoader* vblankTextureLoader) const = 0;
};

View File

@@ -9,19 +9,19 @@ public:
bool IsVertical() const override { return false; }
bool ShowCoverOnTopScreen() const override { return false; }
std::unique_ptr<AppBarView> CreateAppBarView(const IRomBrowserViewFactory* romBrowserViewFactory,
SharedPtr<AppBarView> CreateAppBarView(const IRomBrowserViewFactory* romBrowserViewFactory,
int startButtonCount, int endButtonCount) const override
{
return romBrowserViewFactory->CreateAppBarView(0, 0,
AppBarView::Orientation::Horizontal, startButtonCount, endButtonCount);
}
std::unique_ptr<RecyclerViewBase> CreateRecyclerView(const IRomBrowserViewFactory* romBrowserViewFactory) const override
SharedPtr<RecyclerViewBase> CreateRecyclerView(const IRomBrowserViewFactory* romBrowserViewFactory) const override
{
return romBrowserViewFactory->CreateCoverFlowRecyclerView();
}
FileRecyclerAdapter* CreateRecyclerAdapter(
SharedPtr<FileRecyclerAdapter> CreateRecyclerAdapter(
RomBrowserViewModel* viewModel, const IThemeFileIconFactory* themeFileIconFactory,
const IRomBrowserViewFactory* romBrowserViewFactory, VBlankTextureLoader* vblankTextureLoader) const override
{

View File

@@ -10,26 +10,26 @@ public:
bool IsVertical() const override { return false; }
std::unique_ptr<AppBarView> CreateAppBarView(const IRomBrowserViewFactory* romBrowserViewFactory,
SharedPtr<AppBarView> CreateAppBarView(const IRomBrowserViewFactory* romBrowserViewFactory,
int startButtonCount, int endButtonCount) const override
{
return romBrowserViewFactory->CreateAppBarView(0, 0,
AppBarView::Orientation::Horizontal, startButtonCount, endButtonCount);
}
std::unique_ptr<RecyclerViewBase> CreateRecyclerView(const IRomBrowserViewFactory* romBrowserViewFactory) const override
SharedPtr<RecyclerViewBase> CreateRecyclerView(const IRomBrowserViewFactory* romBrowserViewFactory) const override
{
auto recyclerView = std::make_unique<RecyclerView>(0, 42, 256, 192 - 42, RecyclerView::Mode::HorizontalGrid);
auto recyclerView = RecyclerView::CreateShared(0, 42, 256, 192 - 42, RecyclerView::Mode::HorizontalGrid);
recyclerView->SetPadding(10, 0);
recyclerView->SetItemSpacing(4, 4);
return recyclerView;
}
FileRecyclerAdapter* CreateRecyclerAdapter(
SharedPtr<FileRecyclerAdapter> CreateRecyclerAdapter(
RomBrowserViewModel* viewModel, const IThemeFileIconFactory* themeFileIconFactory,
const IRomBrowserViewFactory* romBrowserViewFactory, VBlankTextureLoader* vblankTextureLoader) const override
{
return new IconGridFileRecyclerAdapter(
return SharedPtr<IconGridFileRecyclerAdapter>::MakeShared(viewModel->GetRomBrowserController(),
&viewModel->GetFileInfoManager(), viewModel->GetIoTaskQueue(),
themeFileIconFactory, romBrowserViewFactory);
}

View File

@@ -10,26 +10,26 @@ public:
bool IsVertical() const override { return true; }
std::unique_ptr<AppBarView> CreateAppBarView(const IRomBrowserViewFactory* romBrowserViewFactory,
SharedPtr<AppBarView> CreateAppBarView(const IRomBrowserViewFactory* romBrowserViewFactory,
int startButtonCount, int endButtonCount) const override
{
return romBrowserViewFactory->CreateAppBarView(0, 0,
AppBarView::Orientation::Vertical, startButtonCount, endButtonCount);
}
std::unique_ptr<RecyclerViewBase> CreateRecyclerView(const IRomBrowserViewFactory* romBrowserViewFactory) const override
SharedPtr<RecyclerViewBase> CreateRecyclerView(const IRomBrowserViewFactory* romBrowserViewFactory) const override
{
auto recyclerView = std::make_unique<RecyclerView>(42, 0, 256 - 42, 192, RecyclerView::Mode::VerticalGrid);
auto recyclerView = RecyclerView::CreateShared(42, 0, 256 - 42, 192, RecyclerView::Mode::VerticalGrid);
recyclerView->SetPadding(0, 3);
recyclerView->SetItemSpacing(9, 3);
return recyclerView;
}
FileRecyclerAdapter* CreateRecyclerAdapter(
SharedPtr<FileRecyclerAdapter> CreateRecyclerAdapter(
RomBrowserViewModel* viewModel, const IThemeFileIconFactory* themeFileIconFactory,
const IRomBrowserViewFactory* romBrowserViewFactory, VBlankTextureLoader* vblankTextureLoader) const override
{
return new IconGridFileRecyclerAdapter(
return SharedPtr<IconGridFileRecyclerAdapter>::MakeShared(viewModel->GetRomBrowserController(),
&viewModel->GetFileInfoManager(), viewModel->GetIoTaskQueue(),
themeFileIconFactory, romBrowserViewFactory);
}

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -15,6 +15,34 @@ FileInfoManager::~FileInfoManager()
}
}
void FileInfoManager::LoadFileInfo(int index)
{
auto internalFileInfo = _extraFileInfo[index].internalFileInfo;
if (!internalFileInfo)
{
internalFileInfo = _items[index]->CreateInternalFileInfo();
}
if (!_extraFileInfo[index].fileCover.Lock())
{
_extraFileInfo[index].fileCover = SharedPtr(_coverRepository.GetCoverForFile(*_items[index], internalFileInfo));
}
_extraFileInfo[index].internalFileInfo = internalFileInfo;
}
void FileInfoManager::ReleaseFileInfo(int index)
{
auto internalFileInfo = _extraFileInfo[index].internalFileInfo;
if (internalFileInfo)
{
_extraFileInfo[index].internalFileInfo = nullptr;
delete internalFileInfo;
}
_extraFileInfo[index].fileCover.Reset();
}
int FileInfoManager::GetItemIndex(const char* fileName)
{
if (fileName == nullptr)

View File

@@ -4,7 +4,7 @@
#include "FileInfo.h"
#include "FileType/FileCover.h"
#include "ICoverRepository.h"
#include "core/SharedPtr.h"
#include "core/AtomicSharedPtr.h"
#include "FileType/InternalFileInfo.h"
class FileInfoManager
@@ -20,36 +20,12 @@ public:
SharedPtr<FileCover> GetFileCover(int index)
{
return _extraFileInfo[index].fileCover;
return _extraFileInfo[index].fileCover.Lock();
}
void LoadFileInfo(int index)
{
auto internalFileInfo = GetInternalFileInfo(index);
if (!internalFileInfo)
{
internalFileInfo = _items[index]->CreateInternalFileInfo();
}
void LoadFileInfo(int index);
if (!_extraFileInfo[index].fileCover.IsValid())
{
_extraFileInfo[index].fileCover = SharedPtr(_coverRepository.GetCoverForFile(*_items[index], internalFileInfo));
}
_extraFileInfo[index].internalFileInfo = internalFileInfo;
}
void ReleaseFileInfo(int index)
{
auto internalFileInfo = GetInternalFileInfo(index);
if (internalFileInfo)
{
delete internalFileInfo;
_extraFileInfo[index].internalFileInfo = nullptr;
}
_extraFileInfo[index].fileCover.Reset();
}
void ReleaseFileInfo(int index);
int GetItemIndex(const char* fileName);
@@ -60,7 +36,7 @@ private:
struct ExtraFileInfo
{
const InternalFileInfo* internalFileInfo;
SharedPtr<FileCover> fileCover;
AtomicSharedPtr<FileCover> fileCover;
};
std::unique_ptr<const FileInfo*[]> _items;

View File

@@ -8,11 +8,17 @@ u32 FileRecyclerAdapter::GetItemCount() const
return _fileInfoManager->GetItemCount();
}
void FileRecyclerAdapter::BindView(View* view, int index) const
void FileRecyclerAdapter::BindView(SharedPtr<View> view, int index) const
{
LOG_DEBUG("Binding %d\n", index);
_taskQueue->Enqueue([=, this] (const vu8& cancelRequested)
auto queueTask = _taskQueue->Enqueue([=, this] (const vu8& cancelRequested)
{
if (cancelRequested)
{
LOG_DEBUG("Task to load %d was canceled\n", index);
return TaskResult<void>::Canceled();
}
LOG_DEBUG("Started task to load %d\n", index);
_fileInfoManager->LoadFileInfo(index);
auto internalFileInfo = _fileInfoManager->GetInternalFileInfo(index);
@@ -23,4 +29,5 @@ void FileRecyclerAdapter::BindView(View* view, int index) const
}
return BindView(view, index, internalFileInfo, cancelRequested);
});
SetQueueTask(view, std::move(queueTask));
}

View File

@@ -7,12 +7,13 @@ class IVramManager;
class InternalFileInfo;
class IThemeFileIconFactory;
class VramContext;
class IRomBrowserController;
class FileRecyclerAdapter : public RecyclerAdapter
{
public:
u32 GetItemCount() const override;
void BindView(View* view, int index) const override;
void BindView(SharedPtr<View> view, int index) const override;
void SetIconFrameCounter(u32 iconFrameCounter)
{
@@ -22,16 +23,18 @@ public:
virtual void InitVram(const VramContext& vramContext) { }
protected:
IRomBrowserController* _romBrowserController;
FileInfoManager* _fileInfoManager;
TaskQueueBase* _taskQueue;
u32 _iconFrameCounter;
const IThemeFileIconFactory* _themeFileIconFactory;
FileRecyclerAdapter(FileInfoManager* fileInfoManager, TaskQueueBase* taskQueue,
const IThemeFileIconFactory* themeFileIconFactory)
: _fileInfoManager(fileInfoManager), _taskQueue(taskQueue)
FileRecyclerAdapter(IRomBrowserController* romBrowserController, FileInfoManager* fileInfoManager,
TaskQueueBase* taskQueue, const IThemeFileIconFactory* themeFileIconFactory)
: _romBrowserController(romBrowserController), _fileInfoManager(fileInfoManager), _taskQueue(taskQueue)
, _iconFrameCounter(0), _themeFileIconFactory(themeFileIconFactory) { }
virtual TaskResult<void> BindView(View* view, int index,
virtual TaskResult<void> BindView(SharedPtr<View> view, int index,
const InternalFileInfo* internalFileInfo, const vu8& cancelRequested) const = 0;
virtual void SetQueueTask(const SharedPtr<View>& view, QueueTask<void> queueTask) const = 0;
};

View File

@@ -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;

View File

@@ -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)

Some files were not shown because too many files have changed in this diff Show More