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 for(auto i
: lsnes_memorywatch
.enumerate())
377 if(p
->watches
.count(i
))
378 lsnes_memorywatch
.set(i
, p
->watches
[i
]);
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
)
392 messages
<< "Can't switch projects: " << e
.what() << std::endl
;
396 update_movie_state();
397 notify_core_change();
398 notify_branch_change();
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
) {
413 split
= id
.find_last_of("\\/");
415 split
= id
.find_last_of("/");
417 if(split
< id
.length())
418 id
= id
.substr(split
+ 1);
419 id
= id
.substr(0, id
.length() - 4);
421 projects2
[id
] = project_getname(id
);
423 messages
<< "Failed to load name for ID '" << id
<< "'" << std::endl
;
429 std::string
project_moviepath()
432 return active_project
->directory
;
434 return lsnes_vset
["moviepath"].str();
437 std::string
project_otherpath()
440 return active_project
->directory
;
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()) {
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
)
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");
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)";
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
)
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
)
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
;
516 j
= (j
== bid
) ? pbid
: branches
[j
].pbid
;
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
)
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();
550 void project_info::delete_branch(uint64_t 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");
562 notify_branch_change();
565 std::string
project_info::get_branch_string()
568 uint64_t j
= active_branch
;
573 r
= get_branch_name(j
);
575 r
= get_branch_name(j
) + "→" + r
;
576 j
= get_parent_branch(j
);
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
);
588 throw std::runtime_error("Can't write project file");
591 throw std::runtime_error("Can't write project file");
594 std::ifstream
f2(file
);
596 std::ofstream
f3(bakfile
);
598 throw std::runtime_error("Can't backup project file");
601 std::getline(f2
, tmp
);
602 f3
<< tmp
<< std::endl
;
607 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
608 if(MoveFileEx(tmpfile
.c_str(), file
.c_str(), MOVEFILE_REPLACE_EXISTING
) < 0)
610 if(rename(tmpfile
.c_str(), file
.c_str()) < 0)
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
;
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
;
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
] != "") {
636 s
<< "slotsha" << static_cast<char>(96 + i
) << "=" << romimg_sha256
[i
] << std::endl
;
638 s
<< "romsha=" << romimg_sha256
[i
] << std::endl
;
640 if(romxml_sha256
[i
] != "") {
642 s
<< "slotxml" << static_cast<char>(96 + i
) << "=" << romxml_sha256
[i
] << std::endl
;
644 s
<< "romxml=" << romxml_sha256
[i
] << std::endl
;
646 if(namehint
[i
] != "") {
648 s
<< "slothint" << static_cast<char>(96 + i
) << "=" << namehint
[i
] << std::endl
;
650 s
<< "romhint=" << namehint
[i
] << std::endl
;
654 s
<< "slotrom" << static_cast<char>(96 + i
) << "=" << roms
[i
] << std::endl
;
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
;
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
;
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
;
685 std::set
<uint64_t> children
= active_project
->branch_children(bid
);
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);
692 << ((bid
== active_project
->get_current_branch()) ? "*" : "")
693 << bid
<< ":" << active_project
->get_branch_name(bid
) << std::endl
;
696 for(auto i
: children
) {
697 bool last
= (++c
== children
.size());
698 recursive_list_branch(i
, dset
, depth
+ 1, last
);
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
);
715 messages
<< "Syntax: create-branch <parentid> <name>" << std::endl
;
719 uint64_t pbid
= parse_value
<uint64_t>(r
[1]);
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
);
735 messages
<< "Syntax: delete-branch <id>" << std::endl
;
739 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
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
);
755 messages
<< "Syntax: set-branch <id>" << std::endl
;
759 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
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
);
776 messages
<< "Syntax: reparent-branch <id> <newpid>" << std::endl
;
780 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
781 uint64_t pbid
= parse_value
<uint64_t>(r
[2]);
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
);
798 messages
<< "Syntax: rename-branch <id> <name>" << std::endl
;
802 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
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
;