From dddee0bb948b47945d9125c7f03a359430a34fd2 Mon Sep 17 00:00:00 2001 From: Gericom Date: Sun, 22 Feb 2026 20:28:35 +0100 Subject: [PATCH] Initial work on implementing support for cheats --- arm9/gfx/checkboxChecked.grit | 7 + arm9/gfx/checkboxChecked.png | Bin 0 -> 203 bytes arm9/gfx/checkboxUnchecked.grit | 7 + arm9/gfx/checkboxUnchecked.png | Bin 0 -> 167 bytes arm9/gfx/folderIcon.grit | 7 + arm9/gfx/folderIcon.png | Bin 0 -> 177 bytes arm9/source/App.cpp | 14 +- arm9/source/cheats/Cheat.h | 28 +++ arm9/source/cheats/CheatCategory.h | 81 ++++++++ arm9/source/cheats/CheatTreeItem.h | 25 +++ arm9/source/cheats/GameCheats.h | 50 +++++ arm9/source/cheats/ICheatRepository.h | 13 ++ arm9/source/cheats/UsrCheatDat.h | 11 ++ arm9/source/cheats/UsrCheatRepository.cpp | 174 ++++++++++++++++++ arm9/source/cheats/UsrCheatRepository.h | 25 +++ .../cheats/UsrCheatRepositoryFactory.cpp | 87 +++++++++ .../source/cheats/UsrCheatRepositoryFactory.h | 9 + .../source/romBrowser/IRomBrowserController.h | 6 +- .../romBrowser/RomBrowserController.cpp | 17 +- arm9/source/romBrowser/RomBrowserController.h | 9 +- .../romBrowser/viewModels/CheatsViewModel.cpp | 66 +++++++ .../romBrowser/viewModels/CheatsViewModel.h | 35 ++++ .../viewModels/RomBrowserViewModel.cpp | 11 +- .../views/cheats/CheatListItemView.cpp | 47 +++++ .../views/cheats/CheatListItemView.h | 35 ++++ .../romBrowser/views/cheats/CheatsAdapter.h | 74 ++++++++ .../views/cheats/CheatsBottomSheetView.cpp | 103 +++++++++++ .../views/cheats/CheatsBottomSheetView.h | 42 +++++ 28 files changed, 966 insertions(+), 17 deletions(-) create mode 100644 arm9/gfx/checkboxChecked.grit create mode 100644 arm9/gfx/checkboxChecked.png create mode 100644 arm9/gfx/checkboxUnchecked.grit create mode 100644 arm9/gfx/checkboxUnchecked.png create mode 100644 arm9/gfx/folderIcon.grit create mode 100644 arm9/gfx/folderIcon.png create mode 100644 arm9/source/cheats/Cheat.h create mode 100644 arm9/source/cheats/CheatCategory.h create mode 100644 arm9/source/cheats/CheatTreeItem.h create mode 100644 arm9/source/cheats/GameCheats.h create mode 100644 arm9/source/cheats/ICheatRepository.h create mode 100644 arm9/source/cheats/UsrCheatDat.h create mode 100644 arm9/source/cheats/UsrCheatRepository.cpp create mode 100644 arm9/source/cheats/UsrCheatRepository.h create mode 100644 arm9/source/cheats/UsrCheatRepositoryFactory.cpp create mode 100644 arm9/source/cheats/UsrCheatRepositoryFactory.h create mode 100644 arm9/source/romBrowser/viewModels/CheatsViewModel.cpp create mode 100644 arm9/source/romBrowser/viewModels/CheatsViewModel.h create mode 100644 arm9/source/romBrowser/views/cheats/CheatListItemView.cpp create mode 100644 arm9/source/romBrowser/views/cheats/CheatListItemView.h create mode 100644 arm9/source/romBrowser/views/cheats/CheatsAdapter.h create mode 100644 arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.cpp create mode 100644 arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.h diff --git a/arm9/gfx/checkboxChecked.grit b/arm9/gfx/checkboxChecked.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/checkboxChecked.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/checkboxChecked.png b/arm9/gfx/checkboxChecked.png new file mode 100644 index 0000000000000000000000000000000000000000..a0d35869066518ded45672109c38646a8c6130f0 GIT binary patch literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPF94QSi?q3QxcI>ahG5ZAv>IEKO{MY;YV9$M_ yOaBk@Ea1|3e7|1fv3(1p*$4eIUD^?E*ckSv*dJSYJ5dp6B!j1`pUXO@geCwIds2b` literal 0 HcmV?d00001 diff --git a/arm9/gfx/checkboxUnchecked.grit b/arm9/gfx/checkboxUnchecked.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/checkboxUnchecked.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/checkboxUnchecked.png b/arm9/gfx/checkboxUnchecked.png new file mode 100644 index 0000000000000000000000000000000000000000..54b5f1ab9470844c729fa49619554e2aab5c07e1 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFAes_<()FN5LbL*&p#Mu-QN5+0j^_eSwjI<+rH3#=@)}K;;ac Lu6{1-oD!M^eAJ1YPH literal 0 HcmV?d00001 diff --git a/arm9/source/App.cpp b/arm9/source/App.cpp index 1099f68..e495712 100644 --- a/arm9/source/App.cpp +++ b/arm9/source/App.cpp @@ -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( - &_romBrowserController, &_theme->GetMaterialColorScheme(), _theme->GetFontRepository()); - gameInfoDialog->SetGraphics(_chipViewVram); - _dialogPresenter.ShowDialog(std::move(gameInfoDialog)); + // auto gameInfoDialog = std::make_unique( + // &_romBrowserController, &_theme->GetMaterialColorScheme(), _theme->GetFontRepository()); + // gameInfoDialog->SetGraphics(_chipViewVram); + // _dialogPresenter.ShowDialog(std::move(gameInfoDialog)); + + auto cheatsViewModel = std::make_unique(_romBrowserController.GetTriggerFileInfo(), &_romBrowserController); + auto cheatsDialog = std::make_unique( + std::move(cheatsViewModel), &_theme->GetMaterialColorScheme(), _theme->GetFontRepository(), &_focusManager); + _dialogPresenter.ShowDialog(std::move(cheatsDialog)); } void App::HandleHideGameInfoTrigger() diff --git a/arm9/source/cheats/Cheat.h b/arm9/source/cheats/Cheat.h new file mode 100644 index 0000000..a77b54e --- /dev/null +++ b/arm9/source/cheats/Cheat.h @@ -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; +}; diff --git a/arm9/source/cheats/CheatCategory.h b/arm9/source/cheats/CheatCategory.h new file mode 100644 index 0000000..733a6ea --- /dev/null +++ b/arm9/source/cheats/CheatCategory.h @@ -0,0 +1,81 @@ +#pragma once +#include +#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; +}; diff --git a/arm9/source/cheats/CheatTreeItem.h b/arm9/source/cheats/CheatTreeItem.h new file mode 100644 index 0000000..4037da5 --- /dev/null +++ b/arm9/source/cheats/CheatTreeItem.h @@ -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) { } +}; diff --git a/arm9/source/cheats/GameCheats.h b/arm9/source/cheats/GameCheats.h new file mode 100644 index 0000000..4ff1f29 --- /dev/null +++ b/arm9/source/cheats/GameCheats.h @@ -0,0 +1,50 @@ +#pragma once +#include "Cheat.h" +#include "CheatCategory.h" + +class GameCheats +{ +public: + GameCheats(std::unique_ptr 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 _cheatData; + const char* _gameName; + CheatCategory* _categories; + u32 _numberOfCategories; + Cheat* _cheats; + u32 _numberOfCheats; +}; diff --git a/arm9/source/cheats/ICheatRepository.h b/arm9/source/cheats/ICheatRepository.h new file mode 100644 index 0000000..cdfab67 --- /dev/null +++ b/arm9/source/cheats/ICheatRepository.h @@ -0,0 +1,13 @@ +#pragma once +#include "GameCheats.h" + +class ICheatRepository +{ +public: + virtual ~ICheatRepository() { } + + virtual std::unique_ptr GetCheatsForGame(u32 gameCode, u32 headerCrc32) const = 0; + +protected: + ICheatRepository() { } +}; diff --git a/arm9/source/cheats/UsrCheatDat.h b/arm9/source/cheats/UsrCheatDat.h new file mode 100644 index 0000000..a5d1080 --- /dev/null +++ b/arm9/source/cheats/UsrCheatDat.h @@ -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); diff --git a/arm9/source/cheats/UsrCheatRepository.cpp b/arm9/source/cheats/UsrCheatRepository.cpp new file mode 100644 index 0000000..f430a2e --- /dev/null +++ b/arm9/source/cheats/UsrCheatRepository.cpp @@ -0,0 +1,174 @@ +#include "common.h" +#include +#include +#include "UsrCheatRepository.h" + +std::unique_ptr 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(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(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; +} diff --git a/arm9/source/cheats/UsrCheatRepository.h b/arm9/source/cheats/UsrCheatRepository.h new file mode 100644 index 0000000..4274b45 --- /dev/null +++ b/arm9/source/cheats/UsrCheatRepository.h @@ -0,0 +1,25 @@ +#pragma once +#include +#include "fat/File.h" +#include "ICheatRepository.h" +#include "UsrCheatDat.h" + +class UsrCheatRepository : public ICheatRepository +{ +public: + UsrCheatRepository(std::unique_ptr usrCheatDatFile, + std::unique_ptr sortedIndices, u32 numberOfIndices) + : _usrCheatFile(std::move(usrCheatDatFile)) + , _sortedIndices(std::move(sortedIndices)), _numberOfIndices(numberOfIndices) { } + + std::unique_ptr GetCheatsForGame(u32 gameCode, u32 headerCrc32) const override; + +private: + std::unique_ptr _usrCheatFile; + std::unique_ptr _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; +}; diff --git a/arm9/source/cheats/UsrCheatRepositoryFactory.cpp b/arm9/source/cheats/UsrCheatRepositoryFactory.cpp new file mode 100644 index 0000000..0ee777e --- /dev/null +++ b/arm9/source/cheats/UsrCheatRepositoryFactory.cpp @@ -0,0 +1,87 @@ +#include "common.h" +#include +#include +#include "fat/File.h" +#include "UsrCheatDat.h" +#include "UsrCheatRepositoryFactory.h" + +std::unique_ptr UsrCheatRepositoryFactory::FromUsrCheatDat(const TCHAR* usrCheatDatPath) +{ + auto file = std::make_unique(); + 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(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(std::move(file), std::move(indices), numberOfIndices); +} diff --git a/arm9/source/cheats/UsrCheatRepositoryFactory.h b/arm9/source/cheats/UsrCheatRepositoryFactory.h new file mode 100644 index 0000000..dda8839 --- /dev/null +++ b/arm9/source/cheats/UsrCheatRepositoryFactory.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include "UsrCheatRepository.h" + +class UsrCheatRepositoryFactory +{ +public: + std::unique_ptr FromUsrCheatDat(const TCHAR* usrCheatDatPath); +}; diff --git a/arm9/source/romBrowser/IRomBrowserController.h b/arm9/source/romBrowser/IRomBrowserController.h index 3f7095f..4c4f6b6 100644 --- a/arm9/source/romBrowser/IRomBrowserController.h +++ b/arm9/source/romBrowser/IRomBrowserController.h @@ -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() { } diff --git a/arm9/source/romBrowser/RomBrowserController.cpp b/arm9/source/romBrowser/RomBrowserController.cpp index e50c9f3..e4dd551 100644 --- a/arm9/source/romBrowser/RomBrowserController.cpp +++ b/arm9/source/romBrowser/RomBrowserController.cpp @@ -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->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(); } diff --git a/arm9/source/romBrowser/RomBrowserController.h b/arm9/source/romBrowser/RomBrowserController.h index 0e87249..f24e48c 100644 --- a/arm9/source/romBrowser/RomBrowserController.h +++ b/arm9/source/romBrowser/RomBrowserController.h @@ -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 _navigateTask; bool _saveSettingsPending = false; std::unique_ptr _coverRepository; ExtensionFileTypeProvider _fileTypeProvider; + std::unique_ptr _cheatRepository; void HandleTrigger(); void HandleNavigateTrigger(); diff --git a/arm9/source/romBrowser/viewModels/CheatsViewModel.cpp b/arm9/source/romBrowser/viewModels/CheatsViewModel.cpp new file mode 100644 index 0000000..797e047 --- /dev/null +++ b/arm9/source/romBrowser/viewModels/CheatsViewModel.cpp @@ -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->Open(_romFileInfo.GetFastFileRef(), FA_READ); + auto headerBuffer = std::make_unique_for_overwrite(512); + if (!file->ReadExact(headerBuffer.get(), 512)) + { + LOG_ERROR("Could not read rom header\n"); + return TaskResult::Failed(); + } + file->Close(); + + if (cancelRequested) + { + return TaskResult::Canceled(); + } + + u32 gameCode = *(u32*)(headerBuffer.get() + 0xC); + u32 crc = crc32(headerBuffer.get(), 512); + headerBuffer.reset(); + + if (cancelRequested) + { + return TaskResult::Canceled(); + } + + _cheats = _romBrowserController->GetCheatRepository().GetCheatsForGame(gameCode, crc); + if (_cheats) + { + _state = State::DisplayCheats; + } + else + { + _state = State::NoCheats; + } + + return TaskResult::Completed(); + }); +} diff --git a/arm9/source/romBrowser/viewModels/CheatsViewModel.h b/arm9/source/romBrowser/viewModels/CheatsViewModel.h new file mode 100644 index 0000000..51ec564 --- /dev/null +++ b/arm9/source/romBrowser/viewModels/CheatsViewModel.h @@ -0,0 +1,35 @@ +#pragma once +#include +#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 _loadCheatsTask; + std::unique_ptr _cheats; + State _state = State::Loading; +}; diff --git a/arm9/source/romBrowser/viewModels/RomBrowserViewModel.cpp b/arm9/source/romBrowser/viewModels/RomBrowserViewModel.cpp index 0ad53a4..975ed08 100644 --- a/arm9/source/romBrowser/viewModels/RomBrowserViewModel.cpp +++ b/arm9/source/romBrowser/viewModels/RomBrowserViewModel.cpp @@ -1,5 +1,6 @@ #include "common.h" #include +#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(std::move(sortedFilteredFiles), filteredCount, _romBrowserController->GetCoverRepository()); + _fileInfoManager = std::make_unique(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); + } } \ No newline at end of file diff --git a/arm9/source/romBrowser/views/cheats/CheatListItemView.cpp b/arm9/source/romBrowser/views/cheats/CheatListItemView.cpp new file mode 100644 index 0000000..80f550f --- /dev/null +++ b/arm9/source/romBrowser/views/cheats/CheatListItemView.cpp @@ -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]); + } +} diff --git a/arm9/source/romBrowser/views/cheats/CheatListItemView.h b/arm9/source/romBrowser/views/cheats/CheatListItemView.h new file mode 100644 index 0000000..6168d21 --- /dev/null +++ b/arm9/source/romBrowser/views/cheats/CheatListItemView.h @@ -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; +}; diff --git a/arm9/source/romBrowser/views/cheats/CheatsAdapter.h b/arm9/source/romBrowser/views/cheats/CheatsAdapter.h new file mode 100644 index 0000000..80ac062 --- /dev/null +++ b/arm9/source/romBrowser/views/cheats/CheatsAdapter.h @@ -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(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; +}; diff --git a/arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.cpp b/arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.cpp new file mode 100644 index 0000000..4b4fb2d --- /dev/null +++ b/arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.cpp @@ -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 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; +} diff --git a/arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.h b/arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.h new file mode 100644 index 0000000..7d51394 --- /dev/null +++ b/arm9/source/romBrowser/views/cheats/CheatsBottomSheetView.h @@ -0,0 +1,42 @@ +#pragma once +#include +#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 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 _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; +};