Make various instance stuff to take references to other instance objs
[lsnes.git] / src / platform / wxwidgets / requestrom.cpp
blob79013a292409b15da7d6e420cddbec6b885eae2c
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/settings.hpp"
8 #include "core/window.hpp"
9 #include "interface/romtype.hpp"
10 #include "library/zip.hpp"
12 namespace
14 std::string rom_path()
16 return lsnes_instance.setcache.get("rompath");
19 bool can_load_singlefile(core_type* t)
21 unsigned icnt = t->get_image_count();
22 unsigned pmand = 0, tmand = 0;
23 if(!icnt) return false;
24 if(icnt > 0) pmand |= t->get_image_info(0).mandatory;
25 if(t->get_biosname() != "" && icnt > 1) pmand |= t->get_image_info(1).mandatory;
26 for(unsigned i = 0; i < t->get_image_count(); i++)
27 tmand |= t->get_image_info(i).mandatory;
28 return pmand == tmand;
31 std::string implode_set(const std::set<std::string>& s)
33 std::string a;
34 for(auto i : s) {
35 if(a != "") a += ";";
36 a += ("*." + i);
38 return a;
41 int resolve_core(std::map<unsigned, core_type*> coreid, const std::string& filename, int findex)
43 if(coreid.count(findex))
44 return findex; //Already resolved.
45 if(loaded_rom::is_gamepak(filename))
46 return 0; //Gamepaks don't resolve.
48 //Get the extension.
49 regex_results r = regex(".*\\.([^.]+)", filename);
50 if(!r)
51 return 0; //WTF is this? Leave unresolved.
52 std::string extension = r[1];
54 std::map<core_type*, unsigned> candidates;
55 for(auto i : coreid) {
56 if(!can_load_singlefile(i.second))
57 continue;
58 if(i.second->is_hidden())
59 continue;
60 if(i.second->isnull())
61 continue;
62 std::set<std::string> exts;
63 unsigned base = (i.second->get_biosname() != "" && i.second->get_image_count() > 1) ? 1 : 0;
64 for(auto j : i.second->get_image_info(base).extensions)
65 if(j == extension) {
66 //This is a candidate.
67 candidates[i.second] = i.first;
71 if(candidates.empty())
72 return 0; //Err. Leave unresolved.
73 if(candidates.size() == 1)
74 return candidates.begin()->second; //Only one candidate.
76 //Okay, we have multiple candidates. Prompt among them.
77 std::vector<std::string> choices;
78 std::vector<unsigned> indexes;
79 for(auto i : candidates) {
80 choices.push_back(i.first->get_core_identifier() + " [" + i.first->get_hname() + "]");
81 indexes.push_back(i.second);
83 std::string coretext;
84 coretext = pick_among(NULL, "Choose core", "Choose core to load the ROM", choices, 0);
85 for(size_t i = 0; i < choices.size(); i++)
86 if(choices[i] == coretext)
87 return indexes[i];
88 //Err?
89 return 0;
92 void do_load_rom_image_single(wxwin_mainwindow* parent)
94 std::map<std::string, core_type*> cores;
95 std::map<unsigned, core_type*> coreid;
96 std::string filter;
97 unsigned corecount = 0;
98 std::set<std::string> all_filetypes;
99 all_filetypes.insert("lsgp");
100 for(auto i : core_type::get_core_types()) {
101 if(!can_load_singlefile(i))
102 continue;
103 unsigned base = (i->get_biosname() != "" && i->get_image_count() > 1) ? 1 : 0;
104 for(auto j : i->get_image_info(base).extensions)
105 all_filetypes.insert(j);
106 cores[i->get_hname() + " [" + i->get_core_identifier() + "]"] = i;
108 filter += "Autodetect|" + implode_set(all_filetypes);
109 for(auto i : cores) {
110 if(!can_load_singlefile(i.second))
111 continue;
112 if(i.second->is_hidden())
113 continue;
114 if(i.second->isnull())
115 continue;
116 std::set<std::string> exts;
117 unsigned base = (i.second->get_biosname() != "" && i.second->get_image_count() > 1) ? 1 : 0;
118 for(auto j : i.second->get_image_info(base).extensions)
119 exts.insert(j);
120 filter += "|" + i.first + "|" + implode_set(exts);
121 coreid[++corecount] = i.second;
123 filter += "|All files|*";
124 std::string directory = lsnes_instance.setcache.get("rompath");
125 wxFileDialog* d = new wxFileDialog(parent, towxstring("Choose ROM to load"), towxstring(directory),
126 wxT(""), towxstring(filter), wxFD_OPEN);
127 if(d->ShowModal() == wxID_CANCEL) {
128 delete d;
129 return;
131 romload_request req;
132 recentfiles::multirom mr;
133 std::string filename = tostdstring(d->GetPath());
134 int findex = d->GetFilterIndex();
135 try {
136 findex = resolve_core(coreid, filename, findex);
137 } catch(canceled_exception& e) {
138 return;
140 if(!coreid.count(findex)) {
141 //Autodetect.
142 mr.packfile = req.packfile = filename;
143 } else {
144 mr.core = req.core = coreid[findex]->get_core_identifier();
145 mr.system = req.system = coreid[findex]->get_iname();
146 mr.singlefile = req.singlefile = filename;
148 parent->recent_roms->add(mr);
149 lsnes_instance.iqueue.run_async([req]() {
150 lsnes_instance.command.invoke("unpause-emulator");
151 load_new_rom(req);
152 }, [](std::exception& e) {});
155 class multirom_dialog : public wxDialog
157 public:
158 void on_wclose(wxCloseEvent& e)
160 EndModal(wxID_CANCEL);
162 multirom_dialog(wxWindow* parent, std::string rtype, core_type& _t)
163 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Load " + rtype + " ROM")), t(_t)
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 if(timer) {
227 timer->Stop();
228 delete timer;
231 void timer_update_hashes()
233 for(auto i = 0; i < ROM_SLOT_COUNT && filenames[i]; i++) {
234 if(hash_ready[i])
235 continue;
236 if(!hashfutures[i].ready())
237 continue;
238 try {
239 hashes[i]->SetLabel(towxstring("Hash: " + hashfutures[i].read()));
240 } catch(std::runtime_error& e) {
241 hashes[i]->SetLabel(towxstring("Hash: Error"));
243 hash_ready[i] = true;
246 void do_command_event(wxCommandEvent& e)
248 int id = e.GetId();
249 if(id == wxID_HIGHEST + 1) {
250 if(!check_requirements()) {
251 show_message_ok(this, "Needed ROM missing", "At least one required ROM "
252 "is missing!", wxICON_EXCLAMATION);
253 return;
255 EndModal(wxID_OK);
256 } else if(id == wxID_HIGHEST + 2) {
257 EndModal(wxID_CANCEL);
258 } else if(id >= wxID_HIGHEST + 100 && id <= wxID_HIGHEST + 199) {
259 filename_updated(id - (wxID_HIGHEST + 100));
260 } else if(id >= wxID_HIGHEST + 200 && id <= wxID_HIGHEST + 299) {
261 do_fileselect(id - (wxID_HIGHEST + 200));
264 std::string getfilename(unsigned i)
266 if(i >= ROM_SLOT_COUNT || !filenames[i])
267 return "";
268 return tostdstring(filenames[i]->GetValue());
270 std::string getregion()
272 int i = regions->GetSelection();
273 if(i == wxNOT_FOUND)
274 return t.get_preferred_region().get_iname();
275 return regions_known[i]->get_iname();
277 private:
278 bool check_requirements()
280 unsigned pm = 0, tm = 0;
281 for(unsigned i = 0; i < t.get_image_count(); i++) {
282 core_romimage_info iinfo = t.get_image_info(i);
283 tm |= iinfo.mandatory;
284 if(filenames[i]->GetValue().Length() != 0)
285 pm |= iinfo.mandatory;
287 return pm == tm;
289 void do_fileselect(unsigned i)
291 if(i >= ROM_SLOT_COUNT || !fileselect[i])
292 return;
293 std::string filter;
294 std::set<std::string> exts;
295 for(auto j : t.get_image_info(i).extensions)
296 exts.insert(j);
297 filter = "Known file types|" + implode_set(exts) + "|All files|*";
298 std::string directory;
299 if(t.get_biosname() != "" && i == 0)
300 directory = "firmwarepath";
301 else
302 directory = "rompath";
303 directory = lsnes_instance.setcache.get(directory);
304 core_romimage_info iinfo = t.get_image_info(i);
305 wxFileDialog* d = new wxFileDialog(this, towxstring("Load " + iinfo.hname),
306 towxstring(directory), wxT(""), towxstring(filter), wxFD_OPEN);
307 if(d->ShowModal() == wxID_CANCEL) {
308 delete d;
309 return;
311 filenames[i]->SetValue(d->GetPath());
312 delete d;
314 void filename_updated(unsigned i)
316 if(i >= t.get_image_count() || !filenames[i])
317 return;
318 uint64_t header = t.get_image_info(i).headersize;
320 std::string filename = tostdstring(filenames[i]->GetValue());
321 if(!zip::file_exists(filename)) {
322 hashfutures[i] = fileimage::hashval();
323 hash_ready[i] = true;
324 hashes[i]->SetLabel(towxstring("Not found"));
325 return;
327 //TODO: Handle files inside ZIP files.
328 hashfutures[i] = lsnes_image_hasher(filename, fileimage::std_headersize_fn(header));
329 if((hash_ready[i] = hashfutures[i].ready()))
330 try {
331 hashes[i]->SetLabel(towxstring("Hash: " + hashfutures[i].read()));
332 } catch(std::runtime_error& e) {
333 hashes[i]->SetLabel(towxstring("Hash: Error"));
335 else
336 hashes[i]->SetLabel(towxstring("Hash: Calculating..."));
337 okb->Enable(check_requirements());
339 struct update_timer : public wxTimer
341 public:
342 update_timer(multirom_dialog* p)
344 w = p;
346 void Notify()
348 w->timer_update_hashes();
350 private:
351 multirom_dialog* w;
353 wxComboBox* regions;
354 wxTextCtrl* filenames[ROM_SLOT_COUNT];
355 wxButton* fileselect[ROM_SLOT_COUNT];
356 wxStaticText* hashes[ROM_SLOT_COUNT];
357 bool hash_ready[ROM_SLOT_COUNT];
358 fileimage::hashval hashfutures[ROM_SLOT_COUNT];
359 wxButton* okb;
360 wxButton* cancelb;
361 update_timer* timer;
362 core_type& t;
363 std::vector<core_region*> regions_known;
366 void do_load_rom_image_multiple(wxwin_mainwindow* parent, core_type& t)
368 multirom_dialog* d = new multirom_dialog(parent, t.get_hname(), t);
369 if(d->ShowModal() == wxID_CANCEL) {
370 delete d;
371 return;
373 std::string files[ROM_SLOT_COUNT];
374 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++)
375 files[i] = d->getfilename(i);
376 std::string region = d->getregion();
377 delete d;
378 romload_request req;
379 recentfiles::multirom mr;
380 mr.core = req.core = t.get_core_identifier();
381 mr.system = req.system = t.get_iname();
382 mr.region = req.region = region;
383 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
384 if(files[i] != "") {
385 mr.files.resize(i + 1);
386 mr.files[i] = files[i];
388 req.files[i] = files[i];
390 parent->recent_roms->add(mr);
391 lsnes_instance.iqueue.run([req]() {
392 lsnes_instance.command.invoke("unpause-emulator");
393 load_new_rom(req);
395 return;
399 void wxwin_mainwindow::request_rom(rom_request& req)
401 std::vector<std::string> choices;
402 for(auto i : req.cores)
403 choices.push_back(i->get_core_identifier());
404 std::string coretext;
405 try {
406 if(choices.size() > 1 && !req.core_guessed)
407 coretext = pick_among(this, "Choose core", "Choose core to load the ROM", choices,
408 req.selected);
409 else
410 coretext = choices[req.selected];
411 } catch(canceled_exception& e) {
412 return;
414 for(size_t i = 0; i < req.cores.size(); i++)
415 if(coretext == req.cores[i]->get_core_identifier())
416 req.selected = i;
417 core_type& type = *req.cores[req.selected];
419 bool has_bios = (type.get_biosname() != "");
420 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
421 if(!req.has_slot[i])
422 continue;
423 if(req.guessed[i])
424 continue; //Leave these alone.
425 if(i >= type.get_image_count()) {
426 messages << "wxwin_mainwindow::request_rom: Present image index (" << i
427 << ") out of range!" << std::endl;
428 continue; //Shouldn't happen.
430 core_romimage_info iinfo = type.get_image_info(i);
431 std::string directory;
432 if(i == 0 && has_bios)
433 directory = "firmwarepath";
434 else
435 directory = "rompath";
436 directory = lsnes_instance.setcache.get(directory);
437 std::string _title = "Select " + iinfo.hname;
438 std::string filespec = "Known ROMs|";
439 std::string exts = "";
440 std::string defaultname = "";
441 for(auto j : iinfo.extensions) {
442 exts = exts + ";*." + j;
443 if(zip::file_exists(directory + "/" + req.filename[i] + "." + j))
444 defaultname = req.filename[i] + "." + j;
446 if(exts != "") exts = exts.substr(1);
447 filespec = "Known ROMs (" + exts + ")|" + exts + "|All files|*";
448 wxFileDialog* d;
449 std::string hash;
450 uint64_t header = type.get_image_info(i).headersize;
451 again:
452 d = new wxFileDialog(this, towxstring(_title), towxstring(directory), wxT(""),
453 towxstring(filespec), wxFD_OPEN);
454 if(defaultname != "") d->SetFilename(towxstring(defaultname));
455 if(d->ShowModal() == wxID_CANCEL) {
456 delete d;
457 return;
459 req.filename[i] = tostdstring(d->GetPath());
460 delete d;
461 //Check the hash.
462 if(!zip::file_exists(req.filename[i])) {
463 show_message_ok(this, "File not found", "Can't find '" + req.filename[i] + "'",
464 wxICON_EXCLAMATION);
465 goto again;
467 try {
468 auto future = lsnes_image_hasher(req.filename[i], fileimage::std_headersize_fn(header));
469 //Dirty method to run the event loop until hashing finishes.
470 while(!future.ready()) {
471 wxSafeYield();
472 usleep(50000);
474 std::string hash = future.read();
475 if(hash != req.hash[i]) {
476 //Hash mismatches.
477 wxMessageDialog* d3 = new wxMessageDialog(this, towxstring("The ROM checksum does "
478 "not match movie\n\nProceed anyway?"), towxstring("Checksum error"),
479 wxYES_NO | wxNO_DEFAULT | wxICON_EXCLAMATION);
480 int r = d3->ShowModal();
481 d3->Destroy();
482 if(r == wxID_NO) goto again;
484 } catch(...) {
485 wxMessageDialog* d3 = new wxMessageDialog(this, towxstring("Can't read checksum for "
486 "ROM\n\nProceed anyway?"), towxstring("Checksum error"), wxYES_NO |
487 wxYES_DEFAULT | wxICON_EXCLAMATION);
488 int r = d3->ShowModal();
489 d3->Destroy();
490 if(r == wxID_NO) goto again;
493 req.canceled = false;
496 void wxwin_mainwindow::do_load_rom_image(core_type* t)
498 if(!t) {
499 return do_load_rom_image_single(this);
500 } else {
501 return do_load_rom_image_multiple(this, *t);