Commit f8d1d08e authored by Arisotura's avatar Arisotura
Browse files

(finally) build the goddamn cheat interface

parent 4299ef5f
......@@ -35,8 +35,21 @@ ARCodeFile::ARCodeFile(const char* filename)
Categories.clear();
if (!Load())
Error = true;
}
ARCodeFile::~ARCodeFile()
{
Categories.clear();
}
bool ARCodeFile::Load()
{
FILE* f = Platform::OpenFile(Filename, "r");
if (!f) return;
if (!f) return true;
Categories.clear();
bool isincat = false;
ARCodeCat curcat;
......@@ -60,15 +73,14 @@ ARCodeFile::ARCodeFile(const char* filename)
if (!strncasecmp(start, "CAT", 3))
{
char catname[128];
int ret = sscanf(start, "CAT %127[^\n]", catname);
int ret = sscanf(start, "CAT %127[^\r\n]", catname);
catname[127] = '\0';
if (ret < 1)
{
printf("AR: malformed CAT line: %s\n", start);
fclose(f);
Error = true;
return;
return false;
}
if (isincode) curcat.Codes.push_back(curcode);
......@@ -84,23 +96,21 @@ ARCodeFile::ARCodeFile(const char* filename)
{
int enable;
char codename[128];
int ret = sscanf(start, "CODE %d %127[^\n]", &enable, codename);
int ret = sscanf(start, "CODE %d %127[^\r\n]", &enable, codename);
codename[127] = '\0';
if (ret < 2)
{
printf("AR: malformed CODE line: %s\n", start);
fclose(f);
Error = true;
return;
return false;
}
if (!isincat)
{
printf("AR: encountered CODE line with no category started\n");
fclose(f);
Error = true;
return;
return false;
}
if (isincode) curcat.Codes.push_back(curcode);
......@@ -119,24 +129,21 @@ ARCodeFile::ARCodeFile(const char* filename)
{
printf("AR: malformed data line: %s\n", start);
fclose(f);
Error = true;
return;
return false;
}
if (!isincode)
{
printf("AR: encountered data line with no code started\n");
fclose(f);
Error = true;
return;
return false;
}
if (curcode.CodeLen >= 2*64)
{
printf("AR: code too long!\n");
fclose(f);
Error = true;
return;
return false;
}
u32 idx = curcode.CodeLen;
......@@ -150,27 +157,35 @@ ARCodeFile::ARCodeFile(const char* filename)
if (isincat) Categories.push_back(curcat);
fclose(f);
return true;
}
bool ARCodeFile::Save()
{
FILE* f = Platform::OpenFile(Filename, "w");
if (!f) return false;
printf("PARSED OUTPUT\n");
for (ARCodeCatList::iterator it = Categories.begin(); it != Categories.end(); it++)
{
ARCodeCat& cat = *it;
printf("CAT %s\n", cat.Name);
if (it != Categories.begin()) fprintf(f, "\n");
fprintf(f, "CAT %s\n\n", cat.Name);
for (ARCodeList::iterator jt = cat.Codes.begin(); jt != cat.Codes.end(); jt++)
{
ARCode& code = *jt;
printf("CODE %d %s\n", code.Enabled, code.Name);
fprintf(f, "CODE %d %s\n", code.Enabled, code.Name);
for (u32 i = 0; i < code.CodeLen; i+=2)
{
printf("%08X %08X\n", code.Code[i], code.Code[i+1]);
fprintf(f, "%08X %08X\n", code.Code[i], code.Code[i+1]);
}
fprintf(f, "\n");
}
}
}
ARCodeFile::~ARCodeFile()
{
//
fclose(f);
return true;
}
......@@ -19,7 +19,7 @@
#ifndef ARCODEFILE_H
#define ARCODEFILE_H
#include <vector>
#include <list>
#include "types.h"
......@@ -32,7 +32,7 @@ typedef struct
} ARCode;
typedef std::vector<ARCode> ARCodeList;
typedef std::list<ARCode> ARCodeList;
typedef struct
{
......@@ -41,7 +41,7 @@ typedef struct
} ARCodeCat;
typedef std::vector<ARCodeCat> ARCodeCatList;
typedef std::list<ARCodeCat> ARCodeCatList;
class ARCodeFile
......@@ -52,6 +52,7 @@ public:
bool Error;
bool Load();
bool Save();
ARCodeCatList Categories;
......
......@@ -104,7 +104,7 @@ void Load()
while (!feof(f))
{
fgets(linebuf, 1024, f);
int ret = sscanf(linebuf, "%31[A-Za-z_0-9]=%[^\t\n]", entryname, entryval);
int ret = sscanf(linebuf, "%31[A-Za-z_0-9]=%[^\t\r\n]", entryname, entryval);
entryname[31] = '\0';
if (ret < 2) continue;
......
......@@ -67,6 +67,9 @@ extern bool SavestateLoaded;
// initialize the ROM handling utility
void Init_ROM();
// deinitialize the ROM handling utility
void DeInit_ROM();
// load the BIOS/firmware and boot from it
int LoadBIOS();
......@@ -97,6 +100,9 @@ bool SaveState(const char* filename);
// undo the latest savestate load
void UndoStateLoad();
// enable or disable cheats
void EnableCheats(bool enable);
// setup the display layout based on the provided display size and parameters
// * screenWidth/screenHeight: size of the host display
......
......@@ -27,6 +27,8 @@
#include "NDS.h"
#include "GBACart.h"
#include "AREngine.h"
namespace Frontend
{
......@@ -37,6 +39,9 @@ char PrevSRAMPath[ROMSlot_MAX][1024]; // for savestate 'undo load'
bool SavestateLoaded;
ARCodeFile* CheatFile;
bool CheatsOn;
void Init_ROM()
{
......@@ -48,6 +53,18 @@ void Init_ROM()
memset(SRAMPath[ROMSlot_GBA], 0, 1024);
memset(PrevSRAMPath[ROMSlot_NDS], 0, 1024);
memset(PrevSRAMPath[ROMSlot_GBA], 0, 1024);
CheatFile = nullptr;
CheatsOn = false;
}
void DeInit_ROM()
{
if (CheatFile)
{
delete CheatFile;
CheatFile = nullptr;
}
}
// TODO: currently, when failing to load a ROM for whatever reason, we attempt
......@@ -198,6 +215,25 @@ int VerifyDSiNAND()
return Load_OK;
}
void LoadCheats()
{
if (CheatFile)
{
delete CheatFile;
CheatFile = nullptr;
}
char filename[1024];
strncpy(filename, ROMPath[ROMSlot_NDS], 1023);
filename[1023] = '\0';
strncpy(filename + strlen(ROMPath[ROMSlot_NDS]) - 3, "mch", 3);
// TODO: check for error (malformed cheat file, ...)
CheatFile = new ARCodeFile(filename);
AREngine::SetCodeFile(CheatsOn ? CheatFile : nullptr);
}
int LoadBIOS()
{
int res;
......@@ -235,6 +271,8 @@ int LoadBIOS()
SavestateLoaded = false;
LoadCheats();
return Load_OK;
}
......@@ -295,6 +333,8 @@ int LoadROM(const char* file, int slot)
{
SavestateLoaded = false;
LoadCheats();
// Reload the inserted GBA cartridge (if any)
// TODO: report failure there??
if (ROMPath[ROMSlot_GBA][0] != '\0') NDS::LoadGBAROM(ROMPath[ROMSlot_GBA], SRAMPath[ROMSlot_GBA]);
......@@ -387,6 +427,8 @@ int Reset()
return Load_ROMLoadError;
}
LoadCheats();
return Load_OK;
}
......@@ -539,4 +581,11 @@ void UndoStateLoad()
}
}
void EnableCheats(bool enable)
{
CheatsOn = enable;
if (CheatFile)
AREngine::SetCodeFile(CheatsOn ? CheatFile : nullptr);
}
}
......@@ -18,6 +18,7 @@
#include <stdio.h>
#include <QFileDialog>
#include <QMessageBox>
#include "types.h"
#include "Platform.h"
......@@ -32,30 +33,380 @@ CheatsDialog* CheatsDialog::currentDlg = nullptr;
extern char* EmuDirectory;
namespace Frontend { extern ARCodeFile* CheatFile; }
CheatsDialog::CheatsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CheatsDialog)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
// setup UI here
codeFile = Frontend::CheatFile;
QStandardItemModel* model = new QStandardItemModel();
ui->tvCodeList->setModel(model);
connect(model, &QStandardItemModel::itemChanged, this, &CheatsDialog::onCheatEntryModified);
connect(ui->tvCodeList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &CheatsDialog::onCheatSelectionChanged);
{
QStandardItem* root = model->invisibleRootItem();
for (ARCodeCatList::iterator i = codeFile->Categories.begin(); i != codeFile->Categories.end(); i++)
{
ARCodeCat& cat = *i;
QStandardItem* catitem = new QStandardItem(cat.Name);
catitem->setEditable(true);
catitem->setData(QVariant::fromValue(i));
root->appendRow(catitem);
for (ARCodeList::iterator j = cat.Codes.begin(); j != cat.Codes.end(); j++)
{
ARCode& code = *j;
QStandardItem* codeitem = new QStandardItem(code.Name);
codeitem->setEditable(true);
codeitem->setCheckable(true);
codeitem->setCheckState(code.Enabled ? Qt::Checked : Qt::Unchecked);
codeitem->setData(QVariant::fromValue(j));
catitem->appendRow(codeitem);
}
}
}
ui->txtCode->setPlaceholderText("");
codeChecker = new ARCodeChecker(ui->txtCode->document());
ui->btnNewARCode->setEnabled(false);
ui->btnDeleteCode->setEnabled(false);
ui->txtCode->setEnabled(false);
}
CheatsDialog::~CheatsDialog()
{
QAbstractItemModel* model = ui->tvCodeList->model();
ui->tvCodeList->setModel(nullptr);
delete model;
delete codeChecker;
delete ui;
}
void CheatsDialog::on_CheatsDialog_accepted()
{
// save shit here
codeFile->Save();
closeDlg();
}
void CheatsDialog::on_CheatsDialog_rejected()
{
// don't save shit here
codeFile->Load();
closeDlg();
}
void CheatsDialog::on_btnNewCat_clicked()
{
QStandardItem* root = ((QStandardItemModel*)ui->tvCodeList->model())->invisibleRootItem();
ARCodeCat cat;
cat.Codes.clear();
memset(cat.Name, 0, 128);
strncpy(cat.Name, "(new category)", 127);
codeFile->Categories.push_back(cat);
ARCodeCatList::iterator id = codeFile->Categories.end(); id--;
QStandardItem* catitem = new QStandardItem(cat.Name);
catitem->setEditable(true);
catitem->setData(QVariant::fromValue(id));
root->appendRow(catitem);
ui->tvCodeList->selectionModel()->select(catitem->index(), QItemSelectionModel::ClearAndSelect);
ui->tvCodeList->edit(catitem->index());
}
void CheatsDialog::on_btnNewARCode_clicked()
{
QModelIndexList indices = ui->tvCodeList->selectionModel()->selectedIndexes();
if (indices.isEmpty())
{
// ????
return;
}
QStandardItemModel* model = (QStandardItemModel*)ui->tvCodeList->model();
QStandardItem* item = model->itemFromIndex(indices.first());
QStandardItem* parentitem;
QVariant data = item->data();
if (data.canConvert<ARCodeCatList::iterator>())
{
parentitem = item;
}
else if (data.canConvert<ARCodeList::iterator>())
{
parentitem = item->parent();
}
else
{
printf("what?? :(\n");
return;
}
ARCodeCatList::iterator it_cat = parentitem->data().value<ARCodeCatList::iterator>();
ARCodeCat& cat = *it_cat;
ARCode code;
memset(code.Name, 0, 128);
strncpy(code.Name, "(new AR code)", 127);
code.Enabled = true;
code.CodeLen = 0;
memset(code.Code, 0, sizeof(code.Code));
cat.Codes.push_back(code);
ARCodeList::iterator id = cat.Codes.end(); id--;
QStandardItem* codeitem = new QStandardItem(code.Name);
codeitem->setEditable(true);
codeitem->setCheckable(true);
codeitem->setCheckState(code.Enabled ? Qt::Checked : Qt::Unchecked);
codeitem->setData(QVariant::fromValue(id));
parentitem->appendRow(codeitem);
ui->tvCodeList->selectionModel()->select(codeitem->index(), QItemSelectionModel::ClearAndSelect);
ui->tvCodeList->edit(codeitem->index());
}
void CheatsDialog::on_btnDeleteCode_clicked()
{
QModelIndexList indices = ui->tvCodeList->selectionModel()->selectedIndexes();
if (indices.isEmpty())
{
// ????
return;
}
QMessageBox::StandardButton res = QMessageBox::question(this,
"Confirm deletion",
"Really delete the selected item?",
QMessageBox::Yes|QMessageBox::No,
QMessageBox::No);
if (res != QMessageBox::Yes) return;
QStandardItemModel* model = (QStandardItemModel*)ui->tvCodeList->model();
QStandardItem* item = model->itemFromIndex(indices.first());
QVariant data = item->data();
if (data.canConvert<ARCodeCatList::iterator>())
{
ARCodeCatList::iterator it_cat = data.value<ARCodeCatList::iterator>();
(*it_cat).Codes.clear();
codeFile->Categories.erase(it_cat);
model->invisibleRootItem()->removeRow(item->row());
}
else if (data.canConvert<ARCodeList::iterator>())
{
ARCodeList::iterator it_code = data.value<ARCodeList::iterator>();
ARCodeCatList::iterator it_cat = item->parent()->data().value<ARCodeCatList::iterator>();
(*it_cat).Codes.erase(it_code);
item->parent()->removeRow(item->row());
}
}
void CheatsDialog::onCheatSelectionChanged(const QItemSelection& sel, const QItemSelection& desel)
{
QModelIndexList indices = sel.indexes();
if (indices.isEmpty())
{
ui->btnNewARCode->setEnabled(false);
ui->btnDeleteCode->setEnabled(false);
ui->txtCode->setEnabled(false);
ui->txtCode->setPlaceholderText("");
ui->txtCode->clear();
}
else
{
QStandardItem* item = ((QStandardItemModel*)ui->tvCodeList->model())->itemFromIndex(indices.first());
QVariant data = item->data();
if (data.canConvert<ARCodeCatList::iterator>())
{
ui->btnDeleteCode->setEnabled(true);
ui->txtCode->setEnabled(false);
ui->txtCode->setPlaceholderText("");
ui->txtCode->clear();
}
else if (data.canConvert<ARCodeList::iterator>())
{
ARCode& code = *(data.value<ARCodeList::iterator>());
ui->btnDeleteCode->setEnabled(true);
ui->txtCode->setEnabled(true);
ui->txtCode->setPlaceholderText("(enter AR code here)");
QString codestr = "";
for (u32 i = 0; i < code.CodeLen; i += 2)
{
u32 c0 = code.Code[i+0];
u32 c1 = code.Code[i+1];
//codestr += QString("%1 %2\n").arg(c0, 8, 16, '0').arg(c1, 8, 16, '0').toUpper();
codestr += QString::asprintf("%08X %08X\n", c0, c1);
}
ui->txtCode->setPlainText(codestr);
}
ui->btnNewARCode->setEnabled(true);
}
}
void CheatsDialog::onCheatEntryModified(QStandardItem* item)
{
QVariant data = item->data();
if (data.canConvert<ARCodeCatList::iterator>())
{
ARCodeCat& cat = *(data.value<ARCodeCatList::iterator>());
if (item->text().isEmpty())
{
QString oldname = QString(cat.Name);
item->setText(oldname.isEmpty() ? "(blank category name??)" : oldname);
}
else
{
strncpy(cat.Name, item->text().toStdString().c_str(), 127);
cat.Name[127] = '\0';
}
}
else if (data.canConvert<ARCodeList::iterator>())
{
ARCode& code = *(data.value<ARCodeList::iterator>());
if (item->text().isEmpty())
{
QString oldname = QString(code.Name);
item->setText(oldname.isEmpty() ? "(blank code name??)" : oldname);
}
else
{
strncpy(code.Name, item->text().toStdString().c_str(), 127);
code.Name[127] = '\0';
}
code.Enabled = (item->checkState() == Qt::Checked);
}
}
void CheatsDialog::on_txtCode_textChanged()
{
QModelIndexList indices = ui->tvCodeList->selectionModel()->selectedIndexes();
if (indices.isEmpty())
return;
QStandardItem* item = ((QStandardItemModel*)ui->tvCodeList->model())->itemFromIndex(indices.first());
QVariant data = item->data();
if (!data.canConvert<ARCodeList::iterator>())
return;
bool error = false;
u32 codeout[2*64];
u32 codelen = 0;
QString text = ui->txtCode->document()->toPlainText();
QStringList lines = text.split('\n', QString::SkipEmptyParts);
for (QStringList::iterator it = lines.begin(); it != lines.end(); it++)
{
QString line = *it;
line = line.trimmed();
if (line.isEmpty()) continue;
if (line.length() > 17)
{
error = true;
break;
}
QStringList numbers = line.split(' ');
if (numbers.length() != 2)
{
error = true;
break;