Fix scaling-related crashes
[lsnes.git] / src / platform / wxwidgets / requestrom.cpp
blob43b0c42902c401bf9b59a83cfc22a93f80db56a9
1 #include "platform/wxwidgets/platform.hpp"
2 #include "platform/wxwidgets/window_mainwindow.hpp"
3 #include "platform/wxwidgets/window-romload.hpp"
4 #include "core/command.hpp"
5 #include "core/instance.hpp"
6 #include "core/mainloop.hpp"
7 #include "core/messages.hpp"
8 #include "core/settings.hpp"
9 #include "core/window.hpp"
10 #include "interface/romtype.hpp"
11 #include "library/zip.hpp"
13 namespace
15 bool can_load_singlefile(core_type* t)
17 unsigned icnt = t->get_image_count();
18 unsigned pmand = 0, tmand = 0;
19 if(!icnt) return false;
20 if(icnt > 0) pmand |= t->get_image_info(0).mandatory;
21 if(t->get_biosname() != "" && icnt > 1) pmand |= t->get_image_info(1).mandatory;
22 for(unsigned i = 0; i < t->get_image_count(); i++)
23 tmand |= t->get_image_info(i).mandatory;
24 return pmand == tmand;
27 std::string implode_set(const std::set<std::string>& s)
29 std::string a;
30 for(auto i : s) {
31 if(a != "") a += ";";
32 a += ("*." + i);
34 return a;
37 int resolve_core(std::map<unsigned, core_type*> coreid, const std::string& filename, int findex)
39 CHECK_UI_THREAD;
40 if(coreid.count(findex))
41 return findex; //Already resolved.
42 if(loaded_rom::is_gamepak(filename))
43 return 0; //Gamepaks don't resolve.
45 //Get the extension.
46 regex_results r = regex(".*\\.([^.]+)", filename);
47 if(!r)
48 return 0; //WTF is this? Leave unresolved.
49 std::string extension = r[1];
51 std::map<core_type*, unsigned> candidates;
52 for(auto i : coreid) {
53 if(!can_load_singlefile(i.second))
54 continue;
55 if(i.second->is_hidden())
56 continue;
57 if(i.second->isnull())
58 continue;
59 std::set<std::string> exts;
60 unsigned base = (i.second->get_biosname() != "" && i.second->get_image_count() > 1) ? 1 : 0;
61 for(auto j : i.second->get_image_info(base).extensions)
62 if(j == extension) {
63 //This is a candidate.
64 candidates[i.second] = i.first;
68 if(candidates.empty())
69 return 0; //Err. Leave unresolved.
70 if(candidates.size() == 1)
71 return candidates.begin()->second; //Only one candidate.
73 //Okay, we have multiple candidates. Prompt among them.
74 std::vector<std::string> choices;
75 std::vector<unsigned> indexes;
76 for(auto i : candidates) {
77 choices.push_back(i.first->get_core_identifier() + " [" + i.first->get_hname() + "]");
78 indexes.push_back(i.second);
80 std::string coretext;
81 coretext = pick_among(NULL, "Choose core", "Choose core to load the ROM", choices, 0);
82 for(size_t i = 0; i < choices.size(); i++)
83 if(choices[i] == coretext)
84 return indexes[i];
85 //Err?
86 return 0;
89 void do_load_rom_image_single(wxwin_mainwindow* parent, emulator_instance& inst)
91 CHECK_UI_THREAD;
92 std::map<std::string, core_type*> cores;
93 std::map<unsigned, core_type*> coreid;
94 std::string filter;
95 unsigned corecount = 0;
96 std::set<std::string> all_filetypes;
97 all_filetypes.insert("lsgp");
98 for(auto i : core_type::get_core_types()) {
99 if(!can_load_singlefile(i))
100 continue;
101 unsigned base = (i->get_biosname() != "" && i->get_image_count() > 1) ? 1 : 0;
102 for(auto j : i->get_image_info(base).extensions)
103 all_filetypes.insert(j);
104 cores[i->get_hname() + " [" + i->get_core_identifier() + "]"] = i;
106 filter += "Autodetect|" + implode_set(all_filetypes);
107 for(auto i : cores) {
108 if(!can_load_singlefile(i.second))
109 continue;
110 if(i.second->is_hidden())
111 continue;
112 if(i.second->isnull())
113 continue;
114 std::set<std::string> exts;
115 unsigned base = (i.second->get_biosname() != "" && i.second->get_image_count() > 1) ? 1 : 0;
116 for(auto j : i.second->get_image_info(base).extensions)
117 exts.insert(j);
118 filter += "|" + i.first + "|" + implode_set(exts);
119 coreid[++corecount] = i.second;
121 filter += "|All files|*";
122 std::string directory = inst.setcache->get("rompath");
123 wxFileDialog* d = new wxFileDialog(parent, towxstring("Choose ROM to load"), towxstring(directory),
124 wxT(""), towxstring(filter), wxFD_OPEN);
125 if(d->ShowModal() == wxID_CANCEL) {
126 delete d;
127 return;
129 romload_request req;
130 recentfiles::multirom mr;
131 std::string filename = tostdstring(d->GetPath());
132 int findex = d->GetFilterIndex();
133 try {
134 findex = resolve_core(coreid, filename, findex);
135 } catch(canceled_exception& e) {
136 return;
138 if(!coreid.count(findex)) {
139 //Autodetect.
140 mr.packfile = req.packfile = filename;
141 } else {
142 mr.core = req.core = coreid[findex]->get_core_identifier();
143 mr.system = req.system = coreid[findex]->get_iname();
144 mr.singlefile = req.singlefile = filename;
146 parent->recent_roms->add(mr);
147 inst.iqueue->run_async([req]() {
148 CORE().command->invoke("unpause-emulator");
149 load_new_rom(req);
150 }, [](std::exception& e) {});
153 class multirom_dialog : public wxDialog
155 public:
156 void on_wclose(wxCloseEvent& e)
158 CHECK_UI_THREAD;
159 EndModal(wxID_CANCEL);
161 multirom_dialog(wxWindow* parent, emulator_instance& _inst, std::string rtype, core_type& _t)
162 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Load " + rtype + " ROM")), inst(_inst), t(_t)
164 CHECK_UI_THREAD;
165 Centre();
166 wxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
167 SetSizer(vsizer);
168 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(multirom_dialog::on_wclose), NULL, this);
170 wxSizer* hsizer2 = new wxBoxSizer(wxHORIZONTAL);
171 hsizer2->Add(new wxStaticText(this, wxID_ANY, wxT("Region: ")), 0, wxGROW);
172 std::vector<wxString> regions_list;
173 core_region& prefr = t.get_preferred_region();
174 unsigned regindex = 0;
175 for(auto i : t.get_regions()) {
176 if(i == &prefr) regindex = regions_list.size();
177 regions_list.push_back(towxstring(i->get_hname()));
178 regions_known.push_back(i);
180 regions = new wxComboBox(this, wxID_ANY, regions_list[regindex], wxDefaultPosition,
181 wxDefaultSize, regions_list.size(), &regions_list[0], wxCB_READONLY);
182 hsizer2->Add(regions, 0, wxGROW);
183 vsizer->Add(hsizer2, 0, wxGROW);
185 wxSizer* rarray = new wxFlexGridSizer(2 * t.get_image_count(), 3, 0, 0);
186 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
187 filenames[i] = NULL;
188 fileselect[i] = NULL;
190 for(unsigned i = 0; i < t.get_image_count(); i++) {
191 core_romimage_info iinfo = t.get_image_info(i);
192 rarray->Add(new wxStaticText(this, wxID_ANY, towxstring(iinfo.hname)), 0, wxGROW);
193 rarray->Add(filenames[i] = new wxTextCtrl(this, wxID_HIGHEST + 100 + i, wxT(""),
194 wxDefaultPosition, wxSize(400, -1)), 1, wxGROW);
195 rarray->Add(fileselect[i] = new wxButton(this, wxID_HIGHEST + 200 + i,
196 towxstring("...")), 0, wxGROW);
197 rarray->Add(new wxStaticText(this, wxID_ANY, towxstring("")), 0, wxGROW);
198 rarray->Add(hashes[i] = new wxStaticText(this, wxID_ANY, wxT("Not found")), 1,
199 wxGROW);
200 rarray->Add(new wxStaticText(this, wxID_ANY, towxstring("")), 0, wxGROW);
201 hash_ready[i] = true;
202 filenames[i]->Connect(wxEVT_COMMAND_TEXT_UPDATED,
203 wxCommandEventHandler(multirom_dialog::do_command_event), NULL, this);
204 fileselect[i]->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
205 wxCommandEventHandler(multirom_dialog::do_command_event), NULL, this);
207 vsizer->Add(rarray, 1, wxGROW);
209 wxSizer* buttonbar = new wxBoxSizer(wxHORIZONTAL);
210 buttonbar->Add(okb = new wxButton(this, wxID_HIGHEST + 1, wxT("Load")), 0, wxGROW);
211 buttonbar->AddStretchSpacer();
212 buttonbar->Add(cancelb = new wxButton(this, wxID_HIGHEST + 2, wxT("Cancel")), 0, wxGROW);
213 vsizer->Add(buttonbar, 0, wxGROW);
214 okb->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
215 wxCommandEventHandler(multirom_dialog::do_command_event), NULL, this);
216 okb->Disable();
217 cancelb->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
218 wxCommandEventHandler(multirom_dialog::do_command_event), NULL, this);
219 timer = new update_timer(this);
220 timer->Start(100);
221 vsizer->SetSizeHints(this);
222 Fit();
224 ~multirom_dialog()
226 CHECK_UI_THREAD;
227 if(timer) {
228 timer->Stop();
229 delete timer;
232 void timer_update_hashes()
234 CHECK_UI_THREAD;
235 for(auto i = 0; i < ROM_SLOT_COUNT && filenames[i]; i++) {
236 if(hash_ready[i])
237 continue;
238 if(!hashfutures[i].ready())
239 continue;
240 try {
241 hashes[i]->SetLabel(towxstring("Hash: " + hashfutures[i].read()));
242 } catch(std::runtime_error& e) {
243 hashes[i]->SetLabel(towxstring("Hash: Error"));
245 hash_ready[i] = true;
248 void do_command_event(wxCommandEvent& e)
250 CHECK_UI_THREAD;
251 int id = e.GetId();
252 if(id == wxID_HIGHEST + 1) {
253 if(!check_requirements()) {
254 show_message_ok(this, "Needed ROM missing", "At least one required ROM "
255 "is missing!", wxICON_EXCLAMATION);
256 return;
258 EndModal(wxID_OK);
259 } else if(id == wxID_HIGHEST + 2) {
260 EndModal(wxID_CANCEL);
261 } else if(id >= wxID_HIGHEST + 100 && id <= wxID_HIGHEST + 199) {
262 filename_updated(id - (wxID_HIGHEST + 100));
263 } else if(id >= wxID_HIGHEST + 200 && id <= wxID_HIGHEST + 299) {
264 do_fileselect(id - (wxID_HIGHEST + 200));
267 std::string getfilename(unsigned i)
269 CHECK_UI_THREAD;
270 if(i >= ROM_SLOT_COUNT || !filenames[i])
271 return "";
272 return tostdstring(filenames[i]->GetValue());
274 std::string getregion()
276 CHECK_UI_THREAD;
277 int i = regions->GetSelection();
278 if(i == wxNOT_FOUND)
279 return t.get_preferred_region().get_iname();
280 return regions_known[i]->get_iname();
282 private:
283 bool check_requirements()
285 CHECK_UI_THREAD;
286 unsigned pm = 0, tm = 0;
287 for(unsigned i = 0; i < t.get_image_count(); i++) {
288 core_romimage_info iinfo = t.get_image_info(i);
289 tm |= iinfo.mandatory;
290 if(filenames[i]->GetValue().Length() != 0)
291 pm |= iinfo.mandatory;
293 return pm == tm;
295 void do_fileselect(unsigned i)
297 CHECK_UI_THREAD;
298 if(i >= ROM_SLOT_COUNT || !fileselect[i])
299 return;
300 std::string filter;
301 std::set<std::string> exts;
302 for(auto j : t.get_image_info(i).extensions)
303 exts.insert(j);
304 filter = "Known file types|" + implode_set(exts) + "|All files|*";
305 std::string directory;
306 if(t.get_biosname() != "" && i == 0)
307 directory = "firmwarepath";
308 else
309 directory = "rompath";
310 directory = inst.setcache->get(directory);
311 core_romimage_info iinfo = t.get_image_info(i);
312 wxFileDialog* d = new wxFileDialog(this, towxstring("Load " + iinfo.hname),
313 towxstring(directory), wxT(""), towxstring(filter), wxFD_OPEN);
314 if(d->ShowModal() == wxID_CANCEL) {
315 delete d;
316 return;
318 filenames[i]->SetValue(d->GetPath());
319 delete d;
321 void filename_updated(unsigned i)
323 CHECK_UI_THREAD;
324 if(i >= t.get_image_count() || !filenames[i])
325 return;
326 uint64_t header = t.get_image_info(i).headersize;
328 std::string filename = tostdstring(filenames[i]->GetValue());
329 if(!zip::file_exists(filename)) {
330 hashfutures[i] = fileimage::hashval();
331 hash_ready[i] = true;
332 hashes[i]->SetLabel(towxstring("Not found"));
333 return;
335 //TODO: Handle files inside ZIP files.
336 hashfutures[i] = lsnes_image_hasher(filename, fileimage::std_headersize_fn(header));
337 if((hash_ready[i] = hashfutures[i].ready()))
338 try {
339 hashes[i]->SetLabel(towxstring("Hash: " + hashfutures[i].read()));
340 } catch(std::runtime_error& e) {
341 hashes[i]->SetLabel(towxstring("Hash: Error"));
343 else
344 hashes[i]->SetLabel(towxstring("Hash: Calculating..."));
345 okb->Enable(check_requirements());
347 struct update_timer : public wxTimer
349 public:
350 update_timer(multirom_dialog* p)
352 w = p;
354 void Notify()
356 w->timer_update_hashes();
358 private:
359 multirom_dialog* w;
361 emulator_instance& inst;
362 wxComboBox* regions;
363 wxTextCtrl* filenames[ROM_SLOT_COUNT];
364 wxButton* fileselect[ROM_SLOT_COUNT];
365 wxStaticText* hashes[ROM_SLOT_COUNT];
366 bool hash_ready[ROM_SLOT_COUNT];
367 fileimage::hashval hashfutures[ROM_SLOT_COUNT];
368 wxButton* okb;
369 wxButton* cancelb;
370 update_timer* timer;
371 core_type& t;
372 std::vector<core_region*> regions_known;
375 void do_load_rom_image_multiple(wxwin_mainwindow* parent, emulator_instance& inst, core_type& t)
377 CHECK_UI_THREAD;
378 multirom_dialog* d = new multirom_dialog(parent, inst, t.get_hname(), t);
379 if(d->ShowModal() == wxID_CANCEL) {
380 delete d;
381 return;
383 std::string files[ROM_SLOT_COUNT];
384 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++)
385 files[i] = d->getfilename(i);
386 std::string region = d->getregion();
387 delete d;
388 romload_request req;
389 recentfiles::multirom mr;
390 mr.core = req.core = t.get_core_identifier();
391 mr.system = req.system = t.get_iname();
392 mr.region = req.region = region;
393 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
394 if(files[i] != "") {
395 mr.files.resize(i + 1);
396 mr.files[i] = files[i];
398 req.files[i] = files[i];
400 parent->recent_roms->add(mr);
401 inst.iqueue->run([req]() {
402 CORE().command->invoke("unpause-emulator");
403 load_new_rom(req);
405 return;
409 void wxwin_mainwindow::request_rom(rom_request& req)
411 CHECK_UI_THREAD;
412 std::vector<std::string> choices;
413 for(auto i : req.cores)
414 choices.push_back(i->get_core_identifier());
415 std::string coretext;
416 try {
417 if(choices.size() > 1 && !req.core_guessed)
418 coretext = pick_among(this, "Choose core", "Choose core to load the ROM", choices,
419 req.selected);
420 else
421 coretext = choices[req.selected];
422 } catch(canceled_exception& e) {
423 return;
425 for(size_t i = 0; i < req.cores.size(); i++)
426 if(coretext == req.cores[i]->get_core_identifier())
427 req.selected = i;
428 core_type& type = *req.cores[req.selected];
430 bool has_bios = (type.get_biosname() != "");
431 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
432 if(!req.has_slot[i])
433 continue;
434 if(req.guessed[i])
435 continue; //Leave these alone.
436 if(i >= type.get_image_count()) {
437 messages << "wxwin_mainwindow::request_rom: Present image index (" << i
438 << ") out of range!" << std::endl;
439 continue; //Shouldn't happen.
441 core_romimage_info iinfo = type.get_image_info(i);
442 std::string directory;
443 if(i == 0 && has_bios)
444 directory = "firmwarepath";
445 else
446 directory = "rompath";
447 directory = inst.setcache->get(directory);
448 std::string _title = "Select " + iinfo.hname;
449 std::string filespec = "Known ROMs|";
450 std::string exts = "";
451 std::string defaultname = "";
452 for(auto j : iinfo.extensions) {
453 exts = exts + ";*." + j;
454 if(zip::file_exists(directory + "/" + req.filename[i] + "." + j))
455 defaultname = req.filename[i] + "." + j;
457 if(exts != "") exts = exts.substr(1);
458 filespec = "Known ROMs (" + exts + ")|" + exts + "|All files|*";
459 wxFileDialog* d;
460 std::string hash;
461 uint64_t header = type.get_image_info(i).headersize;
462 again:
463 d = new wxFileDialog(this, towxstring(_title), towxstring(directory), wxT(""),
464 towxstring(filespec), wxFD_OPEN);
465 if(defaultname != "") d->SetFilename(towxstring(defaultname));
466 if(d->ShowModal() == wxID_CANCEL) {
467 delete d;
468 return;
470 req.filename[i] = tostdstring(d->GetPath());
471 delete d;
472 //Check the hash.
473 if(!zip::file_exists(req.filename[i])) {
474 show_message_ok(this, "File not found", "Can't find '" + req.filename[i] + "'",
475 wxICON_EXCLAMATION);
476 goto again;
478 try {
479 auto future = lsnes_image_hasher(req.filename[i], fileimage::std_headersize_fn(header));
480 //Dirty method to run the event loop until hashing finishes.
481 while(!future.ready()) {
482 wxSafeYield();
483 usleep(50000);
485 std::string hash = future.read();
486 if(hash != req.hash[i]) {
487 //Hash mismatches.
488 wxMessageDialog* d3 = new wxMessageDialog(this, towxstring("The ROM checksum does "
489 "not match movie\n\nProceed anyway?"), towxstring("Checksum error"),
490 wxYES_NO | wxNO_DEFAULT | wxICON_EXCLAMATION);
491 int r = d3->ShowModal();
492 d3->Destroy();
493 if(r == wxID_NO) goto again;
495 } catch(...) {
496 wxMessageDialog* d3 = new wxMessageDialog(this, towxstring("Can't read checksum for "
497 "ROM\n\nProceed anyway?"), towxstring("Checksum error"), wxYES_NO |
498 wxYES_DEFAULT | wxICON_EXCLAMATION);
499 int r = d3->ShowModal();
500 d3->Destroy();
501 if(r == wxID_NO) goto again;
504 req.canceled = false;
507 void wxwin_mainwindow::do_load_rom_image(core_type* t)
509 if(!t) {
510 return do_load_rom_image_single(this, inst);
511 } else {
512 return do_load_rom_image_multiple(this, inst, *t);