Files
source/Utils/INIReader.cpp
T
1d6ff6be3c let CIniReader take its strings as pointers to const
STR8 is char*, so const STR8 is char* const: a const pointer to a mutable
string, which is not what any of these sixty parameters wanted. What they
wanted is const CHAR8*, a pointer to a string they will not write to, and
that is what all of them do.

Under the MSVC compatibility rules clang-cl applies, a string literal still
converts to char*, so most calls survive this mistake untouched. An expression
of type const char* does not, and Intro.cpp has thirteen of them:

    inireader.ReadString("INTRO_BEGINNING", "INTRO_REBEL_CRDT",
                         no_defaults ? "" : "INTRO\\Rebel_cr");

The conditional is a const char*, not a literal, so it has nowhere to bind and
the call does not compile. The other two arguments are literals and are
accepted, which is why only the third one was ever reported.

The invariant: in these two files, every parameter spelled const STR8 becomes
const CHAR8*, and nothing else changes. No body assigns to one of them, so
none needed rewriting; STR8 input_buffer in the five-argument ReadString is
genuinely an output and keeps its type. Three commented-out lines still say
const STR8 and were left as they are.

Verification:

    grep -n 'const[ \t]*STR8' Utils/INIReader.h Utils/INIReader.cpp
        # three hits, all on lines beginning with //

    git show HEAD -U0 | grep '^[+-][^+-]' |
      sed -E 's/const[ \t]+(STR8|CHAR8\*)/const/g; s/^[+-]//' |
      sort | uniq -c | awk '$1 % 2'          # no output

    ninja -C build parse         # ReadString class gone: 45 -> 32 sites
    ninja -C build -k 0          # Release, four applications, green
    ninja -C build-debug -k 0    # Debug, four applications, green

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 19:29:55 -03:00

358 lines
12 KiB
C++

