Binary movies support

This commit is contained in:
Ilari Liusvaara 2013-07-29 22:16:23 +03:00
parent a2fc2a19ec
commit 75b3e79ea7
9 changed files with 657 additions and 63 deletions

View file

@ -1,6 +1,7 @@
#ifndef _mainloop__hpp__included__
#define _mainloop__hpp__included__
#include "settings.hpp"
#include "rom.hpp"
#include "moviefile.hpp"
#include "movie.hpp"
@ -27,4 +28,8 @@ void set_stop_at_frame(uint64_t frame = 0);
void switch_projects(const std::string& newproj);
void close_rom();
extern setting_var<setting_var_model_bool<setting_yes_no>> jukebox_dflt_binary;
extern setting_var<setting_var_model_bool<setting_yes_no>> movie_dflt_binary;
extern setting_var<setting_var_model_bool<setting_yes_no>> save_dflt_binary;
#endif

View file

@ -26,12 +26,12 @@ std::string resolve_relative_path(const std::string& path);
std::pair<std::string, std::string> split_author(const std::string& author) throw(std::bad_alloc,
std::runtime_error);
void do_save_state(const std::string& filename) throw(std::bad_alloc, std::runtime_error);
void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runtime_error);
void do_save_state(const std::string& filename, int binary) throw(std::bad_alloc, std::runtime_error);
void do_save_movie(const std::string& filename, int binary) throw(std::bad_alloc, std::runtime_error);
void do_load_beginning(bool reloading = false) throw(std::bad_alloc, std::runtime_error);
void do_load_state(struct moviefile& _movie, int lmode);
bool do_load_state(const std::string& filename, int lmode);
std::string translate_name_mprefix(std::string original);
std::string translate_name_mprefix(std::string original, int& binary, bool save);
extern std::string last_save;
extern movie_logic movb;

View file

@ -37,10 +37,12 @@ struct moviefile
*
* parameter filename: The file to save to.
* parameter compression: The compression level 0-9. 0 is uncompressed.
* parameter binary: Save in binary form if true.
* throws std::bad_alloc: Not enough memory.
* throws std::runtime_error: Can't save the movie file.
*/
void save(const std::string& filename, unsigned compression) throw(std::bad_alloc, std::runtime_error);
void save(const std::string& filename, unsigned compression, bool binary) throw(std::bad_alloc,
std::runtime_error);
/**
* Force loading as corrupt.
@ -178,6 +180,9 @@ struct moviefile
* returns: Length of the movie in nanoseconds.
*/
uint64_t get_movie_length() throw();
private:
void binary_io(std::ostream& stream) throw(std::bad_alloc, std::runtime_error);
void binary_io(std::istream& stream, struct core_type& romtype) throw(std::bad_alloc, std::runtime_error);
};
#endif

View file

