Add lua functions to manipulate emulator settings
[lsnes.git] / moviefile.cpp
blob6895402d17922619cdaecdec94dea89f52455dd5
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 <sstream>
9 #include <boost/iostreams/copy.hpp>
10 #include <boost/iostreams/device/back_inserter.hpp>
12 void strip_CR(std::string& x) throw(std::bad_alloc)
14 if(x.length() > 0 && x[x.length() - 1] == '\r') {
15 if(x.length() > 1)
16 x = x.substr(0, x.length() - 1);
17 else
18 x = "";
22 void read_linefile(zip_reader& r, const std::string& member, std::string& out, bool conditional = false)
23 throw(std::bad_alloc, std::runtime_error)
25 if(conditional && !r.has_member(member))
26 return;
27 std::istream& m = r[member];
28 try {
29 std::getline(m, out);
30 strip_CR(out);
31 delete &m;
32 } catch(...) {
33 delete &m;
34 throw;
38 void write_linefile(zip_writer& w, const std::string& member, const std::string& value, bool conditional = false)
39 throw(std::bad_alloc, std::runtime_error)
41 if(conditional && value == "")
42 return;
43 std::ostream& m = w.create_file(member);
44 try {
45 m << value << std::endl;
46 w.close_file();
47 } catch(...) {
48 w.close_file();
49 throw;
53 void write_raw_file(zip_writer& w, const std::string& member, std::vector<char>& content) throw(std::bad_alloc,
54 std::runtime_error)
56 std::ostream& m = w.create_file(member);
57 try {
58 m.write(&content[0], content.size());
59 if(!m)
60 throw std::runtime_error("Can't write ZIP file member");
61 w.close_file();
62 } catch(...) {
63 w.close_file();
64 throw;
68 std::vector<char> read_raw_file(zip_reader& r, const std::string& member) throw(std::bad_alloc, std::runtime_error)
70 std::vector<char> out;
71 std::istream& m = r[member];
72 try {
73 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
74 boost::iostreams::copy(m, rd);
75 delete &m;
76 } catch(...) {
77 delete &m;
78 throw;
80 return out;
83 void read_authors_file(zip_reader& r, std::vector<std::pair<std::string, std::string>>& authors) throw(std::bad_alloc,
84 std::runtime_error)
86 std::istream& m = r["authors"];
87 try {
88 std::string x;
89 while(std::getline(m, x)) {
90 strip_CR(x);
91 fieldsplitter fields(x);
92 std::string y = static_cast<std::string>(fields);
93 std::string z = static_cast<std::string>(fields);
94 authors.push_back(std::make_pair(y, z));
96 delete &m;
97 } catch(...) {
98 delete &m;
99 throw;
103 std::string read_rrdata(zip_reader& r, const std::string& projectid) throw(std::bad_alloc, std::runtime_error)
105 std::istream& m = r["rrdata"];
106 uint64_t count;
107 try {
108 rrdata::read_base(projectid);
109 count = rrdata::read(m);
110 delete &m;
111 } catch(...) {
112 delete &m;
113 throw;
115 std::ostringstream x;
116 x << count;
117 return x.str();
120 void write_rrdata(zip_writer& w) throw(std::bad_alloc, std::runtime_error)
122 std::ostream& m = w.create_file("rrdata");
123 uint64_t count;
124 try {
125 count = rrdata::write(m);
126 if(!m)
127 throw std::runtime_error("Can't write ZIP file member");
128 w.close_file();
129 } catch(...) {
130 w.close_file();
131 throw;
133 std::ostream& m2 = w.create_file("rerecords");
134 try {
135 m2 << count << std::endl;
136 if(!m2)
137 throw std::runtime_error("Can't write ZIP file member");
138 w.close_file();
139 } catch(...) {
140 w.close_file();
141 throw;
145 void write_authors_file(zip_writer& w, std::vector<std::pair<std::string, std::string>>& authors)
146 throw(std::bad_alloc, std::runtime_error)
148 std::ostream& m = w.create_file("authors");
149 try {
150 for(auto i = authors.begin(); i != authors.end(); ++i)
151 if(i->second == "")
152 m << i->first << std::endl;
153 else
154 m << i->first << "|" << i->second << std::endl;
155 if(!m)
156 throw std::runtime_error("Can't write ZIP file member");
157 w.close_file();
158 } catch(...) {
159 w.close_file();
160 throw;
164 void write_input(zip_writer& w, std::vector<controls_t>& input, porttype_t port1, porttype_t port2)
165 throw(std::bad_alloc, std::runtime_error)
167 std::vector<cencode::fn_t> encoders;
168 encoders.push_back(port_types[port1].encoder);
169 encoders.push_back(port_types[port2].encoder);
170 std::ostream& m = w.create_file("input");
171 try {
172 for(auto i = input.begin(); i != input.end(); ++i)
173 m << i->tostring(encoders) << std::endl;
174 if(!m)
175 throw std::runtime_error("Can't write ZIP file member");
176 w.close_file();
177 } catch(...) {
178 w.close_file();
179 throw;
183 void read_input(zip_reader& r, std::vector<controls_t>& input, porttype_t port1, porttype_t port2, unsigned version)
184 throw(std::bad_alloc, std::runtime_error)
186 std::vector<cdecode::fn_t> decoders;
187 decoders.push_back(port_types[port1].decoder);
188 decoders.push_back(port_types[port2].decoder);
189 std::istream& m = r["input"];
190 try {
191 std::string x;
192 while(std::getline(m, x)) {
193 strip_CR(x);
194 if(x != "") {
195 input.push_back(controls_t(x, decoders, version));
198 delete &m;
199 } catch(...) {
200 delete &m;
201 throw;
206 porttype_t parse_controller_type(const std::string& type, bool port) throw(std::bad_alloc, std::runtime_error)
208 porttype_t port1 = PT_INVALID;
209 for(unsigned i = 0; i <= PT_LAST_CTYPE; i++)
210 if(type == port_types[i].name && (port || port_types[i].valid_port1))
211 port1 = static_cast<porttype_t>(i);
212 if(port1 == PT_INVALID)
213 throw std::runtime_error(std::string("Illegal port") + (port ? "2" : "1") + " device '" + type + "'");
214 return port1;
218 moviefile::moviefile() throw(std::bad_alloc)
220 gametype = GT_INVALID;
221 port1 = PT_GAMEPAD;
222 port2 = PT_NONE;
223 coreversion = bsnes_core_version;
224 projectid = get_random_hexstring(40);
225 rerecords = "0";
226 is_savestate = false;
229 moviefile::moviefile(const std::string& movie) throw(std::bad_alloc, std::runtime_error)
231 is_savestate = false;
232 std::string tmp;
233 zip_reader r(movie);
234 read_linefile(r, "systemid", tmp);
235 if(tmp.substr(0, 8) != "lsnes-rr")
236 throw std::runtime_error("Not lsnes movie");
237 read_linefile(r, "controlsversion", tmp);
238 if(tmp != "0")
239 throw std::runtime_error("Can't decode movie data");
240 read_linefile(r, "gametype", tmp);
241 try {
242 gametype = gtype::togametype(tmp);
243 } catch(std::bad_alloc& e) {
244 throw;
245 } catch(std::exception& e) {
246 throw std::runtime_error("Illegal game type '" + tmp + "'");
248 tmp = port_types[PT_GAMEPAD].name;
249 read_linefile(r, "port1", tmp, true);
250 port1 = port_type::lookup(tmp, false).ptype;
251 tmp = port_types[PT_NONE].name;
252 read_linefile(r, "port2", tmp, true);
253 port2 = port_type::lookup(tmp, true).ptype;
254 read_linefile(r, "gamename", gamename, true);
255 read_linefile(r, "projectid", projectid);
256 rerecords = read_rrdata(r, projectid);
257 read_linefile(r, "coreversion", coreversion);
258 read_linefile(r, "rom.sha256", rom_sha256, true);
259 read_linefile(r, "romxml.sha256", romxml_sha256, true);
260 read_linefile(r, "slota.sha256", slota_sha256, true);
261 read_linefile(r, "slotaxml.sha256", slotaxml_sha256, true);
262 read_linefile(r, "slotb.sha256", slotb_sha256, true);
263 read_linefile(r, "slotbxml.sha256", slotbxml_sha256, true);
264 if(r.has_member("savestate")) {
265 is_savestate = true;
266 movie_state = read_raw_file(r, "moviestate");
267 if(r.has_member("hostmemory"))
268 host_memory = read_raw_file(r, "hostmemory");
269 savestate = read_raw_file(r, "savestate");
270 for(auto name = r.begin(); name != r.end(); ++name)
271 if((*name).length() >= 5 && (*name).substr(0, 5) == "sram.")
272 sram[(*name).substr(5)] = read_raw_file(r, *name);
273 screenshot = read_raw_file(r, "screenshot");
275 std::string name = r.find_first();
276 for(auto name = r.begin(); name != r.end(); ++name)
277 if((*name).length() >= 10 && (*name).substr(0, 10) == "moviesram.")
278 sram[(*name).substr(10)] = read_raw_file(r, *name);
279 read_authors_file(r, authors);
280 read_input(r, input, port1, port2, 0);
283 void moviefile::save(const std::string& movie, unsigned compression) throw(std::bad_alloc, std::runtime_error)
285 zip_writer w(movie, compression);
286 write_linefile(w, "gametype", gtype::tostring(gametype));
287 if(port1 != PT_GAMEPAD)
288 write_linefile(w, "port1", port_types[port1].name);
289 if(port2 != PT_NONE)
290 write_linefile(w, "port2", port_types[port2].name);
291 write_linefile(w, "gamename", gamename, true);
292 write_linefile(w, "systemid", "lsnes-rr1");
293 write_linefile(w, "controlsversion", "0");
294 coreversion = bsnes_core_version;
295 write_linefile(w, "coreversion", coreversion);
296 write_linefile(w, "projectid", projectid);
297 write_rrdata(w);
298 write_linefile(w, "rom.sha256", rom_sha256, true);
299 write_linefile(w, "romxml.sha256", romxml_sha256, true);
300 write_linefile(w, "slota.sha256", slota_sha256, true);
301 write_linefile(w, "slotaxml.sha256", slotaxml_sha256, true);
302 write_linefile(w, "slotb.sha256", slotb_sha256, true);
303 write_linefile(w, "slotbxml.sha256", slotbxml_sha256, true);
304 for(auto i = movie_sram.begin(); i != movie_sram.end(); ++i)
305 write_raw_file(w, "moviesram." + i->first, i->second);
306 if(is_savestate) {
307 write_raw_file(w, "moviestate", movie_state);
308 write_raw_file(w, "hostmemory", host_memory);
309 write_raw_file(w, "savestate", savestate);
310 write_raw_file(w, "screenshot", screenshot);
311 for(auto i = sram.begin(); i != sram.end(); ++i)
312 write_raw_file(w, "sram." + i->first, i->second);
314 write_authors_file(w, authors);
315 write_input(w, input, port1, port2);
317 w.commit();
320 uint64_t moviefile::get_frame_count() throw()
322 uint64_t frames = 0;
323 for(size_t i = 0; i < input.size(); i++) {
324 if(input[i](CONTROL_FRAME_SYNC))
325 frames++;
327 return frames;
330 namespace
332 const int BLOCK_SECONDS = 0;
333 const int BLOCK_FRAMES = 1;
334 const int STEP_W = 2;
335 const int STEP_N = 3;
337 uint64_t magic[2][4] = {
338 {178683, 10738636, 16639264, 596096},
339 {6448, 322445, 19997208, 266440}
343 uint64_t moviefile::get_movie_length() throw()
345 uint64_t frames = get_frame_count();
346 uint64_t* _magic = magic[(gametype == GT_SNES_PAL || gametype == GT_SGB_PAL) ? 1 : 0];
347 uint64_t t = _magic[BLOCK_SECONDS] * 1000000000ULL * (frames / _magic[BLOCK_FRAMES]);
348 frames %= _magic[BLOCK_FRAMES];
349 t += frames * _magic[STEP_W] + (frames * _magic[STEP_N] / _magic[BLOCK_FRAMES]);
350 return t;
353 gametype_t gametype_compose(rom_type type, rom_region region)
355 switch(type) {
356 case ROMTYPE_SNES:
357 return (region == REGION_PAL) ? GT_SNES_PAL : GT_SNES_NTSC;
358 case ROMTYPE_BSX:
359 return GT_BSX;
360 case ROMTYPE_BSXSLOTTED:
361 return GT_BSX_SLOTTED;
362 case ROMTYPE_SUFAMITURBO:
363 return GT_SUFAMITURBO;
364 case ROMTYPE_SGB:
365 return (region == REGION_PAL) ? GT_SGB_PAL : GT_SGB_NTSC;
366 default:
367 return GT_INVALID;
371 rom_region gametype_region(gametype_t type)
373 switch(type) {
374 case GT_SGB_PAL:
375 case GT_SNES_PAL:
376 return REGION_PAL;
377 default:
378 return REGION_NTSC;
382 rom_type gametype_romtype(gametype_t type)
384 switch(type) {
385 case GT_SNES_NTSC:
386 case GT_SNES_PAL:
387 return ROMTYPE_SNES;
388 case GT_BSX:
389 return ROMTYPE_BSX;
390 case GT_BSX_SLOTTED:
391 return ROMTYPE_BSXSLOTTED;
392 case GT_SUFAMITURBO:
393 return ROMTYPE_SUFAMITURBO;
394 case GT_SGB_PAL:
395 case GT_SGB_NTSC:
396 return ROMTYPE_SGB;
397 default:
398 return ROMTYPE_NONE;