Use master state for trampolines
[lsnes.git] / src / core / moviefile.cpp
blob9ebf0bbb521a8db69676fc590b2ae643c529eab2
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 std::map<std::string, moviefile*> memory_saves;
34 bool check_binary_magic(int s)
36 char buf[6] = {0};
37 int x = 0;
38 while(x < 5) {
39 int r = read(s, buf + x, 5 - x);
40 if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
41 continue;
42 if(r <= 0) //0 => EOF, break on that too.
43 return false;
44 x += r;
46 return !strcmp(buf, "lsmv\x1A");
49 void write_whole(int s, const char* buf, size_t size)
51 size_t w = 0;
52 while(w < size) {
53 int maxw = 32767;
54 if((size_t)maxw > (size - w))
55 maxw = size - w;
56 int r = write(s, buf + w, maxw);
57 if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
58 continue;
59 if(r < 0) {
60 int err = errno;
61 (stringfmt() << strerror(err)).throwex();
63 w += r;
68 moviefile::brief_info::brief_info(const std::string& filename)
70 regex_results rr;
71 if(rr = regex("\\$MEMORY:(.*)", filename)) {
72 if(!memory_saves.count(rr[1]) && memory_saves[rr[1]])
73 throw std::runtime_error("No such memory save");
74 moviefile& mv = *memory_saves[rr[1]];
75 sysregion = mv.gametype->get_name();
76 corename = mv.coreversion;
77 projectid = mv.projectid;
78 current_frame = mv.dyn.save_frame;
79 rerecords = mv.rerecords_mem;
80 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
81 hash[i] = mv.romimg_sha256[i];
82 hashxml[i] = mv.romxml_sha256[i];
83 hint[i] = mv.namehint[i];
85 return;
88 int s = open(filename.c_str(), O_RDONLY | EXTRA_OPENFLAGS);
89 if(s < 0) {
90 int err = errno;
91 (stringfmt() << "Can't read file '" << filename << "': " << strerror(err)).throwex();
93 if(check_binary_magic(s)) {
94 try { binary_io(s); } catch(...) { close(s); throw; }
95 close(s);
96 return;
98 close(s);
100 zip::reader r(filename);
101 load(r);
104 moviefile::moviefile() throw(std::bad_alloc)
106 force_corrupt = false;
107 gametype = NULL;
108 input = NULL;
109 coreversion = "";
110 projectid = "";
111 rerecords = "0";
112 movie_rtc_second = dyn.rtc_second = DEFAULT_RTC_SECOND;
113 movie_rtc_subsecond = dyn.rtc_subsecond = DEFAULT_RTC_SUBSECOND;
114 start_paused = false;
115 lazy_project_create = true;
118 moviefile::moviefile(loaded_rom& rom, std::map<std::string, std::string>& c_settings, uint64_t rtc_sec,
119 uint64_t rtc_subsec)
121 force_corrupt = false;
122 gametype = &rom.get_sysregion();
123 coreversion = rom.get_core_identifier();
124 projectid = get_random_hexstring(40);
125 rerecords = "0";
126 movie_rtc_second = dyn.rtc_second = rtc_sec;
127 movie_rtc_subsecond = dyn.rtc_subsecond = rtc_subsec;
128 start_paused = false;
129 lazy_project_create = true;
130 settings = c_settings;
131 input = NULL;
132 auto ctrldata = rom.controllerconfig(settings);
133 portctrl::type_set& ports = portctrl::type_set::make(ctrldata.ports, ctrldata.portindex());
134 create_default_branch(ports);
135 if(!rom.isnull()) {
136 //Initialize the remainder.
137 rerecords = "0";
138 for(size_t i = 0; i < ROM_SLOT_COUNT; i++) {
139 auto& img = rom.get_rom(i);
140 auto& xml = rom.get_markup(i);
141 romimg_sha256[i] = img.sha_256.read();
142 romxml_sha256[i] = xml.sha_256.read();
143 namehint[i] = img.namehint;
148 moviefile::moviefile(const std::string& movie, core_type& romtype) throw(std::bad_alloc, std::runtime_error)
150 regex_results rr;
151 if(rr = regex("\\$MEMORY:(.*)", movie)) {
152 if(!memory_saves.count(rr[1]) || !memory_saves[rr[1]])
153 throw std::runtime_error("No such memory save");
154 moviefile& s = *memory_saves[rr[1]];
155 copy_fields(s);
156 return;
158 input = NULL;
159 start_paused = false;
160 force_corrupt = false;
161 lazy_project_create = false;
163 int s = open(movie.c_str(), O_RDONLY | EXTRA_OPENFLAGS);
164 if(s < 0) {
165 int err = errno;
166 (stringfmt() << "Can't read file '" << movie << "': " << strerror(err)).throwex();
168 if(check_binary_magic(s)) {
169 try { binary_io(s, romtype); } catch(...) { close(s); throw; }
170 close(s);
171 return;
173 close(s);
175 zip::reader r(movie);
176 load(r, romtype);
179 void moviefile::fixup_current_branch(const moviefile& mv)
181 input = NULL;
182 for(auto& i : mv.branches)
183 if(&i.second == mv.input)
184 input = &branches[i.first];
187 void moviefile::save(const std::string& movie, unsigned compression, bool binary, rrdata_set& rrd, bool as_state)
188 throw(std::bad_alloc, std::runtime_error)
190 regex_results rr;
191 if(rr = regex("\\$MEMORY:(.*)", movie)) {
192 auto tmp = new moviefile();
193 try {
194 tmp->copy_fields(*this);
195 memory_saves[rr[1]] = tmp;
196 } catch(...) {
197 delete tmp;
198 throw;
200 return;
202 if(binary) {
203 std::string tmp = movie + ".tmp";
204 int strm = open(tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC | EXTRA_OPENFLAGS, 0644);
205 if(strm < 0) {
206 int err = errno;
207 (stringfmt() << "Failed to open '" << tmp << "': " << strerror(err)).throwex();
209 try {
210 char buf[5] = {'l', 's', 'm', 'v', 0x1A};
211 write_whole(strm, buf, 5);
212 binary_io(strm, rrd, as_state);
213 } catch(std::exception& e) {
214 close(strm);
215 (stringfmt() << "Failed to write '" << tmp << "': " << e.what()).throwex();
217 if(close(strm) < 0) {
218 int err = errno;
219 (stringfmt() << "Failed to write '" << tmp << "': " << strerror(err)).throwex();
221 std::string backup = movie + ".backup";
222 directory::rename_overwrite(movie.c_str(), backup.c_str());
223 if(directory::rename_overwrite(tmp.c_str(), movie.c_str()) < 0)
224 throw std::runtime_error("Can't rename '" + tmp + "' -> '" + movie + "'");
225 return;
227 zip::writer w(movie, compression);
228 save(w, rrd, as_state);
231 void moviefile::save(std::ostream& stream, rrdata_set& rrd, bool as_state) throw(std::bad_alloc, std::runtime_error)
233 zip::writer w(stream, 0);
234 save(w, rrd, as_state);
237 void moviefile::create_default_branch(portctrl::type_set& ports)
239 if(input)
240 return;
241 //If there is a branch, it becomes default.
242 if(!branches.empty()) {
243 input = &(branches.begin()->second);
244 } else {
245 //Otherwise, just create a branch.
246 branches[""].clear(ports);
247 input = &branches[""];
251 uint64_t moviefile::get_frame_count() throw()
253 return input->count_frames();
256 namespace
258 const int BLOCK_SECONDS = 0;
259 const int BLOCK_FRAMES = 1;
260 const int STEP_W = 2;
261 const int STEP_N = 3;
264 uint64_t moviefile::get_movie_length() throw()
266 uint64_t frames = get_frame_count();
267 if(!gametype) {
268 return (100ULL * frames + 3) / 6;
270 uint64_t _magic[4];
271 gametype->fill_framerate_magic(_magic);
272 uint64_t t = _magic[BLOCK_SECONDS] * 1000ULL * (frames / _magic[BLOCK_FRAMES]);
273 frames %= _magic[BLOCK_FRAMES];
274 t += frames * _magic[STEP_W] + ((frames * _magic[STEP_N] + _magic[BLOCK_FRAMES] - 1) / _magic[BLOCK_FRAMES]);
275 return t;
278 moviefile*& moviefile::memref(const std::string& slot)
280 return memory_saves[slot];
283 void moviefile::copy_fields(const moviefile& mv)
285 force_corrupt = mv.force_corrupt;
286 gametype = mv.gametype;
287 settings = mv.settings;
288 coreversion = mv.coreversion;
289 gamename = mv.gamename;
290 projectid = mv.projectid;
291 rerecords = mv.rerecords;
292 rerecords_mem = mv.rerecords_mem;
293 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
294 romimg_sha256[i] = mv.romimg_sha256[i];
295 romxml_sha256[i] = mv.romxml_sha256[i];
296 namehint[i] = mv.namehint[i];
298 authors = mv.authors;
299 movie_sram = mv.movie_sram;
300 ramcontent = mv.ramcontent;
301 anchor_savestate = mv.anchor_savestate;
302 c_rrdata = mv.c_rrdata;
303 branches = mv.branches;
305 //Copy the active branch.
306 input = &branches.begin()->second;
307 for(auto& i : branches)
308 if(mv.branches.count(i.first) && &mv.branches.find(i.first)->second == mv.input)
309 input = &i.second;
311 movie_rtc_second = mv.movie_rtc_second;
312 movie_rtc_subsecond = mv.movie_rtc_subsecond;
313 start_paused = mv.start_paused;
314 lazy_project_create = mv.lazy_project_create;
315 subtitles = mv.subtitles;
316 dyn = mv.dyn;
319 void moviefile::fork_branch(const std::string& oldname, const std::string& newname)
321 if(oldname == newname || branches.count(newname))
322 return;
323 branches[newname] = branches[oldname];
326 const std::string& moviefile::current_branch()
328 for(auto& i : branches)
329 if(&i.second == input)
330 return i.first;
331 static std::string blank_string;
332 return blank_string;
335 bool moviefile::is_movie_or_savestate(const std::string& filename)
337 try {
338 int s = open(filename.c_str(), O_RDONLY | EXTRA_OPENFLAGS);
339 if(s < 0) {
340 //Can't open.
341 return false;
343 bool is_binary = check_binary_magic(s);
344 close(s);
345 if(is_binary)
346 return true;
347 //It is not binary, might be text.
348 std::string tmp;
349 zip::reader r(filename);
350 r.read_linefile("systemid", tmp);
351 if(tmp.substr(0, 8) != "lsnes-rr")
352 return false;
353 return true;
354 } catch(...) {
355 return false;
359 moviefile::branch_extractor::~branch_extractor()
361 delete real;
364 moviefile::branch_extractor::branch_extractor(const std::string& filename)
366 bool binary = false;
368 std::istream& s = zip::openrel(filename, "");
369 char buf[6] = {0};
370 s.read(buf, 5);
371 if(!strcmp(buf, "lsmv\x1A"))
372 binary = true;
373 delete &s;
375 if(binary)
376 real = new moviefile_branch_extractor_binary(filename);
377 else
378 real = new moviefile_branch_extractor_text(filename);
381 moviefile::sram_extractor::~sram_extractor()
383 delete real;
386 moviefile::sram_extractor::sram_extractor(const std::string& filename)
388 bool binary = false;
390 std::istream& s = zip::openrel(filename, "");
391 char buf[6] = {0};
392 s.read(buf, 5);
393 if(!strcmp(buf, "lsmv\x1A"))
394 binary = true;
395 delete &s;
397 if(binary)
398 real = new moviefile_sram_extractor_binary(filename);
399 else
400 real = new moviefile_sram_extractor_text(filename);
403 void moviefile::clear_dynstate()
405 dyn.clear(movie_rtc_second, movie_rtc_subsecond, movie_sram);
408 dynamic_state::dynamic_state()
410 save_frame = 0;
411 lagged_frames = 0;
412 poll_flag = 0;
413 rtc_second = DEFAULT_RTC_SECOND;
414 rtc_subsecond = DEFAULT_RTC_SUBSECOND;
417 void dynamic_state::clear(int64_t sec, int64_t ssec, const std::map<std::string, std::vector<char>>& initsram)
419 sram = initsram;
420 savestate.clear();
421 host_memory.clear();
422 screenshot.clear();
423 save_frame = 0;
424 lagged_frames = 0;
425 for(auto& i : pollcounters)
426 i = 0;
427 poll_flag = 0;
428 rtc_second = sec;
429 rtc_subsecond = ssec;
430 active_macros.clear();
433 void dynamic_state::swap(dynamic_state& s) throw()
435 std::swap(sram, s.sram);
436 std::swap(savestate, s.savestate);
437 std::swap(host_memory, s.host_memory);
438 std::swap(screenshot, s.screenshot);
439 std::swap(save_frame, s.save_frame);
440 std::swap(lagged_frames, s.lagged_frames);
441 std::swap(pollcounters, s.pollcounters);
442 std::swap(poll_flag, s.poll_flag);
443 std::swap(rtc_second, s.rtc_second);
444 std::swap(rtc_subsecond, s.rtc_subsecond);
445 std::swap(active_macros, s.active_macros);