1 #include "core/emucore.hpp"
2 #include "core/misc.hpp"
3 #include "core/moviedata.hpp"
4 #include "core/moviefile.hpp"
5 #include "core/rrdata.hpp"
6 #include "library/zip.hpp"
7 #include "library/string.hpp"
10 #include <boost/iostreams/copy.hpp>
11 #include <boost/iostreams/device/back_inserter.hpp>
13 #define DEFAULT_RTC_SECOND 1000000000ULL
14 #define DEFAULT_RTC_SUBSECOND 0ULL
17 void read_linefile(zip_reader
& r
, const std::string
& member
, std::string
& out
, bool conditional
= false)
18 throw(std::bad_alloc
, std::runtime_error
)
20 if(conditional
&& !r
.has_member(member
))
22 std::istream
& m
= r
[member
];
34 void read_numeric_file(zip_reader
& r
, const std::string
& member
, T
& out
, bool conditional
= false)
35 throw(std::bad_alloc
, std::runtime_error
)
38 read_linefile(r
, member
, _out
, conditional
);
39 if(conditional
&& _out
== "")
41 out
= parse_value
<int64_t>(_out
);
44 void write_linefile(zip_writer
& w
, const std::string
& member
, const std::string
& value
, bool conditional
= false)
45 throw(std::bad_alloc
, std::runtime_error
)
47 if(conditional
&& value
== "")
49 std::ostream
& m
= w
.create_file(member
);
51 m
<< value
<< std::endl
;
60 void write_numeric_file(zip_writer
& w
, const std::string
& member
, T value
) throw(std::bad_alloc
,
65 write_linefile(w
, member
, x
.str());
68 void write_raw_file(zip_writer
& w
, const std::string
& member
, std::vector
<char>& content
) throw(std::bad_alloc
,
71 std::ostream
& m
= w
.create_file(member
);
73 m
.write(&content
[0], content
.size());
75 throw std::runtime_error("Can't write ZIP file member");
83 std::vector
<char> read_raw_file(zip_reader
& r
, const std::string
& member
) throw(std::bad_alloc
, std::runtime_error
)
85 std::vector
<char> out
;
86 std::istream
& m
= r
[member
];
88 boost::iostreams::back_insert_device
<std::vector
<char>> rd(out
);
89 boost::iostreams::copy(m
, rd
);
98 uint64_t decode_uint64(unsigned char* buf
)
100 return ((uint64_t)buf
[0] << 56) |
101 ((uint64_t)buf
[1] << 48) |
102 ((uint64_t)buf
[2] << 40) |
103 ((uint64_t)buf
[3] << 32) |
104 ((uint64_t)buf
[4] << 24) |
105 ((uint64_t)buf
[5] << 16) |
106 ((uint64_t)buf
[6] << 8) |
110 uint32_t decode_uint32(unsigned char* buf
)
112 return ((uint32_t)buf
[0] << 24) |
113 ((uint32_t)buf
[1] << 16) |
114 ((uint32_t)buf
[2] << 8) |
119 void read_moviestate_file(zip_reader
& r
, const std::string
& file
, uint64_t& save_frame
, uint64_t& lagged_frames
,
120 std::vector
<uint32_t>& pollcounters
) throw(std::bad_alloc
, std::runtime_error
)
122 unsigned char buf
[512];
123 auto s
= read_raw_file(r
, file
);
124 if(s
.size() != sizeof(buf
))
125 throw std::runtime_error("Invalid moviestate file");
126 memcpy(buf
, &s
[0], sizeof(buf
));
127 //Interesting offsets: 32-39: Current frame, 40-439: Poll counters, 440-447 lagged frames. All bigendian.
128 save_frame
= decode_uint64(buf
+ 32);
129 lagged_frames
= decode_uint64(buf
+ 440);
130 pollcounters
.resize(100);
131 for(unsigned i
= 0; i
< 100; i
++)
132 pollcounters
[i
] = decode_uint32(buf
+ 40 + 4 * i
);
135 void read_authors_file(zip_reader
& r
, std::vector
<std::pair
<std::string
, std::string
>>& authors
) throw(std::bad_alloc
,
138 std::istream
& m
= r
["authors"];
141 while(std::getline(m
, x
)) {
143 auto g
= split_author(x
);
144 authors
.push_back(g
);
153 std::string
read_rrdata(zip_reader
& r
, std::vector
<char>& out
) throw(std::bad_alloc
, std::runtime_error
)
155 out
= read_raw_file(r
, "rrdata");
156 uint64_t count
= rrdata::count(out
);
157 std::ostringstream x
;
162 void write_rrdata(zip_writer
& w
) throw(std::bad_alloc
, std::runtime_error
)
165 std::vector
<char> out
;
166 count
= rrdata::write(out
);
167 write_raw_file(w
, "rrdata", out
);
168 std::ostream
& m2
= w
.create_file("rerecords");
170 m2
<< count
<< std::endl
;
172 throw std::runtime_error("Can't write ZIP file member");
180 void write_authors_file(zip_writer
& w
, std::vector
<std::pair
<std::string
, std::string
>>& authors
)
181 throw(std::bad_alloc
, std::runtime_error
)
183 std::ostream
& m
= w
.create_file("authors");
185 for(auto i
: authors
)
187 m
<< i
.first
<< std::endl
;
189 m
<< i
.first
<< "|" << i
.second
<< std::endl
;
191 throw std::runtime_error("Can't write ZIP file member");
199 void write_input(zip_writer
& w
, controller_frame_vector
& input
)
200 throw(std::bad_alloc
, std::runtime_error
)
202 std::ostream
& m
= w
.create_file("input");
204 char buffer
[MAX_SERIALIZED_SIZE
];
205 for(size_t i
= 0; i
< input
.size(); i
++) {
206 input
[i
].serialize(buffer
);
207 m
<< buffer
<< std::endl
;
210 throw std::runtime_error("Can't write ZIP file member");
218 void read_subtitles(zip_reader
& r
, const std::string
& file
, std::map
<moviefile_subtiming
, std::string
>& x
)
221 if(!r
.has_member(file
))
223 std::istream
& m
= r
[file
];
227 std::getline(m
, out
);
229 auto r
= regex("([0-9]+)[ \t]+([0-9]+)[ \t]+(.*)", out
);
232 x
[moviefile_subtiming(parse_value
<uint64_t>(r
[1]), parse_value
<uint64_t>(r
[2]))] =
243 void write_subtitles(zip_writer
& w
, const std::string
& file
, std::map
<moviefile_subtiming
, std::string
>& x
)
245 std::ostream
& m
= w
.create_file(file
);
248 m
<< i
.first
.get_frame() << " " << i
.first
.get_length() << " " << s_escape(i
.second
)
251 throw std::runtime_error("Can't write ZIP file member");
259 void read_input(zip_reader
& r
, controller_frame_vector
& input
, unsigned version
) throw(std::bad_alloc
,
262 controller_frame tmp
= input
.blank_frame(false);
263 std::istream
& m
= r
["input"];
266 while(std::getline(m
, x
)) {
269 tmp
.deserialize(x
.c_str());
280 void read_pollcounters(zip_reader
& r
, const std::string
& file
, std::vector
<uint32_t>& pctr
)
282 std::istream
& m
= r
[file
];
285 while(std::getline(m
, x
)) {
288 int32_t y
= parse_value
<int32_t>(x
);
306 void write_pollcounters(zip_writer
& w
, const std::string
& file
, const std::vector
<uint32_t>& pctr
)
308 std::ostream
& m
= w
.create_file(file
);
311 int32_t x
= i
& 0x7FFFFFFFUL
;
312 if((i
& 0x80000000UL
) == 0)
317 throw std::runtime_error("Can't write ZIP file member");
325 porttype_info
& parse_controller_type(const std::string
& type
, unsigned port
) throw(std::bad_alloc
, std::runtime_error
)
328 porttype_info
& i
= porttype_info::lookup(type
);
329 if(!i
.legal
|| !(i
.legal(port
)))
333 (stringfmt() << "Illegal port " << (port
+ 1) << " device '" << type
<< "'").throwex();
337 moviefile::moviefile() throw(std::bad_alloc
)
339 force_corrupt
= false;
341 port1
= &porttype_info::default_type();
342 port2
= &porttype_info::default_type();
346 is_savestate
= false;
347 movie_rtc_second
= rtc_second
= DEFAULT_RTC_SECOND
;
348 movie_rtc_subsecond
= rtc_subsecond
= DEFAULT_RTC_SUBSECOND
;
349 start_paused
= false;
350 lazy_project_create
= true;
354 moviefile::moviefile(const std::string
& movie
) throw(std::bad_alloc
, std::runtime_error
)
357 start_paused
= false;
358 force_corrupt
= false;
359 is_savestate
= false;
360 lazy_project_create
= false;
363 read_linefile(r
, "systemid", tmp
);
364 if(tmp
.substr(0, 8) != "lsnes-rr")
365 throw std::runtime_error("Not lsnes movie");
366 read_linefile(r
, "controlsversion", tmp
);
368 throw std::runtime_error("Can't decode movie data");
369 read_linefile(r
, "gametype", tmp
);
371 gametype
= &core_sysregion::lookup(tmp
);
372 } catch(std::bad_alloc
& e
) {
374 } catch(std::exception
& e
) {
375 throw std::runtime_error("Illegal game type '" + tmp
+ "'");
377 tmp
= porttype_info::port_default(0).name
;
378 read_linefile(r
, "port1", tmp
, true);
379 port1
= &porttype_info::lookup(tmp
);
380 tmp
= porttype_info::port_default(1).name
;
381 read_linefile(r
, "port2", tmp
, true);
382 port2
= &porttype_info::lookup(tmp
);
383 input
.clear(*port1
, *port2
);
384 read_linefile(r
, "gamename", gamename
, true);
385 read_linefile(r
, "projectid", projectid
);
386 rerecords
= read_rrdata(r
, c_rrdata
);
387 read_linefile(r
, "coreversion", coreversion
);
388 read_linefile(r
, "rom.sha256", romimg_sha256
[0], true);
389 read_linefile(r
, "romxml.sha256", romxml_sha256
[0], true);
391 if(r
.has_member("slot`.sha256"))
393 for(size_t i
= 0; i
< 26; i
++) {
394 read_linefile(r
, (stringfmt() << "slot" << (char)(base
+ i
) << ".sha256").str(), romimg_sha256
[i
+ 1],
396 read_linefile(r
, (stringfmt() << "slot" << (char)(base
+ i
) << "xml.sha256").str(),
397 romxml_sha256
[i
+ 1], true);
399 read_linefile(r
, "prefix", prefix
, true);
400 read_subtitles(r
, "subtitles", subtitles
);
401 prefix
= sanitize_prefix(prefix
);
402 movie_rtc_second
= DEFAULT_RTC_SECOND
;
403 movie_rtc_subsecond
= DEFAULT_RTC_SUBSECOND
;
404 read_numeric_file(r
, "starttime.second", movie_rtc_second
, true);
405 read_numeric_file(r
, "starttime.subsecond", movie_rtc_subsecond
, true);
406 rtc_second
= movie_rtc_second
;
407 rtc_subsecond
= movie_rtc_subsecond
;
408 if(r
.has_member("savestate.anchor"))
409 anchor_savestate
= read_raw_file(r
, "savestate.anchor");
410 if(r
.has_member("savestate")) {
412 if(r
.has_member("moviestate"))
413 //Backwards compat stuff.
414 read_moviestate_file(r
, "moviestate", save_frame
, lagged_frames
, pollcounters
);
416 read_numeric_file(r
, "saveframe", save_frame
, true);
417 read_numeric_file(r
, "lagcounter", lagged_frames
, true);
418 read_pollcounters(r
, "pollcounters", pollcounters
);
420 if(r
.has_member("hostmemory"))
421 host_memory
= read_raw_file(r
, "hostmemory");
422 savestate
= read_raw_file(r
, "savestate");
424 if(name
.length() >= 5 && name
.substr(0, 5) == "sram.")
425 sram
[name
.substr(5)] = read_raw_file(r
, name
);
426 screenshot
= read_raw_file(r
, "screenshot");
427 //If these can't be read, just use some (wrong) values.
428 read_numeric_file(r
, "savetime.second", rtc_second
, true);
429 read_numeric_file(r
, "savetime.subsecond", rtc_subsecond
, true);
430 uint64_t _poll_flag
= 2; //Legacy behaviour is the default.
431 read_numeric_file(r
, "pollflag", _poll_flag
, true);
432 poll_flag
= _poll_flag
;
434 if(rtc_subsecond
< 0 || movie_rtc_subsecond
< 0)
435 throw std::runtime_error("Invalid RTC subsecond value");
436 std::string name
= r
.find_first();
438 if(name
.length() >= 10 && name
.substr(0, 10) == "moviesram.")
439 movie_sram
[name
.substr(10)] = read_raw_file(r
, name
);
440 read_authors_file(r
, authors
);
441 read_input(r
, input
, 0);
444 void moviefile::save(const std::string
& movie
, unsigned compression
) throw(std::bad_alloc
, std::runtime_error
)
446 zip_writer
w(movie
, compression
);
447 write_linefile(w
, "gametype", gametype
->get_name());
448 if(port1
->name
!= porttype_info::port_default(0).name
)
449 write_linefile(w
, "port1", port1
->name
);
450 if(port2
->name
!= porttype_info::port_default(1).name
)
451 write_linefile(w
, "port2", port2
->name
);
452 write_linefile(w
, "gamename", gamename
, true);
453 write_linefile(w
, "systemid", "lsnes-rr1");
454 write_linefile(w
, "controlsversion", "0");
455 coreversion
= get_core_identifier();
456 write_linefile(w
, "coreversion", coreversion
);
457 write_linefile(w
, "projectid", projectid
);
459 write_linefile(w
, "rom.sha256", romimg_sha256
[0], true);
460 write_linefile(w
, "romxml.sha256", romxml_sha256
[0], true);
461 for(size_t i
= 0; i
< 26; i
++) {
462 write_linefile(w
, (stringfmt() << "slot" << (char)(97 + i
) << ".sha256").str(), romimg_sha256
[i
+ 1],
464 write_linefile(w
, (stringfmt() << "slot" << (char)(97 + i
) << "xml.sha256").str(),
465 romxml_sha256
[i
+ 1], true);
467 write_subtitles(w
, "subtitles", subtitles
);
468 write_linefile(w
, "prefix", prefix
, true);
469 for(auto i
: movie_sram
)
470 write_raw_file(w
, "moviesram." + i
.first
, i
.second
);
471 write_numeric_file(w
, "starttime.second", movie_rtc_second
);
472 write_numeric_file(w
, "starttime.subsecond", movie_rtc_subsecond
);
473 if(!anchor_savestate
.empty())
474 write_raw_file(w
, "savestate.anchor", anchor_savestate
);
476 write_numeric_file(w
, "saveframe", save_frame
);
477 write_numeric_file(w
, "lagcounter", lagged_frames
);
478 write_pollcounters(w
, "pollcounters", pollcounters
);
479 write_raw_file(w
, "hostmemory", host_memory
);
480 write_raw_file(w
, "savestate", savestate
);
481 write_raw_file(w
, "screenshot", screenshot
);
483 write_raw_file(w
, "sram." + i
.first
, i
.second
);
484 write_numeric_file(w
, "savetime.second", rtc_second
);
485 write_numeric_file(w
, "savetime.subsecond", rtc_subsecond
);
486 write_numeric_file(w
, "pollflag", poll_flag
);
488 write_authors_file(w
, authors
);
489 write_input(w
, input
);
494 uint64_t moviefile::get_frame_count() throw()
496 return input
.count_frames();
501 const int BLOCK_SECONDS
= 0;
502 const int BLOCK_FRAMES
= 1;
503 const int STEP_W
= 2;
504 const int STEP_N
= 3;
507 uint64_t moviefile::get_movie_length() throw()
509 uint64_t frames
= get_frame_count();
511 return 100000000ULL * frames
/ 6;
514 gametype
->fill_framerate_magic(_magic
);
515 uint64_t t
= _magic
[BLOCK_SECONDS
] * 1000000000ULL * (frames
/ _magic
[BLOCK_FRAMES
]);
516 frames
%= _magic
[BLOCK_FRAMES
];
517 t
+= frames
* _magic
[STEP_W
] + (frames
* _magic
[STEP_N
] / _magic
[BLOCK_FRAMES
]);
521 std::string
sanitize_prefix(const std::string
& in
) throw(std::bad_alloc
)
523 std::ostringstream s
;
525 for(size_t i
= 0; i
< in
.length(); i
++) {
527 if(ch
< 33 || ch
== '$' || ch
== ':' || ch
== '/' || ch
== '\\')
528 continue; //Always disallowed.
529 if(ch
== '.' && !any
)
530 continue; //Sometimes disallowed.