Fix zero luma corner case
[lsnes.git] / src / core / moviefile.cpp
blob2115fc06aee2bfd356e78803c88d27e2bb4524d2
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 void read_numeric_file(zip_reader& r, const std::string& member, int64_t& out, bool conditional = false)
45 throw(std::bad_alloc, std::runtime_error)
47 std::string _out;
48 read_linefile(r, member, _out, conditional);
49 if(conditional && _out == "")
50 return;
51 out = parse_value<int64_t>(_out);
54 void write_linefile(zip_writer& w, const std::string& member, const std::string& value, bool conditional = false)
55 throw(std::bad_alloc, std::runtime_error)
57 if(conditional && value == "")
58 return;
59 std::ostream& m = w.create_file(member);
60 try {
61 m << value << std::endl;
62 w.close_file();
63 } catch(...) {
64 w.close_file();
65 throw;
69 void write_numeric_file(zip_writer& w, const std::string& member, int64_t value) throw(std::bad_alloc,
70 std::runtime_error)
72 std::ostringstream x;
73 x << value;
74 write_linefile(w, member, x.str());
77 void write_raw_file(zip_writer& w, const std::string& member, std::vector<char>& content) throw(std::bad_alloc,
78 std::runtime_error)
80 std::ostream& m = w.create_file(member);
81 try {
82 m.write(&content[0], content.size());
83 if(!m)
84 throw std::runtime_error("Can't write ZIP file member");
85 w.close_file();
86 } catch(...) {
87 w.close_file();
88 throw;
92 std::vector<char> read_raw_file(zip_reader& r, const std::string& member) throw(std::bad_alloc, std::runtime_error)
94 std::vector<char> out;
95 std::istream& m = r[member];
96 try {
97 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
98 boost::iostreams::copy(m, rd);
99 delete &m;
100 } catch(...) {
101 delete &m;
102 throw;
104 return out;
107 void read_authors_file(zip_reader& r, std::vector<std::pair<std::string, std::string>>& authors) throw(std::bad_alloc,
108 std::runtime_error)
110 std::istream& m = r["authors"];
111 try {
112 std::string x;
113 while(std::getline(m, x)) {
114 strip_CR(x);
115 auto g = split_author(x);
116 authors.push_back(g);
118 delete &m;
119 } catch(...) {
120 delete &m;
121 throw;
125 std::string read_rrdata(zip_reader& r, std::vector<char>& out) throw(std::bad_alloc, std::runtime_error)
127 out = read_raw_file(r, "rrdata");
128 uint64_t count = rrdata::count(out);
129 std::ostringstream x;
130 x << count;
131 return x.str();
134 void write_rrdata(zip_writer& w) throw(std::bad_alloc, std::runtime_error)
136 uint64_t count;
137 std::vector<char> out;
138 count = rrdata::write(out);
139 write_raw_file(w, "rrdata", out);
140 std::ostream& m2 = w.create_file("rerecords");
141 try {
142 m2 << count << std::endl;
143 if(!m2)
144 throw std::runtime_error("Can't write ZIP file member");
145 w.close_file();
146 } catch(...) {
147 w.close_file();
148 throw;
152 void write_authors_file(zip_writer& w, std::vector<std::pair<std::string, std::string>>& authors)
153 throw(std::bad_alloc, std::runtime_error)
155 std::ostream& m = w.create_file("authors");
156 try {
157 for(auto i : authors)
158 if(i.second == "")
159 m << i.first << std::endl;
160 else
161 m << i.first << "|" << i.second << std::endl;
162 if(!m)
163 throw std::runtime_error("Can't write ZIP file member");
164 w.close_file();
165 } catch(...) {
166 w.close_file();
167 throw;
171 void write_input(zip_writer& w, std::vector<controls_t>& input, porttype_t port1, porttype_t port2)
172 throw(std::bad_alloc, std::runtime_error)
174 std::vector<cencode::fn_t> encoders;
175 encoders.push_back(port_types[port1].encoder);
176 encoders.push_back(port_types[port2].encoder);
177 std::ostream& m = w.create_file("input");
178 try {
179 for(auto i : input)
180 m << i.tostring(encoders) << std::endl;
181 if(!m)
182 throw std::runtime_error("Can't write ZIP file member");
183 w.close_file();
184 } catch(...) {
185 w.close_file();
186 throw;
190 void read_input(zip_reader& r, std::vector<controls_t>& input, porttype_t port1, porttype_t port2, unsigned version)
191 throw(std::bad_alloc, std::runtime_error)
193 std::vector<cdecode::fn_t> decoders;
194 decoders.push_back(port_types[port1].decoder);
195 decoders.push_back(port_types[port2].decoder);
196 std::istream& m = r["input"];
197 try {
198 std::string x;
199 while(std::getline(m, x)) {
200 strip_CR(x);
201 if(x != "") {
202 input.push_back(controls_t(x, decoders, version));
205 delete &m;
206 } catch(...) {
207 delete &m;
208 throw;
213 porttype_t parse_controller_type(const std::string& type, bool port) throw(std::bad_alloc, std::runtime_error)
215 porttype_t port1 = PT_INVALID;
216 for(unsigned i = 0; i <= PT_LAST_CTYPE; i++)
217 if(type == port_types[i].name && (port || port_types[i].valid_port1))
218 port1 = static_cast<porttype_t>(i);
219 if(port1 == PT_INVALID)
220 throw std::runtime_error(std::string("Illegal port") + (port ? "2" : "1") + " device '" + type + "'");
221 return port1;
225 moviefile::moviefile() throw(std::bad_alloc)
227 force_corrupt = false;
228 gametype = GT_INVALID;
229 port1 = PT_GAMEPAD;
230 port2 = PT_NONE;
231 coreversion = "";
232 projectid = "";
233 rerecords = "0";
234 is_savestate = false;
235 movie_rtc_second = rtc_second = DEFAULT_RTC_SECOND;
236 movie_rtc_subsecond = rtc_subsecond = DEFAULT_RTC_SUBSECOND;
239 moviefile::moviefile(const std::string& movie) throw(std::bad_alloc, std::runtime_error)
241 force_corrupt = false;
242 is_savestate = false;
243 std::string tmp;
244 zip_reader r(movie);
245 read_linefile(r, "systemid", tmp);
246 if(tmp.substr(0, 8) != "lsnes-rr")
247 throw std::runtime_error("Not lsnes movie");
248 read_linefile(r, "controlsversion", tmp);
249 if(tmp != "0")
250 throw std::runtime_error("Can't decode movie data");
251 read_linefile(r, "gametype", tmp);
252 try {
253 gametype = gtype::togametype(tmp);
254 } catch(std::bad_alloc& e) {
255 throw;
256 } catch(std::exception& e) {
257 throw std::runtime_error("Illegal game type '" + tmp + "'");
259 tmp = port_types[PT_GAMEPAD].name;
260 read_linefile(r, "port1", tmp, true);
261 port1 = port_type::lookup(tmp, false).ptype;
262 tmp = port_types[PT_NONE].name;
263 read_linefile(r, "port2", tmp, true);
264 port2 = port_type::lookup(tmp, true).ptype;
265 read_linefile(r, "gamename", gamename, true);
266 read_linefile(r, "projectid", projectid);
267 rerecords = read_rrdata(r, c_rrdata);
268 read_linefile(r, "coreversion", coreversion);
269 read_linefile(r, "rom.sha256", rom_sha256, true);
270 read_linefile(r, "romxml.sha256", romxml_sha256, true);
271 read_linefile(r, "slota.sha256", slota_sha256, true);
272 read_linefile(r, "slotaxml.sha256", slotaxml_sha256, true);
273 read_linefile(r, "slotb.sha256", slotb_sha256, true);
274 read_linefile(r, "slotbxml.sha256", slotbxml_sha256, true);
275 movie_rtc_second = DEFAULT_RTC_SECOND;
276 movie_rtc_subsecond = DEFAULT_RTC_SUBSECOND;
277 read_numeric_file(r, "starttime.second", movie_rtc_second, true);
278 read_numeric_file(r, "starttime.subsecond", movie_rtc_subsecond, true);
279 rtc_second = movie_rtc_second;
280 rtc_subsecond = movie_rtc_subsecond;
281 if(r.has_member("savestate")) {
282 is_savestate = true;
283 movie_state = read_raw_file(r, "moviestate");
284 if(r.has_member("hostmemory"))
285 host_memory = read_raw_file(r, "hostmemory");
286 savestate = read_raw_file(r, "savestate");
287 for(auto name : r)
288 if(name.length() >= 5 && name.substr(0, 5) == "sram.")
289 sram[name.substr(5)] = read_raw_file(r, name);
290 screenshot = read_raw_file(r, "screenshot");
291 //If these can't be read, just use some (wrong) values.
292 read_numeric_file(r, "savetime.second", rtc_second, true);
293 read_numeric_file(r, "savetime.subsecond", rtc_subsecond, true);
295 if(rtc_subsecond < 0 || movie_rtc_subsecond < 0)
296 throw std::runtime_error("Invalid RTC subsecond value");
297 std::string name = r.find_first();
298 for(auto name : r)
299 if(name.length() >= 10 && name.substr(0, 10) == "moviesram.")
300 movie_sram[name.substr(10)] = read_raw_file(r, name);
301 read_authors_file(r, authors);
302 read_input(r, input, port1, port2, 0);
305 void moviefile::save(const std::string& movie, unsigned compression) throw(std::bad_alloc, std::runtime_error)
307 zip_writer w(movie, compression);
308 write_linefile(w, "gametype", gtype::tostring(gametype));
309 if(port1 != PT_GAMEPAD)
310 write_linefile(w, "port1", port_types[port1].name);
311 if(port2 != PT_NONE)
312 write_linefile(w, "port2", port_types[port2].name);
313 write_linefile(w, "gamename", gamename, true);
314 write_linefile(w, "systemid", "lsnes-rr1");
315 write_linefile(w, "controlsversion", "0");
316 coreversion = bsnes_core_version;
317 write_linefile(w, "coreversion", coreversion);
318 write_linefile(w, "projectid", projectid);
319 write_rrdata(w);
320 write_linefile(w, "rom.sha256", rom_sha256, true);
321 write_linefile(w, "romxml.sha256", romxml_sha256, true);
322 write_linefile(w, "slota.sha256", slota_sha256, true);
323 write_linefile(w, "slotaxml.sha256", slotaxml_sha256, true);
324 write_linefile(w, "slotb.sha256", slotb_sha256, true);
325 write_linefile(w, "slotbxml.sha256", slotbxml_sha256, true);
326 for(auto i : movie_sram)
327 write_raw_file(w, "moviesram." + i.first, i.second);
328 write_numeric_file(w, "starttime.second", movie_rtc_second);
329 write_numeric_file(w, "starttime.subsecond", movie_rtc_subsecond);
330 if(is_savestate) {
331 write_raw_file(w, "moviestate", movie_state);
332 write_raw_file(w, "hostmemory", host_memory);
333 write_raw_file(w, "savestate", savestate);
334 write_raw_file(w, "screenshot", screenshot);
335 for(auto i : sram)
336 write_raw_file(w, "sram." + i.first, i.second);
337 write_numeric_file(w, "savetime.second", rtc_second);
338 write_numeric_file(w, "savetime.subsecond", rtc_subsecond);
340 write_authors_file(w, authors);
341 write_input(w, input, port1, port2);
343 w.commit();
346 uint64_t moviefile::get_frame_count() throw()
348 uint64_t frames = 0;
349 for(size_t i = 0; i < input.size(); i++) {
350 if(input[i](CONTROL_FRAME_SYNC))
351 frames++;
353 return frames;
356 namespace
358 const int BLOCK_SECONDS = 0;
359 const int BLOCK_FRAMES = 1;
360 const int STEP_W = 2;
361 const int STEP_N = 3;
363 uint64_t magic[2][4] = {
364 {178683, 10738636, 16639264, 596096},
365 {6448, 322445, 19997208, 266440}
369 uint64_t moviefile::get_movie_length(uint64_t framebias) throw()
371 uint64_t frames = get_frame_count();
372 if(frames > framebias)
373 frames -= framebias;
374 else
375 frames = 0;
376 uint64_t* _magic = magic[(gametype == GT_SNES_PAL || gametype == GT_SGB_PAL) ? 1 : 0];
377 uint64_t t = _magic[BLOCK_SECONDS] * 1000000000ULL * (frames / _magic[BLOCK_FRAMES]);
378 frames %= _magic[BLOCK_FRAMES];
379 t += frames * _magic[STEP_W] + (frames * _magic[STEP_N] / _magic[BLOCK_FRAMES]);
380 return t;
383 gametype_t gametype_compose(rom_type type, rom_region region)
385 switch(type) {
386 case ROMTYPE_SNES:
387 return (region == REGION_PAL) ? GT_SNES_PAL : GT_SNES_NTSC;
388 case ROMTYPE_BSX:
389 return GT_BSX;
390 case ROMTYPE_BSXSLOTTED:
391 return GT_BSX_SLOTTED;
392 case ROMTYPE_SUFAMITURBO:
393 return GT_SUFAMITURBO;
394 case ROMTYPE_SGB:
395 return (region == REGION_PAL) ? GT_SGB_PAL : GT_SGB_NTSC;
396 default:
397 return GT_INVALID;
401 rom_region gametype_region(gametype_t type)
403 switch(type) {
404 case GT_SGB_PAL:
405 case GT_SNES_PAL:
406 return REGION_PAL;
407 default:
408 return REGION_NTSC;
412 rom_type gametype_romtype(gametype_t type)
414 switch(type) {
415 case GT_SNES_NTSC:
416 case GT_SNES_PAL:
417 return ROMTYPE_SNES;
418 case GT_BSX:
419 return ROMTYPE_BSX;
420 case GT_BSX_SLOTTED:
421 return ROMTYPE_BSXSLOTTED;
422 case GT_SUFAMITURBO:
423 return ROMTYPE_SUFAMITURBO;
424 case GT_SGB_PAL:
425 case GT_SGB_NTSC:
426 return ROMTYPE_SGB;
427 default:
428 return ROMTYPE_NONE;