Lua: Fix type confusion between signed and unsigned
[lsnes.git] / src / core / project.cpp
bloba9be77d96bc12d8f15d418785cd24a06ce51b6a1
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"
21 #include <fstream>
22 #include <dirent.h>
23 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
24 #define FUCKED_SYSTEM
25 #include <windows.h>
26 #endif
28 void do_flush_slotinfo();
30 namespace
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)
41 std::vector<char> r;
42 size_t len = str.length();
43 size_t i;
44 uint32_t a, b, c, d, e, v;
45 for(i = 0; i + 5 <= len; i += 5) {
46 a = str[i + 0] - 33;
47 b = str[i + 1] - 33;
48 c = str[i + 2] - 33;
49 d = str[i + 3] - 33;
50 e = str[i + 4] - 33;
51 v = 52200625 * a + 614125 * b + 7225 * c + 85 * d + e;
52 r.push_back(v);
53 r.push_back(v >> 8);
54 r.push_back(v >> 16);
55 r.push_back(v >> 24);
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);
66 return r;
69 template<int x> void blockcode(std::ostringstream& s, const std::vector<char>& data, size_t& ptr,
70 size_t& chars)
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;
78 v >>= (32 - 8 * x);
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);
84 chars -= (x + 1);
87 std::pair<std::string, size_t> base_encode(const std::vector<char>& data, size_t ptr, size_t chars)
89 std::ostringstream s;
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++) {
105 if(str[i] == '\\')
106 s << "\\\\";
107 else if(str[i] == '=')
108 s << "\\e";
109 else
110 s << str[i];
112 return s.str();
115 std::string eq_unescape(const std::string& str)
117 std::ostringstream s;
118 size_t len = str.length();
119 bool escape = false;
120 for(size_t i = 0; i < len; i++) {
121 if(escape) {
122 if(str[i] == 'e')
123 s << "=";
124 else
125 s << str[i];
126 escape = false;
127 } else {
128 if(str[i] == '\\')
129 escape = true;
130 else
131 s << str[i];
134 return s.str();
137 void save_binary(std::ostream& s, const std::string& key, const std::vector<char>& value)
139 size_t ptr = 0;
140 while(ptr < value.size()) {
141 auto v = base_encode(value, ptr, 60);
142 s << key << "=" << v.first << std::endl;
143 ptr = v.second;
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);
169 m.clear_dynstate();
170 try {
171 m.gametype = &coretype.lookup_sysregion(p.gametype);
172 } catch(std::bad_alloc& e) {
173 throw;
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);
183 if(!f)
184 throw std::runtime_error("Can't open project file");
185 std::string name;
186 std::getline(f, name);
187 if(!f)
188 throw std::runtime_error("Can't read project name");
189 return 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);
216 if(!f)
217 throw std::runtime_error("Can't open project file");
218 project_info& pi = *new project_info(edispatch);
219 pi.id = id;
220 pi.movie_rtc_second = 1000000000;
221 pi.movie_rtc_subsecond = 0;
222 pi.active_branch = 0;
223 pi.next_branch = 0;
224 pi.filename = file;
225 //First line is always project name.
226 std::getline(f, pi.name);
227 if(!f || pi.name == "") {
228 delete &pi;
229 throw std::runtime_error("Can't read project file");
231 while(f) {
232 std::string tmp;
233 std::getline(f, tmp);
234 regex_results r;
235 if(r = regex("rom=(.+)", tmp))
236 pi.rom = r[1];
237 else if(r = regex("last-save=(.+)", tmp))
238 pi.last_save = r[1];
239 else if(r = regex("directory=(.+)", tmp))
240 pi.directory = r[1];
241 else if(r = regex("prefix=(.+)", tmp))
242 pi.prefix = r[1];
243 else if(r = regex("luascript=(.+)", tmp))
244 pi.luascripts.push_back(r[1]);
245 else if(r = regex("gametype=(.+)", tmp))
246 pi.gametype = r[1];
247 else if(r = regex("coreversion=(.+)", tmp))
248 pi.coreversion = r[1];
249 else if(r = regex("gamename=(.+)", tmp))
250 pi.gamename = r[1];
251 else if(r = regex("projectid=(.+)", tmp))
252 pi.projectid = r[1];
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))
273 pi.roms[0] = r[1];
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))
283 try {
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;
312 uint64_t m = j;
313 while(j) {
314 j = pi.branches[j].pbid;
315 m = min(m, j);
316 if(j == i.first) {
317 //Cyclic dependency!
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;
321 break;
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;
329 return pi;
332 project_info* project_state::get()
334 return active_project;
337 bool project_state::set(project_info* p, bool current)
339 if(!p) {
340 if(active_project)
341 commentary.unload_collection();
342 active_project = p;
343 edispatch.core_change();
344 edispatch.branch_change();
345 return true;
348 loaded_rom newrom;
349 moviefile* newmovie = NULL;
350 bool switched = false;
351 std::set<core_sysregion*> sysregs;
352 bool used = false;
353 try {
354 if(current)
355 goto skip_rom_movie;
357 sysregs = core_sysregion::find_matching(p->gametype);
358 if(sysregs.empty())
359 throw std::runtime_error("No core supports '" + p->gametype + "'");
361 //First, try to load the ROM and the last movie file into RAM...
362 if(p->rom != "") {
363 rom_image_handle _img(new rom_image(p->rom, p->coreversion));
364 newrom = loaded_rom(_img);
365 } else {
366 core_type* ctype = NULL;
367 for(auto i : sysregs) {
368 ctype = &i->get_type();
369 if(ctype->get_core_identifier() == p->coreversion)
370 break;
372 rom_image_handle _img(new rom_image(p->roms, ctype->get_core_identifier(), ctype->get_iname(),
373 ""));
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()
378 << std::endl;
380 if(p->last_save != "")
381 try {
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());
388 else {
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);
394 rom = newrom;
395 do_load_state(*newmovie, LOAD_STATE_DEFAULT, used);
396 skip_rom_movie:
397 active_project = p;
398 switched = true;
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)
404 try {
405 if(p->watches.count(i))
406 mwatch.set(i, p->watches[i]);
407 else
408 mwatch.clear(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)
419 delete newmovie;
420 platform::error_message(std::string("Can't switch projects: ") + e.what());
421 messages << "Can't switch projects: " << e.what() << std::endl;
423 if(switched) {
424 do_flush_slotinfo();
425 supdater.update();
426 edispatch.core_change();
427 edispatch.branch_change();
429 return switched;
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) {
439 std::string id = i;
440 size_t split;
441 #ifdef FUCKED_SYSTEM
442 split = id.find_last_of("\\/");
443 #else
444 split = id.find_last_of("/");
445 #endif
446 if(split < id.length())
447 id = id.substr(split + 1);
448 id = id.substr(0, id.length() - 4);
449 try {
450 projects2[id] = project_getname(id);
451 } catch(...) {
452 messages << "Failed to load name for ID '" << id << "'" << std::endl;
455 return projects2;
458 std::string project_state::moviepath()
460 if(active_project)
461 return active_project->directory;
462 else
463 return SET_moviepath(setgroup);
466 std::string project_state::otherpath()
468 if(active_project)
469 return active_project->directory;
470 else
471 return ".";
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()) {
482 try {
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)
503 if(!bid)
504 return 0;
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");
514 active_branch = bid;
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)";
522 if(!bid)
523 return rootname;
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)
531 if(!bid)
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)
541 if(!bid)
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");
547 if(bid == pbid)
548 throw std::runtime_error("Branch can't be its own parent");
549 for(auto& i : branches) {
550 uint64_t j = i.first;
551 while(j) {
552 j = (j == bid) ? pbid : branches[j].pbid;
553 if(j == i.first)
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)
568 r.insert(i.first);
569 return r;
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();
583 return assign_bid;
586 void project_info::delete_branch(uint64_t bid)
588 if(!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");
597 branches.erase(bid);
598 edispatch.branch_change();
601 std::string project_info::get_branch_string()
603 std::string r;
604 uint64_t j = active_branch;
605 if(!j)
606 return "(root)";
607 while(j) {
608 if(r == "")
609 r = get_branch_name(j);
610 else
611 r = get_branch_name(j) + "→" + r;
612 j = get_parent_branch(j);
614 return r;
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);
623 if(!f)
624 throw std::runtime_error("Can't write project file");
625 write(f);
626 if(!f)
627 throw std::runtime_error("Can't write project file");
628 f.close();
630 std::ifstream f2(file);
631 if(f2) {
632 std::ofstream f3(bakfile);
633 if(!f3)
634 throw std::runtime_error("Can't backup project file");
635 while(f2) {
636 std::string tmp;
637 std::getline(f2, tmp);
638 f3 << tmp << std::endl;
640 f2.close();
641 f3.close();
643 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
644 if(MoveFileEx(tmpfile.c_str(), file.c_str(), MOVEFILE_REPLACE_EXISTING) < 0)
645 #else
646 if(rename(tmpfile.c_str(), file.c_str()) < 0)
647 #endif
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;
655 if(last_save != "")
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;
663 if(gamename != "")
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] != "") {
671 if(i)
672 s << "slotsha" << static_cast<char>(96 + i) << "=" << romimg_sha256[i] << std::endl;
673 else
674 s << "romsha=" << romimg_sha256[i] << std::endl;
676 if(romxml_sha256[i] != "") {
677 if(i)
678 s << "slotxml" << static_cast<char>(96 + i) << "=" << romxml_sha256[i] << std::endl;
679 else
680 s << "romxml=" << romxml_sha256[i] << std::endl;
682 if(namehint[i] != "") {
683 if(i)
684 s << "slothint" << static_cast<char>(96 + i) << "=" << namehint[i] << std::endl;
685 else
686 s << "romhint=" << namehint[i] << std::endl;
688 if(roms[i] != "") {
689 if(i)
690 s << "slotrom" << static_cast<char>(96 + i) << "=" << roms[i] << std::endl;
691 else
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;
699 for(auto i : macros)
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);
716 if(!r) {
717 messages << "Syntax: create-branch <parentid> <name>" << std::endl;
718 return;
720 try {
721 auto prj = get();
722 uint64_t pbid = parse_value<uint64_t>(r[1]);
723 if(!prj)
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;
727 prj->flush();
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);
736 if(!r) {
737 messages << "Syntax: delete-branch <id>" << std::endl;
738 return;
740 try {
741 auto prj = get();
742 uint64_t bid = parse_value<uint64_t>(r[1]);
743 if(!prj)
744 throw std::runtime_error("Not in project context");
745 prj->delete_branch(bid);
746 messages << "Deleted branch #" << bid << std::endl;
747 prj->flush();
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);
756 if(!r) {
757 messages << "Syntax: set-branch <id>" << std::endl;
758 return;
760 try {
761 auto prj = get();
762 uint64_t bid = parse_value<uint64_t>(r[1]);
763 if(!prj)
764 throw std::runtime_error("Not in project context");
765 prj->set_current_branch(bid);
766 messages << "Set current branch to #" << bid << std::endl;
767 prj->flush();
768 supdater.update();
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);
777 if(!r) {
778 messages << "Syntax: reparent-branch <id> <newpid>" << std::endl;
779 return;
781 try {
782 auto prj = get();
783 uint64_t bid = parse_value<uint64_t>(r[1]);
784 uint64_t pbid = parse_value<uint64_t>(r[2]);
785 if(!prj)
786 throw std::runtime_error("Not in project context");
787 prj->set_parent_branch(bid, pbid);
788 messages << "Reparented branch #" << bid << std::endl;
789 prj->flush();
790 supdater.update();
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);
799 if(!r) {
800 messages << "Syntax: rename-branch <id> <name>" << std::endl;
801 return;
803 try {
804 auto prj = get();
805 uint64_t bid = parse_value<uint64_t>(r[1]);
806 if(!prj)
807 throw std::runtime_error("Not in project context");
808 prj->set_branch_name(bid, r[2]);
809 messages << "Renamed branch #" << bid << std::endl;
810 prj->flush();
811 supdater.update();
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)
825 auto prj = get();
826 if(!prj) {
827 messages << "Not in project context." << std::endl;
828 return;
830 std::set<uint64_t> children = prj->branch_children(bid);
831 std::string prefix;
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);
836 messages << prefix
837 << ((bid == prj->get_current_branch()) ? "*" : "")
838 << bid << ":" << prj->get_branch_name(bid) << std::endl;
839 dset.insert(depth);
840 size_t c = 0;
841 for(auto i : children) {
842 bool last = (++c == children.size());
843 recursive_list_branch(i, dset, depth + 1, last);
845 dset.erase(depth);