Fix SA1 open bus
[lsnes.git] / src / core / moviefile.cpp
blobf72345e91a0ac7448f1f23df48b46daa23488587
1 #include "core/misc.hpp"
2 #include "core/movie.hpp"
3 #include "core/moviedata.hpp"
4 #include "core/moviefile.hpp"
5 #include "core/moviefile-common.hpp"
6 #include "library/zip.hpp"
7 #include "library/string.hpp"
8 #include "library/minmax.hpp"
9 #include "library/serialization.hpp"
10 #include "library/binarystream.hpp"
11 #include "interface/romtype.hpp"
13 #include <fcntl.h>
14 #include <unistd.h>
15 #include <iostream>
16 #include <algorithm>
17 #include <sstream>
18 #include <boost/iostreams/copy.hpp>
19 #include <boost/iostreams/device/back_inserter.hpp>
20 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
21 #include <windows.h>
22 //FUCK YOU. SERIOUSLY.
23 #define EXTRA_OPENFLAGS O_BINARY
24 #else
25 #define EXTRA_OPENFLAGS 0
26 #endif
28 //Damn Windows.
29 #ifndef EWOULDBLOCK
30 #define EWOULDBLOCK EAGAIN
31 #endif
33 namespace
35 std::map<std::string, moviefile*> memory_saves;
37 bool check_binary_magic(int s)
39 char buf[6] = {0};
40 int x = 0;
41 while(x < 5) {
42 int r = read(s, buf + x, 5 - x);
43 if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
44 continue;
45 if(r <= 0) //0 => EOF, break on that too.
46 return false;
47 x += r;
49 return !strcmp(buf, "lsmv\x1A");
52 void write_whole(int s, const char* buf, size_t size)
54 size_t w = 0;
55 while(w < size) {
56 int maxw = 32767;
57 if((size_t)maxw > (size - w))
58 maxw = size - w;
59 int r = write(s, buf + w, maxw);
60 if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
61 continue;
62 if(r < 0) {
63 int err = errno;
64 (stringfmt() << strerror(err)).throwex();
66 w += r;
71 moviefile::brief_info::brief_info(const std::string& filename)
73 regex_results rr;
74 if(rr = regex("\\$MEMORY:(.*)", filename)) {
75 if(!memory_saves.count(rr[1]) && memory_saves[rr[1]])
76 throw std::runtime_error("No such memory save");
77 moviefile& mv = *memory_saves[rr[1]];
78 sysregion = mv.gametype->get_name();
79 corename = mv.coreversion;
80 projectid = mv.projectid;
81 current_frame = mv.is_savestate ? mv.save_frame : 0;
82 rerecords = mv.rerecords_mem;
83 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
84 hash[i] = mv.romimg_sha256[i];
85 hashxml[i] = mv.romxml_sha256[i];
86 hint[i] = mv.namehint[i];
88 return;
91 int s = open(filename.c_str(), O_RDONLY | EXTRA_OPENFLAGS);
92 if(s < 0) {
93 int err = errno;
94 (stringfmt() << "Can't read file '" << filename << "': " << strerror(err)).throwex();
96 if(check_binary_magic(s)) {
97 try { binary_io(s); } catch(...) { close(s); throw; }
98 close(s);
99 return;
101 close(s);
103 zip::reader r(filename);
104 load(r);
107 moviefile::moviefile() throw(std::bad_alloc)
109 static port_type_set dummy_types;
110 force_corrupt = false;
111 gametype = NULL;
112 input = NULL;
113 coreversion = "";
114 projectid = "";
115 rerecords = "0";
116 is_savestate = false;
117 movie_rtc_second = rtc_second = DEFAULT_RTC_SECOND;
118 movie_rtc_subsecond = rtc_subsecond = DEFAULT_RTC_SUBSECOND;
119 start_paused = false;
120 lazy_project_create = true;
121 poll_flag = 0;
124 moviefile::moviefile(loaded_rom& rom, std::map<std::string, std::string>& c_settings, uint64_t rtc_sec,
125 uint64_t rtc_subsec)
127 static port_type_set dummy_types;
128 force_corrupt = false;
129 gametype = &rom.rtype->combine_region(*rom.region);
130 coreversion = rom.rtype->get_core_identifier();
131 projectid = get_random_hexstring(40);
132 rerecords = "0";
133 is_savestate = false;
134 movie_rtc_second = rtc_second = rtc_sec;
135 movie_rtc_subsecond = rtc_subsecond = rtc_subsec;
136 start_paused = false;
137 lazy_project_create = true;
138 poll_flag = 0;
139 settings = c_settings;
140 input = NULL;
141 auto ctrldata = rom.rtype->controllerconfig(settings);
142 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex());
143 create_default_branch(ports);
144 if(!rom.rtype->isnull()) {
145 //Initialize the remainder.
146 rerecords = "0";
147 for(size_t i = 0; i < ROM_SLOT_COUNT; i++) {
148 romimg_sha256[i] = rom.romimg[i].sha_256.read();
149 romxml_sha256[i] = rom.romxml[i].sha_256.read();
150 namehint[i] = rom.romimg[i].namehint;
155 moviefile::moviefile(const std::string& movie, core_type& romtype) throw(std::bad_alloc, std::runtime_error)
157 regex_results rr;
158 if(rr = regex("\\$MEMORY:(.*)", movie)) {
159 if(!memory_saves.count(rr[1]) || !memory_saves[rr[1]])
160 throw std::runtime_error("No such memory save");
161 moviefile& s = *memory_saves[rr[1]];
162 copy_fields(s);
163 return;
165 input = NULL;
166 poll_flag = false;
167 start_paused = false;
168 force_corrupt = false;
169 is_savestate = false;
170 lazy_project_create = false;
172 int s = open(movie.c_str(), O_RDONLY | EXTRA_OPENFLAGS);
173 if(s < 0) {
174 int err = errno;
175 (stringfmt() << "Can't read file '" << movie << "': " << strerror(err)).throwex();
177 if(check_binary_magic(s)) {
178 try { binary_io(s, romtype); } catch(...) { close(s); throw; }
179 close(s);
180 return;
182 close(s);
184 zip::reader r(movie);
185 load(r, romtype);
188 void moviefile::fixup_current_branch(const moviefile& mv)
190 input = NULL;
191 for(auto& i : mv.branches)
192 if(&i.second == mv.input)
193 input = &branches[i.first];
196 void moviefile::save(const std::string& movie, unsigned compression, bool binary, rrdata_set& rrd)
197 throw(std::bad_alloc, std::runtime_error)
199 regex_results rr;
200 if(rr = regex("\\$MEMORY:(.*)", movie)) {
201 auto tmp = new moviefile();
202 try {
203 tmp->copy_fields(*this);
204 memory_saves[rr[1]] = tmp;
205 } catch(...) {
206 delete tmp;
207 throw;
209 return;
211 if(binary) {
212 std::string tmp = movie + ".tmp";
213 int strm = open(tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC | EXTRA_OPENFLAGS, 0644);
214 if(strm < 0) {
215 int err = errno;
216 (stringfmt() << "Failed to open '" << tmp << "': " << strerror(err)).throwex();
218 try {
219 char buf[5] = {'l', 's', 'm', 'v', 0x1A};
220 write_whole(strm, buf, 5);
221 binary_io(strm, rrd);
222 } catch(std::exception& e) {
223 close(strm);
224 (stringfmt() << "Failed to write '" << tmp << "': " << e.what()).throwex();
226 if(close(strm) < 0) {
227 int err = errno;
228 (stringfmt() << "Failed to write '" << tmp << "': " << strerror(err)).throwex();
230 std::string backup = movie + ".backup";
231 zip::rename_overwrite(movie.c_str(), backup.c_str());
232 if(zip::rename_overwrite(tmp.c_str(), movie.c_str()) < 0)
233 throw std::runtime_error("Can't rename '" + tmp + "' -> '" + movie + "'");
234 return;
236 zip::writer w(movie, compression);
237 save(w, rrd);
240 void moviefile::save(std::ostream& stream, rrdata_set& rrd) throw(std::bad_alloc, std::runtime_error)
242 zip::writer w(stream, 0);
243 save(w, rrd);
246 void moviefile::create_default_branch(port_type_set& ports)
248 if(input)
249 return;
250 //If there is a branch, it becomes default.
251 if(!branches.empty()) {
252 input = &(branches.begin()->second);
253 } else {
254 //Otherwise, just create a branch.
255 branches[""].clear(ports);
256 input = &branches[""];
260 uint64_t moviefile::get_frame_count() throw()
262 return input->count_frames();
265 namespace
267 const int BLOCK_SECONDS = 0;
268 const int BLOCK_FRAMES = 1;
269 const int STEP_W = 2;
270 const int STEP_N = 3;
273 uint64_t moviefile::get_movie_length() throw()
275 uint64_t frames = get_frame_count();
276 if(!gametype) {
277 return (100ULL * frames + 3) / 6;
279 uint64_t _magic[4];
280 gametype->fill_framerate_magic(_magic);
281 uint64_t t = _magic[BLOCK_SECONDS] * 1000ULL * (frames / _magic[BLOCK_FRAMES]);
282 frames %= _magic[BLOCK_FRAMES];
283 t += frames * _magic[STEP_W] + ((frames * _magic[STEP_N] + _magic[BLOCK_FRAMES] - 1) / _magic[BLOCK_FRAMES]);
284 return t;
287 moviefile*& moviefile::memref(const std::string& slot)
289 return memory_saves[slot];
292 void moviefile::copy_fields(const moviefile& mv)
294 force_corrupt = mv.force_corrupt;
295 gametype = mv.gametype;
296 settings = mv.settings;
297 coreversion = mv.coreversion;
298 gamename = mv.gamename;
299 projectid = mv.projectid;
300 rerecords = mv.rerecords;
301 rerecords_mem = mv.rerecords_mem;
302 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
303 romimg_sha256[i] = mv.romimg_sha256[i];
304 romxml_sha256[i] = mv.romxml_sha256[i];
305 namehint[i] = mv.namehint[i];
307 authors = mv.authors;
308 movie_sram = mv.movie_sram;
309 ramcontent = mv.ramcontent;
310 is_savestate = mv.is_savestate;
311 sram = mv.sram;
312 savestate = mv.savestate;
313 anchor_savestate = mv.anchor_savestate;
314 host_memory = mv.host_memory;
315 screenshot = mv.screenshot;
316 save_frame = mv.save_frame;
317 lagged_frames = mv.lagged_frames;
318 pollcounters = mv.pollcounters;
319 poll_flag = mv.poll_flag;
320 c_rrdata = mv.c_rrdata;
321 branches = mv.branches;
323 //Copy the active branch.
324 input = &branches.begin()->second;
325 for(auto& i : branches)
326 if(mv.branches.count(i.first) && &mv.branches.find(i.first)->second == mv.input)
327 input = &i.second;
329 rtc_second = mv.rtc_second;
330 rtc_subsecond = mv.rtc_subsecond;
331 movie_rtc_second = mv.movie_rtc_second;
332 movie_rtc_subsecond = mv.movie_rtc_subsecond;
333 start_paused = mv.start_paused;
334 lazy_project_create = mv.lazy_project_create;
335 subtitles = mv.subtitles;
336 active_macros = mv.active_macros;
339 void moviefile::fork_branch(const std::string& oldname, const std::string& newname)
341 if(oldname == newname || branches.count(newname))
342 return;
343 branches[newname] = branches[oldname];
346 const std::string& moviefile::current_branch()
348 for(auto& i : branches)
349 if(&i.second == input)
350 return i.first;
351 static std::string tmp;
352 return tmp;
355 moviefile::branch_extractor::~branch_extractor()
357 delete real;
360 moviefile::branch_extractor::branch_extractor(const std::string& filename)
362 bool binary = false;
364 std::istream& s = zip::openrel(filename, "");
365 char buf[6] = {0};
366 s.read(buf, 5);
367 if(!strcmp(buf, "lsmv\x1A"))
368 binary = true;
369 delete &s;
371 if(binary)
372 real = new moviefile_branch_extractor_binary(filename);
373 else
374 real = new moviefile_branch_extractor_text(filename);