mirror of
https://github.com/LNH-team/pico-launcher.git
synced 2026-06-02 09:06:54 +02:00
Initial work on implementing support for cheats
This commit is contained in:
28
arm9/source/cheats/Cheat.h
Normal file
28
arm9/source/cheats/Cheat.h
Normal 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;
|
||||
};
|
||||
81
arm9/source/cheats/CheatCategory.h
Normal file
81
arm9/source/cheats/CheatCategory.h
Normal 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;
|
||||
};
|
||||
25
arm9/source/cheats/CheatTreeItem.h
Normal file
25
arm9/source/cheats/CheatTreeItem.h
Normal 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) { }
|
||||
};
|
||||
50
arm9/source/cheats/GameCheats.h
Normal file
50
arm9/source/cheats/GameCheats.h
Normal 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;
|
||||
};
|
||||
13
arm9/source/cheats/ICheatRepository.h
Normal file
13
arm9/source/cheats/ICheatRepository.h
Normal 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() { }
|
||||
};
|
||||
11
arm9/source/cheats/UsrCheatDat.h
Normal file
11
arm9/source/cheats/UsrCheatDat.h
Normal 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);
|
||||
174
arm9/source/cheats/UsrCheatRepository.cpp
Normal file
174
arm9/source/cheats/UsrCheatRepository.cpp
Normal 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;
|
||||
}
|
||||
25
arm9/source/cheats/UsrCheatRepository.h
Normal file
25
arm9/source/cheats/UsrCheatRepository.h
Normal 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;
|
||||
};
|
||||
87
arm9/source/cheats/UsrCheatRepositoryFactory.cpp
Normal file
87
arm9/source/cheats/UsrCheatRepositoryFactory.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "common.h"
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
#include "fat/File.h"
|
||||
#include "UsrCheatDat.h"
|
||||
#include "UsrCheatRepositoryFactory.h"
|
||||
|
||||
std::unique_ptr<UsrCheatRepository> UsrCheatRepositoryFactory::FromUsrCheatDat(const TCHAR* usrCheatDatPath)
|
||||
{
|
||||
auto file = std::make_unique<File>();
|
||||
if (file->Open(usrCheatDatPath, FA_READ | FA_WRITE) != FR_OK)
|
||||
{
|
||||
LOG_ERROR("Failed to open %s\n", usrCheatDatPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char header[12];
|
||||
if (!file->ReadExact(header, sizeof(header)))
|
||||
{
|
||||
LOG_ERROR("Failed to read usr cheat header\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (memcmp(header, "R4 CheatCode", sizeof(header)))
|
||||
{
|
||||
LOG_ERROR("Invalid usr cheat header\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Read index
|
||||
if (file->Seek(0x100) != FR_OK)
|
||||
{
|
||||
LOG_ERROR("Failed to seek to usr cheat index\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
usr_cheat_index_entry_t firstEntry;
|
||||
if (!file->ReadExact(&firstEntry, sizeof(firstEntry)))
|
||||
{
|
||||
LOG_ERROR("Failed to read first index\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 numberOfIndices = 0;
|
||||
if (firstEntry.offset != 0)
|
||||
{
|
||||
numberOfIndices = (firstEntry.offset - 0x100 - sizeof(usr_cheat_index_entry_t)) / sizeof(usr_cheat_index_entry_t);
|
||||
}
|
||||
|
||||
if (file->Seek(0x100) != FR_OK)
|
||||
{
|
||||
LOG_ERROR("Failed to seek to usr cheat index\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto indices = std::make_unique_for_overwrite<usr_cheat_index_entry_t[]>(numberOfIndices);
|
||||
if (!file->ReadExact(indices.get(), numberOfIndices * sizeof(usr_cheat_index_entry_t)))
|
||||
{
|
||||
LOG_ERROR("Failed to read first index\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Set padding field to the size of the cheat data
|
||||
if (numberOfIndices > 0)
|
||||
{
|
||||
for (u32 i = 0; i < numberOfIndices - 1; i++)
|
||||
{
|
||||
indices[i].padding = indices[i + 1].offset - indices[i].offset;
|
||||
}
|
||||
|
||||
indices[numberOfIndices - 1].padding = file->GetSize() - indices[numberOfIndices - 1].offset;
|
||||
}
|
||||
|
||||
// sort by gameCode, then by headerCrc32
|
||||
std::sort(indices.get(), indices.get() + numberOfIndices,
|
||||
[] (const usr_cheat_index_entry_t& a, const usr_cheat_index_entry_t& b)
|
||||
{
|
||||
if (a.gameCode != b.gameCode)
|
||||
{
|
||||
return a.gameCode < b.gameCode;
|
||||
}
|
||||
|
||||
return a.headerCrc32 < b.headerCrc32;
|
||||
});
|
||||
|
||||
return std::make_unique<UsrCheatRepository>(std::move(file), std::move(indices), numberOfIndices);
|
||||
}
|
||||
9
arm9/source/cheats/UsrCheatRepositoryFactory.h
Normal file
9
arm9/source/cheats/UsrCheatRepositoryFactory.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "UsrCheatRepository.h"
|
||||
|
||||
class UsrCheatRepositoryFactory
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<UsrCheatRepository> FromUsrCheatDat(const TCHAR* usrCheatDatPath);
|
||||
};
|
||||
Reference in New Issue
Block a user