Initial work on implementing support for cheats

This commit is contained in:
Gericom
2026-02-22 20:28:35 +01:00
parent f73c8b0547
commit dddee0bb94
28 changed files with 966 additions and 17 deletions

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"
@@ -295,10 +296,15 @@ void App::HandleTrigger(RomBrowserStateTrigger trigger, RomBrowserState newState
void App::HandleShowGameInfoTrigger()
{
auto gameInfoDialog = std::make_unique<NdsGameDetailsBottomSheetView>(
&_romBrowserController, &_theme->GetMaterialColorScheme(), _theme->GetFontRepository());
gameInfoDialog->SetGraphics(_chipViewVram);
_dialogPresenter.ShowDialog(std::move(gameInfoDialog));
// auto gameInfoDialog = std::make_unique<NdsGameDetailsBottomSheetView>(
// &_romBrowserController, &_theme->GetMaterialColorScheme(), _theme->GetFontRepository());
// gameInfoDialog->SetGraphics(_chipViewVram);
// _dialogPresenter.ShowDialog(std::move(gameInfoDialog));
auto cheatsViewModel = std::make_unique<CheatsViewModel>(_romBrowserController.GetTriggerFileInfo(), &_romBrowserController);
auto cheatsDialog = std::make_unique<CheatsBottomSheetView>(
std::move(cheatsViewModel), &_theme->GetMaterialColorScheme(), _theme->GetFontRepository(), &_focusManager);
_dialogPresenter.ShowDialog(std::move(cheatsDialog));
}
void App::HandleHideGameInfoTrigger()

View File

@@ -0,0 +1,28 @@
#pragma once
#include "CheatTreeItem.h"
class Cheat : public CheatTreeItem
{
public:
Cheat()
: _cheatData(nullptr), _cheatDataLength(0) { }
Cheat(const char* name, const char* description, bool isCheatActive, const void* cheatData, u32 cheatDataLength)
: CheatTreeItem(name, description), _isCheatActive(isCheatActive)
, _cheatData(cheatData), _cheatDataLength(cheatDataLength) { }
bool GetIsCheatActive() const
{
return _isCheatActive;
}
void SetIsCheatActive(bool isCheatActive)
{
_isCheatActive = isCheatActive;
}
private:
bool _isCheatActive;
const void* _cheatData;
u32 _cheatDataLength;
};

View File

@@ -0,0 +1,81 @@
#pragma once
#include <memory>
#include "CheatTreeItem.h"
#include "Cheat.h"
class CheatCategory : public CheatTreeItem
{
public:
CheatCategory()
: _isMaxOneCheatActive(false), _subCategories(nullptr), _numberOfSubCategories(0)
, _cheats(nullptr), _numberOfCheats(0) { }
CheatCategory(const char* name, const char* description, bool isMaxOneCheatActive,
CheatCategory* subCategories, u32 numberOfSubCategories, Cheat* cheats, u32 numberOfCheats)
: CheatTreeItem(name, description), _isMaxOneCheatActive(isMaxOneCheatActive)
, _subCategories(subCategories), _numberOfSubCategories(numberOfSubCategories)
, _cheats(cheats), _numberOfCheats(numberOfCheats) { }
CheatCategory(CheatCategory& other) = delete;
CheatCategory& operator=(CheatCategory& other) = delete;
CheatCategory(CheatCategory&& other)
{
*this = std::move(other);
}
CheatCategory& operator=(CheatCategory&& other)
{
_name = other._name;
other._name = nullptr;
_description = other._description;
other._description = nullptr;
_isMaxOneCheatActive = other._isMaxOneCheatActive;
other._isMaxOneCheatActive = false;
_subCategories = other._subCategories;
other._subCategories = nullptr;
_numberOfSubCategories = other._numberOfSubCategories;
other._numberOfSubCategories = 0;
_cheats = other._cheats;
other._cheats = nullptr;
_numberOfCheats = other._numberOfCheats;
other._numberOfCheats = 0;
return *this;
}
~CheatCategory()
{
if (_subCategories != nullptr)
{
free(_subCategories);
}
if (_cheats != nullptr)
{
free(_cheats);
}
}
bool GetIsMaxOneCheatActive() const
{
return _isMaxOneCheatActive;
}
const CheatCategory* GetSubCategories(u32& numberOfSubCategories) const
{
numberOfSubCategories = _numberOfSubCategories;
return _subCategories;
}
const Cheat* GetCheats(u32& numberOfCheats) const
{
numberOfCheats = _numberOfCheats;
return _cheats;
}
private:
bool _isMaxOneCheatActive;
CheatCategory* _subCategories;
u32 _numberOfSubCategories;
Cheat* _cheats;
u32 _numberOfCheats;
};

View File

@@ -0,0 +1,25 @@
#pragma once
class CheatTreeItem
{
public:
const char* GetName() const
{
return _name;
}
const char* GetDescription() const
{
return _description;
}
protected:
const char* _name;
const char* _description;
CheatTreeItem()
: _name(nullptr), _description(nullptr) { }
CheatTreeItem(const char* name, const char* description)
: _name(name), _description(description) { }
};

View File

@@ -0,0 +1,50 @@
#pragma once
#include "Cheat.h"
#include "CheatCategory.h"
class GameCheats
{
public:
GameCheats(std::unique_ptr<u8[]> cheatData, const char* gameName,
CheatCategory* categories, u32 numberOfCategories, Cheat* cheats, u32 numberOfCheats)
: _cheatData(std::move(cheatData)), _gameName(gameName)
, _categories(categories), _numberOfCategories(numberOfCategories)
, _cheats(cheats), _numberOfCheats(numberOfCheats) { }
~GameCheats()
{
if (_categories != nullptr)
{
free(_categories);
}
if (_cheats != nullptr)
{
free(_cheats);
}
}
const char* GetGameName() const
{
return _gameName;
}
const CheatCategory* GetCheatCategories(u32& numberOfCategories) const
{
numberOfCategories = _numberOfCategories;
return _categories;
}
const Cheat* GetCheats(u32& numberOfCheats) const
{
numberOfCheats = _numberOfCheats;
return _cheats;
}
private:
std::unique_ptr<u8[]> _cheatData;
const char* _gameName;
CheatCategory* _categories;
u32 _numberOfCategories;
Cheat* _cheats;
u32 _numberOfCheats;
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include "GameCheats.h"
class ICheatRepository
{
public:
virtual ~ICheatRepository() { }
virtual std::unique_ptr<GameCheats> GetCheatsForGame(u32 gameCode, u32 headerCrc32) const = 0;
protected:
ICheatRepository() { }
};

View File

@@ -0,0 +1,11 @@
#pragma once
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,174 @@
#include "common.h"
#include <algorithm>
#include <string.h>
#include "UsrCheatRepository.h"
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;
}
auto cheatData = std::make_unique_for_overwrite<u8[]>(index->padding); // padding was set to the size in UsrCheatRepositoryFactory
if (_usrCheatFile->Seek(index->offset) != FR_OK)
{
LOG_ERROR("Failed to seek to cheat data\n");
return nullptr;
}
if (!_usrCheatFile->ReadExact(cheatData.get(), index->padding))
{
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 categories = (CheatCategory*)malloc(totalNumberOfItems * sizeof(CheatCategory));
u32 categoryCount = 0;
auto cheats = (Cheat*)malloc(totalNumberOfItems * sizeof(Cheat));
u32 cheatCount = 0;
while (ptr < cheatData.get() + index->padding)
{
u32 itemFlags = *(u32*)ptr;
bool isCategory = ((itemFlags >> 28) & 1) == 1;
if (isCategory)
{
ParseCategory(categories[categoryCount], ptr);
categoryCount++;
}
else
{
ParseCheat(cheats[cheatCount], ptr);
cheatCount++;
}
}
categories = (CheatCategory*)realloc(categories, categoryCount * sizeof(CheatCategory));
cheats = (Cheat*)realloc(cheats, cheatCount * sizeof(Cheat));
return std::make_unique<GameCheats>(std::move(cheatData), gameName, categories, categoryCount, cheats, cheatCount);
}
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;
}
void UsrCheatRepository::ParseCategory(CheatCategory& category, 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 categories = (CheatCategory*)malloc(numberOfItems * sizeof(CheatCategory));
u32 categoryCount = 0;
auto cheats = (Cheat*)malloc(numberOfItems * sizeof(Cheat));
u32 cheatCount = 0;
for (u32 i = 0; i < numberOfItems; i++)
{
u32 itemFlags = *(u32*)ptr;
bool isCategory = ((itemFlags >> 28) & 1) == 1;
if (isCategory)
{
ParseCategory(categories[categoryCount], ptr);
categoryCount++;
}
else
{
ParseCheat(cheats[cheatCount], ptr);
cheatCount++;
}
}
categories = (CheatCategory*)realloc(categories, categoryCount * sizeof(CheatCategory));
cheats = (Cheat*)realloc(cheats, cheatCount * sizeof(Cheat));
category = CheatCategory(itemName, itemDescription, isMaxOneCheatActive, categories, categoryCount, cheats, cheatCount);
}
void UsrCheatRepository::ParseCheat(Cheat& cheat, u8*& ptr) const
{
// flags
u32 itemFlags = *(u32*)ptr;
ptr += 4;
bool isCheatActive = ((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
// number of code words
u32 numberOfCodeWords = *(u32*)ptr;
ptr += 4;
cheat = Cheat(itemName, itemDescription, isCheatActive, ptr, numberOfCodeWords * 4);
// code
ptr += numberOfCodeWords * 4;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <memory>
#include "fat/File.h"
#include "ICheatRepository.h"
#include "UsrCheatDat.h"
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(u32 gameCode, u32 headerCrc32) const override;
private:
std::unique_ptr<File> _usrCheatFile;
std::unique_ptr<usr_cheat_index_entry_t[]> _sortedIndices;
u32 _numberOfIndices;
const usr_cheat_index_entry_t* FindIndex(u32 gameCode, u32 headerCrc32) const;
void ParseCategory(CheatCategory& category, u8*& ptr) const;
void ParseCheat(Cheat& cheat, 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,9 @@
#pragma once
#include <memory>
#include "UsrCheatRepository.h"
class UsrCheatRepositoryFactory
{
public:
std::unique_ptr<UsrCheatRepository> FromUsrCheatDat(const TCHAR* usrCheatDatPath);
};

View File

@@ -8,6 +8,7 @@ class RomBrowserViewModel;
class FileInfo;
class TaskQueueBase;
class ICoverRepository;
class ICheatRepository;
class IRomBrowserController
{
@@ -17,7 +18,7 @@ public:
virtual void NavigateUp() = 0;
virtual void NavigateToPath(const TCHAR* name) = 0;
virtual void LaunchFile(const FileInfo& fileInfo) = 0;
virtual void ShowGameInfo() = 0;
virtual void ShowGameInfo(const FileInfo& fileInfo) = 0;
virtual void HideGameInfo() = 0;
virtual void ShowDisplaySettings() = 0;
virtual void HideDisplaySettings() = 0;
@@ -33,11 +34,14 @@ public:
virtual TaskQueueBase* GetIoTaskQueue() const = 0;
virtual TaskQueueBase* GetBgTaskQueue() const = 0;
virtual const ICoverRepository& GetCoverRepository() const = 0;
virtual const ICheatRepository& GetCheatRepository() const = 0;
virtual const RomBrowserDisplaySettings& GetRomBrowserDisplaySettings() const = 0;
virtual void SetRomBrowserDisplaySettings(
const RomBrowserDisplaySettings& romBrowserDisplaySettings) = 0;
virtual const FileInfo& GetTriggerFileInfo() const = 0;
};
inline IRomBrowserController::~IRomBrowserController() { }

View File

@@ -6,6 +6,7 @@
#include "FileType/FileType.h"
#include "SdFolderFactory.h"
#include "services/settings/IAppSettingsService.h"
#include "cheats/UsrCheatRepositoryFactory.h"
#include "RomBrowserController.h"
RomBrowserController::RomBrowserController(
@@ -23,14 +24,14 @@ void RomBrowserController::NavigateToPath(const TCHAR* name)
void RomBrowserController::LaunchFile(const FileInfo& fileInfo)
{
_launchFileInfo = FileInfo(fileInfo);
_triggerFileInfo = FileInfo(fileInfo);
_stateMachine.Fire(RomBrowserStateTrigger::Launch);
}
void RomBrowserController::ShowGameInfo()
void RomBrowserController::ShowGameInfo(const FileInfo& fileInfo)
{
// Currently disabled, as the game info panel is not complete
// _stateMachine.Fire(RomBrowserStateTrigger::ShowGameInfo);
_triggerFileInfo = FileInfo(fileInfo);
_stateMachine.Fire(RomBrowserStateTrigger::ShowGameInfo);
}
void RomBrowserController::HideGameInfo()
@@ -140,6 +141,10 @@ void RomBrowserController::HandleNavigateTrigger()
_coverRepository = std::make_unique<CoverRepository>();
_coverRepository->Initialize();
}
if (!_cheatRepository)
{
_cheatRepository = UsrCheatRepositoryFactory().FromUsrCheatDat("/_pico/usrcheat.dat");
}
u64 startTick = gTickCounter.GetValue();
_navigateFileName = nullptr;
@@ -182,7 +187,7 @@ void RomBrowserController::HandleLaunchTrigger()
int idx = strlcat(_navigatePath, "/", sizeof(_navigatePath));
if (_navigatePath[idx - 2] == '/')
_navigatePath[idx - 1] = 0;
strlcat(_navigatePath, _launchFileInfo.GetFileName(), sizeof(_navigatePath));
strlcat(_navigatePath, _triggerFileInfo.GetFileName(), sizeof(_navigatePath));
_appSettingsService->GetAppSettings().lastUsedFilePath = _navigatePath;
_appSettingsService->Save();
@@ -190,7 +195,7 @@ void RomBrowserController::HandleLaunchTrigger()
loadParams->savePath[0] = 0;
loadParams->arguments[0] = 0;
loadParams->argumentsLength = 0;
if (_launchFileInfo.GetFileType()->TrySetLaunchParameters(loadParams, _navigatePath))
if (_triggerFileInfo.GetFileType()->TrySetLaunchParameters(loadParams, _navigatePath))
{
gProcessManager.Goto<PicoLoaderProcess>();
}

View File

@@ -9,6 +9,7 @@
#include "CoverRepository.h"
#include "FileType/ExtensionFileTypeProvider.h"
#include "services/settings/IAppSettingsService.h"
#include "cheats/ICheatRepository.h"
class RomBrowserController : public IRomBrowserController
{
@@ -23,7 +24,7 @@ public:
void NavigateToPath(const TCHAR* name) override;
void LaunchFile(const FileInfo& fileInfo) override;
void ShowGameInfo() override;
void ShowGameInfo(const FileInfo& fileInfo) override;
void HideGameInfo() override;
void ShowDisplaySettings() override;
void HideDisplaySettings() override;
@@ -39,6 +40,7 @@ public:
TaskQueueBase* GetIoTaskQueue() const override { return _ioTaskQueue; }
TaskQueueBase* GetBgTaskQueue() const override { return _bgTaskQueue; }
const ICoverRepository& GetCoverRepository() const override { return *_coverRepository; }
const ICheatRepository& GetCheatRepository() const override { return *_cheatRepository; }
void SetRomBrowserDisplaySettings(const RomBrowserDisplaySettings& romBrowserDisplaySettings) override;
@@ -47,6 +49,8 @@ public:
return _appSettingsService->GetAppSettings().romBrowserDisplaySettings;
}
virtual const FileInfo& GetTriggerFileInfo() const override { return _triggerFileInfo; }
private:
IAppSettingsService* _appSettingsService;
TaskQueueBase* _ioTaskQueue;
@@ -58,11 +62,12 @@ private:
RomBrowserStateMachine _stateMachine;
TCHAR _navigatePath[256];
TCHAR* _navigateFileName;
FileInfo _launchFileInfo;
FileInfo _triggerFileInfo;
QueueTask<void> _navigateTask;
bool _saveSettingsPending = false;
std::unique_ptr<CoverRepository> _coverRepository;
ExtensionFileTypeProvider _fileTypeProvider;
std::unique_ptr<ICheatRepository> _cheatRepository;
void HandleTrigger();
void HandleNavigateTrigger();

View File

@@ -0,0 +1,66 @@
#include "common.h"
#include "cheats/ICheatRepository.h"
#include "fat/File.h"
#include "CheatsViewModel.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;
}
CheatsViewModel::CheatsViewModel(const FileInfo& romFileInfo, IRomBrowserController* romBrowserController)
: _romFileInfo(romFileInfo), _romBrowserController(romBrowserController)
{
_loadCheatsTask = _romBrowserController->GetIoTaskQueue()->Enqueue([this] (const vu8& cancelRequested)
{
LOG_DEBUG("%s\n", _romFileInfo.GetFileName());
auto file = std::make_unique<File>();
file->Open(_romFileInfo.GetFastFileRef(), 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 TaskResult<void>::Failed();
}
file->Close();
if (cancelRequested)
{
return TaskResult<void>::Canceled();
}
u32 gameCode = *(u32*)(headerBuffer.get() + 0xC);
u32 crc = crc32(headerBuffer.get(), 512);
headerBuffer.reset();
if (cancelRequested)
{
return TaskResult<void>::Canceled();
}
_cheats = _romBrowserController->GetCheatRepository().GetCheatsForGame(gameCode, crc);
if (_cheats)
{
_state = State::DisplayCheats;
}
else
{
_state = State::NoCheats;
}
return TaskResult<void>::Completed();
});
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include <memory>
#include "core/task/TaskQueue.h"
#include "cheats/GameCheats.h"
#include "romBrowser/FileInfo.h"
#include "romBrowser/IRomBrowserController.h"
/// @brief View model for the cheats screen.
class CheatsViewModel
{
public:
enum class State
{
Loading,
NoCheats,
DisplayCheats
};
CheatsViewModel(const FileInfo& romFileInfo, IRomBrowserController* romBrowserController);
void Close()
{
_romBrowserController->HideGameInfo();
}
State GetState() const { return _state; }
const GameCheats* GetCheats() const { return _cheats.get(); }
private:
FileInfo _romFileInfo;
IRomBrowserController* _romBrowserController;
QueueTask<void> _loadCheatsTask;
std::unique_ptr<GameCheats> _cheats;
State _state = State::Loading;
};

View File

@@ -1,5 +1,6 @@
#include "common.h"
#include <algorithm>
#include "romBrowser/FileType/Nds/NdsFileType.h"
#include "RomBrowserViewModel.h"
RomBrowserViewModel::RomBrowserViewModel(IRomBrowserController* romBrowserController, const char* initialSelectedFileName)
@@ -34,7 +35,8 @@ RomBrowserViewModel::RomBrowserViewModel(IRomBrowserController* romBrowserContro
auto sortedFilteredFiles = sdFolder.FilterAndSort(filterSortParams, filteredCount);
u64 endTick = gTickCounter.GetValue();
LOG_DEBUG("Filter + sort took: %d us\n", (u32)TickCounter::TicksToMicroSeconds(endTick - startTick));
_fileInfoManager = std::make_unique<FileInfoManager>(std::move(sortedFilteredFiles), filteredCount, _romBrowserController->GetCoverRepository());
_fileInfoManager = std::make_unique<FileInfoManager>(std::move(sortedFilteredFiles),
filteredCount, _romBrowserController->GetCoverRepository());
_selectedItem = _fileInfoManager->GetItemIndex(initialSelectedFileName);
}
@@ -59,7 +61,8 @@ void RomBrowserViewModel::NavigateUp()
void RomBrowserViewModel::ShowGameInfo()
{
const auto& item = _fileInfoManager->GetItem(_selectedItem);
if (item.GetFileType()->GetClassification() == FileTypeClassification::Folder)
return;
_romBrowserController->ShowGameInfo();
if (item.GetFileType() == &NdsFileType::sInstance)
{
_romBrowserController->ShowGameInfo(item);
}
}

View File

@@ -0,0 +1,47 @@
#include "common.h"
#include "themes/material/MaterialColorScheme.h"
#include "themes/IFontRepository.h"
#include "gui/palette/GradientPalette.h"
#include "gui/GraphicsContext.h"
#include "gui/OamBuilder.h"
#include "CheatListItemView.h"
#define ICON_X 20
#define ICON_Y 3
#define NAME_LABEL_X 40
#define NAME_LABEL_Y 4
CheatListItemView::CheatListItemView(const MaterialColorScheme* materialColorScheme, const IFontRepository* fontRepository)
: _nameLabel(200, 16, 64, fontRepository->GetFont(FontType::Regular10))
, _materialColorScheme(materialColorScheme)
{
_nameLabel.SetEllipsis(true);
AddChildTail(&_nameLabel);
}
void CheatListItemView::Update()
{
_nameLabel.SetPosition(NAME_LABEL_X, _position.y + NAME_LABEL_Y);
ViewContainer::Update();
}
void CheatListItemView::Draw(GraphicsContext& graphicsContext)
{
auto backColor = _materialColorScheme->GetColor(md::sys::color::surfaceContainerLow);
_nameLabel.SetBackgroundColor(backColor);
_nameLabel.SetForegroundColor(_materialColorScheme->onSurface);
ViewContainer::Draw(graphicsContext);
if (graphicsContext.IsVisible(Rectangle(_position.x + ICON_X, _position.y + ICON_Y, 16, 16)))
{
u32 iconPaletteRow = graphicsContext.GetPaletteManager().AllocRow(
GradientPalette(backColor, _materialColorScheme->primary),
_position.y + ICON_Y, _position.y + ICON_Y + 16);
auto iconOam = graphicsContext.GetOamManager().AllocOams(1);
OamBuilder::OamWithSize<16, 16>(_position.x + ICON_X, _position.y + ICON_Y, _iconVramOffset >> 7)
.WithPalette16(iconPaletteRow)
.WithPriority(graphicsContext.GetPriority())
.Build(iconOam[0]);
}
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include "gui/views/ViewContainer.h"
#include "gui/views/Label2DView.h"
class MaterialColorScheme;
class IFontRepository;
class CheatListItemView : public ViewContainer
{
public:
CheatListItemView(const MaterialColorScheme* materialColorScheme, const IFontRepository* fontRepository);
void Update() override;
void Draw(GraphicsContext& graphicsContext) override;
Rectangle GetBounds() const override
{
return Rectangle(_position.x, _position.y, 256, 24);
}
void SetName(const char* name)
{
_nameLabel.SetText(name);
}
void SetIcon(u32 iconVramOffset)
{
_iconVramOffset = iconVramOffset;
}
private:
Label2DView _nameLabel;
u32 _iconVramOffset = 0;
const MaterialColorScheme* _materialColorScheme;
};

View File

@@ -0,0 +1,74 @@
#pragma once
#include "gui/views/RecyclerAdapter.h"
#include "cheats/CheatCategory.h"
#include "cheats/Cheat.h"
#include "CheatListItemView.h"
class CheatsAdapter : public RecyclerAdapter
{
public:
CheatsAdapter(const CheatCategory* categories, u32 numberOfCategories, const Cheat* cheats, u32 numberOfCheats,
const MaterialColorScheme* materialColorScheme, const IFontRepository* fontRepository,
u32 folderIconVramOffset, u32 checkboxUncheckedIconVramOffset, u32 checkboxCheckedIconVramOffset)
: _categories(categories), _numberOfCategories(numberOfCategories), _cheats(cheats), _numberOfCheats(numberOfCheats)
, _materialColorScheme(materialColorScheme), _fontRepository(fontRepository)
, _folderIconVramOffset(folderIconVramOffset)
, _checkboxUncheckedIconVramOffset(checkboxUncheckedIconVramOffset)
, _checkboxCheckedIconVramOffset(checkboxCheckedIconVramOffset) { }
u32 GetItemCount() const override
{
return _numberOfCategories + _numberOfCheats;
}
void GetViewSize(int& width, int& height) const override
{
width = 256;
height = 24;
}
View* CreateView() const override
{
LOG_DEBUG("CheatsAdapter::CreateView\n");
return new CheatListItemView(_materialColorScheme, _fontRepository);
}
void DestroyView(View* view) const override
{
LOG_DEBUG("CheatsAdapter::DestroyView\n");
delete (CheatListItemView*)view;
}
void BindView(View* view, int index) const override
{
LOG_DEBUG("CheatsAdapter::BindView\n");
auto listItemView = static_cast<CheatListItemView*>(view);
if ((u32)index < _numberOfCategories)
{
listItemView->SetName(_categories[index].GetName());
listItemView->SetIcon(_folderIconVramOffset);
}
else
{
index -= _numberOfCategories;
listItemView->SetName(_cheats[index].GetName());
listItemView->SetIcon(_cheats[index].GetIsCheatActive() ? _checkboxCheckedIconVramOffset : _checkboxUncheckedIconVramOffset);
}
}
void ReleaseView(View* view, int index) const override
{
LOG_DEBUG("CheatsAdapter::ReleaseView\n");
}
private:
const CheatCategory* _categories;
u32 _numberOfCategories;
const Cheat* _cheats;
u32 _numberOfCheats;
const MaterialColorScheme* _materialColorScheme;
const IFontRepository* _fontRepository;
u32 _folderIconVramOffset;
u32 _checkboxUncheckedIconVramOffset;
u32 _checkboxCheckedIconVramOffset;
};

View File

@@ -0,0 +1,103 @@
#include "common.h"
#include "gui/GraphicsContext.h"
#include "themes/material/MaterialColorScheme.h"
#include "themes/IFontRepository.h"
#include "gui/input/InputProvider.h"
#include "gui/VramContext.h"
#include "folderIcon.h"
#include "checkboxChecked.h"
#include "checkboxUnchecked.h"
#include "CheatsBottomSheetView.h"
#define TITLE_LABEL_X 20
#define TITLE_LABEL_Y 16
CheatsBottomSheetView::CheatsBottomSheetView(std::unique_ptr<CheatsViewModel> viewModel,
const MaterialColorScheme* materialColorScheme, const IFontRepository* fontRepository,
FocusManager* focusManager)
: _viewModel(std::move(viewModel))
, _titleLabel(128, 16, 25, fontRepository->GetFont(FontType::Medium11))
, _cheatListRecycler(0, 36, 256, 124, RecyclerView::Mode::VerticalList)
, _materialColorScheme(materialColorScheme)
, _fontRepository(fontRepository)
, _focusManager(focusManager)
{
_titleLabel.SetText(u"Cheats");
AddChildTail(&_titleLabel);
AddChildTail(&_cheatListRecycler);
}
void CheatsBottomSheetView::InitVram(const VramContext& vramContext)
{
BottomSheetView::InitVram(vramContext);
const auto objVramManager = vramContext.GetObjVramManager();
if (objVramManager)
{
_folderIconVramOffset = objVramManager->Alloc(folderIconTilesLen);
dma_ntrCopy32(3, folderIconTiles,
objVramManager->GetVramAddress(_folderIconVramOffset), folderIconTilesLen);
_checkboxUncheckedIconVramOffset = objVramManager->Alloc(checkboxUncheckedTilesLen);
dma_ntrCopy32(3, checkboxUncheckedTiles,
objVramManager->GetVramAddress(_checkboxUncheckedIconVramOffset), checkboxUncheckedTilesLen);
_checkboxCheckedIconVramOffset = objVramManager->Alloc(checkboxCheckedTilesLen);
dma_ntrCopy32(3, checkboxCheckedTiles,
objVramManager->GetVramAddress(_checkboxCheckedIconVramOffset), checkboxCheckedTilesLen);
}
_objVramManager = vramContext.GetObjVramManager();
}
void CheatsBottomSheetView::Update()
{
_titleLabel.SetPosition(TITLE_LABEL_X, _position.y + TITLE_LABEL_Y);
_cheatListRecycler.SetPosition(0, _position.y + 36);
if (_viewModel->GetState() == CheatsViewModel::State::DisplayCheats)
{
if (_cheatsAdapter == nullptr)
{
LOG_DEBUG("Setting adapter\n");
auto gameCheats = _viewModel->GetCheats();
u32 numberOfCategories = 0;
auto categories = gameCheats->GetCheatCategories(numberOfCategories);
u32 numberOfCheats = 0;
auto cheats = gameCheats->GetCheats(numberOfCheats);
_cheatsAdapter = new CheatsAdapter(categories, numberOfCategories, cheats, numberOfCheats,
_materialColorScheme, _fontRepository, _folderIconVramOffset,
_checkboxUncheckedIconVramOffset, _checkboxCheckedIconVramOffset);
_cheatListRecycler.SetAdapter(_cheatsAdapter);
_cheatListRecycler.InitVram(VramContext(nullptr, _objVramManager, nullptr, nullptr));
_cheatListRecycler.Focus(*_focusManager);
LOG_DEBUG("Setting adapter done\n");
}
}
BottomSheetView::Update();
}
void CheatsBottomSheetView::Draw(GraphicsContext& graphicsContext)
{
graphicsContext.SetClipArea(GetBounds());
u32 oldPrio = graphicsContext.SetPriority(1);
{
_titleLabel.SetBackgroundColor(_materialColorScheme->GetColor(md::sys::color::surfaceContainerLow));
_titleLabel.SetForegroundColor(_materialColorScheme->onSurface);
_titleLabel.Draw(graphicsContext);
graphicsContext.SetClipArea(_cheatListRecycler.GetBounds());
_cheatListRecycler.Draw(graphicsContext);
// BottomSheetView::Draw(graphicsContext);
}
graphicsContext.SetPriority(oldPrio);
graphicsContext.ResetClipArea();
}
bool CheatsBottomSheetView::HandleInput(const InputProvider& inputProvider, FocusManager& focusManager)
{
if (inputProvider.Triggered(InputKey::B))
{
_viewModel->Close();
return true;
}
return false;
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <memory>
#include "romBrowser/views/BottomSheetView.h"
#include "gui/views/Label2DView.h"
#include "gui/views/RecyclerView.h"
#include "romBrowser/viewModels/CheatsViewModel.h"
#include "CheatsAdapter.h"
class MaterialColorScheme;
class IFontRepository;
class IVramManager;
class CheatsBottomSheetView : public BottomSheetView
{
public:
CheatsBottomSheetView(std::unique_ptr<CheatsViewModel> viewModel,
const MaterialColorScheme* materialColorScheme, const IFontRepository* fontRepository,
FocusManager* focusManager);
void InitVram(const VramContext& vramContext) override;
void Update() override;
void Draw(GraphicsContext& graphicsContext) override;
bool HandleInput(const InputProvider& inputProvider, FocusManager& focusManager) override;
void Focus(FocusManager& focusManager) override
{
_cheatListRecycler.Focus(focusManager);
}
private:
std::unique_ptr<CheatsViewModel> _viewModel;
Label2DView _titleLabel;
RecyclerView _cheatListRecycler;
CheatsAdapter* _cheatsAdapter = nullptr;
const MaterialColorScheme* _materialColorScheme;
const IFontRepository* _fontRepository;
IVramManager* _objVramManager;
FocusManager* _focusManager;
u32 _folderIconVramOffset = 0;
u32 _checkboxUncheckedIconVramOffset = 0;
u32 _checkboxCheckedIconVramOffset = 0;
};