mirror of https://github.com/bsnes-emu/bsnes.git
391 lines
11 KiB
C++
391 lines
11 KiB
C++
|
#include "cheateditor.moc"
|
||
|
CheatEditorWindow *cheatEditorWindow;
|
||
|
CheatImportWindow *cheatImportWindow;
|
||
|
|
||
|
CheatEditorWindow::CheatEditorWindow() {
|
||
|
lock = false;
|
||
|
document = 0;
|
||
|
|
||
|
layout = new QVBoxLayout;
|
||
|
layout->setMargin(Style::WindowMargin);
|
||
|
layout->setSpacing(Style::WidgetSpacing);
|
||
|
setLayout(layout);
|
||
|
|
||
|
list = new QTreeWidget;
|
||
|
list->setColumnCount(3);
|
||
|
list->setHeaderLabels(QStringList() << "Slot" << "Code" << "Description");
|
||
|
list->setColumnWidth(1, list->fontMetrics().width(" 89AB-CDEF+... "));
|
||
|
list->setAllColumnsShowFocus(true);
|
||
|
list->sortByColumn(0, Qt::AscendingOrder);
|
||
|
list->setRootIsDecorated(false);
|
||
|
list->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||
|
list->resizeColumnToContents(0);
|
||
|
layout->addWidget(list);
|
||
|
|
||
|
gridLayout = new QGridLayout;
|
||
|
layout->addLayout(gridLayout);
|
||
|
|
||
|
codeLabel = new QLabel("Code(s):");
|
||
|
codeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||
|
gridLayout->addWidget(codeLabel, 0, 0);
|
||
|
|
||
|
codeEdit = new QLineEdit;
|
||
|
gridLayout->addWidget(codeEdit, 0, 1);
|
||
|
|
||
|
descLabel = new QLabel("Description:");
|
||
|
descLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||
|
gridLayout->addWidget(descLabel, 1, 0);
|
||
|
|
||
|
descEdit = new QLineEdit;
|
||
|
gridLayout->addWidget(descEdit, 1, 1);
|
||
|
|
||
|
controlLayout = new QHBoxLayout;
|
||
|
layout->addLayout(controlLayout);
|
||
|
|
||
|
cheatEnableBox = new QCheckBox("Enable Cheat Engine");
|
||
|
cheatEnableBox->setToolTip("Unchecking this disables all cheat codes");
|
||
|
cheatEnableBox->setChecked(SNES::cheat.enabled());
|
||
|
controlLayout->addWidget(cheatEnableBox);
|
||
|
|
||
|
spacer = new QWidget;
|
||
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||
|
controlLayout->addWidget(spacer);
|
||
|
|
||
|
findButton = new QPushButton("Find Cheat Codes ...");
|
||
|
controlLayout->addWidget(findButton);
|
||
|
|
||
|
clearButton = new QPushButton("Clear Selected");
|
||
|
controlLayout->addWidget(clearButton);
|
||
|
|
||
|
cheatImportWindow = new CheatImportWindow;
|
||
|
synchronize();
|
||
|
|
||
|
connect(list, SIGNAL(itemSelectionChanged()), this, SLOT(listChanged()));
|
||
|
connect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(bind()));
|
||
|
connect(codeEdit, SIGNAL(textEdited(const QString&)), this, SLOT(codeEdited()));
|
||
|
connect(descEdit, SIGNAL(textEdited(const QString&)), this, SLOT(descEdited()));
|
||
|
connect(cheatEnableBox, SIGNAL(stateChanged(int)), this, SLOT(toggleCheatEnable()));
|
||
|
connect(findButton, SIGNAL(released()), this, SLOT(findCheatCodes()));
|
||
|
connect(clearButton, SIGNAL(released()), this, SLOT(clearSelected()));
|
||
|
}
|
||
|
|
||
|
void CheatEditorWindow::synchronize() {
|
||
|
auto items = list->selectedItems();
|
||
|
if(items.count() == 1) {
|
||
|
descEdit->setEnabled(true);
|
||
|
codeEdit->setEnabled(true);
|
||
|
} else {
|
||
|
descEdit->setText("");
|
||
|
codeEdit->setText("");
|
||
|
descEdit->setEnabled(false);
|
||
|
codeEdit->setEnabled(false);
|
||
|
}
|
||
|
clearButton->setEnabled(items.count() > 0);
|
||
|
findButton->setEnabled(SNES::cartridge.loaded());
|
||
|
if(SNES::cartridge.loaded() == false && cheatImportWindow->isVisible()) cheatImportWindow->close();
|
||
|
}
|
||
|
|
||
|
void CheatEditorWindow::load(const char *filename) {
|
||
|
lock = true;
|
||
|
list->clear();
|
||
|
list->setSortingEnabled(false);
|
||
|
SNES::cheat.reset();
|
||
|
|
||
|
string data;
|
||
|
lstring line;
|
||
|
|
||
|
if(data.readfile(filename)) {
|
||
|
data.replace("\r", "");
|
||
|
line.split("\n", data);
|
||
|
}
|
||
|
|
||
|
for(unsigned i = 0; i < 128; i++) {
|
||
|
lstring part;
|
||
|
if(line.size() > i) part.qsplit(",", line[i]);
|
||
|
for(unsigned n = 0; n <= 2; n++) {
|
||
|
part[n].trim(" ");
|
||
|
part[n].trim("\"");
|
||
|
}
|
||
|
part[2].replace("\\q", "\"");
|
||
|
|
||
|
auto item = new QTreeWidgetItem(list);
|
||
|
item->setData(0, Qt::UserRole, QVariant(i));
|
||
|
item->setText(0, strunsigned<3, ' '>(i + 1));
|
||
|
item->setCheckState(0, part[0] == "enabled" ? Qt::Checked : Qt::Unchecked);
|
||
|
item->setText(1, part[1]);
|
||
|
item->setText(2, part[2]);
|
||
|
}
|
||
|
|
||
|
list->resizeColumnToContents(0);
|
||
|
list->setSortingEnabled(true);
|
||
|
list->header()->setSortIndicatorShown(false);
|
||
|
|
||
|
lock = false;
|
||
|
bind();
|
||
|
update();
|
||
|
}
|
||
|
|
||
|
void CheatEditorWindow::save(const char *filename) {
|
||
|
bool empty = true;
|
||
|
string data[128];
|
||
|
|
||
|
auto items = list->findItems("", Qt::MatchContains);
|
||
|
foreach(item, items) {
|
||
|
unsigned index = item->data(0, Qt::UserRole).toUInt();
|
||
|
string code = item->text(1).toUtf8().constData();
|
||
|
string desc = item->text(2).toUtf8().constData();
|
||
|
desc.replace("\"", "\\q");
|
||
|
if((code != "") || (desc != "")) empty = false;
|
||
|
|
||
|
data[index] << "\"" << (item->checkState(0) == Qt::Checked ? "enabled" : "disabled") << "\",";
|
||
|
data[index] << "\"" << code << "\",";
|
||
|
data[index] << "\"" << desc << "\"\r\n";
|
||
|
}
|
||
|
|
||
|
if(empty == true) {
|
||
|
unlink(filename);
|
||
|
} else {
|
||
|
file fp;
|
||
|
if(fp.open(filename, file::mode_write)) {
|
||
|
//determine how many rows from the bottom up are empty, and exclude them from the file
|
||
|
//eg if only the first three slots are used, don't save the last 125 empty slots
|
||
|
unsigned last = 127;
|
||
|
do {
|
||
|
if(data[last] != "\"disabled\",\"\",\"\"\r\n") break;
|
||
|
} while(--last);
|
||
|
|
||
|
for(unsigned i = 0; i <= last; i++) fp.print(data[i]);
|
||
|
fp.close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
list->clear();
|
||
|
SNES::cheat.reset();
|
||
|
SNES::cheat.synchronize();
|
||
|
}
|
||
|
|
||
|
void CheatEditorWindow::update() {
|
||
|
auto items = list->findItems("", Qt::MatchContains);
|
||
|
foreach(item, items) {
|
||
|
string code = item->text(1).toUtf8().constData();
|
||
|
string desc = item->text(2).toUtf8().constData();
|
||
|
if((code != "") || (desc != "")) {
|
||
|
item->setForeground(0, QBrush(QColor(0, 0, 0)));
|
||
|
} else {
|
||
|
//highlight empty slots in gray
|
||
|
item->setForeground(0, QBrush(QColor(128, 128, 128)));
|
||
|
}
|
||
|
unsigned index = item->data(0, Qt::UserRole).toUInt();
|
||
|
if(SNES::cheat[index].addr.size() > 0) {
|
||
|
item->setForeground(1, QBrush(QColor(0, 0, 0)));
|
||
|
} else {
|
||
|
//highlight invalid codes in red
|
||
|
//(this will also highlight empty codes, but as there is no text, it's not an issue)
|
||
|
item->setForeground(1, QBrush(QColor(255, 0, 0)));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CheatEditorWindow::bind() {
|
||
|
if(lock) return;
|
||
|
|
||
|
auto items = list->findItems("", Qt::MatchContains);
|
||
|
foreach(item, items) {
|
||
|
unsigned index = item->data(0, Qt::UserRole).toUInt();
|
||
|
SNES::cheat[index] = item->text(1).toUtf8().constData();
|
||
|
SNES::cheat[index].enabled = item->checkState(0) == Qt::Checked;
|
||
|
}
|
||
|
SNES::cheat.synchronize();
|
||
|
}
|
||
|
|
||
|
void CheatEditorWindow::listChanged() {
|
||
|
if(lock) return;
|
||
|
|
||
|
auto items = list->selectedItems();
|
||
|
if(items.count() > 0) {
|
||
|
auto item = items[0];
|
||
|
codeEdit->setText(item->text(1));
|
||
|
descEdit->setText(item->text(2));
|
||
|
}
|
||
|
synchronize();
|
||
|
}
|
||
|
|
||
|
void CheatEditorWindow::codeEdited() {
|
||
|
if(lock) return;
|
||
|
|
||
|
auto items = list->selectedItems();
|
||
|
if(items.count() == 1) {
|
||
|
auto item = items[0];
|
||
|
item->setText(1, codeEdit->text());
|
||
|
}
|
||
|
bind();
|
||
|
update();
|
||
|
}
|
||
|
|
||
|
void CheatEditorWindow::descEdited() {
|
||
|
if(lock) return;
|
||
|
|
||
|
auto items = list->selectedItems();
|
||
|
if(items.count() == 1) {
|
||
|
auto item = items[0];
|
||
|
item->setText(2, descEdit->text());
|
||
|
}
|
||
|
update();
|
||
|
}
|
||
|
|
||
|
void CheatEditorWindow::toggleCheatEnable() {
|
||
|
SNES::cheat.enable(cheatEnableBox->isChecked());
|
||
|
}
|
||
|
|
||
|
void CheatEditorWindow::findCheatCodes() {
|
||
|
string data;
|
||
|
QFile file(":/cheats.xml");
|
||
|
if(file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
|
data = file.readAll().constData();
|
||
|
file.close();
|
||
|
}
|
||
|
|
||
|
if(auto position = strpos(data, SNES::cartridge.sha256())) {
|
||
|
const char *block = (const char*)data + position() - 19;
|
||
|
if(position = strpos(block, "</cartridge>")) {
|
||
|
xml_element document = xml_parse(substr(block, 0, position() + 12));
|
||
|
if(document.element.size() == 0) return;
|
||
|
|
||
|
cheatImportWindow->refresh(document.element[0]);
|
||
|
cheatImportWindow->show();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
audio.clear();
|
||
|
QMessageBox::information(toolsWindow, "Cheat Code Importer",
|
||
|
"Sorry, no cheat codes were found for the currently loaded cartridge."
|
||
|
);
|
||
|
}
|
||
|
|
||
|
void CheatEditorWindow::clearSelected() {
|
||
|
if(lock) return;
|
||
|
|
||
|
auto items = list->selectedItems();
|
||
|
foreach(item, items) {
|
||
|
item->setCheckState(0, Qt::Unchecked);
|
||
|
item->setText(1, "");
|
||
|
item->setText(2, "");
|
||
|
}
|
||
|
codeEdit->setText("");
|
||
|
descEdit->setText("");
|
||
|
bind();
|
||
|
update();
|
||
|
}
|
||
|
|
||
|
//=================
|
||
|
//CheatImportWindow
|
||
|
//=================
|
||
|
|
||
|
void CheatImportWindow::refresh(xml_element &root) {
|
||
|
list->clear();
|
||
|
|
||
|
foreach(node, root.element) {
|
||
|
if(node.name == "name") {
|
||
|
title->setText(string() << "<b>Name:</b> " << node.parse());
|
||
|
} else if(node.name == "cheat") {
|
||
|
string description = "<undefined>";
|
||
|
string code = "";
|
||
|
|
||
|
foreach(leaf, node.element) {
|
||
|
if(leaf.name == "description") {
|
||
|
description = leaf.parse();
|
||
|
} else if(leaf.name == "code") {
|
||
|
if(code != "") code << "+";
|
||
|
code << leaf.content;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
auto item = new QTreeWidgetItem(list);
|
||
|
item->setCheckState(0, Qt::Unchecked);
|
||
|
item->setText(0, string() << description);
|
||
|
QString qcode = code;
|
||
|
item->setData(0, Qt::UserRole, QVariant(qcode));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CheatImportWindow::CheatImportWindow() {
|
||
|
resize(640, 360);
|
||
|
|
||
|
setObjectName("cheat-import-window");
|
||
|
setWindowTitle("Cheat Code Importer");
|
||
|
setGeometryString(&config().geometry.cheatImportWindow);
|
||
|
application.windowList.append(this);
|
||
|
|
||
|
layout = new QVBoxLayout;
|
||
|
layout->setMargin(Style::WindowMargin);
|
||
|
layout->setSpacing(Style::WidgetSpacing);
|
||
|
setLayout(layout);
|
||
|
|
||
|
title = new QLabel("Test");
|
||
|
layout->addWidget(title);
|
||
|
|
||
|
list = new QTreeWidget;
|
||
|
list->setColumnCount(1);
|
||
|
list->setAllColumnsShowFocus(true);
|
||
|
list->setRootIsDecorated(false);
|
||
|
list->setHeaderHidden(true);
|
||
|
layout->addWidget(list);
|
||
|
|
||
|
controlLayout = new QHBoxLayout;
|
||
|
layout->addLayout(controlLayout);
|
||
|
|
||
|
selectAllButton = new QPushButton("Select All");
|
||
|
controlLayout->addWidget(selectAllButton);
|
||
|
|
||
|
clearAllButton = new QPushButton("Clear All");
|
||
|
controlLayout->addWidget(clearAllButton);
|
||
|
|
||
|
spacer = new QWidget;
|
||
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||
|
controlLayout->addWidget(spacer);
|
||
|
|
||
|
okButton = new QPushButton("Ok");
|
||
|
controlLayout->addWidget(okButton);
|
||
|
|
||
|
cancelButton = new QPushButton("Cancel");
|
||
|
controlLayout->addWidget(cancelButton);
|
||
|
|
||
|
connect(selectAllButton, SIGNAL(released()), this, SLOT(selectAllCodes()));
|
||
|
connect(clearAllButton, SIGNAL(released()), this, SLOT(clearAllCodes()));
|
||
|
connect(okButton, SIGNAL(released()), this, SLOT(addSelectedCodes()));
|
||
|
connect(cancelButton, SIGNAL(released()), this, SLOT(close()));
|
||
|
}
|
||
|
|
||
|
void CheatImportWindow::selectAllCodes() {
|
||
|
auto items = list->findItems("", Qt::MatchContains);
|
||
|
foreach(item, items) item->setCheckState(0, Qt::Checked);
|
||
|
}
|
||
|
|
||
|
void CheatImportWindow::clearAllCodes() {
|
||
|
auto items = list->findItems("", Qt::MatchContains);
|
||
|
foreach(item, items) item->setCheckState(0, Qt::Unchecked);
|
||
|
}
|
||
|
|
||
|
void CheatImportWindow::addSelectedCodes() {
|
||
|
auto items = list->findItems("", Qt::MatchContains);
|
||
|
foreach(item, items) {
|
||
|
if(item->checkState(0) == Qt::Checked) {
|
||
|
auto codeslots = cheatEditorWindow->list->findItems("", Qt::MatchContains);
|
||
|
foreach(codeslot, codeslots) {
|
||
|
if(codeslot->text(1) == "" && codeslot->text(2) == "") {
|
||
|
codeslot->setCheckState(0, Qt::Unchecked);
|
||
|
codeslot->setText(1, item->data(0, Qt::UserRole).toString());
|
||
|
codeslot->setText(2, item->text(0));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cheatEditorWindow->bind();
|
||
|
cheatEditorWindow->update();
|
||
|
close();
|
||
|
}
|