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
.is_savestate
? mv
.save_frame
: 0;
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 is_savestate
= false;
113 movie_rtc_second
= rtc_second
= DEFAULT_RTC_SECOND
;
114 movie_rtc_subsecond
= rtc_subsecond
= DEFAULT_RTC_SUBSECOND
;
115 start_paused
= false;
116 lazy_project_create
= true;
120 moviefile::moviefile(loaded_rom
& rom
, std::map
<std::string
, std::string
>& c_settings
, uint64_t rtc_sec
,
123 force_corrupt
= false;
124 gametype
= &rom
.get_sysregion();
125 coreversion
= rom
.get_core_identifier();
126 projectid
= get_random_hexstring(40);
128 is_savestate
= false;
129 movie_rtc_second
= rtc_second
= rtc_sec
;
130 movie_rtc_subsecond
= rtc_subsecond
= rtc_subsec
;
131 start_paused
= false;
132 lazy_project_create
= true;
134 settings
= c_settings
;
136 auto ctrldata
= rom
.controllerconfig(settings
);
137 portctrl::type_set
& ports
= portctrl::type_set::make(ctrldata
.ports
, ctrldata
.portindex());
138 create_default_branch(ports
);
140 //Initialize the remainder.
142 for(size_t i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
143 romimg_sha256
[i
] = rom
.romimg
[i
].sha_256
.read();
144 romxml_sha256
[i
] = rom
.romxml
[i
].sha_256
.read();
145 namehint
[i
] = rom
.romimg
[i
].namehint
;
150 moviefile::moviefile(const std::string
& movie
, core_type
& romtype
) throw(std::bad_alloc
, std::runtime_error
)
153 if(rr
= regex("\\$MEMORY:(.*)", movie
)) {
154 if(!memory_saves
.count(rr
[1]) || !memory_saves
[rr
[1]])
155 throw std::runtime_error("No such memory save");
156 moviefile
& s
= *memory_saves
[rr
[1]];
162 start_paused
= false;
163 force_corrupt
= false;
164 is_savestate
= false;
165 lazy_project_create
= false;
167 int s
= open(movie
.c_str(), O_RDONLY
| EXTRA_OPENFLAGS
);
170 (stringfmt() << "Can't read file '" << movie
<< "': " << strerror(err
)).throwex();
172 if(check_binary_magic(s
)) {
173 try { binary_io(s
, romtype
); } catch(...) { close(s
); throw; }
179 zip::reader
r(movie
);
183 void moviefile::fixup_current_branch(const moviefile
& mv
)
186 for(auto& i
: mv
.branches
)
187 if(&i
.second
== mv
.input
)
188 input
= &branches
[i
.first
];
191 void moviefile::save(const std::string
& movie
, unsigned compression
, bool binary
, rrdata_set
& rrd
)
192 throw(std::bad_alloc
, std::runtime_error
)
195 if(rr
= regex("\\$MEMORY:(.*)", movie
)) {
196 auto tmp
= new moviefile();
198 tmp
->copy_fields(*this);
199 memory_saves
[rr
[1]] = tmp
;
207 std::string tmp
= movie
+ ".tmp";
208 int strm
= open(tmp
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
| EXTRA_OPENFLAGS
, 0644);
211 (stringfmt() << "Failed to open '" << tmp
<< "': " << strerror(err
)).throwex();
214 char buf
[5] = {'l', 's', 'm', 'v', 0x1A};
215 write_whole(strm
, buf
, 5);
216 binary_io(strm
, rrd
);
217 } catch(std::exception
& e
) {
219 (stringfmt() << "Failed to write '" << tmp
<< "': " << e
.what()).throwex();
221 if(close(strm
) < 0) {
223 (stringfmt() << "Failed to write '" << tmp
<< "': " << strerror(err
)).throwex();
225 std::string backup
= movie
+ ".backup";
226 directory::rename_overwrite(movie
.c_str(), backup
.c_str());
227 if(directory::rename_overwrite(tmp
.c_str(), movie
.c_str()) < 0)
228 throw std::runtime_error("Can't rename '" + tmp
+ "' -> '" + movie
+ "'");
231 zip::writer
w(movie
, compression
);
235 void moviefile::save(std::ostream
& stream
, rrdata_set
& rrd
) throw(std::bad_alloc
, std::runtime_error
)
237 zip::writer
w(stream
, 0);
241 void moviefile::create_default_branch(portctrl::type_set
& ports
)
245 //If there is a branch, it becomes default.
246 if(!branches
.empty()) {
247 input
= &(branches
.begin()->second
);
249 //Otherwise, just create a branch.
250 branches
[""].clear(ports
);
251 input
= &branches
[""];
255 uint64_t moviefile::get_frame_count() throw()
257 return input
->count_frames();
262 const int BLOCK_SECONDS
= 0;
263 const int BLOCK_FRAMES
= 1;
264 const int STEP_W
= 2;
265 const int STEP_N
= 3;
268 uint64_t moviefile::get_movie_length() throw()
270 uint64_t frames
= get_frame_count();
272 return (100ULL * frames
+ 3) / 6;
275 gametype
->fill_framerate_magic(_magic
);
276 uint64_t t
= _magic
[BLOCK_SECONDS
] * 1000ULL * (frames
/ _magic
[BLOCK_FRAMES
]);
277 frames
%= _magic
[BLOCK_FRAMES
];
278 t
+= frames
* _magic
[STEP_W
] + ((frames
* _magic
[STEP_N
] + _magic
[BLOCK_FRAMES
] - 1) / _magic
[BLOCK_FRAMES
]);
282 moviefile
*& moviefile::memref(const std::string
& slot
)
284 return memory_saves
[slot
];
287 void moviefile::copy_fields(const moviefile
& mv
)
289 force_corrupt
= mv
.force_corrupt
;
290 gametype
= mv
.gametype
;
291 settings
= mv
.settings
;
292 coreversion
= mv
.coreversion
;
293 gamename
= mv
.gamename
;
294 projectid
= mv
.projectid
;
295 rerecords
= mv
.rerecords
;
296 rerecords_mem
= mv
.rerecords_mem
;
297 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
298 romimg_sha256
[i
] = mv
.romimg_sha256
[i
];
299 romxml_sha256
[i
] = mv
.romxml_sha256
[i
];
300 namehint
[i
] = mv
.namehint
[i
];
302 authors
= mv
.authors
;
303 movie_sram
= mv
.movie_sram
;
304 ramcontent
= mv
.ramcontent
;
305 is_savestate
= mv
.is_savestate
;
307 savestate
= mv
.savestate
;
308 anchor_savestate
= mv
.anchor_savestate
;
309 host_memory
= mv
.host_memory
;
310 screenshot
= mv
.screenshot
;
311 save_frame
= mv
.save_frame
;
312 lagged_frames
= mv
.lagged_frames
;
313 pollcounters
= mv
.pollcounters
;
314 poll_flag
= mv
.poll_flag
;
315 c_rrdata
= mv
.c_rrdata
;
316 branches
= mv
.branches
;
318 //Copy the active branch.
319 input
= &branches
.begin()->second
;
320 for(auto& i
: branches
)
321 if(mv
.branches
.count(i
.first
) && &mv
.branches
.find(i
.first
)->second
== mv
.input
)
324 rtc_second
= mv
.rtc_second
;
325 rtc_subsecond
= mv
.rtc_subsecond
;
326 movie_rtc_second
= mv
.movie_rtc_second
;
327 movie_rtc_subsecond
= mv
.movie_rtc_subsecond
;
328 start_paused
= mv
.start_paused
;
329 lazy_project_create
= mv
.lazy_project_create
;
330 subtitles
= mv
.subtitles
;
331 active_macros
= mv
.active_macros
;
334 void moviefile::fork_branch(const std::string
& oldname
, const std::string
& newname
)
336 if(oldname
== newname
|| branches
.count(newname
))
338 branches
[newname
] = branches
[oldname
];
341 const std::string
& moviefile::current_branch()
343 for(auto& i
: branches
)
344 if(&i
.second
== input
)
346 static std::string blank_string
;
350 bool moviefile::is_movie_or_savestate(const std::string
& filename
)
353 int s
= open(filename
.c_str(), O_RDONLY
| EXTRA_OPENFLAGS
);
358 bool is_binary
= check_binary_magic(s
);
362 //It is not binary, might be text.
364 zip::reader
r(filename
);
365 r
.read_linefile("systemid", tmp
);
366 if(tmp
.substr(0, 8) != "lsnes-rr")
374 moviefile::branch_extractor::~branch_extractor()
379 moviefile::branch_extractor::branch_extractor(const std::string
& filename
)
383 std::istream
& s
= zip::openrel(filename
, "");
386 if(!strcmp(buf
, "lsmv\x1A"))
391 real
= new moviefile_branch_extractor_binary(filename
);
393 real
= new moviefile_branch_extractor_text(filename
);
396 moviefile::sram_extractor::~sram_extractor()
401 moviefile::sram_extractor::sram_extractor(const std::string
& filename
)
405 std::istream
& s
= zip::openrel(filename
, "");
408 if(!strcmp(buf
, "lsmv\x1A"))
413 real
= new moviefile_sram_extractor_binary(filename
);
415 real
= new moviefile_sram_extractor_text(filename
);