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"
19 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
24 void do_flush_slotinfo();
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
)
38 size_t len
= str
.length();
40 uint32_t a
, b
, c
, d
, e
, v
;
41 for(i
= 0; i
+ 5 <= len
; i
+= 5) {
47 v
= 52200625 * a
+ 614125 * b
+ 7225 * c
+ 85 * d
+ e
;
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);
65 template<int x
> void blockcode(std::ostringstream
& s
, const std::vector
<char>& data
, size_t& ptr
,
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
;
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);
83 std::pair
<std::string
, size_t> base_encode(const std::vector
<char>& data
, size_t ptr
, size_t chars
)
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
)
99 size_t len
= str
.length();
100 for(size_t i
= 0; i
< len
; i
++) {
103 else if(str
[i
] == '=')
111 std::string
eq_unescape(const std::string
& str
)
113 std::ostringstream s
;
114 size_t len
= str
.length();
116 for(size_t i
= 0; i
< len
; i
++) {
133 void save_binary(std::ostream
& s
, const std::string
& key
, const std::vector
<char>& value
)
136 while(ptr
< value
.size()) {
137 auto v
= base_encode(value
, ptr
, 60);
138 s
<< key
<< "=" << v
.first
<< std::endl
;
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
;
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
);
168 m
.gametype
= &coretype
.lookup_sysregion(p
.gametype
);
169 } catch(std::bad_alloc
& e
) {
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
);
181 throw std::runtime_error("Can't open project file");
183 std::getline(f
, name
);
185 throw std::runtime_error("Can't read project 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
);
206 throw std::runtime_error("Can't open project file");
207 project_info
& pi
= *new project_info();
209 pi
.movie_rtc_second
= 1000000000;
210 pi
.movie_rtc_subsecond
= 0;
211 pi
.active_branch
= 0;
214 //First line is always project name.
215 std::getline(f
, pi
.name
);
216 if(!f
|| pi
.name
== "") {
218 throw std::runtime_error("Can't read project file");
222 std::getline(f
, tmp
);
224 if(r
= regex("rom=(.+)", tmp
))
226 else if(r
= regex("last-save=(.+)", tmp
))
228 else if(r
= regex("directory=(.+)", tmp
))
230 else if(r
= regex("prefix=(.+)", tmp
))
232 else if(r
= regex("luascript=(.+)", tmp
))
233 pi
.luascripts
.push_back(r
[1]);
234 else if(r
= regex("gametype=(.+)", tmp
))
236 else if(r
= regex("coreversion=(.+)", tmp
))
237 pi
.coreversion
= r
[1];
238 else if(r
= regex("gamename=(.+)", tmp
))
240 else if(r
= regex("projectid=(.+)", tmp
))
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
))
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
))
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
;
303 j
= pi
.branches
[j
].pbid
;
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;
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;
321 project_info
* project_state::get()
323 return active_project
;
326 bool project_state::set(project_info
* p
, bool current
)
330 commentary
.unload_collection();
332 notify_core_change();
333 notify_branch_change();
338 moviefile
* newmovie
= NULL
;
339 bool switched
= false;
340 std::set
<core_sysregion
*> sysregs
;
346 sysregs
= core_sysregion::find_matching(p
->gametype
);
348 throw std::runtime_error("No core supports '" + p
->gametype
+ "'");
350 //First, try to load the ROM and the last movie file into RAM...
352 newrom
= loaded_rom(p
->rom
, p
->coreversion
);
354 core_type
* ctype
= NULL
;
355 for(auto i
: sysregs
) {
356 ctype
= &i
->get_type();
357 if(ctype
->get_core_identifier() == p
->coreversion
)
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()
366 if(p
->last_save
!= "")
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
);
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
);
381 do_load_state(*newmovie
, LOAD_STATE_DEFAULT
, used
);
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
)
391 if(p
->watches
.count(i
))
392 mwatch
.set(i
, p
->watches
[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
)
406 messages
<< "Can't switch projects: " << e
.what() << std::endl
;
410 update_movie_state();
411 notify_core_change();
412 notify_branch_change();
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
) {
427 split
= id
.find_last_of("\\/");
429 split
= id
.find_last_of("/");
431 if(split
< id
.length())
432 id
= id
.substr(split
+ 1);
433 id
= id
.substr(0, id
.length() - 4);
435 projects2
[id
] = project_getname(id
);
437 messages
<< "Failed to load name for ID '" << id
<< "'" << std::endl
;
443 std::string
project_state::moviepath()
446 return active_project
->directory
;
448 return setcache
.get("moviepath");
451 std::string
project_state::otherpath()
454 return active_project
->directory
;
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()) {
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
)
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");
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)";
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
)
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
)
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
;
530 j
= (j
== bid
) ? pbid
: branches
[j
].pbid
;
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
)
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();
564 void project_info::delete_branch(uint64_t 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");
576 notify_branch_change();
579 std::string
project_info::get_branch_string()
582 uint64_t j
= active_branch
;
587 r
= get_branch_name(j
);
589 r
= get_branch_name(j
) + "→" + r
;
590 j
= get_parent_branch(j
);
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
);
602 throw std::runtime_error("Can't write project file");
605 throw std::runtime_error("Can't write project file");
608 std::ifstream
f2(file
);
610 std::ofstream
f3(bakfile
);
612 throw std::runtime_error("Can't backup project file");
615 std::getline(f2
, tmp
);
616 f3
<< tmp
<< std::endl
;
621 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
622 if(MoveFileEx(tmpfile
.c_str(), file
.c_str(), MOVEFILE_REPLACE_EXISTING
) < 0)
624 if(rename(tmpfile
.c_str(), file
.c_str()) < 0)
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
;
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
;
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
] != "") {
650 s
<< "slotsha" << static_cast<char>(96 + i
) << "=" << romimg_sha256
[i
] << std::endl
;
652 s
<< "romsha=" << romimg_sha256
[i
] << std::endl
;
654 if(romxml_sha256
[i
] != "") {
656 s
<< "slotxml" << static_cast<char>(96 + i
) << "=" << romxml_sha256
[i
] << std::endl
;
658 s
<< "romxml=" << romxml_sha256
[i
] << std::endl
;
660 if(namehint
[i
] != "") {
662 s
<< "slothint" << static_cast<char>(96 + i
) << "=" << namehint
[i
] << std::endl
;
664 s
<< "romhint=" << namehint
[i
] << std::endl
;
668 s
<< "slotrom" << static_cast<char>(96 + i
) << "=" << roms
[i
] << std::endl
;
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
;
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
;
693 void recursive_list_branch(uint64_t bid
, std::set
<unsigned>& dset
, unsigned depth
, bool last_of
)
695 auto prj
= CORE().project
.get();
697 messages
<< "Not in project context." << std::endl
;
700 std::set
<uint64_t> children
= prj
->branch_children(bid
);
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);
707 << ((bid
== prj
->get_current_branch()) ? "*" : "")
708 << bid
<< ":" << prj
->get_branch_name(bid
) << std::endl
;
711 for(auto i
: children
) {
712 bool last
= (++c
== children
.size());
713 recursive_list_branch(i
, dset
, depth
+ 1, last
);
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
);
730 messages
<< "Syntax: create-branch <parentid> <name>" << std::endl
;
734 auto prj
= CORE().project
.get();
735 uint64_t pbid
= parse_value
<uint64_t>(r
[1]);
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
;
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
);
751 messages
<< "Syntax: delete-branch <id>" << std::endl
;
755 auto prj
= CORE().project
.get();
756 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
758 throw std::runtime_error("Not in project context");
759 prj
->delete_branch(bid
);
760 messages
<< "Deleted branch #" << bid
<< std::endl
;
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
);
772 messages
<< "Syntax: set-branch <id>" << std::endl
;
776 auto prj
= CORE().project
.get();
777 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
779 throw std::runtime_error("Not in project context");
780 prj
->set_current_branch(bid
);
781 messages
<< "Set current branch to #" << bid
<< std::endl
;
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
);
794 messages
<< "Syntax: reparent-branch <id> <newpid>" << std::endl
;
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]);
802 throw std::runtime_error("Not in project context");
803 prj
->set_parent_branch(bid
, pbid
);
804 messages
<< "Reparented branch #" << bid
<< std::endl
;
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
);
817 messages
<< "Syntax: rename-branch <id> <name>" << std::endl
;
821 auto prj
= CORE().project
.get();
822 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
824 throw std::runtime_error("Not in project context");
825 prj
->set_branch_name(bid
, r
[2]);
826 messages
<< "Renamed branch #" << bid
<< std::endl
;
828 update_movie_state();
829 } catch(std::exception
& e
) {
830 messages
<< "Can't rename branch: " << e
.what() << std::endl
;