/* FCE Ultra - NES/Famicom Emulator * * Copyright notice for this file: * Copyright (C) 2020 mjbudd77 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // GameGenie.cpp // #include #include #include #include #include #include #include #include "../../types.h" #include "../../fceu.h" #include "../../cart.h" #include "../../cheat.h" #include "../../debug.h" #include "../../driver.h" #include "Qt/main.h" #include "Qt/dface.h" #include "Qt/dface.h" #include "Qt/input.h" #include "Qt/config.h" #include "Qt/keyscan.h" #include "Qt/fceuWrapper.h" #include "Qt/CheatsConf.h" #include "Qt/HexEditor.h" #include "Qt/GameGenie.h" static const char *GameGenieLetters = "APZLGITYEOXUKSVN"; class fceuGGCodeValidtor : public QValidator { public: fceuGGCodeValidtor( QObject *parent) : QValidator(parent) { } QValidator::State validate(QString &input, int &pos) const { int i,j, ok; //printf("Validate: %i '%s'\n", input.size(), input.toStdString().c_str() ); if ( input.size() == 0 ) { return QValidator::Acceptable; } input = input.toUpper(); std::string s = input.toStdString(); i=0; while ( s[i] != 0 ) { j=0; ok=0; while ( GameGenieLetters[j] != 0 ) { if ( s[i] == GameGenieLetters[j] ) { ok = 1; break; } j++; } if ( !ok ) { return QValidator::Invalid; } i++; } return QValidator::Acceptable; } private: }; //---------------------------------------------------------------------------- GameGenieDialog_t::GameGenieDialog_t(QWidget *parent) : QDialog( parent ) { int charWidth; QVBoxLayout *mainLayout, *vbox1, *vbox; QHBoxLayout *hbox1, *hbox; QTreeWidgetItem *item; QGroupBox *frame; QFont font; QPushButton *closeButton; fceuGGCodeValidtor *ggCodeValidator; QSettings settings; font.setFamily("Courier New"); font.setStyle( QFont::StyleNormal ); font.setStyleHint( QFont::Monospace ); QFontMetrics fm(font); #if QT_VERSION > QT_VERSION_CHECK(5, 11, 0) charWidth = fm.horizontalAdvance(QLatin1Char('2')); #else charWidth = fm.width(QLatin1Char('2')); #endif setWindowTitle("Game Genie Encoder/Decoder Tool"); mainLayout = new QVBoxLayout(); vbox = new QVBoxLayout(); vbox1 = new QVBoxLayout(); hbox1 = new QHBoxLayout(); frame = new QGroupBox( tr("Address/Compare/Value") ); mainLayout->addLayout( hbox1 ); frame->setLayout( vbox ); hbox1->addWidget( frame ); hbox1->addLayout( vbox1 ); addr = new QLineEdit(); cmp = new QLineEdit(); val = new QLineEdit(); hbox = new QHBoxLayout(); vbox->addLayout( hbox ); hbox->addWidget( new QLabel( tr("Address:") ), 0, Qt::AlignRight ); hbox->addWidget( addr, 0, Qt::AlignLeft ); hbox = new QHBoxLayout(); vbox->addLayout( hbox ); hbox->addWidget( new QLabel( tr("Compare:") ), 0, Qt::AlignRight ); hbox->addWidget( cmp, 0, Qt::AlignLeft ); hbox = new QHBoxLayout(); vbox->addLayout( hbox ); hbox->addWidget( new QLabel( tr("Value:") ), 0, Qt::AlignRight ); hbox->addWidget( val, 0, Qt::AlignLeft ); frame = new QGroupBox( tr("Game Genie Code") ); vbox = new QVBoxLayout(); vbox1->addWidget( frame ); frame->setLayout( vbox ); ggCode = new QLineEdit(); vbox->addWidget( ggCode ); addCheatBtn = new QPushButton( tr("Add To Cheat List") ); vbox1->addWidget( addCheatBtn ); tree = new QTreeWidget(); tree->setColumnCount(1); item = new QTreeWidgetItem(); item->setText( 0, QString::fromStdString( "Possible Affected ROM File Addresses" ) ); item->setTextAlignment( 0, Qt::AlignLeft); tree->setHeaderItem( item ); tree->header()->setSectionResizeMode( QHeaderView::ResizeToContents ); mainLayout->addWidget( tree ); closeButton = new QPushButton( tr("Close") ); closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton)); connect(closeButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void))); hbox = new QHBoxLayout(); hbox->addStretch(5); hbox->addWidget( closeButton, 1 ); mainLayout->addLayout( hbox ); setLayout( mainLayout ); addrValidator = new fceuHexIntValidtor( 0, 0xFFFF, this ); cmpValidator = new fceuHexIntValidtor( 0, 0x00FF, this ); valValidator = new fceuHexIntValidtor( 0, 0x00FF, this ); ggCodeValidator = new fceuGGCodeValidtor( this ); addr->setValidator( addrValidator ); cmp->setValidator( cmpValidator ); val->setValidator( valValidator ); ggCode->setValidator( ggCodeValidator ); addr->setMaxLength( 4 ); cmp->setMaxLength( 2 ); val->setMaxLength( 2 ); ggCode->setMaxLength( 8 ); addr->setMinimumWidth( 6 * charWidth ); cmp->setMinimumWidth( 4 * charWidth ); val->setMinimumWidth( 4 * charWidth ); addr->setMaximumWidth( 6 * charWidth ); cmp->setMaximumWidth( 4 * charWidth ); val->setMaximumWidth( 4 * charWidth ); addr->setAlignment(Qt::AlignCenter); cmp->setAlignment(Qt::AlignCenter); val->setAlignment(Qt::AlignCenter); addr->setFont( font ); cmp->setFont( font ); val->setFont( font ); ggCode->setFont( font ); connect( addCheatBtn, SIGNAL(clicked(void)), this, SLOT(addCheatClicked(void))); connect( addr , SIGNAL(textEdited(const QString &)), this, SLOT(addrChanged(const QString &))); connect( cmp , SIGNAL(textEdited(const QString &)), this, SLOT(cmpChanged(const QString &))); connect( val , SIGNAL(textEdited(const QString &)), this, SLOT(valChanged(const QString &))); connect( ggCode, SIGNAL(textEdited(const QString &)), this, SLOT(ggChanged(const QString &))); connect( tree, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(romAddrDoubleClicked(QTreeWidgetItem*, int)) ); addCheatBtn->setEnabled( false ); restoreGeometry(settings.value("GameGenieWindow/geometry").toByteArray()); } //---------------------------------------------------------------------------- GameGenieDialog_t::~GameGenieDialog_t(void) { QSettings settings; //printf("Destroy Game Genie Window\n"); settings.setValue("GameGenieWindow/geometry", saveGeometry()); } //---------------------------------------------------------------------------- void GameGenieDialog_t::closeEvent(QCloseEvent *event) { //printf("Game Genie Close Window Event\n"); done(0); deleteLater(); event->accept(); } //---------------------------------------------------------------------------- void GameGenieDialog_t::closeWindow(void) { //printf("Close Window\n"); done(0); deleteLater(); } //---------------------------------------------------------------------------- void GameGenieDialog_t::addCheatClicked(void) { int a = -1, v = -1, c = -1; std::string name; name = ggCode->text().toStdString(); if ( addr->text().size() > 0 ) { a = strtol( addr->text().toStdString().c_str(), NULL, 16 ); } if ( val->text().size() > 0 ) { v = strtol( val->text().toStdString().c_str(), NULL, 16 ); } if ( cmp->text().size() > 0 ) { c = strtol( cmp->text().toStdString().c_str(), NULL, 16 ); } FCEU_WRAPPER_LOCK(); FCEUI_AddCheat( name.c_str(), a, v, c, 1 ); updateCheatDialog(); FCEU_WRAPPER_UNLOCK(); } //---------------------------------------------------------------------------- void GameGenieDialog_t::romAddrDoubleClicked(QTreeWidgetItem *item, int column) { int addr; addr = strtol( item->text(0).toStdString().c_str(), NULL, 16 ); printf("ROM Addr: %06X \n", addr ); hexEditorOpenFromDebugger( QHexEdit::MODE_NES_ROM, addr ); } //---------------------------------------------------------------------------- void GameGenieDialog_t::addrChanged(const QString &s) { int a, v, c = -1; char gg[12]; a = strtol( s.toStdString().c_str(), NULL, 16 ); v = strtol( val->text().toStdString().c_str(), NULL, 16 ); if ( cmp->text().size() > 0 ) { c = strtol( cmp->text().toStdString().c_str(), NULL, 16 ); } EncodeGG( gg, a, v, c ); ggCode->setText( tr(gg) ); ListGGAddresses(); } //---------------------------------------------------------------------------- void GameGenieDialog_t::cmpChanged(const QString &s) { int a, v, c = -1; char gg[12]; a = strtol( addr->text().toStdString().c_str(), NULL, 16 ); v = strtol( val->text().toStdString().c_str(), NULL, 16 ); if ( s.size() > 0 ) { c = strtol( s.toStdString().c_str(), NULL, 16 ); } EncodeGG( gg, a, v, c ); ggCode->setText( tr(gg) ); ListGGAddresses(); } //---------------------------------------------------------------------------- void GameGenieDialog_t::valChanged(const QString &s) { int a, v, c = -1; char gg[12]; a = strtol( addr->text().toStdString().c_str(), NULL, 16 ); v = strtol( s.toStdString().c_str(), NULL, 16 ); if ( cmp->text().size() > 0 ) { c = strtol( cmp->text().toStdString().c_str(), NULL, 16 ); } EncodeGG( gg, a, v, c ); ggCode->setText( tr(gg) ); ListGGAddresses(); } //---------------------------------------------------------------------------- void GameGenieDialog_t::ggChanged(const QString &s) { int a = -1, c = -1, v = -1; char gg[12]; char stmp[32]; memset( gg, 0, sizeof(gg) ); strncpy( gg, ggCode->text().toStdString().c_str(), 8 ); FCEUI_DecodeGG( gg, &a, &v, &c); if ( a >= 0 ) { sprintf( stmp, "%04X", a ); addr->setText( tr(stmp) ); } else { addr->clear(); } if ( v >= 0 ) { sprintf( stmp, "%02X", v ); val->setText( tr(stmp) ); } else { val->clear(); } if ( c >= 0 ) { sprintf( stmp, "%02X", c ); cmp->setText( tr(stmp) ); } else { cmp->clear(); } ListGGAddresses(); } //---------------------------------------------------------------------------- //The code in this function is a modified version //of Chris Covell's work - I'd just like to point that out void EncodeGG(char *str, int a, int v, int c) { uint8 num[8]; int i; a&=0x7fff; num[0]=(v&7)+((v>>4)&8); num[1]=((v>>4)&7)+((a>>4)&8); num[2]=((a>>4)&7); num[3]=(a>>12)+(a&8); num[4]=(a&7)+((a>>8)&8); num[5]=((a>>8)&7); if (c == -1){ num[5]+=v&8; for(i = 0;i < 6;i++)str[i] = GameGenieLetters[num[i]]; str[6] = 0; } else { num[2]+=8; num[5]+=c&8; num[6]=(c&7)+((c>>4)&8); num[7]=((c>>4)&7)+(v&8); for(i = 0;i < 8;i++)str[i] = GameGenieLetters[num[i]]; str[8] = 0; } return; } //---------------------------------------------------------------------------- void GameGenieDialog_t::ListGGAddresses(void) { int a = -1; int v = -1; int c = -1; QTreeWidgetItem *item; char str[32]; bool addCheatEmable; if ( addr->text().size() > 0 ) { a = strtol( addr->text().toStdString().c_str(), NULL, 16 ); } if ( val->text().size() > 0 ) { v = strtol( val->text().toStdString().c_str(), NULL, 16 ); } if ( cmp->text().size() > 0 ) { c = strtol( cmp->text().toStdString().c_str(), NULL, 16 ); } // also enable/disable the add GG button here addCheatEmable = (a >= 0) && ( (ggCode->text().size() == 6) || (ggCode->text().size() == 8) ); addCheatBtn->setEnabled( addCheatEmable ); tree->clear(); if (a != -1 && v != -1) { for (unsigned int i = 0; i < PRGsize[0]; i += 0x2000) { if (c == -1 || PRGptr[0][i + (a & 0x1FFF)] == c) { item = new QTreeWidgetItem(); sprintf(str, "%06X", i + (a & 0x1FFF) + 0x10); //printf("Added ROM ADDR: %s\n", str ); item->setText( 0, tr(str) ); item->setTextAlignment( 0, Qt::AlignCenter); tree->addTopLevelItem( item ); } } } tree->viewport()->update(); } //----------------------------------------------------------------------------