1 #include "core/misc.hpp"
2 #include "core/moviedata.hpp"
3 #include "core/moviefile.hpp"
4 #include "core/rrdata.hpp"
5 #include "library/zip.hpp"
6 #include "library/string.hpp"
7 #include "interface/romtype.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
16 void read_linefile(zip_reader
& r
, const std::string
& member
, std::string
& out
, bool conditional
= false)
17 throw(std::bad_alloc
, std::runtime_error
)
19 if(conditional
&& !r
.has_member(member
))
21 std::istream
& m
= r
[member
];
32 void write_linefile(zip_writer
& w
, const std::string
& member
, const std::string
& value
, bool conditional
= false)
33 throw(std::bad_alloc
, std::runtime_error
)
35 if(conditional
&& value
== "")
37 std::ostream
& m
= w
.create_file(member
);
39 m
<< value
<< std::endl
;
50 std::map
<std::string
, std::string
> read_settings(zip_reader
& r
)
52 std::map
<std::string
, std::string
> x
;
54 if(!regex_match("port[0-9]+|setting\\..+", i
))
57 if(i
.substr(0, 4) == "port")
61 read_linefile(r
, i
, x
[s
], true);
66 void write_settings(zip_writer
& w
, const std::map
<std::string
, std::string
>& settings
,
67 core_setting_group
& sgroup
)
69 for(auto i
: settings
) {
70 if(!sgroup
.settings
.count(i
.first
))
72 if(sgroup
.settings
[i
.first
]->dflt
== i
.second
)
74 if(regex_match("port[0-9]+", i
.first
))
75 write_linefile(w
, i
.first
, i
.second
);
77 write_linefile(w
, "setting." + i
.first
, i
.second
);
84 void read_numeric_file(zip_reader
& r
, const std::string
& member
, T
& out
, bool conditional
= false)
85 throw(std::bad_alloc
, std::runtime_error
)
88 read_linefile(r
, member
, _out
, conditional
);
89 if(conditional
&& _out
== "")
91 out
= parse_value
<int64_t>(_out
);
95 void write_numeric_file(zip_writer
& w
, const std::string
& member
, T value
) throw(std::bad_alloc
,
100 write_linefile(w
, member
, x
.str());
103 void write_raw_file(zip_writer
& w
, const std::string
& member
, std::vector
<char>& content
) throw(std::bad_alloc
,
106 std::ostream
& m
= w
.create_file(member
);
108 m
.write(&content
[0], content
.size());
110 throw std::runtime_error("Can't write ZIP file member");
118 std::vector
<char> read_raw_file(zip_reader
& r
, const std::string
& member
) throw(std::bad_alloc
, std::runtime_error
)
120 std::vector
<char> out
;
121 std::istream
& m
= r
[member
];
123 boost::iostreams::back_insert_device
<std::vector
<char>> rd(out
);
124 boost::iostreams::copy(m
, rd
);
133 uint64_t decode_uint64(unsigned char* buf
)
135 return ((uint64_t)buf
[0] << 56) |
136 ((uint64_t)buf
[1] << 48) |
137 ((uint64_t)buf
[2] << 40) |
138 ((uint64_t)buf
[3] << 32) |
139 ((uint64_t)buf
[4] << 24) |
140 ((uint64_t)buf
[5] << 16) |
141 ((uint64_t)buf
[6] << 8) |
145 uint32_t decode_uint32(unsigned char* buf
)
147 return ((uint32_t)buf
[0] << 24) |
148 ((uint32_t)buf
[1] << 16) |
149 ((uint32_t)buf
[2] << 8) |
154 void read_moviestate_file(zip_reader
& r
, const std::string
& file
, uint64_t& save_frame
, uint64_t& lagged_frames
,
155 std::vector
<uint32_t>& pollcounters
) throw(std::bad_alloc
, std::runtime_error
)
157 unsigned char buf
[512];
158 auto s
= read_raw_file(r
, file
);
159 if(s
.size() != sizeof(buf
))
160 throw std::runtime_error("Invalid moviestate file");
161 memcpy(buf
, &s
[0], sizeof(buf
));
162 //Interesting offsets: 32-39: Current frame, 40-439: Poll counters, 440-447 lagged frames. All bigendian.
163 save_frame
= decode_uint64(buf
+ 32);
164 lagged_frames
= decode_uint64(buf
+ 440);
165 pollcounters
.resize(100);
166 for(unsigned i
= 0; i
< 100; i
++)
167 pollcounters
[i
] = decode_uint32(buf
+ 40 + 4 * i
);
170 void read_authors_file(zip_reader
& r
, std::vector
<std::pair
<std::string
, std::string
>>& authors
) throw(std::bad_alloc
,
173 std::istream
& m
= r
["authors"];
176 while(std::getline(m
, x
)) {
178 auto g
= split_author(x
);
179 authors
.push_back(g
);
188 std::string
read_rrdata(zip_reader
& r
, std::vector
<char>& out
) throw(std::bad_alloc
, std::runtime_error
)
190 out
= read_raw_file(r
, "rrdata");
191 uint64_t count
= rrdata::count(out
);
192 std::ostringstream x
;
197 void write_rrdata(zip_writer
& w
) throw(std::bad_alloc
, std::runtime_error
)
200 std::vector
<char> out
;
201 count
= rrdata::write(out
);
202 write_raw_file(w
, "rrdata", out
);
203 std::ostream
& m2
= w
.create_file("rerecords");
205 m2
<< count
<< std::endl
;
207 throw std::runtime_error("Can't write ZIP file member");
215 void write_authors_file(zip_writer
& w
, std::vector
<std::pair
<std::string
, std::string
>>& authors
)
216 throw(std::bad_alloc
, std::runtime_error
)
218 std::ostream
& m
= w
.create_file("authors");
220 for(auto i
: authors
)
222 m
<< i
.first
<< std::endl
;
224 m
<< i
.first
<< "|" << i
.second
<< std::endl
;
226 throw std::runtime_error("Can't write ZIP file member");
234 void write_input(zip_writer
& w
, controller_frame_vector
& input
)
235 throw(std::bad_alloc
, std::runtime_error
)
237 std::ostream
& m
= w
.create_file("input");
239 char buffer
[MAX_SERIALIZED_SIZE
];
240 for(size_t i
= 0; i
< input
.size(); i
++) {
241 input
[i
].serialize(buffer
);
242 m
<< buffer
<< std::endl
;
245 throw std::runtime_error("Can't write ZIP file member");
253 void read_subtitles(zip_reader
& r
, const std::string
& file
, std::map
<moviefile_subtiming
, std::string
>& x
)
256 if(!r
.has_member(file
))
258 std::istream
& m
= r
[file
];
262 std::getline(m
, out
);
264 auto r
= regex("([0-9]+)[ \t]+([0-9]+)[ \t]+(.*)", out
);
267 x
[moviefile_subtiming(parse_value
<uint64_t>(r
[1]), parse_value
<uint64_t>(r
[2]))] =
278 void write_subtitles(zip_writer
& w
, const std::string
& file
, std::map
<moviefile_subtiming
, std::string
>& x
)
280 std::ostream
& m
= w
.create_file(file
);
283 m
<< i
.first
.get_frame() << " " << i
.first
.get_length() << " " << s_escape(i
.second
)
286 throw std::runtime_error("Can't write ZIP file member");
294 void read_input(zip_reader
& r
, controller_frame_vector
& input
, unsigned version
) throw(std::bad_alloc
,
297 controller_frame tmp
= input
.blank_frame(false);
298 std::istream
& m
= r
["input"];
301 while(std::getline(m
, x
)) {
304 tmp
.deserialize(x
.c_str());
315 void read_pollcounters(zip_reader
& r
, const std::string
& file
, std::vector
<uint32_t>& pctr
)
317 std::istream
& m
= r
[file
];
320 while(std::getline(m
, x
)) {
323 int32_t y
= parse_value
<int32_t>(x
);
341 void write_pollcounters(zip_writer
& w
, const std::string
& file
, const std::vector
<uint32_t>& pctr
)
343 std::ostream
& m
= w
.create_file(file
);
346 int32_t x
= i
& 0x7FFFFFFFUL
;
347 if((i
& 0x80000000UL
) == 0)
352 throw std::runtime_error("Can't write ZIP file member");
360 moviefile::moviefile() throw(std::bad_alloc
)
362 static port_type_set dummy_types
;
363 force_corrupt
= false;
368 is_savestate
= false;
369 movie_rtc_second
= rtc_second
= DEFAULT_RTC_SECOND
;
370 movie_rtc_subsecond
= rtc_subsecond
= DEFAULT_RTC_SUBSECOND
;
371 start_paused
= false;
372 lazy_project_create
= true;
376 moviefile::moviefile(const std::string
& movie
, core_type
& romtype
) throw(std::bad_alloc
, std::runtime_error
)
379 start_paused
= false;
380 force_corrupt
= false;
381 is_savestate
= false;
382 lazy_project_create
= false;
385 read_linefile(r
, "systemid", tmp
);
386 if(tmp
.substr(0, 8) != "lsnes-rr")
387 throw std::runtime_error("Not lsnes movie");
388 read_linefile(r
, "controlsversion", tmp
);
390 throw std::runtime_error("Can't decode movie data");
391 read_linefile(r
, "gametype", tmp
);
393 gametype
= &romtype
.lookup_sysregion(tmp
);
394 } catch(std::bad_alloc
& e
) {
396 } catch(std::exception
& e
) {
397 throw std::runtime_error("Illegal game type '" + tmp
+ "'");
399 settings
= read_settings(r
);
400 auto ctrldata
= gametype
->get_type().controllerconfig(settings
);
401 port_type_set
& ports
= port_type_set::make(ctrldata
.ports
, ctrldata
.portindex
);
404 read_linefile(r
, "gamename", gamename
, true);
405 read_linefile(r
, "projectid", projectid
);
406 rerecords
= read_rrdata(r
, c_rrdata
);
407 read_linefile(r
, "coreversion", coreversion
);
408 read_linefile(r
, "rom.sha256", romimg_sha256
[0], true);
409 read_linefile(r
, "romxml.sha256", romxml_sha256
[0], true);
411 if(r
.has_member("slot`.sha256"))
413 for(size_t i
= 0; i
< 26; i
++) {
414 read_linefile(r
, (stringfmt() << "slot" << (char)(base
+ i
) << ".sha256").str(), romimg_sha256
[i
+ 1],
416 read_linefile(r
, (stringfmt() << "slot" << (char)(base
+ i
) << "xml.sha256").str(),
417 romxml_sha256
[i
+ 1], true);
419 read_linefile(r
, "prefix", prefix
, true);
420 read_subtitles(r
, "subtitles", subtitles
);
421 prefix
= sanitize_prefix(prefix
);
422 movie_rtc_second
= DEFAULT_RTC_SECOND
;
423 movie_rtc_subsecond
= DEFAULT_RTC_SUBSECOND
;
424 read_numeric_file(r
, "starttime.second", movie_rtc_second
, true);
425 read_numeric_file(r
, "starttime.subsecond", movie_rtc_subsecond
, true);
426 rtc_second
= movie_rtc_second
;
427 rtc_subsecond
= movie_rtc_subsecond
;
428 if(r
.has_member("savestate.anchor"))
429 anchor_savestate
= read_raw_file(r
, "savestate.anchor");
430 if(r
.has_member("savestate")) {
432 if(r
.has_member("moviestate"))
433 //Backwards compat stuff.
434 read_moviestate_file(r
, "moviestate", save_frame
, lagged_frames
, pollcounters
);
436 read_numeric_file(r
, "saveframe", save_frame
, true);
437 read_numeric_file(r
, "lagcounter", lagged_frames
, true);
438 read_pollcounters(r
, "pollcounters", pollcounters
);
440 if(r
.has_member("hostmemory"))
441 host_memory
= read_raw_file(r
, "hostmemory");
442 savestate
= read_raw_file(r
, "savestate");
444 if(name
.length() >= 5 && name
.substr(0, 5) == "sram.")
445 sram
[name
.substr(5)] = read_raw_file(r
, name
);
446 screenshot
= read_raw_file(r
, "screenshot");
447 //If these can't be read, just use some (wrong) values.
448 read_numeric_file(r
, "savetime.second", rtc_second
, true);
449 read_numeric_file(r
, "savetime.subsecond", rtc_subsecond
, true);
450 uint64_t _poll_flag
= 2; //Legacy behaviour is the default.
451 read_numeric_file(r
, "pollflag", _poll_flag
, true);
452 poll_flag
= _poll_flag
;
454 if(rtc_subsecond
< 0 || movie_rtc_subsecond
< 0)
455 throw std::runtime_error("Invalid RTC subsecond value");
456 std::string name
= r
.find_first();
458 if(name
.length() >= 10 && name
.substr(0, 10) == "moviesram.")
459 movie_sram
[name
.substr(10)] = read_raw_file(r
, name
);
460 read_authors_file(r
, authors
);
461 read_input(r
, input
, 0);
464 void moviefile::save(const std::string
& movie
, unsigned compression
) throw(std::bad_alloc
, std::runtime_error
)
466 zip_writer
w(movie
, compression
);
467 write_linefile(w
, "gametype", gametype
->get_name());
468 write_settings(w
, settings
, gametype
->get_type().get_settings());
469 write_linefile(w
, "gamename", gamename
, true);
470 write_linefile(w
, "systemid", "lsnes-rr1");
471 write_linefile(w
, "controlsversion", "0");
472 coreversion
= gametype
->get_type().get_core_identifier();
473 write_linefile(w
, "coreversion", coreversion
);
474 write_linefile(w
, "projectid", projectid
);
476 write_linefile(w
, "rom.sha256", romimg_sha256
[0], true);
477 write_linefile(w
, "romxml.sha256", romxml_sha256
[0], true);
478 for(size_t i
= 0; i
< 26; i
++) {
479 write_linefile(w
, (stringfmt() << "slot" << (char)(97 + i
) << ".sha256").str(), romimg_sha256
[i
+ 1],
481 write_linefile(w
, (stringfmt() << "slot" << (char)(97 + i
) << "xml.sha256").str(),
482 romxml_sha256
[i
+ 1], true);
484 write_subtitles(w
, "subtitles", subtitles
);
485 write_linefile(w
, "prefix", prefix
, true);
486 for(auto i
: movie_sram
)
487 write_raw_file(w
, "moviesram." + i
.first
, i
.second
);
488 write_numeric_file(w
, "starttime.second", movie_rtc_second
);
489 write_numeric_file(w
, "starttime.subsecond", movie_rtc_subsecond
);
490 if(!anchor_savestate
.empty())
491 write_raw_file(w
, "savestate.anchor", anchor_savestate
);
493 write_numeric_file(w
, "saveframe", save_frame
);
494 write_numeric_file(w
, "lagcounter", lagged_frames
);
495 write_pollcounters(w
, "pollcounters", pollcounters
);
496 write_raw_file(w
, "hostmemory", host_memory
);
497 write_raw_file(w
, "savestate", savestate
);
498 write_raw_file(w
, "screenshot", screenshot
);
500 write_raw_file(w
, "sram." + i
.first
, i
.second
);
501 write_numeric_file(w
, "savetime.second", rtc_second
);
502 write_numeric_file(w
, "savetime.subsecond", rtc_subsecond
);
503 write_numeric_file(w
, "pollflag", poll_flag
);
505 write_authors_file(w
, authors
);
506 write_input(w
, input
);
511 uint64_t moviefile::get_frame_count() throw()
513 return input
.count_frames();
518 const int BLOCK_SECONDS
= 0;
519 const int BLOCK_FRAMES
= 1;
520 const int STEP_W
= 2;
521 const int STEP_N
= 3;
524 uint64_t moviefile::get_movie_length() throw()
526 uint64_t frames
= get_frame_count();
528 return 100000000ULL * frames
/ 6;
531 gametype
->fill_framerate_magic(_magic
);
532 uint64_t t
= _magic
[BLOCK_SECONDS
] * 1000000000ULL * (frames
/ _magic
[BLOCK_FRAMES
]);
533 frames
%= _magic
[BLOCK_FRAMES
];
534 t
+= frames
* _magic
[STEP_W
] + (frames
* _magic
[STEP_N
] / _magic
[BLOCK_FRAMES
]);
538 std::string
sanitize_prefix(const std::string
& in
) throw(std::bad_alloc
)
540 std::ostringstream s
;
542 for(size_t i
= 0; i
< in
.length(); i
++) {
544 if(ch
< 33 || ch
== '$' || ch
== ':' || ch
== '/' || ch
== '\\')
545 continue; //Always disallowed.
546 if(ch
== '.' && !any
)
547 continue; //Sometimes disallowed.