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/string.hpp"
17 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
22 void do_flush_slotinfo();
26 project_info
* active_project
= NULL
;
28 void concatenate(std::vector
<char>& data
, const std::vector
<char>& app
)
30 size_t dsize
= data
.size();
31 data
.resize(dsize
+ app
.size());
32 std::copy(app
.begin(), app
.end(), data
.begin() + dsize
);
35 std::vector
<char> base_decode(const std::string
& str
)
38 size_t len
= str
.length();
40 uint32_t a
, b
, c
, d
, e
, v
;
41 for(i
= 0; i
+ 5 <= len
; i
+= 5) {
47 v
= 52200625 * a
+ 614125 * b
+ 7225 * c
+ 85 * d
+ e
;
53 a
= b
= c
= d
= e
= 0;
54 if(i
+ 0 < len
) e
= str
[len
- 1] - 33;
55 if(i
+ 1 < len
) d
= str
[len
- 2] - 33;
56 if(i
+ 2 < len
) c
= str
[len
- 3] - 33;
57 if(i
+ 3 < len
) b
= str
[len
- 4] - 33;
58 v
= 614125 * b
+ 7225 * c
+ 85 * d
+ e
;
59 if(i
+ 1 < len
) r
.push_back(v
);
60 if(i
+ 2 < len
) r
.push_back(v
>> 8);
61 if(i
+ 3 < len
) r
.push_back(v
>> 16);
65 template<int x
> void blockcode(std::ostringstream
& s
, const std::vector
<char>& data
, size_t& ptr
,
68 uint32_t a
= 0, b
= 0, c
= 0, d
= 0, v
;
69 if(x
>= 4) a
= static_cast<uint8_t>(data
[ptr
++]);
70 if(x
>= 3) b
= static_cast<uint8_t>(data
[ptr
++]);
71 if(x
>= 2) c
= static_cast<uint8_t>(data
[ptr
++]);
72 if(x
>= 1) d
= static_cast<uint8_t>(data
[ptr
++]);
73 v
= 16777216 * d
+ 65536 * c
+ 256 * b
+ a
;
75 if(x
>= 4) s
<< static_cast<char>(v
/ 52200625 % 85 + 33);
76 if(x
>= 3) s
<< static_cast<char>(v
/ 614125 % 85 + 33);
77 if(x
>= 2) s
<< static_cast<char>(v
/ 7225 % 85 + 33);
78 if(x
>= 1) s
<< static_cast<char>(v
/ 85 % 85 + 33);
79 if(x
>= 1) s
<< static_cast<char>(v
% 85 + 33);
83 std::pair
<std::string
, size_t> base_encode(const std::vector
<char>& data
, size_t ptr
, size_t chars
)
86 while(chars
>= 5 && ptr
+ 4 <= data
.size())
87 blockcode
<4>(s
, data
, ptr
, chars
);
88 if(chars
>= 4 && ptr
+ 3 <= data
.size())
89 blockcode
<3>(s
, data
, ptr
, chars
);
90 if(chars
>= 3 && ptr
+ 2 <= data
.size())
91 blockcode
<2>(s
, data
, ptr
, chars
);
92 if(chars
>= 2 && ptr
+ 1 <= data
.size())
93 blockcode
<1>(s
, data
, ptr
, chars
);
94 return std::make_pair(s
.str(), ptr
);
96 std::string
eq_escape(const std::string
& str
)
99 size_t len
= str
.length();
100 for(size_t i
= 0; i
< len
; i
++) {
103 else if(str
[i
] == '=')
111 std::string
eq_unescape(const std::string
& str
)
113 std::ostringstream s
;
114 size_t len
= str
.length();
116 for(size_t i
= 0; i
< len
; i
++) {
133 void save_binary(std::ostream
& s
, const std::string
& key
, const std::vector
<char>& value
)
136 while(ptr
< value
.size()) {
137 auto v
= base_encode(value
, ptr
, 60);
138 s
<< key
<< "=" << v
.first
<< std::endl
;
143 void project_write(std::ostream
& s
, project_info
& p
)
145 s
<< p
.name
<< std::endl
;
146 s
<< "rom=" << p
.rom
<< std::endl
;
147 if(p
.last_save
!= "")
148 s
<< "last-save=" << p
.last_save
<< std::endl
;
149 s
<< "directory=" << p
.directory
<< std::endl
;
150 s
<< "prefix=" << p
.prefix
<< std::endl
;
151 for(auto i
: p
.luascripts
)
152 s
<< "luascript=" << i
<< std::endl
;
153 s
<< "gametype=" << p
.gametype
<< std::endl
;
154 s
<< "coreversion=" << p
.coreversion
<< std::endl
;
156 s
<< "gamename=" << p
.gamename
<< std::endl
;
157 s
<< "projectid=" << p
.projectid
<< std::endl
;
158 s
<< "time=" << p
.movie_rtc_second
<< ":" << p
.movie_rtc_subsecond
<< std::endl
;
159 for(auto i
: p
.authors
)
160 s
<< "author=" << i
.first
<< "|" << i
.second
<< std::endl
;
161 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
162 if(p
.romimg_sha256
[i
] != "") {
164 s
<< "slotsha" << static_cast<char>(96 + i
) << "=" << p
.romimg_sha256
[i
]
167 s
<< "romsha=" << p
.romimg_sha256
[i
] << std::endl
;
169 if(p
.romxml_sha256
[i
] != "") {
171 s
<< "slotxml" << static_cast<char>(96 + i
) << "=" << p
.romxml_sha256
[i
]
174 s
<< "romxml=" << p
.romxml_sha256
[i
] << std::endl
;
176 if(p
.namehint
[i
] != "") {
178 s
<< "slothint" << static_cast<char>(96 + i
) << "=" << p
.namehint
[i
]
181 s
<< "romhint=" << p
.namehint
[i
] << std::endl
;
183 if(p
.roms
[i
] != "") {
185 s
<< "slotrom" << static_cast<char>(96 + i
) << "=" << p
.roms
[i
]
188 s
<< "romrom=" << p
.roms
[i
] << std::endl
;
191 for(auto i
: p
.settings
)
192 s
<< "setting." << i
.first
<< "=" << i
.second
<< std::endl
;
193 for(auto i
: p
.watches
)
194 s
<< "watch." << eq_escape(i
.first
) << "=" << i
.second
<< std::endl
;
195 for(auto i
: p
.macros
)
196 s
<< "macro." + i
.first
<< "=" << i
.second
.serialize() << std::endl
;
197 for(auto i
: p
.movie_sram
)
198 save_binary(s
, "sram." + i
.first
, i
.second
);
199 if(p
.anchor_savestate
.size())
200 save_binary(s
, "anchor", p
.anchor_savestate
);
203 void fill_stub_movie(struct moviefile
& m
, struct project_info
& p
, struct core_type
& coretype
)
205 //Create a dummy movie.
206 m
.lazy_project_create
= false;
207 m
.start_paused
= true;
208 m
.movie_rtc_second
= p
.movie_rtc_second
;
209 m
.movie_rtc_subsecond
= p
.movie_rtc_subsecond
;
212 m
.anchor_savestate
= p
.anchor_savestate
;
213 m
.movie_sram
= p
.movie_sram
;
214 m
.authors
= p
.authors
;
215 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
216 m
.romimg_sha256
[i
] = p
.romimg_sha256
[i
];
217 m
.romxml_sha256
[i
] = p
.romxml_sha256
[i
];
218 m
.namehint
[i
] = p
.namehint
[i
];
220 m
.projectid
= p
.projectid
;
221 m
.coreversion
= p
.coreversion
;
222 m
.gamename
= p
.gamename
;
223 m
.settings
= p
.settings
;
224 auto ctrldata
= coretype
.controllerconfig(m
.settings
);
225 port_type_set
& ports
= port_type_set::make(ctrldata
.ports
, ctrldata
.portindex());
226 m
.input
.clear(ports
);
228 m
.gametype
= &coretype
.lookup_sysregion(p
.gametype
);
229 } catch(std::bad_alloc
& e
) {
231 } catch(std::exception
& e
) {
232 throw std::runtime_error("Illegal game type '" + p
.gametype
+ "'");
236 std::string
project_getname(const std::string
& id
)
238 std::string file
= get_config_path() + "/" + id
+ ".prj";
239 std::ifstream
f(file
);
241 throw std::runtime_error("Can't open project file");
243 std::getline(f
, name
);
245 throw std::runtime_error("Can't read project name");
250 project_info
& project_load(const std::string
& id
)
252 std::string file
= get_config_path() + "/" + id
+ ".prj";
253 std::ifstream
f(file
);
255 throw std::runtime_error("Can't open project file");
256 project_info
& pi
= *new project_info();
258 //First line is always project name.
259 std::getline(f
, pi
.name
);
260 if(!f
|| pi
.name
== "") {
262 throw std::runtime_error("Can't read project file");
266 std::getline(f
, tmp
);
268 if(r
= regex("rom=(.+)", tmp
))
270 else if(r
= regex("last-save=(.+)", tmp
))
272 else if(r
= regex("directory=(.+)", tmp
))
274 else if(r
= regex("prefix=(.+)", tmp
))
276 else if(r
= regex("luascript=(.+)", tmp
))
277 pi
.luascripts
.push_back(r
[1]);
278 else if(r
= regex("gametype=(.+)", tmp
))
280 else if(r
= regex("coreversion=(.+)", tmp
))
281 pi
.coreversion
= r
[1];
282 else if(r
= regex("gamename=(.+)", tmp
))
284 else if(r
= regex("projectid=(.+)", tmp
))
286 else if(r
= regex("projectid=([0-9]+):([0-9]+)", tmp
)) {
287 pi
.movie_rtc_second
= parse_value
<int64_t>(r
[1]);
288 pi
.movie_rtc_subsecond
= parse_value
<int64_t>(r
[2]);
289 } else if(r
= regex("author=(.*)\\|(.*)", tmp
))
290 pi
.authors
.push_back(std::make_pair(r
[1], r
[2]));
291 else if(r
= regex("author=(.+)", tmp
))
292 pi
.authors
.push_back(std::make_pair(r
[1], ""));
293 else if(r
= regex("romsha=([0-9a-f]+)", tmp
))
294 pi
.romimg_sha256
[0] = r
[1];
295 else if(r
= regex("slotsha([a-z])=([0-9a-f]+)", tmp
))
296 pi
.romimg_sha256
[r
[1][0] - 96] = r
[2];
297 else if(r
= regex("romxml=([0-9a-f]+)", tmp
))
298 pi
.romxml_sha256
[0] = r
[1];
299 else if(r
= regex("slotxml([a-z])=([0-9a-f]+)", tmp
))
300 pi
.romxml_sha256
[r
[1][0] - 96] = r
[2];
301 else if(r
= regex("romhint=(.*)", tmp
))
302 pi
.namehint
[0] = r
[1];
303 else if(r
= regex("slothint([a-z])=(.*)", tmp
))
304 pi
.namehint
[r
[1][0] - 96] = r
[2];
305 else if(r
= regex("romrom=(.*)", tmp
))
307 else if(r
= regex("slotrom([a-z])=(.*)", tmp
))
308 pi
.roms
[r
[1][0] - 96] = r
[2];
309 else if(r
= regex("setting.([^=]+)=(.*)", tmp
))
310 pi
.settings
[r
[1]] = r
[2];
311 else if(r
= regex("watch.([^=]+)=(.*)", tmp
))
312 pi
.watches
[eq_unescape(r
[1])] = r
[2];
313 else if(r
= regex("sram.([^=]+)=(.*)", tmp
))
314 concatenate(pi
.movie_sram
[r
[1]], base_decode(r
[2]));
315 else if(r
= regex("macro.([^=]+)=(.*)", tmp
))
317 pi
.macros
[r
[1]] = JSON::node(r
[2]);
318 } catch(std::exception
& e
) {
319 messages
<< "Unable to load macro '" << r
[1] << "': " << e
.what() << std::endl
;
321 else if(r
= regex("anchor=(.*)", tmp
))
322 concatenate(pi
.anchor_savestate
, base_decode(r
[1]));
323 else if(r
= regex("time=([0-9]+):([0-9]+)", tmp
)) {
324 pi
.movie_rtc_second
= parse_value
<int64_t>(r
[1]);
325 pi
.movie_rtc_subsecond
= parse_value
<int64_t>(r
[2]);
331 void project_flush(project_info
* p
)
335 std::string file
= get_config_path() + "/" + p
->id
+ ".prj";
336 std::string tmpfile
= get_config_path() + "/" + p
->id
+ ".prj.tmp";
337 std::string bakfile
= get_config_path() + "/" + p
->id
+ ".prj.bak";
338 std::ofstream
f(tmpfile
);
340 throw std::runtime_error("Can't write project file");
341 project_write(f
, *p
);
343 throw std::runtime_error("Can't write project file");
346 std::ifstream
f2(file
);
348 std::ofstream
f3(bakfile
);
350 throw std::runtime_error("Can't backup project file");
353 std::getline(f2
, tmp
);
354 f3
<< tmp
<< std::endl
;
359 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
360 if(MoveFileEx(tmpfile
.c_str(), file
.c_str(), MOVEFILE_REPLACE_EXISTING
) < 0)
362 if(rename(tmpfile
.c_str(), file
.c_str()) < 0)
364 throw std::runtime_error("Can't replace project file");
367 project_info
* project_get()
369 return active_project
;
372 bool project_set(project_info
* p
, bool current
)
376 voicesub_unload_collection();
378 notify_core_change();
384 bool switched
= false;
385 std::set
<core_sysregion
*> sysregs
;
390 sysregs
= core_sysregion::find_matching(p
->gametype
);
392 throw std::runtime_error("No core supports '" + p
->gametype
+ "'");
394 //First, try to load the ROM and the last movie file into RAM...
396 newrom
= loaded_rom(p
->rom
, p
->coreversion
);
398 core_type
* ctype
= NULL
;
399 for(auto i
: sysregs
) {
400 ctype
= &i
->get_type();
401 if(ctype
->get_core_identifier() == p
->coreversion
)
404 newrom
= loaded_rom(p
->roms
, ctype
->get_core_identifier(), ctype
->get_iname(), "");
406 if(newrom
.rtype
->get_core_identifier() != p
->coreversion
) {
407 messages
<< "Warning: Can't find matching core, using " << newrom
.rtype
->get_core_identifier()
410 if(p
->last_save
!= "")
412 newmovie
= moviefile(p
->last_save
, *newrom
.rtype
);
413 } catch(std::exception
& e
) {
414 messages
<< "Warning: Can't load last save: " << e
.what() << std::endl
;
415 fill_stub_movie(newmovie
, *p
, *newrom
.rtype
);
418 fill_stub_movie(newmovie
, *p
, *newrom
.rtype
);
420 //Okay, loaded, load into core.
421 newrom
.load(p
->settings
, p
->movie_rtc_second
, p
->movie_rtc_subsecond
);
423 do_load_state(newmovie
, LOAD_STATE_DEFAULT
);
427 for(auto i
: get_watches())
428 if(p
->watches
.count(i
))
429 set_watchexpr_for(i
, p
->watches
[i
]);
431 set_watchexpr_for(i
, "");
432 voicesub_load_collection(p
->directory
+ "/" + p
->prefix
+ ".lsvs");
433 lsnes_cmd
.invoke("reset-lua");
434 for(auto i
: p
->luascripts
)
435 lsnes_cmd
.invoke("run-lua " + i
);
436 load_project_macros(controls
, *active_project
);
437 } catch(std::exception
& e
) {
438 messages
<< "Can't switch projects: " << e
.what() << std::endl
;
442 update_movie_state();
443 notify_core_change();
448 std::map
<std::string
, std::string
> project_enumerate()
450 std::set
<std::string
> projects
;
451 std::map
<std::string
, std::string
> projects2
;
453 projects
= enumerate_directory(get_config_path(), ".*\\.prj");
454 for(auto i
: projects
) {
458 split
= id
.find_last_of("\\/");
460 split
= id
.find_last_of("/");
462 if(split
< id
.length())
463 id
= id
.substr(split
+ 1);
464 id
= id
.substr(0, id
.length() - 4);
466 projects2
[id
] = project_getname(id
);
468 messages
<< "Failed to load name for ID '" << id
<< "'" << std::endl
;
474 std::string
project_moviepath()
477 return active_project
->directory
;
479 return lsnes_vset
["moviepath"].str();
482 std::string
project_otherpath()
485 return active_project
->directory
;
490 std::string
project_savestate_ext()
492 return active_project
? "lss" : "lsmv";
495 void project_copy_watches(project_info
& p
)
497 for(auto i
: get_watches())
498 p
.watches
[i
] = get_watchexpr_for(i
);
501 void project_copy_macros(project_info
& p
, controller_state
& s
)
503 for(auto i
: s
.enumerate_macro())
504 p
.macros
[i
] = s
.get_macro(i
).serialize();