Make various instance stuff to take references to other instance objs
[lsnes.git] / src / core / project.cpp
blobabd7b6c527fbefe0a165022b456ac7fccbaf49e5
1 #include "core/command.hpp"
2 #include "core/controller.hpp"
3 #include "core/dispatch.hpp"
4 #include "core/instance.hpp"
5 #include "core/inthread.hpp"
6 #include "core/project.hpp"
7 #include "core/misc.hpp"
8 #include "core/mainloop.hpp"
9 #include "core/memorywatch.hpp"
10 #include "core/moviefile.hpp"
11 #include "core/moviedata.hpp"
12 #include "core/settings.hpp"
13 #include "core/window.hpp"
14 #include "library/directory.hpp"
15 #include "library/minmax.hpp"
16 #include "library/string.hpp"
17 #include <fstream>
18 #include <dirent.h>
19 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
20 #define FUCKED_SYSTEM
21 #include <windows.h>
22 #endif
24 void do_flush_slotinfo();
26 namespace
28 void concatenate(std::vector<char>& data, const std::vector<char>& app)
30 size_t dsize = data.size();
31 data.resize(dsize + app.size());
32 std::copy(app.begin(), app.end(), data.begin() + dsize);
35 std::vector<char> base_decode(const std::string& str)
37 std::vector<char> r;
38 size_t len = str.length();
39 size_t i;
40 uint32_t a, b, c, d, e, v;
41 for(i = 0; i + 5 <= len; i += 5) {
42 a = str[i + 0] - 33;
43 b = str[i + 1] - 33;
44 c = str[i + 2] - 33;
45 d = str[i + 3] - 33;
46 e = str[i + 4] - 33;
47 v = 52200625 * a + 614125 * b + 7225 * c + 85 * d + e;
48 r.push_back(v);
49 r.push_back(v >> 8);
50 r.push_back(v >> 16);
51 r.push_back(v >> 24);
53 a = b = c = d = e = 0;
54 if(i + 0 < len) e = str[len - 1] - 33;
55 if(i + 1 < len) d = str[len - 2] - 33;
56 if(i + 2 < len) c = str[len - 3] - 33;
57 if(i + 3 < len) b = str[len - 4] - 33;
58 v = 614125 * b + 7225 * c + 85 * d + e;
59 if(i + 1 < len) r.push_back(v);
60 if(i + 2 < len) r.push_back(v >> 8);
61 if(i + 3 < len) r.push_back(v >> 16);
62 return r;
65 template<int x> void blockcode(std::ostringstream& s, const std::vector<char>& data, size_t& ptr,
66 size_t& chars)
68 uint32_t a = 0, b = 0, c = 0, d = 0, v;
69 if(x >= 4) a = static_cast<uint8_t>(data[ptr++]);
70 if(x >= 3) b = static_cast<uint8_t>(data[ptr++]);
71 if(x >= 2) c = static_cast<uint8_t>(data[ptr++]);
72 if(x >= 1) d = static_cast<uint8_t>(data[ptr++]);
73 v = 16777216 * d + 65536 * c + 256 * b + a;
74 v >>= (32 - 8 * x);
75 if(x >= 4) s << static_cast<char>(v / 52200625 % 85 + 33);
76 if(x >= 3) s << static_cast<char>(v / 614125 % 85 + 33);
77 if(x >= 2) s << static_cast<char>(v / 7225 % 85 + 33);
78 if(x >= 1) s << static_cast<char>(v / 85 % 85 + 33);
79 if(x >= 1) s << static_cast<char>(v % 85 + 33);
80 chars -= (x + 1);
83 std::pair<std::string, size_t> base_encode(const std::vector<char>& data, size_t ptr, size_t chars)
85 std::ostringstream s;
86 while(chars >= 5 && ptr + 4 <= data.size())
87 blockcode<4>(s, data, ptr, chars);
88 if(chars >= 4 && ptr + 3 <= data.size())
89 blockcode<3>(s, data, ptr, chars);
90 if(chars >= 3 && ptr + 2 <= data.size())
91 blockcode<2>(s, data, ptr, chars);
92 if(chars >= 2 && ptr + 1 <= data.size())
93 blockcode<1>(s, data, ptr, chars);
94 return std::make_pair(s.str(), ptr);
96 std::string eq_escape(const std::string& str)
98 std::ostringstream s;
99 size_t len = str.length();
100 for(size_t i = 0; i < len; i++) {
101 if(str[i] == '\\')
102 s << "\\\\";
103 else if(str[i] == '=')
104 s << "\\e";
105 else
106 s << str[i];
108 return s.str();
111 std::string eq_unescape(const std::string& str)
113 std::ostringstream s;
114 size_t len = str.length();
115 bool escape = false;
116 for(size_t i = 0; i < len; i++) {
117 if(escape) {
118 if(str[i] == 'e')
119 s << "=";
120 else
121 s << str[i];
122 escape = false;
123 } else {
124 if(str[i] == '\\')
125 escape = true;
126 else
127 s << str[i];
130 return s.str();
133 void save_binary(std::ostream& s, const std::string& key, const std::vector<char>& value)
135 size_t ptr = 0;
136 while(ptr < value.size()) {
137 auto v = base_encode(value, ptr, 60);
138 s << key << "=" << v.first << std::endl;
139 ptr = v.second;
143 void fill_stub_movie(struct moviefile& m, struct project_info& p, struct core_type& coretype)
145 //Create a dummy movie.
146 m.lazy_project_create = false;
147 m.start_paused = true;
148 m.movie_rtc_second = p.movie_rtc_second;
149 m.movie_rtc_subsecond = p.movie_rtc_subsecond;
150 m.save_frame = 0;
151 m.lagged_frames = 0;
152 m.anchor_savestate = p.anchor_savestate;
153 m.movie_sram = p.movie_sram;
154 m.authors = p.authors;
155 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
156 m.romimg_sha256[i] = p.romimg_sha256[i];
157 m.romxml_sha256[i] = p.romxml_sha256[i];
158 m.namehint[i] = p.namehint[i];
160 m.projectid = p.projectid;
161 m.coreversion = p.coreversion;
162 m.gamename = p.gamename;
163 m.settings = p.settings;
164 auto ctrldata = coretype.controllerconfig(m.settings);
165 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex());
166 m.create_default_branch(ports);
167 try {
168 m.gametype = &coretype.lookup_sysregion(p.gametype);
169 } catch(std::bad_alloc& e) {
170 throw;
171 } catch(std::exception& e) {
172 throw std::runtime_error("Illegal game type '" + p.gametype + "'");
176 std::string project_getname(const std::string& id)
178 std::string file = get_config_path() + "/" + id + ".prj";
179 std::ifstream f(file);
180 if(!f)
181 throw std::runtime_error("Can't open project file");
182 std::string name;
183 std::getline(f, name);
184 if(!f)
185 throw std::runtime_error("Can't read project name");
186 return name;
190 project_state::project_state(voice_commentary& _commentary, memwatch_set& _mwatch, command::group& _command,
191 controller_state& _controls, settingvar::cache& _setcache)
192 : commentary(_commentary), mwatch(_mwatch), command(_command), controls(_controls), setcache(_setcache)
194 active_project = NULL;
197 project_state::~project_state()
201 project_info& project_state::load(const std::string& id)
203 std::string file = get_config_path() + "/" + id + ".prj";
204 std::ifstream f(file);
205 if(!f)
206 throw std::runtime_error("Can't open project file");
207 project_info& pi = *new project_info();
208 pi.id = id;
209 pi.movie_rtc_second = 1000000000;
210 pi.movie_rtc_subsecond = 0;
211 pi.active_branch = 0;
212 pi.next_branch = 0;
213 pi.filename = file;
214 //First line is always project name.
215 std::getline(f, pi.name);
216 if(!f || pi.name == "") {
217 delete &pi;
218 throw std::runtime_error("Can't read project file");
220 while(f) {
221 std::string tmp;
222 std::getline(f, tmp);
223 regex_results r;
224 if(r = regex("rom=(.+)", tmp))
225 pi.rom = r[1];
226 else if(r = regex("last-save=(.+)", tmp))
227 pi.last_save = r[1];
228 else if(r = regex("directory=(.+)", tmp))
229 pi.directory = r[1];
230 else if(r = regex("prefix=(.+)", tmp))
231 pi.prefix = r[1];
232 else if(r = regex("luascript=(.+)", tmp))
233 pi.luascripts.push_back(r[1]);
234 else if(r = regex("gametype=(.+)", tmp))
235 pi.gametype = r[1];
236 else if(r = regex("coreversion=(.+)", tmp))
237 pi.coreversion = r[1];
238 else if(r = regex("gamename=(.+)", tmp))
239 pi.gamename = r[1];
240 else if(r = regex("projectid=(.+)", tmp))
241 pi.projectid = r[1];
242 else if(r = regex("projectid=([0-9]+):([0-9]+)", tmp)) {
243 pi.movie_rtc_second = parse_value<int64_t>(r[1]);
244 pi.movie_rtc_subsecond = parse_value<int64_t>(r[2]);
245 } else if(r = regex("author=(.*)\\|(.*)", tmp))
246 pi.authors.push_back(std::make_pair(r[1], r[2]));
247 else if(r = regex("author=(.+)", tmp))
248 pi.authors.push_back(std::make_pair(r[1], ""));
249 else if(r = regex("romsha=([0-9a-f]+)", tmp))
250 pi.romimg_sha256[0] = r[1];
251 else if(r = regex("slotsha([a-z])=([0-9a-f]+)", tmp))
252 pi.romimg_sha256[r[1][0] - 96] = r[2];
253 else if(r = regex("romxml=([0-9a-f]+)", tmp))
254 pi.romxml_sha256[0] = r[1];
255 else if(r = regex("slotxml([a-z])=([0-9a-f]+)", tmp))
256 pi.romxml_sha256[r[1][0] - 96] = r[2];
257 else if(r = regex("romhint=(.*)", tmp))
258 pi.namehint[0] = r[1];
259 else if(r = regex("slothint([a-z])=(.*)", tmp))
260 pi.namehint[r[1][0] - 96] = r[2];
261 else if(r = regex("romrom=(.*)", tmp))
262 pi.roms[0] = r[1];
263 else if(r = regex("slotrom([a-z])=(.*)", tmp))
264 pi.roms[r[1][0] - 96] = r[2];
265 else if(r = regex("setting.([^=]+)=(.*)", tmp))
266 pi.settings[r[1]] = r[2];
267 else if(r = regex("watch.([^=]+)=(.*)", tmp))
268 pi.watches[eq_unescape(r[1])] = r[2];
269 else if(r = regex("sram.([^=]+)=(.*)", tmp))
270 concatenate(pi.movie_sram[r[1]], base_decode(r[2]));
271 else if(r = regex("macro.([^=]+)=(.*)", tmp))
272 try {
273 pi.macros[r[1]] = JSON::node(r[2]);
274 } catch(std::exception& e) {
275 messages << "Unable to load macro '" << r[1] << "': " << e.what() << std::endl;
277 else if(r = regex("anchor=(.*)", tmp))
278 concatenate(pi.anchor_savestate, base_decode(r[1]));
279 else if(r = regex("time=([0-9]+):([0-9]+)", tmp)) {
280 pi.movie_rtc_second = parse_value<int64_t>(r[1]);
281 pi.movie_rtc_subsecond = parse_value<int64_t>(r[2]);
282 } else if(r = regex("branch([1-9][0-9]*)parent=([0-9]+)", tmp)) {
283 uint64_t bid = parse_value<int64_t>(r[1]);
284 uint64_t pbid = parse_value<int64_t>(r[2]);
285 if(!pi.branches.count(bid))
286 pi.branches[bid].name = "(Unnamed branch)";
287 pi.branches[bid].pbid = pbid;
288 } else if(r = regex("branch([1-9][0-9]*)name=(.*)", tmp)) {
289 uint64_t bid = parse_value<int64_t>(r[1]);
290 if(!pi.branches.count(bid))
291 pi.branches[bid].pbid = 0;
292 pi.branches[bid].name = r[2];
293 } else if(r = regex("branchcurrent=([0-9]+)", tmp)) {
294 pi.active_branch = parse_value<int64_t>(r[1]);
295 } else if(r = regex("branchnext=([0-9]+)", tmp)) {
296 pi.next_branch = parse_value<int64_t>(r[1]);
299 for(auto& i : pi.branches) {
300 uint64_t j = i.first;
301 uint64_t m = j;
302 while(j) {
303 j = pi.branches[j].pbid;
304 m = min(m, j);
305 if(j == i.first) {
306 //Cyclic dependency!
307 messages << "Warning: Cyclic slot branch dependency, reparenting '" <<
308 pi.branches[m].name << "' to be child of root..." << std::endl;
309 pi.branches[j].pbid = 0;
310 break;
314 if(pi.active_branch && !pi.branches.count(pi.active_branch)) {
315 messages << "Warning: Current slot branch does not exist, using root..." << std::endl;
316 pi.active_branch = 0;
318 return pi;
321 project_info* project_state::get()
323 return active_project;
326 bool project_state::set(project_info* p, bool current)
328 if(!p) {
329 if(active_project)
330 commentary.unload_collection();
331 active_project = p;
332 notify_core_change();
333 notify_branch_change();
334 return true;
337 loaded_rom newrom;
338 moviefile* newmovie = NULL;
339 bool switched = false;
340 std::set<core_sysregion*> sysregs;
341 bool used = false;
342 try {
343 if(current)
344 goto skip_rom_movie;
346 sysregs = core_sysregion::find_matching(p->gametype);
347 if(sysregs.empty())
348 throw std::runtime_error("No core supports '" + p->gametype + "'");
350 //First, try to load the ROM and the last movie file into RAM...
351 if(p->rom != "") {
352 newrom = loaded_rom(p->rom, p->coreversion);
353 } else {
354 core_type* ctype = NULL;
355 for(auto i : sysregs) {
356 ctype = &i->get_type();
357 if(ctype->get_core_identifier() == p->coreversion)
358 break;
360 newrom = loaded_rom(p->roms, ctype->get_core_identifier(), ctype->get_iname(), "");
362 if(newrom.rtype->get_core_identifier() != p->coreversion) {
363 messages << "Warning: Can't find matching core, using " << newrom.rtype->get_core_identifier()
364 << std::endl;
366 if(p->last_save != "")
367 try {
368 newmovie = new moviefile(p->last_save, *newrom.rtype);
369 } catch(std::exception& e) {
370 messages << "Warning: Can't load last save: " << e.what() << std::endl;
371 newmovie = new moviefile();
372 fill_stub_movie(*newmovie, *p, *newrom.rtype);
374 else {
375 newmovie = new moviefile();
376 fill_stub_movie(*newmovie, *p, *newrom.rtype);
378 //Okay, loaded, load into core.
379 newrom.load(p->settings, p->movie_rtc_second, p->movie_rtc_subsecond);
380 our_rom = newrom;
381 do_load_state(*newmovie, LOAD_STATE_DEFAULT, used);
382 skip_rom_movie:
383 active_project = p;
384 switched = true;
385 //Calculate union of old and new.
386 std::set<std::string> _watches = mwatch.enumerate();
387 for(auto i : p->watches) _watches.insert(i.first);
389 for(auto i : _watches)
390 try {
391 if(p->watches.count(i))
392 mwatch.set(i, p->watches[i]);
393 else
394 mwatch.clear(i);
395 } catch(std::exception& e) {
396 messages << "Can't set/clear watch '" << i << "': " << e.what() << std::endl;
398 commentary.load_collection(p->directory + "/" + p->prefix + ".lsvs");
399 command.invoke("reset-lua");
400 for(auto i : p->luascripts)
401 command.invoke("run-lua " + i);
402 load_project_macros(controls, *active_project);
403 } catch(std::exception& e) {
404 if(newmovie && !used)
405 delete newmovie;
406 messages << "Can't switch projects: " << e.what() << std::endl;
408 if(switched) {
409 do_flush_slotinfo();
410 update_movie_state();
411 notify_core_change();
412 notify_branch_change();
414 return switched;
417 std::map<std::string, std::string> project_state::enumerate()
419 std::set<std::string> projects;
420 std::map<std::string, std::string> projects2;
422 projects = directory::enumerate(get_config_path(), ".*\\.prj");
423 for(auto i : projects) {
424 std::string id = i;
425 size_t split;
426 #ifdef FUCKED_SYSTEM
427 split = id.find_last_of("\\/");
428 #else
429 split = id.find_last_of("/");
430 #endif
431 if(split < id.length())
432 id = id.substr(split + 1);
433 id = id.substr(0, id.length() - 4);
434 try {
435 projects2[id] = project_getname(id);
436 } catch(...) {
437 messages << "Failed to load name for ID '" << id << "'" << std::endl;
440 return projects2;
443 std::string project_state::moviepath()
445 if(active_project)
446 return active_project->directory;
447 else
448 return setcache.get("moviepath");
451 std::string project_state::otherpath()
453 if(active_project)
454 return active_project->directory;
455 else
456 return ".";
459 std::string project_state::savestate_ext()
461 return active_project ? "lss" : "lsmv";
464 void project_state::copy_watches(project_info& p)
466 for(auto i : mwatch.enumerate()) {
467 try {
468 p.watches[i] = mwatch.get_string(i);
469 } catch(std::exception& e) {
470 messages << "Can't read memory watch '" << i << "': " << e.what() << std::endl;
475 void project_state::copy_macros(project_info& p, controller_state& s)
477 for(auto i : s.enumerate_macro())
478 p.macros[i] = s.get_macro(i).serialize();
481 uint64_t project_info::get_parent_branch(uint64_t bid)
483 if(!bid)
484 return 0;
485 if(!branches.count(bid))
486 throw std::runtime_error("Invalid branch ID");
487 return branches[bid].pbid;
490 void project_info::set_current_branch(uint64_t bid)
492 if(bid && !branches.count(bid))
493 throw std::runtime_error("Invalid branch ID");
494 active_branch = bid;
495 notify_branch_change();
496 messages << "Set current slot branch to " << get_branch_string() << std::endl;
499 const std::string& project_info::get_branch_name(uint64_t bid)
501 static std::string rootname = "(root)";
502 if(!bid)
503 return rootname;
504 if(!branches.count(bid))
505 throw std::runtime_error("Invalid branch ID");
506 return branches[bid].name;
509 void project_info::set_branch_name(uint64_t bid, const std::string& name)
511 if(!bid)
512 throw std::runtime_error("Root branch name can't be set");
513 if(!branches.count(bid))
514 throw std::runtime_error("Invalid branch ID");
515 branches[bid].name = name;
516 notify_branch_change();
519 void project_info::set_parent_branch(uint64_t bid, uint64_t pbid)
521 if(!bid)
522 throw std::runtime_error("Root branch never has parent");
523 if(!branches.count(bid))
524 throw std::runtime_error("Invalid branch ID");
525 if(pbid && !branches.count(pbid))
526 throw std::runtime_error("Invalid parent branch ID");
527 for(auto& i : branches) {
528 uint64_t j = i.first;
529 while(j) {
530 j = (j == bid) ? pbid : branches[j].pbid;
531 if(j == i.first)
532 throw std::runtime_error("Reparenting would create a circular dependency");
535 branches[bid].pbid = pbid;
536 notify_branch_change();
539 std::set<uint64_t> project_info::branch_children(uint64_t bid)
541 if(bid && !branches.count(bid))
542 throw std::runtime_error("Invalid branch ID");
543 std::set<uint64_t> r;
544 for(auto& i : branches)
545 if(i.second.pbid == bid)
546 r.insert(i.first);
547 return r;
550 uint64_t project_info::create_branch(uint64_t pbid, const std::string& name)
552 if(pbid && !branches.count(pbid))
553 throw std::runtime_error("Invalid parent branch ID");
554 uint64_t assign_bid = next_branch;
555 uint64_t last_bid = (branches.empty() ? 1 : branches.rbegin()->first + 1);
556 assign_bid = max(assign_bid, last_bid);
557 branches[assign_bid].name = name;
558 branches[assign_bid].pbid = pbid;
559 next_branch = assign_bid + 1;
560 notify_branch_change();
561 return assign_bid;
564 void project_info::delete_branch(uint64_t bid)
566 if(!bid)
567 throw std::runtime_error("Root branch can not be deleted");
568 if(bid == active_branch)
569 throw std::runtime_error("Current branch can't be deleted");
570 if(!branches.count(bid))
571 throw std::runtime_error("Invalid branch ID");
572 for(auto& i : branches)
573 if(i.second.pbid == bid)
574 throw std::runtime_error("Can't delete branch with children");
575 branches.erase(bid);
576 notify_branch_change();
579 std::string project_info::get_branch_string()
581 std::string r;
582 uint64_t j = active_branch;
583 if(!j)
584 return "(root)";
585 while(j) {
586 if(r == "")
587 r = get_branch_name(j);
588 else
589 r = get_branch_name(j) + "→" + r;
590 j = get_parent_branch(j);
592 return r;
595 void project_info::flush()
597 std::string file = get_config_path() + "/" + id + ".prj";
598 std::string tmpfile = get_config_path() + "/" + id + ".prj.tmp";
599 std::string bakfile = get_config_path() + "/" + id + ".prj.bak";
600 std::ofstream f(tmpfile);
601 if(!f)
602 throw std::runtime_error("Can't write project file");
603 write(f);
604 if(!f)
605 throw std::runtime_error("Can't write project file");
606 f.close();
608 std::ifstream f2(file);
609 if(f2) {
610 std::ofstream f3(bakfile);
611 if(!f3)
612 throw std::runtime_error("Can't backup project file");
613 while(f2) {
614 std::string tmp;
615 std::getline(f2, tmp);
616 f3 << tmp << std::endl;
618 f2.close();
619 f3.close();
621 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
622 if(MoveFileEx(tmpfile.c_str(), file.c_str(), MOVEFILE_REPLACE_EXISTING) < 0)
623 #else
624 if(rename(tmpfile.c_str(), file.c_str()) < 0)
625 #endif
626 throw std::runtime_error("Can't replace project file");
629 void project_info::write(std::ostream& s)
631 s << name << std::endl;
632 s << "rom=" << rom << std::endl;
633 if(last_save != "")
634 s << "last-save=" << last_save << std::endl;
635 s << "directory=" << directory << std::endl;
636 s << "prefix=" << prefix << std::endl;
637 for(auto i : luascripts)
638 s << "luascript=" << i << std::endl;
639 s << "gametype=" << gametype << std::endl;
640 s << "coreversion=" << coreversion << std::endl;
641 if(gamename != "")
642 s << "gamename=" << gamename << std::endl;
643 s << "projectid=" << projectid << std::endl;
644 s << "time=" << movie_rtc_second << ":" << movie_rtc_subsecond << std::endl;
645 for(auto i : authors)
646 s << "author=" << i.first << "|" << i.second << std::endl;
647 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
648 if(romimg_sha256[i] != "") {
649 if(i)
650 s << "slotsha" << static_cast<char>(96 + i) << "=" << romimg_sha256[i] << std::endl;
651 else
652 s << "romsha=" << romimg_sha256[i] << std::endl;
654 if(romxml_sha256[i] != "") {
655 if(i)
656 s << "slotxml" << static_cast<char>(96 + i) << "=" << romxml_sha256[i] << std::endl;
657 else
658 s << "romxml=" << romxml_sha256[i] << std::endl;
660 if(namehint[i] != "") {
661 if(i)
662 s << "slothint" << static_cast<char>(96 + i) << "=" << namehint[i] << std::endl;
663 else
664 s << "romhint=" << namehint[i] << std::endl;
666 if(roms[i] != "") {
667 if(i)
668 s << "slotrom" << static_cast<char>(96 + i) << "=" << roms[i] << std::endl;
669 else
670 s << "romrom=" << roms[i] << std::endl;
673 for(auto i : settings)
674 s << "setting." << i.first << "=" << i.second << std::endl;
675 for(auto i : watches)
676 s << "watch." << eq_escape(i.first) << "=" << i.second << std::endl;
677 for(auto i : macros)
678 s << "macro." + i.first << "=" << i.second.serialize() << std::endl;
679 for(auto i : movie_sram)
680 save_binary(s, "sram." + i.first, i.second);
681 if(anchor_savestate.size())
682 save_binary(s, "anchor", anchor_savestate);
683 for(auto& i : branches) {
684 s << "branch" << i.first << "parent=" << i.second.pbid << std::endl;
685 s << "branch" << i.first << "name=" << i.second.name << std::endl;
687 s << "branchcurrent=" << active_branch << std::endl;
688 s << "branchnext=" << next_branch << std::endl;
691 namespace
693 void recursive_list_branch(uint64_t bid, std::set<unsigned>& dset, unsigned depth, bool last_of)
695 auto prj = CORE().project.get();
696 if(!prj) {
697 messages << "Not in project context." << std::endl;
698 return;
700 std::set<uint64_t> children = prj->branch_children(bid);
701 std::string prefix;
702 for(unsigned i = 0; i + 1 < depth; i++)
703 prefix += (dset.count(i) ? "\u2502" : " ");
704 prefix += (dset.count(depth - 1) ? (last_of ? "\u2514" : "\u251c") : " ");
705 if(last_of) dset.erase(depth - 1);
706 messages << prefix
707 << ((bid == prj->get_current_branch()) ? "*" : "")
708 << bid << ":" << prj->get_branch_name(bid) << std::endl;
709 dset.insert(depth);
710 size_t c = 0;
711 for(auto i : children) {
712 bool last = (++c == children.size());
713 recursive_list_branch(i, dset, depth + 1, last);
715 dset.erase(depth);
718 command::fnptr<> list_branches(lsnes_cmds, "list-branches", "List all slot branches",
719 "Syntax: list-branches\nList all slot branches.\n",
720 []() throw(std::bad_alloc, std::runtime_error) {
721 std::set<unsigned> dset;
722 recursive_list_branch(0, dset, 0, false);
725 command::fnptr<const std::string&> create_branch(lsnes_cmds, "create-branch", "Create a new slot branch",
726 "Syntax: create-branch <parentid> <name>\nCreate new branch named <name> under <parentid>.\n",
727 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
728 regex_results r = regex("([0-9]+)[ \t]+(.*)", args);
729 if(!r) {
730 messages << "Syntax: create-branch <parentid> <name>" << std::endl;
731 return;
733 try {
734 auto prj = CORE().project.get();
735 uint64_t pbid = parse_value<uint64_t>(r[1]);
736 if(!prj)
737 throw std::runtime_error("Not in project context");
738 uint64_t bid = prj->create_branch(pbid, r[2]);
739 messages << "Created branch #" << bid << std::endl;
740 prj->flush();
741 } catch(std::exception& e) {
742 messages << "Can't create new branch: " << e.what() << std::endl;
746 command::fnptr<const std::string&> delete_branch(lsnes_cmds, "delete-branch", "Delete a slot branch",
747 "Syntax: delete-branch <id>\nDelete slot branch with id <id>.\n",
748 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
749 regex_results r = regex("([0-9]+)[ \t]*", args);
750 if(!r) {
751 messages << "Syntax: delete-branch <id>" << std::endl;
752 return;
754 try {
755 auto prj = CORE().project.get();
756 uint64_t bid = parse_value<uint64_t>(r[1]);
757 if(!prj)
758 throw std::runtime_error("Not in project context");
759 prj->delete_branch(bid);
760 messages << "Deleted branch #" << bid << std::endl;
761 prj->flush();
762 } catch(std::exception& e) {
763 messages << "Can't delete branch: " << e.what() << std::endl;
767 command::fnptr<const std::string&> set_branch(lsnes_cmds, "set-branch", "Set current slot branch",
768 "Syntax: set-branch <id>\nSet current branch to <id>.\n",
769 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
770 regex_results r = regex("([0-9]+)[ \t]*", args);
771 if(!r) {
772 messages << "Syntax: set-branch <id>" << std::endl;
773 return;
775 try {
776 auto prj = CORE().project.get();
777 uint64_t bid = parse_value<uint64_t>(r[1]);
778 if(!prj)
779 throw std::runtime_error("Not in project context");
780 prj->set_current_branch(bid);
781 messages << "Set current branch to #" << bid << std::endl;
782 prj->flush();
783 update_movie_state();
784 } catch(std::exception& e) {
785 messages << "Can't set branch: " << e.what() << std::endl;
789 command::fnptr<const std::string&> reparent_branch(lsnes_cmds, "reparent-branch", "Reparent a slot branch",
790 "Syntax: reparent-branch <id> <newpid>\nReparent branch <id> to be child of <newpid>.\n",
791 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
792 regex_results r = regex("([0-9]+)[ \t]+([0-9]+)[ \t]*", args);
793 if(!r) {
794 messages << "Syntax: reparent-branch <id> <newpid>" << std::endl;
795 return;
797 try {
798 auto prj = CORE().project.get();
799 uint64_t bid = parse_value<uint64_t>(r[1]);
800 uint64_t pbid = parse_value<uint64_t>(r[2]);
801 if(!prj)
802 throw std::runtime_error("Not in project context");
803 prj->set_parent_branch(bid, pbid);
804 messages << "Reparented branch #" << bid << std::endl;
805 prj->flush();
806 update_movie_state();
807 } catch(std::exception& e) {
808 messages << "Can't reparent branch: " << e.what() << std::endl;
812 command::fnptr<const std::string&> rename_branch(lsnes_cmds, "rename-branch", "Rename a slot branch",
813 "Syntax: rename-branch <id> <name>\nRename branch <id> to <name>.\n",
814 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
815 regex_results r = regex("([0-9]+)[ \t]+(.*)", args);
816 if(!r) {
817 messages << "Syntax: rename-branch <id> <name>" << std::endl;
818 return;
820 try {
821 auto prj = CORE().project.get();
822 uint64_t bid = parse_value<uint64_t>(r[1]);
823 if(!prj)
824 throw std::runtime_error("Not in project context");
825 prj->set_branch_name(bid, r[2]);
826 messages << "Renamed branch #" << bid << std::endl;
827 prj->flush();
828 update_movie_state();
829 } catch(std::exception& e) {
830 messages << "Can't rename branch: " << e.what() << std::endl;