Tier0: binary io stream class refactor

Class has been reworked to always take the reverve seek amount into account when adding to output size. Previously, we still incremented the output size even when we seeked back and modified data rather than appending to the end.

The manual write size calculation was a design choice as seeking and calling tellp is slow.

The enum has also been slightly reworked by removing the BINARY enumerant, and adding a new mode which allows you to open a stream in read/write mode.
This commit is contained in:
Kawe Mazidjatari 2025-01-01 22:44:43 +01:00
parent 4ffcc4ca75
commit b75655c101
7 changed files with 283 additions and 116 deletions

View File

@ -3,32 +3,39 @@
class CIOStream
{
public:
enum Mode_t
enum class Mode_e
{
NONE = 0,
READ = std::ios::in,
WRITE = std::ios::out,
BINARY = std::ios::binary,
None = 0,
Read,
Write,
ReadWrite, // For existing files only.
ReadWriteCreate
};
CIOStream();
~CIOStream();
bool Open(const char* const pFilePath, const int nFlags);
bool Open(const char* const filePath, const Mode_e mode);
inline bool Open(const std::string& filePath, const Mode_e mode) { return Open(filePath.c_str(), mode); };
void Close();
void Reset();
void Flush();
std::streampos TellGet();
std::streampos TellPut();
std::streamoff TellGet();
std::streamoff TellPut();
void SeekGet(const std::streampos nOffset);
void SeekPut(const std::streampos nOffset);
void Seek(const std::streampos nOffset);
void SeekGet(const std::streamoff offset, const std::ios_base::seekdir way = std::ios::beg);
void SeekPut(const std::streamoff offset, const std::ios_base::seekdir way = std::ios::beg);
void Seek(const std::streamoff offset, const std::ios_base::seekdir way = std::ios::beg);
const std::filebuf* GetData() const;
const std::streampos GetSize() const;
const std::streamoff GetSize() const;
bool IsReadable();
bool IsReadMode() const;
bool IsWriteMode() const;
bool IsReadable() const;
bool IsWritable() const;
bool IsEof() const;
@ -37,39 +44,39 @@ public:
// Purpose: reads any value from the file
//-----------------------------------------------------------------------------
template<typename T>
void Read(T& tValue)
inline void Read(T& value)
{
if (IsReadable())
m_Stream.read(reinterpret_cast<char*>(&tValue), sizeof(tValue));
m_stream.read(reinterpret_cast<char*>(&value), sizeof(value));
}
//-----------------------------------------------------------------------------
// Purpose: reads any value from the file with specified size
//-----------------------------------------------------------------------------
template<typename T>
void Read(T* tValue, const size_t nSize)
inline void Read(T* const value, const size_t size)
{
if (IsReadable())
m_Stream.read(reinterpret_cast<char*>(tValue), nSize);
m_stream.read(reinterpret_cast<char*>(value), size);
}
template<typename T>
void Read(T& tValue, const size_t nSize)
inline void Read(T& value, const size_t size)
{
if (IsReadable())
m_Stream.read(reinterpret_cast<char*>(&tValue), nSize);
m_stream.read(reinterpret_cast<char*>(&value), size);
}
//-----------------------------------------------------------------------------
// Purpose: reads any value from the file and returns it
//-----------------------------------------------------------------------------
template<typename T>
T Read()
inline T Read()
{
T value{};
if (!IsReadable())
return value;
m_Stream.read(reinterpret_cast<char*>(&value), sizeof(value));
m_stream.read(reinterpret_cast<char*>(&value), sizeof(value));
return value;
}
bool ReadString(std::string& svOut);
@ -79,31 +86,40 @@ public:
// Purpose: writes any value to the file
//-----------------------------------------------------------------------------
template<typename T>
void Write(T tValue)
inline void Write(const T& value)
{
if (!IsWritable())
return;
m_Stream.write(reinterpret_cast<const char*>(&tValue), sizeof(tValue));
m_nSize += sizeof(tValue);
const size_t count = sizeof(value);
m_stream.write(reinterpret_cast<const char*>(&value), count);
CalcAddDelta(count);
}
//-----------------------------------------------------------------------------
// Purpose: writes any value to the file with specified size
//-----------------------------------------------------------------------------
template<typename T>
void Write(T* tValue, size_t nSize)
inline void Write(const T* const value, const size_t size)
{
if (!IsWritable())
return;
m_Stream.write(reinterpret_cast<const char*>(tValue), nSize);
m_nSize += nSize;
m_stream.write(reinterpret_cast<const char*>(value), size);
CalcAddDelta(size);
}
bool WriteString(const std::string& svInput);
bool WriteString(const std::string& svInput, const bool nullterminate);
void Pad(const size_t count);
protected:
void CalcAddDelta(const size_t count);
void CalcSkipDelta(const std::streamoff offset, const std::ios_base::seekdir way);
private:
std::streampos m_nSize; // File size.
int m_nFlags; // Stream flags.
std::fstream m_Stream; // I/O stream.
std::fstream m_stream; // I/O stream.
std::streamoff m_size; // File size.
std::streamoff m_skip; // Amount skipped back.
std::ios_base::openmode m_flags; // Stream flags.
Mode_e m_mode; // Stream mode.
};

