- moved most basic utility code without any dependencies on the rest of the engine to 'common' directory.

Again the objective is easier sharing with Raze.
This commit is contained in:
Christoph Oelckers 2020-04-11 12:59:55 +02:00
commit fb1a7679ec
28 changed files with 293 additions and 227 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,87 @@
// cmdlib.h
#ifndef __CMDLIB__
#define __CMDLIB__
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <stdarg.h>
#include <time.h>
#include "zstring.h"
#if !defined(GUID_DEFINED)
#define GUID_DEFINED
typedef struct _GUID
{
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} GUID;
#endif
template <typename T, size_t N>
char(&_ArraySizeHelper(T(&array)[N]))[N];
#define countof( array ) (sizeof( _ArraySizeHelper( array ) ))
// the dec offsetof macro doesnt work very well...
#define myoffsetof(type,identifier) ((size_t)&((type *)alignof(type))->identifier - alignof(type))
bool FileExists (const char *filename);
bool DirExists(const char *filename);
bool DirEntryExists (const char *pathname, bool *isdir = nullptr);
bool GetFileInfo(const char* pathname, size_t* size, time_t* time);
extern FString progdir;
void FixPathSeperator (char *path);
static void inline FixPathSeperator (FString &path) { path.ReplaceChars('\\', '/'); }
void DefaultExtension (FString &path, const char *extension);
void NormalizeFileName(FString &str);
FString ExtractFilePath (const char *path);
FString ExtractFileBase (const char *path, bool keep_extension=false);
FString StripExtension(const char* path);
struct FScriptPosition;
bool IsNum (const char *str); // [RH] added
char *copystring(const char *s);
void ReplaceString (char **ptr, const char *str);
bool CheckWildcards (const char *pattern, const char *text);
void FormatGUID (char *buffer, size_t buffsize, const GUID &guid);
const char *myasctime ();
int strbin (char *str);
FString strbin1 (const char *start);
void CreatePath(const char * fn);
FString ExpandEnvVars(const char *searchpathstring);
FString NicePath(const char *path);
struct FFileList
{
FString Filename;
bool isDirectory;
};
bool ScanDirectory(TArray<FFileList> &list, const char *dirpath);
bool IsAbsPath(const char*);
FString M_ZLibError(int zerrnum);
inline int32_t Scale(int32_t a, int32_t b, int32_t c)
{
return (int32_t)(((int64_t)a * b) / c);
}
#endif

View file

