/* Copyright 2016-2021 Arisotura This file is part of melonDS. melonDS 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 3 of the License, or (at your option) any later version. melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. */ #include "RAMInfoDialog.h" #include "ui_RAMInfoDialog.h" #include "main.h" extern EmuThread* emuThread; s32 GetMainRAMValue(const u32& addr, const ramInfo_ByteType& byteType) { switch (byteType) { case ramInfo_OneByte: return *(s8*)(NDS::MainRAM + (addr&NDS::MainRAMMask)); case ramInfo_TwoBytes: return *(s16*)(NDS::MainRAM + (addr&NDS::MainRAMMask)); case ramInfo_FourBytes: return *(s32*)(NDS::MainRAM + (addr&NDS::MainRAMMask)); default: return 0; } } RAMInfoDialog* RAMInfoDialog::currentDlg = nullptr; RAMInfoDialog::RAMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::RAMInfoDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); qRegisterMetaType>("QVector"); qRegisterMetaType("u32"); qRegisterMetaType("s32"); qRegisterMetaType("s16"); qRegisterMetaType("s8"); SearchThread = new RAMSearchThread(this); connect(SearchThread, &RAMSearchThread::SetProgressbarValue, this, &RAMInfoDialog::SetProgressbarValue); connect(SearchThread, &RAMSearchThread::finished, this, &RAMInfoDialog::OnSearchFinished); // First search (Show everything in main ram) SearchThread->Start(ramInfoSTh_SearchAll); TableUpdater = new QTimer(this); TableUpdater->setInterval(100); connect(TableUpdater, &QTimer::timeout, this, &RAMInfoDialog::ShowRowsInTable); TableUpdater->start(); } RAMInfoDialog::~RAMInfoDialog() { delete SearchThread; if (TableUpdater->isActive()) TableUpdater->stop(); delete TableUpdater; delete ui; } void RAMInfoDialog::OnSearchFinished() { SearchThread->wait(); ui->btnSearch->setEnabled(true); ui->ramTable->clearContents(); ui->ramTable->setRowCount(SearchThread->GetResults()->size()); ui->ramTable->verticalScrollBar()->setSliderPosition(0); ui->txtFound->setText(QString("Found: %1").arg(SearchThread->GetResults()->size())); } void RAMInfoDialog::ShowRowsInTable() { const u32& scrollValue = ui->ramTable->verticalScrollBar()->sliderPosition(); std::vector* RowDataVector = SearchThread->GetResults(); for (u32 row = scrollValue; row < std::min(scrollValue+25, RowDataVector->size()); row++) { ramInfo_RowData& rowData = RowDataVector->at(row); rowData.Update(SearchThread->GetSearchByteType()); if (ui->ramTable->item(row, ramInfo_Address) == nullptr) { // A new row QTableWidgetItem* addressItem = new QTableWidgetItem(QString("%1").arg(rowData.Address, 8, 16)); QTableWidgetItem* valueItem = new QTableWidgetItem(QString("%1").arg(rowData.Value)); QTableWidgetItem* previousItem = new QTableWidgetItem(QString("%1").arg(rowData.Previous)); addressItem->setFlags(addressItem->flags() & ~Qt::ItemIsEditable); valueItem->setFlags(valueItem->flags() | Qt::ItemIsEditable); previousItem->setFlags(previousItem->flags() & ~Qt::ItemIsEditable); ui->ramTable->setItem(row, ramInfo_Address, addressItem); ui->ramTable->setItem(row, ramInfo_Value, valueItem); ui->ramTable->setItem(row, ramInfo_Previous, previousItem); } else { // A row that exists ui->ramTable->item(row, ramInfo_Address)->setText(QString("%1").arg(rowData.Address, 8, 16)); ui->ramTable->item(row, ramInfo_Value)->setText(QString("%1").arg(rowData.Value)); ui->ramTable->item(row, ramInfo_Previous)->setText(QString("%1").arg(rowData.Previous)); if (rowData.Value != rowData.Previous) ui->ramTable->item(row, ramInfo_Previous)->setForeground(Qt::red); } } } void RAMInfoDialog::ClearTableContents() { ui->ramTable->clearContents(); ui->ramTable->setRowCount(0); } void RAMInfoDialog::SetProgressbarValue(const u32& value) { ui->progressBar->setValue(value); } void RAMInfoDialog::done(int r) { QDialog::done(r); closeDlg(); } void RAMInfoDialog::on_btnSearch_clicked() { ui->btnSearch->setEnabled(false); ui->radiobtn1byte->setEnabled(false); ui->radiobtn2bytes->setEnabled(false); ui->radiobtn4bytes->setEnabled(false); if (ui->txtSearch->text().isEmpty()) SearchThread->Start(ramInfoSTh_SearchAll); else SearchThread->Start(ui->txtSearch->text().toInt()); if (!TableUpdater->isActive()) TableUpdater->start(); } void RAMInfoDialog::on_btnClear_clicked() { SearchThread->Stop(); TableUpdater->stop(); ui->radiobtn1byte->setEnabled(true); ui->radiobtn2bytes->setEnabled(true); ui->radiobtn4bytes->setEnabled(true); OnSearchFinished(); } void RAMInfoDialog::on_radiobtn1byte_clicked() { SearchThread->SetSearchByteType(ramInfo_OneByte); } void RAMInfoDialog::on_radiobtn2bytes_clicked() { SearchThread->SetSearchByteType(ramInfo_TwoBytes); } void RAMInfoDialog::on_radiobtn4bytes_clicked() { SearchThread->SetSearchByteType(ramInfo_FourBytes); } void RAMInfoDialog::on_ramTable_itemChanged(QTableWidgetItem *item) { ramInfo_RowData& rowData = SearchThread->GetResults()->at(item->row()); s32 itemValue = item->text().toInt(); if (rowData.Value != itemValue) rowData.SetValue(itemValue); } /** * RAMSearchThread */ RAMSearchThread::RAMSearchThread(RAMInfoDialog* dialog) : Dialog(dialog) { RowDataVector = new std::vector(); } RAMSearchThread::~RAMSearchThread() { Stop(); if (RowDataVector) { delete RowDataVector; RowDataVector = nullptr; } } void RAMSearchThread::Start(const s32& searchValue, const ramInfoSTh_SearchMode& searchMode) { SearchValue = searchValue; SearchMode = searchMode; start(); } void RAMSearchThread::Start(const ramInfoSTh_SearchMode& searchMode) { SearchMode = searchMode; start(); } void RAMSearchThread::Stop() { SearchRunning = false; RowDataVector->clear(); quit(); wait(); } void RAMSearchThread::run() { SearchRunning = true; u32 progress = 0; // Pause game running emuThread->emuPause(); // For following search modes below, RowDataVector must be filled. if (SearchMode == ramInfoSTh_SearchAll || RowDataVector->size() == 0) { // First search mode for (u32 addr = 0x02000000; SearchRunning && addr < 0x02000000+NDS::MainRAMMaxSize; addr += SearchByteType) { const s32& value = GetMainRAMValue(addr, SearchByteType); RowDataVector->push_back({ addr, value, value }); // A solution to prevent to call too many slot. u32 newProgress = (int)((addr-0x02000000) / (NDS::MainRAMMaxSize-1.0f) * 100); if (progress < newProgress) { progress = newProgress; emit SetProgressbarValue(progress); } } } if (SearchMode == ramInfoSTh_Default) { // Next search mode std::vector* newRowDataVector = new std::vector(); for (u32 row = 0; SearchRunning && row < RowDataVector->size(); row++) { const u32& addr = RowDataVector->at(row).Address; const s32& value = GetMainRAMValue(addr, SearchByteType); if (SearchValue == value) newRowDataVector->push_back({ addr, value, value }); // A solution to prevent to call too many slot. u32 newProgress = (int)(row / (RowDataVector->size()-1.0f) * 100); if (progress < newProgress) { progress = newProgress; emit SetProgressbarValue(progress); } } delete RowDataVector; RowDataVector = newRowDataVector; } // Unpause game running emuThread->emuUnpause(); SearchRunning = false; } void RAMSearchThread::SetSearchByteType(const ramInfo_ByteType& bytetype) { SearchByteType = bytetype; } ramInfo_ByteType RAMSearchThread::GetSearchByteType() const { return SearchByteType; } std::vector* RAMSearchThread::GetResults() { return RowDataVector; }