lsnes rr2-β24
[lsnes.git] / src / core / moviefile.cpp
blobf27f217eb85ebf0b6097ff17c5538bed9035d48d
1 #include "core/moviefile-common.hpp"
2 #include "core/moviefile.hpp"
3 #include "core/random.hpp"
4 #include "core/rom.hpp"
5 #include "library/binarystream.hpp"
6 #include "library/directory.hpp"
7 #include "library/minmax.hpp"
8 #include "library/serialization.hpp"
9 #include "library/string.hpp"
10 #include "library/zip.hpp"
12 #include <fcntl.h>
13 #include <unistd.h>
14 #include <iostream>
15 #include <algorithm>
16 #include <sstream>
17 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
18 #include <windows.h>
19 //FUCK YOU. SERIOUSLY.
20 #define EXTRA_OPENFLAGS O_BINARY
21 #else
22 #define EXTRA_OPENFLAGS 0
23 #endif
25 //Damn Windows.
26 #ifndef EWOULDBLOCK
27 #define EWOULDBLOCK EAGAIN
28 #endif
30 namespace
32 const char* movie_file_id = "Movie files";
33 std::map<std::string, moviefile*> memory_saves;
35 bool check_binary_magic(int s)
37 char buf[6] = {0};
38 int x = 0;
39 while(x < 5) {
40 int r = read(s, buf + x, 5 - x);
41 if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
42 continue;
43 if(r <= 0) //0 => EOF, break on that too.
44 return false;
45 x += r;
47 return !strcmp(buf, "lsmv\x1A");
50 void write_whole(int s, const char* buf, size_t size)
52 size_t w = 0;
53 while(w < size) {
54 int maxw = 32767;
55 if((size_t)maxw > (size - w))
56 maxw = size - w;
57 int r = write(s, buf + w, maxw);
58 if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
59 continue;
60 if(r < 0) {
61 int err = errno;
62 (stringfmt() << strerror(err)).throwex();
64 w += r;
69 moviefile::brief_info::brief_info(const std::string& filename)
71 regex_results rr;
72 if(rr = regex("\\$MEMORY:(.*)", filename)) {
73 if(!memory_saves.count(rr[1]) && memory_saves[rr[1]])
74 throw std::runtime_error("No such memory save");
75 moviefile& mv = *memory_saves[rr[1]];
76 sysregion = mv.gametype->get_name();
77 corename = mv.coreversion;
78 projectid = mv.projectid;
79 current_frame = mv.dyn.save_frame;
80 rerecords = mv.rerecords_mem;
81 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
82 hash[i] = mv.romimg_sha256[i];
83 hashxml[i] = mv.romxml_sha256[i];
84 hint[i] = mv.namehint[i];
86 return;
89 int s = open(filename.c_str(), O_RDONLY | EXTRA_OPENFLAGS);
90 if(s < 0) {
91 int err = errno;
92 (stringfmt() << "Can't read file '" << filename << "': " << strerror(err)).throwex();
94 if(check_binary_magic(s)) {
95 try { binary_io(s); } catch(...) { close(s); throw; }
96 close(s);
97 return;
99 close(s);
101 zip::reader r(filename);
102 load(r);
105 moviefile::moviefile() throw(std::bad_alloc)
106 : tracker(memtracker::singleton(), movie_file_id, sizeof(*this))
108 force_corrupt = false;
109 gametype = NULL;
110 input = NULL;
111 coreversion = "";
112 projectid = "";
113 rerecords = "0";
114 movie_rtc_second = dyn.rtc_second = DEFAULT_RTC_SECOND;
115 movie_rtc_subsecond = dyn.rtc_subsecond = DEFAULT_RTC_SUBSECOND;
116 start_paused = false;
117 lazy_project_create = true;
118 this->filename = "";
121 moviefile::moviefile(loaded_rom& rom, std::map<std::string, std::string>& c_settings, uint64_t rtc_sec,
122 uint64_t rtc_subsec)
123 : tracker(memtracker::singleton(), movie_file_id, sizeof(*this))
125 force_corrupt = false;
126 gametype = &rom.get_sysregion();
127 coreversion = rom.get_core_identifier();
128 projectid = get_random_hexstring(40);
129 rerecords = "0";
130 movie_rtc_second = dyn.rtc_second = rtc_sec;
131 movie_rtc_subsecond = dyn.rtc_subsecond = rtc_subsec;
132 start_paused = false;
133 lazy_project_create = true;
134 this->filename = "";
135 settings = c_settings;
136 input = NULL;
137 auto ctrldata = rom.controllerconfig(settings);
138 portctrl::type_set& ports = portctrl::type_set::make(ctrldata.ports, ctrldata.portindex());
139 create_default_branch(ports);
140 if(!rom.isnull()) {
141 //Initialize the remainder.
142 rerecords = "0";
143 for(size_t i = 0; i < ROM_SLOT_COUNT; i++) {
144 auto& img = rom.get_rom(i);
145 auto& xml = rom.get_markup(i);
146 romimg_sha256[i] = img.sha_256.read();
147 romxml_sha256[i] = xml.sha_256.read();
148 namehint[i] = img.namehint;
153 moviefile::moviefile(const std::string& movie, core_type& romtype) throw(std::bad_alloc, std::runtime_error)
154 : tracker(memtracker::singleton(), movie_file_id, sizeof(*this))
156 regex_results rr;
157 if(rr = regex("\\$MEMORY:(.*)", movie)) {
158 if(!memory_saves.count(rr[1]) || !memory_saves[rr[1]])
159 throw std::runtime_error("No such memory save");
160 moviefile& s = *memory_saves[rr[1]];
161 copy_fields(s);
162 return;
164 this->filename = movie;
165 input = NULL;
166 start_paused = false;
167 force_corrupt = false;
168 lazy_project_create = false;
170 int s = open(movie.c_str(), O_RDONLY | EXTRA_OPENFLAGS);
171 if(s < 0) {
172 int err = errno;
173 (stringfmt() << "Can't read file '" << movie << "': " << strerror(err)).throwex();
175 if(check_binary_magic(s)) {
176 try { binary_io(s, romtype); } catch(...) { close(s); throw; }
177 close(s);
178 return;
180 close(s);
182 zip::reader r(movie);
183 load(r, romtype);
186 void moviefile::fixup_current_branch(const moviefile& mv)
188 input = NULL;
189 for(auto& i : mv.branches)
190 if(&i.second == mv.input)
191 input = &branches[i.first];
194 void moviefile::save(const std::string& movie, unsigned compression, bool binary, rrdata_set& rrd, bool as_state)
195 throw(std::bad_alloc, std::runtime_error)
197 regex_results rr;
198 if(rr = regex("\\$MEMORY:(.*)", movie)) {
199 auto tmp = new moviefile();
200 try {
201 tmp->copy_fields(*this);
202 memory_saves[rr[1]] = tmp;
203 } catch(...) {
204 delete tmp;
205 throw;
207 return;
209 if(binary) {
210 std::string tmp = movie + ".tmp";
211 int strm = open(tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC | EXTRA_OPENFLAGS, 0644);
212 if(strm < 0) {
213 int err = errno;
214 (stringfmt() << "Failed to open '" << tmp << "': " << strerror(err)).throwex();
216 try {
217 char buf[5] = {'l', 's', 'm', 'v', 0x1A};
218 write_whole(strm, buf, 5);
219 binary_io(strm, rrd, as_state);
220 } catch(std::exception& e) {
221 close(strm);
222 (stringfmt() << "Failed to write '" << tmp << "': " << e.what()).throwex();
224 if(close(strm) < 0) {
225 int err = errno;
226 (stringfmt() << "Failed to write '" << tmp << "': " << strerror(err)).throwex();
228 std::string backup = movie + ".backup";
229 directory::rename_overwrite(movie.c_str(), backup.c_str());
230 if(directory::rename_overwrite(tmp.c_str(), movie.c_str()) < 0)
231 throw std::runtime_error("Can't rename '" + tmp + "' -> '" + movie + "'");
232 return;
234 zip::writer w(movie, compression);
235 save(w, rrd, as_state);
238 void moviefile::save(std::ostream& stream, rrdata_set& rrd, bool as_state) throw(std::bad_alloc, std::runtime_error)
240 zip::writer w(stream, 0);
241 save(w, rrd, as_state);
244 void moviefile::create_default_branch(portctrl::type_set& ports)
246 if(input)
247 return;
248 //If there is a branch, it becomes default.
249 if(!branches.empty()) {
250 input = &(branches.begin()->second);
251 } else {
252 //Otherwise, just create a branch.
253 branches[""].clear(ports);
254 input = &branches[""];
258 uint64_t moviefile::get_frame_count() throw()
260 return input->count_frames();
263 namespace
265 const int BLOCK_SECONDS = 0;
266 const int BLOCK_FRAMES = 1;
267 const int STEP_W = 2;
268 const int STEP_N = 3;
271 uint64_t moviefile::get_movie_length() throw()
273 uint64_t frames = get_frame_count();
274 if(!gametype) {
275 return (100ULL * frames + 3) / 6;
277 uint64_t _magic[4];
278 gametype->fill_framerate_magic(_magic);
279 uint64_t t = _magic[BLOCK_SECONDS] * 1000ULL * (frames / _magic[BLOCK_FRAMES]);
280 frames %= _magic[BLOCK_FRAMES];
281 t += frames * _magic[STEP_W] + ((frames * _magic[STEP_N] + _magic[BLOCK_FRAMES] - 1) / _magic[BLOCK_FRAMES]);
282 return t;
285 moviefile*& moviefile::memref(const std::string& slot)
287 return memory_saves[slot];
290 void moviefile::copy_fields(const moviefile& mv)
292 force_corrupt = mv.force_corrupt;
293 gametype = mv.gametype;
294 settings = mv.settings;
295 coreversion = mv.coreversion;
296 gamename = mv.gamename;
297 projectid = mv.projectid;
298 rerecords = mv.rerecords;
299 rerecords_mem = mv.rerecords_mem;
300 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
301 romimg_sha256[i] = mv.romimg_sha256[i];
302 romxml_sha256[i] = mv.romxml_sha256[i];
303 namehint[i] = mv.namehint[i];
305 authors = mv.authors;
306 movie_sram = mv.movie_sram;
307 ramcontent = mv.ramcontent;
308 anchor_savestate = mv.anchor_savestate;
309 c_rrdata = mv.c_rrdata;
310 branches = mv.branches;
312 //Copy the active branch.
313 input = &branches.begin()->second;
314 for(auto& i : branches)
315 if(mv.branches.count(i.first) && &mv.branches.find(i.first)->second == mv.input)
316 input = &i.second;
318 movie_rtc_second = mv.movie_rtc_second;
319 movie_rtc_subsecond = mv.movie_rtc_subsecond;
320 start_paused = mv.start_paused;
321 lazy_project_create = mv.lazy_project_create;
322 subtitles = mv.subtitles;
323 dyn = mv.dyn;
326 void moviefile::fork_branch(const std::string& oldname, const std::string& newname)
328 if(oldname == newname || branches.count(newname))
329 return;
330 branches[newname] = branches[oldname];
333 const std::string& moviefile::current_branch()
335 for(auto& i : branches)
336 if(&i.second == input)
337 return i.first;
338 static std::string blank_string;
339 return blank_string;
342 bool moviefile::is_movie_or_savestate(const std::string& filename)
344 try {
345 int s = open(filename.c_str(), O_RDONLY | EXTRA_OPENFLAGS);
346 if(s < 0) {
347 //Can't open.
348 return false;
350 bool is_binary = check_binary_magic(s);
351 close(s);
352 if(is_binary)
353 return true;
354 //It is not binary, might be text.
355 std::string tmp;
356 zip::reader r(filename);
357 r.read_linefile("systemid", tmp);
358 if(tmp.substr(0, 8) != "lsnes-rr")
359 return false;
360 return true;
361 } catch(...) {
362 return false;
366 moviefile::branch_extractor::~branch_extractor()
368 delete real;
371 moviefile::branch_extractor::branch_extractor(const std::string& filename)
373 bool binary = false;
375 std::istream& s = zip::openrel(filename, "");
376 char buf[6] = {0};
377 s.read(buf, 5);
378 if(!strcmp(buf, "lsmv\x1A"))
379 binary = true;
380 delete &s;
382 if(binary)
383 real = new moviefile_branch_extractor_binary(filename);
384 else
385 real = new moviefile_branch_extractor_text(filename);
388 moviefile::sram_extractor::~sram_extractor()
390 delete real;
393 moviefile::sram_extractor::sram_extractor(const std::string& filename)
395 bool binary = false;
397 std::istream& s = zip::openrel(filename, "");
398 char buf[6] = {0};
399 s.read(buf, 5);
400 if(!strcmp(buf, "lsmv\x1A"))
401 binary = true;
402 delete &s;
404 if(binary)
405 real = new moviefile_sram_extractor_binary(filename);
406 else
407 real = new moviefile_sram_extractor_text(filename);
410 void moviefile::clear_dynstate()
412 dyn.clear(movie_rtc_second, movie_rtc_subsecond, movie_sram);
415 dynamic_state::dynamic_state()
417 save_frame = 0;
418 lagged_frames = 0;
419 poll_flag = 0;
420 rtc_second = DEFAULT_RTC_SECOND;
421 rtc_subsecond = DEFAULT_RTC_SUBSECOND;
424 void dynamic_state::clear(int64_t sec, int64_t ssec, const std::map<std::string, std::vector<char>>& initsram)
426 sram = initsram;
427 savestate.clear();
428 host_memory.clear();
429 screenshot.clear();
430 save_frame = 0;
431 lagged_frames = 0;
432 for(auto& i : pollcounters)
433 i = 0;
434 poll_flag = 0;
435 rtc_second = sec;
436 rtc_subsecond = ssec;
437 active_macros.clear();
440 void dynamic_state::swap(dynamic_state& s) throw()
442 std::swap(sram, s.sram);
443 std::swap(savestate, s.savestate);
444 std::swap(host_memory, s.host_memory);
445 std::swap(screenshot, s.screenshot);
446 std::swap(save_frame, s.save_frame);
447 std::swap(lagged_frames, s.lagged_frames);
448 std::swap(pollcounters, s.pollcounters);
449 std::swap(poll_flag, s.poll_flag);
450 std::swap(rtc_second, s.rtc_second);
451 std::swap(rtc_subsecond, s.rtc_subsecond);
452 std::swap(active_macros, s.active_macros);