lsnes rr2-β24
[lsnes.git] / src / core / moviefile-text-load.cpp
blob6c25595cf1e0f8ac55c3cc84fcf8d1a281b32d79
1 #include "core/messages.hpp"
2 #include "core/moviedata.hpp"
3 #include "core/moviefile-common.hpp"
4 #include "core/moviefile.hpp"
5 #include "library/binarystream.hpp"
6 #include "library/minmax.hpp"
7 #include "library/serialization.hpp"
8 #include "library/string.hpp"
9 #include "library/zip.hpp"
11 #include <fcntl.h>
12 #include <unistd.h>
13 #include <iostream>
14 #include <algorithm>
15 #include <sstream>
16 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
17 #include <windows.h>
18 #endif
20 namespace
22 std::map<std::string, std::string> read_settings(zip::reader& r)
24 std::map<std::string, std::string> x;
25 for(auto i : r) {
26 if(!regex_match("port[0-9]+|setting\\..+", i))
27 continue;
28 std::string s;
29 std::string v;
30 if(i.substr(0, 4) == "port")
31 s = i;
32 else
33 s = i.substr(8);
34 if(r.read_linefile(i, v, true))
35 x[s] = v;
37 return x;
40 std::map<std::string, uint64_t> read_active_macros(zip::reader& r, const std::string& member)
42 std::map<std::string, uint64_t> x;
43 if(!r.has_member(member))
44 return x;
45 std::istream& m = r[member];
46 try {
47 while(m) {
48 std::string out;
49 std::getline(m, out);
50 istrip_CR(out);
51 if(out == "")
52 continue;
53 regex_results rx = regex("([0-9]+) +(.*)", out);
54 if(!rx) {
55 messages << "Warning: Bad macro state: '" << out << "'" << std::endl;
56 continue;
58 try {
59 uint64_t f = parse_value<uint64_t>(rx[1]);
60 x[rx[2]] = f;
61 } catch(...) {
64 delete &m;
65 } catch(...) {
66 delete &m;
67 throw;
69 return x;
72 template<typename T> std::string pick_a_name(const std::map<std::string, T>& map, bool prefer_unnamed)
74 if(prefer_unnamed && !map.count(""))
75 return "";
76 size_t count = 1;
77 while(true) {
78 std::string c = (stringfmt() << "(unnamed branch #" << count++ << ")").str();
79 if(!map.count(c))
80 return c;
84 void read_authors_file(zip::reader& r, std::vector<std::pair<std::string, std::string>>& authors)
85 throw(std::bad_alloc, std::runtime_error)
87 std::istream& m = r["authors"];
88 try {
89 std::string x;
90 while(std::getline(m, x)) {
91 istrip_CR(x);
92 auto g = split_author(x);
93 authors.push_back(g);
95 delete &m;
96 } catch(...) {
97 delete &m;
98 throw;
102 std::string read_rrdata(zip::reader& r, std::vector<char>& out) throw(std::bad_alloc, std::runtime_error)
104 r.read_raw_file("rrdata", out);
105 uint64_t count = rrdata_set::count(out);
106 std::ostringstream x;
107 x << count;
108 return x.str();
111 void read_subtitles(zip::reader& r, const std::string& file, std::map<moviefile_subtiming, std::string>& x)
113 x.clear();
114 if(!r.has_member(file))
115 return;
116 std::istream& m = r[file];
117 try {
118 while(m) {
119 std::string out;
120 std::getline(m, out);
121 istrip_CR(out);
122 auto r = regex("([0-9]+)[ \t]+([0-9]+)[ \t]+(.*)", out);
123 if(!r)
124 continue;
125 x[moviefile_subtiming(parse_value<uint64_t>(r[1]), parse_value<uint64_t>(r[2]))] =
126 subtitle_commentary::s_unescape(r[3]);
128 delete &m;
129 } catch(...) {
130 delete &m;
131 throw;
135 void read_input(zip::reader& r, const std::string& mname, portctrl::frame_vector& input)
136 throw(std::bad_alloc, std::runtime_error)
138 portctrl::frame tmp = input.blank_frame(false);
139 std::istream& m = r[mname];
140 try {
141 std::string x;
142 while(std::getline(m, x)) {
143 istrip_CR(x);
144 if(x != "") {
145 tmp.deserialize(x.c_str());
146 input.append(tmp);
149 delete &m;
150 } catch(...) {
151 delete &m;
152 throw;
156 void read_pollcounters(zip::reader& r, const std::string& file, std::vector<uint32_t>& pctr)
158 std::istream& m = r[file];
159 try {
160 std::string x;
161 while(std::getline(m, x)) {
162 istrip_CR(x);
163 if(x != "") {
164 int32_t y = parse_value<int32_t>(x);
165 uint32_t z = 0;
166 if(y < 0)
167 z = -(y + 1);
168 else {
169 z = y;
170 z |= 0x80000000UL;
172 pctr.push_back(z);
175 delete &m;
176 } catch(...) {
177 delete &m;
178 throw;
182 std::string get_namefile(const std::string& input)
184 regex_results s;
185 if(input == "input")
186 return "branchname.0";
187 else if(s = regex("input\\.([1-9][0-9]*)", input))
188 return "branchname." + s[1];
189 else
190 return "";
194 void moviefile::brief_info::load(zip::reader& r)
196 std::string tmp;
197 r.read_linefile("systemid", tmp);
198 if(tmp.substr(0, 8) != "lsnes-rr")
199 throw std::runtime_error("Not lsnes movie");
200 r.read_linefile("gametype", sysregion);
201 r.read_linefile("coreversion", corename);
202 r.read_linefile("projectid", projectid);
203 if(r.has_member("savestate"))
204 r.read_numeric_file("saveframe", current_frame);
205 else
206 current_frame = 0;
207 r.read_numeric_file("rerecords", rerecords);
208 r.read_linefile("rom.sha256", hash[0], true);
209 r.read_linefile("romxml.sha256", hashxml[0], true);
210 r.read_linefile("rom.hint", hint[0], true);
211 unsigned base = 97;
212 if(r.has_member("slot`.sha256"))
213 base = 96;
214 for(size_t i = 1; i < ROM_SLOT_COUNT; i++) {
215 r.read_linefile((stringfmt() << "slot" << (char)(base + i - 1) << ".sha256").str(), hash[i],
216 true);
217 r.read_linefile((stringfmt() << "slot" << (char)(base + i - 1) << "xml.sha256").str(),
218 hashxml[i], true);
219 r.read_linefile((stringfmt() << "slot" << (char)(base + i - 1) << ".hint").str(), hint[i],
220 true);
224 void moviefile::load(zip::reader& r, core_type& romtype) throw(std::bad_alloc, std::runtime_error)
226 std::string tmp;
227 r.read_linefile("systemid", tmp);
228 if(tmp.substr(0, 8) != "lsnes-rr")
229 throw std::runtime_error("Not lsnes movie");
230 r.read_linefile("controlsversion", tmp);
231 if(tmp != "0")
232 throw std::runtime_error("Can't decode movie data");
233 r.read_linefile("gametype", tmp);
234 try {
235 gametype = &romtype.lookup_sysregion(tmp);
236 } catch(std::bad_alloc& e) {
237 throw;
238 } catch(std::exception& e) {
239 throw std::runtime_error("Illegal game type '" + tmp + "'");
241 settings = read_settings(r);
242 auto ctrldata = gametype->get_type().controllerconfig(settings);
243 portctrl::type_set& ports = portctrl::type_set::make(ctrldata.ports, ctrldata.portindex());
244 dyn.save_frame = 0; //Ensure load as movie if no savestate.
246 branches.clear();
247 r.read_linefile("gamename", gamename, true);
248 r.read_linefile("projectid", projectid);
249 rerecords = read_rrdata(r, c_rrdata);
250 r.read_linefile("coreversion", coreversion);
251 r.read_linefile("rom.sha256", romimg_sha256[0], true);
252 r.read_linefile("romxml.sha256", romxml_sha256[0], true);
253 r.read_linefile("rom.hint", namehint[0], true);
254 unsigned base = 97;
255 if(r.has_member("slot`.sha256"))
256 base = 96;
257 for(size_t i = 1; i < ROM_SLOT_COUNT; i++) {
258 r.read_linefile((stringfmt() << "slot" << (char)(base + i - 1) << ".sha256").str(), romimg_sha256[i],
259 true);
260 r.read_linefile((stringfmt() << "slot" << (char)(base + i - 1) << "xml.sha256").str(),
261 romxml_sha256[i], true);
262 r.read_linefile((stringfmt() << "slot" << (char)(base + i - 1) << ".hint").str(), namehint[i],
263 true);
265 read_subtitles(r, "subtitles", subtitles);
266 movie_rtc_second = DEFAULT_RTC_SECOND;
267 movie_rtc_subsecond = DEFAULT_RTC_SUBSECOND;
268 r.read_numeric_file("starttime.second", movie_rtc_second, true);
269 r.read_numeric_file("starttime.subsecond", movie_rtc_subsecond, true);
270 dyn.rtc_second = movie_rtc_second;
271 dyn.rtc_subsecond = movie_rtc_subsecond;
272 if(r.has_member("savestate.anchor"))
273 r.read_raw_file("savestate.anchor", anchor_savestate);
274 if(r.has_member("savestate")) {
275 r.read_numeric_file("saveframe", dyn.save_frame, true);
276 r.read_numeric_file("lagcounter", dyn.lagged_frames, true);
277 read_pollcounters(r, "pollcounters", dyn.pollcounters);
278 if(r.has_member("hostmemory"))
279 r.read_raw_file("hostmemory", dyn.host_memory);
280 r.read_raw_file("savestate", dyn.savestate);
281 for(auto name : r)
282 if(name.length() >= 5 && name.substr(0, 5) == "sram.")
283 r.read_raw_file(name, dyn.sram[name.substr(5)]);
284 r.read_raw_file("screenshot", dyn.screenshot);
285 //If these can't be read, just use some (wrong) values.
286 r.read_numeric_file("savetime.second", dyn.rtc_second, true);
287 r.read_numeric_file("savetime.subsecond", dyn.rtc_subsecond, true);
288 uint64_t _poll_flag = 2; //Legacy behaviour is the default.
289 r.read_numeric_file("pollflag", _poll_flag, true);
290 dyn.poll_flag = _poll_flag;
291 dyn.active_macros = read_active_macros(r, "macros");
293 for(auto name : r)
294 if(name.length() >= 8 && name.substr(0, 8) == "initram.")
295 r.read_raw_file(name, ramcontent[name.substr(8)]);
296 if(dyn.rtc_subsecond < 0 || movie_rtc_subsecond < 0)
297 throw std::runtime_error("Invalid RTC subsecond value");
298 std::string name = r.find_first();
299 for(auto name : r)
300 if(name.length() >= 10 && name.substr(0, 10) == "moviesram.")
301 r.read_raw_file(name, movie_sram[name.substr(10)]);
302 read_authors_file(r, authors);
304 std::map<uint64_t, std::string> branch_table;
305 //Load branch names.
306 for(auto name : r) {
307 regex_results s;
308 if(s = regex("branchname\\.([0-9]+)", name)) {
309 uint64_t n = parse_value<uint64_t>(s[1]);
310 r.read_linefile(name, branch_table[n]);
311 branches[branch_table[n]].clear(ports);
315 for(auto name : r) {
316 regex_results s;
317 if(name == "input") {
318 std::string bname = branch_table.count(0) ? branch_table[0] : pick_a_name(branches, true);
319 if(!branches.count(bname)) branches[bname].clear(ports);
320 read_input(r, name, branches[bname]);
321 input = &branches[bname];
322 } else if(s = regex("input\\.([1-9][0-9]*)", name)) {
323 uint64_t n = parse_value<uint64_t>(s[1]);
324 std::string bname = branch_table.count(n) ? branch_table[n] : pick_a_name(branches, false);
325 if(!branches.count(bname)) branches[bname].clear(ports);
326 read_input(r, name, branches[bname]);
330 create_default_branch(ports);
333 moviefile_branch_extractor_text::moviefile_branch_extractor_text(const std::string& filename)
334 : z(filename)
338 moviefile_branch_extractor_text::~moviefile_branch_extractor_text()
342 std::set<std::string> moviefile_branch_extractor_text::enumerate()
344 std::set<std::string> r;
345 for(auto& i : z) {
346 std::string bname;
347 std::string n = get_namefile(i);
348 if(n != "") {
349 if(z.has_member(n)) {
350 z.read_linefile(n, bname);
351 r.insert(bname);
352 } else
353 r.insert("");
356 return r;
359 void moviefile_branch_extractor_text::read(const std::string& name, portctrl::frame_vector& v)
361 std::set<std::string> r;
362 bool done = false;
363 for(auto& i : z) {
364 std::string bname;
365 std::string n = get_namefile(i);
366 if(n != "") {
367 std::string bname;
368 if(z.has_member(n))
369 z.read_linefile(n, bname);
370 if(name == bname) {
371 v.clear();
372 read_input(z, i, v);
373 done = true;
377 if(!done)
378 (stringfmt() << "Can't find branch '" << name << "' in file.").throwex();
382 moviefile_sram_extractor_text::moviefile_sram_extractor_text(const std::string& filename)
383 : z(filename)
387 moviefile_sram_extractor_text::~moviefile_sram_extractor_text()
391 std::set<std::string> moviefile_sram_extractor_text::enumerate()
393 std::set<std::string> r;
394 for(auto& i : z) {
395 regex_results rgx;
396 if(i == "savestate")
397 r.insert("");
398 else if(rgx = regex("sram\\.(.*)", i))
399 r.insert(rgx[1]);
401 return r;
404 void moviefile_sram_extractor_text::read(const std::string& name, std::vector<char>& v)
406 std::set<std::string> r;
407 std::string realname;
408 if(name == "") {
409 //Try to read savestate.
410 realname = "savestate";
411 } else {
412 realname = "sram." + name;
414 try {
415 z.read_raw_file(realname, v);
416 } catch(...) {
417 if(name != "")
418 (stringfmt() << "Can't find SRAM '" << name << "' in file.").throwex();
419 else
420 (stringfmt() << "Can't find savestate in file.").throwex();