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 std::map
<std::string
, moviefile
*> memory_saves
;
34 bool check_binary_magic(int s
)
39 int r
= read(s
, buf
+ x
, 5 - x
);
40 if(r
< 0 && (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
))
42 if(r
<= 0) //0 => EOF, break on that too.
46 return !strcmp(buf
, "lsmv\x1A");
49 void write_whole(int s
, const char* buf
, size_t size
)
54 if((size_t)maxw
> (size
- w
))
56 int r
= write(s
, buf
+ w
, maxw
);
57 if(r
< 0 && (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
))
61 (stringfmt() << strerror(err
)).throwex();
68 moviefile::brief_info::brief_info(const std::string
& filename
)
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
];
88 int s
= open(filename
.c_str(), O_RDONLY
| EXTRA_OPENFLAGS
);
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; }
100 zip::reader
r(filename
);
104 moviefile::moviefile() throw(std::bad_alloc
)
106 force_corrupt
= false;
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
,
121 force_corrupt
= false;
122 gametype
= &rom
.get_sysregion();
123 coreversion
= rom
.get_core_identifier();
124 projectid
= get_random_hexstring(40);
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
;
132 auto ctrldata
= rom
.controllerconfig(settings
);
133 portctrl::type_set
& ports
= portctrl::type_set::make(ctrldata
.ports
, ctrldata
.portindex());
134 create_default_branch(ports
);
136 //Initialize the remainder.
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
)
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]];
159 start_paused
= false;
160 force_corrupt
= false;
161 lazy_project_create
= false;
163 int s
= open(movie
.c_str(), O_RDONLY
| EXTRA_OPENFLAGS
);
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; }
175 zip::reader
r(movie
);
179 void moviefile::fixup_current_branch(const moviefile
& mv
)
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
)
191 if(rr
= regex("\\$MEMORY:(.*)", movie
)) {
192 auto tmp
= new moviefile();
194 tmp
->copy_fields(*this);
195 memory_saves
[rr
[1]] = tmp
;
203 std::string tmp
= movie
+ ".tmp";
204 int strm
= open(tmp
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
| EXTRA_OPENFLAGS
, 0644);
207 (stringfmt() << "Failed to open '" << tmp
<< "': " << strerror(err
)).throwex();
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
) {
215 (stringfmt() << "Failed to write '" << tmp
<< "': " << e
.what()).throwex();
217 if(close(strm
) < 0) {
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
+ "'");
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
)
241 //If there is a branch, it becomes default.
242 if(!branches
.empty()) {
243 input
= &(branches
.begin()->second
);
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();
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();
268 return (100ULL * frames
+ 3) / 6;
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
]);
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
)
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
;
319 void moviefile::fork_branch(const std::string
& oldname
, const std::string
& newname
)
321 if(oldname
== newname
|| branches
.count(newname
))
323 branches
[newname
] = branches
[oldname
];
326 const std::string
& moviefile::current_branch()
328 for(auto& i
: branches
)
329 if(&i
.second
== input
)
331 static std::string blank_string
;
335 bool moviefile::is_movie_or_savestate(const std::string
& filename
)
338 int s
= open(filename
.c_str(), O_RDONLY
| EXTRA_OPENFLAGS
);
343 bool is_binary
= check_binary_magic(s
);
347 //It is not binary, might be text.
349 zip::reader
r(filename
);
350 r
.read_linefile("systemid", tmp
);
351 if(tmp
.substr(0, 8) != "lsnes-rr")
359 moviefile::branch_extractor::~branch_extractor()
364 moviefile::branch_extractor::branch_extractor(const std::string
& filename
)
368 std::istream
& s
= zip::openrel(filename
, "");
371 if(!strcmp(buf
, "lsmv\x1A"))
376 real
= new moviefile_branch_extractor_binary(filename
);
378 real
= new moviefile_branch_extractor_text(filename
);
381 moviefile::sram_extractor::~sram_extractor()
386 moviefile::sram_extractor::sram_extractor(const std::string
& filename
)
390 std::istream
& s
= zip::openrel(filename
, "");
393 if(!strcmp(buf
, "lsmv\x1A"))
398 real
= new moviefile_sram_extractor_binary(filename
);
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()
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
)
425 for(auto& i
: pollcounters
)
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
);