View File

@ -807,7 +807,7 @@ bool Pak_DecodePakFile(const char* const inPakFile, const char* const outPakFile
CIOStream inPakStream;
if (!inPakStream.Open(inPakFile, CIOStream::READ | CIOStream::BINARY))
if (!inPakStream.Open(inPakFile, CIOStream::Mode_e::Read))
{
Error(eDLL_T::RTECH, NO_ERROR, "%s: failed to open pak file '%s' for read!\n",
__FUNCTION__, inPakFile);
@ -817,7 +817,7 @@ bool Pak_DecodePakFile(const char* const inPakFile, const char* const outPakFile
CIOStream outPakStream;
if (!outPakStream.Open(outPakFile, CIOStream::WRITE | CIOStream::BINARY))
if (!outPakStream.Open(outPakFile, CIOStream::Mode_e::Write))
{
Error(eDLL_T::RTECH, NO_ERROR, "%s: failed to open pak file '%s' for write!\n",
__FUNCTION__, outPakFile);

View File

@ -84,7 +84,7 @@ bool Pak_EncodePakFile(const char* const inPakFile, const char* const outPakFile
CIOStream inPakStream;
if (!inPakStream.Open(inPakFile, CIOStream::READ | CIOStream::BINARY))
if (!inPakStream.Open(inPakFile, CIOStream::Mode_e::Read))
{
Error(eDLL_T::RTECH, NO_ERROR, "%s: failed to open pak file '%s' for read!\n",
__FUNCTION__, inPakFile);
@ -94,7 +94,7 @@ bool Pak_EncodePakFile(const char* const inPakFile, const char* const outPakFile
CIOStream outPakStream;
if (!outPakStream.Open(outPakFile, CIOStream::WRITE | CIOStream::BINARY))
if (!outPakStream.Open(outPakFile, CIOStream::Mode_e::Write))
{
Error(eDLL_T::RTECH, NO_ERROR, "%s: failed to open pak file '%s' for write!\n",
__FUNCTION__, outPakFile);

View File

@ -365,7 +365,7 @@ bool Pak_UpdatePatchHeaders(uint8_t* const inBuf, const char* const outPakFile)
// unable to open patch while there should be one, we must calculate
// new file sizes here, or else the runtime would fail to load them
if (!inPatch.Open(patchFile, CIOStream::READ | CIOStream::BINARY))
if (!inPatch.Open(patchFile, CIOStream::Mode_e::Read))
return false;
const size_t fileSize = inPatch.GetSize();

View File

@ -303,7 +303,7 @@ void CLauncher::SetupLaunchContext(const char* szConfig, const char* szGameDll,
{
cfgFileName.Format(GAME_CFG_PATH"%s", szConfig);
if (cfgFile.Open(cfgFileName.String(), CIOStream::READ))
if (cfgFile.Open(cfgFileName.String(), CIOStream::Mode_e::Read))
{
if (!cfgFile.ReadString(commandLine.Access(), commandLine.GetMaxLength()))
{

View File

@ -6,8 +6,7 @@
//-----------------------------------------------------------------------------
CIOStream::CIOStream()
{
m_nSize = 0;
m_nFlags = Mode_t::NONE;
Reset();
}
//-----------------------------------------------------------------------------
@ -15,53 +14,85 @@ CIOStream::CIOStream()
//-----------------------------------------------------------------------------
CIOStream::~CIOStream()
{
if (m_Stream.is_open())
m_Stream.close();
if (m_Stream.is_open())
m_Stream.close();
Close();
}
//-----------------------------------------------------------------------------
// Purpose: get internal stream mode from selected mode
//-----------------------------------------------------------------------------
static std::ios_base::openmode GetInternalStreamMode(const CIOStream::Mode_e mode)
{
switch (mode)
{
case CIOStream::Mode_e::Read:
return (std::ios::in | std::ios::binary);
case CIOStream::Mode_e::Write:
return (std::ios::out | std::ios::binary);
case CIOStream::Mode_e::ReadWrite:
return (std::ios::in | std::ios::out | std::ios::binary);
case CIOStream::Mode_e::ReadWriteCreate:
return (std::ios::in | std::ios::out | std::ios::binary | std::ios::trunc);
}
assert(0); // code bug, can never reach this.
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: opens the file in specified mode
// Input : *pFilePath -
// nFlags -
// Input : *filePath -
// mode -
// Output : true if operation is successful
//-----------------------------------------------------------------------------
bool CIOStream::Open(const char* const pFilePath, const int nFlags)
bool CIOStream::Open(const char* const filePath, const Mode_e mode)
{
m_nFlags = nFlags;
m_flags = GetInternalStreamMode(mode);
m_mode = mode;
if (m_Stream.is_open())
if (m_stream.is_open())
{
m_Stream.close();
m_stream.close();
}
m_Stream.open(pFilePath, nFlags);
if (!m_Stream.is_open() || !m_Stream.good())
m_stream.open(filePath, GetInternalStreamMode(mode));
if (!m_stream.is_open() || !m_stream.good())
{
m_nFlags = Mode_t::NONE;
return false;
}
if (nFlags & Mode_t::READ)
if (IsReadMode())
{
struct _stat64 status;
if (_stat64(pFilePath, &status) != NULL)
if (_stat64(filePath, &status) != NULL)
{
return false;
}
m_nSize = status.st_size;
m_size = status.st_size;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: resets the state
//-----------------------------------------------------------------------------
void CIOStream::Reset()
{
m_size = 0;
m_skip = 0;
m_mode = Mode_e::None;
m_flags = 0;
}
//-----------------------------------------------------------------------------
// Purpose: closes the stream
//-----------------------------------------------------------------------------
void CIOStream::Close()
{
m_Stream.close();
m_stream.close();
Reset();
}
//-----------------------------------------------------------------------------
@ -70,67 +101,88 @@ void CIOStream::Close()
void CIOStream::Flush()
{
if (IsWritable())
m_Stream.flush();
m_stream.flush();
}
//-----------------------------------------------------------------------------
// Purpose: gets the position of the current character in the stream
// Input : mode -
// Output : std::streampos
//-----------------------------------------------------------------------------
std::streampos CIOStream::TellGet()
std::streamoff CIOStream::TellGet()
{
return m_Stream.tellg();
assert(IsReadMode());
return m_stream.tellg();
}
std::streampos CIOStream::TellPut()
std::streamoff CIOStream::TellPut()
{
return m_Stream.tellp();
assert(IsWriteMode());
return m_stream.tellp();
}
//-----------------------------------------------------------------------------
// Purpose: sets the position of the current character in the stream
// Input : nOffset -
// mode -
// Input : offset -
// way -
//-----------------------------------------------------------------------------
void CIOStream::SeekGet(const std::streampos nOffset)
void CIOStream::SeekGet(const std::streamoff offset, const std::ios_base::seekdir way)
{
m_Stream.seekg(nOffset, std::ios::beg);
assert(IsReadMode());
m_stream.seekg(offset, way);
}
void CIOStream::SeekPut(const std::streampos nOffset)
//-----------------------------------------------------------------------------
// NOTE: if you seek beyond the end of the file to try and pad it out, use the
// Pad() method instead as the behavior of seek is operating system dependent
//-----------------------------------------------------------------------------
void CIOStream::SeekPut(const std::streamoff offset, const std::ios_base::seekdir way)
{
m_Stream.seekp(nOffset, std::ios::beg);
assert(IsWriteMode());
CalcSkipDelta(offset, way);
m_stream.seekp(offset, way);
}
void CIOStream::Seek(const std::streampos nOffset)
void CIOStream::Seek(const std::streamoff offset, const std::ios_base::seekdir way)
{
SeekGet(nOffset);
SeekPut(nOffset);
if (IsReadMode())
SeekGet(offset, way);
if (IsWriteMode())
SeekPut(offset, way);
}
//-----------------------------------------------------------------------------
// Purpose: returns the data (ifstream only)
// Purpose: returns the data
// Output : std::filebuf*
//-----------------------------------------------------------------------------
const std::filebuf* CIOStream::GetData() const
{
return m_Stream.rdbuf();
return m_stream.rdbuf();
}
//-----------------------------------------------------------------------------
// Purpose: returns the data size (ifstream only)
// Purpose: returns the data size
// Output : std::streampos
//-----------------------------------------------------------------------------
const std::streampos CIOStream::GetSize() const
const std::streamoff CIOStream::GetSize() const
{
return m_nSize;
return m_size;
}
bool CIOStream::IsReadMode() const
{
return (m_flags & std::ios::in);
}
bool CIOStream::IsWriteMode() const
{
return (m_flags & std::ios::out);
}
//-----------------------------------------------------------------------------
// Purpose: checks if we are able to read the file
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CIOStream::IsReadable()
bool CIOStream::IsReadable() const
{
if (!(m_nFlags & Mode_t::READ) || !m_Stream || m_Stream.eof())
if (!IsReadMode() || !m_stream || m_stream.eof())
return false;
return true;
@ -142,7 +194,7 @@ bool CIOStream::IsReadable()
//-----------------------------------------------------------------------------
bool CIOStream::IsWritable() const
{
if (!(m_nFlags & Mode_t::WRITE) || !m_Stream)
if (!IsWriteMode() || !m_stream)
return false;
return true;
@ -154,7 +206,7 @@ bool CIOStream::IsWritable() const
//-----------------------------------------------------------------------------
bool CIOStream::IsEof() const
{
return m_Stream.eof();
return m_stream.eof();
}
//-----------------------------------------------------------------------------
@ -162,69 +214,168 @@ bool CIOStream::IsEof() const
// Input : &svOut -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CIOStream::ReadString(std::string& svOut)
bool CIOStream::ReadString(std::string& out)
{
if (IsReadable())
if (!IsReadable())
return false;
while (!m_stream.eof())
{
while (!m_Stream.eof())
{
const char c = Read<char>();
const char c = Read<char>();
if (c == '\0')
break;
if (c == '\0')
break;
svOut += c;
}
return true;
out += c;
}
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: reads a string from the file into a fixed size buffer
// Input : *pBuf -
// nLen -
// Input : *buf -
// len -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CIOStream::ReadString(char* const pBuf, const size_t nLen)
bool CIOStream::ReadString(char* const buf, const size_t len)
{
if (IsReadable())
if (!IsReadable())
return false;
size_t i = 0;
while (i < len && !m_stream.eof())
{
size_t i = 0;
const char c = Read<char>();
while (i < nLen && !m_Stream.eof())
{
const char c = Read<char>();
if (c == '\0')
break;
if (c == '\0')
break;
pBuf[i++] = c;
}
return true;
buf[i++] = c;
}
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: writes a string to the file
// Input : &svInput -
// Input : &input -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CIOStream::WriteString(const std::string& svInput)
bool CIOStream::WriteString(const std::string& input, const bool nullterminate)
{
if (!IsWritable())
return false;
const char* const szText = svInput.c_str();
const size_t nSize = svInput.size();
const char* const text = input.c_str();
const size_t len = input.length() + nullterminate;
m_Stream.write(szText, nSize);
m_nSize += nSize;
m_stream.write(text, len);
CalcAddDelta(len);
return true;
}
// limit number of io calls and allocations by just using this static buffer
// for padding out the stream.
static constexpr size_t PAD_BUF_SIZE = 4096;
const static char s_padBuf[PAD_BUF_SIZE];
//-----------------------------------------------------------------------------
// Purpose: pads the out stream up to count bytes
// Input : count -
//-----------------------------------------------------------------------------
void CIOStream::Pad(const size_t count)
{
assert(count > 0);
size_t remainder = count;
while (remainder)
{
const size_t writeCount = (std::min)(count, PAD_BUF_SIZE);
Write(s_padBuf, writeCount);
remainder -= writeCount;
}
}
//-----------------------------------------------------------------------------
// Purpose: makes sure that the size gets incremented if we exceeded the end of
// the stream with the delta amount
//-----------------------------------------------------------------------------
void CIOStream::CalcAddDelta(const size_t count)
{
if (m_skip > 0)
{
m_skip -= count;
if (m_skip < 0)
{
m_size += -m_skip; // Add the overshoot to the file size.
m_skip = 0;
}
}
else
m_size += count;
}
//-----------------------------------------------------------------------------
// Purpose: if we seek backwards, and then write new data, we should not add
// this to the total output size of the stream as we modify and not
// add. we have to keep by how much we shifted backwards and advanced
// forward until we can start adding again.
//-----------------------------------------------------------------------------
void CIOStream::CalcSkipDelta(const std::streamoff offset, const std::ios_base::seekdir way)
{
switch (way)
{
case std::ios_base::beg:
{
if (offset < 0)
{
assert(false && "Negative offset in std::ios_base::beg is invalid.");
return;
}
if (offset > m_size)
{
m_size = offset;
m_skip = 0;
}
else
m_skip = m_size - offset;
break;
}
case std::ios_base::cur:
{
if (offset > 0)
CalcAddDelta(offset);
else
m_skip += -offset;
break;
}
case std::ios_base::end:
{
if (offset >= 0)
{
m_size += offset;
m_skip = 0;
}
else
m_skip += -offset;
break;
}
default:
assert(false && "Unsupported seek direction.");
break;
}
// Ensure m_skip is non-negative, this can happen if you call this method
// with cur or end, and a negative value who's absolute value is greater
// than the total stream size. If you hit this, you have a bug somewhere.
assert(m_skip >= 0);
if (m_skip < 0)
m_skip = 0;
}

View File

@ -93,7 +93,7 @@ bool CSigCache::ReadCache(const char* szCacheFile)
}
CIOStream reader;
if (!reader.Open(szCacheFile, CIOStream::READ | CIOStream::BINARY))
if (!reader.Open(szCacheFile, CIOStream::Mode_e::Read))
{
return false;
}
@ -168,7 +168,7 @@ bool CSigCache::WriteCache(const char* szCacheFile) const
}
CIOStream writer;
if (!writer.Open(szCacheFile, CIOStream::WRITE | CIOStream::BINARY))
if (!writer.Open(szCacheFile, CIOStream::Mode_e::Write))
{
Error(eDLL_T::COMMON, NO_ERROR, "%s - Unable to write to '%s' (read-only?)\n",
__FUNCTION__, szCacheFile);