Supermodel/Src/INIFile.cpp

700 lines
18 KiB
C++
Raw Normal View History

2011-04-24 01:14:00 +00:00
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski, Nik Henson
2011-04-24 01:14:00 +00:00
**
** This file is part of Supermodel.
**
** Supermodel is free software: you can redistribute it and/or modify it under
** the terms of the GNU General Public License as published by the Free
** Software Foundation, either version 3 of the License, or (at your option)
** any later version.
**
** Supermodel is distributed in the hope that it will be useful, but WITHOUT
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
** more details.
**
** You should have received a copy of the GNU General Public License along
** with Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* INIFile.cpp
*
* INI file management. Implementation of the CINIFile class.
*
* To-Do List
* ----------
* - Add an iterator to retrieve all settings associated with a given section.
* This will allow detection of invalid setting names, if the caller desires.
2011-04-24 01:14:00 +00:00
* - Add boolean on/off, true/false keywords.
* - Note that linePtr does not necessarily correspond to actual lines in the
* file (newlines should be counted by the tokenizer for that).
*
* Grammar
* -------
*
* Section: '[' Identifier ']'
* Line: Identifier '=' Argument
* Argument: Number
* String
*
* Overview
* --------
* INI files are linear in nature, divided into sections. Each section has
* settings associated with it which may be either numerical values or strings.
* Presently, only unsigned 32-bit integers are supported.
*
* INI files are first opened, then parsed, and finally closed. The parse tree
* is cleared only during object construction and when the INI file is closed.
* Default settings may be inserted before a file is opened or parsed. When an
* INI file is written out, the current INI file on the disk is cleared. If an
* error occurs during this process, the data will be lost.
*
* Sections are added only when a setting is found in that section. Empty
* sections are not added to the tree with the exception of the default ("")
* section. A default section name can be specified, which creates an alias
* for the default section. The alias will be output when the file is
* written (there will be no settings without an explicit section).
2011-04-24 01:14:00 +00:00
*/
#include "Supermodel.h"
using namespace std;
/******************************************************************************
Basic Functions
******************************************************************************/
bool CINIFile::Write(const char *comment)
2011-04-24 01:14:00 +00:00
{
bool writeSuccess;
2011-04-24 01:14:00 +00:00
// In order to truncate, we must close and reopen as truncated
File.close();
File.clear(); // required to clear EOF flag (open() does not clear)
File.open(FileName.c_str(),fstream::out|fstream::trunc);
if (File.fail())
{
return FAIL;
}
// Output comment
if (comment != NULL)
File << comment << endl;
// Iterate through all sections sequentially
for (unsigned i = 0; i < Sections.size(); i++)
{
if (Sections[i].Settings.size() != 0)
2011-04-24 01:14:00 +00:00
{
// Output section name
if (Sections[i].Name != "")
File << "[ " << Sections[i].Name << " ]" << endl << endl;
else // if null name, use default section name (if exists) or nothing at all
{
if (DefaultSectionName != "")
File << "[ " << DefaultSectionName << " ]" << endl << endl;
}
// Iterate through all settings within this section
for (unsigned j = 0; j < Sections[i].Settings.size(); j++)
{
// Output setting
File << Sections[i].Settings[j].Name << " = ";
if (Sections[i].Settings[j].isNumber)
File << Sections[i].Settings[j].value << endl;
else
File << '\"' << Sections[i].Settings[j].String << '\"' << endl;
}
2011-04-24 01:14:00 +00:00
// New line
File << endl;
}
2011-04-24 01:14:00 +00:00
}
writeSuccess = File.good()?OKAY:FAIL;
// Close and reopen as read/write
File.close();
File.open(FileName.c_str(),fstream::in|fstream::out);
if (File.fail())
{
//printf("unable to re-open %s for reading/writing\n", FileName.c_str());
return FAIL;
}
// Report any errors that occurred during writing
return writeSuccess;
}
bool CINIFile::Open(const char *fileNameStr)
2011-04-24 01:14:00 +00:00
{
FileName = fileNameStr;
// Try to open for reading AND writing
2011-04-24 01:14:00 +00:00
File.open(fileNameStr, fstream::in|fstream::out);
if (File.fail())
return FAIL;
InitParseTree();
return OKAY;
}
bool CINIFile::OpenAndCreate(const char *fileNameStr)
{
FileName = fileNameStr;
// Try to open for reading and writing
File.open(fileNameStr, fstream::in|fstream::out);
if (File.fail())
{
// File does not exist, try opening as write only (create it)
File.clear();
File.open(fileNameStr, fstream::out);
if (File.fail())
return FAIL;
2011-04-24 01:14:00 +00:00
}
InitParseTree();
2011-04-24 01:14:00 +00:00
return OKAY;
}
void CINIFile::Close(void)
{
File.close();
Sections.clear(); // clear the parse tree!
}
/******************************************************************************
Management of Settings
******************************************************************************/
// Finds index of first matching section in the section list. Returns FAIL if not found.
bool CINIFile::LookUpSection(unsigned *idx, string SectionName)
2011-04-24 01:14:00 +00:00
{
for (unsigned i = 0; i < Sections.size(); i++)
{
if ((Sections[i].Name == SectionName) ||
((Sections[i].Name == "") && (SectionName == DefaultSectionName)) || // if default section, also accept its alias
((Sections[i].Name == DefaultSectionName) && (SectionName == ""))) // ...
2011-04-24 01:14:00 +00:00
{
*idx = i;
return OKAY;
}
}
return FAIL;
}
// Assigns a value to the given setting, creating the setting if it does not exist. Nulls out the string (sets it to "").
void CINIFile::Set(string SectionName, string SettingName, int value)
2011-04-24 01:14:00 +00:00
{
struct Setting NewSetting;
unsigned sectionIdx, settingIdx;
2011-04-24 01:14:00 +00:00
// Check if the section exists anywhere in parse tree. If not, create it
2011-04-24 01:14:00 +00:00
if (OKAY != LookUpSection(&sectionIdx, SectionName))
{
//printf("unable to find %s:%s, creating section\n", SectionName.c_str(), SettingName.c_str());
struct Section NewSection;
NewSection.Name = SectionName;
Sections.push_back(NewSection);
sectionIdx = Sections.size()-1; // the new section will be at the last index
}
// Search through all sections with the requested name for the first occurance of the desired setting
for (unsigned i = 0; i < Sections.size(); i++)
2011-04-24 01:14:00 +00:00
{
if ((Sections[i].Name == SectionName) ||
((Sections[i].Name == "") && (SectionName == DefaultSectionName)) || // accept alias for default section
((Sections[i].Name == DefaultSectionName) && (SectionName == ""))) // ...
{
for (unsigned j = 0; j < Sections[i].Settings.size(); j++)
{
if (Sections[i].Settings[j].Name == SettingName)
{
// Found it! Update value of this setting
sectionIdx = i;
settingIdx = j;
goto UpdateIntValue;
}
}
}
2011-04-24 01:14:00 +00:00
}
// Couldn't find setting, create it in the first matching section found earlier
NewSetting.Name = SettingName;
Sections[sectionIdx].Settings.push_back(NewSetting);
settingIdx = Sections[sectionIdx].Settings.size()-1;
// Update the setting!
UpdateIntValue:
Sections[sectionIdx].Settings[settingIdx].isNumber = true;
2011-04-24 01:14:00 +00:00
Sections[sectionIdx].Settings[settingIdx].value = value;
Sections[sectionIdx].Settings[settingIdx].String = "";
}
// Assigns the string to the given setting, creating the setting if it does not exist. Zeros out the value.
void CINIFile::Set(string SectionName, string SettingName, string String)
{
struct Setting NewSetting;
unsigned sectionIdx, settingIdx;
2011-04-24 01:14:00 +00:00
// Check if the section exists anywhere in parse tree. If not, create it
2011-04-24 01:14:00 +00:00
if (OKAY != LookUpSection(&sectionIdx, SectionName))
{
//printf("unable to find %s:%s, creating section\n", SectionName.c_str(), SettingName.c_str());
struct Section NewSection;
NewSection.Name = SectionName;
Sections.push_back(NewSection);
sectionIdx = Sections.size()-1; // the new section will be at the last index
}
// Search through all sections with the requested name for the first occurance of the desired setting
for (unsigned i = 0; i < Sections.size(); i++)
2011-04-24 01:14:00 +00:00
{
if ((Sections[i].Name == SectionName) ||
((Sections[i].Name == "") && (SectionName == DefaultSectionName)) || // accept alias for default section
((Sections[i].Name == DefaultSectionName) && (SectionName == ""))) // ...
{
for (unsigned j = 0; j < Sections[i].Settings.size(); j++)
{
if (Sections[i].Settings[j].Name == SettingName)
{
// Found it! Update value of this setting
sectionIdx = i;
settingIdx = j;
goto UpdateString;
}
}
}
2011-04-24 01:14:00 +00:00
}
// Couldn't find setting, create it in the first matching section found earlier
NewSetting.Name = SettingName;
Sections[sectionIdx].Settings.push_back(NewSetting);
settingIdx = Sections[sectionIdx].Settings.size()-1;
// Update the setting!
UpdateString:
Sections[sectionIdx].Settings[settingIdx].isNumber = false;
2011-04-24 01:14:00 +00:00
Sections[sectionIdx].Settings[settingIdx].String = String;
Sections[sectionIdx].Settings[settingIdx].value = 0;
}
// Obtains a numerical setting, if it exists, otherwise does nothing.
bool CINIFile::Get(string SectionName, string SettingName, int& value)
2011-04-24 01:14:00 +00:00
{
for (unsigned i = 0; i < Sections.size(); i++)
{
if ((Sections[i].Name == SectionName) ||
((Sections[i].Name == "") && (SectionName == DefaultSectionName)) || // accept alias for default section
((Sections[i].Name == DefaultSectionName) && (SectionName == ""))) // ...
{
for (unsigned j = 0; j < Sections[i].Settings.size(); j++)
{
if (Sections[i].Settings[j].Name == SettingName)
{
value = Sections[i].Settings[j].value;
return OKAY;
}
}
}
}
return FAIL;
2011-04-24 01:14:00 +00:00
}
bool CINIFile::Get(string SectionName, string SettingName, unsigned& value)
{
int intVal;
if (Get(SectionName, SettingName, intVal) == FAIL || intVal < 0)
return FAIL;
value = (unsigned)intVal;
return OKAY;
}
Committing various small updates that have been hanging around in my source tree for a while now: - Added 'crosshairs' command line and config option. - Added 'vsync' command line and config option (so far only tested on NVidia cards on Windows 7 - other graphics drivers, O/Ss or driver settings may simply chose to ignore this). - Added fullscreen toggle within game using Alt+Enter key combination. - Added framework for lamp outputs and 'outputs' command line and config option. So far only the lamps for driving games are hooked up in the emulator (others to be added later). - Added an initial outputs implementation for Windows that sends MAMEHooker compatible messages (-outputs=win to enable) - Fixed fps calculation in Main.cpp that was producing incorrect results and so giving the impression that frame throttling wasn't working properly when in fact it was. - Fixed palette indexed colours as the index was always off by one, causing incorrect colours in various games, eg drivers' suits and flashing Start sign in Daytona 2. - Altered caching of models so that models with palette indexed colours use the dynamic cache rather than the static one. This is so that changes in palette indexed colours appear on screen, eg the flashing Start sign on the advanced course of Daytona 2 (although currently the START message itself is not visible due to other problems with texture decoding). - Fixed small bug in TileGen.cpp which meant both palettes were being completely recomputed pretty much with every frame. This was a significant performance hit, particularly as palette recomputation is currently being done in SyncSnapshots (it should be moved out of here at some point, although for now it's no big deal). - Made sure all OpenGL objects and resources are deleted in Render2D/3D destructors, in particular the deleting of the VBO buffer in DestroyModelCache. - Made sure that GLSL uniforms are always checked to see if they are bound before using them in order to stop unecessary (but harmless) GL errors. - Altered the default texture sheet handling to use a single large GL texture holding multiple Model3 texture sheets rather than multiple GL textures as before (if required, the old behaviour can still be selected with the mulisheet fragment shader). I believe this fixes the disappearing crosshairs/corrupt GL state problem which the multisheet fragment shader seemed to be triggering somehow. - Fixed a bug in debugger which meant memory watches were not triggering properly
2012-07-15 21:04:46 +00:00
bool CINIFile::Get(string SectionName, string SettingName, bool& value)
{
int intVal;
if (Get(SectionName, SettingName, intVal) == FAIL)
return FAIL;
value = (bool)intVal;
return OKAY;
}
2011-04-24 01:14:00 +00:00
// Obtains a string setting, if it exists, otherwise does nothing.
bool CINIFile::Get(string SectionName, string SettingName, string& String)
2011-04-24 01:14:00 +00:00
{
for (unsigned i = 0; i < Sections.size(); i++)
{
if ((Sections[i].Name == SectionName) ||
((Sections[i].Name == "") && (SectionName == DefaultSectionName)) || // accept alias for default section
((Sections[i].Name == DefaultSectionName) && (SectionName == ""))) // ...
{
for (unsigned j = 0; j < Sections[i].Settings.size(); j++)
{
if (Sections[i].Settings[j].Name == SettingName)
{
String = Sections[i].Settings[j].String;
return OKAY;
}
}
}
}
return FAIL;
}
void CINIFile::SetDefaultSectionName(string SectionName)
{
DefaultSectionName = SectionName;
2011-04-24 01:14:00 +00:00
}
/******************************************************************************
Tokenizer
Never need to check for newlines because it is assumed these have been
stripped by getline().
******************************************************************************/
// Token types
#define TOKEN_INVALID -1
#define TOKEN_NULL 0
#define TOKEN_IDENTIFIER 1
#define TOKEN_NUMBER 2
#define TOKEN_STRING 3
// Token constructor (initializes token to null)
CINIFile::CToken::CToken(void)
{
type = TOKEN_NULL;
}
// Returns true for white space, comment symbol, or null terminator.
static bool IsBlank(char c)
2011-04-24 01:14:00 +00:00
{
if (isspace(c) || (c==';') || (c=='\0'))
return true;
return false;
2011-04-24 01:14:00 +00:00
}
// Fetches a string. Tolerates all characters between quotes, except \n.
2011-04-24 01:14:00 +00:00
CINIFile::CToken CINIFile::GetString(void)
{
CToken T;
T.type = TOKEN_STRING;
// Search for next quote
++linePtr;
while (1)
{
if (linePtr[0] == '\"')
{
++linePtr; // so we can find next token
break;
}
else if ((linePtr[0] == '\0') || (linePtr[0] == '\n'))
2011-04-24 01:14:00 +00:00
{
//printf("tokenizer: warning: string is missing end quote\n");
break;
}
else
T.String += linePtr[0];
++linePtr;
}
return T;
}
// Fetch number (decimal or hexadecimal positive/negative integer).
// linePtr must point to a character and therefore linePtr[1] is guaranteed to be within bounds.
2011-04-24 01:14:00 +00:00
CINIFile::CToken CINIFile::GetNumber(void)
{
CToken T;
unsigned long long number = 0;
bool isNeg = false;
2011-04-24 01:14:00 +00:00
int overflow = 0;
T.type = TOKEN_NUMBER;
// See if begins with minus sign
if (linePtr[0]=='-')
{
isNeg = true;
linePtr++;
}
2011-04-24 01:14:00 +00:00
// Hexadecimal?
if ((linePtr[0]=='0') && ((linePtr[1]=='X') || (linePtr[1]=='x')))
{
linePtr += 2; // advance to digits
// Ensure that at we have at least one digit
if (!isxdigit(linePtr[0]))
{
//printf("tokenizer: invalid hexadecimal number\n");
T.type = TOKEN_INVALID;
return T;
}
// Read number digit by digit
while (1)
{
if (isxdigit(linePtr[0]))
{
number <<= 4;
if (isdigit(linePtr[0]))
number |= (linePtr[0]-'0');
else if (isupper(linePtr[0]))
number |= (linePtr[0]-'A');
else // must be lowercase...
number |= (linePtr[0]-'a');
++linePtr;
// Check for overflows
if (!isNeg && number > 0x000000007FFFFFFFULL || isNeg && number > 0x0000000080000000ULL)
2011-04-24 01:14:00 +00:00
overflow = 1;
}
else if (IsBlank(linePtr[0]))
break;
else
{
//printf("tokenizer: invalid hexadecimal number\n");
T.type = TOKEN_INVALID;
return T;
}
}
}
// Decimal?
else
{
// Read number digit by digit
while (1)
{
if (isdigit(linePtr[0]))
{
number *= 10;
number += (linePtr[0]-'0');
++linePtr;
// Check for overflows
if (!isNeg && number > 0x000000007FFFFFFFULL || isNeg && number > 0x0000000080000000ULL)
2011-04-24 01:14:00 +00:00
overflow = 1;
}
else if (IsBlank(linePtr[0]))
break;
else
{
//printf("tokenizer: invalid number\n");
T.type = TOKEN_INVALID;
return T;
}
}
}
//if (overflow)
// printf("tokenizer: number exceeds 32 bits and has been truncated\n");
T.number = (isNeg ? -(int)number : (int)number);
2011-04-24 01:14:00 +00:00
return T;
}
// Fetch identifier
CINIFile::CToken CINIFile::GetIdentifier(void)
{
CToken T;
T.type = TOKEN_IDENTIFIER;
while (1)
{
if (isalpha(linePtr[0]) || isdigit(linePtr[0]) || (linePtr[0]=='_'))
{
T.String += linePtr[0];
++linePtr;
}
else
break;
}
return T;
}
// Fetch token
CINIFile::CToken CINIFile::GetToken(void)
{
CToken T;
while (1)
{
// Gobble up whitespace
if (isspace(linePtr[0]))
++linePtr;
// Comment or end of line
else if ((linePtr[0]==';') || (linePtr[0]=='\0'))
{
T.type = TOKEN_NULL;
return T; // do not advance linePtr (so we do not de-sync the parser)
2011-04-24 01:14:00 +00:00
}
// Delimiters
else if ((linePtr[0]=='[') || (linePtr[0]==']') || (linePtr[0]=='='))
{
T.type = *linePtr++;
return T;
}
// Identifier?
else if (isalpha(linePtr[0]) || (linePtr[0] == '_'))
{
T = GetIdentifier();
return T;
}
// Number? (+/-?)
else if (linePtr[0]=='-' || isdigit(linePtr[0]))
2011-04-24 01:14:00 +00:00
{
T = GetNumber();
return T;
}
// String?
else if (linePtr[0]=='\"')
{
T = GetString();
return T;
}
// Illegal symbol
else
break; // return null token
}
// If we got here, invalid token
T.type = TOKEN_INVALID;
return T;
}
/******************************************************************************
Parser
******************************************************************************/
/*
* Parse tree is initialized with a blank section in case the user adds
* settings with defined sections before adding settings without an explicit
* section. If this is not done, settings without a section will be wrongly
* output as part of the previous section in the parse tree.
*/
void CINIFile::InitParseTree(void)
{
struct Section FirstSection;
FirstSection.Name = "";
Sections.clear();
Sections.push_back(FirstSection);
}
bool CINIFile::Parse(void)
2011-04-24 01:14:00 +00:00
{
CToken T, U, V, W;
string currentSection; // current section we're processing
bool parseStatus = OKAY;
2011-04-24 01:14:00 +00:00
lineNum = 0;
if (!File.is_open())
return FAIL;
File.clear();
if (!File.good())
return FAIL;
while (!File.eof())
{
++lineNum;
File.getline(lineBuf,2048);
if (File.fail())
return FAIL;
linePtr = lineBuf; // beginning of line
// Top level
T = GetToken();
U = GetToken();
V = GetToken();
W = GetToken(); // should always be null
switch (T.type)
{
// [ Identifier ]
case '[':
if ((U.type==TOKEN_IDENTIFIER) && (V.type==']') && (W.type==TOKEN_NULL))
{
//printf("Section: %s\n", U.String.c_str());
currentSection = U.String;
}
else
{
parseStatus = FAIL;
//printf("%d: parse error\n", lineNum);
}
break;
// Identifier '=' Argument
case TOKEN_IDENTIFIER:
if (U.type != '=')
{
parseStatus = FAIL;
//printf("%d: expected '=' after identifier\n", lineNum);
}
else
{
if (((V.type==TOKEN_NUMBER) || (V.type==TOKEN_STRING)) && (W.type==TOKEN_NULL))
{
if (V.type == TOKEN_NUMBER)
{
//printf("\t%s = %X\n", T.String.c_str(), V.number);
Set(currentSection, T.String, V.number);
}
else if (V.type == TOKEN_STRING)
{
//printf("\t%s = %s\n", T.String.c_str(), V.String.c_str());
Set(currentSection, T.String, V.String);
}
}
else
{
parseStatus = FAIL;
//printf("%d: expected a number or string after '='\n", lineNum);
}
}
break;
// Blank line
case TOKEN_NULL:
break;
// Illegal
default:
parseStatus = FAIL;
//printf("%d: parse error\n", lineNum);
break;
}
}
//printf("end of file reached\n");
return parseStatus;
}