2 #include <snes/snes.hpp>
3 #include <ui-libsnes/libsnes.hpp>
5 #include "core/misc.hpp"
6 #include "core/moviedata.hpp"
7 #include "core/moviefile.hpp"
8 #include "core/rrdata.hpp"
9 #include "core/zip.hpp"
12 #include <boost/iostreams/copy.hpp>
13 #include <boost/iostreams/device/back_inserter.hpp>
15 #define DEFAULT_RTC_SECOND 1000000000ULL
16 #define DEFAULT_RTC_SUBSECOND 0ULL
18 void strip_CR(std::string
& x
) throw(std::bad_alloc
)
20 if(x
.length() > 0 && x
[x
.length() - 1] == '\r') {
22 x
= x
.substr(0, x
.length() - 1);
28 void read_linefile(zip_reader
& r
, const std::string
& member
, std::string
& out
, bool conditional
= false)
29 throw(std::bad_alloc
, std::runtime_error
)
31 if(conditional
&& !r
.has_member(member
))
33 std::istream
& m
= r
[member
];
45 void read_numeric_file(zip_reader
& r
, const std::string
& member
, T
& out
, bool conditional
= false)
46 throw(std::bad_alloc
, std::runtime_error
)
49 read_linefile(r
, member
, _out
, conditional
);
50 if(conditional
&& _out
== "")
52 out
= parse_value
<int64_t>(_out
);
55 void write_linefile(zip_writer
& w
, const std::string
& member
, const std::string
& value
, bool conditional
= false)
56 throw(std::bad_alloc
, std::runtime_error
)
58 if(conditional
&& value
== "")
60 std::ostream
& m
= w
.create_file(member
);
62 m
<< value
<< std::endl
;
71 void write_numeric_file(zip_writer
& w
, const std::string
& member
, T value
) throw(std::bad_alloc
,
76 write_linefile(w
, member
, x
.str());
79 void write_raw_file(zip_writer
& w
, const std::string
& member
, std::vector
<char>& content
) throw(std::bad_alloc
,
82 std::ostream
& m
= w
.create_file(member
);
84 m
.write(&content
[0], content
.size());
86 throw std::runtime_error("Can't write ZIP file member");
94 std::vector
<char> read_raw_file(zip_reader
& r
, const std::string
& member
) throw(std::bad_alloc
, std::runtime_error
)
96 std::vector
<char> out
;
97 std::istream
& m
= r
[member
];
99 boost::iostreams::back_insert_device
<std::vector
<char>> rd(out
);
100 boost::iostreams::copy(m
, rd
);
109 uint64_t decode_uint64(unsigned char* buf
)
111 return ((uint64_t)buf
[0] << 56) |
112 ((uint64_t)buf
[1] << 48) |
113 ((uint64_t)buf
[2] << 40) |
114 ((uint64_t)buf
[3] << 32) |
115 ((uint64_t)buf
[4] << 24) |
116 ((uint64_t)buf
[5] << 16) |
117 ((uint64_t)buf
[6] << 8) |
121 uint32_t decode_uint32(unsigned char* buf
)
123 return ((uint32_t)buf
[0] << 24) |
124 ((uint32_t)buf
[1] << 16) |
125 ((uint32_t)buf
[2] << 8) |
130 void read_moviestate_file(zip_reader
& r
, const std::string
& file
, uint64_t& save_frame
, uint64_t& lagged_frames
,
131 std::vector
<uint32_t>& pollcounters
) throw(std::bad_alloc
, std::runtime_error
)
133 unsigned char buf
[512];
134 auto s
= read_raw_file(r
, file
);
135 if(s
.size() != sizeof(buf
))
136 throw std::runtime_error("Invalid moviestate file");
137 memcpy(buf
, &s
[0], sizeof(buf
));
138 //Interesting offsets: 32-39: Current frame, 40-439: Poll counters, 440-447 lagged frames. All bigendian.
139 save_frame
= decode_uint64(buf
+ 32);
140 lagged_frames
= decode_uint64(buf
+ 440);
141 pollcounters
.resize(100);
142 for(unsigned i
= 0; i
< 100; i
++)
143 pollcounters
[i
] = decode_uint32(buf
+ 40 + 4 * i
);
146 void read_authors_file(zip_reader
& r
, std::vector
<std::pair
<std::string
, std::string
>>& authors
) throw(std::bad_alloc
,
149 std::istream
& m
= r
["authors"];
152 while(std::getline(m
, x
)) {
154 auto g
= split_author(x
);
155 authors
.push_back(g
);
164 std::string
read_rrdata(zip_reader
& r
, std::vector
<char>& out
) throw(std::bad_alloc
, std::runtime_error
)
166 out
= read_raw_file(r
, "rrdata");
167 uint64_t count
= rrdata::count(out
);
168 std::ostringstream x
;
173 void write_rrdata(zip_writer
& w
) throw(std::bad_alloc
, std::runtime_error
)
176 std::vector
<char> out
;
177 count
= rrdata::write(out
);
178 write_raw_file(w
, "rrdata", out
);
179 std::ostream
& m2
= w
.create_file("rerecords");
181 m2
<< count
<< std::endl
;
183 throw std::runtime_error("Can't write ZIP file member");
191 void write_authors_file(zip_writer
& w
, std::vector
<std::pair
<std::string
, std::string
>>& authors
)
192 throw(std::bad_alloc
, std::runtime_error
)
194 std::ostream
& m
= w
.create_file("authors");
196 for(auto i
: authors
)
198 m
<< i
.first
<< std::endl
;
200 m
<< i
.first
<< "|" << i
.second
<< std::endl
;
202 throw std::runtime_error("Can't write ZIP file member");
210 void write_input(zip_writer
& w
, controller_frame_vector
& input
, porttype_t port1
, porttype_t port2
)
211 throw(std::bad_alloc
, std::runtime_error
)
213 std::ostream
& m
= w
.create_file("input");
215 char buffer
[MAX_SERIALIZED_SIZE
];
216 for(size_t i
= 0; i
< input
.size(); i
++) {
217 input
[i
].serialize(buffer
);
218 m
<< buffer
<< std::endl
;
221 throw std::runtime_error("Can't write ZIP file member");
229 void read_input(zip_reader
& r
, controller_frame_vector
& input
, porttype_t port1
, porttype_t port2
, unsigned version
)
230 throw(std::bad_alloc
, std::runtime_error
)
232 controller_frame tmp
= input
.blank_frame(false);
233 std::istream
& m
= r
["input"];
236 while(std::getline(m
, x
)) {
239 tmp
.deserialize(x
.c_str());
250 void read_pollcounters(zip_reader
& r
, const std::string
& file
, std::vector
<uint32_t>& pctr
)
252 std::istream
& m
= r
[file
];
255 while(std::getline(m
, x
)) {
258 int32_t y
= parse_value
<int32_t>(x
);
276 void write_pollcounters(zip_writer
& w
, const std::string
& file
, const std::vector
<uint32_t>& pctr
)
278 std::ostream
& m
= w
.create_file(file
);
281 int32_t x
= i
& 0x7FFFFFFFUL
;
282 if((i
& 0x80000000UL
) == 0)
287 throw std::runtime_error("Can't write ZIP file member");
295 porttype_t
parse_controller_type(const std::string
& type
, bool port
) throw(std::bad_alloc
, std::runtime_error
)
298 const porttype_info
& i
= porttype_info::lookup(type
);
299 if(!i
.legal(port
? 1 : 0))
303 throw std::runtime_error(std::string("Illegal port") + (port
? "2" : "1") + " device '" + type
+ "'");
308 moviefile::moviefile() throw(std::bad_alloc
)
310 force_corrupt
= false;
311 gametype
= GT_INVALID
;
317 is_savestate
= false;
318 movie_rtc_second
= rtc_second
= DEFAULT_RTC_SECOND
;
319 movie_rtc_subsecond
= rtc_subsecond
= DEFAULT_RTC_SUBSECOND
;
322 moviefile::moviefile(const std::string
& movie
) throw(std::bad_alloc
, std::runtime_error
)
324 force_corrupt
= false;
325 is_savestate
= false;
328 read_linefile(r
, "systemid", tmp
);
329 if(tmp
.substr(0, 8) != "lsnes-rr")
330 throw std::runtime_error("Not lsnes movie");
331 read_linefile(r
, "controlsversion", tmp
);
333 throw std::runtime_error("Can't decode movie data");
334 read_linefile(r
, "gametype", tmp
);
336 gametype
= gtype::togametype(tmp
);
337 } catch(std::bad_alloc
& e
) {
339 } catch(std::exception
& e
) {
340 throw std::runtime_error("Illegal game type '" + tmp
+ "'");
343 read_linefile(r
, "port1", tmp
, true);
344 port1
= porttype_info::lookup(tmp
).value
;
346 read_linefile(r
, "port2", tmp
, true);
347 port2
= porttype_info::lookup(tmp
).value
;
348 input
.clear(port1
, port2
);
349 read_linefile(r
, "gamename", gamename
, true);
350 read_linefile(r
, "projectid", projectid
);
351 rerecords
= read_rrdata(r
, c_rrdata
);
352 read_linefile(r
, "coreversion", coreversion
);
353 read_linefile(r
, "rom.sha256", rom_sha256
, true);
354 read_linefile(r
, "romxml.sha256", romxml_sha256
, true);
355 read_linefile(r
, "slota.sha256", slota_sha256
, true);
356 read_linefile(r
, "slotaxml.sha256", slotaxml_sha256
, true);
357 read_linefile(r
, "slotb.sha256", slotb_sha256
, true);
358 read_linefile(r
, "slotbxml.sha256", slotbxml_sha256
, true);
359 read_linefile(r
, "prefix", prefix
, true);
360 prefix
= sanitize_prefix(prefix
);
361 movie_rtc_second
= DEFAULT_RTC_SECOND
;
362 movie_rtc_subsecond
= DEFAULT_RTC_SUBSECOND
;
363 read_numeric_file(r
, "starttime.second", movie_rtc_second
, true);
364 read_numeric_file(r
, "starttime.subsecond", movie_rtc_subsecond
, true);
365 rtc_second
= movie_rtc_second
;
366 rtc_subsecond
= movie_rtc_subsecond
;
367 if(r
.has_member("savestate")) {
369 if(r
.has_member("moviestate"))
370 //Backwards compat stuff.
371 read_moviestate_file(r
, "moviestate", save_frame
, lagged_frames
, pollcounters
);
373 read_numeric_file(r
, "saveframe", save_frame
, true);
374 read_numeric_file(r
, "lagcounter", lagged_frames
, true);
375 read_pollcounters(r
, "pollcounters", pollcounters
);
377 if(r
.has_member("hostmemory"))
378 host_memory
= read_raw_file(r
, "hostmemory");
379 savestate
= read_raw_file(r
, "savestate");
381 if(name
.length() >= 5 && name
.substr(0, 5) == "sram.")
382 sram
[name
.substr(5)] = read_raw_file(r
, name
);
383 screenshot
= read_raw_file(r
, "screenshot");
384 //If these can't be read, just use some (wrong) values.
385 read_numeric_file(r
, "savetime.second", rtc_second
, true);
386 read_numeric_file(r
, "savetime.subsecond", rtc_subsecond
, true);
388 if(rtc_subsecond
< 0 || movie_rtc_subsecond
< 0)
389 throw std::runtime_error("Invalid RTC subsecond value");
390 std::string name
= r
.find_first();
392 if(name
.length() >= 10 && name
.substr(0, 10) == "moviesram.")
393 movie_sram
[name
.substr(10)] = read_raw_file(r
, name
);
394 read_authors_file(r
, authors
);
395 read_input(r
, input
, port1
, port2
, 0);
398 void moviefile::save(const std::string
& movie
, unsigned compression
) throw(std::bad_alloc
, std::runtime_error
)
400 zip_writer
w(movie
, compression
);
401 write_linefile(w
, "gametype", gtype::tostring(gametype
));
402 if(port1
!= PT_GAMEPAD
)
403 write_linefile(w
, "port1", porttype_info::lookup(port1
).name
);
405 write_linefile(w
, "port2", porttype_info::lookup(port2
).name
);
406 write_linefile(w
, "gamename", gamename
, true);
407 write_linefile(w
, "systemid", "lsnes-rr1");
408 write_linefile(w
, "controlsversion", "0");
409 coreversion
= bsnes_core_version
;
410 write_linefile(w
, "coreversion", coreversion
);
411 write_linefile(w
, "projectid", projectid
);
413 write_linefile(w
, "rom.sha256", rom_sha256
, true);
414 write_linefile(w
, "romxml.sha256", romxml_sha256
, true);
415 write_linefile(w
, "slota.sha256", slota_sha256
, true);
416 write_linefile(w
, "slotaxml.sha256", slotaxml_sha256
, true);
417 write_linefile(w
, "slotb.sha256", slotb_sha256
, true);
418 write_linefile(w
, "slotbxml.sha256", slotbxml_sha256
, true);
419 write_linefile(w
, "prefix", prefix
, true);
420 for(auto i
: movie_sram
)
421 write_raw_file(w
, "moviesram." + i
.first
, i
.second
);
422 write_numeric_file(w
, "starttime.second", movie_rtc_second
);
423 write_numeric_file(w
, "starttime.subsecond", movie_rtc_subsecond
);
425 write_numeric_file(w
, "saveframe", save_frame
);
426 write_numeric_file(w
, "lagcounter", lagged_frames
);
427 write_pollcounters(w
, "pollcounters", pollcounters
);
428 write_raw_file(w
, "hostmemory", host_memory
);
429 write_raw_file(w
, "savestate", savestate
);
430 write_raw_file(w
, "screenshot", screenshot
);
432 write_raw_file(w
, "sram." + i
.first
, i
.second
);
433 write_numeric_file(w
, "savetime.second", rtc_second
);
434 write_numeric_file(w
, "savetime.subsecond", rtc_subsecond
);
436 write_authors_file(w
, authors
);
437 write_input(w
, input
, port1
, port2
);
442 uint64_t moviefile::get_frame_count() throw()
444 return input
.count_frames();
449 const int BLOCK_SECONDS
= 0;
450 const int BLOCK_FRAMES
= 1;
451 const int STEP_W
= 2;
452 const int STEP_N
= 3;
454 uint64_t magic
[2][4] = {
455 {178683, 10738636, 16639264, 596096},
456 {6448, 322445, 19997208, 266440}
460 uint64_t moviefile::get_movie_length(uint64_t framebias
) throw()
462 uint64_t frames
= get_frame_count();
463 if(frames
> framebias
)
467 uint64_t* _magic
= magic
[(gametype
== GT_SNES_PAL
|| gametype
== GT_SGB_PAL
) ? 1 : 0];
468 uint64_t t
= _magic
[BLOCK_SECONDS
] * 1000000000ULL * (frames
/ _magic
[BLOCK_FRAMES
]);
469 frames
%= _magic
[BLOCK_FRAMES
];
470 t
+= frames
* _magic
[STEP_W
] + (frames
* _magic
[STEP_N
] / _magic
[BLOCK_FRAMES
]);
474 gametype_t
gametype_compose(rom_type type
, rom_region region
)
478 return (region
== REGION_PAL
) ? GT_SNES_PAL
: GT_SNES_NTSC
;
481 case ROMTYPE_BSXSLOTTED
:
482 return GT_BSX_SLOTTED
;
483 case ROMTYPE_SUFAMITURBO
:
484 return GT_SUFAMITURBO
;
486 return (region
== REGION_PAL
) ? GT_SGB_PAL
: GT_SGB_NTSC
;
492 rom_region
gametype_region(gametype_t type
)
503 rom_type
gametype_romtype(gametype_t type
)
512 return ROMTYPE_BSXSLOTTED
;
514 return ROMTYPE_SUFAMITURBO
;
523 std::string
sanitize_prefix(const std::string
& in
) throw(std::bad_alloc
)
525 std::ostringstream s
;
527 for(size_t i
= 0; i
< in
.length(); i
++) {
529 if(ch
< 33 || ch
== '$' || ch
== ':' || ch
== '/' || ch
== '\\')
530 continue; //Always disallowed.
531 if(ch
== '.' && !any
)
532 continue; //Sometimes disallowed.