lsnes rr0-β2
[lsnes.git] / moviefile.cpp
blobbce06d6a58a9d8425023c7391e470eaf319df9ef
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 void strip_CR(std::string& x) throw(std::bad_alloc)
15 if(x.length() > 0 && x[x.length() - 1] == '\r') {
16 if(x.length() > 1)
17 x = x.substr(0, x.length() - 1);
18 else
19 x = "";
23 void read_linefile(zip_reader& r, const std::string& member, std::string& out, bool conditional = false)
24 throw(std::bad_alloc, std::runtime_error)
26 if(conditional && !r.has_member(member))
27 return;
28 std::istream& m = r[member];
29 try {
30 std::getline(m, out);
31 strip_CR(out);
32 delete &m;
33 } catch(...) {
34 delete &m;
35 throw;
39 void write_linefile(zip_writer& w, const std::string& member, const std::string& value, bool conditional = false)
40 throw(std::bad_alloc, std::runtime_error)
42 if(conditional && value == "")
43 return;
44 std::ostream& m = w.create_file(member);
45 try {
46 m << value << std::endl;
47 w.close_file();
48 } catch(...) {
49 w.close_file();
50 throw;
54 void write_raw_file(zip_writer& w, const std::string& member, std::vector<char>& content) throw(std::bad_alloc,
55 std::runtime_error)
57 std::ostream& m = w.create_file(member);
58 try {
59 m.write(&content[0], content.size());
60 if(!m)
61 throw std::runtime_error("Can't write ZIP file member");
62 w.close_file();
63 } catch(...) {
64 w.close_file();
65 throw;
69 std::vector<char> read_raw_file(zip_reader& r, const std::string& member) throw(std::bad_alloc, std::runtime_error)
71 std::vector<char> out;
72 std::istream& m = r[member];
73 try {
74 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
75 boost::iostreams::copy(m, rd);
76 delete &m;
77 } catch(...) {
78 delete &m;
79 throw;
81 return out;
84 void read_authors_file(zip_reader& r, std::vector<std::pair<std::string, std::string>>& authors) throw(std::bad_alloc,
85 std::runtime_error)
87 std::istream& m = r["authors"];
88 try {
89 std::string x;
90 while(std::getline(m, x)) {
91 strip_CR(x);
92 auto g = split_author(x);
93 authors.push_back(g);
95 delete &m;
96 } catch(...) {
97 delete &m;
98 throw;
102 std::string read_rrdata(zip_reader& r, const std::string& projectid) throw(std::bad_alloc, std::runtime_error)
104 std::istream& m = r["rrdata"];
105 uint64_t count;
106 try {
107 rrdata::read_base(projectid);
108 count = rrdata::read(m);
109 delete &m;
110 } catch(...) {
111 delete &m;
112 throw;
114 std::ostringstream x;
115 x << count;
116 return x.str();
119 void write_rrdata(zip_writer& w) throw(std::bad_alloc, std::runtime_error)
121 std::ostream& m = w.create_file("rrdata");
122 uint64_t count;
123 try {
124 count = rrdata::write(m);
125 if(!m)
126 throw std::runtime_error("Can't write ZIP file member");
127 w.close_file();
128 } catch(...) {
129 w.close_file();
130 throw;
132 std::ostream& m2 = w.create_file("rerecords");
133 try {
134 m2 << count << std::endl;
135 if(!m2)
136 throw std::runtime_error("Can't write ZIP file member");
137 w.close_file();
138 } catch(...) {
139 w.close_file();
140 throw;
144 void write_authors_file(zip_writer& w, std::vector<std::pair<std::string, std::string>>& authors)
145 throw(std::bad_alloc, std::runtime_error)
147 std::ostream& m = w.create_file("authors");
148 try {
149 for(auto i = authors.begin(); i != authors.end(); ++i)
150 if(i->second == "")
151 m << i->first << std::endl;
152 else
153 m << i->first << "|" << i->second << std::endl;
154 if(!m)
155 throw std::runtime_error("Can't write ZIP file member");
156 w.close_file();
157 } catch(...) {
158 w.close_file();
159 throw;
163 void write_input(zip_writer& w, std::vector<controls_t>& input, porttype_t port1, porttype_t port2)
164 throw(std::bad_alloc, std::runtime_error)
166 std::vector<cencode::fn_t> encoders;
167 encoders.push_back(port_types[port1].encoder);
168 encoders.push_back(port_types[port2].encoder);
169 std::ostream& m = w.create_file("input");
170 try {
171 for(auto i = input.begin(); i != input.end(); ++i)
172 m << i->tostring(encoders) << std::endl;
173 if(!m)
174 throw std::runtime_error("Can't write ZIP file member");
175 w.close_file();
176 } catch(...) {
177 w.close_file();
178 throw;
182 void read_input(zip_reader& r, std::vector<controls_t>& input, porttype_t port1, porttype_t port2, unsigned version)
183 throw(std::bad_alloc, std::runtime_error)
185 std::vector<cdecode::fn_t> decoders;
186 decoders.push_back(port_types[port1].decoder);
187 decoders.push_back(port_types[port2].decoder);
188 std::istream& m = r["input"];
189 try {
190 std::string x;
191 while(std::getline(m, x)) {
192 strip_CR(x);
193 if(x != "") {
194 input.push_back(controls_t(x, decoders, version));
197 delete &m;
198 } catch(...) {
199 delete &m;
200 throw;
205 porttype_t parse_controller_type(const std::string& type, bool port) throw(std::bad_alloc, std::runtime_error)
207 porttype_t port1 = PT_INVALID;
208 for(unsigned i = 0; i <= PT_LAST_CTYPE; i++)
209 if(type == port_types[i].name && (port || port_types[i].valid_port1))
210 port1 = static_cast<porttype_t>(i);
211 if(port1 == PT_INVALID)
212 throw std::runtime_error(std::string("Illegal port") + (port ? "2" : "1") + " device '" + type + "'");
213 return port1;
217 moviefile::moviefile() throw(std::bad_alloc)
219 gametype = GT_INVALID;
220 port1 = PT_GAMEPAD;
221 port2 = PT_NONE;
222 coreversion = bsnes_core_version;
223 projectid = get_random_hexstring(40);
224 rerecords = "0";
225 is_savestate = false;
228 moviefile::moviefile(const std::string& movie) throw(std::bad_alloc, std::runtime_error)
230 is_savestate = false;
231 std::string tmp;
232 zip_reader r(movie);
233 read_linefile(r, "systemid", tmp);
234 if(tmp.substr(0, 8) != "lsnes-rr")
235 throw std::runtime_error("Not lsnes movie");
236 read_linefile(r, "controlsversion", tmp);
237 if(tmp != "0")
238 throw std::runtime_error("Can't decode movie data");
239 read_linefile(r, "gametype", tmp);
240 try {
241 gametype = gtype::togametype(tmp);
242 } catch(std::bad_alloc& e) {
243 throw;
244 } catch(std::exception& e) {
245 throw std::runtime_error("Illegal game type '" + tmp + "'");
247 tmp = port_types[PT_GAMEPAD].name;
248 read_linefile(r, "port1", tmp, true);
249 port1 = port_type::lookup(tmp, false).ptype;
250 tmp = port_types[PT_NONE].name;
251 read_linefile(r, "port2", tmp, true);
252 port2 = port_type::lookup(tmp, true).ptype;
253 read_linefile(r, "gamename", gamename, true);
254 read_linefile(r, "projectid", projectid);
255 rerecords = read_rrdata(r, projectid);
256 read_linefile(r, "coreversion", coreversion);
257 read_linefile(r, "rom.sha256", rom_sha256, true);
258 read_linefile(r, "romxml.sha256", romxml_sha256, true);
259 read_linefile(r, "slota.sha256", slota_sha256, true);
260 read_linefile(r, "slotaxml.sha256", slotaxml_sha256, true);
261 read_linefile(r, "slotb.sha256", slotb_sha256, true);
262 read_linefile(r, "slotbxml.sha256", slotbxml_sha256, true);
263 if(r.has_member("savestate")) {
264 is_savestate = true;
265 movie_state = read_raw_file(r, "moviestate");
266 if(r.has_member("hostmemory"))
267 host_memory = read_raw_file(r, "hostmemory");
268 savestate = read_raw_file(r, "savestate");
269 for(auto name = r.begin(); name != r.end(); ++name)
270 if((*name).length() >= 5 && (*name).substr(0, 5) == "sram.")
271 sram[(*name).substr(5)] = read_raw_file(r, *name);
272 screenshot = read_raw_file(r, "screenshot");
274 std::string name = r.find_first();
275 for(auto name = r.begin(); name != r.end(); ++name)
276 if((*name).length() >= 10 && (*name).substr(0, 10) == "moviesram.")
277 sram[(*name).substr(10)] = read_raw_file(r, *name);
278 read_authors_file(r, authors);
279 read_input(r, input, port1, port2, 0);
282 void moviefile::save(const std::string& movie, unsigned compression) throw(std::bad_alloc, std::runtime_error)
284 zip_writer w(movie, compression);
285 write_linefile(w, "gametype", gtype::tostring(gametype));
286 if(port1 != PT_GAMEPAD)
287 write_linefile(w, "port1", port_types[port1].name);
288 if(port2 != PT_NONE)
289 write_linefile(w, "port2", port_types[port2].name);
290 write_linefile(w, "gamename", gamename, true);
291 write_linefile(w, "systemid", "lsnes-rr1");
292 write_linefile(w, "controlsversion", "0");
293 coreversion = bsnes_core_version;
294 write_linefile(w, "coreversion", coreversion);
295 write_linefile(w, "projectid", projectid);
296 write_rrdata(w);
297 write_linefile(w, "rom.sha256", rom_sha256, true);
298 write_linefile(w, "romxml.sha256", romxml_sha256, true);
299 write_linefile(w, "slota.sha256", slota_sha256, true);
300 write_linefile(w, "slotaxml.sha256", slotaxml_sha256, true);
301 write_linefile(w, "slotb.sha256", slotb_sha256, true);
302 write_linefile(w, "slotbxml.sha256", slotbxml_sha256, true);
303 for(auto i = movie_sram.begin(); i != movie_sram.end(); ++i)
304 write_raw_file(w, "moviesram." + i->first, i->second);
305 if(is_savestate) {
306 write_raw_file(w, "moviestate", movie_state);
307 write_raw_file(w, "hostmemory", host_memory);
308 write_raw_file(w, "savestate", savestate);
309 write_raw_file(w, "screenshot", screenshot);
310 for(auto i = sram.begin(); i != sram.end(); ++i)
311 write_raw_file(w, "sram." + i->first, i->second);
313 write_authors_file(w, authors);
314 write_input(w, input, port1, port2);
316 w.commit();
319 uint64_t moviefile::get_frame_count() throw()
321 uint64_t frames = 0;
322 for(size_t i = 0; i < input.size(); i++) {
323 if(input[i](CONTROL_FRAME_SYNC))
324 frames++;
326 return frames;
329 namespace
331 const int BLOCK_SECONDS = 0;
332 const int BLOCK_FRAMES = 1;
333 const int STEP_W = 2;
334 const int STEP_N = 3;
336 uint64_t magic[2][4] = {
337 {178683, 10738636, 16639264, 596096},
338 {6448, 322445, 19997208, 266440}
342 uint64_t moviefile::get_movie_length() throw()
344 uint64_t frames = get_frame_count();
345 uint64_t* _magic = magic[(gametype == GT_SNES_PAL || gametype == GT_SGB_PAL) ? 1 : 0];
346 uint64_t t = _magic[BLOCK_SECONDS] * 1000000000ULL * (frames / _magic[BLOCK_FRAMES]);
347 frames %= _magic[BLOCK_FRAMES];
348 t += frames * _magic[STEP_W] + (frames * _magic[STEP_N] / _magic[BLOCK_FRAMES]);
349 return t;
352 gametype_t gametype_compose(rom_type type, rom_region region)
354 switch(type) {
355 case ROMTYPE_SNES:
356 return (region == REGION_PAL) ? GT_SNES_PAL : GT_SNES_NTSC;
357 case ROMTYPE_BSX:
358 return GT_BSX;
359 case ROMTYPE_BSXSLOTTED:
360 return GT_BSX_SLOTTED;
361 case ROMTYPE_SUFAMITURBO:
362 return GT_SUFAMITURBO;
363 case ROMTYPE_SGB:
364 return (region == REGION_PAL) ? GT_SGB_PAL : GT_SGB_NTSC;
365 default:
366 return GT_INVALID;
370 rom_region gametype_region(gametype_t type)
372 switch(type) {
373 case GT_SGB_PAL:
374 case GT_SNES_PAL:
375 return REGION_PAL;
376 default:
377 return REGION_NTSC;
381 rom_type gametype_romtype(gametype_t type)
383 switch(type) {
384 case GT_SNES_NTSC:
385 case GT_SNES_PAL:
386 return ROMTYPE_SNES;
387 case GT_BSX:
388 return ROMTYPE_BSX;
389 case GT_BSX_SLOTTED:
390 return ROMTYPE_BSXSLOTTED;
391 case GT_SUFAMITURBO:
392 return ROMTYPE_SUFAMITURBO;
393 case GT_SGB_PAL:
394 case GT_SGB_NTSC:
395 return ROMTYPE_SGB;
396 default:
397 return ROMTYPE_NONE;