Evdev joystick plugin
[lsnes.git] / generic / moviefile.cpp
blobd5fb1e03c4e0359f17f181041a7916b9be2760f5
1 #include "lsnes.hpp"
2 #include <snes/snes.hpp>
3 #include <ui-libsnes/libsnes.hpp>
4 #include "moviefile.hpp"
5 #include "zip.hpp"
6 #include "misc.hpp"
7 #include "rrdata.hpp"
8 #include "moviedata.hpp"
9 #include <sstream>
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 strip_CR(std::string& x) throw(std::bad_alloc)
18 if(x.length() > 0 && x[x.length() - 1] == '\r') {
19 if(x.length() > 1)
20 x = x.substr(0, x.length() - 1);
21 else
22 x = "";
26 void read_linefile(zip_reader& r, const std::string& member, std::string& out, bool conditional = false)
27 throw(std::bad_alloc, std::runtime_error)
29 if(conditional && !r.has_member(member))
30 return;
31 std::istream& m = r[member];
32 try {
33 std::getline(m, out);
34 strip_CR(out);
35 delete &m;
36 } catch(...) {
37 delete &m;
38 throw;
42 void read_numeric_file(zip_reader& r, const std::string& member, int64_t& out, bool conditional = false)
43 throw(std::bad_alloc, std::runtime_error)
45 std::string _out;
46 read_linefile(r, member, _out, conditional);
47 if(conditional && _out == "")
48 return;
49 out = parse_value<int64_t>(_out);
52 void write_linefile(zip_writer& w, const std::string& member, const std::string& value, bool conditional = false)
53 throw(std::bad_alloc, std::runtime_error)
55 if(conditional && value == "")
56 return;
57 std::ostream& m = w.create_file(member);
58 try {
59 m << value << std::endl;
60 w.close_file();
61 } catch(...) {
62 w.close_file();
63 throw;
67 void write_numeric_file(zip_writer& w, const std::string& member, int64_t value) throw(std::bad_alloc,
68 std::runtime_error)
70 std::ostringstream x;
71 x << value;
72 write_linefile(w, member, x.str());
75 void write_raw_file(zip_writer& w, const std::string& member, std::vector<char>& content) throw(std::bad_alloc,
76 std::runtime_error)
78 std::ostream& m = w.create_file(member);
79 try {
80 m.write(&content[0], content.size());
81 if(!m)
82 throw std::runtime_error("Can't write ZIP file member");
83 w.close_file();
84 } catch(...) {
85 w.close_file();
86 throw;
90 std::vector<char> read_raw_file(zip_reader& r, const std::string& member) throw(std::bad_alloc, std::runtime_error)
92 std::vector<char> out;
93 std::istream& m = r[member];
94 try {
95 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
96 boost::iostreams::copy(m, rd);
97 delete &m;
98 } catch(...) {
99 delete &m;
100 throw;
102 return out;
105 void read_authors_file(zip_reader& r, std::vector<std::pair<std::string, std::string>>& authors) throw(std::bad_alloc,
106 std::runtime_error)
108 std::istream& m = r["authors"];
109 try {
110 std::string x;
111 while(std::getline(m, x)) {
112 strip_CR(x);
113 auto g = split_author(x);
114 authors.push_back(g);
116 delete &m;
117 } catch(...) {
118 delete &m;
119 throw;
123 std::string read_rrdata(zip_reader& r, std::vector<char>& out) throw(std::bad_alloc, std::runtime_error)
125 out = read_raw_file(r, "rrdata");
126 uint64_t count = rrdata::count(out);
127 std::ostringstream x;
128 x << count;
129 return x.str();
132 void write_rrdata(zip_writer& w) throw(std::bad_alloc, std::runtime_error)
134 uint64_t count;
135 std::vector<char> out;
136 count = rrdata::write(out);
137 write_raw_file(w, "rrdata", out);
138 std::ostream& m2 = w.create_file("rerecords");
139 try {
140 m2 << count << std::endl;
141 if(!m2)
142 throw std::runtime_error("Can't write ZIP file member");
143 w.close_file();
144 } catch(...) {
145 w.close_file();
146 throw;
150 void write_authors_file(zip_writer& w, std::vector<std::pair<std::string, std::string>>& authors)
151 throw(std::bad_alloc, std::runtime_error)
153 std::ostream& m = w.create_file("authors");
154 try {
155 for(auto i : authors)
156 if(i.second == "")
157 m << i.first << std::endl;
158 else
159 m << i.first << "|" << i.second << std::endl;
160 if(!m)
161 throw std::runtime_error("Can't write ZIP file member");
162 w.close_file();
163 } catch(...) {
164 w.close_file();
165 throw;
169 void write_input(zip_writer& w, std::vector<controls_t>& input, porttype_t port1, porttype_t port2)
170 throw(std::bad_alloc, std::runtime_error)
172 std::vector<cencode::fn_t> encoders;
173 encoders.push_back(port_types[port1].encoder);
174 encoders.push_back(port_types[port2].encoder);
175 std::ostream& m = w.create_file("input");
176 try {
177 for(auto i : input)
178 m << i.tostring(encoders) << std::endl;
179 if(!m)
180 throw std::runtime_error("Can't write ZIP file member");
181 w.close_file();
182 } catch(...) {
183 w.close_file();
184 throw;
188 void read_input(zip_reader& r, std::vector<controls_t>& input, porttype_t port1, porttype_t port2, unsigned version)
189 throw(std::bad_alloc, std::runtime_error)
191 std::vector<cdecode::fn_t> decoders;
192 decoders.push_back(port_types[port1].decoder);
193 decoders.push_back(port_types[port2].decoder);
194 std::istream& m = r["input"];
195 try {
196 std::string x;
197 while(std::getline(m, x)) {
198 strip_CR(x);
199 if(x != "") {
200 input.push_back(controls_t(x, decoders, version));
203 delete &m;
204 } catch(...) {
205 delete &m;
206 throw;
211 porttype_t parse_controller_type(const std::string& type, bool port) throw(std::bad_alloc, std::runtime_error)
213 porttype_t port1 = PT_INVALID;
214 for(unsigned i = 0; i <= PT_LAST_CTYPE; i++)
215 if(type == port_types[i].name && (port || port_types[i].valid_port1))
216 port1 = static_cast<porttype_t>(i);
217 if(port1 == PT_INVALID)
218 throw std::runtime_error(std::string("Illegal port") + (port ? "2" : "1") + " device '" + type + "'");
219 return port1;
223 moviefile::moviefile() throw(std::bad_alloc)
225 force_corrupt = false;
226 gametype = GT_INVALID;
227 port1 = PT_GAMEPAD;
228 port2 = PT_NONE;
229 coreversion = "";
230 projectid = "";
231 rerecords = "0";
232 is_savestate = false;
233 movie_rtc_second = rtc_second = DEFAULT_RTC_SECOND;
234 movie_rtc_subsecond = rtc_subsecond = DEFAULT_RTC_SUBSECOND;
237 moviefile::moviefile(const std::string& movie) throw(std::bad_alloc, std::runtime_error)
239 force_corrupt = false;
240 is_savestate = false;
241 std::string tmp;
242 zip_reader r(movie);
243 read_linefile(r, "systemid", tmp);
244 if(tmp.substr(0, 8) != "lsnes-rr")
245 throw std::runtime_error("Not lsnes movie");
246 read_linefile(r, "controlsversion", tmp);
247 if(tmp != "0")
248 throw std::runtime_error("Can't decode movie data");
249 read_linefile(r, "gametype", tmp);
250 try {
251 gametype = gtype::togametype(tmp);
252 } catch(std::bad_alloc& e) {
253 throw;
254 } catch(std::exception& e) {
255 throw std::runtime_error("Illegal game type '" + tmp + "'");
257 tmp = port_types[PT_GAMEPAD].name;
258 read_linefile(r, "port1", tmp, true);
259 port1 = port_type::lookup(tmp, false).ptype;
260 tmp = port_types[PT_NONE].name;
261 read_linefile(r, "port2", tmp, true);
262 port2 = port_type::lookup(tmp, true).ptype;
263 read_linefile(r, "gamename", gamename, true);
264 read_linefile(r, "projectid", projectid);
265 rerecords = read_rrdata(r, c_rrdata);
266 read_linefile(r, "coreversion", coreversion);
267 read_linefile(r, "rom.sha256", rom_sha256, true);
268 read_linefile(r, "romxml.sha256", romxml_sha256, true);
269 read_linefile(r, "slota.sha256", slota_sha256, true);
270 read_linefile(r, "slotaxml.sha256", slotaxml_sha256, true);
271 read_linefile(r, "slotb.sha256", slotb_sha256, true);
272 read_linefile(r, "slotbxml.sha256", slotbxml_sha256, true);
273 movie_rtc_second = DEFAULT_RTC_SECOND;
274 movie_rtc_subsecond = DEFAULT_RTC_SUBSECOND;
275 read_numeric_file(r, "starttime.second", movie_rtc_second, true);
276 read_numeric_file(r, "starttime.subsecond", movie_rtc_subsecond, true);
277 rtc_second = movie_rtc_second;
278 rtc_subsecond = movie_rtc_subsecond;
279 if(r.has_member("savestate")) {
280 is_savestate = true;
281 movie_state = read_raw_file(r, "moviestate");
282 if(r.has_member("hostmemory"))
283 host_memory = read_raw_file(r, "hostmemory");
284 savestate = read_raw_file(r, "savestate");
285 for(auto name : r)
286 if(name.length() >= 5 && name.substr(0, 5) == "sram.")
287 sram[name.substr(5)] = read_raw_file(r, name);
288 screenshot = read_raw_file(r, "screenshot");
289 //If these can't be read, just use some (wrong) values.
290 read_numeric_file(r, "savetime.second", rtc_second, true);
291 read_numeric_file(r, "savetime.subsecond", rtc_subsecond, true);
293 if(rtc_subsecond < 0 || movie_rtc_subsecond < 0)
294 throw std::runtime_error("Invalid RTC subsecond value");
295 std::string name = r.find_first();
296 for(auto name : r)
297 if(name.length() >= 10 && name.substr(0, 10) == "moviesram.")
298 movie_sram[name.substr(10)] = read_raw_file(r, name);
299 read_authors_file(r, authors);
300 read_input(r, input, port1, port2, 0);
303 void moviefile::save(const std::string& movie, unsigned compression) throw(std::bad_alloc, std::runtime_error)
305 zip_writer w(movie, compression);
306 write_linefile(w, "gametype", gtype::tostring(gametype));
307 if(port1 != PT_GAMEPAD)
308 write_linefile(w, "port1", port_types[port1].name);
309 if(port2 != PT_NONE)
310 write_linefile(w, "port2", port_types[port2].name);
311 write_linefile(w, "gamename", gamename, true);
312 write_linefile(w, "systemid", "lsnes-rr1");
313 write_linefile(w, "controlsversion", "0");
314 coreversion = bsnes_core_version;
315 write_linefile(w, "coreversion", coreversion);
316 write_linefile(w, "projectid", projectid);
317 write_rrdata(w);
318 write_linefile(w, "rom.sha256", rom_sha256, true);
319 write_linefile(w, "romxml.sha256", romxml_sha256, true);
320 write_linefile(w, "slota.sha256", slota_sha256, true);
321 write_linefile(w, "slotaxml.sha256", slotaxml_sha256, true);
322 write_linefile(w, "slotb.sha256", slotb_sha256, true);
323 write_linefile(w, "slotbxml.sha256", slotbxml_sha256, true);
324 for(auto i : movie_sram)
325 write_raw_file(w, "moviesram." + i.first, i.second);
326 write_numeric_file(w, "starttime.second", movie_rtc_second);
327 write_numeric_file(w, "starttime.subsecond", movie_rtc_subsecond);
328 if(is_savestate) {
329 write_raw_file(w, "moviestate", movie_state);
330 write_raw_file(w, "hostmemory", host_memory);
331 write_raw_file(w, "savestate", savestate);
332 write_raw_file(w, "screenshot", screenshot);
333 for(auto i : sram)
334 write_raw_file(w, "sram." + i.first, i.second);
335 write_numeric_file(w, "savetime.second", rtc_second);
336 write_numeric_file(w, "savetime.subsecond", rtc_subsecond);
338 write_authors_file(w, authors);
339 write_input(w, input, port1, port2);
341 w.commit();
344 uint64_t moviefile::get_frame_count() throw()
346 uint64_t frames = 0;
347 for(size_t i = 0; i < input.size(); i++) {
348 if(input[i](CONTROL_FRAME_SYNC))
349 frames++;
351 return frames;
354 namespace
356 const int BLOCK_SECONDS = 0;
357 const int BLOCK_FRAMES = 1;
358 const int STEP_W = 2;
359 const int STEP_N = 3;
361 uint64_t magic[2][4] = {
362 {178683, 10738636, 16639264, 596096},
363 {6448, 322445, 19997208, 266440}
367 uint64_t moviefile::get_movie_length(uint64_t framebias) throw()
369 uint64_t frames = get_frame_count();
370 if(frames > framebias)
371 frames -= framebias;
372 else
373 frames = 0;
374 uint64_t* _magic = magic[(gametype == GT_SNES_PAL || gametype == GT_SGB_PAL) ? 1 : 0];
375 uint64_t t = _magic[BLOCK_SECONDS] * 1000000000ULL * (frames / _magic[BLOCK_FRAMES]);
376 frames %= _magic[BLOCK_FRAMES];
377 t += frames * _magic[STEP_W] + (frames * _magic[STEP_N] / _magic[BLOCK_FRAMES]);
378 return t;
381 gametype_t gametype_compose(rom_type type, rom_region region)
383 switch(type) {
384 case ROMTYPE_SNES:
385 return (region == REGION_PAL) ? GT_SNES_PAL : GT_SNES_NTSC;
386 case ROMTYPE_BSX:
387 return GT_BSX;
388 case ROMTYPE_BSXSLOTTED:
389 return GT_BSX_SLOTTED;
390 case ROMTYPE_SUFAMITURBO:
391 return GT_SUFAMITURBO;
392 case ROMTYPE_SGB:
393 return (region == REGION_PAL) ? GT_SGB_PAL : GT_SGB_NTSC;
394 default:
395 return GT_INVALID;
399 rom_region gametype_region(gametype_t type)
401 switch(type) {
402 case GT_SGB_PAL:
403 case GT_SNES_PAL:
404 return REGION_PAL;
405 default:
406 return REGION_NTSC;
410 rom_type gametype_romtype(gametype_t type)
412 switch(type) {
413 case GT_SNES_NTSC:
414 case GT_SNES_PAL:
415 return ROMTYPE_SNES;
416 case GT_BSX:
417 return ROMTYPE_BSX;
418 case GT_BSX_SLOTTED:
419 return ROMTYPE_BSXSLOTTED;
420 case GT_SUFAMITURBO:
421 return ROMTYPE_SUFAMITURBO;
422 case GT_SGB_PAL:
423 case GT_SGB_NTSC:
424 return ROMTYPE_SGB;
425 default:
426 return ROMTYPE_NONE;