@ -41,6 +41,12 @@
void update_movie_state();
time_t random_seed_value = 0;
setting_var<setting_var_model_bool<setting_yes_no>> jukebox_dflt_binary(lsnes_vset, "jukebox-default-binary",
"Movie‣Saving‣Saveslots binary", true);
setting_var<setting_var_model_bool<setting_yes_no>> movie_dflt_binary(lsnes_vset, "movie-default-binary",
"Movie‣Saving‣Movies binary", false);
setting_var<setting_var_model_bool<setting_yes_no>> save_dflt_binary(lsnes_vset, "savestate-default-binary",
"Movie‣Saving‣Savestates binary", false);
namespace
{
@ -78,7 +84,7 @@ namespace
std::string pending_load;
std::string pending_new_project;
//Queued saves (all savestates).
std::set<std::string> queued_saves;
std::set<std::pair<std::string, int>> queued_saves;
//Save jukebox.
size_t save_jukebox_pointer;
//Special subframe location. One of SPECIAL_* constants.
@ -268,21 +274,22 @@ namespace
platform::set_paused(false);
}
void mark_pending_save(const std::string& filename, int smode)
void mark_pending_save(const std::string& filename, int smode, int binary)
{
int tmp = -1;
if(smode == SAVE_MOVIE) {
//Just do this immediately.
do_save_movie(filename);
flush_slotinfo(translate_name_mprefix(filename));
do_save_movie(filename, binary);
flush_slotinfo(translate_name_mprefix(filename, tmp, false));
return;
}
if(location_special == SPECIAL_SAVEPOINT) {
//We can save immediately here.
do_save_state(filename);
flush_slotinfo(translate_name_mprefix(filename));
do_save_state(filename, binary);
flush_slotinfo(translate_name_mprefix(filename, tmp, false));
return;
}
queued_saves.insert(filename);
queued_saves.insert(std::make_pair(filename, binary));
messages << "Pending save on '" << filename << "'" << std::endl;
}
@ -411,7 +418,8 @@ void update_movie_state()
_status.set("!mode", "F");
}
if(jukebox_size > 0) {
std::string sfilen = translate_name_mprefix(save_jukebox_name(save_jukebox_pointer));
int tmp = -1;
std::string sfilen = translate_name_mprefix(save_jukebox_name(save_jukebox_pointer), tmp, false);
_status.set("!saveslot", (stringfmt() << (save_jukebox_pointer + 1)).str());
_status.set("!saveslotinfo", get_slotinfo(sfilen));
} else {
@ -655,7 +663,7 @@ namespace
[]() throw(std::bad_alloc, std::runtime_error) {
if(jukebox_size == 0)
throw std::runtime_error("No slot selected");
mark_pending_save(save_jukebox_name(save_jukebox_pointer), SAVE_STATE);
mark_pending_save(save_jukebox_name(save_jukebox_pointer), SAVE_STATE, -1);
});
function_ptr_command<> padvance_frame(lsnes_cmd, "+advance-frame", "Advance one frame",
@ -766,13 +774,37 @@ namespace
function_ptr_command<arg_filename> save_state(lsnes_cmd, "save-state", "Save state",
"Syntax: save-state <file>\nSaves SNES state to <file>\n",
[](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_STATE);
mark_pending_save(args, SAVE_STATE, -1);
});
function_ptr_command<arg_filename> save_state2(lsnes_cmd, "save-state-binary", "Save state (binary)",
"Syntax: save-state-binary <file>\nSaves binary state to <file>\n",
[](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_STATE, 1);
});
function_ptr_command<arg_filename> save_state3(lsnes_cmd, "save-state-zip", "Save state (zip)",
"Syntax: save-state-zip <file>\nSaves zip state to <file>\n",
[](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_STATE, 0);
});
function_ptr_command<arg_filename> save_movie(lsnes_cmd, "save-movie", "Save movie",
"Syntax: save-movie <file>\nSaves SNES movie to <file>\n",
[](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_MOVIE);
mark_pending_save(args, SAVE_MOVIE, -1);
});
function_ptr_command<arg_filename> save_movie2(lsnes_cmd, "save-movie-binary", "Save movie (binary)",
"Syntax: save-movie-binary <file>\nSaves binary movie to <file>\n",
[](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_MOVIE, 1);
});
function_ptr_command<arg_filename> save_movie3(lsnes_cmd, "save-movie-zip", "Save movie (zip)",
"Syntax: save-movie-zip <file>\nSaves zip movie to <file>\n",
[](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_MOVIE, 0);
});
function_ptr_command<> set_rwmode(lsnes_cmd, "set-rwmode", "Switch to read/write mode",
@ -1080,8 +1112,9 @@ nothing_to_do:
if(!queued_saves.empty() || (do_unsafe_rewind && !unsafe_rewind_obj)) {
our_rom->rtype->runtosave();
for(auto i : queued_saves) {
do_save_state(i);
flush_slotinfo(translate_name_mprefix(i));
do_save_state(i.first, i.second);
int tmp = -1;
flush_slotinfo(translate_name_mprefix(i.first, tmp, false));
}
if(do_unsafe_rewind && !unsafe_rewind_obj) {
uint64_t t = get_utime();

View file

@ -7,6 +7,7 @@
#include "core/framerate.hpp"
#include "lua/lua.hpp"
#include "core/misc.hpp"
#include "core/mainloop.hpp"
#include "core/moviedata.hpp"
#include "core/project.hpp"
#include "core/rrdata.hpp"
@ -55,10 +56,10 @@ movie& get_movie()
namespace
{
setting_var<setting_var_model_int<0, 9>> savecompression(lsnes_vset, "savecompression", "Movie‣Compression",
7);
setting_var<setting_var_model_int<0, 9>> savecompression(lsnes_vset, "savecompression",
"Movie‣Saving‣Compression", 7);
setting_var<setting_var_model_bool<setting_yes_no>> readonly_load_preserves(lsnes_vset,
"preserve_on_readonly_load", "Movie‣Preserve on readonly load", true);
"preserve_on_readonly_load", "Movie‣Loading‣Preserve on readonly load", true);
mutex_class mprefix_lock;
std::string mprefix;
bool mprefix_valid;
@ -142,7 +143,7 @@ void set_mprefix_for_project(const std::string& pfx)
set_mprefix(pfx);
}
std::string translate_name_mprefix(std::string original)
std::string translate_name_mprefix(std::string original, int& binary, bool save)
{
auto p = project_get();
regex_results r;
@ -151,13 +152,18 @@ std::string translate_name_mprefix(std::string original)
}
size_t prefixloc = original.find("${project}");
if(prefixloc < original.length()) {
if(binary < 0)
binary = jukebox_dflt_binary ? 1 : 0;
std::string pprf = lsnes_vset["slotpath"].str() + "/";
if(prefixloc == 0)
return pprf + get_mprefix() + original.substr(prefixloc + 10);
else
return original.substr(0, prefixloc) + get_mprefix() + original.substr(prefixloc + 10);
} else
} else {
if(binary < 0)
binary = (save ? save_dflt_binary : movie_dflt_binary) ? 1 : 0;
return original;
}
}
std::pair<std::string, std::string> split_author(const std::string& author) throw(std::bad_alloc,
@ -189,7 +195,7 @@ std::string resolve_relative_path(const std::string& path)
}
//Save state.
void do_save_state(const std::string& filename) throw(std::bad_alloc,
void do_save_state(const std::string& filename, int binary) throw(std::bad_alloc,
std::runtime_error)
{
if(!our_movie.gametype) {
@ -197,7 +203,7 @@ void do_save_state(const std::string& filename) throw(std::bad_alloc,
messages << "Can't save movie without a ROM" << std::endl;
return;
}
std::string filename2 = translate_name_mprefix(filename);
std::string filename2 = translate_name_mprefix(filename, binary, true);
lua_callback_pre_save(filename2, true);
try {
uint64_t origtime = get_utime();
@ -219,9 +225,11 @@ void do_save_state(const std::string& filename) throw(std::bad_alloc,
our_movie.authors = prj->authors;
}
our_movie.active_macros = controls.get_macro_frames();
our_movie.save(filename2, savecompression);
our_movie.save(filename2, savecompression, binary > 0);
uint64_t took = get_utime() - origtime;
messages << "Saved state '" << filename2 << "' in " << took << " microseconds." << std::endl;
std::string kind = (binary > 0) ? "(binary format)" : "(zip format)";
messages << "Saved state " << kind << " '" << filename2 << "' in " << took << " microseconds."
<< std::endl;
lua_callback_post_save(filename2, true);
} catch(std::bad_alloc& e) {
throw;
@ -239,14 +247,14 @@ void do_save_state(const std::string& filename) throw(std::bad_alloc,
}
//Save movie.
void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
void do_save_movie(const std::string& filename, int binary) throw(std::bad_alloc, std::runtime_error)
{
if(!our_movie.gametype) {
platform::error_message("Can't save movie without a ROM");
messages << "Can't save movie without a ROM" << std::endl;
return;
}
std::string filename2 = translate_name_mprefix(filename);
std::string filename2 = translate_name_mprefix(filename, binary, false);
lua_callback_pre_save(filename2, false);
try {
uint64_t origtime = get_utime();
@ -258,9 +266,11 @@ void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runti
our_movie.authors = prj->authors;
}
our_movie.active_macros.clear();
our_movie.save(filename2, savecompression);
our_movie.save(filename2, savecompression, binary > 0);
uint64_t took = get_utime() - origtime;
messages << "Saved movie '" << filename2 << "' in " << took << " microseconds." << std::endl;
std::string kind = (binary > 0) ? "(binary format)" : "(zip format)";
messages << "Saved movie " << kind << " '" << filename2 << "' in " << took << " microseconds."
<< std::endl;
lua_callback_post_save(filename2, false);
} catch(std::bad_alloc& e) {
OOM_panic();
@ -399,7 +409,8 @@ void do_load_state(struct moviefile& _movie, int lmode)
try {
our_rom->region = _movie.gametype ? &(_movie.gametype->get_region()) : NULL;
random_seed_value = _movie.movie_rtc_second;
our_rom->load(_movie.settings, _movie.movie_rtc_second, _movie.movie_rtc_subsecond);
if(!will_load_state || our_movie.projectid != _movie.projectid)
our_rom->load(_movie.settings, _movie.movie_rtc_second, _movie.movie_rtc_subsecond);
if(will_load_state) {
//Load the savestate and movie state.
@ -500,7 +511,8 @@ void do_load_state(struct moviefile& _movie, int lmode)
//Load state
bool do_load_state(const std::string& filename, int lmode)
{
std::string filename2 = translate_name_mprefix(filename);
int tmp = -1;
std::string filename2 = translate_name_mprefix(filename, tmp, false);
uint64_t origtime = get_utime();
lua_callback_pre_load(filename2);
struct moviefile mfile;

View file

@ -4,8 +4,12 @@
#include "core/rrdata.hpp"
#include "library/zip.hpp"
#include "library/string.hpp"
#include "library/minmax.hpp"
#include "library/serialization.hpp"
#include "interface/romtype.hpp"
#include <iostream>
#include <algorithm>
#include <sstream>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
@ -13,6 +17,27 @@
#define DEFAULT_RTC_SECOND 1000000000ULL
#define DEFAULT_RTC_SUBSECOND 0ULL
enum lsnes_movie_tags
{
TAG_ = 0xaddb2d86,
TAG_ANCHOR_SAVE = 0xf5e0fad7,
TAG_AUTHOR = 0xafff97b4,
TAG_CORE_VERSION = 0xe4344c7e,
TAG_GAMENAME = 0xe80d6970,
TAG_HOSTMEMORY = 0x3bf9d187,
TAG_MACRO = 0xd261338f,
TAG_MOVIE = 0xf3dca44b,
TAG_MOVIE_SRAM = 0xbbc824b7,
TAG_MOVIE_TIME = 0x18c3a975,
TAG_PROJECT_ID = 0x359bfbab,
TAG_ROMHASH = 0x0428acfc,
TAG_RRDATA = 0xa3a07f71,
TAG_SAVE_SRAM = 0xae9bfb2f,
TAG_SAVESTATE = 0x2e5bc2ac,
TAG_SCREENSHOT = 0xc6760d0e,
TAG_SUBTITLE = 0x6a7054d3,
};
void read_linefile(zip_reader& r, const std::string& member, std::string& out, bool conditional = false)
throw(std::bad_alloc, std::runtime_error)
{
@ -47,6 +72,233 @@ void write_linefile(zip_writer& w, const std::string& member, const std::string&
namespace
{
int lsnes_rename(const char* oldname, const char* newname)
{
#if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
return MoveFileEx(oldname, newname, MOVEFILE_REPLACE_EXISTING);
#else
return rename(oldname, newname);
#endif
}
void binary_write_byte(std::ostream& stream, uint8_t byte)
{
stream.write(reinterpret_cast<char*>(&byte), 1);
}
void binary_write_number(std::ostream& stream, uint64_t number)
{
char data[10];
size_t len = 0;
do {
bool cont = (number > 127);
data[len++] = (cont ? 0x80 : 0x00) | (number & 0x7F);
number >>= 7;
} while(number);
stream.write(data, len);
}
void binary_write_number32(std::ostream& stream, uint32_t number)
{
char data[4];
write32ube(data, number);
stream.write(data, 4);
}
void binary_write_string(std::ostream& stream, const std::string& string)
{
size_t slen = string.length();
binary_write_number(stream, slen);
std::copy(string.begin(), string.end(), std::ostream_iterator<char>(stream));
}
void binary_write_string_implicit(std::ostream& stream, const std::string& string)
{
std::copy(string.begin(), string.end(), std::ostream_iterator<char>(stream));
}
void binary_write_blob(std::ostream& stream, const std::vector<char>& blob)
{
stream.write(&blob[0], blob.size());
}
void binary_write_extension(std::ostream& stream, uint32_t tag, std::function<void(std::ostream& s)> fn,
bool even_empty = false);
void binary_write_extension(std::ostream& stream, uint32_t tag, std::function<void(std::ostream& s)> fn,
bool even_empty)
{
std::ostringstream tmp;
fn(tmp);
std::string str = tmp.str();
if(!even_empty && !str.length())
return;
binary_write_number32(stream, TAG_);
binary_write_number32(stream, tag);
binary_write_string(stream, str);
}
uint8_t binary_read_byte(std::istream& stream)
{
char byte;
stream.read(&byte, 1);
if(!stream)
throw std::runtime_error("Unexpected EOF");
return byte;
}
uint32_t binary_read_number32(std::istream& stream)
{
char c[4];
stream.read(c, 4);
if(!stream)
throw std::runtime_error("Unexpected EOF");
return read32ube(c);
}
uint64_t binary_read_number(std::istream& stream)
{
uint64_t s = 0;
int sh = 0;
uint8_t c;
do {
c = binary_read_byte(stream);
s |= (static_cast<uint64_t>(c & 0x7F) << sh);
sh += 7;
} while(c & 0x80);
if(!stream)
throw std::runtime_error("Unexpected EOF");
return s;
}
std::string binary_read_string(std::istream& stream)
{
size_t sz = binary_read_number(stream);
std::vector<char> _r;
_r.resize(sz);
stream.read(&_r[0], _r.size());
if(!stream)
throw std::runtime_error("Unexpected EOF");
std::string r(_r.begin(), _r.end());
return r;
}
class extension_stream
{
public:
extension_stream(std::istream& s, uint64_t _left) : under(s), left(_left) {}
uint64_t binary_read_number()
{
uint64_t s = 0;
int sh = 0;
uint8_t c;
do {
c = binary_read_byte();
s |= (static_cast<uint64_t>(c & 0x7F) << sh);
sh += 7;
} while(c & 0x80);
return s;
}
void binary_read_movie(controller_frame_vector& v)
{
uint64_t stride = v.get_stride();
uint64_t pageframes = v.get_frames_per_page();
uint64_t vsize = 0;
size_t pagenum = 0;
uint64_t pagesize = stride * pageframes;
while(left) {
v.resize(vsize + pageframes);
unsigned char* contents = v.get_page_buffer(pagenum++);
uint64_t gcount = min(pagesize, left);
read_stream(reinterpret_cast<char*>(contents), gcount);
vsize += (gcount / stride);
}
v.resize(vsize);
}
uint32_t binary_read_number32()
{
char c[4];
read_stream(c, 4);
return read32ube(c);
}
std::string binary_read_string()
{
size_t l = binary_read_number();
std::vector<char> _r;
_r.resize(l);
read_stream(&_r[0], l);
std::string r(_r.begin(), _r.end());
return r;
}
std::string binary_read_string_implicit()
{
std::vector<char> _r;
_r.resize(left);
read_stream(&_r[0], left);
std::string r(_r.begin(), _r.end());
return r;
}
uint8_t binary_read_byte()
{
char c;
read_stream(&c, 1);
return c;
}
void binary_read_blob(std::vector<char>& v)
{
v.resize(left);
read_stream(&v[0], left);
}
private:
void read_stream(char* dest, size_t size)
{
if(size > left)
throw std::runtime_error("Substream unexpected EOF");
under.read(dest, size);
if(!under)
throw std::runtime_error("Unexpected EOF");
left -= size;
}
std::istream& under;
uint64_t left;
};
void for_each_extension(std::istream& stream, std::function<void(uint32_t tag, extension_stream& s)> fn)
{
while(stream) {
char c[4];
stream.read(c, 4);
if(!stream)
break;
uint32_t tagid = read32ube(c);
if(tagid != TAG_)
throw std::runtime_error("Movie file packet structure desync");
uint32_t tag = binary_read_number32(stream);
uint64_t size = binary_read_number(stream);
extension_stream strm(stream, size);
fn(tag, strm);
}
}
void binary_write_movie(std::ostream& stream, controller_frame_vector& v)
{
uint64_t pages = v.get_page_count();
uint64_t stride = v.get_stride();
uint64_t pageframes = v.get_frames_per_page();
uint64_t vsize = v.size();
binary_write_number32(stream, TAG_);
binary_write_number32(stream, TAG_MOVIE);
binary_write_number(stream, vsize * stride);
size_t pagenum = 0;
while(vsize > 0) {
uint64_t count = (vsize > pageframes) ? pageframes : vsize;
size_t bytes = count * stride;
unsigned char* content = v.get_page_buffer(pagenum++);
stream.write(reinterpret_cast<char*>(content), bytes);
vsize -= count;
}
}
std::map<std::string, std::string> read_settings(zip_reader& r)
{
std::map<std::string, std::string> x;
@ -63,18 +315,17 @@ namespace
return x;
}
void write_settings(zip_writer& w, const std::map<std::string, std::string>& settings,
core_setting_group& sgroup)
template<typename target>
void write_settings(target& w, const std::map<std::string, std::string>& settings,
core_setting_group& sgroup, std::function<void(target& w, const std::string& name,
const std::string& value)> writefn)
{
for(auto i : settings) {
if(!sgroup.settings.count(i.first))
continue;
if(sgroup.settings.find(i.first)->second.dflt == i.second)
continue;
if(regex_match("port[0-9]+", i.first))
write_linefile(w, i.first, i.second);
else
write_linefile(w, "setting." + i.first, i.second);
writefn(w, i.first, i.second);
}
}
@ -413,6 +664,17 @@ moviefile::moviefile(const std::string& movie, core_type& romtype) throw(std::ba
is_savestate = false;
lazy_project_create = false;
std::string tmp;
{
std::istream& s = open_file_relative(movie, "");
char buf[6] = {0};
s.read(buf, 5);
if(!strcmp(buf, "lsmv\x1A")) {
binary_io(s, romtype);
delete &s;
return;
}
delete &s;
}
zip_reader r(movie);
read_linefile(r, "systemid", tmp);
if(tmp.substr(0, 8) != "lsnes-rr")
@ -487,11 +749,36 @@ moviefile::moviefile(const std::string& movie, core_type& romtype) throw(std::ba
read_input(r, input, 0);
}
void moviefile::save(const std::string& movie, unsigned compression) throw(std::bad_alloc, std::runtime_error)
void moviefile::save(const std::string& movie, unsigned compression, bool binary) throw(std::bad_alloc,
std::runtime_error)
{
if(binary) {
std::string tmp = movie + ".tmp";
std::ofstream strm(tmp.c_str(), std::ios_base::binary);
if(!strm)
throw std::runtime_error("Can't open output file");
char buf[5] = {'l', 's', 'm', 'v', 0x1A};
strm.write(buf, 5);
if(!strm)
throw std::runtime_error("Failed to write to output file");
binary_io(strm);
if(!strm)
throw std::runtime_error("Failed to write to output file");
strm.close();
std::string backup = movie + ".backup";
lsnes_rename(movie.c_str(), backup.c_str());
lsnes_rename(tmp.c_str(), movie.c_str());
return;
}
zip_writer w(movie, compression);
write_linefile(w, "gametype", gametype->get_name());
write_settings(w, settings, gametype->get_type().get_settings());
write_settings<zip_writer>(w, settings, gametype->get_type().get_settings(), [](zip_writer& w,
const std::string& name, const std::string& value) -> void {
if(regex_match("port[0-9]+", name))
write_linefile(w, name, value);
else
write_linefile(w, "setting." + name, value);
});
write_linefile(w, "gamename", gamename, true);
write_linefile(w, "systemid", "lsnes-rr1");
write_linefile(w, "controlsversion", "0");
@ -534,6 +821,249 @@ void moviefile::save(const std::string& movie, unsigned compression) throw(std::
w.commit();
}
/*
Following need to be saved:
- gametype (string)
- settings (string name, value pairs)
- gamename (optional string)
- core version (string)
- project id (string
- rrdata (blob)
- ROM hashes (2*27 table of optional strings)
- Subtitles (list of number,number,string)
- SRAMs (dictionary string->blob.)
- Starttime (number,number)
- Anchor savestate (optional blob)
- Save frame (savestate-only, numeric).
- Lag counter (savestate-only, numeric).
- pollcounters (savestate-only, vector of numbers).
- hostmemory (savestate-only, blob).
- screenshot (savestate-only, blob).
- Save SRAMs (savestate-only, dictionary string->blob.)
- Save time (savestate-only, number,number)
- Poll flag (savestate-only, boolean)
- Macros (savestate-only, ???)
- Authors (list of string,string).
- Input (blob).
- Extensions (???)
*/
void moviefile::binary_io(std::ostream& stream) throw(std::bad_alloc, std::runtime_error)
{
binary_write_string(stream, gametype->get_name());
write_settings<std::ostream>(stream, settings, gametype->get_type().get_settings(), [](std::ostream& s,
const std::string& name, const std::string& value) -> void {
binary_write_byte(s, 0x01);
binary_write_string(s, name);
binary_write_string(s, value);
});
binary_write_byte(stream, 0x00);
binary_write_extension(stream, TAG_MOVIE_TIME, [this](std::ostream& s) {
binary_write_number(s, movie_rtc_second);
binary_write_number(s, movie_rtc_subsecond);
});
binary_write_extension(stream, TAG_PROJECT_ID, [this](std::ostream& s) {
binary_write_string_implicit(s, this->projectid);
});
binary_write_extension(stream, TAG_CORE_VERSION, [this](std::ostream& s) {
this->coreversion = this->gametype->get_type().get_core_identifier();
binary_write_string_implicit(s, this->coreversion);
});
for(unsigned i = 0; i < sizeof(romimg_sha256) / sizeof(romimg_sha256[0]); i++) {
binary_write_extension(stream, TAG_ROMHASH, [this, i](std::ostream& s) {
if(!this->romimg_sha256[i].length()) return;
binary_write_byte(s, 2 * i);
binary_write_string_implicit(s, romimg_sha256[i]);
});
binary_write_extension(stream, TAG_ROMHASH, [this, i](std::ostream& s) {
if(!this->romxml_sha256[i].length()) return;
binary_write_byte(s, 2 * i + 1);
binary_write_string_implicit(s, romxml_sha256[i]);
});
}
binary_write_extension(stream, TAG_RRDATA, [this](std::ostream& s) {
uint64_t count;
std::vector<char> rrd;
count = rrdata::write(rrd);
binary_write_blob(s, rrd);
});
for(auto i : movie_sram) {
binary_write_extension(stream, TAG_MOVIE_SRAM, [&i](std::ostream& s) {
binary_write_string(s, i.first);
binary_write_blob(s, i.second);
});
}
binary_write_extension(stream, TAG_ANCHOR_SAVE, [this](std::ostream& s) {
binary_write_blob(s, this->anchor_savestate);
});
if(is_savestate) {
binary_write_extension(stream, TAG_SAVESTATE, [this](std::ostream& s) {
binary_write_number(s, this->save_frame);
binary_write_number(s, this->lagged_frames);
binary_write_number(s, this->rtc_second);
binary_write_number(s, this->rtc_subsecond);
binary_write_number(s, this->pollcounters.size());
for(auto i : this->pollcounters)
binary_write_number32(s, i);
binary_write_byte(s, this->poll_flag ? 0x01 : 0x00);
binary_write_blob(s, this->savestate);
});
binary_write_extension(stream, TAG_HOSTMEMORY, [this](std::ostream& s) {
binary_write_blob(s, this->host_memory);
});
binary_write_extension(stream, TAG_SCREENSHOT, [this](std::ostream& s) {
binary_write_blob(s, this->screenshot);
});
for(auto i : sram) {
binary_write_extension(stream, TAG_SAVE_SRAM, [&i](std::ostream& s) {
binary_write_string(s, i.first);
binary_write_blob(s, i.second);
});
}
}
binary_write_extension(stream, TAG_GAMENAME, [this](std::ostream& s) {
binary_write_string_implicit(s, this->gamename);
});
for(auto i : subtitles)
binary_write_extension(stream, TAG_SUBTITLE, [&i](std::ostream& s) {
binary_write_number(s, i.first.get_frame());
binary_write_number(s, i.first.get_length());
binary_write_string_implicit(s, i.second);
});
for(auto i : authors)
binary_write_extension(stream, TAG_AUTHOR, [&i](std::ostream& s) {
binary_write_string(s, i.first);
binary_write_string_implicit(s, i.second);
});
for(auto i : active_macros)
binary_write_extension(stream, TAG_MACRO, [&i](std::ostream& s) {
binary_write_number(s, i.second);
binary_write_string_implicit(s, i.first);
});
binary_write_movie(stream, input);
}
void moviefile::binary_io(std::istream& stream, core_type& romtype) throw(std::bad_alloc, std::runtime_error)
{
std::string tmp = binary_read_string(stream);
try {
gametype = &romtype.lookup_sysregion(tmp);
} catch(std::bad_alloc& e) {
throw;
} catch(std::exception& e) {
throw std::runtime_error("Illegal game type '" + tmp + "'");
}
while(binary_read_byte(stream)) {
std::string name = binary_read_string(stream);
settings[name] = binary_read_string(stream);
}
auto ctrldata = gametype->get_type().controllerconfig(settings);
port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex());
input.clear(ports);
for_each_extension(stream, [this](uint32_t tag, extension_stream& s) {
switch(tag) {
case TAG_ANCHOR_SAVE:
s.binary_read_blob(this->anchor_savestate);
break;
case TAG_AUTHOR: {
std::string a = s.binary_read_string();
std::string b = s.binary_read_string_implicit();
this->authors.push_back(std::make_pair(a, b));
break;
}
case TAG_CORE_VERSION:
this->coreversion = s.binary_read_string_implicit();
break;
case TAG_GAMENAME:
this->gamename = s.binary_read_string_implicit();
break;
case TAG_HOSTMEMORY:
s.binary_read_blob(this->host_memory);
break;
case TAG_MACRO: {
uint64_t n = s.binary_read_number();
this->active_macros[s.binary_read_string_implicit()] = n;
break;
}
case TAG_MOVIE: {
s.binary_read_movie(input);
break;
}
case TAG_MOVIE_SRAM: {
std::string a = s.binary_read_string();
s.binary_read_blob(this->movie_sram[a]);
break;
}
case TAG_MOVIE_TIME:
this->movie_rtc_second = s.binary_read_number();
this->movie_rtc_subsecond = s.binary_read_number();
break;
case TAG_PROJECT_ID:
this->projectid = s.binary_read_string_implicit();
break;
case TAG_ROMHASH: {
uint8_t n = s.binary_read_byte();
std::string h = s.binary_read_string_implicit();
if(n > 2 * (sizeof(this->romimg_sha256) / sizeof(romimg_sha256[0])))
break;
if(n & 1)
romxml_sha256[n >> 1] = h;
else
romimg_sha256[n >> 1] = h;
break;
}
case TAG_RRDATA: {
s.binary_read_blob(c_rrdata);
this->rerecords = (stringfmt() << rrdata::count(c_rrdata)).str();
break;
}
case TAG_SAVE_SRAM: {
std::string a = s.binary_read_string();
s.binary_read_blob(this->sram[a]);
break;
}
case TAG_SAVESTATE: {
this->is_savestate = true;
this->save_frame = s.binary_read_number();
this->lagged_frames = s.binary_read_number();
this->rtc_second = s.binary_read_number();
this->rtc_subsecond = s.binary_read_number();
this->pollcounters.resize(s.binary_read_number());
for(auto& i : this->pollcounters)
i = s.binary_read_number32();
this->poll_flag = (s.binary_read_byte() != 0);
s.binary_read_blob(this->savestate);
break;
}
case TAG_SCREENSHOT:
s.binary_read_blob(this->screenshot);
break;
case TAG_SUBTITLE: {
uint64_t f = s.binary_read_number();
uint64_t l = s.binary_read_number();
std::string x = s.binary_read_string_implicit();
this->subtitles[moviefile_subtiming(f, l)] = x;
break;
}
default:
break;
}
});
}
uint64_t moviefile::get_frame_count() throw()
{
return input.count_frames();

View file

@ -46,6 +46,9 @@ namespace
const char* null_chars = "F";
uint16_t null_cover_fbmem[512 * 448];
setting_var<setting_var_model_bool<setting_yes_no>> savestate_no_check(lsnes_vset, "dont-check-savestate",
"Movie‣Loading‣Don't check savestates", false);
//Framebuffer.
struct framebuffer_info null_fbinfo = {
&_pixel_format_bgr16, //Format.
@ -533,10 +536,12 @@ void loaded_rom::load_core_state(const std::vector<char>& buf, bool nochecksum)
if(buf.size() < 32)
throw std::runtime_error("Savestate corrupt");
unsigned char tmp[32];
sha256::hash(tmp, reinterpret_cast<const uint8_t*>(&buf[0]), buf.size() - 32);
if(memcmp(tmp, &buf[buf.size() - 32], 32))
throw std::runtime_error("Savestate corrupt");
if(!savestate_no_check) {
unsigned char tmp[32];
sha256::hash(tmp, reinterpret_cast<const uint8_t*>(&buf[0]), buf.size() - 32);
if(memcmp(tmp, &buf[buf.size() - 32], 32))
throw std::runtime_error("Savestate corrupt");
}
rtype->unserialize(&buf[0], buf.size() - 32);;
}

View file

@ -1080,12 +1080,13 @@ namespace
{
filedialog_input_params p;
std::string ext = state ? project_savestate_ext() : "lsmv";
if(save)
p.types.push_back(filedialog_type_entry(state ? "Savestates" : "Movies", "*." + ext,
std::string name = state ? "Savestates" : "Movies";
if(save) {
p.types.push_back(filedialog_type_entry(name, "*." + ext, ext));
p.types.push_back(filedialog_type_entry(name + " (binary)", "*." + ext, ext));
} else
p.types.push_back(filedialog_type_entry(name, "*." + ext + ";*." + ext + ".backup",
ext));
else
p.types.push_back(filedialog_type_entry(state ? "Savestates" : "Movies", "*." + ext +
";*." + ext + ".backup", ext));
if(!save && state) {
p.types.push_back(filedialog_type_entry("Savestates [read only]", "*." + ext +
";*." + ext + ".backup", ext));
@ -1094,17 +1095,20 @@ namespace
p.types.push_back(filedialog_type_entry("Savestates [preserve]", "*." + ext +
";*." + ext + ".backup", ext));
}
p.default_type = 0;
p.default_type = save ? (state ? save_dflt_binary : movie_dflt_binary) : 0;
return p;
}
std::pair<std::string, std::string> output(const filedialog_output_params& p, bool save) const
{
std::string cmdmod;
switch(p.typechoice) {
case 0: cmdmod = ""; break;
case 1: cmdmod = "-readonly"; break;
case 2: cmdmod = "-state"; break;
case 3: cmdmod = "-preserve"; break;
if(save)
cmdmod = p.typechoice ? "-binary" : "-zip";
else if(state)
switch(p.typechoice) {
case 0: cmdmod = ""; break;
case 1: cmdmod = "-readonly"; break;
case 2: cmdmod = "-state"; break;
case 3: cmdmod = "-preserve"; break;
}
return std::make_pair(cmdmod, p.path);
}
@ -1161,19 +1165,19 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
platform::queue("rewind-movie");
return;
case wxID_SAVE_MOVIE:
filename = choose_file_save(this, "Save Movie", project_moviepath(), filetype_movie,
project_prefixname("lsmv")).second;
recent_movies->add(filename);
platform::queue("save-movie " + filename);
filename2 = choose_file_save(this, "Save Movie", project_moviepath(), filetype_movie,
project_prefixname("lsmv"));
recent_movies->add(filename2.second);
platform::queue("save-movie" + filename2.first + " " + filename2.second);
return;
case wxID_SAVE_SUBTITLES:
platform::queue("save-subtitle " + choose_file_save(this, "Save subtitles", project_moviepath(),
filetype_sub, project_prefixname("sub")));
return;
case wxID_SAVE_STATE:
filename = choose_file_save(this, "Save State", project_moviepath(), filetype_savestate).second;
recent_movies->add(filename);
platform::queue("save-state " + filename);
filename2 = choose_file_save(this, "Save State", project_moviepath(), filetype_savestate);
recent_movies->add(filename2.second);
platform::queue("save-state" + filename2.first + " " + filename2.second);
return;
case wxID_SAVE_SCREENSHOT:
platform::queue("take-screenshot " + choose_file_save(this, "Save Screenshot", project_moviepath(),

View file

@ -583,7 +583,7 @@ void wxwin_project::on_load(wxCommandEvent& e)
try {
moviefile mov = make_movie();
mov.start_paused = false;
mov.save(get_config_path() + "/movie.tmp", 0);
mov.save(get_config_path() + "/movie.tmp", 0, true);
platform::queue("load-state " + get_config_path() + "/movie.tmp");
EndModal(0);
} catch(std::exception& e) {