Actually call on_reset callback
[lsnes.git] / src / util / lsnes-dumpavi.cpp
blob1f6e8175ca2a6c635016ebd274254d33f937f89d
1 #include "lsnes.hpp"
3 #include "core/advdumper.hpp"
4 #include "core/controller.hpp"
5 #include "core/command.hpp"
6 #include "core/dispatch.hpp"
7 #include "core/mainloop.hpp"
8 #include "core/framerate.hpp"
9 #include "core/keymapper.hpp"
10 #include "interface/romtype.hpp"
11 #include "core/loadlib.hpp"
12 #include "lua/lua.hpp"
13 #include "core/filedownload.hpp"
14 #include "core/mainloop.hpp"
15 #include "core/messages.hpp"
16 #include "core/misc.hpp"
17 #include "core/instance.hpp"
18 #include "core/moviedata.hpp"
19 #include "core/random.hpp"
20 #include "core/rom.hpp"
21 #include "core/settings.hpp"
22 #include "core/window.hpp"
23 #include "library/directory.hpp"
24 #include "library/crandom.hpp"
25 #include "library/string.hpp"
27 #include <sys/time.h>
28 #include <sstream>
30 namespace
32 bool hashing_in_progress = false;
33 uint64_t hashing_left = 0;
34 int64_t last_update = 0;
36 std::string do_download_movie(const std::string& origname)
38 if(!regex_match("[A-Za-z][A-Za-z0-9+.-]*:.+", origname))
39 return origname; //File.
40 if(directory::is_regular(origname))
41 return origname; //Even exists.
42 //Okay, we need to download this.
43 auto download_in_progress = new file_download();
44 download_in_progress->url = lsnes_uri_rewrite(origname);
45 if(download_in_progress->url != origname)
46 messages << "Internally redirecting to " << download_in_progress->url << std::endl;
47 download_in_progress->target_slot = "dumpavi_download_tmp";
48 download_in_progress->do_async(*lsnes_instance.rom);
49 messages << "Downloading " << download_in_progress->url << ":" << std::endl;
50 while(!download_in_progress->finished) {
51 messages << download_in_progress->statusmsg() << std::endl;
52 usleep(1000000);
54 if(download_in_progress->errormsg != "") {
55 std::string err = download_in_progress->errormsg;
56 delete download_in_progress;
57 throw std::runtime_error(err);
59 delete download_in_progress;
60 messages << "Download finished." << std::endl;
61 return "$MEMORY:dumpavi_download_tmp";
64 void hash_callback(uint64_t left, uint64_t total)
66 if(left == 0xFFFFFFFFFFFFFFFFULL) {
67 hashing_in_progress = false;
68 std::cout << "Done." << std::endl;
69 last_update = framerate_regulator::get_utime() - 2000000;
70 return;
72 if(!hashing_in_progress) {
73 std::cout << "Hashing disc images..." << std::flush;
75 hashing_in_progress = true;
76 hashing_left = left;
77 int64_t this_update = framerate_regulator::get_utime();
78 if(this_update < last_update - 1000000 || this_update > last_update + 1000000) {
79 std::cout << ((hashing_left + 524288) >> 20) << "..." << std::flush;
80 last_update = this_update;
84 class myavsnoop : public dumper_base
86 public:
87 myavsnoop(dumper_base& _dumper, uint64_t frames_to_dump)
88 : dumper(_dumper)
90 frames_dumped = 0;
91 total = frames_to_dump;
92 lsnes_instance.mdumper->add_dumper(*this);
95 ~myavsnoop() throw()
97 lsnes_instance.mdumper->drop_dumper(*this);
100 void on_frame(struct framebuffer::raw& _frame, uint32_t fps_n, uint32_t fps_d)
102 frames_dumped++;
103 if(frames_dumped % 100 == 0) {
104 std::cout << "Dumping frame " << frames_dumped << "/" << total << " ("
105 << (100 * frames_dumped / total) << "%)" << std::endl;
107 if(frames_dumped >= total) {
108 //Rough way to end it.
109 CORE().command->invoke("quit-emulator");
112 void on_sample(short l, short r)
114 //We aren't interested in samples.
116 void on_rate_change(uint32_t n, uint32_t d)
118 //We aren't interested in samples.
120 void on_gameinfo_change(const master_dumper::gameinfo& gi)
122 std::cout << "Game:" << gi.gamename << std::endl;
123 std::cout << "Length:" << gi.get_readable_time(3) << std::endl;
124 std::cout << "Rerecords:" << gi.get_rerecords() << std::endl;
125 for(unsigned i = 0; i < gi.get_author_count(); i++)
126 std::cout << "Author: " << gi.get_author_long(i) << std::endl;
128 void on_end()
130 std::cout << "Finished!" << std::endl;
131 delete this;
133 private:
134 uint64_t frames_dumped;
135 uint64_t total;
136 dumper_base& dumper;
139 void dumper_startup(dumper_factory_base& dumper, const std::string& mode, const std::string& prefix,
140 uint64_t length)
142 dumper_base* _dumper;
143 std::cout << "Invoking dumper" << std::endl;
144 try {
145 _dumper = lsnes_instance.mdumper->start(dumper, mode, prefix);
146 } catch(std::exception& e) {
147 std::cerr << "Can't start dumper: " << e.what() << std::endl;
148 exit(1);
150 if(lsnes_instance.mdumper->get_dumper_count()) {
151 std::cout << "Dumper attach confirmed" << std::endl;
152 } else {
153 std::cout << "Can't start dumper!" << std::endl;
154 exit(1);
156 auto d = new myavsnoop(*_dumper, length);
157 d->on_gameinfo_change(lsnes_instance.mdumper->get_gameinfo());
160 void startup_lua_scripts(const std::vector<std::string>& cmdline)
162 for(auto i = cmdline.begin(); i != cmdline.end(); i++) {
163 std::string a = *i;
164 if(a.length() > 6 && a.substr(0, 6) == "--lua=") {
165 lsnes_instance.lua2->add_startup_script(a.substr(6));
170 struct dumper_factory_base& locate_dumper(const std::string& name)
172 dumper_factory_base* _dumper = NULL;
173 std::set<dumper_factory_base*> dumpers = dumper_factory_base::get_dumper_set();
174 for(auto i : dumpers)
175 if(i->id() == name)
176 _dumper = i;
177 if(!_dumper) {
178 std::cerr << "No such dumper '" << name << "' found (try --dumper=list)" << std::endl;
179 exit(1);
181 return *_dumper;
184 std::string format_details(unsigned detail)
186 std::string r;
187 if((detail & dumper_factory_base::target_type_mask) == dumper_factory_base::target_type_file)
188 r = r + "TARGET_FILE";
189 else if((detail & dumper_factory_base::target_type_mask) == dumper_factory_base::target_type_prefix)
190 r = r + "TARGET_PREFIX";
191 else if((detail & dumper_factory_base::target_type_mask) == dumper_factory_base::target_type_special)
192 r = r + "TARGET_SPECIAL";
193 else
194 r = r + "TARGET_UNKNOWN";
195 return r;
198 dumper_factory_base& get_dumper(const std::vector<std::string>& cmdline, std::string& mode,
199 std::string& prefix, uint64_t& length, bool& overdump_mode, uint64_t& overdump_length)
201 bool dumper_given = false;
202 std::string dumper;
203 bool mode_given = false;
204 prefix = "avidump";
205 length = 0;
206 overdump_mode = false;
207 overdump_length = 0;
208 for(auto i = cmdline.begin(); i != cmdline.end(); i++) {
209 std::string a = *i;
210 if(a.length() >= 9 && a.substr(0, 9) == "--dumper=") {
211 dumper_given = true;
212 dumper = a.substr(9);
213 } else if(a.length() >= 7 && a.substr(0, 7) == "--mode=") {
214 mode_given = true;
215 mode = a.substr(7);
216 } else if(a.length() >= 9 && a.substr(0, 9) == "--prefix=")
217 prefix = a.substr(9);
218 else if(a.length() >= 9 && a.substr(0, 9) == "--length=")
219 try {
220 length = raw_lexical_cast<uint64_t>(a.substr(9));
221 if(!length)
222 throw std::runtime_error("Length out of range (1-)");
223 if(overdump_mode)
224 throw std::runtime_error("--length and --overdump-length are "
225 "mutually exclusive.");
226 } catch(std::exception& e) {
227 std::cerr << "Bad --length: " << e.what() << std::endl;
228 exit(1);
230 else if(a.length() >= 18 && a.substr(0, 18) == "--overdump-length=")
231 try {
232 overdump_length = raw_lexical_cast<uint64_t>(a.substr(18));
233 overdump_mode = true;
234 if(length)
235 throw std::runtime_error("--length and --overdump-length are "
236 "mutually exclusive.");
237 } catch(std::exception& e) {
238 std::cerr << "Bad --overdump-length: " << e.what() << std::endl;
239 exit(1);
241 else if(a.length() >= 9 && a.substr(0, 9) == "--option=") {
242 std::string nameval = a.substr(9);
243 size_t s = nameval.find_first_of("=");
244 if(s >= nameval.length()) {
245 std::cerr << "Invalid option syntax (expected --option=foo=bar)" << std::endl;
246 exit(1);
248 std::string name = nameval.substr(0, s);
249 std::string val = nameval.substr(s + 1);
250 try {
251 lsnes_instance.setcache->set(name, val);
252 } catch(std::exception& e) {
253 std::cerr << "Can't set '" << name << "' to '" << val << "': " << e.what()
254 << std::endl;
255 exit(1);
257 } else if(a.length() >= 12 && a.substr(0, 15) == "--load-library=")
258 try {
259 with_loaded_library(*new loadlib::module(loadlib::library(a.substr(15))));
260 handle_post_loadlibrary();
261 } catch(std::runtime_error& e) {
262 std::cerr << "Can't load '" << a.substr(15) << "': " << e.what() << std::endl;
263 exit(1);
266 if(dumper == "list") {
267 //Help on dumpers.
268 std::set<dumper_factory_base*> dumpers = dumper_factory_base::get_dumper_set();
269 std::cout << "Dumpers available:" << std::endl;
270 for(auto i : dumpers)
271 std::cout << i->id() << "\t" << i->name() << std::endl;
272 exit(0);
274 if(!dumper_given) {
275 std::cerr << "Dumper required (--dumper=foo)" << std::endl;
276 exit(1);
278 if(mode == "list") {
279 //Help on modes.
280 dumper_factory_base& _dumper = locate_dumper(dumper);
281 std::set<std::string> modes = _dumper.list_submodes();
282 if(modes.empty()) {
283 unsigned d = _dumper.mode_details("");
284 std::cout << "No modes available for " << dumper << " (" << format_details(d) << ")"
285 << std::endl;
286 exit(0);
288 std::cout << "Modes available for " << dumper << ":" << std::endl;
289 for(auto i : modes) {
290 unsigned d = _dumper.mode_details(i);
291 std::cout << i << "\t" << _dumper.modename(i) << "\t(" << format_details(d) << ")"
292 << std::endl;
294 exit(0);
296 dumper_factory_base& _dumper = locate_dumper(dumper);
297 if(!mode_given && !_dumper.list_submodes().empty()) {
298 std::cerr << "Mode required for this dumper" << std::endl;
299 exit(1);
301 if(mode_given && _dumper.list_submodes().empty()) {
302 std::cerr << "This dumper does not have mode select" << std::endl;
303 exit(1);
305 if(mode_given && !_dumper.list_submodes().count(mode)) {
306 std::cerr << "'" << mode << "' is not a valid mode for '" << dumper << "'" << std::endl;
307 exit(1);
309 if(!length && !overdump_mode) {
310 std::cerr << "--length=<frames> or --overdump-length=<frames> has to be specified"
311 << std::endl;
312 exit(1);
314 return locate_dumper(dumper);
318 int main(int argc, char** argv)
320 try {
321 crandom::init();
322 } catch(std::exception& e) {
323 std::cerr << "Error initializing system RNG" << std::endl;
324 return 1;
327 reached_main();
328 std::vector<std::string> cmdline;
329 for(int i = 1; i < argc; i++)
330 cmdline.push_back(argv[i]);
331 uint64_t length, overdump_length;
332 bool overdump_mode;
333 std::string mode, prefix;
335 dumper_factory_base& dumper = get_dumper(cmdline, mode, prefix, length, overdump_mode, overdump_length);
337 set_random_seed();
338 platform::init();
339 init_lua(lsnes_instance);
340 lsnes_instance.mdumper->set_output(&messages.getstream());
341 set_hasher_callback(hash_callback);
343 messages << "lsnes version: lsnes rr" << lsnes_version << std::endl;
344 messages << "Command line is: ";
345 for(auto k = cmdline.begin(); k != cmdline.end(); k++)
346 messages << "\"" << *k << "\" ";
347 messages << std::endl;
349 std::string cfgpath = get_config_path();
350 autoload_libraries();
352 lsnes_uri_rewrite.load(cfgpath + "/lsnesurirewrite.cfg");
354 for(auto i : cmdline) {
355 regex_results r;
356 if(r = regex("--firmware-path=(.*)", i)) {
357 try {
358 lsnes_instance.setcache->set("firmwarepath", r[1]);
359 std::cerr << "Set firmware path to '" << r[1] << "'" << std::endl;
360 } catch(std::exception& e) {
361 std::cerr << "Can't set firmware path to '" << r[1] << "': " << e.what() << std::endl;
364 if(r = regex("--setting-(.*)=(.*)", i)) {
365 try {
366 lsnes_instance.setcache->set(r[1], r[2]);
367 std::cerr << "Set " << r[1] << " to '" << r[2] << "'" << std::endl;
368 } catch(std::exception& e) {
369 std::cerr << "Can't set " << r[1] << " to '" << r[2] << "': " << e.what()
370 << std::endl;
375 std::string movfn;
376 for(auto i : cmdline) {
377 if(i.length() > 0 && i[0] != '-') {
378 movfn = i;
381 if(movfn == "") {
382 messages << "Movie filename required" << std::endl;
383 return 0;
386 try {
387 movfn = do_download_movie(movfn);
388 } catch(std::exception& e) {
389 messages << "FATAL: Can't download movie: " << e.what() << std::endl;
390 quit_lua(lsnes_instance);
391 fatal_error();
392 exit(1);
395 init_main_callbacks();
396 messages << "--- Loading ROM ---" << std::endl;
397 struct loaded_rom r;
398 try {
399 std::map<std::string, std::string> tmp;
400 r = construct_rom(movfn, cmdline);
401 r.load(tmp, 1000000000, 0);
402 messages << "Using core: " << r.get_core_identifier() << std::endl;
403 } catch(std::bad_alloc& e) {
404 OOM_panic();
405 } catch(std::exception& e) {
406 messages << "FATAL: Can't load ROM: " << e.what() << std::endl;
407 quit_lua(lsnes_instance);
408 fatal_error();
409 exit(1);
411 messages << "Detected region: " << r.get_sysregion().get_name() << std::endl;
412 lsnes_instance.framerate->set_nominal_framerate(r.region_approx_framerate());
414 messages << "--- End of Startup --- " << std::endl;
416 moviefile* movie;
417 try {
418 movie = new moviefile(movfn, r.get_internal_rom_type());
419 //Load ROM before starting the dumper.
420 *lsnes_instance.rom = r;
421 messages << "Using core: " << lsnes_instance.rom->get_core_identifier() << std::endl;
422 lsnes_instance.rom->set_internal_region(movie->gametype->get_region());
423 lsnes_instance.rom->load(movie->settings, movie->movie_rtc_second, movie->movie_rtc_subsecond);
424 startup_lua_scripts(cmdline);
425 if(overdump_mode)
426 length = overdump_length + movie->get_frame_count();
427 dumper_startup(dumper, mode, prefix, length);
428 main_loop(r, *movie, true);
429 } catch(std::bad_alloc& e) {
430 OOM_panic();
431 } catch(std::exception& e) {
432 messages << "FATAL: " << e.what() << std::endl;
433 quit_lua(lsnes_instance);
434 fatal_error();
435 return 1;
437 quit_lua(lsnes_instance);
438 lsnes_instance.mlogic->release_memory();
439 lsnes_instance.buttons->cleanup();
440 return 0;