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