Fix some memory leak complaints from Valgrind
[lsnes.git] / src / core / project.cpp
blob3ce09388c629daa9ae051462d51d641a6f3fd616
1 #include "core/command.hpp"
2 #include "core/controller.hpp"
3 #include "core/dispatch.hpp"
4 #include "core/inthread.hpp"
5 #include "core/project.hpp"
6 #include "core/misc.hpp"
7 #include "core/mainloop.hpp"
8 #include "core/memorywatch.hpp"
9 #include "core/moviefile.hpp"
10 #include "core/moviedata.hpp"
11 #include "core/settings.hpp"
12 #include "core/window.hpp"
13 #include "library/directory.hpp"
14 #include "library/minmax.hpp"
15 #include "library/string.hpp"
16 #include <fstream>
17 #include <dirent.h>
18 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
19 #define FUCKED_SYSTEM
20 #include <windows.h>
21 #endif
23 void do_flush_slotinfo();
25 namespace
27 project_info* active_project = NULL;
29 void concatenate(std::vector<char>& data, const std::vector<char>& app)
31 size_t dsize = data.size();
32 data.resize(dsize + app.size());
33 std::copy(app.begin(), app.end(), data.begin() + dsize);
36 std::vector<char> base_decode(const std::string& str)
38 std::vector<char> r;
39 size_t len = str.length();
40 size_t i;
41 uint32_t a, b, c, d, e, v;
42 for(i = 0; i + 5 <= len; i += 5) {
43 a = str[i + 0] - 33;
44 b = str[i + 1] - 33;
45 c = str[i + 2] - 33;
46 d = str[i + 3] - 33;
47 e = str[i + 4] - 33;
48 v = 52200625 * a + 614125 * b + 7225 * c + 85 * d + e;
49 r.push_back(v);
50 r.push_back(v >> 8);
51 r.push_back(v >> 16);
52 r.push_back(v >> 24);
54 a = b = c = d = e = 0;
55 if(i + 0 < len) e = str[len - 1] - 33;
56 if(i + 1 < len) d = str[len - 2] - 33;
57 if(i + 2 < len) c = str[len - 3] - 33;
58 if(i + 3 < len) b = str[len - 4] - 33;
59 v = 614125 * b + 7225 * c + 85 * d + e;
60 if(i + 1 < len) r.push_back(v);
61 if(i + 2 < len) r.push_back(v >> 8);
62 if(i + 3 < len) r.push_back(v >> 16);
63 return r;
66 template<int x> void blockcode(std::ostringstream& s, const std::vector<char>& data, size_t& ptr,
67 size_t& chars)
69 uint32_t a = 0, b = 0, c = 0, d = 0, v;
70 if(x >= 4) a = static_cast<uint8_t>(data[ptr++]);
71 if(x >= 3) b = static_cast<uint8_t>(data[ptr++]);
72 if(x >= 2) c = static_cast<uint8_t>(data[ptr++]);
73 if(x >= 1) d = static_cast<uint8_t>(data[ptr++]);
74 v = 16777216 * d + 65536 * c + 256 * b + a;
75 v >>= (32 - 8 * x);
76 if(x >= 4) s << static_cast<char>(v / 52200625 % 85 + 33);
77 if(x >= 3) s << static_cast<char>(v / 614125 % 85 + 33);
78 if(x >= 2) s << static_cast<char>(v / 7225 % 85 + 33);
79 if(x >= 1) s << static_cast<char>(v / 85 % 85 + 33);
80 if(x >= 1) s << static_cast<char>(v % 85 + 33);
81 chars -= (x + 1);
84 std::pair<std::string, size_t> base_encode(const std::vector<char>& data, size_t ptr, size_t chars)
86 std::ostringstream s;
87 while(chars >= 5 && ptr + 4 <= data.size())
88 blockcode<4>(s, data, ptr, chars);
89 if(chars >= 4 && ptr + 3 <= data.size())
90 blockcode<3>(s, data, ptr, chars);
91 if(chars >= 3 && ptr + 2 <= data.size())
92 blockcode<2>(s, data, ptr, chars);
93 if(chars >= 2 && ptr + 1 <= data.size())
94 blockcode<1>(s, data, ptr, chars);
95 return std::make_pair(s.str(), ptr);
97 std::string eq_escape(const std::string& str)
99 std::ostringstream s;
100 size_t len = str.length();
101 for(size_t i = 0; i < len; i++) {
102 if(str[i] == '\\')
103 s << "\\\\";
104 else if(str[i] == '=')
105 s << "\\e";
106 else
107 s << str[i];
109 return s.str();
112 std::string eq_unescape(const std::string& str)
114 std::ostringstream s;
115 size_t len = str.length();
116 bool escape = false;
117 for(size_t i = 0; i < len; i++) {
118 if(escape) {
119 if(str[i] == 'e')
120 s << "=";
121 else
122 s << str[i];
123 escape = false;
124 } else {
125 if(str[i] == '\\')
126 escape = true;
127 else
128 s << str[i];
131 return s.str();
134 void save_binary(std::ostream& s, const std::string& key, const std::vector<char>& value)
136 size_t ptr = 0;
137 while(ptr < value.size()) {
138 auto v = base_encode(value, ptr, 60);
139 s << key << "=" << v.first << std::endl;
140 ptr = v.second;
144 void fill_stub_movie(struct moviefile& m, struct project_info& p, struct core_type& coretype)
146 //Create a dummy movie.
147 m.lazy_project_create = false;
148 m.start_paused = true;
149 m.movie_rtc_second = p.movie_rtc_second;
150 m.movie_rtc_subsecond = p.movie_rtc_subsecond;
151 m.save_frame = 0;
152 m.lagged_frames = 0;
153 m.anchor_savestate = p.anchor_savestate;
154 m.movie_sram = p.movie_sram;
155 m.authors = p.authors;
156 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
157 m.romimg_sha256[i] = p.romimg_sha256[i];
158 m.romxml_sha256[i] = p.romxml_sha256[i];
159 m.namehint[i] = p.namehint[i];
161 m.projectid = p.projectid;
162 m.coreversion = p.coreversion;
163 m.gamename = p.gamename;
164 m.settings = p.settings;
165 auto ctrldata = coretype.controllerconfig(m.settings);
166 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex());
167 m.create_default_branch(ports);
168 try {
169 m.gametype = &coretype.lookup_sysregion(p.gametype);
170 } catch(std::bad_alloc& e) {
171 throw;
172 } catch(std::exception& e) {
173 throw std::runtime_error("Illegal game type '" + p.gametype + "'");
177 std::string project_getname(const std::string& id)
179 std::string file = get_config_path() + "/" + id + ".prj";
180 std::ifstream f(file);
181 if(!f)
182 throw std::runtime_error("Can't open project file");
183 std::string name;
184 std::getline(f, name);
185 if(!f)
186 throw std::runtime_error("Can't read project name");
187 return name;
191 project_info& project_load(const std::string& id)
193 std::string file = get_config_path() + "/" + id + ".prj";
194 std::ifstream f(file);
195 if(!f)
196 throw std::runtime_error("Can't open project file");
197 project_info& pi = *new project_info();
198 pi.id = id;
199 pi.movie_rtc_second = 1000000000;
200 pi.movie_rtc_subsecond = 0;
201 pi.active_branch = 0;
202 pi.next_branch = 0;
203 pi.filename = file;
204 //First line is always project name.
205 std::getline(f, pi.name);
206 if(!f || pi.name == "") {
207 delete &pi;
208 throw std::runtime_error("Can't read project file");
210 while(f) {
211 std::string tmp;
212 std::getline(f, tmp);
213 regex_results r;
214 if(r = regex("rom=(.+)", tmp))
215 pi.rom = r[1];
216 else if(r = regex("last-save=(.+)", tmp))
217 pi.last_save = r[1];
218 else if(r = regex("directory=(.+)", tmp))
219 pi.directory = r[1];
220 else if(r = regex("prefix=(.+)", tmp))
221 pi.prefix = r[1];
222 else if(r = regex("luascript=(.+)", tmp))
223 pi.luascripts.push_back(r[1]);
224 else if(r = regex("gametype=(.+)", tmp))
225 pi.gametype = r[1];
226 else if(r = regex("coreversion=(.+)", tmp))
227 pi.coreversion = r[1];
228 else if(r = regex("gamename=(.+)", tmp))
229 pi.gamename = r[1];
230 else if(r = regex("projectid=(.+)", tmp))
231 pi.projectid = r[1];
232 else if(r = regex("projectid=([0-9]+):([0-9]+)", tmp)) {
233 pi.movie_rtc_second = parse_value<int64_t>(r[1]);
234 pi.movie_rtc_subsecond = parse_value<int64_t>(r[2]);
235 } else if(r = regex("author=(.*)\\|(.*)", tmp))
236 pi.authors.push_back(std::make_pair(r[1], r[2]));
237 else if(r = regex("author=(.+)", tmp))
238 pi.authors.push_back(std::make_pair(r[1], ""));
239 else if(r = regex("romsha=([0-9a-f]+)", tmp))
240 pi.romimg_sha256[0] = r[1];
241 else if(r = regex("slotsha([a-z])=([0-9a-f]+)", tmp))
242 pi.romimg_sha256[r[1][0] - 96] = r[2];
243 else if(r = regex("romxml=([0-9a-f]+)", tmp))
244 pi.romxml_sha256[0] = r[1];
245 else if(r = regex("slotxml([a-z])=([0-9a-f]+)", tmp))
246 pi.romxml_sha256[r[1][0] - 96] = r[2];
247 else if(r = regex("romhint=(.*)", tmp))
248 pi.namehint[0] = r[1];
249 else if(r = regex("slothint([a-z])=(.*)", tmp))
250 pi.namehint[r[1][0] - 96] = r[2];
251 else if(r = regex("romrom=(.*)", tmp))
252 pi.roms[0] = r[1];
253 else if(r = regex("slotrom([a-z])=(.*)", tmp))
254 pi.roms[r[1][0] - 96] = r[2];
255 else if(r = regex("setting.([^=]+)=(.*)", tmp))
256 pi.settings[r[1]] = r[2];
257 else if(r = regex("watch.([^=]+)=(.*)", tmp))
258 pi.watches[eq_unescape(r[1])] = r[2];
259 else if(r = regex("sram.([^=]+)=(.*)", tmp))
260 concatenate(pi.movie_sram[r[1]], base_decode(r[2]));
261 else if(r = regex("macro.([^=]+)=(.*)", tmp))
262 try {
263 pi.macros[r[1]] = JSON::node(r[2]);
264 } catch(std::exception& e) {
265 messages << "Unable to load macro '" << r[1] << "': " << e.what() << std::endl;
267 else if(r = regex("anchor=(.*)", tmp))
268 concatenate(pi.anchor_savestate, base_decode(r[1]));
269 else if(r = regex("time=([0-9]+):([0-9]+)", tmp)) {
270 pi.movie_rtc_second = parse_value<int64_t>(r[1]);
271 pi.movie_rtc_subsecond = parse_value<int64_t>(r[2]);
272 } else if(r = regex("branch([1-9][0-9]*)parent=([0-9]+)", tmp)) {
273 uint64_t bid = parse_value<int64_t>(r[1]);
274 uint64_t pbid = parse_value<int64_t>(r[2]);
275 if(!pi.branches.count(bid))
276 pi.branches[bid].name = "(Unnamed branch)";
277 pi.branches[bid].pbid = pbid;
278 } else if(r = regex("branch([1-9][0-9]*)name=(.*)", tmp)) {
279 uint64_t bid = parse_value<int64_t>(r[1]);
280 if(!pi.branches.count(bid))
281 pi.branches[bid].pbid = 0;
282 pi.branches[bid].name = r[2];
283 } else if(r = regex("branchcurrent=([0-9]+)", tmp)) {
284 pi.active_branch = parse_value<int64_t>(r[1]);
285 } else if(r = regex("branchnext=([0-9]+)", tmp)) {
286 pi.next_branch = parse_value<int64_t>(r[1]);
289 for(auto& i : pi.branches) {
290 uint64_t j = i.first;
291 uint64_t m = j;
292 while(j) {
293 j = pi.branches[j].pbid;
294 m = min(m, j);
295 if(j == i.first) {
296 //Cyclic dependency!
297 messages << "Warning: Cyclic slot branch dependency, reparenting '" <<
298 pi.branches[m].name << "' to be child of root..." << std::endl;
299 pi.branches[j].pbid = 0;
300 break;
304 if(pi.active_branch && !pi.branches.count(pi.active_branch)) {
305 messages << "Warning: Current slot branch does not exist, using root..." << std::endl;
306 pi.active_branch = 0;
308 return pi;
311 project_info* project_get()
313 return active_project;
316 bool project_set(project_info* p, bool current)
318 if(!p) {
319 if(active_project)
320 voicesub_unload_collection();
321 active_project = p;
322 notify_core_change();
323 notify_branch_change();
324 return true;
327 loaded_rom newrom;
328 moviefile* newmovie = NULL;
329 bool switched = false;
330 std::set<core_sysregion*> sysregs;
331 bool used = false;
332 try {
333 if(current)
334 goto skip_rom_movie;
336 sysregs = core_sysregion::find_matching(p->gametype);
337 if(sysregs.empty())
338 throw std::runtime_error("No core supports '" + p->gametype + "'");
340 //First, try to load the ROM and the last movie file into RAM...
341 if(p->rom != "") {
342 newrom = loaded_rom(p->rom, p->coreversion);
343 } else {
344 core_type* ctype = NULL;
345 for(auto i : sysregs) {
346 ctype = &i->get_type();
347 if(ctype->get_core_identifier() == p->coreversion)
348 break;
350 newrom = loaded_rom(p->roms, ctype->get_core_identifier(), ctype->get_iname(), "");
352 if(newrom.rtype->get_core_identifier() != p->coreversion) {
353 messages << "Warning: Can't find matching core, using " << newrom.rtype->get_core_identifier()
354 << std::endl;
356 if(p->last_save != "")
357 try {
358 newmovie = new moviefile(p->last_save, *newrom.rtype);
359 } catch(std::exception& e) {
360 messages << "Warning: Can't load last save: " << e.what() << std::endl;
361 newmovie = new moviefile();
362 fill_stub_movie(*newmovie, *p, *newrom.rtype);
364 else {
365 newmovie = new moviefile();
366 fill_stub_movie(*newmovie, *p, *newrom.rtype);
368 //Okay, loaded, load into core.
369 newrom.load(p->settings, p->movie_rtc_second, p->movie_rtc_subsecond);
370 our_rom = newrom;
371 do_load_state(*newmovie, LOAD_STATE_DEFAULT, used);
372 skip_rom_movie:
373 active_project = p;
374 switched = true;
375 for(auto i : lsnes_memorywatch.enumerate())
376 try {
377 if(p->watches.count(i))
378 lsnes_memorywatch.set(i, p->watches[i]);
379 else
380 lsnes_memorywatch.clear(i);
381 } catch(std::exception& e) {
382 messages << "Can't set/clear watch '" << i << "': " << e.what() << std::endl;
384 voicesub_load_collection(p->directory + "/" + p->prefix + ".lsvs");
385 lsnes_cmd.invoke("reset-lua");
386 for(auto i : p->luascripts)
387 lsnes_cmd.invoke("run-lua " + i);
388 load_project_macros(controls, *active_project);
389 } catch(std::exception& e) {
390 if(newmovie && !used)
391 delete newmovie;
392 messages << "Can't switch projects: " << e.what() << std::endl;
394 if(switched) {
395 do_flush_slotinfo();
396 update_movie_state();
397 notify_core_change();
398 notify_branch_change();
400 return switched;
403 std::map<std::string, std::string> project_enumerate()
405 std::set<std::string> projects;
406 std::map<std::string, std::string> projects2;
408 projects = enumerate_directory(get_config_path(), ".*\\.prj");
409 for(auto i : projects) {
410 std::string id = i;
411 size_t split;
412 #ifdef FUCKED_SYSTEM
413 split = id.find_last_of("\\/");
414 #else
415 split = id.find_last_of("/");
416 #endif
417 if(split < id.length())
418 id = id.substr(split + 1);
419 id = id.substr(0, id.length() - 4);
420 try {
421 projects2[id] = project_getname(id);
422 } catch(...) {
423 messages << "Failed to load name for ID '" << id << "'" << std::endl;
426 return projects2;
429 std::string project_moviepath()
431 if(active_project)
432 return active_project->directory;
433 else
434 return lsnes_vset["moviepath"].str();
437 std::string project_otherpath()
439 if(active_project)
440 return active_project->directory;
441 else
442 return ".";
445 std::string project_savestate_ext()
447 return active_project ? "lss" : "lsmv";
450 void project_copy_watches(project_info& p)
452 for(auto i : lsnes_memorywatch.enumerate()) {
453 try {
454 p.watches[i] = lsnes_memorywatch.get_string(i);
455 } catch(std::exception& e) {
456 messages << "Can't read memory watch '" << i << "': " << e.what() << std::endl;
461 void project_copy_macros(project_info& p, controller_state& s)
463 for(auto i : s.enumerate_macro())
464 p.macros[i] = s.get_macro(i).serialize();
467 uint64_t project_info::get_parent_branch(uint64_t bid)
469 if(!bid)
470 return 0;
471 if(!branches.count(bid))
472 throw std::runtime_error("Invalid branch ID");
473 return branches[bid].pbid;
476 void project_info::set_current_branch(uint64_t bid)
478 if(bid && !branches.count(bid))
479 throw std::runtime_error("Invalid branch ID");
480 active_branch = bid;
481 notify_branch_change();
482 messages << "Set current slot branch to " << get_branch_string() << std::endl;
485 const std::string& project_info::get_branch_name(uint64_t bid)
487 static std::string rootname = "(root)";
488 if(!bid)
489 return rootname;
490 if(!branches.count(bid))
491 throw std::runtime_error("Invalid branch ID");
492 return branches[bid].name;
495 void project_info::set_branch_name(uint64_t bid, const std::string& name)
497 if(!bid)
498 throw std::runtime_error("Root branch name can't be set");
499 if(!branches.count(bid))
500 throw std::runtime_error("Invalid branch ID");
501 branches[bid].name = name;
502 notify_branch_change();
505 void project_info::set_parent_branch(uint64_t bid, uint64_t pbid)
507 if(!bid)
508 throw std::runtime_error("Root branch never has parent");
509 if(!branches.count(bid))
510 throw std::runtime_error("Invalid branch ID");
511 if(pbid && !branches.count(pbid))
512 throw std::runtime_error("Invalid parent branch ID");
513 for(auto& i : branches) {
514 uint64_t j = i.first;
515 while(j) {
516 j = (j == bid) ? pbid : branches[j].pbid;
517 if(j == i.first)
518 throw std::runtime_error("Reparenting would create a circular dependency");
521 branches[bid].pbid = pbid;
522 notify_branch_change();
525 std::set<uint64_t> project_info::branch_children(uint64_t bid)
527 if(bid && !branches.count(bid))
528 throw std::runtime_error("Invalid branch ID");
529 std::set<uint64_t> r;
530 for(auto& i : branches)
531 if(i.second.pbid == bid)
532 r.insert(i.first);
533 return r;
536 uint64_t project_info::create_branch(uint64_t pbid, const std::string& name)
538 if(pbid && !branches.count(pbid))
539 throw std::runtime_error("Invalid parent branch ID");
540 uint64_t assign_bid = next_branch;
541 uint64_t last_bid = (branches.empty() ? 1 : branches.rbegin()->first + 1);
542 assign_bid = max(assign_bid, last_bid);
543 branches[assign_bid].name = name;
544 branches[assign_bid].pbid = pbid;
545 next_branch = assign_bid + 1;
546 notify_branch_change();
547 return assign_bid;
550 void project_info::delete_branch(uint64_t bid)
552 if(!bid)
553 throw std::runtime_error("Root branch can not be deleted");
554 if(bid == active_branch)
555 throw std::runtime_error("Current branch can't be deleted");
556 if(!branches.count(bid))
557 throw std::runtime_error("Invalid branch ID");
558 for(auto& i : branches)
559 if(i.second.pbid == bid)
560 throw std::runtime_error("Can't delete branch with children");
561 branches.erase(bid);
562 notify_branch_change();
565 std::string project_info::get_branch_string()
567 std::string r;
568 uint64_t j = active_branch;
569 if(!j)
570 return "(root)";
571 while(j) {
572 if(r == "")
573 r = get_branch_name(j);
574 else
575 r = get_branch_name(j) + "→" + r;
576 j = get_parent_branch(j);
578 return r;
581 void project_info::flush()
583 std::string file = get_config_path() + "/" + id + ".prj";
584 std::string tmpfile = get_config_path() + "/" + id + ".prj.tmp";
585 std::string bakfile = get_config_path() + "/" + id + ".prj.bak";
586 std::ofstream f(tmpfile);
587 if(!f)
588 throw std::runtime_error("Can't write project file");
589 write(f);
590 if(!f)
591 throw std::runtime_error("Can't write project file");
592 f.close();
594 std::ifstream f2(file);
595 if(f2) {
596 std::ofstream f3(bakfile);
597 if(!f3)
598 throw std::runtime_error("Can't backup project file");
599 while(f2) {
600 std::string tmp;
601 std::getline(f2, tmp);
602 f3 << tmp << std::endl;
604 f2.close();
605 f3.close();
607 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
608 if(MoveFileEx(tmpfile.c_str(), file.c_str(), MOVEFILE_REPLACE_EXISTING) < 0)
609 #else
610 if(rename(tmpfile.c_str(), file.c_str()) < 0)
611 #endif
612 throw std::runtime_error("Can't replace project file");
615 void project_info::write(std::ostream& s)
617 s << name << std::endl;
618 s << "rom=" << rom << std::endl;
619 if(last_save != "")
620 s << "last-save=" << last_save << std::endl;
621 s << "directory=" << directory << std::endl;
622 s << "prefix=" << prefix << std::endl;
623 for(auto i : luascripts)
624 s << "luascript=" << i << std::endl;
625 s << "gametype=" << gametype << std::endl;
626 s << "coreversion=" << coreversion << std::endl;
627 if(gamename != "")
628 s << "gamename=" << gamename << std::endl;
629 s << "projectid=" << projectid << std::endl;
630 s << "time=" << movie_rtc_second << ":" << movie_rtc_subsecond << std::endl;
631 for(auto i : authors)
632 s << "author=" << i.first << "|" << i.second << std::endl;
633 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
634 if(romimg_sha256[i] != "") {
635 if(i)
636 s << "slotsha" << static_cast<char>(96 + i) << "=" << romimg_sha256[i] << std::endl;
637 else
638 s << "romsha=" << romimg_sha256[i] << std::endl;
640 if(romxml_sha256[i] != "") {
641 if(i)
642 s << "slotxml" << static_cast<char>(96 + i) << "=" << romxml_sha256[i] << std::endl;
643 else
644 s << "romxml=" << romxml_sha256[i] << std::endl;
646 if(namehint[i] != "") {
647 if(i)
648 s << "slothint" << static_cast<char>(96 + i) << "=" << namehint[i] << std::endl;
649 else
650 s << "romhint=" << namehint[i] << std::endl;
652 if(roms[i] != "") {
653 if(i)
654 s << "slotrom" << static_cast<char>(96 + i) << "=" << roms[i] << std::endl;
655 else
656 s << "romrom=" << roms[i] << std::endl;
659 for(auto i : settings)
660 s << "setting." << i.first << "=" << i.second << std::endl;
661 for(auto i : watches)
662 s << "watch." << eq_escape(i.first) << "=" << i.second << std::endl;
663 for(auto i : macros)
664 s << "macro." + i.first << "=" << i.second.serialize() << std::endl;
665 for(auto i : movie_sram)
666 save_binary(s, "sram." + i.first, i.second);
667 if(anchor_savestate.size())
668 save_binary(s, "anchor", anchor_savestate);
669 for(auto& i : branches) {
670 s << "branch" << i.first << "parent=" << i.second.pbid << std::endl;
671 s << "branch" << i.first << "name=" << i.second.name << std::endl;
673 s << "branchcurrent=" << active_branch << std::endl;
674 s << "branchnext=" << next_branch << std::endl;
677 namespace
679 void recursive_list_branch(uint64_t bid, std::set<unsigned>& dset, unsigned depth, bool last_of)
681 if(!active_project) {
682 messages << "Not in project context." << std::endl;
683 return;
685 std::set<uint64_t> children = active_project->branch_children(bid);
686 std::string prefix;
687 for(unsigned i = 0; i + 1 < depth; i++)
688 prefix += (dset.count(i) ? "\u2502" : " ");
689 prefix += (dset.count(depth - 1) ? (last_of ? "\u2514" : "\u251c") : " ");
690 if(last_of) dset.erase(depth - 1);
691 messages << prefix
692 << ((bid == active_project->get_current_branch()) ? "*" : "")
693 << bid << ":" << active_project->get_branch_name(bid) << std::endl;
694 dset.insert(depth);
695 size_t c = 0;
696 for(auto i : children) {
697 bool last = (++c == children.size());
698 recursive_list_branch(i, dset, depth + 1, last);
700 dset.erase(depth);
703 command::fnptr<> list_branches(lsnes_cmd, "list-branches", "List all slot branches",
704 "Syntax: list-branches\nList all slot branches.\n",
705 []() throw(std::bad_alloc, std::runtime_error) {
706 std::set<unsigned> dset;
707 recursive_list_branch(0, dset, 0, false);
710 command::fnptr<const std::string&> create_branch(lsnes_cmd, "create-branch", "Create a new slot branch",
711 "Syntax: create-branch <parentid> <name>\nCreate new branch named <name> under <parentid>.\n",
712 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
713 regex_results r = regex("([0-9]+)[ \t]+(.*)", args);
714 if(!r) {
715 messages << "Syntax: create-branch <parentid> <name>" << std::endl;
716 return;
718 try {
719 uint64_t pbid = parse_value<uint64_t>(r[1]);
720 if(!active_project)
721 throw std::runtime_error("Not in project context");
722 uint64_t bid = active_project->create_branch(pbid, r[2]);
723 messages << "Created branch #" << bid << std::endl;
724 active_project->flush();
725 } catch(std::exception& e) {
726 messages << "Can't create new branch: " << e.what() << std::endl;
730 command::fnptr<const std::string&> delete_branch(lsnes_cmd, "delete-branch", "Delete a slot branch",
731 "Syntax: delete-branch <id>\nDelete slot branch with id <id>.\n",
732 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
733 regex_results r = regex("([0-9]+)[ \t]*", args);
734 if(!r) {
735 messages << "Syntax: delete-branch <id>" << std::endl;
736 return;
738 try {
739 uint64_t bid = parse_value<uint64_t>(r[1]);
740 if(!active_project)
741 throw std::runtime_error("Not in project context");
742 active_project->delete_branch(bid);
743 messages << "Deleted branch #" << bid << std::endl;
744 active_project->flush();
745 } catch(std::exception& e) {
746 messages << "Can't delete branch: " << e.what() << std::endl;
750 command::fnptr<const std::string&> set_branch(lsnes_cmd, "set-branch", "Set current slot branch",
751 "Syntax: set-branch <id>\nSet current branch to <id>.\n",
752 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
753 regex_results r = regex("([0-9]+)[ \t]*", args);
754 if(!r) {
755 messages << "Syntax: set-branch <id>" << std::endl;
756 return;
758 try {
759 uint64_t bid = parse_value<uint64_t>(r[1]);
760 if(!active_project)
761 throw std::runtime_error("Not in project context");
762 active_project->set_current_branch(bid);
763 messages << "Set current branch to #" << bid << std::endl;
764 active_project->flush();
765 update_movie_state();
766 } catch(std::exception& e) {
767 messages << "Can't set branch: " << e.what() << std::endl;
771 command::fnptr<const std::string&> reparent_branch(lsnes_cmd, "reparent-branch", "Reparent a slot branch",
772 "Syntax: reparent-branch <id> <newpid>\nReparent branch <id> to be child of <newpid>.\n",
773 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
774 regex_results r = regex("([0-9]+)[ \t]+([0-9]+)[ \t]*", args);
775 if(!r) {
776 messages << "Syntax: reparent-branch <id> <newpid>" << std::endl;
777 return;
779 try {
780 uint64_t bid = parse_value<uint64_t>(r[1]);
781 uint64_t pbid = parse_value<uint64_t>(r[2]);
782 if(!active_project)
783 throw std::runtime_error("Not in project context");
784 active_project->set_parent_branch(bid, pbid);
785 messages << "Reparented branch #" << bid << std::endl;
786 active_project->flush();
787 update_movie_state();
788 } catch(std::exception& e) {
789 messages << "Can't reparent branch: " << e.what() << std::endl;
793 command::fnptr<const std::string&> rename_branch(lsnes_cmd, "rename-branch", "Rename a slot branch",
794 "Syntax: rename-branch <id> <name>\nRename branch <id> to <name>.\n",
795 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
796 regex_results r = regex("([0-9]+)[ \t]+(.*)", args);
797 if(!r) {
798 messages << "Syntax: rename-branch <id> <name>" << std::endl;
799 return;
801 try {
802 uint64_t bid = parse_value<uint64_t>(r[1]);
803 if(!active_project)
804 throw std::runtime_error("Not in project context");
805 active_project->set_branch_name(bid, r[2]);
806 messages << "Renamed branch #" << bid << std::endl;
807 active_project->flush();
808 update_movie_state();
809 } catch(std::exception& e) {
810 messages << "Can't rename branch: " << e.what() << std::endl;