Upload UI
[lsnes.git] / src / platform / wxwidgets / requestrom.cpp
blob645988713ec86f64263533acb29637a0fd5fe893
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/mainloop.hpp"
6 #include "core/settings.hpp"
7 #include "core/window.hpp"
8 #include "interface/romtype.hpp"
9 #include "library/zip.hpp"
11 namespace
13 std::string rom_path()
15 return lsnes_vset["rompath"].str();
18 bool can_load_singlefile(core_type* t)
20 unsigned icnt = t->get_image_count();
21 unsigned pmand = 0, tmand = 0;
22 if(!icnt) return false;
23 if(icnt > 0) pmand |= t->get_image_info(0).mandatory;
24 if(t->get_biosname() != "" && icnt > 1) pmand |= t->get_image_info(1).mandatory;
25 for(unsigned i = 0; i < t->get_image_count(); i++)
26 tmand |= t->get_image_info(i).mandatory;
27 return pmand == tmand;
30 std::string implode_set(const std::set<std::string>& s)
32 std::string a;
33 for(auto i : s) {
34 if(a != "") a += ";";
35 a += ("*." + i);
37 return a;
40 void do_load_rom_image_single(wxwin_mainwindow* parent)
42 std::map<std::string, core_type*> cores;
43 std::map<unsigned, core_type*> coreid;
44 std::string filter;
45 unsigned corecount = 0;
46 std::set<std::string> all_filetypes;
47 all_filetypes.insert("lsgp");
48 for(auto i : core_type::get_core_types()) {
49 if(!can_load_singlefile(i))
50 continue;
51 unsigned base = (i->get_biosname() != "" && i->get_image_count() > 1) ? 1 : 0;
52 for(auto j : i->get_image_info(base).extensions)
53 all_filetypes.insert(j);
54 cores[i->get_hname() + " [" + i->get_core_identifier() + "]"] = i;
56 filter += "Autodetect|" + implode_set(all_filetypes);
57 for(auto i : cores) {
58 if(!can_load_singlefile(i.second))
59 continue;
60 if(i.second->is_hidden())
61 continue;
62 if(i.second->isnull())
63 continue;
64 std::set<std::string> exts;
65 unsigned base = (i.second->get_biosname() != "" && i.second->get_image_count() > 1) ? 1 : 0;
66 for(auto j : i.second->get_image_info(base).extensions)
67 exts.insert(j);
68 filter += "|" + i.first + "|" + implode_set(exts);
69 coreid[++corecount] = i.second;
71 filter += "|All files|*";
72 std::string directory = lsnes_vset["rompath"].str();
73 wxFileDialog* d = new wxFileDialog(parent, towxstring("Choose ROM to load"), towxstring(directory),
74 wxT(""), towxstring(filter), wxFD_OPEN);
75 if(d->ShowModal() == wxID_CANCEL) {
76 delete d;
77 return;
79 romload_request req;
80 recentfile_multirom mr;
81 std::string filename = tostdstring(d->GetPath());
82 int findex = d->GetFilterIndex();
83 if(!coreid.count(findex)) {
84 //Autodetect.
85 mr.packfile = req.packfile = filename;
86 } else {
87 mr.core = req.core = coreid[findex]->get_core_identifier();
88 mr.system = req.system = coreid[findex]->get_iname();
89 mr.singlefile = req.singlefile = filename;
91 parent->recent_roms->add(mr);
92 runemufn([req]() {
93 lsnes_cmd.invoke("unpause-emulator");
94 load_new_rom(req);
95 });
98 class multirom_dialog : public wxDialog
100 public:
101 void on_wclose(wxCloseEvent& e)
103 EndModal(wxID_CANCEL);
105 multirom_dialog(wxWindow* parent, std::string rtype, core_type& _t)
106 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Load " + rtype + " ROM")), t(_t)
108 Centre();
109 wxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
110 SetSizer(vsizer);
111 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(multirom_dialog::on_wclose), NULL, this);
113 wxSizer* hsizer2 = new wxBoxSizer(wxHORIZONTAL);
114 hsizer2->Add(new wxStaticText(this, wxID_ANY, wxT("Region: ")), 0, wxGROW);
115 std::vector<wxString> regions_list;
116 core_region& prefr = t.get_preferred_region();
117 unsigned regindex = 0;
118 for(auto i : t.get_regions()) {
119 if(i == &prefr) regindex = regions_list.size();
120 regions_list.push_back(towxstring(i->get_hname()));
121 regions_known.push_back(i);
123 regions = new wxComboBox(this, wxID_ANY, regions_list[regindex], wxDefaultPosition,
124 wxDefaultSize, regions_list.size(), &regions_list[0], wxCB_READONLY);
125 hsizer2->Add(regions, 0, wxGROW);
126 vsizer->Add(hsizer2, 0, wxGROW);
128 wxSizer* rarray = new wxFlexGridSizer(2 * t.get_image_count(), 3, 0, 0);
129 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
130 filenames[i] = NULL;
131 fileselect[i] = NULL;
133 for(unsigned i = 0; i < t.get_image_count(); i++) {
134 core_romimage_info iinfo = t.get_image_info(i);
135 rarray->Add(new wxStaticText(this, wxID_ANY, towxstring(iinfo.hname)), 0, wxGROW);
136 rarray->Add(filenames[i] = new wxTextCtrl(this, wxID_HIGHEST + 100 + i, wxT(""),
137 wxDefaultPosition, wxSize(400, -1)), 1, wxGROW);
138 rarray->Add(fileselect[i] = new wxButton(this, wxID_HIGHEST + 200 + i,
139 towxstring("...")), 0, wxGROW);
140 rarray->Add(new wxStaticText(this, wxID_ANY, towxstring("")), 0, wxGROW);
141 rarray->Add(hashes[i] = new wxStaticText(this, wxID_ANY, wxT("Not found")), 1,
142 wxGROW);
143 rarray->Add(new wxStaticText(this, wxID_ANY, towxstring("")), 0, wxGROW);
144 hash_ready[i] = true;
145 filenames[i]->Connect(wxEVT_COMMAND_TEXT_UPDATED,
146 wxCommandEventHandler(multirom_dialog::do_command_event), NULL, this);
147 fileselect[i]->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
148 wxCommandEventHandler(multirom_dialog::do_command_event), NULL, this);
150 vsizer->Add(rarray, 1, wxGROW);
152 wxSizer* buttonbar = new wxBoxSizer(wxHORIZONTAL);
153 buttonbar->Add(okb = new wxButton(this, wxID_HIGHEST + 1, wxT("Load")), 0, wxGROW);
154 buttonbar->AddStretchSpacer();
155 buttonbar->Add(cancelb = new wxButton(this, wxID_HIGHEST + 2, wxT("Cancel")), 0, wxGROW);
156 vsizer->Add(buttonbar, 0, wxGROW);
157 okb->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
158 wxCommandEventHandler(multirom_dialog::do_command_event), NULL, this);
159 okb->Disable();
160 cancelb->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
161 wxCommandEventHandler(multirom_dialog::do_command_event), NULL, this);
162 timer = new update_timer(this);
163 timer->Start(100);
164 vsizer->SetSizeHints(this);
165 Fit();
167 ~multirom_dialog()
169 if(timer) {
170 timer->Stop();
171 delete timer;
174 void timer_update_hashes()
176 for(auto i = 0; i < ROM_SLOT_COUNT && filenames[i]; i++) {
177 if(hash_ready[i])
178 continue;
179 if(!hashfutures[i].ready())
180 continue;
181 try {
182 hashes[i]->SetLabel(towxstring("Hash: " + hashfutures[i].read()));
183 } catch(std::runtime_error& e) {
184 hashes[i]->SetLabel(towxstring("Hash: Error"));
186 hash_ready[i] = true;
189 void do_command_event(wxCommandEvent& e)
191 int id = e.GetId();
192 if(id == wxID_HIGHEST + 1) {
193 if(!check_requirements()) {
194 show_message_ok(this, "Needed ROM missing", "At least one required ROM "
195 "is missing!", wxICON_EXCLAMATION);
196 return;
198 EndModal(wxID_OK);
199 } else if(id == wxID_HIGHEST + 2) {
200 EndModal(wxID_CANCEL);
201 } else if(id >= wxID_HIGHEST + 100 && id <= wxID_HIGHEST + 199) {
202 filename_updated(id - (wxID_HIGHEST + 100));
203 } else if(id >= wxID_HIGHEST + 200 && id <= wxID_HIGHEST + 299) {
204 do_fileselect(id - (wxID_HIGHEST + 200));
207 std::string getfilename(unsigned i)
209 if(i >= ROM_SLOT_COUNT || !filenames[i])
210 return "";
211 return tostdstring(filenames[i]->GetValue());
213 std::string getregion()
215 int i = regions->GetSelection();
216 if(i == wxNOT_FOUND)
217 return t.get_preferred_region().get_iname();
218 return regions_known[i]->get_iname();
220 private:
221 bool check_requirements()
223 unsigned pm = 0, tm = 0;
224 for(unsigned i = 0; i < t.get_image_count(); i++) {
225 core_romimage_info iinfo = t.get_image_info(i);
226 tm |= iinfo.mandatory;
227 if(filenames[i]->GetValue().Length() != 0)
228 pm |= iinfo.mandatory;
230 return pm == tm;
232 void do_fileselect(unsigned i)
234 if(i >= ROM_SLOT_COUNT || !fileselect[i])
235 return;
236 std::string filter;
237 std::set<std::string> exts;
238 for(auto j : t.get_image_info(i).extensions)
239 exts.insert(j);
240 filter = "Known file types|" + implode_set(exts) + "|All files|*";
241 std::string directory;
242 if(t.get_biosname() != "" && i == 0)
243 directory = "firmwarepath";
244 else
245 directory = "rompath";
246 directory = lsnes_vset[directory].str();
247 core_romimage_info iinfo = t.get_image_info(i);
248 wxFileDialog* d = new wxFileDialog(this, towxstring("Load " + iinfo.hname),
249 towxstring(directory), wxT(""), towxstring(filter), wxFD_OPEN);
250 if(d->ShowModal() == wxID_CANCEL) {
251 delete d;
252 return;
254 filenames[i]->SetValue(d->GetPath());
255 delete d;
257 void filename_updated(unsigned i)
259 if(i >= t.get_image_count() || !filenames[i])
260 return;
261 uint64_t header = t.get_image_info(i).headersize;
263 std::string filename = tostdstring(filenames[i]->GetValue());
264 if(!file_exists_zip(filename)) {
265 hashfutures[i] = sha256_future();
266 hash_ready[i] = true;
267 hashes[i]->SetLabel(towxstring("Not found"));
268 return;
270 //TODO: Handle files inside ZIP files.
271 hashfutures[i] = lsnes_image_hasher(filename, std_headersize_fn(header));
272 if(hash_ready[i] = hashfutures[i].ready())
273 try {
274 hashes[i]->SetLabel(towxstring("Hash: " + hashfutures[i].read()));
275 } catch(std::runtime_error& e) {
276 hashes[i]->SetLabel(towxstring("Hash: Error"));
278 else
279 hashes[i]->SetLabel(towxstring("Hash: Calculating..."));
280 okb->Enable(check_requirements());
282 struct update_timer : public wxTimer
284 public:
285 update_timer(multirom_dialog* p)
287 w = p;
289 void Notify()
291 w->timer_update_hashes();
293 private:
294 multirom_dialog* w;
296 wxComboBox* regions;
297 wxTextCtrl* filenames[ROM_SLOT_COUNT];
298 wxButton* fileselect[ROM_SLOT_COUNT];
299 wxStaticText* hashes[ROM_SLOT_COUNT];
300 bool hash_ready[ROM_SLOT_COUNT];
301 sha256_future hashfutures[ROM_SLOT_COUNT];
302 wxButton* okb;
303 wxButton* cancelb;
304 update_timer* timer;
305 core_type& t;
306 std::vector<core_region*> regions_known;
309 void do_load_rom_image_multiple(wxwin_mainwindow* parent, core_type& t)
311 multirom_dialog* d = new multirom_dialog(parent, t.get_hname(), t);
312 if(d->ShowModal() == wxID_CANCEL) {
313 delete d;
314 return;
316 std::string files[ROM_SLOT_COUNT];
317 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++)
318 files[i] = d->getfilename(i);
319 std::string region = d->getregion();
320 delete d;
321 romload_request req;
322 recentfile_multirom mr;
323 mr.core = req.core = t.get_core_identifier();
324 mr.system = req.system = t.get_iname();
325 mr.region = req.region = region;
326 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
327 if(files[i] != "") {
328 mr.files.resize(i + 1);
329 mr.files[i] = files[i];
331 req.files[i] = files[i];
333 parent->recent_roms->add(mr);
334 runemufn([req]() {
335 lsnes_cmd.invoke("unpause-emulator");
336 load_new_rom(req);
338 return;
342 void wxwin_mainwindow::request_rom(rom_request& req)
344 std::vector<std::string> choices;
345 for(auto i : req.cores)
346 choices.push_back(i->get_core_identifier());
347 std::string coretext;
348 try {
349 if(choices.size() > 1 && !req.core_guessed)
350 coretext = pick_among(this, "Choose core", "Choose core to load the ROM", choices,
351 req.selected);
352 else
353 coretext = choices[req.selected];
354 } catch(canceled_exception& e) {
355 return;
357 for(size_t i = 0; i < req.cores.size(); i++)
358 if(coretext == req.cores[i]->get_core_identifier())
359 req.selected = i;
360 core_type& type = *req.cores[req.selected];
362 bool has_bios = (type.get_biosname() != "");
363 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
364 if(!req.has_slot[i])
365 continue;
366 if(req.guessed[i])
367 continue; //Leave these alone.
368 if(i >= type.get_image_count()) {
369 messages << "wxwin_mainwindow::request_rom: Present image index (" << i
370 << ") out of range!" << std::endl;
371 continue; //Shouldn't happen.
373 core_romimage_info iinfo = type.get_image_info(i);
374 std::string directory;
375 if(i == 0 && has_bios)
376 directory = "firmwarepath";
377 else
378 directory = "rompath";
379 directory = lsnes_vset[directory].str();
380 std::string _title = "Select " + iinfo.hname;
381 std::string filespec = "Known ROMs|";
382 std::string exts = "";
383 std::string defaultname = "";
384 for(auto j : iinfo.extensions) {
385 exts = exts + ";*." + j;
386 if(file_exists_zip(directory + "/" + req.filename[i] + "." + j))
387 defaultname = req.filename[i] + "." + j;
389 if(exts != "") exts = exts.substr(1);
390 filespec = "Known ROMs (" + exts + ")|" + exts + "|All files|*";
391 wxFileDialog* d;
392 std::string hash;
393 uint64_t header = type.get_image_info(i).headersize;
394 again:
395 d = new wxFileDialog(this, towxstring(_title), towxstring(directory), wxT(""),
396 towxstring(filespec), wxFD_OPEN);
397 if(defaultname != "") d->SetFilename(towxstring(defaultname));
398 if(d->ShowModal() == wxID_CANCEL) {
399 delete d;
400 return;
402 req.filename[i] = tostdstring(d->GetPath());
403 delete d;
404 //Check the hash.
405 if(!file_exists_zip(req.filename[i])) {
406 show_message_ok(this, "File not found", "Can't find '" + req.filename[i] + "'",
407 wxICON_EXCLAMATION);
408 goto again;
410 try {
411 auto future = lsnes_image_hasher(req.filename[i], std_headersize_fn(header));
412 //Dirty method to run the event loop until hashing finishes.
413 while(!future.ready()) {
414 wxSafeYield();
415 usleep(50000);
417 std::string hash = future.read();
418 if(hash != req.hash[i]) {
419 //Hash mismatches.
420 wxMessageDialog* d3 = new wxMessageDialog(this, towxstring("The ROM checksum does "
421 "not match movie\n\nProceed anyway?"), towxstring("Checksum error"),
422 wxYES_NO | wxNO_DEFAULT | wxICON_EXCLAMATION);
423 int r = d3->ShowModal();
424 d3->Destroy();
425 if(r == wxID_NO) goto again;
427 } catch(...) {
428 wxMessageDialog* d3 = new wxMessageDialog(this, towxstring("Can't read checksum for "
429 "ROM\n\nProceed anyway?"), towxstring("Checksum error"), wxYES_NO |
430 wxYES_DEFAULT | wxICON_EXCLAMATION);
431 int r = d3->ShowModal();
432 d3->Destroy();
433 if(r == wxID_NO) goto again;
436 req.canceled = false;
439 void wxwin_mainwindow::do_load_rom_image(core_type* t)
441 if(!t) {
442 return do_load_rom_image_single(this);
443 } else {
444 return do_load_rom_image_multiple(this, *t);