mirror of https://github.com/bsnes-emu/bsnes.git
281 lines
8.4 KiB
C++
Executable File
281 lines
8.4 KiB
C++
Executable File
#include "statemanager.moc"
|
|
StateManagerWindow *stateManagerWindow;
|
|
|
|
StateManagerWindow::StateManagerWindow() {
|
|
layout = new QVBoxLayout;
|
|
layout->setMargin(Style::WindowMargin);
|
|
layout->setSpacing(Style::WidgetSpacing);
|
|
setLayout(layout);
|
|
|
|
list = new QTreeWidget;
|
|
list->setColumnCount(2);
|
|
list->setHeaderLabels(QStringList() << "Slot" << "Description");
|
|
list->setAllColumnsShowFocus(true);
|
|
list->sortByColumn(0, Qt::AscendingOrder);
|
|
list->setRootIsDecorated(false);
|
|
list->resizeColumnToContents(0);
|
|
layout->addWidget(list);
|
|
|
|
infoLayout = new QHBoxLayout;
|
|
layout->addLayout(infoLayout);
|
|
|
|
descriptionLabel = new QLabel("Description:");
|
|
infoLayout->addWidget(descriptionLabel);
|
|
|
|
descriptionText = new QLineEdit;
|
|
infoLayout->addWidget(descriptionText);
|
|
|
|
controlLayout = new QHBoxLayout;
|
|
layout->addLayout(controlLayout);
|
|
|
|
spacer = new QWidget;
|
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
controlLayout->addWidget(spacer);
|
|
|
|
loadButton = new QPushButton("Load");
|
|
controlLayout->addWidget(loadButton);
|
|
|
|
saveButton = new QPushButton("Save");
|
|
controlLayout->addWidget(saveButton);
|
|
|
|
eraseButton = new QPushButton("Erase");
|
|
controlLayout->addWidget(eraseButton);
|
|
|
|
connect(list, SIGNAL(itemSelectionChanged()), this, SLOT(synchronize()));
|
|
connect(list, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(loadAction()));
|
|
connect(descriptionText, SIGNAL(textEdited(const QString&)), this, SLOT(writeDescription()));
|
|
connect(loadButton, SIGNAL(released()), this, SLOT(loadAction()));
|
|
connect(saveButton, SIGNAL(released()), this, SLOT(saveAction()));
|
|
connect(eraseButton, SIGNAL(released()), this, SLOT(eraseAction()));
|
|
|
|
synchronize();
|
|
}
|
|
|
|
void StateManagerWindow::reload() {
|
|
list->clear();
|
|
list->setSortingEnabled(false);
|
|
|
|
if(SNES::cartridge.loaded() && cartridge.saveStatesSupported()) {
|
|
for(unsigned n = 0; n < StateCount; n++) {
|
|
QTreeWidgetItem *item = new QTreeWidgetItem(list);
|
|
item->setData(0, Qt::UserRole, QVariant(n));
|
|
char slot[16];
|
|
sprintf(slot, "%2u", n + 1);
|
|
item->setText(0, slot);
|
|
}
|
|
update();
|
|
}
|
|
|
|
list->setSortingEnabled(true);
|
|
list->header()->setSortIndicatorShown(false);
|
|
synchronize();
|
|
}
|
|
|
|
void StateManagerWindow::update() {
|
|
//iterate all list items
|
|
QList<QTreeWidgetItem*> items = list->findItems("", Qt::MatchContains);
|
|
for(unsigned i = 0; i < items.count(); i++) {
|
|
QTreeWidgetItem *item = items[i];
|
|
unsigned n = item->data(0, Qt::UserRole).toUInt();
|
|
if(isStateValid(n) == false) {
|
|
item->setForeground(0, QBrush(QColor(128, 128, 128)));
|
|
item->setForeground(1, QBrush(QColor(128, 128, 128)));
|
|
item->setText(1, "Empty");
|
|
} else {
|
|
item->setForeground(0, QBrush(QColor(0, 0, 0)));
|
|
item->setForeground(1, QBrush(QColor(0, 0, 0)));
|
|
item->setText(1, getStateDescription(n));
|
|
}
|
|
}
|
|
|
|
for(unsigned n = 0; n <= 1; n++) list->resizeColumnToContents(n);
|
|
}
|
|
|
|
void StateManagerWindow::synchronize() {
|
|
QList<QTreeWidgetItem*> items = list->selectedItems();
|
|
if(items.count() > 0) {
|
|
QTreeWidgetItem *item = items[0];
|
|
unsigned n = item->data(0, Qt::UserRole).toUInt();
|
|
|
|
if(isStateValid(n)) {
|
|
descriptionText->setText(getStateDescription(n));
|
|
descriptionText->setEnabled(true);
|
|
loadButton->setEnabled(true);
|
|
eraseButton->setEnabled(true);
|
|
} else {
|
|
descriptionText->setText("");
|
|
descriptionText->setEnabled(false);
|
|
loadButton->setEnabled(false);
|
|
eraseButton->setEnabled(false);
|
|
}
|
|
saveButton->setEnabled(true);
|
|
} else {
|
|
descriptionText->setText("");
|
|
descriptionText->setEnabled(false);
|
|
loadButton->setEnabled(false);
|
|
saveButton->setEnabled(false);
|
|
eraseButton->setEnabled(false);
|
|
}
|
|
}
|
|
|
|
void StateManagerWindow::writeDescription() {
|
|
QList<QTreeWidgetItem*> items = list->selectedItems();
|
|
if(items.count() > 0) {
|
|
QTreeWidgetItem *item = items[0];
|
|
unsigned n = item->data(0, Qt::UserRole).toUInt();
|
|
string description = descriptionText->text().toUtf8().constData();
|
|
setStateDescription(n, description);
|
|
update();
|
|
}
|
|
}
|
|
|
|
void StateManagerWindow::loadAction() {
|
|
QList<QTreeWidgetItem*> items = list->selectedItems();
|
|
if(items.count() > 0) {
|
|
QTreeWidgetItem *item = items[0];
|
|
unsigned n = item->data(0, Qt::UserRole).toUInt();
|
|
loadState(n);
|
|
}
|
|
}
|
|
|
|
void StateManagerWindow::saveAction() {
|
|
QList<QTreeWidgetItem*> items = list->selectedItems();
|
|
if(items.count() > 0) {
|
|
QTreeWidgetItem *item = items[0];
|
|
unsigned n = item->data(0, Qt::UserRole).toUInt();
|
|
saveState(n);
|
|
writeDescription();
|
|
synchronize();
|
|
descriptionText->setFocus();
|
|
}
|
|
}
|
|
|
|
void StateManagerWindow::eraseAction() {
|
|
QList<QTreeWidgetItem*> items = list->selectedItems();
|
|
if(items.count() > 0) {
|
|
QTreeWidgetItem *item = items[0];
|
|
unsigned n = item->data(0, Qt::UserRole).toUInt();
|
|
eraseState(n);
|
|
update();
|
|
synchronize();
|
|
}
|
|
}
|
|
|
|
string StateManagerWindow::filename() const {
|
|
string name = filepath(nall::basename(cartridge.fileName), config().path.state);
|
|
name << ".bsa";
|
|
return name;
|
|
}
|
|
|
|
bool StateManagerWindow::isStateValid(unsigned slot) {
|
|
if(SNES::cartridge.loaded() == false) return false;
|
|
file fp;
|
|
if(fp.open(filename(), file::mode_read) == false) return false;
|
|
if(fp.size() < (slot + 1) * SNES::system.serialize_size()) { fp.close(); return false; }
|
|
fp.seek(slot * SNES::system.serialize_size());
|
|
uint32_t signature = fp.readl(4);
|
|
if(signature == 0) { fp.close(); return false; }
|
|
uint32_t version = fp.readl(4);
|
|
if(version != SNES::Info::SerializerVersion) { fp.close(); return false; }
|
|
fp.close();
|
|
return true;
|
|
}
|
|
|
|
string StateManagerWindow::getStateDescription(unsigned slot) {
|
|
if(isStateValid(slot) == false) return "";
|
|
file fp;
|
|
fp.open(filename(), file::mode_read);
|
|
char description[513];
|
|
fp.seek(slot * SNES::system.serialize_size() + 12);
|
|
fp.read((uint8_t*)description, 512);
|
|
fp.close();
|
|
description[512] = 0;
|
|
return description;
|
|
}
|
|
|
|
void StateManagerWindow::setStateDescription(unsigned slot, const string &text) {
|
|
if(isStateValid(slot) == false) return;
|
|
file fp;
|
|
fp.open(filename(), file::mode_readwrite);
|
|
char description[512];
|
|
memset(&description, 0, sizeof description);
|
|
strncpy(description, text, 512);
|
|
fp.seek(slot * SNES::system.serialize_size() + 12);
|
|
fp.write((uint8_t*)description, 512);
|
|
fp.close();
|
|
}
|
|
|
|
void StateManagerWindow::loadState(unsigned slot) {
|
|
if(isStateValid(slot) == false) return;
|
|
file fp;
|
|
fp.open(filename(), file::mode_read);
|
|
fp.seek(slot * SNES::system.serialize_size());
|
|
unsigned size = SNES::system.serialize_size();
|
|
uint8_t *data = new uint8_t[size];
|
|
fp.read(data, size);
|
|
fp.close();
|
|
|
|
serializer state(data, size);
|
|
delete[] data;
|
|
|
|
if(SNES::system.unserialize(state) == true) {
|
|
//toolsWindow->close();
|
|
}
|
|
}
|
|
|
|
void StateManagerWindow::saveState(unsigned slot) {
|
|
file fp;
|
|
if(file::exists(filename()) == false) {
|
|
//try and create the file, bail out on failure (eg read-only device)
|
|
if(fp.open(filename(), file::mode_write) == false) return;
|
|
fp.close();
|
|
}
|
|
|
|
SNES::system.runtosave();
|
|
serializer state = SNES::system.serialize();
|
|
|
|
fp.open(filename(), file::mode_readwrite);
|
|
|
|
//user may save to slot #2 when slot #1 is empty; pad file to current slot if needed
|
|
unsigned stateOffset = SNES::system.serialize_size() * slot;
|
|
fp.seek(fp.size());
|
|
while(fp.size() < stateOffset) fp.write(0x00);
|
|
|
|
fp.seek(stateOffset);
|
|
fp.write(state.data(), state.size());
|
|
fp.close();
|
|
}
|
|
|
|
void StateManagerWindow::eraseState(unsigned slot) {
|
|
if(isStateValid(slot) == false) return;
|
|
file fp;
|
|
fp.open(filename(), file::mode_readwrite);
|
|
unsigned size = SNES::system.serialize_size();
|
|
fp.seek(slot * size);
|
|
for(unsigned i = 0; i < size; i++) fp.write(0x00);
|
|
fp.close();
|
|
|
|
//shrink state archive as much as possible:
|
|
//eg if only slot #2 and slot #31 were valid, but slot #31 was erased,
|
|
//file can be resized to only hold blank slot #1 + valid slot #2
|
|
signed lastValidState = -1;
|
|
for(signed i = StateCount - 1; i >= 0; i--) {
|
|
if(isStateValid(i)) {
|
|
lastValidState = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(lastValidState == -1) {
|
|
//no states used, remove empty file
|
|
unlink(filename());
|
|
} else {
|
|
unsigned neededFileSize = (lastValidState + 1) * SNES::system.serialize_size();
|
|
file fp;
|
|
if(fp.open(filename(), file::mode_readwrite)) {
|
|
if(fp.size() > neededFileSize) fp.truncate(neededFileSize);
|
|
fp.close();
|
|
}
|
|
}
|
|
}
|