Reinitialize gamepads command and fix EVDEV going bonkers on gamepad suddenly disconn...
[lsnes.git] / src / core / project.cpp
blob24894b25eda88764fd9ebc48eb7c54aaa65ca53a
1 #include "cmdhelp/project.hpp"
2 #include "core/command.hpp"
3 #include "core/controller.hpp"
4 #include "core/dispatch.hpp"
5 #include "core/emustatus.hpp"
6 #include "core/instance.hpp"
7 #include "core/inthread.hpp"
8 #include "core/mainloop.hpp"
9 #include "core/memorywatch.hpp"
10 #include "core/messages.hpp"
11 #include "core/moviedata.hpp"
12 #include "core/moviefile.hpp"
13 #include "core/project.hpp"
14 #include "core/queue.hpp"
15 #include "core/window.hpp"
16 #include "library/directory.hpp"
17 #include "library/minmax.hpp"
18 #include "library/string.hpp"
20 #include <fstream>
21 #include <dirent.h>
22 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
23 #define FUCKED_SYSTEM
24 #include <windows.h>
25 #endif
27 void do_flush_slotinfo();
29 namespace
31 void concatenate(std::vector<char>& data, const std::vector<char>& app)
33 size_t dsize = data.size();
34 data.resize(dsize + app.size());
35 std::copy(app.begin(), app.end(), data.begin() + dsize);
38 std::vector<char> base_decode(const std::string& str)
40 std::vector<char> r;
41 size_t len = str.length();
42 size_t i;
43 uint32_t a, b, c, d, e, v;
44 for(i = 0; i + 5 <= len; i += 5) {
45 a = str[i + 0] - 33;
46 b = str[i + 1] - 33;
47 c = str[i + 2] - 33;
48 d = str[i + 3] - 33;
49 e = str[i + 4] - 33;
50 v = 52200625 * a + 614125 * b + 7225 * c + 85 * d + e;
51 r.push_back(v);
52 r.push_back(v >> 8);
53 r.push_back(v >> 16);
54 r.push_back(v >> 24);
56 a = b = c = d = e = 0;
57 if(i + 0 < len) e = str[len - 1] - 33;
58 if(i + 1 < len) d = str[len - 2] - 33;
59 if(i + 2 < len) c = str[len - 3] - 33;
60 if(i + 3 < len) b = str[len - 4] - 33;
61 v = 614125 * b + 7225 * c + 85 * d + e;
62 if(i + 1 < len) r.push_back(v);
63 if(i + 2 < len) r.push_back(v >> 8);
64 if(i + 3 < len) r.push_back(v >> 16);
65 return r;
68 template<int x> void blockcode(std::ostringstream& s, const std::vector<char>& data, size_t& ptr,
69 size_t& chars)
71 uint32_t a = 0, b = 0, c = 0, d = 0, v;
72 if(x >= 4) a = static_cast<uint8_t>(data[ptr++]);
73 if(x >= 3) b = static_cast<uint8_t>(data[ptr++]);
74 if(x >= 2) c = static_cast<uint8_t>(data[ptr++]);
75 if(x >= 1) d = static_cast<uint8_t>(data[ptr++]);
76 v = 16777216 * d + 65536 * c + 256 * b + a;
77 v >>= (32 - 8 * x);
78 if(x >= 4) s << static_cast<char>(v / 52200625 % 85 + 33);
79 if(x >= 3) s << static_cast<char>(v / 614125 % 85 + 33);
80 if(x >= 2) s << static_cast<char>(v / 7225 % 85 + 33);
81 if(x >= 1) s << static_cast<char>(v / 85 % 85 + 33);
82 if(x >= 1) s << static_cast<char>(v % 85 + 33);
83 chars -= (x + 1);
86 std::pair<std::string, size_t> base_encode(const std::vector<char>& data, size_t ptr, size_t chars)
88 std::ostringstream s;
89 while(chars >= 5 && ptr + 4 <= data.size())
90 blockcode<4>(s, data, ptr, chars);
91 if(chars >= 4 && ptr + 3 <= data.size())
92 blockcode<3>(s, data, ptr, chars);
93 if(chars >= 3 && ptr + 2 <= data.size())
94 blockcode<2>(s, data, ptr, chars);
95 if(chars >= 2 && ptr + 1 <= data.size())
96 blockcode<1>(s, data, ptr, chars);
97 return std::make_pair(s.str(), ptr);
99 std::string eq_escape(const std::string& str)
101 std::ostringstream s;
102 size_t len = str.length();
103 for(size_t i = 0; i < len; i++) {
104 if(str[i] == '\\')
105 s << "\\\\";
106 else if(str[i] == '=')
107 s << "\\e";
108 else
109 s << str[i];
111 return s.str();
114 std::string eq_unescape(const std::string& str)
116 std::ostringstream s;
117 size_t len = str.length();
118 bool escape = false;
119 for(size_t i = 0; i < len; i++) {
120 if(escape) {
121 if(str[i] == 'e')
122 s << "=";
123 else
124 s << str[i];
125 escape = false;
126 } else {
127 if(str[i] == '\\')
128 escape = true;
129 else
130 s << str[i];
133 return s.str();
136 void save_binary(std::ostream& s, const std::string& key, const std::vector<char>& value)
138 size_t ptr = 0;
139 while(ptr < value.size()) {
140 auto v = base_encode(value, ptr, 60);
141 s << key << "=" << v.first << std::endl;
142 ptr = v.second;
146 void fill_stub_movie(struct moviefile& m, struct project_info& p, struct core_type& coretype)
148 //Create a dummy movie.
149 m.lazy_project_create = false;
150 m.start_paused = true;
151 m.movie_rtc_second = p.movie_rtc_second;
152 m.movie_rtc_subsecond = p.movie_rtc_subsecond;
153 m.save_frame = 0;
154 m.lagged_frames = 0;
155 m.anchor_savestate = p.anchor_savestate;
156 m.movie_sram = p.movie_sram;
157 m.authors = p.authors;
158 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
159 m.romimg_sha256[i] = p.romimg_sha256[i];
160 m.romxml_sha256[i] = p.romxml_sha256[i];
161 m.namehint[i] = p.namehint[i];
163 m.projectid = p.projectid;
164 m.coreversion = p.coreversion;
165 m.gamename = p.gamename;
166 m.settings = p.settings;
167 auto ctrldata = coretype.controllerconfig(m.settings);
168 portctrl::type_set& ports = portctrl::type_set::make(ctrldata.ports, ctrldata.portindex());
169 m.create_default_branch(ports);
170 try {
171 m.gametype = &coretype.lookup_sysregion(p.gametype);
172 } catch(std::bad_alloc& e) {
173 throw;
174 } catch(std::exception& e) {
175 throw std::runtime_error("Illegal game type '" + p.gametype + "'");
179 std::string project_getname(const std::string& id)
181 std::string file = get_config_path() + "/" + id + ".prj";
182 std::ifstream f(file);
183 if(!f)
184 throw std::runtime_error("Can't open project file");
185 std::string name;
186 std::getline(f, name);
187 if(!f)
188 throw std::runtime_error("Can't read project name");
189 return name;
193 project_state::project_state(voice_commentary& _commentary, memwatch_set& _mwatch, command::group& _command,
194 controller_state& _controls, settingvar::cache& _setcache, button_mapping& _buttons,
195 emulator_dispatch& _edispatch, input_queue& _iqueue, loaded_rom& _rom, status_updater& _supdater)
196 : commentary(_commentary), mwatch(_mwatch), command(_command), controls(_controls), setcache(_setcache),
197 buttons(_buttons), edispatch(_edispatch), iqueue(_iqueue), rom(_rom), supdater(_supdater),
198 branch_ls(command, STUBS::branch_ls, [this]() { this->do_branch_ls(); }),
199 branch_mk(command, STUBS::branch_mk, [this](const std::string& a) { this->do_branch_mk(a); }),
200 branch_rm(command, STUBS::branch_rm, [this](const std::string& a) { this->do_branch_rm(a); }),
201 branch_set(command, STUBS::branch_set, [this](const std::string& a) { this->do_branch_set(a); }),
202 branch_rp(command, STUBS::branch_rp, [this](const std::string& a) { this->do_branch_rp(a); }),
203 branch_mv(command, STUBS::branch_mv, [this](const std::string& a) { this->do_branch_mv(a); })
205 active_project = NULL;
208 project_state::~project_state()
212 project_info& project_state::load(const std::string& id)
214 std::string file = get_config_path() + "/" + id + ".prj";
215 std::ifstream f(file);
216 if(!f)
217 throw std::runtime_error("Can't open project file");
218 project_info& pi = *new project_info(edispatch);
219 pi.id = id;
220 pi.movie_rtc_second = 1000000000;
221 pi.movie_rtc_subsecond = 0;
222 pi.active_branch = 0;
223 pi.next_branch = 0;
224 pi.filename = file;
225 //First line is always project name.
226 std::getline(f, pi.name);
227 if(!f || pi.name == "") {
228 delete &pi;
229 throw std::runtime_error("Can't read project file");
231 while(f) {
232 std::string tmp;
233 std::getline(f, tmp);
234 regex_results r;
235 if(r = regex("rom=(.+)", tmp))
236 pi.rom = r[1];
237 else if(r = regex("last-save=(.+)", tmp))
238 pi.last_save = r[1];
239 else if(r = regex("directory=(.+)", tmp))
240 pi.directory = r[1];
241 else if(r = regex("prefix=(.+)", tmp))
242 pi.prefix = r[1];
243 else if(r = regex("luascript=(.+)", tmp))
244 pi.luascripts.push_back(r[1]);
245 else if(r = regex("gametype=(.+)", tmp))
246 pi.gametype = r[1];
247 else if(r = regex("coreversion=(.+)", tmp))
248 pi.coreversion = r[1];
249 else if(r = regex("gamename=(.+)", tmp))
250 pi.gamename = r[1];
251 else if(r = regex("projectid=(.+)", tmp))
252 pi.projectid = r[1];
253 else if(r = regex("projectid=([0-9]+):([0-9]+)", tmp)) {
254 pi.movie_rtc_second = parse_value<int64_t>(r[1]);
255 pi.movie_rtc_subsecond = parse_value<int64_t>(r[2]);
256 } else if(r = regex("author=(.*)\\|(.*)", tmp))
257 pi.authors.push_back(std::make_pair(r[1], r[2]));
258 else if(r = regex("author=(.+)", tmp))
259 pi.authors.push_back(std::make_pair(r[1], ""));
260 else if(r = regex("romsha=([0-9a-f]+)", tmp))
261 pi.romimg_sha256[0] = r[1];
262 else if(r = regex("slotsha([a-z])=([0-9a-f]+)", tmp))
263 pi.romimg_sha256[r[1][0] - 96] = r[2];
264 else if(r = regex("romxml=([0-9a-f]+)", tmp))
265 pi.romxml_sha256[0] = r[1];
266 else if(r = regex("slotxml([a-z])=([0-9a-f]+)", tmp))
267 pi.romxml_sha256[r[1][0] - 96] = r[2];
268 else if(r = regex("romhint=(.*)", tmp))
269 pi.namehint[0] = r[1];
270 else if(r = regex("slothint([a-z])=(.*)", tmp))
271 pi.namehint[r[1][0] - 96] = r[2];
272 else if(r = regex("romrom=(.*)", tmp))
273 pi.roms[0] = r[1];
274 else if(r = regex("slotrom([a-z])=(.*)", tmp))
275 pi.roms[r[1][0] - 96] = r[2];
276 else if(r = regex("setting.([^=]+)=(.*)", tmp))
277 pi.settings[r[1]] = r[2];
278 else if(r = regex("watch.([^=]+)=(.*)", tmp))
279 pi.watches[eq_unescape(r[1])] = r[2];
280 else if(r = regex("sram.([^=]+)=(.*)", tmp))
281 concatenate(pi.movie_sram[r[1]], base_decode(r[2]));
282 else if(r = regex("macro.([^=]+)=(.*)", tmp))
283 try {
284 pi.macros[r[1]] = JSON::node(r[2]);
285 } catch(std::exception& e) {
286 messages << "Unable to load macro '" << r[1] << "': " << e.what() << std::endl;
288 else if(r = regex("anchor=(.*)", tmp))
289 concatenate(pi.anchor_savestate, base_decode(r[1]));
290 else if(r = regex("time=([0-9]+):([0-9]+)", tmp)) {
291 pi.movie_rtc_second = parse_value<int64_t>(r[1]);
292 pi.movie_rtc_subsecond = parse_value<int64_t>(r[2]);
293 } else if(r = regex("branch([1-9][0-9]*)parent=([0-9]+)", tmp)) {
294 uint64_t bid = parse_value<int64_t>(r[1]);
295 uint64_t pbid = parse_value<int64_t>(r[2]);
296 if(!pi.branches.count(bid))
297 pi.branches[bid].name = "(Unnamed branch)";
298 pi.branches[bid].pbid = pbid;
299 } else if(r = regex("branch([1-9][0-9]*)name=(.*)", tmp)) {
300 uint64_t bid = parse_value<int64_t>(r[1]);
301 if(!pi.branches.count(bid))
302 pi.branches[bid].pbid = 0;
303 pi.branches[bid].name = r[2];
304 } else if(r = regex("branchcurrent=([0-9]+)", tmp)) {
305 pi.active_branch = parse_value<int64_t>(r[1]);
306 } else if(r = regex("branchnext=([0-9]+)", tmp)) {
307 pi.next_branch = parse_value<int64_t>(r[1]);
310 for(auto& i : pi.branches) {
311 uint64_t j = i.first;
312 uint64_t m = j;
313 while(j) {
314 j = pi.branches[j].pbid;
315 m = min(m, j);
316 if(j == i.first) {
317 //Cyclic dependency!
318 messages << "Warning: Cyclic slot branch dependency, reparenting '" <<
319 pi.branches[m].name << "' to be child of root..." << std::endl;
320 pi.branches[j].pbid = 0;
321 break;
325 if(pi.active_branch && !pi.branches.count(pi.active_branch)) {
326 messages << "Warning: Current slot branch does not exist, using root..." << std::endl;
327 pi.active_branch = 0;
329 return pi;
332 project_info* project_state::get()
334 return active_project;
337 bool project_state::set(project_info* p, bool current)
339 if(!p) {
340 if(active_project)
341 commentary.unload_collection();
342 active_project = p;
343 edispatch.core_change();
344 edispatch.branch_change();
345 return true;
348 loaded_rom newrom;
349 moviefile* newmovie = NULL;
350 bool switched = false;
351 std::set<core_sysregion*> sysregs;
352 bool used = false;
353 try {
354 if(current)
355 goto skip_rom_movie;
357 sysregs = core_sysregion::find_matching(p->gametype);
358 if(sysregs.empty())
359 throw std::runtime_error("No core supports '" + p->gametype + "'");
361 //First, try to load the ROM and the last movie file into RAM...
362 if(p->rom != "") {
363 newrom = loaded_rom(p->rom, p->coreversion);
364 } else {
365 core_type* ctype = NULL;
366 for(auto i : sysregs) {
367 ctype = &i->get_type();
368 if(ctype->get_core_identifier() == p->coreversion)
369 break;
371 newrom = loaded_rom(p->roms, ctype->get_core_identifier(), ctype->get_iname(), "");
373 if(newrom.get_core_identifier() != p->coreversion) {
374 messages << "Warning: Can't find matching core, using " << newrom.get_core_identifier()
375 << std::endl;
377 if(p->last_save != "")
378 try {
379 newmovie = new moviefile(p->last_save, newrom.get_internal_rom_type());
380 } catch(std::exception& e) {
381 messages << "Warning: Can't load last save: " << e.what() << std::endl;
382 newmovie = new moviefile();
383 fill_stub_movie(*newmovie, *p, newrom.get_internal_rom_type());
385 else {
386 newmovie = new moviefile();
387 fill_stub_movie(*newmovie, *p, newrom.get_internal_rom_type());
389 //Okay, loaded, load into core.
390 newrom.load(p->settings, p->movie_rtc_second, p->movie_rtc_subsecond);
391 rom = newrom;
392 do_load_state(*newmovie, LOAD_STATE_DEFAULT, used);
393 skip_rom_movie:
394 active_project = p;
395 switched = true;
396 //Calculate union of old and new.
397 std::set<std::string> _watches = mwatch.enumerate();
398 for(auto i : p->watches) _watches.insert(i.first);
400 for(auto i : _watches)
401 try {
402 if(p->watches.count(i))
403 mwatch.set(i, p->watches[i]);
404 else
405 mwatch.clear(i);
406 } catch(std::exception& e) {
407 messages << "Can't set/clear watch '" << i << "': " << e.what() << std::endl;
409 commentary.load_collection(p->directory + "/" + p->prefix + ".lsvs");
410 command.invoke("reset-lua");
411 for(auto i : p->luascripts)
412 command.invoke("run-lua " + i);
413 buttons.load(controls, *active_project);
414 } catch(std::exception& e) {
415 if(newmovie && !used)
416 delete newmovie;
417 platform::error_message(std::string("Can't switch projects: ") + e.what());
418 messages << "Can't switch projects: " << e.what() << std::endl;
420 if(switched) {
421 do_flush_slotinfo();
422 supdater.update();
423 edispatch.core_change();
424 edispatch.branch_change();
426 return switched;
429 std::map<std::string, std::string> project_state::enumerate()
431 std::set<std::string> projects;
432 std::map<std::string, std::string> projects2;
434 projects = directory::enumerate(get_config_path(), ".*\\.prj");
435 for(auto i : projects) {
436 std::string id = i;
437 size_t split;
438 #ifdef FUCKED_SYSTEM
439 split = id.find_last_of("\\/");
440 #else
441 split = id.find_last_of("/");
442 #endif
443 if(split < id.length())
444 id = id.substr(split + 1);
445 id = id.substr(0, id.length() - 4);
446 try {
447 projects2[id] = project_getname(id);
448 } catch(...) {
449 messages << "Failed to load name for ID '" << id << "'" << std::endl;
452 return projects2;
455 std::string project_state::moviepath()
457 if(active_project)
458 return active_project->directory;
459 else
460 return setcache.get("moviepath");
463 std::string project_state::otherpath()
465 if(active_project)
466 return active_project->directory;
467 else
468 return ".";
471 std::string project_state::savestate_ext()
473 return active_project ? "lss" : "lsmv";
476 void project_state::copy_watches(project_info& p)
478 for(auto i : mwatch.enumerate()) {
479 try {
480 p.watches[i] = mwatch.get_string(i);
481 } catch(std::exception& e) {
482 messages << "Can't read memory watch '" << i << "': " << e.what() << std::endl;
487 void project_state::copy_macros(project_info& p, controller_state& s)
489 for(auto i : s.enumerate_macro())
490 p.macros[i] = s.get_macro(i).serialize();
493 project_info::project_info(emulator_dispatch& _dispatch)
494 : edispatch(_dispatch)
498 uint64_t project_info::get_parent_branch(uint64_t bid)
500 if(!bid)
501 return 0;
502 if(!branches.count(bid))
503 throw std::runtime_error("Invalid branch ID");
504 return branches[bid].pbid;
507 void project_info::set_current_branch(uint64_t bid)
509 if(bid && !branches.count(bid))
510 throw std::runtime_error("Invalid branch ID");
511 active_branch = bid;
512 edispatch.branch_change();
513 messages << "Set current slot branch to " << get_branch_string() << std::endl;
516 const std::string& project_info::get_branch_name(uint64_t bid)
518 static std::string rootname = "(root)";
519 if(!bid)
520 return rootname;
521 if(!branches.count(bid))
522 throw std::runtime_error("Invalid branch ID");
523 return branches[bid].name;
526 void project_info::set_branch_name(uint64_t bid, const std::string& name)
528 if(!bid)
529 throw std::runtime_error("Root branch name can't be set");
530 if(!branches.count(bid))
531 throw std::runtime_error("Invalid branch ID");
532 branches[bid].name = name;
533 edispatch.branch_change();
536 void project_info::set_parent_branch(uint64_t bid, uint64_t pbid)
538 if(!bid)
539 throw std::runtime_error("Root branch never has parent");
540 if(!branches.count(bid))
541 throw std::runtime_error("Invalid branch ID");
542 if(pbid && !branches.count(pbid))
543 throw std::runtime_error("Invalid parent branch ID");
544 if(bid == pbid)
545 throw std::runtime_error("Branch can't be its own parent");
546 for(auto& i : branches) {
547 uint64_t j = i.first;
548 while(j) {
549 j = (j == bid) ? pbid : branches[j].pbid;
550 if(j == i.first)
551 throw std::runtime_error("Reparenting would create a circular dependency");
554 branches[bid].pbid = pbid;
555 edispatch.branch_change();
558 std::set<uint64_t> project_info::branch_children(uint64_t bid)
560 if(bid && !branches.count(bid))
561 throw std::runtime_error("Invalid branch ID");
562 std::set<uint64_t> r;
563 for(auto& i : branches)
564 if(i.second.pbid == bid)
565 r.insert(i.first);
566 return r;
569 uint64_t project_info::create_branch(uint64_t pbid, const std::string& name)
571 if(pbid && !branches.count(pbid))
572 throw std::runtime_error("Invalid parent branch ID");
573 uint64_t assign_bid = next_branch;
574 uint64_t last_bid = (branches.empty() ? 1 : branches.rbegin()->first + 1);
575 assign_bid = max(assign_bid, last_bid);
576 branches[assign_bid].name = name;
577 branches[assign_bid].pbid = pbid;
578 next_branch = assign_bid + 1;
579 edispatch.branch_change();
580 return assign_bid;
583 void project_info::delete_branch(uint64_t bid)
585 if(!bid)
586 throw std::runtime_error("Root branch can not be deleted");
587 if(bid == active_branch)
588 throw std::runtime_error("Current branch can't be deleted");
589 if(!branches.count(bid))
590 throw std::runtime_error("Invalid branch ID");
591 for(auto& i : branches)
592 if(i.second.pbid == bid)
593 throw std::runtime_error("Can't delete branch with children");
594 branches.erase(bid);
595 edispatch.branch_change();
598 std::string project_info::get_branch_string()
600 std::string r;
601 uint64_t j = active_branch;
602 if(!j)
603 return "(root)";
604 while(j) {
605 if(r == "")
606 r = get_branch_name(j);
607 else
608 r = get_branch_name(j) + "→" + r;
609 j = get_parent_branch(j);
611 return r;
614 void project_info::flush()
616 std::string file = get_config_path() + "/" + id + ".prj";
617 std::string tmpfile = get_config_path() + "/" + id + ".prj.tmp";
618 std::string bakfile = get_config_path() + "/" + id + ".prj.bak";
619 std::ofstream f(tmpfile);
620 if(!f)
621 throw std::runtime_error("Can't write project file");
622 write(f);
623 if(!f)
624 throw std::runtime_error("Can't write project file");
625 f.close();
627 std::ifstream f2(file);
628 if(f2) {
629 std::ofstream f3(bakfile);
630 if(!f3)
631 throw std::runtime_error("Can't backup project file");
632 while(f2) {
633 std::string tmp;
634 std::getline(f2, tmp);
635 f3 << tmp << std::endl;
637 f2.close();
638 f3.close();
640 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
641 if(MoveFileEx(tmpfile.c_str(), file.c_str(), MOVEFILE_REPLACE_EXISTING) < 0)
642 #else
643 if(rename(tmpfile.c_str(), file.c_str()) < 0)
644 #endif
645 throw std::runtime_error("Can't replace project file");
648 void project_info::write(std::ostream& s)
650 s << name << std::endl;
651 s << "rom=" << rom << std::endl;
652 if(last_save != "")
653 s << "last-save=" << last_save << std::endl;
654 s << "directory=" << directory << std::endl;
655 s << "prefix=" << prefix << std::endl;
656 for(auto i : luascripts)
657 s << "luascript=" << i << std::endl;
658 s << "gametype=" << gametype << std::endl;
659 s << "coreversion=" << coreversion << std::endl;
660 if(gamename != "")
661 s << "gamename=" << gamename << std::endl;
662 s << "projectid=" << projectid << std::endl;
663 s << "time=" << movie_rtc_second << ":" << movie_rtc_subsecond << std::endl;
664 for(auto i : authors)
665 s << "author=" << i.first << "|" << i.second << std::endl;
666 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
667 if(romimg_sha256[i] != "") {
668 if(i)
669 s << "slotsha" << static_cast<char>(96 + i) << "=" << romimg_sha256[i] << std::endl;
670 else
671 s << "romsha=" << romimg_sha256[i] << std::endl;
673 if(romxml_sha256[i] != "") {
674 if(i)
675 s << "slotxml" << static_cast<char>(96 + i) << "=" << romxml_sha256[i] << std::endl;
676 else
677 s << "romxml=" << romxml_sha256[i] << std::endl;
679 if(namehint[i] != "") {
680 if(i)
681 s << "slothint" << static_cast<char>(96 + i) << "=" << namehint[i] << std::endl;
682 else
683 s << "romhint=" << namehint[i] << std::endl;
685 if(roms[i] != "") {
686 if(i)
687 s << "slotrom" << static_cast<char>(96 + i) << "=" << roms[i] << std::endl;
688 else
689 s << "romrom=" << roms[i] << std::endl;
692 for(auto i : settings)
693 s << "setting." << i.first << "=" << i.second << std::endl;
694 for(auto i : watches)
695 s << "watch." << eq_escape(i.first) << "=" << i.second << std::endl;
696 for(auto i : macros)
697 s << "macro." + i.first << "=" << i.second.serialize() << std::endl;
698 for(auto i : movie_sram)
699 save_binary(s, "sram." + i.first, i.second);
700 if(anchor_savestate.size())
701 save_binary(s, "anchor", anchor_savestate);
702 for(auto& i : branches) {
703 s << "branch" << i.first << "parent=" << i.second.pbid << std::endl;
704 s << "branch" << i.first << "name=" << i.second.name << std::endl;
706 s << "branchcurrent=" << active_branch << std::endl;
707 s << "branchnext=" << next_branch << std::endl;
710 void project_state::do_branch_mk(const std::string& args)
712 regex_results r = regex("([0-9]+)[ \t]+(.*)", args);
713 if(!r) {
714 messages << "Syntax: create-branch <parentid> <name>" << std::endl;
715 return;
717 try {
718 auto prj = get();
719 uint64_t pbid = parse_value<uint64_t>(r[1]);
720 if(!prj)
721 throw std::runtime_error("Not in project context");
722 uint64_t bid = prj->create_branch(pbid, r[2]);
723 messages << "Created branch #" << bid << std::endl;
724 prj->flush();
725 } catch(std::exception& e) {
726 messages << "Can't create new branch: " << e.what() << std::endl;
730 void project_state::do_branch_rm(const std::string& args)
732 regex_results r = regex("([0-9]+)[ \t]*", args);
733 if(!r) {
734 messages << "Syntax: delete-branch <id>" << std::endl;
735 return;
737 try {
738 auto prj = get();
739 uint64_t bid = parse_value<uint64_t>(r[1]);
740 if(!prj)
741 throw std::runtime_error("Not in project context");
742 prj->delete_branch(bid);
743 messages << "Deleted branch #" << bid << std::endl;
744 prj->flush();
745 } catch(std::exception& e) {
746 messages << "Can't delete branch: " << e.what() << std::endl;
750 void project_state::do_branch_set(const std::string& args)
752 regex_results r = regex("([0-9]+)[ \t]*", args);
753 if(!r) {
754 messages << "Syntax: set-branch <id>" << std::endl;
755 return;
757 try {
758 auto prj = get();
759 uint64_t bid = parse_value<uint64_t>(r[1]);
760 if(!prj)
761 throw std::runtime_error("Not in project context");
762 prj->set_current_branch(bid);
763 messages << "Set current branch to #" << bid << std::endl;
764 prj->flush();
765 supdater.update();
766 } catch(std::exception& e) {
767 messages << "Can't set branch: " << e.what() << std::endl;
771 void project_state::do_branch_rp(const std::string& args)
773 regex_results r = regex("([0-9]+)[ \t]+([0-9]+)[ \t]*", args);
774 if(!r) {
775 messages << "Syntax: reparent-branch <id> <newpid>" << std::endl;
776 return;
778 try {
779 auto prj = get();
780 uint64_t bid = parse_value<uint64_t>(r[1]);
781 uint64_t pbid = parse_value<uint64_t>(r[2]);
782 if(!prj)
783 throw std::runtime_error("Not in project context");
784 prj->set_parent_branch(bid, pbid);
785 messages << "Reparented branch #" << bid << std::endl;
786 prj->flush();
787 supdater.update();
788 } catch(std::exception& e) {
789 messages << "Can't reparent branch: " << e.what() << std::endl;
793 void project_state::do_branch_mv(const std::string& args)
795 regex_results r = regex("([0-9]+)[ \t]+(.*)", args);
796 if(!r) {
797 messages << "Syntax: rename-branch <id> <name>" << std::endl;
798 return;
800 try {
801 auto prj = get();
802 uint64_t bid = parse_value<uint64_t>(r[1]);
803 if(!prj)
804 throw std::runtime_error("Not in project context");
805 prj->set_branch_name(bid, r[2]);
806 messages << "Renamed branch #" << bid << std::endl;
807 prj->flush();
808 supdater.update();
809 } catch(std::exception& e) {
810 messages << "Can't rename branch: " << e.what() << std::endl;
814 void project_state::do_branch_ls()
816 std::set<unsigned> dset;
817 recursive_list_branch(0, dset, 0, false);
820 void project_state::recursive_list_branch(uint64_t bid, std::set<unsigned>& dset, unsigned depth, bool last_of)
822 auto prj = get();
823 if(!prj) {
824 messages << "Not in project context." << std::endl;
825 return;
827 std::set<uint64_t> children = prj->branch_children(bid);
828 std::string prefix;
829 for(unsigned i = 0; i + 1 < depth; i++)
830 prefix += (dset.count(i) ? "\u2502" : " ");
831 prefix += (dset.count(depth - 1) ? (last_of ? "\u2514" : "\u251c") : " ");
832 if(last_of) dset.erase(depth - 1);
833 messages << prefix
834 << ((bid == prj->get_current_branch()) ? "*" : "")
835 << bid << ":" << prj->get_branch_name(bid) << std::endl;
836 dset.insert(depth);
837 size_t c = 0;
838 for(auto i : children) {
839 bool last = (++c == children.size());
840 recursive_list_branch(i, dset, depth + 1, last);
842 dset.erase(depth);