JSON-based controller descriptions
[lsnes.git] / src / core / misc.cpp
blobce0ea9af5156595e1c9de25e0867aee0814011c2
1 #include "lsnes.hpp"
3 #include "core/command.hpp"
4 #include "core/controller.hpp"
5 #include "core/memorymanip.hpp"
6 #include "core/misc.hpp"
7 #include "core/rom.hpp"
8 #include "core/rrdata.hpp"
9 #include "core/settings.hpp"
10 #include "core/window.hpp"
11 #include "library/sha256.hpp"
12 #include "library/string.hpp"
14 #include <sstream>
15 #include <iostream>
16 #include <iomanip>
17 #include <fstream>
18 #include <string>
19 #include <vector>
20 #include <algorithm>
21 #include <ctime>
22 #include <cstdlib>
23 #include <cstring>
24 #include <boost/filesystem.hpp>
26 #ifdef USE_LIBGCRYPT_SHA256
27 #include <gcrypt.h>
28 #endif
30 namespace
32 std::string rseed;
33 uint64_t rcounter = 0;
34 bool reached_main_flag;
36 std::string get_random_hexstring_64(size_t index)
38 std::ostringstream str;
39 str << rseed << " " << time(NULL) << " " << (rcounter++) << " " << index;
40 std::string s = str.str();
41 std::vector<char> x;
42 x.resize(s.length());
43 std::copy(s.begin(), s.end(), x.begin());
44 return sha256::hash(reinterpret_cast<uint8_t*>(&x[0]), x.size());
47 std::string collect_identifying_information()
49 //TODO: Collect as much identifying information as possible.
50 std::ostringstream str;
51 time_t told = time(NULL);
52 time_t tnew;
53 uint64_t loops = 0;
54 uint64_t base = 0;
55 int cnt = 0;
56 while(cnt < 3) {
57 tnew = time(NULL);
58 if(tnew > told) {
59 told = tnew;
60 cnt++;
61 str << (loops - base) << " ";
62 base = loops;
64 loops++;
66 return str.str();
69 char endian_char(int e)
71 if(e < 0)
72 return 'L';
73 if(e > 0)
74 return 'B';
75 return 'N';
78 //% is intentionally missing.
79 const char* allowed_filename_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
80 "^&'@{}[],$?!-#().+~_";
81 const char* hexes = "0123456789ABCDEF";
84 std::string safe_filename(const std::string& str)
86 std::ostringstream o;
87 for(size_t i = 0; i < str.length(); i++) {
88 unsigned char ch = static_cast<unsigned char>(str[i]);
89 if(strchr(allowed_filename_chars, ch))
90 o << str[i];
91 else
92 o << "%" << hexes[ch / 16] << hexes[ch % 16];
94 return o.str();
97 std::string get_random_hexstring(size_t length) throw(std::bad_alloc)
99 std::string out;
100 for(size_t i = 0; i < length; i += 64)
101 out = out + get_random_hexstring_64(i);
102 return out.substr(0, length);
105 void set_random_seed(const std::string& seed) throw(std::bad_alloc)
107 std::ostringstream str;
108 str << seed.length() << " " << seed;
109 rseed = str.str();
110 rrdata.set_internal(random_rrdata());
113 void set_random_seed() throw(std::bad_alloc)
115 //Try /dev/urandom first.
117 std::ifstream r("/dev/urandom", std::ios::binary);
118 if(r.is_open()) {
119 char buf[64];
120 r.read(buf, 64);
121 std::string s(buf, 64);
122 set_random_seed(s);
123 return;
126 //If libgcrypt is available, use that.
127 #ifdef USE_LIBGCRYPT_SHA256
129 char buf[64];
130 gcry_randomize((unsigned char*)buf, sizeof(buf), GCRY_STRONG_RANDOM);
131 std::string s(buf, sizeof(buf));
132 set_random_seed(s);
133 return;
135 #endif
136 //Fall back to time.
137 std::ostringstream str;
138 str << collect_identifying_information() << " " << time(NULL);
139 set_random_seed(str.str());
143 struct loaded_rom load_rom_from_commandline(std::vector<std::string> cmdline) throw(std::bad_alloc,
144 std::runtime_error)
146 std::string f;
147 regex_results optp;
148 for(auto i : cmdline) {
149 if(!(optp = regex("--rom=(.+)", i)))
150 continue;
151 f = optp[1];
154 struct loaded_rom r;
155 try {
156 r = loaded_rom(f);
157 } catch(std::bad_alloc& e) {
158 OOM_panic();
159 } catch(std::exception& e) {
160 throw std::runtime_error(std::string("Can't load ROM: ") + e.what());
163 std::string not_present = "N/A";
164 for(size_t i = 0; i < ROM_SLOT_COUNT; i++) {
165 std::string romname = "UNKNOWN ROM";
166 std::string xmlname = "UNKNOWN XML";
167 if(i < r.rtype->get_image_count()) {
168 romname = (r.rtype->get_image_info(i).hname == "ROM") ? std::string("ROM") :
169 (r.rtype->get_image_info(i).hname + " ROM");
170 xmlname = r.rtype->get_image_info(i).hname + " XML";
172 if(r.romimg[i].data) messages << romname << " hash: " << r.romimg[i].sha_256.read() << std::endl;
173 if(r.romxml[i].data) messages << xmlname << " hash: " << r.romxml[i].sha_256.read() << std::endl;
175 return r;
178 void dump_region_map() throw(std::bad_alloc)
180 std::list<struct memory_region*> regions = lsnes_memory.get_regions();
181 for(auto i : regions) {
182 std::ostringstream x;
183 x << std::setfill('0') << std::setw(16) << std::hex << i->base << "-";
184 x << std::setfill('0') << std::setw(16) << std::hex << i->last_address() << " ";
185 x << std::setfill('0') << std::setw(16) << std::hex << i->size << " ";
186 messages << x.str() << (i->readonly ? "R-" : "RW") << endian_char(i->endian)
187 << (i->special ? 'I' : 'M') << " " << i->name << std::endl;
191 void fatal_error() throw()
193 platform::fatal_error();
194 std::cout << "PANIC: Fatal error, can't continue." << std::endl;
195 exit(1);
198 std::string get_config_path() throw(std::bad_alloc)
200 const char* tmp;
201 std::string basedir;
202 if((tmp = getenv("APPDATA"))) {
203 //If $APPDATA exists, it is the base directory
204 basedir = tmp;
205 } else if((tmp = getenv("XDG_CONFIG_HOME"))) {
206 //If $XDG_CONFIG_HOME exists, it is the base directory
207 basedir = tmp;
208 } else if((tmp = getenv("HOME"))) {
209 //If $HOME exists, the base directory is '.config' there.
210 basedir = std::string(tmp) + "/.config";
211 } else {
212 //Last chance: Return current directory.
213 return ".";
215 //Try to create 'lsnes'. If it exists (or is created) and is directory, great. Otherwise error out.
216 std::string lsnes_path = basedir + "/lsnes";
217 boost::filesystem::path p(lsnes_path);
218 if(!boost::filesystem::create_directories(p) && !boost::filesystem::is_directory(p)) {
219 messages << "FATAL: Can't create configuration directory '" << lsnes_path << "'" << std::endl;
220 fatal_error();
222 //Yes, this is racy, but portability is more important than being absolutely correct...
223 std::string tfile = lsnes_path + "/test";
224 remove(tfile.c_str());
225 FILE* x;
226 if(!(x = fopen(tfile.c_str(), "w+"))) {
227 messages << "FATAL: Configuration directory '" << lsnes_path << "' is not writable" << std::endl;
228 fatal_error();
230 fclose(x);
231 remove(tfile.c_str());
232 return lsnes_path;
235 void OOM_panic()
237 messages << "FATAL: Out of memory!" << std::endl;
238 fatal_error();
241 std::ostream& messages_relay_class::getstream() { return platform::out(); }
242 messages_relay_class messages;
244 uint32_t gcd(uint32_t a, uint32_t b) throw()
246 if(b == 0)
247 return a;
248 else
249 return gcd(b, a % b);
252 std::string format_address(void* addr)
254 unsigned long x = (unsigned long)addr;
255 std::ostringstream y;
256 y << "0x" << std::hex << std::setfill('0') << std::setw(2 * sizeof(unsigned long)) << x;
257 return y.str();
260 bool in_global_ctors()
262 return !reached_main_flag;
265 void reached_main()
267 new_core_flag = false; //We'll process the static cores anyway.
268 reached_main_flag = true;
269 lsnes_cmd.set_oom_panic(OOM_panic);
270 lsnes_cmd.set_output(platform::out());
273 std::string mangle_name(const std::string& orig)
275 std::ostringstream out;
276 for(auto i : orig) {
277 if(i == '(')
278 out << "[";
279 else if(i == ')')
280 out << "]";
281 else if(i == '|')
282 out << "\xE2\x8F\xBF";
283 else if(i == '/')
284 out << "\xE2\x8B\xBF";
285 else
286 out << i;
288 return out.str();
293 function_ptr_command<const std::string&> macro_test(lsnes_cmd, "test-macro", "", "",
294 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
295 regex_results r = regex("([0-9]+)[ \t](.*)", args);
296 if(!r) {
297 messages << "Bad syntax" << std::endl;
298 return;
300 unsigned ctrl = parse_value<unsigned>(r[1]);
301 auto pcid = controls.lcid_to_pcid(ctrl);
302 if(pcid.first < 0) {
303 messages << "Bad controller" << std::endl;
304 return;
306 try {
307 const port_controller* _ctrl = controls.get_blank().porttypes().port_type(pcid.first).
308 controller_info->get(pcid.second);
309 if(!_ctrl) {
310 messages << "No controller data for controller" << std::endl;
311 return;
313 controller_macro_data mdata(r[2].c_str(), controller_macro_data::make_descriptor(*_ctrl), 0);
314 messages << "Macro: " << mdata.dump(*_ctrl) << std::endl;
315 } catch(std::exception& e) {
316 messages << "Exception: " << e.what() << std::endl;