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
20using namespace std;
21
22bool ConfigFile::defaultAutoAdd = false;
23bool ConfigFile::niceAlignment = false;
24bool ConfigFile::showComments = true;
25bool ConfigFile::alphaSort = true;
26bool ConfigFile::timeSort = false;
27static ConfigFile* curConfigFile = NULL; // for section_then_key_less
28
29ConfigFile::ConfigFile(void) {
30 Clear();
31}
32
33void ConfigFile::Clear(void){
34 data.clear();
35 sectionSizes.ClearSections();
36 linectr = 0;
37}
38
39bool 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
60void ConfigFile::LoadFile(Stream *r, const char *name){
61 curConfigFile = this;
62 string l, key, val;
63 string section;
64 string comment;
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
124bool 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
234string 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}
240bool 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)
245bool ConfigFile::Exists(const char *key){
246 const char* val = GetString(key, NULL);
247 return val && *val;
248}
249
250
251string ConfigFile::GetString(const char *key, string def){
252 if(!Exists(key))
253 return def;
254 return Get(key);
255}
256
257char *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
269const 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
283char *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
289bool ConfigFile::SetString(const char *key, string val, const char *comment){
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
314int32 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
329bool ConfigFile::SetInt(const char *key, int32 val, const char *comment){
330 char buf[20];
331 snprintf(buf, sizeof(buf), "%d", (int)val);
332 return SetString(key, buf, comment);
333}
334
335uint32 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
351bool ConfigFile::SetUInt(const char *key, uint32 val, int base, const char *comment){
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
368bool 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
380bool ConfigFile::SetBool(const char *key, bool val, const char *true_val, const char *false_val, const char *comment){
381 return SetString(key, val?true_val:false_val, comment);
382}
383
384const char* ConfigFile::GetComment(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
396bool 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
407bool 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
418ConfigFile::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
430int 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
435void ConfigFile::ClearUnused()
436{
437 set<ConfigEntry, ConfigEntry::key_less>::iterator i;
438again:
439 for(i=data.begin(); i!=data.end(); i++){
440 if(!i->used){
441 data.erase(i);
442 goto again;
443 }
444 }
445}
446
447void 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
455bool 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
467void ConfigFile::SetDefaultAutoAdd(bool autoAdd)
468{
469 defaultAutoAdd = autoAdd;
470}
471void ConfigFile::SetNiceAlignment(bool align)
472{
473 niceAlignment = align;
474}
475void ConfigFile::SetShowComments(bool show)
476{
477 showComments = show;
478}
479void ConfigFile::SetAlphaSort(bool sort)
480{
481 alphaSort = sort;
482}
483void ConfigFile::SetTimeSort(bool sort)
484{
485 timeSort = sort;
486}
487