1 | /*****************************************************************************\ |
2 | Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. |
3 | This file is licensed under the Snes9x License. |
4 | For further information, consult the LICENSE file in the root directory. |
5 | \*****************************************************************************/ |
6 | |
7 | #include <stdio.h> |
8 | #include <time.h> |
9 | #include <string.h> |
10 | #include <string> |
11 | |
12 | #include "conffile.h" |
13 | |
14 | #ifdef __WIN32__ |
15 | #define snprintf _snprintf // needs ANSI compliant name |
16 | #endif |
17 | |
18 | #define SORT_SECTIONS_BY_SIZE // output |
19 | |
20 | using namespace std; |
21 | |
22 | bool ConfigFile::defaultAutoAdd = false; |
23 | bool ConfigFile::niceAlignment = false; |
24 | bool ConfigFile:: = true; |
25 | bool ConfigFile::alphaSort = true; |
26 | bool ConfigFile::timeSort = false; |
27 | static ConfigFile* curConfigFile = NULL; // for section_then_key_less |
28 | |
29 | ConfigFile::ConfigFile(void) { |
30 | Clear(); |
31 | } |
32 | |
33 | void ConfigFile::Clear(void){ |
34 | data.clear(); |
35 | sectionSizes.ClearSections(); |
36 | linectr = 0; |
37 | } |
38 | |
39 | bool ConfigFile::LoadFile(const char *filename){ |
40 | FSTREAM s; |
41 | bool ret=false; |
42 | const char *n, *n2; |
43 | |
44 | if((s=OPEN_FSTREAM(filename, "r" ))){ |
45 | n=filename; |
46 | n2=strrchr(n, '/'); if(n2!=NULL) n=n2+1; |
47 | n2=strrchr(n, '\\'); if(n2!=NULL) n=n2+1; |
48 | fStream fS(s); |
49 | LoadFile(&fS, n); |
50 | CLOSE_FSTREAM(s); |
51 | ret = true; |
52 | } else { |
53 | fprintf(stderr, "Couldn't open conffile " ); |
54 | perror(filename); |
55 | } |
56 | return ret; |
57 | } |
58 | |
59 | |
60 | void ConfigFile::LoadFile(Stream *r, const char *name){ |
61 | curConfigFile = this; |
62 | string l, key, val; |
63 | string section; |
64 | string ; |
65 | int i, line, line2; |
66 | bool eof; |
67 | |
68 | line=line2=0; |
69 | section.clear(); |
70 | do { |
71 | line=line2++; |
72 | l=r->getline(eof); |
73 | ConfigEntry::trim(l); |
74 | if(l.size()==0) continue; |
75 | |
76 | if(l[0]=='#' || l[0]==';'){ |
77 | // comment |
78 | continue; |
79 | } |
80 | |
81 | if(l[0]=='['){ |
82 | if(*l.rbegin()!=']'){ |
83 | if(name) fprintf(stderr, "%s:" , name); |
84 | fprintf(stderr, "[%d]: Ignoring invalid section header\n" , line); |
85 | continue; |
86 | } |
87 | section.assign(l, 1, l.size()-2); |
88 | continue; |
89 | } |
90 | |
91 | while(*l.rbegin()=='\\'){ |
92 | l.erase(l.size()-1); |
93 | line2++; |
94 | val=r->getline(eof); |
95 | if(eof){ |
96 | fprintf(stderr, "Unexpected EOF reading config file" ); |
97 | if(name) fprintf(stderr, " '%s'" , name); |
98 | fprintf(stderr, "\n" ); |
99 | return; |
100 | } |
101 | ConfigEntry::trim(val); |
102 | l+=val; |
103 | } |
104 | i=l.find('='); |
105 | if(i<0){ |
106 | if(name) fprintf(stderr, "%s:" , name); |
107 | fprintf(stderr, "[%d]: Ignoring invalid entry\n" , line); |
108 | continue; |
109 | } |
110 | key=l.substr(0,i); ConfigEntry::trim(key); |
111 | val=l.substr(i+1); comment = ConfigEntry::trimCommented(val); |
112 | if(val.size() > 0 && val[0]=='"' && *val.rbegin()=='"') val=val.substr(1, val.size()-2); |
113 | |
114 | ConfigEntry e(line, section, key, val); |
115 | e.comment = comment; |
116 | if(data.erase(e)) |
117 | sectionSizes.DecreaseSectionSize(e.section); |
118 | data.insert(e); |
119 | sectionSizes.IncreaseSectionSize(e.section); |
120 | } while(!eof); |
121 | curConfigFile = NULL; |
122 | } |
123 | |
124 | bool ConfigFile::SaveTo(const char *filename){ |
125 | string section; |
126 | FILE *fp; |
127 | |
128 | if((fp=fopen(filename, "w" ))==NULL){ |
129 | fprintf(stderr, "Couldn't write conffile " ); |
130 | perror(filename); |
131 | return false; |
132 | } |
133 | |
134 | curConfigFile = this; |
135 | section.clear(); |
136 | set<ConfigEntry, ConfigEntry::line_less> tmp; |
137 | fprintf(fp, "# Config file output by snes9x\n" ); |
138 | time_t t=time(NULL); |
139 | fprintf(fp, "# %s" , ctime(&t)); |
140 | |
141 | #ifdef SORT_SECTIONS_BY_SIZE |
142 | std::set<ConfigEntry, ConfigEntry::section_then_key_less> data2; |
143 | for(set<ConfigEntry, ConfigEntry::key_less>::iterator k=data.begin(); k!=data.end(); k++){ |
144 | ConfigEntry e (k->line, k->section, k->key, k->val); e.comment = k->comment; |
145 | data2.insert(e); |
146 | } |
147 | #else |
148 | #define data2 data |
149 | #define section_then_key_less key_less |
150 | #endif |
151 | |
152 | for(set<ConfigEntry, ConfigEntry::section_then_key_less>::iterator j=data2.begin(); ; j++){ |
153 | if(j==data2.end() || j->section!=section){ |
154 | if(!tmp.empty()){ |
155 | fprintf(fp, "\n[%s]\n" , section.c_str()); |
156 | unsigned int maxKeyLen=0, maxValLen=0; int maxLeftDiv=0; int maxRightDiv=-1; |
157 | if(niceAlignment){ |
158 | for(set<ConfigEntry, ConfigEntry::line_less>::iterator i=tmp.begin(); i!=tmp.end(); i++){ |
159 | int len3 = i->key.find_last_of(':'); |
160 | maxRightDiv = MAX(maxRightDiv, len3); |
161 | len3 = i->key.length() - len3; |
162 | maxLeftDiv = MAX(maxLeftDiv, len3); |
163 | maxKeyLen = MAX(maxKeyLen, i->key.length()+3); |
164 | if(showComments){ |
165 | string o=i->val; ConfigEntry::trim(o); |
166 | unsigned int len = o.length(); |
167 | for(signed int j=len-1;j>=0;j--) if(o.at(j)=='#') len++; |
168 | if(o!=i->val) len+=2; |
169 | maxValLen = MAX(maxValLen, len); |
170 | } |
171 | } |
172 | if(maxValLen>48) maxValLen=48; // limit spacing |
173 | } |
174 | |
175 | for(set<ConfigEntry, ConfigEntry::line_less>::iterator i=tmp.begin(); i!=tmp.end(); i++){ |
176 | string o=i->val; ConfigEntry::trim(o); |
177 | if(o!=i->val) o="\"" +i->val+"\"" ; |
178 | int off=0, len3=0; |
179 | for(;;){ |
180 | int k=o.find('#',off); |
181 | if(k>=0){ |
182 | o.insert(k,1,'#'); // re-double any comment characters |
183 | off=k+2; |
184 | if(off<(int)o.length()) continue; |
185 | } |
186 | break; |
187 | } |
188 | if(niceAlignment){ |
189 | len3=i->key.find_last_of(':'); |
190 | int len3sub=0; |
191 | if(len3 < maxRightDiv){ |
192 | for(int j=len3;j<maxRightDiv;j++) fputc(' ',fp); |
193 | len3sub=maxRightDiv-len3; |
194 | len3 = maxRightDiv; |
195 | } |
196 | len3+=maxLeftDiv-i->key.length(); |
197 | for(unsigned int j=i->key.length()+len3+3;j<maxKeyLen;j++) fputc(' ',fp); |
198 | fprintf(fp, "%s" , i->key.c_str()); |
199 | for(int j=0;j<len3-len3sub;j++) fputc(' ',fp); |
200 | fprintf(fp, " = %s" , o.c_str()); |
201 | } else |
202 | fprintf(fp, "%s = %s" , i->key.c_str(), o.c_str()); |
203 | |
204 | if(showComments && !i->comment.empty()){ |
205 | if(niceAlignment) for(unsigned int j=o.length();j<maxValLen;j++) fputc(' ',fp); |
206 | fprintf(fp, " # %s" , i->comment.c_str()); |
207 | } |
208 | fprintf(fp, "\n" ); |
209 | } |
210 | } |
211 | if(j==data2.end()) break; |
212 | section=j->section; |
213 | tmp.clear(); |
214 | } |
215 | tmp.insert(*j); |
216 | } |
217 | curConfigFile = NULL; |
218 | |
219 | #undef data2 |
220 | #undef section_then_key_less |
221 | |
222 | if(ferror(fp)) |
223 | { |
224 | printf ("Error writing config file %s\n" , filename); |
225 | } |
226 | |
227 | fclose(fp); |
228 | return true; |
229 | } |
230 | |
231 | |
232 | /***********************************************/ |
233 | |
234 | string ConfigFile::Get(const char *key){ |
235 | set<ConfigEntry, ConfigEntry::key_less>::iterator i; |
236 | i=data.find(ConfigEntry(key)); |
237 | i->used=true; |
238 | return i->val; |
239 | } |
240 | bool ConfigFile::Has(const char *key){ |
241 | return data.find(ConfigEntry(key))!=data.end(); |
242 | } |
243 | |
244 | // exists and isn't completely empty (any side-effects are intentional) |
245 | bool ConfigFile::Exists(const char *key){ |
246 | const char* val = GetString(key, NULL); |
247 | return val && *val; |
248 | } |
249 | |
250 | |
251 | string ConfigFile::GetString(const char *key, string def){ |
252 | if(!Exists(key)) |
253 | return def; |
254 | return Get(key); |
255 | } |
256 | |
257 | char *ConfigFile::GetString(const char *key, char *out, uint32 outlen){ |
258 | if(!Exists(key)) return NULL; |
259 | memset(out, 0, outlen); |
260 | string o=Get(key); |
261 | if(outlen>0){ |
262 | outlen--; |
263 | if(o.size()<outlen) outlen=o.size(); |
264 | memcpy(out, o.data(), outlen); |
265 | } |
266 | return out; |
267 | } |
268 | |
269 | const char *ConfigFile::GetString(const char *key, const char *def){ |
270 | set<ConfigEntry, ConfigEntry::key_less>::iterator i; |
271 | i=data.find(ConfigEntry(key)); |
272 | if(i==data.end()) |
273 | { |
274 | if(defaultAutoAdd) SetString(key,"" ); //SetString(key, def?def:""); |
275 | return def; |
276 | } |
277 | i->used=true; |
278 | // This should be OK, until this key gets removed |
279 | const std::string &iVal = i->val; |
280 | return iVal.c_str(); |
281 | } |
282 | |
283 | char *ConfigFile::GetStringDup(const char *key, const char *def){ |
284 | const char *c=GetString(key, def); |
285 | if(c==NULL) return NULL; |
286 | return strdup(c); |
287 | } |
288 | |
289 | bool ConfigFile::SetString(const char *key, string val, const char *){ |
290 | set<ConfigEntry, ConfigEntry::key_less>::iterator i; |
291 | bool ret=false; |
292 | bool found; |
293 | |
294 | ConfigEntry e(key, val); |
295 | if(comment && *comment) e.comment = comment; |
296 | e.used=true; |
297 | |
298 | i=data.find(e); |
299 | found=(i==data.end()); |
300 | if(!found){ |
301 | e.line=i->line; |
302 | data.erase(e); |
303 | sectionSizes.DecreaseSectionSize(e.section); |
304 | ret=true; |
305 | } |
306 | if((found && (!alphaSort || timeSort)) || (!alphaSort && timeSort)) |
307 | e.line = linectr++; |
308 | |
309 | data.insert(e); |
310 | sectionSizes.IncreaseSectionSize(e.section); |
311 | return ret; |
312 | } |
313 | |
314 | int32 ConfigFile::GetInt(const char *key, int32 def, bool *bad){ |
315 | if(bad) *bad=false; |
316 | if(!Exists(key)) |
317 | return def; |
318 | char *c; |
319 | int32 i; |
320 | string o=Get(key); |
321 | i=strtol(o.c_str(), &c, 10); |
322 | if(c!=NULL && *c!='\0'){ |
323 | i=def; |
324 | if(bad) *bad=true; |
325 | } |
326 | return i; |
327 | } |
328 | |
329 | bool ConfigFile::SetInt(const char *key, int32 val, const char *){ |
330 | char buf[20]; |
331 | snprintf(buf, sizeof(buf), "%d" , (int)val); |
332 | return SetString(key, buf, comment); |
333 | } |
334 | |
335 | uint32 ConfigFile::GetUInt(const char *key, uint32 def, int base, bool *bad){ |
336 | if(bad) *bad=false; |
337 | if(!Exists(key)) |
338 | return def; |
339 | if(base!=8 && base!=10 && base!=16) base=0; |
340 | char *c; |
341 | uint32 i; |
342 | string o=Get(key); |
343 | i=strtol(o.c_str(), &c, base); |
344 | if(c!=NULL && *c!='\0'){ |
345 | i=def; |
346 | if(bad) *bad=true; |
347 | } |
348 | return i; |
349 | } |
350 | |
351 | bool ConfigFile::SetUInt(const char *key, uint32 val, int base, const char *){ |
352 | char buf[20]; |
353 | switch(base){ |
354 | case 10: |
355 | default: |
356 | snprintf(buf, sizeof(buf), "%u" , (unsigned int)val); |
357 | break; |
358 | case 8: |
359 | snprintf(buf, sizeof(buf), "%#o" , (unsigned int)val); |
360 | break; |
361 | case 16: |
362 | snprintf(buf, sizeof(buf), "%#x" , (unsigned int)val); |
363 | break; |
364 | } |
365 | return SetString(key, buf, comment); |
366 | } |
367 | |
368 | bool ConfigFile::GetBool(const char *key, bool def, bool *bad){ |
369 | if(bad) *bad=false; |
370 | if(!Exists(key)) |
371 | return def; |
372 | string o=Get(key); |
373 | const char *c=o.c_str(); |
374 | if(!strcasecmp(c, "true" ) || !strcasecmp(c, "1" ) || !strcasecmp(c, "yes" ) || !strcasecmp(c, "on" )) return true; |
375 | if(!strcasecmp(c, "false" ) || !strcasecmp(c, "0" ) || !strcasecmp(c, "no" ) || !strcasecmp(c, "off" )) return false; |
376 | if(bad) *bad=true; |
377 | return def; |
378 | } |
379 | |
380 | bool ConfigFile::SetBool(const char *key, bool val, const char *true_val, const char *false_val, const char *){ |
381 | return SetString(key, val?true_val:false_val, comment); |
382 | } |
383 | |
384 | const char* ConfigFile::(const char *key) |
385 | { |
386 | set<ConfigEntry, ConfigEntry::key_less>::iterator i; |
387 | i=data.find(ConfigEntry(key)); |
388 | if(i==data.end()) |
389 | return NULL; |
390 | |
391 | // This should be OK, until this key gets removed |
392 | const std::string &iCom = i->comment; |
393 | return iCom.c_str(); |
394 | } |
395 | |
396 | bool ConfigFile::DeleteKey(const char *key){ |
397 | ConfigEntry e = ConfigEntry(key); |
398 | if(data.erase(e)) { |
399 | sectionSizes.DecreaseSectionSize(e.section); |
400 | return true; |
401 | } |
402 | return false; |
403 | } |
404 | |
405 | /***********************************************/ |
406 | |
407 | bool ConfigFile::DeleteSection(const char *section){ |
408 | set<ConfigEntry, ConfigEntry::key_less>::iterator s, e; |
409 | |
410 | for(s=data.begin(); s!=data.end() && s->section!=section; s++) ; |
411 | if(s==data.end()) return false; |
412 | for(e=s; e!=data.end() && e->section==section; e++) ; |
413 | data.erase(s, e); |
414 | sectionSizes.DeleteSection(section); |
415 | return true; |
416 | } |
417 | |
418 | ConfigFile::secvec_t ConfigFile::GetSection(const char *section){ |
419 | secvec_t v; |
420 | set<ConfigEntry, ConfigEntry::key_less>::iterator i; |
421 | |
422 | v.clear(); |
423 | for(i=data.begin(); i!=data.end(); i++){ |
424 | if(i->section!=section) continue; |
425 | v.push_back(std::pair<string,string>(i->key, i->val)); |
426 | } |
427 | return v; |
428 | } |
429 | |
430 | int ConfigFile::GetSectionSize(const std::string section){ |
431 | return sectionSizes.GetSectionSize(section); |
432 | } |
433 | |
434 | // Clears all key-value pairs that didn't receive a "Get" or "Exists" command |
435 | void ConfigFile::ClearUnused() |
436 | { |
437 | set<ConfigEntry, ConfigEntry::key_less>::iterator i; |
438 | again: |
439 | for(i=data.begin(); i!=data.end(); i++){ |
440 | if(!i->used){ |
441 | data.erase(i); |
442 | goto again; |
443 | } |
444 | } |
445 | } |
446 | |
447 | void ConfigFile::ClearLines() |
448 | { |
449 | set<ConfigEntry, ConfigEntry::key_less>::iterator i; |
450 | for(i=data.begin(); i!=data.end(); i++){ |
451 | *(const_cast<int*>(&i->line)) = -1; |
452 | } |
453 | } |
454 | |
455 | bool ConfigFile::ConfigEntry::section_then_key_less::operator()(const ConfigEntry &a, const ConfigEntry &b) { |
456 | if(curConfigFile && a.section!=b.section){ |
457 | const int sva = curConfigFile->GetSectionSize(a.section); |
458 | const int svb = curConfigFile->GetSectionSize(b.section); |
459 | if(sva<svb) return true; |
460 | if(sva>svb) return false; |
461 | return a.section<b.section; |
462 | } |
463 | return a.key<b.key; |
464 | } |
465 | |
466 | |
467 | void ConfigFile::SetDefaultAutoAdd(bool autoAdd) |
468 | { |
469 | defaultAutoAdd = autoAdd; |
470 | } |
471 | void ConfigFile::SetNiceAlignment(bool align) |
472 | { |
473 | niceAlignment = align; |
474 | } |
475 | void ConfigFile::(bool show) |
476 | { |
477 | showComments = show; |
478 | } |
479 | void ConfigFile::SetAlphaSort(bool sort) |
480 | { |
481 | alphaSort = sort; |
482 | } |
483 | void ConfigFile::SetTimeSort(bool sort) |
484 | { |
485 | timeSort = sort; |
486 | } |
487 | |