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"
18 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
23 void do_flush_slotinfo();
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
)
39 size_t len
= str
.length();
41 uint32_t a
, b
, c
, d
, e
, v
;
42 for(i
= 0; i
+ 5 <= len
; i
+= 5) {
48 v
= 52200625 * a
+ 614125 * b
+ 7225 * c
+ 85 * d
+ e
;
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);
66 template<int x
> void blockcode(std::ostringstream
& s
, const std::vector
<char>& data
, size_t& ptr
,
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
;
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);
84 std::pair
<std::string
, size_t> base_encode(const std::vector
<char>& data
, size_t ptr
, size_t chars
)
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
)
100 size_t len
= str
.length();
101 for(size_t i
= 0; i
< len
; i
++) {
104 else if(str
[i
] == '=')
112 std::string
eq_unescape(const std::string
& str
)
114 std::ostringstream s
;
115 size_t len
= str
.length();
117 for(size_t i
= 0; i
< len
; i
++) {
134 void save_binary(std::ostream
& s
, const std::string
& key
, const std::vector
<char>& value
)
137 while(ptr
< value
.size()) {
138 auto v
= base_encode(value
, ptr
, 60);
139 s
<< key
<< "=" << v
.first
<< std::endl
;
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
;
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
);
169 m
.gametype
= &coretype
.lookup_sysregion(p
.gametype
);
170 } catch(std::bad_alloc
& e
) {
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
);
182 throw std::runtime_error("Can't open project file");
184 std::getline(f
, name
);
186 throw std::runtime_error("Can't read project name");
191 project_info
& project_load(const std::string
& id
)
193 std::string file
= get_config_path() + "/" + id
+ ".prj";
194 std::ifstream
f(file
);
196 throw std::runtime_error("Can't open project file");
197 project_info
& pi
= *new project_info();
199 pi
.movie_rtc_second
= 1000000000;
200 pi
.movie_rtc_subsecond
= 0;
201 pi
.active_branch
= 0;
204 //First line is always project name.
205 std::getline(f
, pi
.name
);
206 if(!f
|| pi
.name
== "") {
208 throw std::runtime_error("Can't read project file");
212 std::getline(f
, tmp
);
214 if(r
= regex("rom=(.+)", tmp
))
216 else if(r
= regex("last-save=(.+)", tmp
))
218 else if(r
= regex("directory=(.+)", tmp
))
220 else if(r
= regex("prefix=(.+)", tmp
))
222 else if(r
= regex("luascript=(.+)", tmp
))
223 pi
.luascripts
.push_back(r
[1]);
224 else if(r
= regex("gametype=(.+)", tmp
))
226 else if(r
= regex("coreversion=(.+)", tmp
))
227 pi
.coreversion
= r
[1];
228 else if(r
= regex("gamename=(.+)", tmp
))
230 else if(r
= regex("projectid=(.+)", tmp
))
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
))
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
))
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
;
293 j
= pi
.branches
[j
].pbid
;
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;
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;
311 project_info
* project_get()
313 return active_project
;
316 bool project_set(project_info
* p
, bool current
)
320 voicesub_unload_collection();
322 notify_core_change();
323 notify_branch_change();
328 moviefile
* newmovie
= NULL
;
329 bool switched
= false;
330 std::set
<core_sysregion
*> sysregs
;
336 sysregs
= core_sysregion::find_matching(p
->gametype
);
338 throw std::runtime_error("No core supports '" + p
->gametype
+ "'");
340 //First, try to load the ROM and the last movie file into RAM...
342 newrom
= loaded_rom(p
->rom
, p
->coreversion
);
344 core_type
* ctype
= NULL
;
345 for(auto i
: sysregs
) {
346 ctype
= &i
->get_type();
347 if(ctype
->get_core_identifier() == p
->coreversion
)
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()
356 if(p
->last_save
!= "")
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
);
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
);
371 do_load_state(*newmovie
, LOAD_STATE_DEFAULT
, used
);
375 //Calculate union of old and new.
376 std::set
<std::string
> _watches
= lsnes_memorywatch
.enumerate();
377 for(auto i
: p
->watches
) _watches
.insert(i
.first
);
379 for(auto i
: _watches
)
381 if(p
->watches
.count(i
))
382 lsnes_memorywatch
.set(i
, p
->watches
[i
]);
384 lsnes_memorywatch
.clear(i
);
385 } catch(std::exception
& e
) {
386 messages
<< "Can't set/clear watch '" << i
<< "': " << e
.what() << std::endl
;
388 voicesub_load_collection(p
->directory
+ "/" + p
->prefix
+ ".lsvs");
389 lsnes_cmd
.invoke("reset-lua");
390 for(auto i
: p
->luascripts
)
391 lsnes_cmd
.invoke("run-lua " + i
);
392 load_project_macros(controls
, *active_project
);
393 } catch(std::exception
& e
) {
394 if(newmovie
&& !used
)
396 messages
<< "Can't switch projects: " << e
.what() << std::endl
;
400 update_movie_state();
401 notify_core_change();
402 notify_branch_change();
407 std::map
<std::string
, std::string
> project_enumerate()
409 std::set
<std::string
> projects
;
410 std::map
<std::string
, std::string
> projects2
;
412 projects
= enumerate_directory(get_config_path(), ".*\\.prj");
413 for(auto i
: projects
) {
417 split
= id
.find_last_of("\\/");
419 split
= id
.find_last_of("/");
421 if(split
< id
.length())
422 id
= id
.substr(split
+ 1);
423 id
= id
.substr(0, id
.length() - 4);
425 projects2
[id
] = project_getname(id
);
427 messages
<< "Failed to load name for ID '" << id
<< "'" << std::endl
;
433 std::string
project_moviepath()
436 return active_project
->directory
;
438 return lsnes_vset
["moviepath"].str();
441 std::string
project_otherpath()
444 return active_project
->directory
;
449 std::string
project_savestate_ext()
451 return active_project
? "lss" : "lsmv";
454 void project_copy_watches(project_info
& p
)
456 for(auto i
: lsnes_memorywatch
.enumerate()) {
458 p
.watches
[i
] = lsnes_memorywatch
.get_string(i
);
459 } catch(std::exception
& e
) {
460 messages
<< "Can't read memory watch '" << i
<< "': " << e
.what() << std::endl
;
465 void project_copy_macros(project_info
& p
, controller_state
& s
)
467 for(auto i
: s
.enumerate_macro())
468 p
.macros
[i
] = s
.get_macro(i
).serialize();
471 uint64_t project_info::get_parent_branch(uint64_t bid
)
475 if(!branches
.count(bid
))
476 throw std::runtime_error("Invalid branch ID");
477 return branches
[bid
].pbid
;
480 void project_info::set_current_branch(uint64_t bid
)
482 if(bid
&& !branches
.count(bid
))
483 throw std::runtime_error("Invalid branch ID");
485 notify_branch_change();
486 messages
<< "Set current slot branch to " << get_branch_string() << std::endl
;
489 const std::string
& project_info::get_branch_name(uint64_t bid
)
491 static std::string rootname
= "(root)";
494 if(!branches
.count(bid
))
495 throw std::runtime_error("Invalid branch ID");
496 return branches
[bid
].name
;
499 void project_info::set_branch_name(uint64_t bid
, const std::string
& name
)
502 throw std::runtime_error("Root branch name can't be set");
503 if(!branches
.count(bid
))
504 throw std::runtime_error("Invalid branch ID");
505 branches
[bid
].name
= name
;
506 notify_branch_change();
509 void project_info::set_parent_branch(uint64_t bid
, uint64_t pbid
)
512 throw std::runtime_error("Root branch never has parent");
513 if(!branches
.count(bid
))
514 throw std::runtime_error("Invalid branch ID");
515 if(pbid
&& !branches
.count(pbid
))
516 throw std::runtime_error("Invalid parent branch ID");
517 for(auto& i
: branches
) {
518 uint64_t j
= i
.first
;
520 j
= (j
== bid
) ? pbid
: branches
[j
].pbid
;
522 throw std::runtime_error("Reparenting would create a circular dependency");
525 branches
[bid
].pbid
= pbid
;
526 notify_branch_change();
529 std::set
<uint64_t> project_info::branch_children(uint64_t bid
)
531 if(bid
&& !branches
.count(bid
))
532 throw std::runtime_error("Invalid branch ID");
533 std::set
<uint64_t> r
;
534 for(auto& i
: branches
)
535 if(i
.second
.pbid
== bid
)
540 uint64_t project_info::create_branch(uint64_t pbid
, const std::string
& name
)
542 if(pbid
&& !branches
.count(pbid
))
543 throw std::runtime_error("Invalid parent branch ID");
544 uint64_t assign_bid
= next_branch
;
545 uint64_t last_bid
= (branches
.empty() ? 1 : branches
.rbegin()->first
+ 1);
546 assign_bid
= max(assign_bid
, last_bid
);
547 branches
[assign_bid
].name
= name
;
548 branches
[assign_bid
].pbid
= pbid
;
549 next_branch
= assign_bid
+ 1;
550 notify_branch_change();
554 void project_info::delete_branch(uint64_t bid
)
557 throw std::runtime_error("Root branch can not be deleted");
558 if(bid
== active_branch
)
559 throw std::runtime_error("Current branch can't be deleted");
560 if(!branches
.count(bid
))
561 throw std::runtime_error("Invalid branch ID");
562 for(auto& i
: branches
)
563 if(i
.second
.pbid
== bid
)
564 throw std::runtime_error("Can't delete branch with children");
566 notify_branch_change();
569 std::string
project_info::get_branch_string()
572 uint64_t j
= active_branch
;
577 r
= get_branch_name(j
);
579 r
= get_branch_name(j
) + "→" + r
;
580 j
= get_parent_branch(j
);
585 void project_info::flush()
587 std::string file
= get_config_path() + "/" + id
+ ".prj";
588 std::string tmpfile
= get_config_path() + "/" + id
+ ".prj.tmp";
589 std::string bakfile
= get_config_path() + "/" + id
+ ".prj.bak";
590 std::ofstream
f(tmpfile
);
592 throw std::runtime_error("Can't write project file");
595 throw std::runtime_error("Can't write project file");
598 std::ifstream
f2(file
);
600 std::ofstream
f3(bakfile
);
602 throw std::runtime_error("Can't backup project file");
605 std::getline(f2
, tmp
);
606 f3
<< tmp
<< std::endl
;
611 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
612 if(MoveFileEx(tmpfile
.c_str(), file
.c_str(), MOVEFILE_REPLACE_EXISTING
) < 0)
614 if(rename(tmpfile
.c_str(), file
.c_str()) < 0)
616 throw std::runtime_error("Can't replace project file");
619 void project_info::write(std::ostream
& s
)
621 s
<< name
<< std::endl
;
622 s
<< "rom=" << rom
<< std::endl
;
624 s
<< "last-save=" << last_save
<< std::endl
;
625 s
<< "directory=" << directory
<< std::endl
;
626 s
<< "prefix=" << prefix
<< std::endl
;
627 for(auto i
: luascripts
)
628 s
<< "luascript=" << i
<< std::endl
;
629 s
<< "gametype=" << gametype
<< std::endl
;
630 s
<< "coreversion=" << coreversion
<< std::endl
;
632 s
<< "gamename=" << gamename
<< std::endl
;
633 s
<< "projectid=" << projectid
<< std::endl
;
634 s
<< "time=" << movie_rtc_second
<< ":" << movie_rtc_subsecond
<< std::endl
;
635 for(auto i
: authors
)
636 s
<< "author=" << i
.first
<< "|" << i
.second
<< std::endl
;
637 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
638 if(romimg_sha256
[i
] != "") {
640 s
<< "slotsha" << static_cast<char>(96 + i
) << "=" << romimg_sha256
[i
] << std::endl
;
642 s
<< "romsha=" << romimg_sha256
[i
] << std::endl
;
644 if(romxml_sha256
[i
] != "") {
646 s
<< "slotxml" << static_cast<char>(96 + i
) << "=" << romxml_sha256
[i
] << std::endl
;
648 s
<< "romxml=" << romxml_sha256
[i
] << std::endl
;
650 if(namehint
[i
] != "") {
652 s
<< "slothint" << static_cast<char>(96 + i
) << "=" << namehint
[i
] << std::endl
;
654 s
<< "romhint=" << namehint
[i
] << std::endl
;
658 s
<< "slotrom" << static_cast<char>(96 + i
) << "=" << roms
[i
] << std::endl
;
660 s
<< "romrom=" << roms
[i
] << std::endl
;
663 for(auto i
: settings
)
664 s
<< "setting." << i
.first
<< "=" << i
.second
<< std::endl
;
665 for(auto i
: watches
)
666 s
<< "watch." << eq_escape(i
.first
) << "=" << i
.second
<< std::endl
;
668 s
<< "macro." + i
.first
<< "=" << i
.second
.serialize() << std::endl
;
669 for(auto i
: movie_sram
)
670 save_binary(s
, "sram." + i
.first
, i
.second
);
671 if(anchor_savestate
.size())
672 save_binary(s
, "anchor", anchor_savestate
);
673 for(auto& i
: branches
) {
674 s
<< "branch" << i
.first
<< "parent=" << i
.second
.pbid
<< std::endl
;
675 s
<< "branch" << i
.first
<< "name=" << i
.second
.name
<< std::endl
;
677 s
<< "branchcurrent=" << active_branch
<< std::endl
;
678 s
<< "branchnext=" << next_branch
<< std::endl
;
683 void recursive_list_branch(uint64_t bid
, std::set
<unsigned>& dset
, unsigned depth
, bool last_of
)
685 if(!active_project
) {
686 messages
<< "Not in project context." << std::endl
;
689 std::set
<uint64_t> children
= active_project
->branch_children(bid
);
691 for(unsigned i
= 0; i
+ 1 < depth
; i
++)
692 prefix
+= (dset
.count(i
) ? "\u2502" : " ");
693 prefix
+= (dset
.count(depth
- 1) ? (last_of
? "\u2514" : "\u251c") : " ");
694 if(last_of
) dset
.erase(depth
- 1);
696 << ((bid
== active_project
->get_current_branch()) ? "*" : "")
697 << bid
<< ":" << active_project
->get_branch_name(bid
) << std::endl
;
700 for(auto i
: children
) {
701 bool last
= (++c
== children
.size());
702 recursive_list_branch(i
, dset
, depth
+ 1, last
);
707 command::fnptr
<> list_branches(lsnes_cmd
, "list-branches", "List all slot branches",
708 "Syntax: list-branches\nList all slot branches.\n",
709 []() throw(std::bad_alloc
, std::runtime_error
) {
710 std::set
<unsigned> dset
;
711 recursive_list_branch(0, dset
, 0, false);
714 command::fnptr
<const std::string
&> create_branch(lsnes_cmd
, "create-branch", "Create a new slot branch",
715 "Syntax: create-branch <parentid> <name>\nCreate new branch named <name> under <parentid>.\n",
716 [](const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
) {
717 regex_results r
= regex("([0-9]+)[ \t]+(.*)", args
);
719 messages
<< "Syntax: create-branch <parentid> <name>" << std::endl
;
723 uint64_t pbid
= parse_value
<uint64_t>(r
[1]);
725 throw std::runtime_error("Not in project context");
726 uint64_t bid
= active_project
->create_branch(pbid
, r
[2]);
727 messages
<< "Created branch #" << bid
<< std::endl
;
728 active_project
->flush();
729 } catch(std::exception
& e
) {
730 messages
<< "Can't create new branch: " << e
.what() << std::endl
;
734 command::fnptr
<const std::string
&> delete_branch(lsnes_cmd
, "delete-branch", "Delete a slot branch",
735 "Syntax: delete-branch <id>\nDelete slot branch with id <id>.\n",
736 [](const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
) {
737 regex_results r
= regex("([0-9]+)[ \t]*", args
);
739 messages
<< "Syntax: delete-branch <id>" << std::endl
;
743 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
745 throw std::runtime_error("Not in project context");
746 active_project
->delete_branch(bid
);
747 messages
<< "Deleted branch #" << bid
<< std::endl
;
748 active_project
->flush();
749 } catch(std::exception
& e
) {
750 messages
<< "Can't delete branch: " << e
.what() << std::endl
;
754 command::fnptr
<const std::string
&> set_branch(lsnes_cmd
, "set-branch", "Set current slot branch",
755 "Syntax: set-branch <id>\nSet current branch to <id>.\n",
756 [](const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
) {
757 regex_results r
= regex("([0-9]+)[ \t]*", args
);
759 messages
<< "Syntax: set-branch <id>" << std::endl
;
763 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
765 throw std::runtime_error("Not in project context");
766 active_project
->set_current_branch(bid
);
767 messages
<< "Set current branch to #" << bid
<< std::endl
;
768 active_project
->flush();
769 update_movie_state();
770 } catch(std::exception
& e
) {
771 messages
<< "Can't set branch: " << e
.what() << std::endl
;
775 command::fnptr
<const std::string
&> reparent_branch(lsnes_cmd
, "reparent-branch", "Reparent a slot branch",
776 "Syntax: reparent-branch <id> <newpid>\nReparent branch <id> to be child of <newpid>.\n",
777 [](const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
) {
778 regex_results r
= regex("([0-9]+)[ \t]+([0-9]+)[ \t]*", args
);
780 messages
<< "Syntax: reparent-branch <id> <newpid>" << std::endl
;
784 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
785 uint64_t pbid
= parse_value
<uint64_t>(r
[2]);
787 throw std::runtime_error("Not in project context");
788 active_project
->set_parent_branch(bid
, pbid
);
789 messages
<< "Reparented branch #" << bid
<< std::endl
;
790 active_project
->flush();
791 update_movie_state();
792 } catch(std::exception
& e
) {
793 messages
<< "Can't reparent branch: " << e
.what() << std::endl
;
797 command::fnptr
<const std::string
&> rename_branch(lsnes_cmd
, "rename-branch", "Rename a slot branch",
798 "Syntax: rename-branch <id> <name>\nRename branch <id> to <name>.\n",
799 [](const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
) {
800 regex_results r
= regex("([0-9]+)[ \t]+(.*)", args
);
802 messages
<< "Syntax: rename-branch <id> <name>" << std::endl
;
806 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
808 throw std::runtime_error("Not in project context");
809 active_project
->set_branch_name(bid
, r
[2]);
810 messages
<< "Renamed branch #" << bid
<< std::endl
;
811 active_project
->flush();
812 update_movie_state();
813 } catch(std::exception
& e
) {
814 messages
<< "Can't rename branch: " << e
.what() << std::endl
;