#include "builddefines.h"
#include "INIReader.h"
#include "FileMan.h"
#include "DEBUG.H"
#include "Font Control.h"
#include "message.h"
#include <stdio.h>
#include <string.h>
#include <sstream>
// Kaiden: INI reading function definitions:
#include <vfs/Core/vfs.h>
std::set<vfs::Path,vfs::Path::Less> CIniReader::m_merge_files;
std::stack<std::string> iniErrorMessages;
template<typename ValueType>
void PushErrorMessage(std::string const& filename,
std::string const& section,
std::string const& key,
ValueType value, ValueType used_value,
ValueType minVal, ValueType maxVal)
{
std::stringstream errMessage;
errMessage << "The value [" << section << "][" << key << "] = \"" << value << "\" "
<< "in file [" << filename << "] "
<< "is outside the valid range [" << minVal << " , " << maxVal << "]. "
<< used_value << " will be used.";
iniErrorMessages.push(errMessage.str());
}
void CIniReader::RegisterFileForMerging(vfs::Path const& filename)
{
m_merge_files.insert(filename);
}
CIniReader::CIniReader(const CHAR8* szFileName)
{
memset(m_szFileName,0,sizeof(m_szFileName));
strncpy(m_szFileName,szFileName, std::min<int>(strlen(szFileName), sizeof(m_szFileName)-1));
if(m_merge_files.find(szFileName) == m_merge_files.end())
{
m_oProps.initFromIniFile(vfs::Path(szFileName));
}
else
{
vfs::CProfileStack* profs = getVFS()->getProfileStack();
vfs::CProfileStack::Iterator it = profs->begin();
std::stack<vfs::CVirtualProfile*> rev_order;
for(; !it.end(); it.next()) { rev_order.push(it.value()); }
while(!rev_order.empty())
{
vfs::IBaseFile* file = rev_order.top()->getFile(szFileName);
if(file)
{
m_oProps.initFromIniFile(vfs::tReadableFile::cast(file));
}
rev_order.pop();
}
}
// check for override file
{
CHAR8 OvrFileName[256], Drive[128], Dir[128], Name[128], Ext[128];
_splitpath(szFileName, Drive, Dir, Name, Ext);
_makepath(OvrFileName, Drive, Dir, Name, "Override");
if(getVFS()->fileExists(OvrFileName))
m_oProps.initFromIniFile(vfs::Path(OvrFileName));
}
}
CIniReader::CIniReader(const CHAR8* szFileName, BOOLEAN Force_Custom_Data_Path)
{
memset(m_szFileName,0,sizeof(m_szFileName));
// ary-05/05/2009 : force custom data path for potential non existing file -or- force default data path
// : Also, flag file detection to allow functions to determine course of action for case of file [not found/is found].
strncpy(m_szFileName,szFileName, std::min<int>(strlen(szFileName), sizeof(m_szFileName)-1));
if(m_merge_files.find(szFileName) == m_merge_files.end())
{
CIniReader_File_Found = m_oProps.initFromIniFile(vfs::Path(szFileName));
}
else
{
CIniReader_File_Found = TRUE;
vfs::CProfileStack* profs = getVFS()->getProfileStack();
vfs::CProfileStack::Iterator it = profs->begin();
std::stack<vfs::CVirtualProfile*> rev_order;
for(; !it.end(); it.next()) { rev_order.push(it.value()); }
while(!rev_order.empty())
{
vfs::IBaseFile* file = rev_order.top()->getFile(szFileName);
if(file)
{
CIniReader_File_Found = ((CIniReader_File_Found != FALSE) && m_oProps.initFromIniFile(vfs::tReadableFile::cast(file))) ? TRUE : FALSE;
}
rev_order.pop();
}
}
}
void CIniReader::Clear()
{
memset(m_szFileName, 0, MAX_PATH);
m_oProps.clearContainer();
}
int CIniReader::ReadInteger(const CHAR8* szSection, const CHAR8* szKey, int iDefaultValue)
{
return (int)(m_oProps.getIntProperty(szSection, szKey, iDefaultValue));
}
int CIniReader::ReadInteger(const CHAR8* szSection, const CHAR8* szKey, int defaultValue, int minValue, int maxValue)
{
int iniValueReadFromFile = (int)(m_oProps.getIntProperty(szSection, szKey, defaultValue));
//AssertGE(iniValueReadFromFile, minValue);
//AssertLE(iniValueReadFromFile, maxValue);
if (iniValueReadFromFile < minValue)
{
PushErrorMessage(this->m_szFileName, szSection, szKey, iniValueReadFromFile, minValue, minValue, maxValue);
return minValue;
}
else if (iniValueReadFromFile > maxValue)
{
PushErrorMessage(this->m_szFileName, szSection, szKey, iniValueReadFromFile, maxValue, minValue, maxValue);
return maxValue;
}
return iniValueReadFromFile;
}
//float CIniReader::ReadDouble(const STR8 szSection, const STR8 szKey, float fltDefaultValue)
//{
// char szResult[255];
// char szDefault[255];
// float fltResult;
// sprintf(szDefault, "%f",fltDefaultValue);
// GetPrivateProfileString(szSection, szKey, szDefault, szResult, 255, m_szFileName);
// fltResult = (float) atof(szResult);
// return fltResult;
//}
double CIniReader::ReadDouble(const CHAR8* szSection, const CHAR8* szKey, double defaultValue, double minValue, double maxValue)
{
double iniValueReadFromFile;
iniValueReadFromFile = m_oProps.getFloatProperty(szSection, szKey, defaultValue);
//AssertGE(iniValueReadFromFile, minValue);
//AssertLE(iniValueReadFromFile, maxValue);
if (iniValueReadFromFile < minValue)
{
PushErrorMessage(this->m_szFileName, szSection, szKey,iniValueReadFromFile, minValue, minValue, maxValue);
return minValue;
}
else if (iniValueReadFromFile > maxValue)
{
PushErrorMessage(this->m_szFileName, szSection, szKey, iniValueReadFromFile, maxValue, minValue, maxValue);
return maxValue;
}
return iniValueReadFromFile;
}
FLOAT CIniReader::ReadFloat(const CHAR8* szSection, const CHAR8* szKey, FLOAT defaultValue, FLOAT minValue, FLOAT maxValue)
{
FLOAT iniValueReadFromFile;
iniValueReadFromFile = (FLOAT) m_oProps.getFloatProperty(szSection, szKey, (float)defaultValue);
//AssertGE(iniValueReadFromFile, minValue);
//AssertLE(iniValueReadFromFile, maxValue);
if (iniValueReadFromFile < minValue)
{
PushErrorMessage(this->m_szFileName, szSection, szKey, iniValueReadFromFile, minValue, minValue, maxValue);
return minValue;
}
else if (iniValueReadFromFile > maxValue)
{
PushErrorMessage(this->m_szFileName, szSection, szKey, iniValueReadFromFile, maxValue, minValue, maxValue);
return maxValue;
}
return iniValueReadFromFile;
}
void CIniReader::ReadFloatArray(const CHAR8* szSection, const CHAR8* szKey, std::vector<FLOAT>& vec)
{
// read the array as a string
STRING512 textBuffer;
ReadString(szSection, szKey, "", textBuffer, _countof(textBuffer));
std::string str, token;
std::string delim = ",";
size_t offset = 0, prevOffset = 0;
// sanitise input
vec.clear();
str = textBuffer;
str.erase(std::remove(str.begin(), str.end(), ' '), str.end());
// split into array
try
{
do
{
offset = str.find(delim, prevOffset);
token = str.substr(prevOffset, offset - prevOffset);
prevOffset = offset + delim.length();
vec.push_back(std::stof(token.c_str()));
} while (offset != std::string::npos);
}
catch (...)
{
std::stringstream errMessage;
errMessage << "There was an error reading array [" << szSection << "][" << szKey << "] in file [" << m_szFileName << "]. Defaulting to [0].";
iniErrorMessages.push(errMessage.str());
vec.push_back(0);
}
}
void CIniReader::ReadINT32Array(const CHAR8* szSection, const CHAR8* szKey, std::vector<INT32>& vec)
{
// read the array as a string
STRING512 textBuffer;
ReadString(szSection, szKey, "", textBuffer, _countof(textBuffer));
std::string str, token;
std::string delim = ",";
size_t offset = 0, prevOffset = 0;
// sanitise input
vec.clear();
str = textBuffer;
str.erase(std::remove(str.begin(), str.end(), ' '), str.end());
// split into array
try
{
do
{
offset = str.find(delim, prevOffset);
token = str.substr(prevOffset, offset - prevOffset);
prevOffset = offset + delim.length();
vec.push_back(std::stoi(token.c_str()));
} while (offset != std::string::npos);
}
catch (...)
{
std::stringstream errMessage;
errMessage << "There was an error reading array [" << szSection << "][" << szKey << "] in file [" << m_szFileName << "]. Defaulting to [0].";
iniErrorMessages.push(errMessage.str());
vec.push_back(0);
}
}
BOOLEAN CIniReader::ReadBoolean(const CHAR8* szSection, const CHAR8* szKey, bool defaultValue, bool bolDisplayError)
{
vfs::String str = m_oProps.getStringProperty(szSection, szKey, L"");
if( vfs::StrCmp::Equal(str, L"true") )
{
return TRUE;
}
else if( vfs::StrCmp::Equal(str, L"false") )
{
return FALSE;
}
std::string szResult = str.utf8();
char szDefault[255];
sprintf(szDefault, "%s", defaultValue? "TRUE" : "FALSE");
if(bolDisplayError){
std::stringstream errMessage;
errMessage << "The value [" << szSection << "][" << szKey << "] = \"" << szResult << "\" "
<< "in file [" << this->m_szFileName << "] is neither TRUE nor FALSE. The value " << szDefault << " will be used.";
iniErrorMessages.push(errMessage.str());
}
return defaultValue;
}
// ary-05/15/2009 : snippet on how to use CIniReader::ReadString
// const STR8 test_ini_string = new char[255];
// memset(test_ini_string, 0x00, 255);
// iniReader.ReadString("JA2 Game Settings" , "TEST_STRING" , "default string" , test_ini_string , 255 );
void CIniReader::ReadString(const CHAR8* szSection, const CHAR8* szKey, const CHAR8* szDefaultValue, STR8 input_buffer, size_t buffer_size)
{
std::string s = m_oProps.getStringProperty(szSection, szKey, szDefaultValue).utf8();
int len = std::min<unsigned int>(s.length(),buffer_size-1);
strncpy(input_buffer, s.c_str(), len);
input_buffer[len] = 0;
}
// WANNE - MP: Old version, currently used by Multiplayer
STR8 CIniReader::ReadString(const CHAR8* szSection, const CHAR8* szKey, const CHAR8* szDefaultValue)
{
// >>>>> Memory Leak <<<<<
STR8 szResult = new char[255];
memset(szResult, 0x00, 255);
std::string s = m_oProps.getStringProperty(szSection, szKey, szDefaultValue).utf8();
strncpy(szResult, s.c_str(), std::min<int>(s.length(),254));
return szResult;
}
UINT8 CIniReader::ReadUINT8(const CHAR8* szSection, const CHAR8* szKey, UINT8 defaultValue, UINT8 minValue, UINT8 maxValue)
{
UINT8 iniValueReadFromFile;
iniValueReadFromFile = (UINT8) this->ReadUINT( szSection, szKey, (UINT32) defaultValue, (UINT32) minValue, (UINT32) maxValue);
return iniValueReadFromFile;
}
UINT16 CIniReader::ReadUINT16(const CHAR8* szSection, const CHAR8* szKey, UINT16 defaultValue, UINT16 minValue, UINT16 maxValue)
{
UINT16 iniValueReadFromFile;
iniValueReadFromFile = (UINT16) this->ReadUINT( szSection, szKey, (UINT32) defaultValue, (UINT32) minValue, (UINT32) maxValue);
return iniValueReadFromFile;
}
UINT32 CIniReader::ReadUINT32(const CHAR8* szSection, const CHAR8* szKey, UINT32 defaultValue, UINT32 minValue, UINT32 maxValue)
{
UINT32 iniValueReadFromFile;
iniValueReadFromFile = (UINT32) this->ReadUINT( szSection, szKey, (UINT32) defaultValue, (UINT32) minValue, (UINT32) maxValue);
return iniValueReadFromFile;
}
UINT32 CIniReader::ReadUINT(const CHAR8* szSection, const CHAR8* szKey, UINT32 defaultValue, UINT32 minValue, UINT32 maxValue )
{
UINT32 iniValueReadFromFile;
iniValueReadFromFile = (UINT32) m_oProps.getUIntProperty(szSection, szKey, defaultValue);
//AssertGE(iniValueReadFromFile, minValue);
//AssertLE(iniValueReadFromFile, maxValue);
if (iniValueReadFromFile < minValue)
{
PushErrorMessage(this->m_szFileName, szSection, szKey, iniValueReadFromFile, minValue, minValue, maxValue);
iniValueReadFromFile = minValue;
}
else if (iniValueReadFromFile > maxValue)
{
PushErrorMessage(this->m_szFileName, szSection, szKey, iniValueReadFromFile, maxValue, minValue, maxValue);
iniValueReadFromFile = maxValue;
}
return iniValueReadFromFile;
}