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"
17 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
19 //FUCK YOU. SERIOUSLY.
20 #define EXTRA_OPENFLAGS O_BINARY
22 #define EXTRA_OPENFLAGS 0
27 #define EWOULDBLOCK EAGAIN
32 const char* movie_file_id
= "Movie files";
33 std::map
<std::string
, moviefile
*> memory_saves
;
35 bool check_binary_magic(int s
)
40 int r
= read(s
, buf
+ x
, 5 - x
);
41 if(r
< 0 && (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
))
43 if(r
<= 0) //0 => EOF, break on that too.
47 return !strcmp(buf
, "lsmv\x1A");
50 void write_whole(int s
, const char* buf
, size_t size
)
55 if((size_t)maxw
> (size
- w
))
57 int r
= write(s
, buf
+ w
, maxw
);
58 if(r
< 0 && (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
))
62 (stringfmt() << strerror(err
)).throwex();
69 moviefile::brief_info::brief_info(const std::string
& filename
)
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
];
89 int s
= open(filename
.c_str(), O_RDONLY
| EXTRA_OPENFLAGS
);
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; }
101 zip::reader
r(filename
);
105 moviefile::moviefile() throw(std::bad_alloc
)
106 : tracker(memtracker::singleton(), movie_file_id
, sizeof(*this))
108 force_corrupt
= false;
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;
121 moviefile::moviefile(loaded_rom
& rom
, std::map
<std::string
, std::string
>& c_settings
, uint64_t rtc_sec
,
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);
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;
135 settings
= c_settings
;
137 auto ctrldata
= rom
.controllerconfig(settings
);
138 portctrl::type_set
& ports
= portctrl::type_set::make(ctrldata
.ports
, ctrldata
.portindex());
139 create_default_branch(ports
);
141 //Initialize the remainder.
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))
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]];
164 this->filename
= movie
;
166 start_paused
= false;
167 force_corrupt
= false;
168 lazy_project_create
= false;
170 int s
= open(movie
.c_str(), O_RDONLY
| EXTRA_OPENFLAGS
);
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; }
182 zip::reader
r(movie
);
186 void moviefile::fixup_current_branch(const moviefile
& mv
)
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
)
198 if(rr
= regex("\\$MEMORY:(.*)", movie
)) {
199 auto tmp
= new moviefile();
201 tmp
->copy_fields(*this);
202 memory_saves
[rr
[1]] = tmp
;
210 std::string tmp
= movie
+ ".tmp";
211 int strm
= open(tmp
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
| EXTRA_OPENFLAGS
, 0644);
214 (stringfmt() << "Failed to open '" << tmp
<< "': " << strerror(err
)).throwex();
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
) {
222 (stringfmt() << "Failed to write '" << tmp
<< "': " << e
.what()).throwex();
224 if(close(strm
) < 0) {
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
+ "'");
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
)
248 //If there is a branch, it becomes default.
249 if(!branches
.empty()) {
250 input
= &(branches
.begin()->second
);
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();
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();
275 return (100ULL * frames
+ 3) / 6;
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
]);
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
)
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
;
326 void moviefile::fork_branch(const std::string
& oldname
, const std::string
& newname
)
328 if(oldname
== newname
|| branches
.count(newname
))
330 branches
[newname
] = branches
[oldname
];
333 const std::string
& moviefile::current_branch()
335 for(auto& i
: branches
)
336 if(&i
.second
== input
)
338 static std::string blank_string
;
342 bool moviefile::is_movie_or_savestate(const std::string
& filename
)
345 int s
= open(filename
.c_str(), O_RDONLY
| EXTRA_OPENFLAGS
);
350 bool is_binary
= check_binary_magic(s
);
354 //It is not binary, might be text.
356 zip::reader
r(filename
);
357 r
.read_linefile("systemid", tmp
);
358 if(tmp
.substr(0, 8) != "lsnes-rr")
366 moviefile::branch_extractor::~branch_extractor()
371 moviefile::branch_extractor::branch_extractor(const std::string
& filename
)
375 std::istream
& s
= zip::openrel(filename
, "");
378 if(!strcmp(buf
, "lsmv\x1A"))
383 real
= new moviefile_branch_extractor_binary(filename
);
385 real
= new moviefile_branch_extractor_text(filename
);
388 moviefile::sram_extractor::~sram_extractor()
393 moviefile::sram_extractor::sram_extractor(const std::string
& filename
)
397 std::istream
& s
= zip::openrel(filename
, "");
400 if(!strcmp(buf
, "lsmv\x1A"))
405 real
= new moviefile_sram_extractor_binary(filename
);
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()
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
)
432 for(auto& i
: pollcounters
)
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
);