1 #include "cmdhelp/lua.hpp"
2 #include "cmdhelp/project.hpp"
3 #include "core/command.hpp"
4 #include "core/controller.hpp"
5 #include "core/dispatch.hpp"
6 #include "core/emustatus.hpp"
7 #include "core/instance.hpp"
8 #include "core/inthread.hpp"
9 #include "core/mainloop.hpp"
10 #include "core/memorywatch.hpp"
11 #include "core/messages.hpp"
12 #include "core/moviedata.hpp"
13 #include "core/moviefile.hpp"
14 #include "core/project.hpp"
15 #include "core/queue.hpp"
16 #include "core/window.hpp"
17 #include "library/directory.hpp"
18 #include "library/minmax.hpp"
19 #include "library/string.hpp"
23 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
28 void do_flush_slotinfo();
32 void concatenate(std::vector
<char>& data
, const std::vector
<char>& app
)
34 size_t dsize
= data
.size();
35 data
.resize(dsize
+ app
.size());
36 std::copy(app
.begin(), app
.end(), data
.begin() + dsize
);
39 std::vector
<char> base_decode(const std::string
& str
)
42 size_t len
= str
.length();
44 uint32_t a
, b
, c
, d
, e
, v
;
45 for(i
= 0; i
+ 5 <= len
; i
+= 5) {
51 v
= 52200625 * a
+ 614125 * b
+ 7225 * c
+ 85 * d
+ e
;
57 a
= b
= c
= d
= e
= 0;
58 if(i
+ 0 < len
) e
= str
[len
- 1] - 33;
59 if(i
+ 1 < len
) d
= str
[len
- 2] - 33;
60 if(i
+ 2 < len
) c
= str
[len
- 3] - 33;
61 if(i
+ 3 < len
) b
= str
[len
- 4] - 33;
62 v
= 614125 * b
+ 7225 * c
+ 85 * d
+ e
;
63 if(i
+ 1 < len
) r
.push_back(v
);
64 if(i
+ 2 < len
) r
.push_back(v
>> 8);
65 if(i
+ 3 < len
) r
.push_back(v
>> 16);
69 template<int x
> void blockcode(std::ostringstream
& s
, const std::vector
<char>& data
, size_t& ptr
,
72 uint32_t a
= 0, b
= 0, c
= 0, d
= 0, v
;
73 if(x
>= 4) a
= static_cast<uint8_t>(data
[ptr
++]);
74 if(x
>= 3) b
= static_cast<uint8_t>(data
[ptr
++]);
75 if(x
>= 2) c
= static_cast<uint8_t>(data
[ptr
++]);
76 if(x
>= 1) d
= static_cast<uint8_t>(data
[ptr
++]);
77 v
= 16777216 * d
+ 65536 * c
+ 256 * b
+ a
;
79 if(x
>= 4) s
<< static_cast<char>(v
/ 52200625 % 85 + 33);
80 if(x
>= 3) s
<< static_cast<char>(v
/ 614125 % 85 + 33);
81 if(x
>= 2) s
<< static_cast<char>(v
/ 7225 % 85 + 33);
82 if(x
>= 1) s
<< static_cast<char>(v
/ 85 % 85 + 33);
83 if(x
>= 1) s
<< static_cast<char>(v
% 85 + 33);
87 std::pair
<std::string
, size_t> base_encode(const std::vector
<char>& data
, size_t ptr
, size_t chars
)
90 while(chars
>= 5 && ptr
+ 4 <= data
.size())
91 blockcode
<4>(s
, data
, ptr
, chars
);
92 if(chars
>= 4 && ptr
+ 3 <= data
.size())
93 blockcode
<3>(s
, data
, ptr
, chars
);
94 if(chars
>= 3 && ptr
+ 2 <= data
.size())
95 blockcode
<2>(s
, data
, ptr
, chars
);
96 if(chars
>= 2 && ptr
+ 1 <= data
.size())
97 blockcode
<1>(s
, data
, ptr
, chars
);
98 return std::make_pair(s
.str(), ptr
);
100 std::string
eq_escape(const std::string
& str
)
102 std::ostringstream s
;
103 size_t len
= str
.length();
104 for(size_t i
= 0; i
< len
; i
++) {
107 else if(str
[i
] == '=')
115 std::string
eq_unescape(const std::string
& str
)
117 std::ostringstream s
;
118 size_t len
= str
.length();
120 for(size_t i
= 0; i
< len
; i
++) {
137 void save_binary(std::ostream
& s
, const std::string
& key
, const std::vector
<char>& value
)
140 while(ptr
< value
.size()) {
141 auto v
= base_encode(value
, ptr
, 60);
142 s
<< key
<< "=" << v
.first
<< std::endl
;
147 void fill_stub_movie(struct moviefile
& m
, struct project_info
& p
, struct core_type
& coretype
)
149 //Create a dummy movie.
150 m
.lazy_project_create
= false;
151 m
.start_paused
= true;
152 m
.movie_rtc_second
= p
.movie_rtc_second
;
153 m
.movie_rtc_subsecond
= p
.movie_rtc_subsecond
;
154 m
.anchor_savestate
= p
.anchor_savestate
;
155 m
.movie_sram
= p
.movie_sram
;
156 m
.authors
= p
.authors
;
157 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
158 m
.romimg_sha256
[i
] = p
.romimg_sha256
[i
];
159 m
.romxml_sha256
[i
] = p
.romxml_sha256
[i
];
160 m
.namehint
[i
] = p
.namehint
[i
];
162 m
.projectid
= p
.projectid
;
163 m
.coreversion
= p
.coreversion
;
164 m
.gamename
= p
.gamename
;
165 m
.settings
= p
.settings
;
166 auto ctrldata
= coretype
.controllerconfig(m
.settings
);
167 portctrl::type_set
& ports
= portctrl::type_set::make(ctrldata
.ports
, ctrldata
.portindex());
168 m
.create_default_branch(ports
);
171 m
.gametype
= &coretype
.lookup_sysregion(p
.gametype
);
172 } catch(std::bad_alloc
& e
) {
174 } catch(std::exception
& e
) {
175 throw std::runtime_error("Illegal game type '" + p
.gametype
+ "'");
179 std::string
project_getname(const std::string
& id
)
181 std::string file
= get_config_path() + "/" + id
+ ".prj";
182 std::ifstream
f(file
);
184 throw std::runtime_error("Can't open project file");
186 std::getline(f
, name
);
188 throw std::runtime_error("Can't read project name");
193 project_state::project_state(voice_commentary
& _commentary
, memwatch_set
& _mwatch
, command::group
& _command
,
194 controller_state
& _controls
, settingvar::group
& _setgroup
, button_mapping
& _buttons
,
195 emulator_dispatch
& _edispatch
, input_queue
& _iqueue
, loaded_rom
& _rom
, status_updater
& _supdater
)
196 : commentary(_commentary
), mwatch(_mwatch
), command(_command
), controls(_controls
), setgroup(_setgroup
),
197 buttons(_buttons
), edispatch(_edispatch
), iqueue(_iqueue
), rom(_rom
), supdater(_supdater
),
198 branch_ls(command
, CPROJECT::bls
, [this]() { this->do_branch_ls(); }),
199 branch_mk(command
, CPROJECT::bmk
, [this](const std::string
& a
) { this->do_branch_mk(a
); }),
200 branch_rm(command
, CPROJECT::brm
, [this](const std::string
& a
) { this->do_branch_rm(a
); }),
201 branch_set(command
, CPROJECT::bset
, [this](const std::string
& a
) { this->do_branch_set(a
); }),
202 branch_rp(command
, CPROJECT::brp
, [this](const std::string
& a
) { this->do_branch_rp(a
); }),
203 branch_mv(command
, CPROJECT::bmv
, [this](const std::string
& a
) { this->do_branch_mv(a
); })
205 active_project
= NULL
;
208 project_state::~project_state()
212 project_info
& project_state::load(const std::string
& id
)
214 std::string file
= get_config_path() + "/" + id
+ ".prj";
215 std::ifstream
f(file
);
217 throw std::runtime_error("Can't open project file");
218 project_info
& pi
= *new project_info(edispatch
);
220 pi
.movie_rtc_second
= 1000000000;
221 pi
.movie_rtc_subsecond
= 0;
222 pi
.active_branch
= 0;
225 //First line is always project name.
226 std::getline(f
, pi
.name
);
227 if(!f
|| pi
.name
== "") {
229 throw std::runtime_error("Can't read project file");
233 std::getline(f
, tmp
);
235 if(r
= regex("rom=(.+)", tmp
))
237 else if(r
= regex("last-save=(.+)", tmp
))
239 else if(r
= regex("directory=(.+)", tmp
))
241 else if(r
= regex("prefix=(.+)", tmp
))
243 else if(r
= regex("luascript=(.+)", tmp
))
244 pi
.luascripts
.push_back(r
[1]);
245 else if(r
= regex("gametype=(.+)", tmp
))
247 else if(r
= regex("coreversion=(.+)", tmp
))
248 pi
.coreversion
= r
[1];
249 else if(r
= regex("gamename=(.+)", tmp
))
251 else if(r
= regex("projectid=(.+)", tmp
))
253 else if(r
= regex("projectid=([0-9]+):([0-9]+)", tmp
)) {
254 pi
.movie_rtc_second
= parse_value
<int64_t>(r
[1]);
255 pi
.movie_rtc_subsecond
= parse_value
<int64_t>(r
[2]);
256 } else if(r
= regex("author=(.*)\\|(.*)", tmp
))
257 pi
.authors
.push_back(std::make_pair(r
[1], r
[2]));
258 else if(r
= regex("author=(.+)", tmp
))
259 pi
.authors
.push_back(std::make_pair(r
[1], ""));
260 else if(r
= regex("romsha=([0-9a-f]+)", tmp
))
261 pi
.romimg_sha256
[0] = r
[1];
262 else if(r
= regex("slotsha([a-z])=([0-9a-f]+)", tmp
))
263 pi
.romimg_sha256
[r
[1][0] - 96] = r
[2];
264 else if(r
= regex("romxml=([0-9a-f]+)", tmp
))
265 pi
.romxml_sha256
[0] = r
[1];
266 else if(r
= regex("slotxml([a-z])=([0-9a-f]+)", tmp
))
267 pi
.romxml_sha256
[r
[1][0] - 96] = r
[2];
268 else if(r
= regex("romhint=(.*)", tmp
))
269 pi
.namehint
[0] = r
[1];
270 else if(r
= regex("slothint([a-z])=(.*)", tmp
))
271 pi
.namehint
[r
[1][0] - 96] = r
[2];
272 else if(r
= regex("romrom=(.*)", tmp
))
274 else if(r
= regex("slotrom([a-z])=(.*)", tmp
))
275 pi
.roms
[r
[1][0] - 96] = r
[2];
276 else if(r
= regex("setting.([^=]+)=(.*)", tmp
))
277 pi
.settings
[r
[1]] = r
[2];
278 else if(r
= regex("watch.([^=]+)=(.*)", tmp
))
279 pi
.watches
[eq_unescape(r
[1])] = r
[2];
280 else if(r
= regex("sram.([^=]+)=(.*)", tmp
))
281 concatenate(pi
.movie_sram
[r
[1]], base_decode(r
[2]));
282 else if(r
= regex("macro.([^=]+)=(.*)", tmp
))
284 pi
.macros
[r
[1]] = JSON::node(r
[2]);
285 } catch(std::exception
& e
) {
286 messages
<< "Unable to load macro '" << r
[1] << "': " << e
.what() << std::endl
;
288 else if(r
= regex("anchor=(.*)", tmp
))
289 concatenate(pi
.anchor_savestate
, base_decode(r
[1]));
290 else if(r
= regex("time=([0-9]+):([0-9]+)", tmp
)) {
291 pi
.movie_rtc_second
= parse_value
<int64_t>(r
[1]);
292 pi
.movie_rtc_subsecond
= parse_value
<int64_t>(r
[2]);
293 } else if(r
= regex("branch([1-9][0-9]*)parent=([0-9]+)", tmp
)) {
294 uint64_t bid
= parse_value
<int64_t>(r
[1]);
295 uint64_t pbid
= parse_value
<int64_t>(r
[2]);
296 if(!pi
.branches
.count(bid
))
297 pi
.branches
[bid
].name
= "(Unnamed branch)";
298 pi
.branches
[bid
].pbid
= pbid
;
299 } else if(r
= regex("branch([1-9][0-9]*)name=(.*)", tmp
)) {
300 uint64_t bid
= parse_value
<int64_t>(r
[1]);
301 if(!pi
.branches
.count(bid
))
302 pi
.branches
[bid
].pbid
= 0;
303 pi
.branches
[bid
].name
= r
[2];
304 } else if(r
= regex("branchcurrent=([0-9]+)", tmp
)) {
305 pi
.active_branch
= parse_value
<int64_t>(r
[1]);
306 } else if(r
= regex("branchnext=([0-9]+)", tmp
)) {
307 pi
.next_branch
= parse_value
<int64_t>(r
[1]);
310 for(auto& i
: pi
.branches
) {
311 uint64_t j
= i
.first
;
314 j
= pi
.branches
[j
].pbid
;
318 messages
<< "Warning: Cyclic slot branch dependency, reparenting '" <<
319 pi
.branches
[m
].name
<< "' to be child of root..." << std::endl
;
320 pi
.branches
[j
].pbid
= 0;
325 if(pi
.active_branch
&& !pi
.branches
.count(pi
.active_branch
)) {
326 messages
<< "Warning: Current slot branch does not exist, using root..." << std::endl
;
327 pi
.active_branch
= 0;
332 project_info
* project_state::get()
334 return active_project
;
337 bool project_state::set(project_info
* p
, bool current
)
341 commentary
.unload_collection();
343 edispatch
.core_change();
344 edispatch
.branch_change();
349 moviefile
* newmovie
= NULL
;
350 bool switched
= false;
351 std::set
<core_sysregion
*> sysregs
;
357 sysregs
= core_sysregion::find_matching(p
->gametype
);
359 throw std::runtime_error("No core supports '" + p
->gametype
+ "'");
361 //First, try to load the ROM and the last movie file into RAM...
363 rom_image_handle
_img(new rom_image(p
->rom
, p
->coreversion
));
364 newrom
= loaded_rom(_img
);
366 core_type
* ctype
= NULL
;
367 for(auto i
: sysregs
) {
368 ctype
= &i
->get_type();
369 if(ctype
->get_core_identifier() == p
->coreversion
)
372 rom_image_handle
_img(new rom_image(p
->roms
, ctype
->get_core_identifier(), ctype
->get_iname(),
374 newrom
= loaded_rom(_img
);
376 if(newrom
.get_core_identifier() != p
->coreversion
) {
377 messages
<< "Warning: Can't find matching core, using " << newrom
.get_core_identifier()
380 if(p
->last_save
!= "")
382 newmovie
= new moviefile(p
->last_save
, newrom
.get_internal_rom_type());
383 } catch(std::exception
& e
) {
384 messages
<< "Warning: Can't load last save: " << e
.what() << std::endl
;
385 newmovie
= new moviefile();
386 fill_stub_movie(*newmovie
, *p
, newrom
.get_internal_rom_type());
389 newmovie
= new moviefile();
390 fill_stub_movie(*newmovie
, *p
, newrom
.get_internal_rom_type());
392 //Okay, loaded, load into core.
393 newrom
.load(p
->settings
, p
->movie_rtc_second
, p
->movie_rtc_subsecond
);
395 do_load_state(*newmovie
, LOAD_STATE_DEFAULT
, used
);
399 //Calculate union of old and new.
400 std::set
<std::string
> _watches
= mwatch
.enumerate();
401 for(auto i
: p
->watches
) _watches
.insert(i
.first
);
403 for(auto i
: _watches
)
405 if(p
->watches
.count(i
))
406 mwatch
.set(i
, p
->watches
[i
]);
409 } catch(std::exception
& e
) {
410 messages
<< "Can't set/clear watch '" << i
<< "': " << e
.what() << std::endl
;
412 commentary
.load_collection(p
->directory
+ "/" + p
->prefix
+ ".lsvs");
413 command
.invoke(CLUA::reset
.name
);
414 for(auto i
: p
->luascripts
)
415 command
.invoke(CLUA::run
.name
+ (" " + i
));
416 buttons
.load(controls
, *active_project
);
417 } catch(std::exception
& e
) {
418 if(newmovie
&& !used
)
420 platform::error_message(std::string("Can't switch projects: ") + e
.what());
421 messages
<< "Can't switch projects: " << e
.what() << std::endl
;
426 edispatch
.core_change();
427 edispatch
.branch_change();
432 std::map
<std::string
, std::string
> project_state::enumerate()
434 std::set
<std::string
> projects
;
435 std::map
<std::string
, std::string
> projects2
;
437 projects
= directory::enumerate(get_config_path(), ".*\\.prj");
438 for(auto i
: projects
) {
442 split
= id
.find_last_of("\\/");
444 split
= id
.find_last_of("/");
446 if(split
< id
.length())
447 id
= id
.substr(split
+ 1);
448 id
= id
.substr(0, id
.length() - 4);
450 projects2
[id
] = project_getname(id
);
452 messages
<< "Failed to load name for ID '" << id
<< "'" << std::endl
;
458 std::string
project_state::moviepath()
461 return active_project
->directory
;
463 return SET_moviepath(setgroup
);
466 std::string
project_state::otherpath()
469 return active_project
->directory
;
474 std::string
project_state::savestate_ext()
476 return active_project
? "lss" : "lsmv";
479 void project_state::copy_watches(project_info
& p
)
481 for(auto i
: mwatch
.enumerate()) {
483 p
.watches
[i
] = mwatch
.get_string(i
);
484 } catch(std::exception
& e
) {
485 messages
<< "Can't read memory watch '" << i
<< "': " << e
.what() << std::endl
;
490 void project_state::copy_macros(project_info
& p
, controller_state
& s
)
492 for(auto i
: s
.enumerate_macro())
493 p
.macros
[i
] = s
.get_macro(i
).serialize();
496 project_info::project_info(emulator_dispatch
& _dispatch
)
497 : edispatch(_dispatch
)
501 uint64_t project_info::get_parent_branch(uint64_t bid
)
505 if(!branches
.count(bid
))
506 throw std::runtime_error("Invalid branch ID");
507 return branches
[bid
].pbid
;
510 void project_info::set_current_branch(uint64_t bid
)
512 if(bid
&& !branches
.count(bid
))
513 throw std::runtime_error("Invalid branch ID");
515 edispatch
.branch_change();
516 messages
<< "Set current slot branch to " << get_branch_string() << std::endl
;
519 const std::string
& project_info::get_branch_name(uint64_t bid
)
521 static std::string rootname
= "(root)";
524 if(!branches
.count(bid
))
525 throw std::runtime_error("Invalid branch ID");
526 return branches
[bid
].name
;
529 void project_info::set_branch_name(uint64_t bid
, const std::string
& name
)
532 throw std::runtime_error("Root branch name can't be set");
533 if(!branches
.count(bid
))
534 throw std::runtime_error("Invalid branch ID");
535 branches
[bid
].name
= name
;
536 edispatch
.branch_change();
539 void project_info::set_parent_branch(uint64_t bid
, uint64_t pbid
)
542 throw std::runtime_error("Root branch never has parent");
543 if(!branches
.count(bid
))
544 throw std::runtime_error("Invalid branch ID");
545 if(pbid
&& !branches
.count(pbid
))
546 throw std::runtime_error("Invalid parent branch ID");
548 throw std::runtime_error("Branch can't be its own parent");
549 for(auto& i
: branches
) {
550 uint64_t j
= i
.first
;
552 j
= (j
== bid
) ? pbid
: branches
[j
].pbid
;
554 throw std::runtime_error("Reparenting would create a circular dependency");
557 branches
[bid
].pbid
= pbid
;
558 edispatch
.branch_change();
561 std::set
<uint64_t> project_info::branch_children(uint64_t bid
)
563 if(bid
&& !branches
.count(bid
))
564 throw std::runtime_error("Invalid branch ID");
565 std::set
<uint64_t> r
;
566 for(auto& i
: branches
)
567 if(i
.second
.pbid
== bid
)
572 uint64_t project_info::create_branch(uint64_t pbid
, const std::string
& name
)
574 if(pbid
&& !branches
.count(pbid
))
575 throw std::runtime_error("Invalid parent branch ID");
576 uint64_t assign_bid
= next_branch
;
577 uint64_t last_bid
= (branches
.empty() ? 1 : branches
.rbegin()->first
+ 1);
578 assign_bid
= max(assign_bid
, last_bid
);
579 branches
[assign_bid
].name
= name
;
580 branches
[assign_bid
].pbid
= pbid
;
581 next_branch
= assign_bid
+ 1;
582 edispatch
.branch_change();
586 void project_info::delete_branch(uint64_t bid
)
589 throw std::runtime_error("Root branch can not be deleted");
590 if(bid
== active_branch
)
591 throw std::runtime_error("Current branch can't be deleted");
592 if(!branches
.count(bid
))
593 throw std::runtime_error("Invalid branch ID");
594 for(auto& i
: branches
)
595 if(i
.second
.pbid
== bid
)
596 throw std::runtime_error("Can't delete branch with children");
598 edispatch
.branch_change();
601 std::string
project_info::get_branch_string()
604 uint64_t j
= active_branch
;
609 r
= get_branch_name(j
);
611 r
= get_branch_name(j
) + "→" + r
;
612 j
= get_parent_branch(j
);
617 void project_info::flush()
619 std::string file
= get_config_path() + "/" + id
+ ".prj";
620 std::string tmpfile
= get_config_path() + "/" + id
+ ".prj.tmp";
621 std::string bakfile
= get_config_path() + "/" + id
+ ".prj.bak";
622 std::ofstream
f(tmpfile
);
624 throw std::runtime_error("Can't write project file");
627 throw std::runtime_error("Can't write project file");
630 std::ifstream
f2(file
);
632 std::ofstream
f3(bakfile
);
634 throw std::runtime_error("Can't backup project file");
637 std::getline(f2
, tmp
);
638 f3
<< tmp
<< std::endl
;
643 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
644 if(MoveFileEx(tmpfile
.c_str(), file
.c_str(), MOVEFILE_REPLACE_EXISTING
) < 0)
646 if(rename(tmpfile
.c_str(), file
.c_str()) < 0)
648 throw std::runtime_error("Can't replace project file");
651 void project_info::write(std::ostream
& s
)
653 s
<< name
<< std::endl
;
654 s
<< "rom=" << rom
<< std::endl
;
656 s
<< "last-save=" << last_save
<< std::endl
;
657 s
<< "directory=" << directory
<< std::endl
;
658 s
<< "prefix=" << prefix
<< std::endl
;
659 for(auto i
: luascripts
)
660 s
<< "luascript=" << i
<< std::endl
;
661 s
<< "gametype=" << gametype
<< std::endl
;
662 s
<< "coreversion=" << coreversion
<< std::endl
;
664 s
<< "gamename=" << gamename
<< std::endl
;
665 s
<< "projectid=" << projectid
<< std::endl
;
666 s
<< "time=" << movie_rtc_second
<< ":" << movie_rtc_subsecond
<< std::endl
;
667 for(auto i
: authors
)
668 s
<< "author=" << i
.first
<< "|" << i
.second
<< std::endl
;
669 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
670 if(romimg_sha256
[i
] != "") {
672 s
<< "slotsha" << static_cast<char>(96 + i
) << "=" << romimg_sha256
[i
] << std::endl
;
674 s
<< "romsha=" << romimg_sha256
[i
] << std::endl
;
676 if(romxml_sha256
[i
] != "") {
678 s
<< "slotxml" << static_cast<char>(96 + i
) << "=" << romxml_sha256
[i
] << std::endl
;
680 s
<< "romxml=" << romxml_sha256
[i
] << std::endl
;
682 if(namehint
[i
] != "") {
684 s
<< "slothint" << static_cast<char>(96 + i
) << "=" << namehint
[i
] << std::endl
;
686 s
<< "romhint=" << namehint
[i
] << std::endl
;
690 s
<< "slotrom" << static_cast<char>(96 + i
) << "=" << roms
[i
] << std::endl
;
692 s
<< "romrom=" << roms
[i
] << std::endl
;
695 for(auto i
: settings
)
696 s
<< "setting." << i
.first
<< "=" << i
.second
<< std::endl
;
697 for(auto i
: watches
)
698 s
<< "watch." << eq_escape(i
.first
) << "=" << i
.second
<< std::endl
;
700 s
<< "macro." + i
.first
<< "=" << i
.second
.serialize() << std::endl
;
701 for(auto i
: movie_sram
)
702 save_binary(s
, "sram." + i
.first
, i
.second
);
703 if(anchor_savestate
.size())
704 save_binary(s
, "anchor", anchor_savestate
);
705 for(auto& i
: branches
) {
706 s
<< "branch" << i
.first
<< "parent=" << i
.second
.pbid
<< std::endl
;
707 s
<< "branch" << i
.first
<< "name=" << i
.second
.name
<< std::endl
;
709 s
<< "branchcurrent=" << active_branch
<< std::endl
;
710 s
<< "branchnext=" << next_branch
<< std::endl
;
713 void project_state::do_branch_mk(const std::string
& args
)
715 regex_results r
= regex("([0-9]+)[ \t]+(.*)", args
);
717 messages
<< "Syntax: create-branch <parentid> <name>" << std::endl
;
722 uint64_t pbid
= parse_value
<uint64_t>(r
[1]);
724 throw std::runtime_error("Not in project context");
725 uint64_t bid
= prj
->create_branch(pbid
, r
[2]);
726 messages
<< "Created branch #" << bid
<< std::endl
;
728 } catch(std::exception
& e
) {
729 messages
<< "Can't create new branch: " << e
.what() << std::endl
;
733 void project_state::do_branch_rm(const std::string
& args
)
735 regex_results r
= regex("([0-9]+)[ \t]*", args
);
737 messages
<< "Syntax: delete-branch <id>" << std::endl
;
742 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
744 throw std::runtime_error("Not in project context");
745 prj
->delete_branch(bid
);
746 messages
<< "Deleted branch #" << bid
<< std::endl
;
748 } catch(std::exception
& e
) {
749 messages
<< "Can't delete branch: " << e
.what() << std::endl
;
753 void project_state::do_branch_set(const std::string
& args
)
755 regex_results r
= regex("([0-9]+)[ \t]*", args
);
757 messages
<< "Syntax: set-branch <id>" << std::endl
;
762 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
764 throw std::runtime_error("Not in project context");
765 prj
->set_current_branch(bid
);
766 messages
<< "Set current branch to #" << bid
<< std::endl
;
769 } catch(std::exception
& e
) {
770 messages
<< "Can't set branch: " << e
.what() << std::endl
;
774 void project_state::do_branch_rp(const std::string
& args
)
776 regex_results r
= regex("([0-9]+)[ \t]+([0-9]+)[ \t]*", args
);
778 messages
<< "Syntax: reparent-branch <id> <newpid>" << std::endl
;
783 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
784 uint64_t pbid
= parse_value
<uint64_t>(r
[2]);
786 throw std::runtime_error("Not in project context");
787 prj
->set_parent_branch(bid
, pbid
);
788 messages
<< "Reparented branch #" << bid
<< std::endl
;
791 } catch(std::exception
& e
) {
792 messages
<< "Can't reparent branch: " << e
.what() << std::endl
;
796 void project_state::do_branch_mv(const std::string
& args
)
798 regex_results r
= regex("([0-9]+)[ \t]+(.*)", args
);
800 messages
<< "Syntax: rename-branch <id> <name>" << std::endl
;
805 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
807 throw std::runtime_error("Not in project context");
808 prj
->set_branch_name(bid
, r
[2]);
809 messages
<< "Renamed branch #" << bid
<< std::endl
;
812 } catch(std::exception
& e
) {
813 messages
<< "Can't rename branch: " << e
.what() << std::endl
;
817 void project_state::do_branch_ls()
819 std::set
<unsigned> dset
;
820 recursive_list_branch(0, dset
, 0, false);
823 void project_state::recursive_list_branch(uint64_t bid
, std::set
<unsigned>& dset
, unsigned depth
, bool last_of
)
827 messages
<< "Not in project context." << std::endl
;
830 std::set
<uint64_t> children
= prj
->branch_children(bid
);
832 for(unsigned i
= 0; i
+ 1 < depth
; i
++)
833 prefix
+= (dset
.count(i
) ? "\u2502" : " ");
834 prefix
+= (dset
.count(depth
- 1) ? (last_of
? "\u2514" : "\u251c") : " ");
835 if(last_of
) dset
.erase(depth
- 1);
837 << ((bid
== prj
->get_current_branch()) ? "*" : "")
838 << bid
<< ":" << prj
->get_branch_name(bid
) << std::endl
;
841 for(auto i
: children
) {
842 bool last
= (++c
== children
.size());
843 recursive_list_branch(i
, dset
, depth
+ 1, last
);