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"
18 #include <boost/iostreams/copy.hpp>
19 #include <boost/iostreams/device/back_inserter.hpp>
20 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
22 //FUCK YOU. SERIOUSLY.
23 #define EXTRA_OPENFLAGS O_BINARY
25 #define EXTRA_OPENFLAGS 0
30 #define EWOULDBLOCK EAGAIN
35 std::map
<std::string
, moviefile
*> memory_saves
;
37 bool check_binary_magic(int s
)
42 int r
= read(s
, buf
+ x
, 5 - x
);
43 if(r
< 0 && (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
))
45 if(r
<= 0) //0 => EOF, break on that too.
49 return !strcmp(buf
, "lsmv\x1A");
52 void write_whole(int s
, const char* buf
, size_t size
)
57 if((size_t)maxw
> (size
- w
))
59 int r
= write(s
, buf
+ w
, maxw
);
60 if(r
< 0 && (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
))
64 (stringfmt() << strerror(err
)).throwex();
71 moviefile::brief_info::brief_info(const std::string
& filename
)
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
];
91 int s
= open(filename
.c_str(), O_RDONLY
| EXTRA_OPENFLAGS
);
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; }
103 zip::reader
r(filename
);
107 moviefile::moviefile() throw(std::bad_alloc
)
109 static port_type_set dummy_types
;
110 force_corrupt
= false;
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;
124 moviefile::moviefile(loaded_rom
& rom
, std::map
<std::string
, std::string
>& c_settings
, uint64_t rtc_sec
,
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);
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;
139 settings
= c_settings
;
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.
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
)
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]];
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
);
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; }
184 zip::reader
r(movie
);
188 void moviefile::fixup_current_branch(const moviefile
& mv
)
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
)
200 if(rr
= regex("\\$MEMORY:(.*)", movie
)) {
201 auto tmp
= new moviefile();
203 tmp
->copy_fields(*this);
204 memory_saves
[rr
[1]] = tmp
;
212 std::string tmp
= movie
+ ".tmp";
213 int strm
= open(tmp
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
| EXTRA_OPENFLAGS
, 0644);
216 (stringfmt() << "Failed to open '" << tmp
<< "': " << strerror(err
)).throwex();
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
) {
224 (stringfmt() << "Failed to write '" << tmp
<< "': " << e
.what()).throwex();
226 if(close(strm
) < 0) {
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
+ "'");
236 zip::writer
w(movie
, compression
);
240 void moviefile::save(std::ostream
& stream
, rrdata_set
& rrd
) throw(std::bad_alloc
, std::runtime_error
)
242 zip::writer
w(stream
, 0);
246 void moviefile::create_default_branch(port_type_set
& ports
)
250 //If there is a branch, it becomes default.
251 if(!branches
.empty()) {
252 input
= &(branches
.begin()->second
);
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();
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();
277 return (100ULL * frames
+ 3) / 6;
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
]);
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
;
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
)
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
))
343 branches
[newname
] = branches
[oldname
];
346 const std::string
& moviefile::current_branch()
348 for(auto& i
: branches
)
349 if(&i
.second
== input
)
351 static std::string tmp
;
355 moviefile::branch_extractor::~branch_extractor()
360 moviefile::branch_extractor::branch_extractor(const std::string
& filename
)
364 std::istream
& s
= zip::openrel(filename
, "");
367 if(!strcmp(buf
, "lsmv\x1A"))
372 real
= new moviefile_branch_extractor_binary(filename
);
374 real
= new moviefile_branch_extractor_text(filename
);