@ -0,0 +1,952 @@
/*
** configfile.cpp
** Implements the basic .ini parsing class
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "configfile.h"
#include "files.h"
#define READBUFFERSIZE 256
//====================================================================
//
// FConfigFile Constructor
//
//====================================================================
FConfigFile::FConfigFile ()
{
Sections = CurrentSection = NULL;
LastSectionPtr = &Sections;
CurrentEntry = NULL;
PathName = "";
OkayToWrite = true;
FileExisted = true;
}
//====================================================================
//
// FConfigFile Constructor
//
//====================================================================
FConfigFile::FConfigFile (const char *pathname)
{
Sections = CurrentSection = NULL;
LastSectionPtr = &Sections;
CurrentEntry = NULL;
ChangePathName (pathname);
LoadConfigFile ();
OkayToWrite = true;
FileExisted = true;
}
//====================================================================
//
// FConfigFile Copy Constructor
//
//====================================================================
FConfigFile::FConfigFile (const FConfigFile &other)
{
Sections = CurrentSection = NULL;
LastSectionPtr = &Sections;
CurrentEntry = NULL;
ChangePathName (other.PathName);
*this = other;
OkayToWrite = other.OkayToWrite;
FileExisted = other.FileExisted;
}
//====================================================================
//
// FConfigFile Destructor
//
//====================================================================
FConfigFile::~FConfigFile ()
{
FConfigSection *section = Sections;
while (section != NULL)
{
FConfigSection *nextsection = section->Next;
FConfigEntry *entry = section->RootEntry;
while (entry != NULL)
{
FConfigEntry *nextentry = entry->Next;
delete[] entry->Value;
delete[] (char *)entry;
entry = nextentry;
}
delete section;
section = nextsection;
}
}
//====================================================================
//
// FConfigFile Copy Operator
//
//====================================================================
FConfigFile &FConfigFile::operator = (const FConfigFile &other)
{
FConfigSection *fromsection, *tosection;
FConfigEntry *fromentry;
ClearConfig ();
fromsection = other.Sections;
while (fromsection != NULL)
{
fromentry = fromsection->RootEntry;
tosection = NewConfigSection (fromsection->SectionName);
while (fromentry != NULL)
{
NewConfigEntry (tosection, fromentry->Key, fromentry->Value);
fromentry = fromentry->Next;
}
fromsection = fromsection->Next;
}
return *this;
}
//====================================================================
//
// FConfigFile :: ClearConfig
//
// Removes all sections and entries from the config file.
//
//====================================================================
void FConfigFile::ClearConfig ()
{
CurrentSection = Sections;
while (CurrentSection != NULL)
{
FConfigSection *next = CurrentSection->Next;
ClearCurrentSection ();
delete CurrentSection;
CurrentSection = next;
}
Sections = NULL;
LastSectionPtr = &Sections;
}
//====================================================================
//
// FConfigFile :: ChangePathName
//
//====================================================================
void FConfigFile::ChangePathName (const char *pathname)
{
PathName = pathname;
}
//====================================================================
//
// FConfigFile :: CreateSectionAtStart
//
// Creates the section at the start of the file if it does not exist.
// Otherwise, simply moves the section to the start of the file.
//
//====================================================================
void FConfigFile::CreateSectionAtStart (const char *name)
{
NewConfigSection (name);
MoveSectionToStart (name);
}
//====================================================================
//
// FConfigFile :: MoveSectionToStart
//
// Moves the named section to the start of the file if it exists.
// Otherwise, does nothing.
//
//====================================================================
void FConfigFile::MoveSectionToStart (const char *name)
{
FConfigSection *section = FindSection (name);
if (section != NULL)
{
FConfigSection **prevsec = &Sections;
while (*prevsec != NULL && *prevsec != section)
{
prevsec = &((*prevsec)->Next);
}
*prevsec = section->Next;
section->Next = Sections;
Sections = section;
if (LastSectionPtr == &section->Next)
{
LastSectionPtr = prevsec;
}
}
}
//====================================================================
//
// FConfigFile :: SetSection
//
// Sets the current section to the named one, optionally creating it
// if it does not exist. Returns true if the section exists (even if
// it was newly created), false otherwise.
//
//====================================================================
bool FConfigFile::SetSection (const char *name, bool allowCreate)
{
FConfigSection *section = FindSection (name);
if (section == NULL && allowCreate)
{
section = NewConfigSection (name);
}
if (section != NULL)
{
CurrentSection = section;
CurrentEntry = section->RootEntry;
return true;
}
return false;
}
//====================================================================
//
// FConfigFile :: SetFirstSection
//
// Sets the current section to the first one in the file. Returns
// false if there are no sections.
//
//====================================================================
bool FConfigFile::SetFirstSection ()
{
CurrentSection = Sections;
if (CurrentSection != NULL)
{
CurrentEntry = CurrentSection->RootEntry;
return true;
}
return false;
}
//====================================================================
//
// FConfigFile :: SetNextSection
//
// Advances the current section to the next one in the file. Returns
// false if there are no more sections.
//
//====================================================================
bool FConfigFile::SetNextSection ()
{
if (CurrentSection != NULL)
{
CurrentSection = CurrentSection->Next;
if (CurrentSection != NULL)
{
CurrentEntry = CurrentSection->RootEntry;
return true;
}
}
return false;
}
//====================================================================
//
// FConfigFile :: GetCurrentSection
//
// Returns the name of the current section.
//
//====================================================================
const char *FConfigFile::GetCurrentSection () const
{
if (CurrentSection != NULL)
{
return CurrentSection->SectionName.GetChars();
}
return NULL;
}
//====================================================================
//
// FConfigFile :: ClearCurrentSection
//
// Removes all entries from the current section.
//
//====================================================================
void FConfigFile::ClearCurrentSection ()
{
if (CurrentSection != NULL)
{
FConfigEntry *entry, *next;
entry = CurrentSection->RootEntry;
while (entry != NULL)
{
next = entry->Next;
delete[] entry->Value;
delete[] (char *)entry;
entry = next;
}
CurrentSection->RootEntry = NULL;
CurrentSection->LastEntryPtr = &CurrentSection->RootEntry;
}
}
//====================================================================
//
// FConfigFile :: DeleteCurrentSection
//
// Completely removes the current section. The current section is
// advanced to the next section. Returns true if there is still a
// current section.
//
//====================================================================
bool FConfigFile::DeleteCurrentSection()
{
if (CurrentSection != NULL)
{
FConfigSection *sec;
ClearCurrentSection();
// Find the preceding section.
for (sec = Sections; sec != NULL && sec->Next != CurrentSection; sec = sec->Next)
{ }
sec->Next = CurrentSection->Next;
if (LastSectionPtr == &CurrentSection->Next)
{
LastSectionPtr = &sec->Next;
}
delete CurrentSection;
CurrentSection = sec->Next;
return CurrentSection != NULL;
}
return false;
}
//====================================================================
//
// FConfigFile :: ClearKey
//
// Removes a key from the current section, if found. If there are
// duplicates, only the first is removed.
//
//====================================================================
void FConfigFile::ClearKey(const char *key)
{
if (CurrentSection->RootEntry == NULL)
{
return;
}
FConfigEntry **prober = &CurrentSection->RootEntry, *probe = *prober;
while (probe != NULL && stricmp(probe->Key, key) != 0)
{
prober = &probe->Next;
probe = *prober;
}
if (probe != NULL)
{
*prober = probe->Next;
if (CurrentSection->LastEntryPtr == &probe->Next)
{
CurrentSection->LastEntryPtr = prober;
}
delete[] probe->Value;
delete[] (char *)probe;
}
}
//====================================================================
//
// FConfigFile :: SectionIsEmpty
//
// Returns true if the current section has no entries. If there is
// no current section, it is also considered empty.
//
//====================================================================
bool FConfigFile::SectionIsEmpty()
{
return (CurrentSection == NULL) || (CurrentSection->RootEntry == NULL);
}
//====================================================================
//
// FConfigFile :: NextInSection
//
// Provides the next key/value pair in the current section. Returns
// true if there was another, false otherwise.
//
//====================================================================
bool FConfigFile::NextInSection (const char *&key, const char *&value)
{
FConfigEntry *entry = CurrentEntry;
if (entry == NULL)
return false;
CurrentEntry = entry->Next;
key = entry->Key;
value = entry->Value;
return true;
}
//====================================================================
//
// FConfigFile :: GetValueForKey
//
// Returns the value for the specified key in the current section,
// returning NULL if the key does not exist.
//
//====================================================================
const char *FConfigFile::GetValueForKey (const char *key) const
{
FConfigEntry *entry = FindEntry (CurrentSection, key);
if (entry != NULL)
{
return entry->Value;
}
return NULL;
}
//====================================================================
//
// FConfigFile :: SetValueForKey
//
// Sets they key/value pair as specified in the current section. If
// duplicates are allowed, it always creates a new pair. Otherwise, it
// will overwrite the value of an existing key with the same name.
//
//====================================================================
void FConfigFile::SetValueForKey (const char *key, const char *value, bool duplicates)
{
if (CurrentSection != NULL)
{
FConfigEntry *entry;
if (duplicates || (entry = FindEntry (CurrentSection, key)) == NULL)
{
NewConfigEntry (CurrentSection, key, value);
}
else
{
entry->SetValue (value);
}
}
}
//====================================================================
//
// FConfigFile :: FindSection
//
//====================================================================
FConfigFile::FConfigSection *FConfigFile::FindSection (const char *name) const
{
FConfigSection *section = Sections;
while (section != NULL && section->SectionName.CompareNoCase(name) != 0)
{
section = section->Next;
}
return section;
}
//====================================================================
//
// FConfigFile :: RenameSection
//
//====================================================================
void FConfigFile::RenameSection (const char *oldname, const char *newname) const
{
FConfigSection *section = FindSection(oldname);
if (section != NULL)
{
section->SectionName = newname;
}
}
//====================================================================
//
// FConfigFile :: FindEntry
//
//====================================================================
FConfigFile::FConfigEntry *FConfigFile::FindEntry (
FConfigFile::FConfigSection *section, const char *key) const
{
FConfigEntry *probe = section->RootEntry;
while (probe != NULL && stricmp (probe->Key, key) != 0)
{
probe = probe->Next;
}
return probe;
}
//====================================================================
//
// FConfigFile :: NewConfigSection
//
//====================================================================
FConfigFile::FConfigSection *FConfigFile::NewConfigSection (const char *name)
{
FConfigSection *section;
section = FindSection (name);
if (section == NULL)
{
section = new FConfigSection;
section->RootEntry = NULL;
section->LastEntryPtr = &section->RootEntry;
section->Next = NULL;
section->SectionName = name;
*LastSectionPtr = section;
LastSectionPtr = &section->Next;
}
return section;
}
//====================================================================
//
// FConfigFile :: NewConfigEntry
//
//====================================================================
FConfigFile::FConfigEntry *FConfigFile::NewConfigEntry (
FConfigFile::FConfigSection *section, const char *key, const char *value)
{
FConfigEntry *entry;
size_t keylen;
keylen = strlen (key);
entry = (FConfigEntry *)new char[sizeof(*section)+keylen];
entry->Value = NULL;
entry->Next = NULL;
memcpy (entry->Key, key, keylen);
entry->Key[keylen] = 0;
*(section->LastEntryPtr) = entry;
section->LastEntryPtr = &entry->Next;
entry->SetValue (value);
return entry;
}
//====================================================================
//
// FConfigFile :: LoadConfigFile
//
//====================================================================
void FConfigFile::LoadConfigFile ()
{
FileReader file;
bool succ;
FileExisted = false;
if (!file.OpenFile (PathName))
{
return;
}
succ = ReadConfig (&file);
FileExisted = succ;
}
//====================================================================
//
// FConfigFile :: ReadConfig
//
//====================================================================
bool FConfigFile::ReadConfig (FileReader *file)
{
TArray<uint8_t> readbuf;
FConfigSection *section = NULL;
ClearConfig ();
while (ReadLine (readbuf, file) != NULL)
{
uint8_t *start = readbuf.Data();
uint8_t *equalpt;
uint8_t *endpt;
// Remove white space at start of line
while (*start && *start <= ' ')
{
start++;
}
// Remove comment lines
if (*start == '#' || (start[0] == '/' && start[1] == '/'))
{
continue;
}
// Remove white space at end of line
endpt = start + strlen ((char*)start) - 1;
while (endpt > start && *endpt <= ' ')
{
endpt--;
}
// Remove line feed '\n' character
endpt[1] = 0;
if (endpt <= start)
continue; // Nothing here
if (*start == '[')
{ // Section header
if (*endpt == ']')
*endpt = 0;
section = NewConfigSection ((char*)start+1);
}
else if (section == NULL)
{
return false;
}
else
{ // Should be key=value
equalpt = (uint8_t*)strchr ((char*)start, '=');
if (equalpt != NULL && equalpt > start)
{
// Remove white space in front of =
uint8_t *whiteprobe = equalpt - 1;
while (whiteprobe > start && isspace(*whiteprobe))
{
whiteprobe--;
}
whiteprobe[1] = 0;
// Remove white space after =
whiteprobe = equalpt + 1;
while (*whiteprobe && isspace(*whiteprobe))
{
whiteprobe++;
}
*(whiteprobe - 1) = 0;
// Check for multi-line value
if (whiteprobe[0] == '<' && whiteprobe[1] == '<' && whiteprobe[2] == '<' && whiteprobe[3] != '\0')
{
ReadMultiLineValue (file, section, (char*)start, (char*)whiteprobe + 3);
}
else
{
NewConfigEntry (section, (char*)start, (char*)whiteprobe);
}
}
}
}
return true;
}
//====================================================================
//
// FConfigFile :: ReadMultiLineValue
//
// Reads a multi-line value, with format as follows:
//
// key=<<<ENDTAG
// ... blah blah blah ...
// >>>ENDTAG
//
// The final ENDTAG must be on a line all by itself.
//
//====================================================================
FConfigFile::FConfigEntry *FConfigFile::ReadMultiLineValue(FileReader *file, FConfigSection *section, const char *key, const char *endtag)
{
TArray<uint8_t> readbuf;
FString value;
size_t endlen = strlen(endtag);
// Keep on reading lines until we reach a line that matches >>>endtag
while (ReadLine(readbuf, file) != NULL)
{
// Does the start of this line match the endtag?
if (readbuf[0] == '>' && readbuf[1] == '>' && readbuf[2] == '>' &&
strncmp((char*)readbuf.Data() + 3, endtag, endlen) == 0)
{ // Is there nothing but line break characters after the match?
size_t i;
for (i = endlen + 3; readbuf[i] != '\0'; ++i)
{
if (readbuf[i] != '\n' && readbuf[i] != '\r')
{ // Not a line break character
break;
}
}
if (readbuf[i] == '\0')
{ // We're done; strip the previous line's line breaks, since it's not part of the value.
value.StripRight("\n\r");
}
break;
}
// Append this line to the value.
value << readbuf;
}
return NewConfigEntry(section, key, value);
}
//====================================================================
//
// FConfigFile :: ReadLine
//
//====================================================================
uint8_t *FConfigFile::ReadLine(TArray<uint8_t>& string, FileReader* file) const
{
uint8_t byte;
string.Clear();
while (file->Read(&byte, 1))
{
if (byte == 0)
{
break;
}
if (byte != '\r')
{
string.Push(byte);
if (byte == '\n')
{
break;
}
}
}
if (string.Size() == 0) return nullptr;
string.Push(0);
return string.Data();
}
//====================================================================
//
// FConfigFile :: WriteConfigFile
//
//====================================================================
bool FConfigFile::WriteConfigFile () const
{
if (!OkayToWrite && FileExisted)
{ // Pretend it was written anyway so that the user doesn't get
// any "config not written" notifications, but only if the file
// already existed. Otherwise, let it write out a default one.
return true;
}
FileWriter *file = FileWriter::Open (PathName);
FConfigSection *section;
FConfigEntry *entry;
if (file == NULL)
return false;
WriteCommentHeader (file);
section = Sections;
while (section != NULL)
{
entry = section->RootEntry;
if (section->Note.IsNotEmpty())
{
file->Write (section->Note.GetChars(), section->Note.Len());
}
file->Printf ("[%s]\n", section->SectionName.GetChars());
while (entry != NULL)
{
if (strpbrk(entry->Value, "\r\n") == NULL)
{ // Single-line value
file->Printf ("%s=%s\n", entry->Key, entry->Value);
}
else
{ // Multi-line value
const char *endtag = GenerateEndTag(entry->Value);
file->Printf ("%s=<<<%s\n%s\n>>>%s\n", entry->Key,
endtag, entry->Value, endtag);
}
entry = entry->Next;
}
section = section->Next;
file->Write ("\n", 1);
}
delete file;
return true;
}
//====================================================================
//
// FConfigFile :: GenerateEndTag
//
// Generates a terminator sequence for multi-line values that does
// not appear anywhere in the value.
//
//====================================================================
const char *FConfigFile::GenerateEndTag(const char *value)
{
static const char Base64Table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._";
static char EndTag[25] = "EOV-";
// Try different 20-character sequences until we find one that
// isn't in the value. We create the sequences by generating two
// 64-bit random numbers and Base64 encoding the first 15 bytes
// from them.
union { uint16_t rand_num[8]; uint8_t rand_bytes[16]; };
do
{
for (auto &r : rand_num) r = (uint16_t)rand();
for (int i = 0; i < 5; ++i)
{
//uint32_t three_bytes = (rand_bytes[i*3] << 16) | (rand_bytes[i*3+1] << 8) | (rand_bytes[i*3+2]); // ???
EndTag[4+i*4 ] = Base64Table[rand_bytes[i*3] >> 2];
EndTag[4+i*4+1] = Base64Table[((rand_bytes[i*3] & 3) << 4) | (rand_bytes[i*3+1] >> 4)];
EndTag[4+i*4+2] = Base64Table[((rand_bytes[i*3+1] & 15) << 2) | (rand_bytes[i*3+2] >> 6)];
EndTag[4+i*4+3] = Base64Table[rand_bytes[i*3+2] & 63];
}
}
while (strstr(value, EndTag) != NULL);
return EndTag;
}
//====================================================================
//
// FConfigFile :: WriteCommentHeader
//
// Override in a subclass to write a header to the config file.
//
//====================================================================
void FConfigFile::WriteCommentHeader (FileWriter *file) const
{
}
//====================================================================
//
// FConfigFile :: FConfigEntry :: SetValue
//
//====================================================================
void FConfigFile::FConfigEntry::SetValue (const char *value)
{
if (Value != NULL)
{
delete[] Value;
}
Value = new char[strlen (value)+1];
strcpy (Value, value);
}
//====================================================================
//
// FConfigFile :: GetPosition
//
// Populates a struct with the current position of the parse cursor.
//
//====================================================================
void FConfigFile::GetPosition (FConfigFile::Position &pos) const
{
pos.Section = CurrentSection;
pos.Entry = CurrentEntry;
}
//====================================================================
//
// FConfigFile :: SetPosition
//
// Sets the parse cursor to a previously retrieved position.
//
//====================================================================
void FConfigFile::SetPosition (const FConfigFile::Position &pos)
{
CurrentSection = pos.Section;
CurrentEntry = pos.Entry;
}
//====================================================================
//
// FConfigFile :: SetSectionNote
//
// Sets a comment note to be inserted into the INI verbatim directly
// ahead of the section. Notes are lost when the INI is read so must
// be explicitly set to be maintained.
//
//====================================================================
void FConfigFile::SetSectionNote(const char *section, const char *note)
{
SetSectionNote(FindSection(section), note);
}
void FConfigFile::SetSectionNote(const char *note)
{
SetSectionNote(CurrentSection, note);
}
void FConfigFile::SetSectionNote(FConfigSection *section, const char *note)
{
if (section != NULL)
{
if (note == NULL)
{
note = "";
}
section->Note = note;
}
}

View file

@ -0,0 +1,132 @@
/*
** configfile.h
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#ifndef __CONFIGFILE_H__
#define __CONFIGFILE_H__
#include <stdio.h>
#include "files.h"
#include "zstring.h"
class FConfigFile
{
public:
FConfigFile ();
FConfigFile (const char *pathname);
FConfigFile (const FConfigFile &other);
virtual ~FConfigFile ();
void ClearConfig ();
FConfigFile &operator= (const FConfigFile &other);
bool HaveSections () { return Sections != NULL; }
void CreateSectionAtStart (const char *name);
void MoveSectionToStart (const char *name);
void SetSectionNote (const char *section, const char *note);
void SetSectionNote (const char *note);
bool SetSection (const char *section, bool allowCreate=false);
bool SetFirstSection ();
bool SetNextSection ();
const char *GetCurrentSection () const;
void ClearCurrentSection ();
bool DeleteCurrentSection ();
void ClearKey (const char *key);
bool SectionIsEmpty ();
bool NextInSection (const char *&key, const char *&value);
const char *GetValueForKey (const char *key) const;
void SetValueForKey (const char *key, const char *value, bool duplicates=false);
const char *GetPathName () const { return PathName.GetChars(); }
void ChangePathName (const char *path);
void LoadConfigFile ();
bool WriteConfigFile () const;
protected:
virtual void WriteCommentHeader (FileWriter *file) const;
uint8_t *ReadLine (TArray<uint8_t> &string, FileReader *file) const;
bool ReadConfig (FileReader *file);
static const char *GenerateEndTag(const char *value);
void RenameSection(const char *oldname, const char *newname) const;
bool OkayToWrite;
bool FileExisted;
private:
struct FConfigEntry
{
char *Value;
FConfigEntry *Next;
char Key[1]; // + length of key
void SetValue (const char *val);
};
struct FConfigSection
{
FString SectionName;
FConfigEntry *RootEntry;
FConfigEntry **LastEntryPtr;
FConfigSection *Next;
FString Note;
//char Name[1]; // + length of name
};
FConfigSection* Sections = nullptr;
FConfigSection **LastSectionPtr;
FConfigSection *CurrentSection;
FConfigEntry *CurrentEntry;
FString PathName;
FConfigSection *FindSection (const char *name) const;
FConfigEntry *FindEntry (FConfigSection *section, const char *key) const;
FConfigSection *NewConfigSection (const char *name);
FConfigEntry *NewConfigEntry (FConfigSection *section, const char *key, const char *value);
FConfigEntry *ReadMultiLineValue (FileReader *file, FConfigSection *section, const char *key, const char *terminator);
void SetSectionNote (FConfigSection *section, const char *note);
public:
class Position
{
friend class FConfigFile;
FConfigSection *Section;
FConfigEntry *Entry;
};
void GetPosition (Position &pos) const;
void SetPosition (const Position &pos);
};
#endif //__CONFIGFILE_H__

View file

@ -0,0 +1,489 @@
/*
** files.cpp
** Implements classes for reading from files or memory blocks
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
** Copyright 2005-2008 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "files.h"
#include "templates.h" // just for 'clamp'
#include "zstring.h"
FILE *myfopen(const char *filename, const char *flags)
{
#ifndef _WIN32
return fopen(filename, flags);
#else
auto widename = WideString(filename);
auto wideflags = WideString(flags);
return _wfopen(widename.c_str(), wideflags.c_str());
#endif
}
//==========================================================================
//
// StdFileReader
//
// reads data from an stdio FILE* or part of it.
//
//==========================================================================
class StdFileReader : public FileReaderInterface
{
FILE *File = nullptr;
long StartPos = 0;
long FilePos = 0;
public:
StdFileReader()
{}
~StdFileReader()
{
if (File != nullptr)
{
fclose(File);
}
File = nullptr;
}
bool Open(const char *filename, long startpos = 0, long len = -1)
{
File = myfopen(filename, "rb");
if (File == nullptr) return false;
FilePos = startpos;
StartPos = startpos;
Length = CalcFileLen();
if (len >= 0 && len < Length) Length = len;
if (startpos > 0) Seek(0, SEEK_SET);
return true;
}
long Tell() const override
{
return FilePos - StartPos;
}
long Seek(long offset, int origin) override
{
if (origin == SEEK_SET)
{
offset += StartPos;
}
else if (origin == SEEK_CUR)
{
offset += FilePos;
}
else if (origin == SEEK_END)
{
offset += StartPos + Length;
}
if (offset < StartPos || offset > StartPos + Length) return -1; // out of scope
if (0 == fseek(File, offset, SEEK_SET))
{
FilePos = offset;
return 0;
}
return -1;
}
long Read(void *buffer, long len) override
{
assert(len >= 0);
if (len <= 0) return 0;
if (FilePos + len > StartPos + Length)
{
len = Length - FilePos + StartPos;
}
len = (long)fread(buffer, 1, len, File);
FilePos += len;
return len;
}
char *Gets(char *strbuf, int len) override
{
if (len <= 0 || FilePos >= StartPos + Length) return NULL;
char *p = fgets(strbuf, len, File);
if (p != NULL)
{
int old = FilePos;
FilePos = ftell(File);
if (FilePos - StartPos > Length)
{
strbuf[Length - old + StartPos] = 0;
}
}
return p;
}
private:
long CalcFileLen() const
{
long endpos;
fseek(File, 0, SEEK_END);
endpos = ftell(File);
fseek(File, 0, SEEK_SET);
return endpos;
}
};
//==========================================================================
//
// FileReaderRedirect
//
// like the above, but uses another File reader as its backing data
//
//==========================================================================
class FileReaderRedirect : public FileReaderInterface
{
FileReader *mReader = nullptr;
long StartPos = 0;
long FilePos = 0;
public:
FileReaderRedirect(FileReader &parent, long start, long length)
{
mReader = &parent;
FilePos = start;
StartPos = start;
Length = length;
Seek(0, SEEK_SET);
}
virtual long Tell() const override
{
return FilePos - StartPos;
}
virtual long Seek(long offset, int origin) override
{
switch (origin)
{
case SEEK_SET:
offset += StartPos;
break;
case SEEK_END:
offset += StartPos + Length;
break;
case SEEK_CUR:
offset += (long)mReader->Tell();
break;
}
if (offset < StartPos || offset > StartPos + Length) return -1; // out of scope
if (mReader->Seek(offset, FileReader::SeekSet) == 0)
{
FilePos = offset;
return 0;
}
return -1;
}
virtual long Read(void *buffer, long len) override
{
assert(len >= 0);
if (len <= 0) return 0;
if (FilePos + len > StartPos + Length)
{
len = Length - FilePos + StartPos;
}
len = (long)mReader->Read(buffer, len);
FilePos += len;
return len;
}
virtual char *Gets(char *strbuf, int len) override
{
if (len <= 0 || FilePos >= StartPos + Length) return NULL;
char *p = mReader->Gets(strbuf, len);
if (p != NULL)
{
int old = FilePos;
FilePos = (long)mReader->Tell();
if (FilePos - StartPos > Length)
{
strbuf[Length - old + StartPos] = 0;
}
}
return p;
}
};
//==========================================================================
//
// MemoryReader
//
// reads data from a block of memory
//
//==========================================================================
long MemoryReader::Tell() const
{
return FilePos;
}
long MemoryReader::Seek(long offset, int origin)
{
switch (origin)
{
case SEEK_CUR:
offset += FilePos;
break;
case SEEK_END:
offset += Length;
break;
}
if (offset < 0 || offset > Length) return -1;
FilePos = clamp<long>(offset, 0, Length);
return 0;
}
long MemoryReader::Read(void *buffer, long len)
{
if (len>Length - FilePos) len = Length - FilePos;
if (len<0) len = 0;
memcpy(buffer, bufptr + FilePos, len);
FilePos += len;
return len;
}
char *MemoryReader::Gets(char *strbuf, int len)
{
if (len>Length - FilePos) len = Length - FilePos;
if (len <= 0) return NULL;
char *p = strbuf;
while (len > 1)
{
if (bufptr[FilePos] == 0)
{
FilePos++;
break;
}
if (bufptr[FilePos] != '\r')
{
*p++ = bufptr[FilePos];
len--;
if (bufptr[FilePos] == '\n')
{
FilePos++;
break;
}
}
FilePos++;
}
if (p == strbuf) return NULL;
*p++ = 0;
return strbuf;
}
//==========================================================================
//
// MemoryArrayReader
//
// reads data from an array of memory
//
//==========================================================================
class MemoryArrayReader : public MemoryReader
{
TArray<uint8_t> buf;
public:
MemoryArrayReader(const char *buffer, long length)
{
if (length > 0)
{
buf.Resize(length);
memcpy(&buf[0], buffer, length);
}
UpdateBuffer();
}
TArray<uint8_t> &GetArray() { return buf; }
void UpdateBuffer()
{
bufptr = (const char*)&buf[0];
FilePos = 0;
Length = buf.Size();
}
};
//==========================================================================
//
// FileReader
//
// this wraps the different reader types in an object with value semantics.
//
//==========================================================================
bool FileReader::OpenFile(const char *filename, FileReader::Size start, FileReader::Size length)
{
auto reader = new StdFileReader;
if (!reader->Open(filename, (long)start, (long)length))
{
delete reader;
return false;
}
Close();
mReader = reader;
return true;
}
bool FileReader::OpenFilePart(FileReader &parent, FileReader::Size start, FileReader::Size length)
{
auto reader = new FileReaderRedirect(parent, (long)start, (long)length);
Close();
mReader = reader;
return true;
}
bool FileReader::OpenMemory(const void *mem, FileReader::Size length)
{
Close();
mReader = new MemoryReader((const char *)mem, (long)length);
return true;
}
bool FileReader::OpenMemoryArray(const void *mem, FileReader::Size length)
{
Close();
mReader = new MemoryArrayReader((const char *)mem, (long)length);
return true;
}
bool FileReader::OpenMemoryArray(std::function<bool(TArray<uint8_t>&)> getter)
{
auto reader = new MemoryArrayReader(nullptr, 0);
if (getter(reader->GetArray()))
{
Close();
reader->UpdateBuffer();
mReader = reader;
return true;
}
else
{
// This will keep the old buffer, if one existed
delete reader;
return false;
}
}
//==========================================================================
//
// FileWriter (the motivation here is to have a buffer writing subclass)
//
//==========================================================================
bool FileWriter::OpenDirect(const char *filename)
{
File = myfopen(filename, "wb");
return (File != NULL);
}
FileWriter *FileWriter::Open(const char *filename)
{
FileWriter *fwrit = new FileWriter();
if (fwrit->OpenDirect(filename))
{
return fwrit;
}
delete fwrit;
return NULL;
}
size_t FileWriter::Write(const void *buffer, size_t len)
{
if (File != NULL)
{
return fwrite(buffer, 1, len, File);
}
else
{
return 0;
}
}
long FileWriter::Tell()
{
if (File != NULL)
{
return ftell(File);
}
else
{
return 0;
}
}
long FileWriter::Seek(long offset, int mode)
{
if (File != NULL)
{
return fseek(File, offset, mode);
}
else
{
return 0;
}
}
size_t FileWriter::Printf(const char *fmt, ...)
{
va_list ap;
FString out;
va_start(ap, fmt);
out.VFormat(fmt, ap);
va_end(ap);
return Write(out.GetChars(), out.Len());
}
size_t BufferWriter::Write(const void *buffer, size_t len)
{
unsigned int ofs = mBuffer.Reserve((unsigned)len);
memcpy(&mBuffer[ofs], buffer, len);
return len;
}

369
src/common/utility/files.h Normal file
View file

@ -0,0 +1,369 @@
/*
** files.h
** Implements classes for reading from files or memory blocks
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
** Copyright 2005-2008 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#ifndef FILES_H
#define FILES_H
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <functional>
#include "basics.h"
#include "m_swap.h"
#include "tarray.h"
// Zip compression methods, extended by some internal types to be passed to OpenDecompressor
enum
{
METHOD_STORED = 0,
METHOD_SHRINK = 1,
METHOD_IMPLODE = 6,
METHOD_DEFLATE = 8,
METHOD_BZIP2 = 12,
METHOD_LZMA = 14,
METHOD_PPMD = 98,
METHOD_LZSS = 1337, // not used in Zips - this is for Console Doom compression
METHOD_ZLIB = 1338, // Zlib stream with header, used by compressed nodes.
METHOD_TRANSFEROWNER = 0x8000,
};
class FileReader;
class FileReaderInterface
{
public:
long Length = -1;
virtual ~FileReaderInterface() {}
virtual long Tell () const = 0;
virtual long Seek (long offset, int origin) = 0;
virtual long Read (void *buffer, long len) = 0;
virtual char *Gets(char *strbuf, int len) = 0;
virtual const char *GetBuffer() const { return nullptr; }
long GetLength () const { return Length; }
};
class MemoryReader : public FileReaderInterface
{
protected:
const char * bufptr = nullptr;
long FilePos = 0;
MemoryReader()
{}
public:
MemoryReader(const char *buffer, long length)
{
bufptr = buffer;
Length = length;
FilePos = 0;
}
long Tell() const override;
long Seek(long offset, int origin) override;
long Read(void *buffer, long len) override;
char *Gets(char *strbuf, int len) override;
virtual const char *GetBuffer() const override { return bufptr; }
};
struct FResourceLump;
class FileReader
{
friend struct FResourceLump; // needs access to the private constructor.
FileReaderInterface *mReader = nullptr;
FileReader(const FileReader &r) = delete;
FileReader &operator=(const FileReader &r) = delete;
public:
explicit FileReader(FileReaderInterface *r)
{
mReader = r;
}
enum ESeek
{
SeekSet = SEEK_SET,
SeekCur = SEEK_CUR,
SeekEnd = SEEK_END
};
typedef ptrdiff_t Size; // let's not use 'long' here.
FileReader() {}
FileReader(FileReader &&r)
{
mReader = r.mReader;
r.mReader = nullptr;
}
FileReader& operator =(FileReader &&r)
{
Close();
mReader = r.mReader;
r.mReader = nullptr;
return *this;
}
// This is for wrapping the actual reader for custom access where a managed FileReader won't work.
FileReaderInterface* GetInterface()
{
auto i = mReader;
mReader = nullptr;
return i;
}
~FileReader()
{
Close();
}
bool isOpen() const
{
return mReader != nullptr;
}
void Close()
{
if (mReader != nullptr) delete mReader;
mReader = nullptr;
}
bool OpenFile(const char *filename, Size start = 0, Size length = -1);
bool OpenFilePart(FileReader &parent, Size start, Size length);
bool OpenMemory(const void *mem, Size length); // read directly from the buffer
bool OpenMemoryArray(const void *mem, Size length); // read from a copy of the buffer.
bool OpenMemoryArray(std::function<bool(TArray<uint8_t>&)> getter); // read contents to a buffer and return a reader to it
bool OpenDecompressor(FileReader &parent, Size length, int method, bool seekable, const std::function<void(const char*)>& cb); // creates a decompressor stream. 'seekable' uses a buffered version so that the Seek and Tell methods can be used.
Size Tell() const
{
return mReader->Tell();
}
Size Seek(Size offset, ESeek origin)
{
return mReader->Seek((long)offset, origin);
}
Size Read(void *buffer, Size len)
{
return mReader->Read(buffer, (long)len);
}
TArray<uint8_t> Read(size_t len)
{
TArray<uint8_t> buffer((int)len, true);
Size length = mReader->Read(&buffer[0], (long)len);
buffer.Clamp((int)length);
return buffer;
}
TArray<uint8_t> Read()
{
TArray<uint8_t> buffer(mReader->Length, true);
Size length = mReader->Read(&buffer[0], mReader->Length);
if (length < mReader->Length) buffer.Clear();
return buffer;
}
TArray<uint8_t> ReadPadded(int padding)
{
TArray<uint8_t> buffer(mReader->Length + padding, true);
Size length = mReader->Read(&buffer[0], mReader->Length);
if (length < mReader->Length) buffer.Clear();
else memset(buffer.Data() + mReader->Length, 0, padding);
return buffer;
}
char *Gets(char *strbuf, Size len)
{
return mReader->Gets(strbuf, (int)len);
}
const char *GetBuffer()
{
return mReader->GetBuffer();
}
Size GetLength() const
{
return mReader->GetLength();
}
uint8_t ReadUInt8()
{
uint8_t v = 0;
Read(&v, 1);
return v;
}
int8_t ReadInt8()
{
int8_t v = 0;
Read(&v, 1);
return v;
}
uint16_t ReadUInt16()
{
uint16_t v = 0;
Read(&v, 2);
return LittleShort(v);
}
int16_t ReadInt16()
{
uint16_t v = 0;
Read(&v, 2);
return LittleShort(v);
}
int16_t ReadInt16BE()
{
uint16_t v = 0;
Read(&v, 2);
return BigShort(v);
}
uint32_t ReadUInt32()
{
uint32_t v = 0;
Read(&v, 4);
return LittleLong(v);
}
int32_t ReadInt32()
{
uint32_t v = 0;
Read(&v, 4);
return LittleLong(v);
}
uint32_t ReadUInt32BE()
{
uint32_t v = 0;
Read(&v, 4);
return BigLong(v);
}
int32_t ReadInt32BE()
{
uint32_t v = 0;
Read(&v, 4);
return BigLong(v);
}
friend class FWadCollection;
};
class DecompressorBase : public FileReaderInterface
{
std::function<void(const char*)> ErrorCallback = nullptr;
public:
// These do not work but need to be defined to satisfy the FileReaderInterface.
// They will just error out when called.
long Tell() const override;
long Seek(long offset, int origin) override;
char* Gets(char* strbuf, int len) override;
void DecompressionError(const char* error, ...) const;
void SetErrorCallback(const std::function<void(const char*)>& cb)
{
ErrorCallback = cb;
}
void SetOwnsReader();
protected:
FileReader* File = nullptr;
FileReader OwnedFile;
};
class FileWriter
{
protected:
bool OpenDirect(const char *filename);
public:
FileWriter(FILE *f = nullptr) // if passed, this writer will take over the file.
{
File = f;
}
virtual ~FileWriter()
{
Close();
}
static FileWriter *Open(const char *filename);
virtual size_t Write(const void *buffer, size_t len);
virtual long Tell();
virtual long Seek(long offset, int mode);
size_t Printf(const char *fmt, ...) GCCPRINTF(2,3);
virtual void Close()
{
if (File != NULL) fclose(File);
File = nullptr;
}
protected:
FILE *File;
protected:
bool CloseOnDestruct;
};
class BufferWriter : public FileWriter
{
protected:
TArray<unsigned char> mBuffer;
public:
BufferWriter() {}
virtual size_t Write(const void *buffer, size_t len) override;
TArray<unsigned char> *GetBuffer() { return &mBuffer; }
TArray<unsigned char>&& TakeBuffer() { return std::move(mBuffer); }
};
#endif

View file

@ -0,0 +1,653 @@
/*
** files.cpp
** Implements classes for reading from files or memory blocks
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
** Copyright 2005-2008 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
// Caution: LzmaDec also pulls in windows.h!
#define NOMINMAX
#include "LzmaDec.h"
#include <zlib.h>
#include <bzlib.h>
#include <algorithm>
#include "files.h"
#include "templates.h"
#include "zstring.h"
#include "cmdlib.h"
//==========================================================================
//
// I_Error
//
// Throw an error that will send us to the console if we are far enough
// along in the startup process.
//
//==========================================================================
void DecompressorBase::DecompressionError(const char *error, ...) const
{
const int MAX_ERRORTEXT = 300;
va_list argptr;
char errortext[MAX_ERRORTEXT];
va_start(argptr, error);
vsnprintf(errortext, MAX_ERRORTEXT, error, argptr);
va_end(argptr);
if (ErrorCallback != nullptr) ErrorCallback(errortext);
else throw std::runtime_error(errortext);
}
long DecompressorBase::Tell () const
{
DecompressionError("Cannot get position of decompressor stream");
return 0;
}
long DecompressorBase::Seek (long offset, int origin)
{
DecompressionError("Cannot seek in decompressor stream");
return 0;
}
char *DecompressorBase::Gets(char *strbuf, int len)
{
DecompressionError("Cannot use Gets on decompressor stream");
return nullptr;
}
void DecompressorBase::SetOwnsReader()
{
OwnedFile = std::move(*File);
File = &OwnedFile;
}
//==========================================================================
//
// DecompressorZ
//
// The zlib wrapper
// reads data from a ZLib compressed stream
//
//==========================================================================
class DecompressorZ : public DecompressorBase
{
enum { BUFF_SIZE = 4096 };
bool SawEOF;
z_stream Stream;
uint8_t InBuff[BUFF_SIZE];
public:
DecompressorZ (FileReader *file, bool zip, const std::function<void(const char*)>& cb)
: SawEOF(false)
{
int err;
File = file;
SetErrorCallback(cb);
FillBuffer ();
Stream.zalloc = Z_NULL;
Stream.zfree = Z_NULL;
if (!zip) err = inflateInit (&Stream);
else err = inflateInit2 (&Stream, -MAX_WBITS);
if (err != Z_OK)
{
DecompressionError ("DecompressorZ: inflateInit failed: %s\n", M_ZLibError(err).GetChars());
}
}
~DecompressorZ ()
{
inflateEnd (&Stream);
}
long Read (void *buffer, long len) override
{
int err;
Stream.next_out = (Bytef *)buffer;
Stream.avail_out = len;
do
{
err = inflate (&Stream, Z_SYNC_FLUSH);
if (Stream.avail_in == 0 && !SawEOF)
{
FillBuffer ();
}
} while (err == Z_OK && Stream.avail_out != 0);
if (err != Z_OK && err != Z_STREAM_END)
{
DecompressionError ("Corrupt zlib stream");
}
if (Stream.avail_out != 0)
{
DecompressionError ("Ran out of data in zlib stream");
}
return len - Stream.avail_out;
}
void FillBuffer ()
{
auto numread = File->Read (InBuff, BUFF_SIZE);
if (numread < BUFF_SIZE)
{
SawEOF = true;
}
Stream.next_in = InBuff;
Stream.avail_in = (uInt)numread;
}
};
//==========================================================================
//
// DecompressorZ
//
// The bzip2 wrapper
// reads data from a libbzip2 compressed stream
//
//==========================================================================
class DecompressorBZ2;
static DecompressorBZ2 * stupidGlobal; // Why does that dumb global error callback not pass the decompressor state?
// Thanks to that brain-dead interface we have to use a global variable to get the error to the proper handler.
class DecompressorBZ2 : public DecompressorBase
{
enum { BUFF_SIZE = 4096 };
bool SawEOF;
bz_stream Stream;
uint8_t InBuff[BUFF_SIZE];
public:
DecompressorBZ2 (FileReader *file, const std::function<void(const char*)>& cb)
: SawEOF(false)
{
int err;
File = file;
SetErrorCallback(cb);
stupidGlobal = this;
FillBuffer ();
Stream.bzalloc = NULL;
Stream.bzfree = NULL;
Stream.opaque = NULL;
err = BZ2_bzDecompressInit(&Stream, 0, 0);
if (err != BZ_OK)
{
DecompressionError ("DecompressorBZ2: bzDecompressInit failed: %d\n", err);
}
}
~DecompressorBZ2 ()
{
stupidGlobal = this;
BZ2_bzDecompressEnd (&Stream);
}
long Read (void *buffer, long len) override
{
int err;
stupidGlobal = this;
Stream.next_out = (char *)buffer;
Stream.avail_out = len;
do
{
err = BZ2_bzDecompress(&Stream);
if (Stream.avail_in == 0 && !SawEOF)
{
FillBuffer ();
}
} while (err == BZ_OK && Stream.avail_out != 0);
if (err != BZ_OK && err != BZ_STREAM_END)
{
DecompressionError ("Corrupt bzip2 stream");
}
if (Stream.avail_out != 0)
{
DecompressionError ("Ran out of data in bzip2 stream");
}
return len - Stream.avail_out;
}
void FillBuffer ()
{
auto numread = File->Read(InBuff, BUFF_SIZE);
if (numread < BUFF_SIZE)
{
SawEOF = true;
}
Stream.next_in = (char *)InBuff;
Stream.avail_in = (unsigned)numread;
}
};
//==========================================================================
//
// bz_internal_error
//
// libbzip2 wants this, since we build it with BZ_NO_STDIO set.
//
//==========================================================================
extern "C" void bz_internal_error (int errcode)
{
if (stupidGlobal) stupidGlobal->DecompressionError("libbzip2: internal error number %d\n", errcode);
else std::terminate();
}
//==========================================================================
//
// DecompressorLZMA
//
// The lzma wrapper
// reads data from a LZMA compressed stream
//
//==========================================================================
static void *SzAlloc(ISzAllocPtr, size_t size) { return malloc(size); }
static void SzFree(ISzAllocPtr, void *address) { free(address); }
ISzAlloc g_Alloc = { SzAlloc, SzFree };
// Wraps around a Decompressor to decompress a lzma stream
class DecompressorLZMA : public DecompressorBase
{
enum { BUFF_SIZE = 4096 };
bool SawEOF;
CLzmaDec Stream;
size_t Size;
size_t InPos, InSize;
size_t OutProcessed;
uint8_t InBuff[BUFF_SIZE];
public:
DecompressorLZMA (FileReader *file, size_t uncompressed_size, const std::function<void(const char*)>& cb)
: SawEOF(false)
{
uint8_t header[4 + LZMA_PROPS_SIZE];
int err;
File = file;
SetErrorCallback(cb);
Size = uncompressed_size;
OutProcessed = 0;
// Read zip LZMA properties header
if (File->Read(header, sizeof(header)) < (long)sizeof(header))
{
DecompressionError("DecompressorLZMA: File too short\n");
}
if (header[2] + header[3] * 256 != LZMA_PROPS_SIZE)
{
DecompressionError("DecompressorLZMA: LZMA props size is %d (expected %d)\n",
header[2] + header[3] * 256, LZMA_PROPS_SIZE);
}
FillBuffer();
LzmaDec_Construct(&Stream);
err = LzmaDec_Allocate(&Stream, header + 4, LZMA_PROPS_SIZE, &g_Alloc);
if (err != SZ_OK)
{
DecompressionError("DecompressorLZMA: LzmaDec_Allocate failed: %d\n", err);
}
LzmaDec_Init(&Stream);
}
~DecompressorLZMA ()
{
LzmaDec_Free(&Stream, &g_Alloc);
}
long Read (void *buffer, long len) override
{
int err;
Byte *next_out = (Byte *)buffer;
do
{
ELzmaFinishMode finish_mode = LZMA_FINISH_ANY;
ELzmaStatus status;
size_t out_processed = len;
size_t in_processed = InSize;
err = LzmaDec_DecodeToBuf(&Stream, next_out, &out_processed, InBuff + InPos, &in_processed, finish_mode, &status);
InPos += in_processed;
InSize -= in_processed;
next_out += out_processed;
len = (long)(len - out_processed);
if (err != SZ_OK)
{
DecompressionError ("Corrupt LZMA stream");
}
if (in_processed == 0 && out_processed == 0)
{
if (status != LZMA_STATUS_FINISHED_WITH_MARK)
{
DecompressionError ("Corrupt LZMA stream");
}
}
if (InSize == 0 && !SawEOF)
{
FillBuffer ();
}
} while (err == SZ_OK && len != 0);
if (err != Z_OK && err != Z_STREAM_END)
{
DecompressionError ("Corrupt LZMA stream");
}
if (len != 0)
{
DecompressionError ("Ran out of data in LZMA stream");
}
return (long)(next_out - (Byte *)buffer);
}
void FillBuffer ()
{
auto numread = File->Read(InBuff, BUFF_SIZE);
if (numread < BUFF_SIZE)
{
SawEOF = true;
}
InPos = 0;
InSize = numread;
}
};
//==========================================================================
//
// Console Doom LZSS wrapper.
//
//==========================================================================
class DecompressorLZSS : public DecompressorBase
{
enum { BUFF_SIZE = 4096, WINDOW_SIZE = 4096, INTERNAL_BUFFER_SIZE = 128 };
bool SawEOF;
uint8_t InBuff[BUFF_SIZE];
enum StreamState
{
STREAM_EMPTY,
STREAM_BITS,
STREAM_FLUSH,
STREAM_FINAL
};
struct
{
StreamState State;
uint8_t *In;
unsigned int AvailIn;
unsigned int InternalOut;
uint8_t CFlags, Bits;
uint8_t Window[WINDOW_SIZE+INTERNAL_BUFFER_SIZE];
const uint8_t *WindowData;
uint8_t *InternalBuffer;
} Stream;
void FillBuffer()
{
if(Stream.AvailIn)
memmove(InBuff, Stream.In, Stream.AvailIn);
auto numread = File->Read(InBuff+Stream.AvailIn, BUFF_SIZE-Stream.AvailIn);
if (numread < BUFF_SIZE)
{
SawEOF = true;
}
Stream.In = InBuff;
Stream.AvailIn = (unsigned)numread+Stream.AvailIn;
}
// Reads a flag byte.
void PrepareBlocks()
{
assert(Stream.InternalBuffer == Stream.WindowData);
Stream.CFlags = *Stream.In++;
--Stream.AvailIn;
Stream.Bits = 0xFF;
Stream.State = STREAM_BITS;
}
// Reads the next chunk in the block. Returns true if successful and
// returns false if it ran out of input data.
bool UncompressBlock()
{
if(Stream.CFlags & 1)
{
// Check to see if we have enough input
if(Stream.AvailIn < 2)
return false;
Stream.AvailIn -= 2;
uint16_t pos = BigShort(*(uint16_t*)Stream.In);
uint8_t len = (pos & 0xF)+1;
pos >>= 4;
Stream.In += 2;
if(len == 1)
{
// We've reached the end of the stream.
Stream.State = STREAM_FINAL;
return true;
}
const uint8_t* copyStart = Stream.InternalBuffer-pos-1;
// Complete overlap: Single byte repeated
if(pos == 0)
memset(Stream.InternalBuffer, *copyStart, len);
// No overlap: One copy
else if(pos >= len)
memcpy(Stream.InternalBuffer, copyStart, len);
else
{
// Partial overlap: Copy in 2 or 3 chunks.
do
{
unsigned int copy = std::min<unsigned int>(len, pos+1);
memcpy(Stream.InternalBuffer, copyStart, copy);
Stream.InternalBuffer += copy;
Stream.InternalOut += copy;
len -= copy;
pos += copy; // Increase our position since we can copy twice as much the next round.
}
while(len);
}
Stream.InternalOut += len;
Stream.InternalBuffer += len;
}
else
{
// Uncompressed byte.
*Stream.InternalBuffer++ = *Stream.In++;
--Stream.AvailIn;
++Stream.InternalOut;
}
Stream.CFlags >>= 1;
Stream.Bits >>= 1;
// If we're done with this block, flush the output
if(Stream.Bits == 0)
Stream.State = STREAM_FLUSH;
return true;
}
public:
DecompressorLZSS(FileReader *file, const std::function<void(const char*)>& cb) : SawEOF(false)
{
File = file;
SetErrorCallback(cb);
Stream.State = STREAM_EMPTY;
Stream.WindowData = Stream.InternalBuffer = Stream.Window+WINDOW_SIZE;
Stream.InternalOut = 0;
Stream.AvailIn = 0;
FillBuffer();
}
~DecompressorLZSS()
{
}
long Read(void *buffer, long len) override
{
uint8_t *Out = (uint8_t*)buffer;
long AvailOut = len;
do
{
while(Stream.AvailIn)
{
if(Stream.State == STREAM_EMPTY)
PrepareBlocks();
else if(Stream.State == STREAM_BITS && !UncompressBlock())
break;
else
break;
}
unsigned int copy = std::min<unsigned int>(Stream.InternalOut, AvailOut);
if(copy > 0)
{
memcpy(Out, Stream.WindowData, copy);
Out += copy;
AvailOut -= copy;
// Slide our window
memmove(Stream.Window, Stream.Window+copy, WINDOW_SIZE+INTERNAL_BUFFER_SIZE-copy);
Stream.InternalBuffer -= copy;
Stream.InternalOut -= copy;
}
if(Stream.State == STREAM_FINAL)
break;
if(Stream.InternalOut == 0 && Stream.State == STREAM_FLUSH)
Stream.State = STREAM_EMPTY;
if(Stream.AvailIn < 2)
FillBuffer();
}
while(AvailOut && Stream.State != STREAM_FINAL);
assert(AvailOut == 0);
return (long)(Out - (uint8_t*)buffer);
}
};
bool FileReader::OpenDecompressor(FileReader &parent, Size length, int method, bool seekable, const std::function<void(const char*)>& cb)
{
DecompressorBase *dec = nullptr;
FileReader *p = &parent;
switch (method & ~METHOD_TRANSFEROWNER)
{
case METHOD_DEFLATE:
case METHOD_ZLIB:
dec = new DecompressorZ(p, method == METHOD_DEFLATE, cb);
break;
case METHOD_BZIP2:
dec = new DecompressorBZ2(p, cb);
break;
case METHOD_LZMA:
dec = new DecompressorLZMA(p, length, cb);
break;
case METHOD_LZSS:
dec = new DecompressorLZSS(p, cb);
break;
// todo: METHOD_IMPLODE, METHOD_SHRINK
default:
return false;
}
if (method & METHOD_TRANSFEROWNER)
{
dec->SetOwnsReader();
}
dec->Length = (long)length;
if (!seekable)
{
Close();
mReader = dec;
return true;
}
else
{
// todo: create a wrapper. for now this fails
delete dec;
return false;
}
}

View file

@ -0,0 +1,190 @@
/*
** findfile.cpp
** Warpper around the native directory scanning API
**
**---------------------------------------------------------------------------
** Copyright 1998-2016 Randy Heit
** Copyright 2005-2020 Christoph Oelckers
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "findfile.h"
#include "zstring.h"
#ifndef _WIN32
#include <fnmatch.h>
static const char *pattern;
#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED < 1080
static int matchfile(struct dirent *ent)
#else
static int matchfile(const struct dirent *ent)
#endif
{
return fnmatch(pattern, ent->d_name, FNM_NOESCAPE) == 0;
}
void *I_FindFirst(const char *const filespec, findstate_t *const fileinfo)
{
FString dir;
const char *const slash = strrchr(filespec, '/');
if (slash)
{
pattern = slash + 1;
dir = FString(filespec, slash - filespec + 1);
fileinfo->path = dir;
}
else
{
pattern = filespec;
dir = ".";
}
fileinfo->current = 0;
fileinfo->count = scandir(dir.GetChars(), &fileinfo->namelist, matchfile, alphasort);
if (fileinfo->count > 0)
{
return fileinfo;
}
return (void *)-1;
}
int I_FindNext(void *const handle, findstate_t *const fileinfo)
{
findstate_t *const state = static_cast<findstate_t *>(handle);
if (state->current < fileinfo->count)
{
return ++state->current < fileinfo->count ? 0 : -1;
}
return -1;
}
int I_FindClose(void *const handle)
{
findstate_t *const state = static_cast<findstate_t *>(handle);
if (handle != (void *)-1 && state->count > 0)
{
for (int i = 0; i < state->count; ++i)
{
free(state->namelist[i]);
}
free(state->namelist);
state->namelist = nullptr;
state->count = 0;
}
return 0;
}
int I_FindAttr(findstate_t *const fileinfo)
{
dirent *const ent = fileinfo->namelist[fileinfo->current];
const FString path = fileinfo->path + ent->d_name;
bool isdir;
if (DirEntryExists(path, &isdir))
{
return isdir ? FA_DIREC : 0;
}
return 0;
}
#else
#include <windows.h>
//==========================================================================
//
// I_FindFirst
//
// Start a pattern matching sequence.
//
//==========================================================================
void *I_FindFirst(const char *filespec, findstate_t *fileinfo)
{
static_assert(sizeof(WIN32_FIND_DATAW) == sizeof(fileinfo->FindData), "FindData size mismatch");
auto widespec = WideString(filespec);
fileinfo->UTF8Name = "";
return FindFirstFileW(widespec.c_str(), (LPWIN32_FIND_DATAW)&fileinfo->FindData);
}
//==========================================================================
//
// I_FindNext
//
// Return the next file in a pattern matching sequence.
//
//==========================================================================
int I_FindNext(void *handle, findstate_t *fileinfo)
{
fileinfo->UTF8Name = "";
return !FindNextFileW((HANDLE)handle, (LPWIN32_FIND_DATAW)&fileinfo->FindData);
}
//==========================================================================
//
// I_FindClose
//
// Finish a pattern matching sequence.
//
//==========================================================================
int I_FindClose(void *handle)
{
return FindClose((HANDLE)handle);
}
//==========================================================================
//
// I_FindName
//
// Returns the name for an entry
//
//==========================================================================
const char *I_FindName(findstate_t *fileinfo)
{
if (fileinfo->UTF8Name.IsEmpty()) fileinfo->UTF8Name = fileinfo->FindData.Name;
return fileinfo->UTF8Name.GetChars();
}
#endif

View file

@ -0,0 +1,85 @@
#pragma once
// Directory searching routines
#include <stdint.h>
#include "zstring.h"
#ifndef _WIN32
struct findstate_t
{
private:
FString path;
struct dirent **namelist;
int current;
int count;
friend void *I_FindFirst(const char *filespec, findstate_t *fileinfo);
friend int I_FindNext(void *handle, findstate_t *fileinfo);
friend const char *I_FindName(findstate_t *fileinfo);
friend int I_FindAttr(findstate_t *fileinfo);
friend int I_FindClose(void *handle);
};
int I_FindAttr (findstate_t *fileinfo);
inline const char *I_FindName(findstate_t *fileinfo)
{
return (fileinfo->namelist[fileinfo->current]->d_name);
}
#define FA_RDONLY 1
#define FA_HIDDEN 2
#define FA_SYSTEM 4
#define FA_DIREC 8
#define FA_ARCH 16
#else
// Mirror WIN32_FIND_DATAW in <winbase.h>
struct findstate_t
{
private:
struct FileTime
{
uint32_t lo, hi;
};
struct WinData
{
uint32_t Attribs;
FileTime Times[3];
uint32_t Size[2];
uint32_t Reserved[2];
wchar_t Name[260];
wchar_t AltName[14];
};
WinData FindData;
FString UTF8Name;
friend void *I_FindFirst(const char *filespec, findstate_t *fileinfo);
friend int I_FindNext(void *handle, findstate_t *fileinfo);
friend const char *I_FindName(findstate_t *fileinfo);
friend int I_FindAttr(findstate_t *fileinfo);
};
const char *I_FindName(findstate_t *fileinfo);
inline int I_FindAttr(findstate_t *fileinfo)
{
return fileinfo->FindData.Attribs;
}
#define FA_RDONLY 0x00000001
#define FA_HIDDEN 0x00000002
#define FA_SYSTEM 0x00000004
#define FA_DIREC 0x00000010
#define FA_ARCH 0x00000020
#endif
void *I_FindFirst (const char *filespec, findstate_t *fileinfo);
int I_FindNext (void *handle, findstate_t *fileinfo);
int I_FindClose (void *handle);

1643
src/common/utility/tarray.h Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,479 @@
#pragma once
/*
** zstring.h
**
**---------------------------------------------------------------------------
** Copyright 2005-2007 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stddef.h>
#include <string>
#include "tarray.h"
#ifdef __GNUC__
#define PRINTFISH(x) __attribute__((format(printf, 2, x)))
#else
#define PRINTFISH(x)
#endif
#ifdef __GNUC__
#define IGNORE_FORMAT_PRE \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wformat\"") \
_Pragma("GCC diagnostic ignored \"-Wformat-extra-args\"")
#define IGNORE_FORMAT_POST _Pragma("GCC diagnostic pop")
#else
#define IGNORE_FORMAT_PRE
#define IGNORE_FORMAT_POST
#endif
#ifdef _WIN32
std::wstring WideString(const char *);
#endif
struct FStringData
{
unsigned int Len; // Length of string, excluding terminating null
unsigned int AllocLen; // Amount of memory allocated for string
int RefCount; // < 0 means it's locked
// char StrData[xxx];
char *Chars()
{
return (char *)(this + 1);
}
const char *Chars() const
{
return (const char *)(this + 1);
}
char *AddRef()
{
if (RefCount < 0)
{
return (char *)(MakeCopy() + 1);
}
else
{
RefCount++;
return (char *)(this + 1);
}
}
void Release()
{
assert (RefCount != 0);
if (--RefCount <= 0)
{
Dealloc();
}
}
FStringData *MakeCopy();
static FStringData *Alloc (size_t strlen);
FStringData *Realloc (size_t newstrlen);
void Dealloc ();
};
struct FNullStringData
{
unsigned int Len;
unsigned int AllocLen;
int RefCount;
char Nothing[2];
};
enum ELumpNum
{
};
class FString
{
public:
FString () { ResetToNull(); }
// Copy constructors
FString (const FString &other) { AttachToOther (other); }
FString (FString &&other) : Chars(other.Chars) { other.ResetToNull(); }
FString (const char *copyStr);
FString (const char *copyStr, size_t copyLen);
FString (char oneChar);
FString(const TArray<char> & source) : FString(source.Data(), source.Size()) {}
FString(const TArray<uint8_t> & source) : FString((char*)source.Data(), source.Size()) {}
// This is intentionally #ifdef'd. The only code which needs this is parts of the Windows backend that receive Unicode text from the system.
#ifdef _WIN32
explicit FString(const wchar_t *copyStr);
FString &operator = (const wchar_t *copyStr);
std::wstring WideString() const { return ::WideString(Chars); }
#endif
// Concatenation constructors
FString (const FString &head, const FString &tail);
FString (const FString &head, const char *tail);
FString (const FString &head, char tail);
FString (const char *head, const FString &tail);
FString (const char *head, const char *tail);
FString (char head, const FString &tail);
// Other constructors
FString (ELumpNum); // Create from a lump
~FString ();
// Discard string's contents, create a new buffer, and lock it.
char *LockNewBuffer(size_t len);
char *LockBuffer(); // Obtain write access to the character buffer
void UnlockBuffer(); // Allow shared access to the character buffer
void Swap(FString &other)
{
std::swap(Chars, other.Chars);
}
operator const char *() const { return Chars; }
const char *GetChars() const { return Chars; }
const char &operator[] (int index) const { return Chars[index]; }
#if defined(_WIN32) && !defined(_WIN64) && defined(_MSC_VER)
// Compiling 32-bit Windows source with MSVC: size_t is typedefed to an
// unsigned int with the 64-bit portability warning attribute, so the
// prototype cannot substitute unsigned int for size_t, or you get
// spurious warnings.
const char &operator[] (size_t index) const { return Chars[index]; }
#else
const char &operator[] (unsigned int index) const { return Chars[index]; }
#endif
const char &operator[] (unsigned long index) const { return Chars[index]; }
const char &operator[] (unsigned long long index) const { return Chars[index]; }
FString &operator = (const FString &other);
FString &operator = (FString &&other);
FString &operator = (const char *copyStr);
FString operator + (const FString &tail) const;
FString operator + (const char *tail) const;
FString operator + (char tail) const;
friend FString operator + (const char *head, const FString &tail);
friend FString operator + (char head, const FString &tail);
FString &operator += (const FString &tail);
FString &operator += (const char *tail);
FString &operator += (char tail);
FString &AppendCStrPart (const char *tail, size_t tailLen);
FString &CopyCStrPart(const char *tail, size_t tailLen);
FString &operator << (const FString &tail) { return *this += tail; }
FString &operator << (const char *tail) { return *this += tail; }
FString &operator << (char tail) { return *this += tail; }
const char &Front() const { assert(IsNotEmpty()); return Chars[0]; }
const char &Back() const { assert(IsNotEmpty()); return Chars[Len() - 1]; }
FString Left (size_t numChars) const;
FString Right (size_t numChars) const;
FString Mid (size_t pos, size_t numChars = ~(size_t)0) const;
void AppendCharacter(int codepoint);
void DeleteLastCharacter();
long IndexOf (const FString &substr, long startIndex=0) const;
long IndexOf (const char *substr, long startIndex=0) const;
long IndexOf (char subchar, long startIndex=0) const;
long IndexOfAny (const FString &charset, long startIndex=0) const;
long IndexOfAny (const char *charset, long startIndex=0) const;
// This is only kept for backwards compatibility with old ZScript versions that used this function and depend on its bug.
long LastIndexOf (char subchar) const;
long LastIndexOfBroken (const FString &substr, long endIndex) const;
long LastIndexOf (char subchar, long endIndex) const;
long LastIndexOfAny (const FString &charset) const;
long LastIndexOfAny (const char *charset) const;
long LastIndexOfAny (const FString &charset, long endIndex) const;
long LastIndexOfAny (const char *charset, long endIndex) const;
long LastIndexOf (const FString &substr) const;
long LastIndexOf (const FString &substr, long endIndex) const;
long LastIndexOf (const char *substr) const;
long LastIndexOf (const char *substr, long endIndex) const;
long LastIndexOf (const char *substr, long endIndex, size_t substrlen) const;
void ToUpper ();
void ToLower ();
FString MakeUpper() const;
FString MakeLower() const;
void StripLeft ();
void StripLeft (const FString &charset);
void StripLeft (const char *charset);
void StripRight ();
void StripRight (const FString &charset);
void StripRight (const char *charset);
void StripLeftRight ();
void StripLeftRight (const FString &charset);
void StripLeftRight (const char *charset);
void Insert (size_t index, const FString &instr);
void Insert (size_t index, const char *instr);
void Insert (size_t index, const char *instr, size_t instrlen);
template<typename Func>
void ReplaceChars (Func IsOldChar, char newchar)
{
size_t i, j;
LockBuffer();
for (i = 0, j = Len(); i < j; ++i)
{
if (IsOldChar(Chars[i]))
{
Chars[i] = newchar;
}
}
UnlockBuffer();
}
void ReplaceChars (char oldchar, char newchar);
void ReplaceChars (const char *oldcharset, char newchar);
template<typename Func>
void StripChars (Func IsKillChar)
{
size_t read, write, mylen;
LockBuffer();
for (read = write = 0, mylen = Len(); read < mylen; ++read)
{
if (!IsKillChar(Chars[read]))
{
Chars[write++] = Chars[read];
}
}
Chars[write] = '\0';
ReallocBuffer (write);
UnlockBuffer();
}
void StripChars (char killchar);
void StripChars (const char *killcharset);
void MergeChars (char merger);
void MergeChars (char merger, char newchar);
void MergeChars (const char *charset, char newchar);
void Substitute (const FString &oldstr, const FString &newstr);
void Substitute (const char *oldstr, const FString &newstr);
void Substitute (const FString &oldstr, const char *newstr);
void Substitute (const char *oldstr, const char *newstr);
void Substitute (const char *oldstr, const char *newstr, size_t oldstrlen, size_t newstrlen);
void Format (const char *fmt, ...) PRINTFISH(3);
void AppendFormat (const char *fmt, ...) PRINTFISH(3);
void VFormat (const char *fmt, va_list arglist) PRINTFISH(0);
void VAppendFormat (const char *fmt, va_list arglist) PRINTFISH(0);
bool IsInt () const;
bool IsFloat () const;
int64_t ToLong (int base=0) const;
uint64_t ToULong (int base=0) const;
double ToDouble () const;
size_t Len() const { return Data()->Len; }
size_t CharacterCount() const;
int GetNextCharacter(int &position) const;
bool IsEmpty() const { return Len() == 0; }
bool IsNotEmpty() const { return Len() != 0; }
void Truncate (size_t newlen);
void Remove(size_t index, size_t remlen);
int Compare (const FString &other) const { return strcmp (Chars, other.Chars); }
int Compare (const char *other) const { return strcmp (Chars, other); }
int Compare(const FString &other, int len) const { return strncmp(Chars, other.Chars, len); }
int Compare(const char *other, int len) const { return strncmp(Chars, other, len); }
int CompareNoCase (const FString &other) const { return stricmp (Chars, other.Chars); }
int CompareNoCase (const char *other) const { return stricmp (Chars, other); }
int CompareNoCase(const FString &other, int len) const { return strnicmp(Chars, other.Chars, len); }
int CompareNoCase(const char *other, int len) const { return strnicmp(Chars, other, len); }
enum EmptyTokenType
{
TOK_SKIPEMPTY = 0,
TOK_KEEPEMPTY = 1,
};
TArray<FString> Split(const FString &delimiter, EmptyTokenType keepEmpty = TOK_KEEPEMPTY) const;
TArray<FString> Split(const char *delimiter, EmptyTokenType keepEmpty = TOK_KEEPEMPTY) const;
void Split(TArray<FString>& tokens, const FString &delimiter, EmptyTokenType keepEmpty = TOK_KEEPEMPTY) const;
void Split(TArray<FString>& tokens, const char *delimiter, EmptyTokenType keepEmpty = TOK_KEEPEMPTY) const;
protected:
const FStringData *Data() const { return (FStringData *)Chars - 1; }
FStringData *Data() { return (FStringData *)Chars - 1; }
void ResetToNull()
{
NullString.RefCount++;
Chars = &NullString.Nothing[0];
}
void AttachToOther (const FString &other);
void AllocBuffer (size_t len);
void ReallocBuffer (size_t newlen);
static int FormatHelper (void *data, const char *str, int len);
static void StrCopy (char *to, const char *from, size_t len);
static void StrCopy (char *to, const FString &from);
char *Chars;
static FNullStringData NullString;
friend struct FStringData;
public:
bool operator == (const FString &other) const
{
return Compare(other) == 0;
}
bool operator != (const FString &other) const
{
return Compare(other) != 0;
}
bool operator < (const FString &other) const
{
return Compare(other) < 0;
}
bool operator > (const FString &other) const
{
return Compare(other) > 0;
}
bool operator <= (const FString &other) const
{
return Compare(other) <= 0;
}
bool operator >= (const FString &other) const
{
return Compare(other) >= 0;
}
// These are needed to block the default char * conversion operator from making a mess.
bool operator == (const char *) const = delete;
bool operator != (const char *) const = delete;
bool operator < (const char *) const = delete;
bool operator > (const char *) const = delete;
bool operator <= (const char *) const = delete;
bool operator >= (const char *) const = delete;
private:
};
// These are also needed to block the default char * conversion operator from making a mess.
bool operator == (const char *, const FString &) = delete;
bool operator != (const char *, const FString &) = delete;
bool operator < (const char *, const FString &) = delete;
bool operator > (const char *, const FString &) = delete;
bool operator <= (const char *, const FString &) = delete;
bool operator >= (const char *, const FString &) = delete;
class FStringf : public FString
{
public:
FStringf(const char *fmt, ...);
};
namespace StringFormat
{
enum
{
// Format specification flags
F_MINUS = 1,
F_PLUS = 2,
F_ZERO = 4,
F_BLANK = 8,
F_HASH = 16,
F_SIGNED = 32,
F_NEGATIVE = 64,
F_ZEROVALUE = 128,
F_FPT = 256,
// Format specification size prefixes
F_HALFHALF = 0x1000, // hh
F_HALF = 0x2000, // h
F_LONG = 0x3000, // l
F_LONGLONG = 0x4000, // ll or I64
F_BIGI = 0x5000, // I
F_PTRDIFF = 0x6000, // t
F_SIZE = 0x7000, // z
};
typedef int (*OutputFunc)(void *data, const char *str, int len);
int VWorker (OutputFunc output, void *outputData, const char *fmt, va_list arglist);
int Worker (OutputFunc output, void *outputData, const char *fmt, ...);
};
#undef PRINTFISH
// Hash FStrings on their contents. (used by TMap)
#include "superfasthash.h"
template<> struct THashTraits<FString>
{
hash_t Hash(const FString &key) { return (hash_t)SuperFastHash(key.GetChars(), key.Len()); }
// Compares two keys, returning zero if they are the same.
int Compare(const FString &left, const FString &right) { return left.Compare(right); }
};
struct StringNoCaseHashTraits
{
hash_t Hash(const FString& key) { return (hash_t)SuperFastHashI(key.GetChars(), key.Len()); }
// Compares two keys, returning zero if they are the same.
int Compare(const FString& left, const FString& right) { return left.CompareNoCase(right); }
};