1 #include "statemanager.moc"
2 StateManagerWindow *stateManagerWindow;
3 
StateManagerWindow()4 StateManagerWindow::StateManagerWindow() {
5   layout = new QVBoxLayout;
6   layout->setMargin(Style::WindowMargin);
7   layout->setSpacing(Style::WidgetSpacing);
8   setLayout(layout);
9 
10   list = new QTreeWidget;
11   list->setColumnCount(2);
12   list->setHeaderLabels(QStringList() << "Slot" << "Description");
13   list->setAllColumnsShowFocus(true);
14   list->sortByColumn(0, Qt::AscendingOrder);
15   list->setRootIsDecorated(false);
16   list->resizeColumnToContents(0);
17   layout->addWidget(list);
18 
19   infoLayout = new QHBoxLayout;
20   layout->addLayout(infoLayout);
21 
22   descriptionLabel = new QLabel("Description:");
23   infoLayout->addWidget(descriptionLabel);
24 
25   descriptionText = new QLineEdit;
26   infoLayout->addWidget(descriptionText);
27 
28   controlLayout = new QHBoxLayout;
29   layout->addLayout(controlLayout);
30 
31   spacer = new QWidget;
32   spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
33   controlLayout->addWidget(spacer);
34 
35   loadButton = new QPushButton("Load");
36   controlLayout->addWidget(loadButton);
37 
38   saveButton = new QPushButton("Save");
39   controlLayout->addWidget(saveButton);
40 
41   eraseButton = new QPushButton("Erase");
42   controlLayout->addWidget(eraseButton);
43 
44   connect(list, SIGNAL(itemSelectionChanged()), this, SLOT(synchronize()));
45   connect(list, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(loadAction()));
46   connect(descriptionText, SIGNAL(textEdited(const QString&)), this, SLOT(writeDescription()));
47   connect(loadButton, SIGNAL(released()), this, SLOT(loadAction()));
48   connect(saveButton, SIGNAL(released()), this, SLOT(saveAction()));
49   connect(eraseButton, SIGNAL(released()), this, SLOT(eraseAction()));
50 
51   synchronize();
52 }
53 
reload()54 void StateManagerWindow::reload() {
55   list->clear();
56   list->setSortingEnabled(false);
57 
58   if(SNES::cartridge.loaded() && cartridge.saveStatesSupported()) {
59     for(unsigned n = 0; n < StateCount; n++) {
60       QTreeWidgetItem *item = new QTreeWidgetItem(list);
61       item->setData(0, Qt::UserRole, QVariant(n));
62       char slot[16];
63       sprintf(slot, "%2u", n + 1);
64       item->setText(0, slot);
65     }
66     update();
67   }
68 
69   list->setSortingEnabled(true);
70   list->header()->setSortIndicatorShown(false);
71   synchronize();
72 }
73 
update()74 void StateManagerWindow::update() {
75   //iterate all list items
76   QList<QTreeWidgetItem*> items = list->findItems("", Qt::MatchContains);
77   for(unsigned i = 0; i < items.count(); i++) {
78     QTreeWidgetItem *item = items[i];
79     unsigned n = item->data(0, Qt::UserRole).toUInt();
80     if(isStateValid(n) == false) {
81       item->setForeground(0, QBrush(QColor(128, 128, 128)));
82       item->setForeground(1, QBrush(QColor(128, 128, 128)));
83       item->setText(1, "Empty");
84     } else {
85       item->setForeground(0, QBrush(QColor(0, 0, 0)));
86       item->setForeground(1, QBrush(QColor(0, 0, 0)));
87       item->setText(1, getStateDescription(n));
88     }
89   }
90 
91   for(unsigned n = 0; n <= 1; n++) list->resizeColumnToContents(n);
92 }
93 
synchronize()94 void StateManagerWindow::synchronize() {
95   QList<QTreeWidgetItem*> items = list->selectedItems();
96   if(items.count() > 0) {
97     QTreeWidgetItem *item = items[0];
98     unsigned n = item->data(0, Qt::UserRole).toUInt();
99 
100     if(isStateValid(n)) {
101       descriptionText->setText(getStateDescription(n));
102       descriptionText->setEnabled(true);
103       loadButton->setEnabled(true);
104       eraseButton->setEnabled(true);
105     } else {
106       descriptionText->setText("");
107       descriptionText->setEnabled(false);
108       loadButton->setEnabled(false);
109       eraseButton->setEnabled(false);
110     }
111     saveButton->setEnabled(true);
112   } else {
113     descriptionText->setText("");
114     descriptionText->setEnabled(false);
115     loadButton->setEnabled(false);
116     saveButton->setEnabled(false);
117     eraseButton->setEnabled(false);
118   }
119 }
120 
writeDescription()121 void StateManagerWindow::writeDescription() {
122   QList<QTreeWidgetItem*> items = list->selectedItems();
123   if(items.count() > 0) {
124     QTreeWidgetItem *item = items[0];
125     unsigned n = item->data(0, Qt::UserRole).toUInt();
126     string description = descriptionText->text().toUtf8().constData();
127     setStateDescription(n, description);
128     update();
129   }
130 }
131 
loadAction()132 void StateManagerWindow::loadAction() {
133   QList<QTreeWidgetItem*> items = list->selectedItems();
134   if(items.count() > 0) {
135     QTreeWidgetItem *item = items[0];
136     unsigned n = item->data(0, Qt::UserRole).toUInt();
137     loadState(n);
138   }
139 }
140 
saveAction()141 void StateManagerWindow::saveAction() {
142   QList<QTreeWidgetItem*> items = list->selectedItems();
143   if(items.count() > 0) {
144     QTreeWidgetItem *item = items[0];
145     unsigned n = item->data(0, Qt::UserRole).toUInt();
146     saveState(n);
147     writeDescription();
148     synchronize();
149     descriptionText->setFocus();
150   }
151 }
152 
eraseAction()153 void StateManagerWindow::eraseAction() {
154   QList<QTreeWidgetItem*> items = list->selectedItems();
155   if(items.count() > 0) {
156     QTreeWidgetItem *item = items[0];
157     unsigned n = item->data(0, Qt::UserRole).toUInt();
158     eraseState(n);
159     update();
160     synchronize();
161   }
162 }
163 
filename() const164 string StateManagerWindow::filename() const {
165   string name = filepath(nall::basename(cartridge.fileName), config().path.state);
166   name << ".bsa";
167   return name;
168 }
169 
isStateValid(unsigned slot)170 bool StateManagerWindow::isStateValid(unsigned slot) {
171   if(SNES::cartridge.loaded() == false) return false;
172   file fp;
173   if(fp.open(filename(), file::mode_read) == false) return false;
174   if(fp.size() < (slot + 1) * SNES::system.serialize_size()) { fp.close(); return false; }
175   fp.seek(slot * SNES::system.serialize_size());
176   uint32_t signature = fp.readl(4);
177   if(signature == 0) { fp.close(); return false; }
178   uint32_t version = fp.readl(4);
179   if(version != bsnesSerializerVersion) { fp.close(); return false; }
180   fp.close();
181   return true;
182 }
183 
getStateDescription(unsigned slot)184 string StateManagerWindow::getStateDescription(unsigned slot) {
185   if(isStateValid(slot) == false) return "";
186   file fp;
187   fp.open(filename(), file::mode_read);
188   char description[513];
189   fp.seek(slot * SNES::system.serialize_size() + 12);
190   fp.read((uint8_t*)description, 512);
191   fp.close();
192   description[512] = 0;
193   return description;
194 }
195 
setStateDescription(unsigned slot,const string & text)196 void StateManagerWindow::setStateDescription(unsigned slot, const string &text) {
197   if(isStateValid(slot) == false) return;
198   file fp;
199   fp.open(filename(), file::mode_readwrite);
200   char description[512];
201   memset(&description, 0, sizeof description);
202   strncpy(description, text, 512);
203   fp.seek(slot * SNES::system.serialize_size() + 12);
204   fp.write((uint8_t*)description, 512);
205   fp.close();
206 }
207 
loadState(unsigned slot)208 void StateManagerWindow::loadState(unsigned slot) {
209   if(isStateValid(slot) == false) return;
210   file fp;
211   fp.open(filename(), file::mode_read);
212   fp.seek(slot * SNES::system.serialize_size());
213   unsigned size = SNES::system.serialize_size();
214   uint8_t *data = new uint8_t[size];
215   fp.read(data, size);
216   fp.close();
217 
218   serializer state(data, size);
219   delete[] data;
220 
221   if(SNES::system.unserialize(state) == true) {
222     //toolsWindow->close();
223   }
224 }
225 
saveState(unsigned slot)226 void StateManagerWindow::saveState(unsigned slot) {
227   file fp;
228   if(file::exists(filename()) == false) {
229     //try and create the file, bail out on failure (eg read-only device)
230     if(fp.open(filename(), file::mode_write) == false) return;
231     fp.close();
232   }
233 
234   SNES::system.runtosave();
235   serializer state = SNES::system.serialize();
236 
237   fp.open(filename(), file::mode_readwrite);
238 
239   //user may save to slot #2 when slot #1 is empty; pad file to current slot if needed
240   unsigned stateOffset = SNES::system.serialize_size() * slot;
241   fp.seek(fp.size());
242   while(fp.size() < stateOffset) fp.write(0x00);
243 
244   fp.seek(stateOffset);
245   fp.write(state.data(), state.size());
246   fp.close();
247 }
248 
eraseState(unsigned slot)249 void StateManagerWindow::eraseState(unsigned slot) {
250   if(isStateValid(slot) == false) return;
251   file fp;
252   fp.open(filename(), file::mode_readwrite);
253   unsigned size = SNES::system.serialize_size();
254   fp.seek(slot * size);
255   for(unsigned i = 0; i < size; i++) fp.write(0x00);
256   fp.close();
257 
258   //shrink state archive as much as possible:
259   //eg if only slot #2 and slot #31 were valid, but slot #31 was erased,
260   //file can be resized to only hold blank slot #1 + valid slot #2
261   signed lastValidState = -1;
262   for(signed i = StateCount - 1; i >= 0; i--) {
263     if(isStateValid(i)) {
264       lastValidState = i;
265       break;
266     }
267   }
268 
269   if(lastValidState == -1) {
270     //no states used, remove empty file
271     unlink(filename());
272   } else {
273     unsigned neededFileSize = (lastValidState + 1) * SNES::system.serialize_size();
274     file fp;
275     if(fp.open(filename(), file::mode_readwrite)) {
276       if(fp.size() > neededFileSize) fp.truncate(neededFileSize);
277       fp.close();
278     }
279   }
280 }
281