True movie slot support
[lsnes.git] / src / core / moviefile.cpp
blobf2c0a3d588f73f086fafed2053bb030b6dc4ea09
1 #include "lsnes.hpp"
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"
11 #include <sstream>
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') {
21 if(x.length() > 1)
22 x = x.substr(0, x.length() - 1);
23 else
24 x = "";
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))
32 return;
33 std::istream& m = r[member];
34 try {
35 std::getline(m, out);
36 strip_CR(out);
37 delete &m;
38 } catch(...) {
39 delete &m;
40 throw;
44 template<typename T>
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)
48 std::string _out;
49 read_linefile(r, member, _out, conditional);
50 if(conditional && _out == "")
51 return;
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 == "")
59 return;
60 std::ostream& m = w.create_file(member);
61 try {
62 m << value << std::endl;
63 w.close_file();
64 } catch(...) {
65 w.close_file();
66 throw;
70 template<typename T>
71 void write_numeric_file(zip_writer& w, const std::string& member, T value) throw(std::bad_alloc,
72 std::runtime_error)
74 std::ostringstream x;
75 x << value;
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,
80 std::runtime_error)
82 std::ostream& m = w.create_file(member);
83 try {
84 m.write(&content[0], content.size());
85 if(!m)
86 throw std::runtime_error("Can't write ZIP file member");
87 w.close_file();
88 } catch(...) {
89 w.close_file();
90 throw;
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];
98 try {
99 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
100 boost::iostreams::copy(m, rd);
101 delete &m;
102 } catch(...) {
103 delete &m;
104 throw;
106 return out;
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) |
118 ((uint64_t)buf[7]);
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) |
126 ((uint32_t)buf[3]);
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,
147 std::runtime_error)
149 std::istream& m = r["authors"];
150 try {
151 std::string x;
152 while(std::getline(m, x)) {
153 strip_CR(x);
154 auto g = split_author(x);
155 authors.push_back(g);
157 delete &m;
158 } catch(...) {
159 delete &m;
160 throw;
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;
169 x << count;
170 return x.str();
173 void write_rrdata(zip_writer& w) throw(std::bad_alloc, std::runtime_error)
175 uint64_t count;
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");
180 try {
181 m2 << count << std::endl;
182 if(!m2)
183 throw std::runtime_error("Can't write ZIP file member");
184 w.close_file();
185 } catch(...) {
186 w.close_file();
187 throw;
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");
195 try {
196 for(auto i : authors)
197 if(i.second == "")
198 m << i.first << std::endl;
199 else
200 m << i.first << "|" << i.second << std::endl;
201 if(!m)
202 throw std::runtime_error("Can't write ZIP file member");
203 w.close_file();
204 } catch(...) {
205 w.close_file();
206 throw;
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");
214 try {
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;
220 if(!m)
221 throw std::runtime_error("Can't write ZIP file member");
222 w.close_file();
223 } catch(...) {
224 w.close_file();
225 throw;
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"];
234 try {
235 std::string x;
236 while(std::getline(m, x)) {
237 strip_CR(x);
238 if(x != "") {
239 tmp.deserialize(x.c_str());
240 input.append(tmp);
243 delete &m;
244 } catch(...) {
245 delete &m;
246 throw;
250 void read_pollcounters(zip_reader& r, const std::string& file, std::vector<uint32_t>& pctr)
252 std::istream& m = r[file];
253 try {
254 std::string x;
255 while(std::getline(m, x)) {
256 strip_CR(x);
257 if(x != "") {
258 int32_t y = parse_value<int32_t>(x);
259 uint32_t z = 0;
260 if(y < 0)
261 z = -(y + 1);
262 else {
263 z = y;
264 z |= 0x80000000UL;
266 pctr.push_back(z);
269 delete &m;
270 } catch(...) {
271 delete &m;
272 throw;
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);
279 try {
280 for(auto i : pctr) {
281 int32_t x = i & 0x7FFFFFFFUL;
282 if((i & 0x80000000UL) == 0)
283 x = -x - 1;
284 m << x << std::endl;
286 if(!m)
287 throw std::runtime_error("Can't write ZIP file member");
288 w.close_file();
289 } catch(...) {
290 w.close_file();
291 throw;
295 porttype_t parse_controller_type(const std::string& type, bool port) throw(std::bad_alloc, std::runtime_error)
297 try {
298 const porttype_info& i = porttype_info::lookup(type);
299 if(!i.legal(port ? 1 : 0))
300 throw 42;
301 return i.value;
302 } catch(...) {
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;
312 port1 = PT_INVALID;
313 port2 = PT_INVALID;
314 coreversion = "";
315 projectid = "";
316 rerecords = "0";
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;
326 std::string tmp;
327 zip_reader r(movie);
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);
332 if(tmp != "0")
333 throw std::runtime_error("Can't decode movie data");
334 read_linefile(r, "gametype", tmp);
335 try {
336 gametype = gtype::togametype(tmp);
337 } catch(std::bad_alloc& e) {
338 throw;
339 } catch(std::exception& e) {
340 throw std::runtime_error("Illegal game type '" + tmp + "'");
342 tmp = "gamepad";
343 read_linefile(r, "port1", tmp, true);
344 port1 = porttype_info::lookup(tmp).value;
345 tmp = "none";
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")) {
368 is_savestate = true;
369 if(r.has_member("moviestate"))
370 //Backwards compat stuff.
371 read_moviestate_file(r, "moviestate", save_frame, lagged_frames, pollcounters);
372 else {
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");
380 for(auto name : r)
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();
391 for(auto name : r)
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);
404 if(port2 != PT_NONE)
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);
412 write_rrdata(w);
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);
424 if(is_savestate) {
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);
431 for(auto i : sram)
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);
439 w.commit();
442 uint64_t moviefile::get_frame_count() throw()
444 return input.count_frames();
447 namespace
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)
464 frames -= framebias;
465 else
466 frames = 0;
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]);
471 return t;
474 gametype_t gametype_compose(rom_type type, rom_region region)
476 switch(type) {
477 case ROMTYPE_SNES:
478 return (region == REGION_PAL) ? GT_SNES_PAL : GT_SNES_NTSC;
479 case ROMTYPE_BSX:
480 return GT_BSX;
481 case ROMTYPE_BSXSLOTTED:
482 return GT_BSX_SLOTTED;
483 case ROMTYPE_SUFAMITURBO:
484 return GT_SUFAMITURBO;
485 case ROMTYPE_SGB:
486 return (region == REGION_PAL) ? GT_SGB_PAL : GT_SGB_NTSC;
487 default:
488 return GT_INVALID;
492 rom_region gametype_region(gametype_t type)
494 switch(type) {
495 case GT_SGB_PAL:
496 case GT_SNES_PAL:
497 return REGION_PAL;
498 default:
499 return REGION_NTSC;
503 rom_type gametype_romtype(gametype_t type)
505 switch(type) {
506 case GT_SNES_NTSC:
507 case GT_SNES_PAL:
508 return ROMTYPE_SNES;
509 case GT_BSX:
510 return ROMTYPE_BSX;
511 case GT_BSX_SLOTTED:
512 return ROMTYPE_BSXSLOTTED;
513 case GT_SUFAMITURBO:
514 return ROMTYPE_SUFAMITURBO;
515 case GT_SGB_PAL:
516 case GT_SGB_NTSC:
517 return ROMTYPE_SGB;
518 default:
519 return ROMTYPE_NONE;
523 std::string sanitize_prefix(const std::string& in) throw(std::bad_alloc)
525 std::ostringstream s;
526 bool any = false;
527 for(size_t i = 0; i < in.length(); i++) {
528 char ch = in[i];
529 if(ch < 33 || ch == '$' || ch == ':' || ch == '/' || ch == '\\')
530 continue; //Always disallowed.
531 if(ch == '.' && !any)
532 continue; //Sometimes disallowed.
533 any = true;
534 s << ch;
536 return s.str();