Initial commit
This commit is contained in:
176
engines/ags/shared/util/bbop.h
Normal file
176
engines/ags/shared/util/bbop.h
Normal file
@@ -0,0 +1,176 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Various utility bit and byte operations
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_BBOP_H
|
||||
#define AGS_SHARED_UTIL_BBOP_H
|
||||
|
||||
#include "ags/shared/core/platform.h"
|
||||
#include "ags/shared/core/types.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
#if AGS_PLATFORM_ENDIAN_BIG || defined (TEST_BIGENDIAN)
|
||||
#define BITBYTE_BIG_ENDIAN
|
||||
#endif
|
||||
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
enum DataEndianess {
|
||||
kBigEndian,
|
||||
kLittleEndian,
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
kDefaultSystemEndianess = kBigEndian
|
||||
#else
|
||||
kDefaultSystemEndianess = kLittleEndian
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Various bit flags operations
|
||||
//
|
||||
// Converts from one flag into another:
|
||||
// sets flag2 if flag1 IS set
|
||||
// TODO: find more optimal way, using bitwise ops?
|
||||
inline int FlagToFlag(int value, int flag1, int flag2) {
|
||||
return ((value & flag1) != 0) * flag2;
|
||||
}
|
||||
// Sets flag2 if flag1 is NOT set
|
||||
inline int FlagToNoFlag(int value, int flag1, int flag2) {
|
||||
return ((value & flag1) == 0) * flag2;
|
||||
}
|
||||
|
||||
|
||||
namespace BitByteOperations {
|
||||
|
||||
struct IntFloatSwap {
|
||||
union {
|
||||
float f;
|
||||
int32_t i32;
|
||||
} val;
|
||||
|
||||
explicit IntFloatSwap(int32_t i) { val.i32 = i; }
|
||||
explicit IntFloatSwap(float f) { val.f = f; }
|
||||
};
|
||||
|
||||
inline int16_t SwapBytesInt16(const int16_t val) {
|
||||
return ((val >> 8) & 0xFF) | ((val << 8) & 0xFF00);
|
||||
}
|
||||
|
||||
inline int32_t SwapBytesInt32(const int32_t val) {
|
||||
return ((val >> 24) & 0xFF) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | ((val << 24) & 0xFF000000);
|
||||
}
|
||||
|
||||
inline int64_t SwapBytesInt64(const int64_t val) {
|
||||
return ((val >> 56) & 0xFF) | ((val >> 40) & 0xFF00) | ((val >> 24) & 0xFF0000) |
|
||||
((val >> 8) & 0xFF000000) | ((val << 8) & 0xFF00000000LL) |
|
||||
((val << 24) & 0xFF0000000000LL) | ((val << 40) & 0xFF000000000000LL) | ((val << 56) & 0xFF00000000000000LL);
|
||||
}
|
||||
|
||||
inline float SwapBytesFloat(const float val) {
|
||||
IntFloatSwap swap(val);
|
||||
swap.val.i32 = SwapBytesInt32(swap.val.i32);
|
||||
return swap.val.f;
|
||||
}
|
||||
|
||||
inline int16_t Int16FromLE(const int16_t val) {
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
return SwapBytesInt16(val);
|
||||
#else
|
||||
return val;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int32_t Int32FromLE(const int32_t val) {
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
return SwapBytesInt32(val);
|
||||
#else
|
||||
return val;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int64_t Int64FromLE(const int64_t val) {
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
return SwapBytesInt64(val);
|
||||
#else
|
||||
return val;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline float FloatFromLE(const float val) {
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
return SwapBytesFloat(val);
|
||||
#else
|
||||
return val;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int16_t Int16FromBE(const int16_t val) {
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
return val;
|
||||
#else
|
||||
return SwapBytesInt16(val);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int32_t Int32FromBE(const int32_t val) {
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
return val;
|
||||
#else
|
||||
return SwapBytesInt32(val);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int64_t Int64FromBE(const int64_t val) {
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
return val;
|
||||
#else
|
||||
return SwapBytesInt64(val);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline float FloatFromBE(const float val) {
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
return val;
|
||||
#else
|
||||
return SwapBytesFloat(val);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace BitByteOperations
|
||||
|
||||
|
||||
// Aliases for easier calling
|
||||
namespace BBOp = BitByteOperations;
|
||||
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
210
engines/ags/shared/util/buffered_stream.cpp
Normal file
210
engines/ags/shared/util/buffered_stream.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//include <cstring>
|
||||
#include "common/std/algorithm.h"
|
||||
#include "ags/shared/util/buffered_stream.h"
|
||||
#include "ags/shared/util/stdio_compat.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// BufferedStream
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const size_t BufferedStream::BufferSize;
|
||||
|
||||
BufferedStream::BufferedStream(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode, DataEndianess stream_endianess)
|
||||
: FileStream(file_name, open_mode, work_mode, stream_endianess) {
|
||||
if (IsValid()) {
|
||||
soff_t end_pos = FileStream::Seek(0, kSeekEnd);
|
||||
if (end_pos >= 0) {
|
||||
_start = 0;
|
||||
_end = end_pos;
|
||||
if (FileStream::Seek(0, kSeekBegin) < 0)
|
||||
_end = -1;
|
||||
}
|
||||
|
||||
if (_end == -1) {
|
||||
FileStream::Close();
|
||||
error("Error determining stream end.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BufferedStream::~BufferedStream() {
|
||||
BufferedStream::Close();
|
||||
}
|
||||
|
||||
void BufferedStream::FillBufferFromPosition(soff_t position) {
|
||||
FileStream::Seek(position, kSeekBegin);
|
||||
// remember to restrict to the end position!
|
||||
size_t fill_size = std::min(BufferSize, static_cast<size_t>(_end - position));
|
||||
_buffer.resize(fill_size);
|
||||
auto sz = FileStream::Read(_buffer.data(), fill_size);
|
||||
_buffer.resize(sz);
|
||||
_bufferPosition = position;
|
||||
}
|
||||
|
||||
void BufferedStream::FlushBuffer(soff_t position) {
|
||||
size_t sz = _buffer.size() > 0 ? FileStream::Write(_buffer.data(), _buffer.size()) : 0u;
|
||||
_buffer.clear(); // will start from the clean buffer next time
|
||||
_bufferPosition += sz;
|
||||
if (position != _bufferPosition) {
|
||||
FileStream::Seek(position, kSeekBegin);
|
||||
_bufferPosition = position;
|
||||
}
|
||||
}
|
||||
|
||||
bool BufferedStream::EOS() const {
|
||||
return _position == _end;
|
||||
}
|
||||
|
||||
soff_t BufferedStream::GetLength() const {
|
||||
return _end - _start;
|
||||
}
|
||||
|
||||
soff_t BufferedStream::GetPosition() const {
|
||||
return _position - _start;
|
||||
}
|
||||
|
||||
void BufferedStream::Close() {
|
||||
if (GetWorkMode() == kFile_Write)
|
||||
FlushBuffer(_position);
|
||||
FileStream::Close();
|
||||
}
|
||||
|
||||
bool BufferedStream::Flush() {
|
||||
if (GetWorkMode() == kFile_Write)
|
||||
FlushBuffer(_position);
|
||||
return FileStream::Flush();
|
||||
}
|
||||
|
||||
size_t BufferedStream::Read(void *buffer, size_t size) {
|
||||
// If the read size is larger than the internal buffer size,
|
||||
// then read directly into the user buffer and bail out.
|
||||
if (size >= BufferSize) {
|
||||
FileStream::Seek(_position, kSeekBegin);
|
||||
// remember to restrict to the end position!
|
||||
size_t fill_size = std::min(size, static_cast<size_t>(_end - _position));
|
||||
size_t sz = FileStream::Read(buffer, fill_size);
|
||||
_position += sz;
|
||||
return sz;
|
||||
}
|
||||
|
||||
auto *to = static_cast<uint8_t*>(buffer);
|
||||
|
||||
while(size > 0) {
|
||||
if (_position < _bufferPosition || _position >= _bufferPosition + _buffer.size()) {
|
||||
FillBufferFromPosition(_position);
|
||||
}
|
||||
if (_buffer.empty()) { break; } // reached EOS
|
||||
assert(_position >= _bufferPosition && _position < _bufferPosition + _buffer.size());
|
||||
|
||||
soff_t bufferOffset = _position - _bufferPosition;
|
||||
assert(bufferOffset >= 0);
|
||||
size_t bytesLeft = _buffer.size() - (size_t)bufferOffset;
|
||||
size_t chunkSize = MIN<size_t>(bytesLeft, size);
|
||||
|
||||
memcpy(to, _buffer.data() + bufferOffset, chunkSize);
|
||||
|
||||
to += chunkSize;
|
||||
_position += chunkSize;
|
||||
size -= chunkSize;
|
||||
}
|
||||
return to - static_cast<uint8_t*>(buffer);
|
||||
}
|
||||
|
||||
int32_t BufferedStream::ReadByte() {
|
||||
uint8_t ch;
|
||||
auto bytesRead = Read(&ch, 1);
|
||||
if (bytesRead != 1) {
|
||||
return EOF;
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
size_t BufferedStream::Write(const void *buffer, size_t size) {
|
||||
const uint8_t *from = static_cast<const uint8_t*>(buffer);
|
||||
while (size > 0) {
|
||||
if (_position < _bufferPosition || // seek'd before buffer pos
|
||||
_position > _bufferPosition + _buffer.size() || // seek'd beyond buffer pos
|
||||
_position >= _bufferPosition + (soff_t) BufferSize) // seek'd, or exceeded buffer limit
|
||||
{
|
||||
FlushBuffer(_position);
|
||||
}
|
||||
size_t pos_in_buff = static_cast<size_t>(_position - _bufferPosition);
|
||||
size_t chunk_sz = std::min(size, BufferSize - pos_in_buff);
|
||||
if (_buffer.size() < pos_in_buff + chunk_sz)
|
||||
_buffer.resize(pos_in_buff + chunk_sz);
|
||||
memcpy(_buffer.data() + pos_in_buff, from, chunk_sz);
|
||||
_position += chunk_sz;
|
||||
from += chunk_sz;
|
||||
size -= chunk_sz;
|
||||
}
|
||||
|
||||
_end = std::max(_end, _position);
|
||||
return from - static_cast<const uint8_t*>(buffer);
|
||||
|
||||
}
|
||||
|
||||
int32_t BufferedStream::WriteByte(uint8_t val) {
|
||||
auto sz = Write(&val, 1);
|
||||
if (sz != 1) {
|
||||
return -1;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
soff_t BufferedStream::Seek(soff_t offset, StreamSeek origin) {
|
||||
soff_t want_pos = -1;
|
||||
switch (origin) {
|
||||
case StreamSeek::kSeekCurrent: want_pos = _position + offset; break;
|
||||
case StreamSeek::kSeekBegin: want_pos = _start + offset; break;
|
||||
case StreamSeek::kSeekEnd: want_pos = _end + offset; break;
|
||||
default: return -1;
|
||||
}
|
||||
// clamp to the valid range
|
||||
_position = MIN(MAX(want_pos, _start), _end);
|
||||
return _position - _start; // convert to a stream section pos
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// BufferedSectionStream
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
BufferedSectionStream::BufferedSectionStream(const String &file_name, soff_t start_pos, soff_t end_pos,
|
||||
FileOpenMode open_mode, FileWorkMode work_mode, DataEndianess stream_endianess)
|
||||
: BufferedStream(file_name, open_mode, work_mode, stream_endianess) {
|
||||
assert(start_pos <= end_pos);
|
||||
start_pos = MIN(start_pos, end_pos);
|
||||
_start = MIN(start_pos, _end);
|
||||
_end = MIN(end_pos, _end);
|
||||
BufferedStream::Seek(0, kSeekBegin);
|
||||
}
|
||||
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
97
engines/ags/shared/util/buffered_stream.h
Normal file
97
engines/ags/shared/util/buffered_stream.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// BufferedStream represents a buffered file stream; uses memory buffer
|
||||
// during read and write operations to limit number reads and writes on disk
|
||||
// and thus improve i/o performance.
|
||||
//
|
||||
// BufferedSectionStream is a subclass stream that limits reading by an
|
||||
// arbitrary offset range.
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_BUFFEREDSTREAM_H
|
||||
#define AGS_SHARED_UTIL_BUFFEREDSTREAM_H
|
||||
|
||||
#include "common/std/vector.h"
|
||||
#include "ags/shared/util/file_stream.h"
|
||||
#include "ags/shared/util/file.h" // TODO: extract filestream mode constants
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class BufferedStream : public FileStream {
|
||||
public:
|
||||
// Needs tuning depending on the platform.
|
||||
static const size_t BufferSize = 1024u * 8;
|
||||
|
||||
// The constructor may raise std::runtime_error if
|
||||
// - there is an issue opening the file (does not exist, locked, permissions, etc)
|
||||
// - the open mode could not be determined
|
||||
// - could not determine the length of the stream
|
||||
// It is recommended to use File::OpenFile to safely construct this object.
|
||||
BufferedStream(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode, DataEndianess stream_endianess = kLittleEndian);
|
||||
~BufferedStream();
|
||||
|
||||
// Is end of stream
|
||||
bool EOS() const override;
|
||||
// Total length of stream (if known)
|
||||
soff_t GetLength() const override;
|
||||
// Current position (if known)
|
||||
soff_t GetPosition() const override;
|
||||
|
||||
void Close() override;
|
||||
bool Flush() override;
|
||||
|
||||
size_t Read(void *buffer, size_t size) override;
|
||||
int32_t ReadByte() override;
|
||||
size_t Write(const void *buffer, size_t size) override;
|
||||
int32_t WriteByte(uint8_t b) override;
|
||||
|
||||
soff_t Seek(soff_t offset, StreamSeek origin) override;
|
||||
|
||||
protected:
|
||||
soff_t _start = 0; // valid section starting offset
|
||||
soff_t _end = -1; // valid section ending offset
|
||||
|
||||
private:
|
||||
// Reads a chunk of file into the buffer, starting from the given offset
|
||||
void FillBufferFromPosition(soff_t position);
|
||||
// Writes a buffer into the file, and reposition to the new offset
|
||||
void FlushBuffer(soff_t position);
|
||||
|
||||
soff_t _position = 0; // absolute read/write offset
|
||||
soff_t _bufferPosition = 0; // buffer's location relative to file
|
||||
std::vector<uint8_t> _buffer;
|
||||
};
|
||||
|
||||
|
||||
// Creates a BufferedStream limited by an arbitrary offset range
|
||||
class BufferedSectionStream : public BufferedStream {
|
||||
public:
|
||||
BufferedSectionStream(const String &file_name, soff_t start_pos, soff_t end_pos,
|
||||
FileOpenMode open_mode, FileWorkMode work_mode, DataEndianess stream_endianess = kLittleEndian);
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
441
engines/ags/shared/util/compress.cpp
Normal file
441
engines/ags/shared/util/compress.cpp
Normal file
@@ -0,0 +1,441 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/util/compress.h"
|
||||
#include "common/std/vector.h"
|
||||
#include "ags/shared/ac/common.h" // quit, update_polled_stuff
|
||||
#include "ags/shared/gfx/bitmap.h"
|
||||
#include "ags/shared/util/file.h"
|
||||
#include "ags/shared/util/lzw.h"
|
||||
#include "ags/shared/util/memory_stream.h"
|
||||
#include "ags/globals.h"
|
||||
#if AGS_PLATFORM_ENDIAN_BIG
|
||||
#include "ags/shared/util/bbop.h"
|
||||
#endif
|
||||
|
||||
#include "common/compression/deflate.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
using namespace AGS::Shared;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// RLE
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static void cpackbitl(const uint8_t *line, size_t size, Stream *out) {
|
||||
size_t cnt = 0; // bytes encoded
|
||||
|
||||
while (cnt < size) {
|
||||
// IMPORTANT: the algorithm below requires signed operations
|
||||
int i = static_cast<int32_t>(cnt);
|
||||
int j = i + 1;
|
||||
int jmax = i + 126;
|
||||
if (static_cast<uint32_t>(jmax) >= size)
|
||||
jmax = size - 1;
|
||||
|
||||
if (static_cast<uint32_t>(i) == size - 1) { //......last byte alone
|
||||
out->WriteInt8(0);
|
||||
out->WriteInt8(line[i]);
|
||||
cnt++;
|
||||
|
||||
} else if (line[i] == line[j]) { //....run
|
||||
while ((j < jmax) && (line[j] == line[j + 1]))
|
||||
j++;
|
||||
|
||||
out->WriteInt8(i - j);
|
||||
out->WriteInt8(line[i]);
|
||||
cnt += j - i + 1;
|
||||
|
||||
} else { //.............................sequence
|
||||
while ((j < jmax) && (line[j] != line[j + 1]))
|
||||
j++;
|
||||
|
||||
out->WriteInt8(j - i);
|
||||
out->Write(line + i, j - i + 1);
|
||||
cnt += j - i + 1;
|
||||
|
||||
}
|
||||
} // end while
|
||||
}
|
||||
|
||||
static void cpackbitl16(const uint16_t *line, size_t size, Stream *out) {
|
||||
size_t cnt = 0; // bytes encoded
|
||||
|
||||
while (cnt < size) {
|
||||
// IMPORTANT: the algorithm below requires signed operations
|
||||
int i = cnt;
|
||||
int j = i + 1;
|
||||
int jmax = i + 126;
|
||||
if (static_cast<uint32_t>(jmax) >= size)
|
||||
jmax = size - 1;
|
||||
|
||||
if (static_cast<uint32_t>(i) == size - 1) { //......last byte alone
|
||||
out->WriteInt8(0);
|
||||
out->WriteInt16(line[i]);
|
||||
cnt++;
|
||||
|
||||
} else if (line[i] == line[j]) { //....run
|
||||
while ((j < jmax) && (line[j] == line[j + 1]))
|
||||
j++;
|
||||
|
||||
out->WriteInt8(i - j);
|
||||
out->WriteInt16(line[i]);
|
||||
cnt += j - i + 1;
|
||||
|
||||
} else { //.............................sequence
|
||||
while ((j < jmax) && (line[j] != line[j + 1]))
|
||||
j++;
|
||||
|
||||
out->WriteInt8(j - i);
|
||||
out->WriteArray(line + i, j - i + 1, 2);
|
||||
cnt += j - i + 1;
|
||||
|
||||
}
|
||||
} // end while
|
||||
}
|
||||
|
||||
static void cpackbitl32(const uint32_t *line, size_t size, Stream *out) {
|
||||
size_t cnt = 0; // bytes encoded
|
||||
|
||||
while (cnt < size) {
|
||||
// IMPORTANT: the algorithm below requires signed operations
|
||||
int i = cnt;
|
||||
int j = i + 1;
|
||||
int jmax = i + 126;
|
||||
if (static_cast<uint32_t>(jmax) >= size)
|
||||
jmax = size - 1;
|
||||
|
||||
if (static_cast<uint32_t>(i) == size - 1) { //......last byte alone
|
||||
out->WriteInt8(0);
|
||||
out->WriteInt32(line[i]);
|
||||
cnt++;
|
||||
|
||||
} else if (line[i] == line[j]) { //....run
|
||||
while ((j < jmax) && (line[j] == line[j + 1]))
|
||||
j++;
|
||||
|
||||
out->WriteInt8(i - j);
|
||||
out->WriteInt32(line[i]);
|
||||
cnt += j - i + 1;
|
||||
|
||||
} else { //.............................sequence
|
||||
while ((j < jmax) && (line[j] != line[j + 1]))
|
||||
j++;
|
||||
|
||||
out->WriteInt8(j - i);
|
||||
out->WriteArray(line + i, j - i + 1, 4);
|
||||
cnt += j - i + 1;
|
||||
|
||||
}
|
||||
} // end while
|
||||
}
|
||||
|
||||
static int cunpackbitl(uint8_t *line, size_t size, Stream *in) {
|
||||
size_t n = 0; // number of bytes decoded
|
||||
|
||||
while (n < size) {
|
||||
int ix = in->ReadByte(); // get index byte
|
||||
|
||||
int8 cx = ix;
|
||||
if (cx == -128)
|
||||
cx = 0;
|
||||
|
||||
if (cx < 0) { //.............run
|
||||
int i = 1 - cx;
|
||||
char ch = in->ReadInt8();
|
||||
while (i--) {
|
||||
// test for buffer overflow
|
||||
if (n >= size)
|
||||
return -1;
|
||||
|
||||
line[n++] = ch;
|
||||
}
|
||||
} else { //.....................seq
|
||||
int i = cx + 1;
|
||||
while (i--) {
|
||||
// test for buffer overflow
|
||||
if (n >= size)
|
||||
return -1;
|
||||
|
||||
line[n++] = in->ReadByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cunpackbitl16(uint16_t *line, size_t size, Stream *in) {
|
||||
size_t n = 0; // number of bytes decoded
|
||||
|
||||
while (n < size) {
|
||||
int ix = in->ReadByte(); // get index byte
|
||||
|
||||
int8 cx = ix;
|
||||
if (cx == -128)
|
||||
cx = 0;
|
||||
|
||||
if (cx < 0) { //.............run
|
||||
int i = 1 - cx;
|
||||
unsigned short ch = in->ReadInt16();
|
||||
while (i--) {
|
||||
// test for buffer overflow
|
||||
if (n >= size)
|
||||
return -1;
|
||||
|
||||
line[n++] = ch;
|
||||
}
|
||||
} else { //.....................seq
|
||||
int i = cx + 1;
|
||||
while (i--) {
|
||||
// test for buffer overflow
|
||||
if (n >= size)
|
||||
return -1;
|
||||
|
||||
line[n++] = in->ReadInt16();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cunpackbitl32(uint32_t *line, size_t size, Stream *in) {
|
||||
size_t n = 0; // number of bytes decoded
|
||||
|
||||
while (n < size) {
|
||||
int ix = in->ReadByte(); // get index byte
|
||||
|
||||
int8 cx = ix;
|
||||
if (cx == -128)
|
||||
cx = 0;
|
||||
|
||||
if (cx < 0) { //.............run
|
||||
int i = 1 - cx;
|
||||
unsigned int ch = in->ReadInt32();
|
||||
while (i--) {
|
||||
// test for buffer overflow
|
||||
if (n >= size)
|
||||
return -1;
|
||||
|
||||
line[n++] = ch;
|
||||
}
|
||||
} else { //.....................seq
|
||||
int i = cx + 1;
|
||||
while (i--) {
|
||||
// test for buffer overflow
|
||||
if (n >= size)
|
||||
return -1;
|
||||
|
||||
line[n++] = (unsigned int)in->ReadInt32();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool rle_compress(const uint8_t *data, size_t data_sz, int image_bpp, Stream *out) {
|
||||
switch (image_bpp) {
|
||||
case 1: cpackbitl(data, data_sz, out); break;
|
||||
case 2: cpackbitl16(reinterpret_cast<const uint16_t *>(data), data_sz / sizeof(uint16_t), out); break;
|
||||
case 4: cpackbitl32(reinterpret_cast<const uint32_t *>(data), data_sz / sizeof(uint32_t), out); break;
|
||||
default: assert(0); break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rle_decompress(uint8_t *data, size_t data_sz, int image_bpp, Stream *in) {
|
||||
switch (image_bpp) {
|
||||
case 1: cunpackbitl(data, data_sz, in); break;
|
||||
case 2: cunpackbitl16(reinterpret_cast<uint16_t *>(data), data_sz / sizeof(uint16_t), in); break;
|
||||
case 4: cunpackbitl32(reinterpret_cast<uint32_t *>(data), data_sz / sizeof(uint32_t), in); break;
|
||||
default: assert(0); break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void save_rle_bitmap8(Stream *out, const Bitmap *bmp, const RGB(*pal)[256]) {
|
||||
assert(bmp->GetBPP() == 1);
|
||||
out->WriteInt16(static_cast<uint16_t>(bmp->GetWidth()));
|
||||
out->WriteInt16(static_cast<uint16_t>(bmp->GetHeight()));
|
||||
// Pack the pixels
|
||||
cpackbitl(bmp->GetData(), bmp->GetWidth() * bmp->GetHeight(), out);
|
||||
// Save palette
|
||||
if (!pal) { // if no pal, write dummy palette, because we have to
|
||||
out->WriteByteCount(0, 256 * 3);
|
||||
return;
|
||||
}
|
||||
const RGB *ppal = *pal;
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
out->WriteInt8(ppal[i].r);
|
||||
out->WriteInt8(ppal[i].g);
|
||||
out->WriteInt8(ppal[i].b);
|
||||
}
|
||||
}
|
||||
|
||||
Shared::Bitmap *load_rle_bitmap8(Stream *in, RGB(*pal)[256]) {
|
||||
int w = in->ReadInt16();
|
||||
int h = in->ReadInt16();
|
||||
Bitmap *bmp = BitmapHelper::CreateBitmap(w, h, 8);
|
||||
if (!bmp) return nullptr;
|
||||
// Unpack the pixels
|
||||
cunpackbitl(bmp->GetDataForWriting(), w * h, in);
|
||||
// Load or skip the palette
|
||||
if (!pal) {
|
||||
in->Seek(3 * 256);
|
||||
return bmp;
|
||||
}
|
||||
RGB *ppal = *pal;
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
ppal[i].r = in->ReadInt8();
|
||||
ppal[i].g = in->ReadInt8();
|
||||
ppal[i].b = in->ReadInt8();
|
||||
}
|
||||
return bmp;
|
||||
}
|
||||
|
||||
void skip_rle_bitmap8(Stream *in) {
|
||||
int w = in->ReadInt16();
|
||||
int h = in->ReadInt16();
|
||||
// Unpack the pixels into temp buf
|
||||
std::vector<uint8_t> buf;
|
||||
buf.resize(w * h);
|
||||
cunpackbitl(&buf[0], w * h, in);
|
||||
// Skip RGB palette
|
||||
in->Seek(3 * 256);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// LZW
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool lzw_compress(const uint8_t *data, size_t data_sz, int /*image_bpp*/, Shared::Stream *out) {
|
||||
// LZW algorithm that we use fails on sequence less than 16 bytes.
|
||||
if (data_sz < 16) {
|
||||
out->Write(data, data_sz);
|
||||
return true;
|
||||
}
|
||||
MemoryStream mem_in(data, data_sz);
|
||||
return lzwcompress(&mem_in, out);
|
||||
}
|
||||
|
||||
bool lzw_decompress(uint8_t *data, size_t data_sz, int /*image_bpp*/, Shared::Stream *in, size_t in_sz) {
|
||||
// LZW algorithm that we use fails on sequence less than 16 bytes.
|
||||
if (data_sz < 16) {
|
||||
in->Read(data, data_sz);
|
||||
return true;
|
||||
}
|
||||
std::vector<uint8_t> in_buf(in_sz);
|
||||
in->Read(in_buf.data(), in_sz);
|
||||
return lzwexpand(in_buf.data(), in_sz, data, data_sz);
|
||||
}
|
||||
|
||||
void save_lzw(Stream *out, const Bitmap *bmpp, const RGB(*pal)[256]) {
|
||||
// First write original bitmap's info and data into the memory buffer
|
||||
// NOTE: we must do this purely for backward compatibility with old room formats:
|
||||
// because they also included bmp width and height into compressed data!
|
||||
std::vector<uint8_t> membuf;
|
||||
{
|
||||
VectorStream memws(membuf, kStream_Write);
|
||||
int w = bmpp->GetWidth(), h = bmpp->GetHeight(), bpp = bmpp->GetBPP();
|
||||
memws.WriteInt32(w * bpp); // stride
|
||||
memws.WriteInt32(h);
|
||||
switch (bpp) {
|
||||
case 1: memws.Write(bmpp->GetData(), w * h * bpp); break;
|
||||
case 2: memws.WriteArrayOfInt16(reinterpret_cast<const int16_t *>(bmpp->GetData()), w *h); break;
|
||||
case 4: memws.WriteArrayOfInt32(reinterpret_cast<const int32_t *>(bmpp->GetData()), w *h); break;
|
||||
default: assert(0); break;
|
||||
}
|
||||
}
|
||||
|
||||
// Open same buffer for reading, and begin writing compressed data into the output
|
||||
VectorStream mem_in(membuf);
|
||||
// NOTE: old format saves full RGB struct here (4 bytes, including the filler)
|
||||
if (pal)
|
||||
out->WriteArray(*pal, sizeof(RGB), 256);
|
||||
else
|
||||
out->WriteByteCount(0, sizeof(RGB) * 256);
|
||||
out->WriteInt32((uint32_t)mem_in.GetLength());
|
||||
|
||||
// reserve space for compressed size
|
||||
soff_t cmpsz_at = out->GetPosition();
|
||||
out->WriteInt32(0);
|
||||
lzwcompress(&mem_in, out);
|
||||
soff_t toret = out->GetPosition();
|
||||
out->Seek(cmpsz_at, kSeekBegin);
|
||||
soff_t compressed_sz = (toret - cmpsz_at) - sizeof(uint32_t);
|
||||
out->WriteInt32(compressed_sz); // write compressed size
|
||||
// seek back to the end of the output stream
|
||||
out->Seek(toret, kSeekBegin);
|
||||
}
|
||||
|
||||
Bitmap *load_lzw(Stream *in, int dst_bpp, RGB(*pal)[256]) {
|
||||
// NOTE: old format saves full RGB struct here (4 bytes, including the filler)
|
||||
if (pal)
|
||||
in->Read(*pal, sizeof(RGB) * 256);
|
||||
else
|
||||
in->Seek(sizeof(RGB) * 256);
|
||||
const size_t uncomp_sz = in->ReadInt32();
|
||||
const size_t comp_sz = in->ReadInt32();
|
||||
const soff_t end_pos = in->GetPosition() + comp_sz;
|
||||
|
||||
// First decompress data into the memory buffer
|
||||
std::vector<uint8_t> inbuf(comp_sz);
|
||||
std::vector<uint8_t> membuf(uncomp_sz);
|
||||
in->Read(inbuf.data(), comp_sz);
|
||||
lzwexpand(inbuf.data(), comp_sz, membuf.data(), uncomp_sz);
|
||||
|
||||
// Open same buffer for reading and get params and pixels
|
||||
VectorStream mem_in(membuf);
|
||||
int stride = mem_in.ReadInt32(); // width * bpp
|
||||
int height = mem_in.ReadInt32();
|
||||
Bitmap *bmm = BitmapHelper::CreateBitmap((stride / dst_bpp), height, dst_bpp * 8);
|
||||
if (!bmm) return nullptr; // out of mem?
|
||||
|
||||
size_t num_pixels = stride * height / dst_bpp;
|
||||
uint8_t *bmp_data = bmm->GetDataForWriting();
|
||||
switch (dst_bpp) {
|
||||
case 1: mem_in.Read(bmp_data, num_pixels); break;
|
||||
case 2: mem_in.ReadArrayOfInt16(reinterpret_cast<int16_t *>(bmp_data), num_pixels); break;
|
||||
case 4: mem_in.ReadArrayOfInt32(reinterpret_cast<int32_t *>(bmp_data), num_pixels); break;
|
||||
default: assert(0); break;
|
||||
}
|
||||
|
||||
if (in->GetPosition() != end_pos)
|
||||
in->Seek(end_pos, kSeekBegin);
|
||||
|
||||
return bmm;
|
||||
}
|
||||
|
||||
bool deflate_compress(const uint8_t *data, size_t data_sz, int /*image_bpp*/, Stream *out) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
bool inflate_decompress(uint8_t *data, size_t data_sz, int /*image_bpp*/, Stream *in, size_t in_sz) {
|
||||
std::vector<uint8_t> in_buf(in_sz);
|
||||
in->Read(in_buf.data(), in_sz);
|
||||
return Common::inflateZlib(data, (unsigned long)data_sz, in_buf.data(), in_sz);
|
||||
}
|
||||
|
||||
} // namespace AGS3
|
||||
62
engines/ags/shared/util/compress.h
Normal file
62
engines/ags/shared/util/compress.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_COMPRESS_H
|
||||
#define AGS_SHARED_UTIL_COMPRESS_H
|
||||
|
||||
#include "ags/shared/core/types.h"
|
||||
#include "ags/shared/util/wgt2_allg.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
class Stream;
|
||||
class Bitmap;
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
|
||||
using namespace AGS; // FIXME later
|
||||
|
||||
bool rle_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
|
||||
bool rle_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in);
|
||||
// Packs a 8-bit bitmap using RLE compression, and writes into stream along with the palette
|
||||
void save_rle_bitmap8(Shared::Stream *out, const Shared::Bitmap *bmp, const RGB(*pal)[256] = nullptr);
|
||||
// Reads a 8-bit bitmap with palette from the stream and unpacks from RLE
|
||||
Shared::Bitmap *load_rle_bitmap8(Shared::Stream *in, RGB(*pal)[256] = nullptr);
|
||||
// Skips the 8-bit RLE bitmap
|
||||
void skip_rle_bitmap8(Shared::Stream *in);
|
||||
|
||||
// LZW compression
|
||||
bool lzw_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
|
||||
bool lzw_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in, size_t in_sz);
|
||||
// Saves bitmap with an optional palette compressed by LZW
|
||||
void save_lzw(Shared::Stream *out, const Shared::Bitmap *bmpp, const RGB(*pal)[256] = nullptr);
|
||||
// Loads bitmap decompressing
|
||||
Shared::Bitmap *load_lzw(Shared::Stream *in, int dst_bpp, RGB (*pal)[256] = nullptr);
|
||||
|
||||
// Deflate compression
|
||||
bool deflate_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
|
||||
bool inflate_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in, size_t in_sz);
|
||||
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
159
engines/ags/shared/util/data_ext.cpp
Normal file
159
engines/ags/shared/util/data_ext.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/util/data_ext.h"
|
||||
#include "ags/shared/debugging/out.h"
|
||||
#include "ags/shared/util/stream.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
String GetDataExtErrorText(DataExtErrorType err) {
|
||||
switch (err) {
|
||||
case kDataExtErr_NoError:
|
||||
return "No error.";
|
||||
case kDataExtErr_UnexpectedEOF:
|
||||
return "Unexpected end of file.";
|
||||
case kDataExtErr_BlockDataOverlapping:
|
||||
return "Block data overlapping.";
|
||||
case kDataExtErr_BlockNotFound:
|
||||
return "Block not found.";
|
||||
default: return "Unknown error.";
|
||||
}
|
||||
}
|
||||
|
||||
HError DataExtParser::OpenBlock() {
|
||||
// - 1 or 4 bytes - an old-style unsigned numeric ID:
|
||||
// where 0 would indicate following string ID,
|
||||
// and -1 indicates end of the block list.
|
||||
// - 16 bytes - string ID of an extension (if numeric ID is 0).
|
||||
// - 4 or 8 bytes - length of extension data, in bytes.
|
||||
_blockID = ((_flags & kDataExt_NumID32) != 0) ?
|
||||
_in->ReadInt32() :
|
||||
_in->ReadInt8();
|
||||
|
||||
if (_blockID < 0)
|
||||
return HError::None(); // end of list
|
||||
if (_in->EOS())
|
||||
return new DataExtError(kDataExtErr_UnexpectedEOF);
|
||||
|
||||
if (_blockID > 0) { // old-style block identified by a numeric id
|
||||
_blockLen = ((_flags & kDataExt_File64) != 0) ? _in->ReadInt64() : _in->ReadInt32();
|
||||
_extID = GetOldBlockName(_blockID);
|
||||
} else { // new style block identified by a string id
|
||||
_extID = String::FromStreamCount(_in, 16);
|
||||
_blockLen = _in->ReadInt64();
|
||||
}
|
||||
_blockStart = _in->GetPosition();
|
||||
return HError::None();
|
||||
}
|
||||
|
||||
void DataExtParser::SkipBlock() {
|
||||
if (_blockID >= 0)
|
||||
_in->Seek(_blockStart + _blockLen, kSeekBegin);
|
||||
}
|
||||
|
||||
HError DataExtParser::PostAssert() {
|
||||
const soff_t cur_pos = _in->GetPosition();
|
||||
const soff_t block_end = _blockStart + _blockLen;
|
||||
if (cur_pos > block_end) {
|
||||
String err = String::FromFormat("Block: '%s', expected to end at offset: %llu, finished reading at %llu.",
|
||||
_extID.GetCStr(), static_cast<int64>(block_end), static_cast<int64>(cur_pos));
|
||||
if (cur_pos <= block_end + GetOverLeeway(_blockID))
|
||||
Debug::Printf(kDbgMsg_Warn, err);
|
||||
else
|
||||
return new DataExtError(kDataExtErr_BlockDataOverlapping, err);
|
||||
} else if (cur_pos < block_end) {
|
||||
Debug::Printf(kDbgMsg_Warn, "WARNING: data blocks nonsequential, block '%s' expected to end at %llu, finished reading at %llu",
|
||||
_extID.GetCStr(), static_cast<int64>(block_end), static_cast<int64>(cur_pos));
|
||||
_in->Seek(block_end, Shared::kSeekBegin);
|
||||
}
|
||||
return HError::None();
|
||||
}
|
||||
|
||||
HError DataExtParser::FindOne(int id) {
|
||||
if (id <= 0) return new DataExtError(kDataExtErr_BlockNotFound);
|
||||
|
||||
HError err = HError::None();
|
||||
for (err = OpenBlock(); err && !AtEnd(); err = OpenBlock()) {
|
||||
if (id == _blockID)
|
||||
return HError::None();
|
||||
_in->Seek(_blockLen); // skip it
|
||||
}
|
||||
if (!err)
|
||||
return err;
|
||||
return new DataExtError(kDataExtErr_BlockNotFound);
|
||||
}
|
||||
|
||||
HError DataExtReader::Read() {
|
||||
HError err = HError::None();
|
||||
bool read_next = true;
|
||||
for (err = OpenBlock(); err && !AtEnd() && read_next; err = OpenBlock()) {
|
||||
// Call the reader function to read current block's data
|
||||
read_next = true;
|
||||
err = ReadBlock(_blockID, _extID, _blockLen, read_next);
|
||||
if (!err)
|
||||
return err;
|
||||
// Test that we did not read too much or too little
|
||||
err = PostAssert();
|
||||
if (!err)
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
// Generic function that saves a block and automatically adds its size into header
|
||||
void WriteExtBlock(int block, const String &ext_id, const PfnWriteExtBlock &writer, int flags, Stream *out) {
|
||||
const bool is_id32 = (flags & kDataExt_NumID32) != 0;
|
||||
// 64-bit file offsets are written for blocks with ext_id, OR File64 flag
|
||||
const bool is_file64 = (block == 0) || ((flags & kDataExt_File64) != 0);
|
||||
// Write block's header
|
||||
is_id32 != 0 ?
|
||||
out->WriteInt32(block) :
|
||||
out->WriteInt8(static_cast<int8_t>(block));
|
||||
if (block == 0) // new-style string id
|
||||
ext_id.WriteCount(out, 16);
|
||||
soff_t sz_at = out->GetPosition();
|
||||
// block size placeholder
|
||||
is_file64 ?
|
||||
out->WriteInt64(0) :
|
||||
out->WriteInt32(0);
|
||||
soff_t start_at = out->GetPosition();
|
||||
|
||||
// Call writer to save actual block contents
|
||||
writer(out);
|
||||
|
||||
// Now calculate the block's size...
|
||||
soff_t end_at = out->GetPosition();
|
||||
soff_t block_size = (end_at - start_at);
|
||||
// ...return back and write block's size in the placeholder
|
||||
out->Seek(sz_at, Shared::kSeekBegin);
|
||||
is_file64 ?
|
||||
out->WriteInt64(block_size) :
|
||||
out->WriteInt32((int32_t)block_size);
|
||||
// ...and get back to the end of the file
|
||||
out->Seek(0, Shared::kSeekEnd);
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
175
engines/ags/shared/util/data_ext.h
Normal file
175
engines/ags/shared/util/data_ext.h
Normal file
@@ -0,0 +1,175 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// A simple data extension format which may be useful as an amendment to
|
||||
// any data file in the game.
|
||||
//
|
||||
// Format consists of a list of "blocks", each preceded with an integer and an
|
||||
// optional string identifier, and a size in bytes which lets a reader to skip
|
||||
// the block completely if necessary.
|
||||
// Because the serialization algorithm was accommodated to be shared among
|
||||
// several existing data files, few things in the meta info may be read
|
||||
// slightly differently depending on flags passed into the function.
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// Extension format description.
|
||||
//
|
||||
// Each block starts with the header.
|
||||
// * 1 or 4 bytes (depends on flags) - an old-style unsigned numeric ID :
|
||||
// - where 0 would indicate following string ID,
|
||||
// - and -1 (0xFF in case of 1 byte ID) indicates an end of the block list.
|
||||
// * 16 bytes - fixed-len string ID of an extension (if num ID == 0).
|
||||
// * 4 bytes - total length of the data in bytes;
|
||||
// - does not include the size of the header (only following data).
|
||||
// - new style blocks (w string ID) always use 8 bytes for a block len;
|
||||
// * Then goes regular data.
|
||||
//
|
||||
// After the block is read the stream position supposed to be at
|
||||
// (start + length of block), where "start" is a first byte of the header.
|
||||
// If it's further - the reader bails out with error. If it's not far enough -
|
||||
// the function logs a warning and skips remaining bytes.
|
||||
// Blocks are read until meeting ID == -1 in the next header, which indicates
|
||||
// the end of extension list. An EOF met earlier is considered an error.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_DATA_EXT_H
|
||||
#define AGS_SHARED_UTIL_DATA_EXT_H
|
||||
|
||||
#include "ags/shared/util/error.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
enum DataExtFlags {
|
||||
// Size of a numeric ID in bytes
|
||||
kDataExt_NumID8 = 0x0000, // default
|
||||
kDataExt_NumID32 = 0x0001,
|
||||
// 32-bit or 64-bit file offset support
|
||||
// NOTE: for historical reasons this refers to blocks with numeric ID ONLY;
|
||||
// new-style blocks with a 16-char ID always write 64-bit offset
|
||||
kDataExt_File32 = 0x0000, // default
|
||||
kDataExt_File64 = 0x0002
|
||||
};
|
||||
|
||||
enum DataExtErrorType {
|
||||
kDataExtErr_NoError,
|
||||
kDataExtErr_UnexpectedEOF,
|
||||
kDataExtErr_BlockNotFound,
|
||||
kDataExtErr_BlockDataOverlapping
|
||||
};
|
||||
|
||||
String GetDataExtErrorText(DataExtErrorType err);
|
||||
typedef TypedCodeError<DataExtErrorType, GetDataExtErrorText> DataExtError;
|
||||
|
||||
|
||||
// DataExtReader parses a generic extendable block list and
|
||||
// does format checks, but does not read any data itself.
|
||||
// Use it to open blocks, and assert reading correctness.
|
||||
class DataExtParser {
|
||||
public:
|
||||
DataExtParser(Stream *in, int flags) : _in(in), _flags(flags) {
|
||||
}
|
||||
virtual ~DataExtParser() = default;
|
||||
|
||||
// Returns the conventional string ID for an old-style block with numeric ID
|
||||
virtual String GetOldBlockName(int blockId) const {
|
||||
return String::FromFormat("id:%d", blockId);
|
||||
}
|
||||
|
||||
// Provides a leeway for over-reading (reading past the reported block length):
|
||||
// the parser will not error if the mistake is in this range of bytes
|
||||
virtual soff_t GetOverLeeway(int /*block_id*/) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Gets a stream
|
||||
inline Stream *GetStream() {
|
||||
return _in;
|
||||
}
|
||||
// Tells if the end of the block list was reached
|
||||
inline bool AtEnd() const {
|
||||
return _blockID < 0;
|
||||
}
|
||||
// Tries to opens a next standard block from the stream,
|
||||
// fills in identifier and length on success
|
||||
HError OpenBlock();
|
||||
// Skips current block
|
||||
void SkipBlock();
|
||||
// Asserts current stream position after a block was read
|
||||
HError PostAssert();
|
||||
// Parses a block list in search for a particular block,
|
||||
// if found opens it.
|
||||
HError FindOne(int id);
|
||||
|
||||
protected:
|
||||
Stream *_in = nullptr;
|
||||
int _flags = 0;
|
||||
|
||||
int _blockID = -1;
|
||||
String _extID;
|
||||
soff_t _blockStart = 0;
|
||||
soff_t _blockLen = 0;
|
||||
};
|
||||
|
||||
// DataExtReader is a virtual base class of a block list reader; provides
|
||||
// a helper method for reading all the blocks one by one, but leaves data
|
||||
// reading for the child classes. A child class must override ReadBlock method.
|
||||
// TODO: don't extend Parser, but have it as a member?
|
||||
class DataExtReader : protected DataExtParser {
|
||||
public:
|
||||
virtual ~DataExtReader() = default;
|
||||
|
||||
// Parses a block list, calls ReadBlock for each found block
|
||||
HError Read();
|
||||
|
||||
protected:
|
||||
DataExtReader(Stream *in, int flags) : DataExtParser(in, flags) {
|
||||
}
|
||||
// Reads a single data block and tell whether to continue reading;
|
||||
// default implementation skips the block
|
||||
virtual HError ReadBlock(int block_id, const String &ext_id,
|
||||
soff_t block_len, bool &read_next) = 0;
|
||||
};
|
||||
|
||||
|
||||
// Type of function that writes a single data block.
|
||||
typedef void(*PfnWriteExtBlock)(Stream *out);
|
||||
void WriteExtBlock(int block, const String &ext_id, const PfnWriteExtBlock &writer, int flags, Stream *out);
|
||||
// Writes a block with a new-style string id
|
||||
inline void WriteExtBlock(const String &ext_id, PfnWriteExtBlock writer, int flags, Stream *out) {
|
||||
WriteExtBlock(0, ext_id, writer, flags, out);
|
||||
}
|
||||
// Writes a block with a old-style numeric id
|
||||
inline void WriteExtBlock(int block, PfnWriteExtBlock writer, int flags, Stream *out) {
|
||||
WriteExtBlock(block, String(), writer, flags, out);
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
204
engines/ags/shared/util/data_stream.cpp
Normal file
204
engines/ags/shared/util/data_stream.cpp
Normal file
@@ -0,0 +1,204 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/std/algorithm.h"
|
||||
#include "ags/shared/util/data_stream.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
DataStream::DataStream(DataEndianess stream_endianess)
|
||||
: _streamEndianess(stream_endianess) {
|
||||
}
|
||||
|
||||
DataStream::~DataStream() {}
|
||||
|
||||
int16_t DataStream::ReadInt16() {
|
||||
int16_t val = 0;
|
||||
Read(&val, sizeof(int16_t));
|
||||
ConvertInt16(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
int32_t DataStream::ReadInt32() {
|
||||
int32_t val = 0;
|
||||
Read(&val, sizeof(int32_t));
|
||||
ConvertInt32(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
int64_t DataStream::ReadInt64() {
|
||||
int64_t val = 0;
|
||||
Read(&val, sizeof(int64_t));
|
||||
ConvertInt64(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
size_t DataStream::WriteInt16(int16_t val) {
|
||||
ConvertInt16(val);
|
||||
return Write(&val, sizeof(int16_t));
|
||||
}
|
||||
|
||||
size_t DataStream::WriteInt32(int32_t val) {
|
||||
ConvertInt32(val);
|
||||
return Write(&val, sizeof(int32_t));
|
||||
}
|
||||
|
||||
size_t DataStream::WriteInt64(int64_t val) {
|
||||
ConvertInt64(val);
|
||||
return Write(&val, sizeof(int64_t));
|
||||
}
|
||||
|
||||
size_t DataStream::ReadAndConvertArrayOfInt16(int16_t *buffer, size_t count) {
|
||||
count = ReadArray(buffer, sizeof(int16_t), count);
|
||||
for (size_t i = 0; i < count; ++i, ++buffer) {
|
||||
*buffer = BBOp::SwapBytesInt16(*buffer);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t DataStream::ReadAndConvertArrayOfInt32(int32_t *buffer, size_t count) {
|
||||
count = ReadArray(buffer, sizeof(int32_t), count);
|
||||
for (size_t i = 0; i < count; ++i, ++buffer) {
|
||||
*buffer = BBOp::SwapBytesInt32(*buffer);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t DataStream::ReadAndConvertArrayOfInt64(int64_t *buffer, size_t count) {
|
||||
count = ReadArray(buffer, sizeof(int64_t), count);
|
||||
for (size_t i = 0; i < count; ++i, ++buffer) {
|
||||
*buffer = BBOp::SwapBytesInt64(*buffer);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t DataStream::WriteAndConvertArrayOfInt16(const int16_t *buffer, size_t count) {
|
||||
size_t elem;
|
||||
for (elem = 0; elem < count; ++elem, ++buffer) {
|
||||
int16_t val = *buffer;
|
||||
ConvertInt16(val);
|
||||
if (Write(&val, sizeof(int16_t)) < sizeof(int16_t)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
size_t DataStream::WriteAndConvertArrayOfInt32(const int32_t *buffer, size_t count) {
|
||||
size_t elem;
|
||||
for (elem = 0; elem < count; ++elem, ++buffer) {
|
||||
int32_t val = *buffer;
|
||||
ConvertInt32(val);
|
||||
if (Write(&val, sizeof(int32_t)) < sizeof(int32_t)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
size_t DataStream::WriteAndConvertArrayOfInt64(const int64_t *buffer, size_t count) {
|
||||
size_t elem;
|
||||
for (elem = 0; elem < count; ++elem, ++buffer) {
|
||||
int64_t val = *buffer;
|
||||
ConvertInt64(val);
|
||||
if (Write(&val, sizeof(int64_t)) < sizeof(int64_t)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
DataStreamSection::DataStreamSection(Stream *base, soff_t start, soff_t end)
|
||||
: _base(base) {
|
||||
_start = std::max((soff_t)0, std::min(start, end));
|
||||
_end = std::min(std::max((soff_t)0, end), base->GetLength());
|
||||
soff_t pos = base->Seek(_start, kSeekBegin);
|
||||
if (pos >= 0)
|
||||
_position = pos;
|
||||
else
|
||||
_position = base->GetPosition();
|
||||
}
|
||||
|
||||
size_t DataStreamSection::Read(void *buffer, size_t len) {
|
||||
if (_position >= _end)
|
||||
return 0;
|
||||
len = std::min(len, static_cast<size_t>(_end - _position));
|
||||
_position += _base->Read(buffer, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
int32_t DataStreamSection::ReadByte() {
|
||||
if (_position >= _end)
|
||||
return -1;
|
||||
int32_t b = _base->ReadByte();
|
||||
if (b >= 0)
|
||||
_position++;
|
||||
return b;
|
||||
}
|
||||
|
||||
size_t DataStreamSection::Write(const void *buffer, size_t len) {
|
||||
len = _base->Write(buffer, len);
|
||||
_position += len;
|
||||
_end = std::max(_end, _position); // we might be overwriting after seeking back
|
||||
return len;
|
||||
}
|
||||
|
||||
int32_t DataStreamSection::WriteByte(uint8_t b) {
|
||||
int32_t rb = _base->WriteByte(b);
|
||||
if (rb == b) {
|
||||
_position++;
|
||||
_end = std::max(_end, _position); // we might be overwriting after seeking back
|
||||
}
|
||||
return rb;
|
||||
}
|
||||
|
||||
soff_t DataStreamSection::Seek(soff_t offset, StreamSeek origin) {
|
||||
soff_t want_pos = -1;
|
||||
switch (origin) {
|
||||
case StreamSeek::kSeekCurrent:
|
||||
want_pos = _position + offset;
|
||||
break;
|
||||
case StreamSeek::kSeekBegin:
|
||||
want_pos = _start + offset;
|
||||
break;
|
||||
case StreamSeek::kSeekEnd:
|
||||
want_pos = _end + offset;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
want_pos = std::min(std::max(want_pos, _start), _end);
|
||||
soff_t new_pos = _base->Seek(want_pos, kSeekBegin);
|
||||
if (new_pos >= 0) // the position remains in case of seek error
|
||||
_position = want_pos;
|
||||
return _position - _start; // convert to a stream section pos
|
||||
}
|
||||
|
||||
void DataStreamSection::Close() {
|
||||
// We do not close nor delete the base stream, but release it from use
|
||||
_base = nullptr;
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
162
engines/ags/shared/util/data_stream.h
Normal file
162
engines/ags/shared/util/data_stream.h
Normal file
@@ -0,0 +1,162 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Standard AGS stream implementation for reading raw data with support for
|
||||
// converting to opposite endianess. Most I/O devices should inherit this
|
||||
// class and provide implementation for basic reading and writing only.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_DATASTREAM_H
|
||||
#define AGS_SHARED_UTIL_DATASTREAM_H
|
||||
|
||||
#include "ags/shared/util/bbop.h"
|
||||
#include "ags/shared/util/stream.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class DataStream : public Stream {
|
||||
public:
|
||||
DataStream(DataEndianess stream_endianess = kLittleEndian);
|
||||
~DataStream() override;
|
||||
|
||||
int16_t ReadInt16() override;
|
||||
int32_t ReadInt32() override;
|
||||
int64_t ReadInt64() override;
|
||||
|
||||
//
|
||||
// Read- and WriteArray methods return number of full elements (NOT bytes)
|
||||
// read or written, or -1 if end of stream is reached
|
||||
//
|
||||
// Note that ReadArray and WriteArray do NOT convert byte order even when
|
||||
// work with data of different endianess; they are meant for optimal
|
||||
// reading and writing blocks of raw bytes
|
||||
inline size_t ReadArray(void *buffer, size_t elem_size, size_t count) override {
|
||||
return Read(buffer, elem_size * count) / elem_size;
|
||||
}
|
||||
|
||||
inline size_t ReadArrayOfInt16(int16_t *buffer, size_t count) override {
|
||||
return MustSwapBytes() ?
|
||||
ReadAndConvertArrayOfInt16(buffer, count) : ReadArray(buffer, sizeof(int16_t), count);
|
||||
}
|
||||
|
||||
inline size_t ReadArrayOfInt32(int32_t *buffer, size_t count) override {
|
||||
return MustSwapBytes() ?
|
||||
ReadAndConvertArrayOfInt32(buffer, count) : ReadArray(buffer, sizeof(int32_t), count);
|
||||
}
|
||||
|
||||
inline size_t ReadArrayOfInt64(int64_t *buffer, size_t count) override {
|
||||
return MustSwapBytes() ?
|
||||
ReadAndConvertArrayOfInt64(buffer, count) : ReadArray(buffer, sizeof(int64_t), count);
|
||||
}
|
||||
|
||||
size_t WriteInt16(int16_t val) override;
|
||||
size_t WriteInt32(int32_t val) override;
|
||||
size_t WriteInt64(int64_t val) override;
|
||||
|
||||
inline size_t WriteArray(const void *buffer, size_t elem_size, size_t count) override {
|
||||
return Write(buffer, elem_size * count) / elem_size;
|
||||
}
|
||||
|
||||
inline size_t WriteArrayOfInt16(const int16_t *buffer, size_t count) override {
|
||||
return MustSwapBytes() ?
|
||||
WriteAndConvertArrayOfInt16(buffer, count) : WriteArray(buffer, sizeof(int16_t), count);
|
||||
}
|
||||
|
||||
inline size_t WriteArrayOfInt32(const int32_t *buffer, size_t count) override {
|
||||
return MustSwapBytes() ?
|
||||
WriteAndConvertArrayOfInt32(buffer, count) : WriteArray(buffer, sizeof(int32_t), count);
|
||||
}
|
||||
|
||||
inline size_t WriteArrayOfInt64(const int64_t *buffer, size_t count) override {
|
||||
return MustSwapBytes() ?
|
||||
WriteAndConvertArrayOfInt64(buffer, count) : WriteArray(buffer, sizeof(int64_t), count);
|
||||
}
|
||||
|
||||
protected:
|
||||
DataEndianess _streamEndianess;
|
||||
|
||||
// Helper methods for reading/writing arrays of basic types and
|
||||
// converting their elements to opposite endianess (swapping bytes).
|
||||
size_t ReadAndConvertArrayOfInt16(int16_t *buffer, size_t count);
|
||||
size_t ReadAndConvertArrayOfInt32(int32_t *buffer, size_t count);
|
||||
size_t ReadAndConvertArrayOfInt64(int64_t *buffer, size_t count);
|
||||
size_t WriteAndConvertArrayOfInt16(const int16_t *buffer, size_t count);
|
||||
size_t WriteAndConvertArrayOfInt32(const int32_t *buffer, size_t count);
|
||||
size_t WriteAndConvertArrayOfInt64(const int64_t *buffer, size_t count);
|
||||
|
||||
inline bool MustSwapBytes() {
|
||||
return kDefaultSystemEndianess != _streamEndianess;
|
||||
}
|
||||
|
||||
inline void ConvertInt16(int16_t &val) {
|
||||
if (MustSwapBytes()) val = BBOp::SwapBytesInt16(val);
|
||||
}
|
||||
inline void ConvertInt32(int32_t &val) {
|
||||
if (MustSwapBytes()) val = BBOp::SwapBytesInt32(val);
|
||||
}
|
||||
inline void ConvertInt64(int64_t &val) {
|
||||
if (MustSwapBytes()) val = BBOp::SwapBytesInt64(val);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// DataStreamSection wraps another stream and restricts its access
|
||||
// to a particular range of offsets of the base stream.
|
||||
// StreamSection does NOT own the base stream, and closing
|
||||
// a "stream section" does NOT close the base stream.
|
||||
// Base stream must stay in memory for as long as there are
|
||||
// "stream sections" referring it.
|
||||
class DataStreamSection : public DataStream {
|
||||
public:
|
||||
// Constructs a StreamSection over a base stream,
|
||||
// restricting working range to [start, end), i.e. end offset is
|
||||
// +1 past allowed position.
|
||||
DataStreamSection(Stream *base, soff_t start, soff_t end);
|
||||
const char *GetPath() const { return _base->GetPath().GetCStr(); }
|
||||
bool EOS() const override { return _position >= _end; }
|
||||
bool GetError() const override { return _base->GetError(); }
|
||||
soff_t GetLength() const override { return _end - _start; }
|
||||
soff_t GetPosition() const override { return _position - _start; }
|
||||
size_t Read(void *buffer, size_t len) override;
|
||||
int32_t ReadByte() override;
|
||||
size_t Write(const void *buffer, size_t len) override;
|
||||
int32_t WriteByte(uint8_t b) override;
|
||||
soff_t Seek(soff_t offset, StreamSeek origin = kSeekCurrent) override;
|
||||
bool Flush() override { return _base->Flush(); }
|
||||
void Close() override;
|
||||
|
||||
private:
|
||||
Stream *_base = nullptr;
|
||||
soff_t _start = 0;
|
||||
soff_t _end = 0;
|
||||
soff_t _position = 0;
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
230
engines/ags/shared/util/directory.cpp
Normal file
230
engines/ags/shared/util/directory.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/fs.h"
|
||||
#include "common/std/regex.h"
|
||||
#include "ags/shared/core/platform.h"
|
||||
#include "ags/shared/util/string_utils.h"
|
||||
#include "ags/shared/util/directory.h"
|
||||
#include "ags/shared/util/path.h"
|
||||
#include "ags/shared/util/stdio_compat.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
const char *SAVE_FOLDER_PREFIX = "/saves/";
|
||||
|
||||
namespace Directory {
|
||||
|
||||
bool CreateDirectory(const String &path) {
|
||||
return Common::FSNode(path.GetCStr()).createDirectory();
|
||||
}
|
||||
|
||||
bool CreateAllDirectories(const String &parent, const String &sub_dirs) {
|
||||
if (sub_dirs == SAVE_FOLDER_PREFIX)
|
||||
// ScummVM save folder doesn't need creating
|
||||
return true;
|
||||
|
||||
if (parent.IsEmpty() || !ags_directory_exists(parent.GetCStr()))
|
||||
return false; // no sense, or base dir not exist
|
||||
if (sub_dirs.IsEmpty())
|
||||
return true; // nothing to create, so fine
|
||||
|
||||
String make_path = String::FromFormat("%s/", parent.GetCStr());
|
||||
for (const char *sect = sub_dirs.GetCStr();
|
||||
sect < sub_dirs.GetCStr() + sub_dirs.GetLength();) {
|
||||
const char *cur = sect + 1;
|
||||
for (; *cur && *cur != '/' && *cur != PATH_ALT_SEPARATOR; ++cur);
|
||||
// Skip empty dirs (duplicated separators etc)
|
||||
if ((cur - sect == 1) && (*sect == '.' || *sect == '/' || *sect == PATH_ALT_SEPARATOR)) {
|
||||
sect = cur;
|
||||
continue;
|
||||
}
|
||||
// In case of ".." just fail
|
||||
if (strncmp(sect, "..", cur - sect) == 0)
|
||||
return false;
|
||||
make_path.Append(sect, cur - sect);
|
||||
if (!CreateDirectory(make_path))
|
||||
return false;
|
||||
sect = cur;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String SetCurrentDirectory(const String &path) {
|
||||
warning("TODO: SetCurrentDirectory: %s", path.GetCStr());
|
||||
// chdir(path);
|
||||
// return GetCurrentDirectory();
|
||||
return path;
|
||||
}
|
||||
|
||||
String GetCurrentDirectory() {
|
||||
#ifdef TODO
|
||||
char buf[512];
|
||||
getcwd(buf, 512);
|
||||
String str(buf);
|
||||
Path::FixupPath(str);
|
||||
return str;
|
||||
#else
|
||||
// Use / separator
|
||||
return ConfMan.getPath("path").toString('/');
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool GetFilesImpl(const String &dir_path, std::vector<String> &files, bool isDirectories) {
|
||||
Common::FSNode fsNode(dir_path.GetCStr());
|
||||
Common::FSList fsList;
|
||||
|
||||
fsNode.getChildren(fsList,
|
||||
isDirectories ? Common::FSNode::kListDirectoriesOnly :
|
||||
Common::FSNode::kListFilesOnly);
|
||||
|
||||
for (uint i = 0; i < fsList.size(); ++i)
|
||||
files.emplace_back(fsList[i].getName());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetDirs(const String &dir_path, std::vector<String> &dirs) {
|
||||
return GetFilesImpl(dir_path, dirs, true);
|
||||
}
|
||||
|
||||
bool GetFiles(const String &dir_path, std::vector<String> &files) {
|
||||
return GetFilesImpl(dir_path, files, false);
|
||||
}
|
||||
|
||||
} // namespace Directory
|
||||
|
||||
FindFile::~FindFile() {
|
||||
Close();
|
||||
}
|
||||
|
||||
FindFile FindFile::Open(const String &path, const String &wildcard, bool do_file, bool do_dir) {
|
||||
FindFile ff;
|
||||
ff._folder = Common::FSNode(path.GetCStr());
|
||||
|
||||
Common::FSNode::ListMode mode = Common::FSNode::kListAll;
|
||||
if (do_file && !do_dir)
|
||||
mode = Common::FSNode::kListFilesOnly;
|
||||
else if (!do_file && do_dir)
|
||||
mode = Common::FSNode::kListDirectoriesOnly;
|
||||
|
||||
warning("TODO: Wildcard not yet supported - %s", wildcard.GetCStr());
|
||||
|
||||
ff._folder.getChildren(ff._files, mode);
|
||||
return ff;
|
||||
}
|
||||
|
||||
void FindFile::Close() {
|
||||
_index = 0;
|
||||
}
|
||||
|
||||
bool FindFile::Next() {
|
||||
++_index;
|
||||
return _index < (int)_files.size();
|
||||
}
|
||||
|
||||
FindFileRecursive::~FindFileRecursive() {
|
||||
Close();
|
||||
}
|
||||
|
||||
FindFileRecursive FindFileRecursive::Open(const String &path, const String &wildcard, size_t max_level) {
|
||||
FindFile fdir = FindFile::OpenDirs(path);
|
||||
FindFile ffile = FindFile::OpenFiles(path, wildcard);
|
||||
if (ffile.AtEnd() && fdir.AtEnd())
|
||||
return {}; // return invalid object
|
||||
FindFileRecursive ff;
|
||||
ff._fdir = std::move(fdir);
|
||||
ff._ffile = std::move(ffile);
|
||||
// Try get the first matching entry
|
||||
if (ff._ffile.AtEnd() && !ff.Next())
|
||||
return {}; // return invalid object
|
||||
ff._maxLevel = max_level;
|
||||
ff._fullDir = path;
|
||||
ff._curFile = ff._ffile.Current();
|
||||
return ff; // success
|
||||
}
|
||||
|
||||
void FindFileRecursive::Close() {
|
||||
while (!_fdirs.empty())
|
||||
_fdirs.pop();
|
||||
_fdir.Close();
|
||||
_ffile.Close();
|
||||
}
|
||||
|
||||
bool FindFileRecursive::Next() {
|
||||
// Look up for the next file in the current dir
|
||||
if (_ffile.Next()) {
|
||||
Path::ConcatPaths(_curFile, _curDir, _ffile.Current());
|
||||
return true;
|
||||
}
|
||||
// No more files? Find a directory that still has
|
||||
while (_ffile.AtEnd()) {
|
||||
// first make sure there are unchecked subdirs left in current dir
|
||||
while (_fdir.AtEnd()) { // if not, go up, until found any, or hit the top
|
||||
if (!PopDir())
|
||||
return false; // no more directories
|
||||
}
|
||||
|
||||
// Found an unchecked subdirectory/ies, try opening one
|
||||
while (!PushDir(_fdir.Current()) && !_fdir.AtEnd())
|
||||
_fdir.Next();
|
||||
}
|
||||
Path::ConcatPaths(_curFile, _curDir, _ffile.Current());
|
||||
return true; // success
|
||||
}
|
||||
|
||||
bool FindFileRecursive::PushDir(const String &sub) {
|
||||
if (_maxLevel != SIZE_MAX && (uint32_t)_fdirs.size() == _maxLevel)
|
||||
return false; // no more nesting allowed
|
||||
|
||||
String path = Path::ConcatPaths(_fullDir, sub);
|
||||
FindFile fdir = FindFile::OpenDirs(path);
|
||||
FindFile ffile = FindFile::OpenFiles(path);
|
||||
if (ffile.AtEnd() && fdir.AtEnd())
|
||||
return false; // dir is empty, or error
|
||||
_fdirs.push(std::move(_fdir)); // save previous dir iterator
|
||||
_fdir = std::move(fdir);
|
||||
_ffile = std::move(ffile);
|
||||
_fullDir = path;
|
||||
_curDir = Path::ConcatPaths(_curDir, sub);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FindFileRecursive::PopDir() {
|
||||
if (_fdirs.empty())
|
||||
return false; // no more parent levels
|
||||
// restore parent level
|
||||
_fdir = std::move(_fdirs.top());
|
||||
_fdirs.pop();
|
||||
_fullDir = Path::GetParent(_fullDir);
|
||||
_curDir = Path::GetParent(_curDir);
|
||||
if (_curDir.Compare(".") == 0)
|
||||
_curDir = ""; // hotfix for GetParent returning "."
|
||||
// advance dir iterator that we just recovered
|
||||
_fdir.Next();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
125
engines/ags/shared/util/directory.h
Normal file
125
engines/ags/shared/util/directory.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Platform-independent Directory functions
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_DIRECTORY_H
|
||||
#define AGS_SHARED_UTIL_DIRECTORY_H
|
||||
|
||||
#include "common/fs.h"
|
||||
#include "common/stack.h"
|
||||
#include "common/std/memory.h"
|
||||
#include "ags/shared/core/platform.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
extern const char *SAVE_FOLDER_PREFIX;
|
||||
|
||||
namespace Directory {
|
||||
|
||||
// Creates new directory (if it does not exist)
|
||||
bool CreateDirectory(const String &path);
|
||||
// Makes sure all the sub-directories in the path are created. Parent path is
|
||||
// not touched, and function must fail if parent path is not accessible.
|
||||
bool CreateAllDirectories(const String &parent, const String &sub_dirs);
|
||||
// Sets current working directory, returns the resulting path
|
||||
String SetCurrentDirectory(const String &path);
|
||||
// Gets current working directory
|
||||
String GetCurrentDirectory();
|
||||
|
||||
// Get list of subdirs found in the given directory
|
||||
bool GetDirs(const String &dir_path, std::vector<String> &dirs);
|
||||
// Get list of files found in the given directory
|
||||
bool GetFiles(const String &dir_path, std::vector<String> &files);
|
||||
|
||||
} // namespace Directory
|
||||
|
||||
class FindFile {
|
||||
private:
|
||||
Common::FSNode _folder;
|
||||
Common::FSList _files;
|
||||
int _index = 0;
|
||||
|
||||
private:
|
||||
static FindFile Open(const String &path, const String &wildcard,
|
||||
bool do_file, bool do_dir);
|
||||
|
||||
public:
|
||||
FindFile() {}
|
||||
~FindFile();
|
||||
static FindFile OpenFiles(const String &path, const String &wildcard = "*") {
|
||||
return Open(path, wildcard, true, false);
|
||||
}
|
||||
static FindFile OpenDirs(const String &path, const String &wildcard = "*") {
|
||||
return Open(path, wildcard, false, true);
|
||||
}
|
||||
bool AtEnd() const {
|
||||
return (_index >= (int)_files.size());
|
||||
}
|
||||
String Current() const {
|
||||
return AtEnd() ? String() : String(_files[_index].getName().c_str());
|
||||
}
|
||||
void Close();
|
||||
bool Next();
|
||||
};
|
||||
|
||||
class FindFileRecursive {
|
||||
public:
|
||||
FindFileRecursive() {}
|
||||
~FindFileRecursive();
|
||||
|
||||
static FindFileRecursive Open(const String &path, const String &wildcard = "*",
|
||||
size_t max_level = -1);
|
||||
// TODO: directory mode, like in FindFile
|
||||
bool AtEnd() const {
|
||||
return _ffile.AtEnd();
|
||||
}
|
||||
String Current() const {
|
||||
return _curFile;
|
||||
}
|
||||
void Close();
|
||||
bool Next();
|
||||
|
||||
private:
|
||||
bool PushDir(const String &sub);
|
||||
bool PopDir();
|
||||
|
||||
Common::Stack<FindFile> _fdirs;
|
||||
FindFile _fdir; // current find dir iterator
|
||||
FindFile _ffile; // current find file iterator
|
||||
uint32_t _maxLevel = SIZE_MAX;
|
||||
String _fullDir; // full directory path
|
||||
String _curDir; // current dir path, relative to the base path
|
||||
String _curFile; // current file path with parent dirs
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
170
engines/ags/shared/util/error.h
Normal file
170
engines/ags/shared/util/error.h
Normal file
@@ -0,0 +1,170 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Universal error class, that may be used both as a return value or
|
||||
// thrown as an exception.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_ERROR_H
|
||||
#define AGS_SHARED_UTIL_ERROR_H
|
||||
|
||||
#include "common/std/memory.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class Error;
|
||||
typedef std::shared_ptr<Error> PError;
|
||||
|
||||
//
|
||||
// A simple struct, that provides several fields to describe an error in the program.
|
||||
// If wanted, may be reworked into subclass of std::exception.
|
||||
//
|
||||
class Error {
|
||||
public:
|
||||
Error(int code, String general, PError inner_error = PError()) : _code(code), _general(general), _innerError(inner_error) {
|
||||
}
|
||||
Error(int code, String general, String comment, PError inner_error = PError()) : _code(code), _general(general), _comment(comment), _innerError(inner_error) {
|
||||
}
|
||||
Error(String general, PError inner_error = PError()) : _code(0), _general(general), _innerError(inner_error) {
|
||||
}
|
||||
Error(String general, String comment, PError inner_error = PError()) : _code(0), _general(general), _comment(comment), _innerError(inner_error) {
|
||||
}
|
||||
|
||||
|
||||
// Error code is a number, defining error subtype. It is not much use to the end-user,
|
||||
// but may be checked in the program to know more precise cause of the error.
|
||||
int Code() const {
|
||||
return _code;
|
||||
}
|
||||
// General description of this error type and subtype.
|
||||
String General() const {
|
||||
return _general;
|
||||
}
|
||||
// Any complementary information.
|
||||
String Comment() const {
|
||||
return _comment;
|
||||
}
|
||||
PError InnerError() const {
|
||||
return _innerError;
|
||||
}
|
||||
// Full error message combines general description and comment.
|
||||
// NOTE: if made a child of std::exception, FullMessage may be substituted
|
||||
// or complemented with virtual const char* what().
|
||||
String FullMessage() const {
|
||||
String msg;
|
||||
const Error *err = this;
|
||||
do {
|
||||
msg.Append(err->General());
|
||||
if (!err->Comment().IsEmpty()) {
|
||||
msg.AppendChar('\n');
|
||||
msg.Append(err->Comment());
|
||||
}
|
||||
err = err->InnerError().get();
|
||||
if (err)
|
||||
msg.AppendChar('\n');
|
||||
} while (err);
|
||||
return msg;
|
||||
}
|
||||
|
||||
private:
|
||||
int _code; // numeric code, for specific uses
|
||||
String _general; // general description of this error class
|
||||
String _comment; // additional information about particular case
|
||||
PError _innerError; // previous error that caused this one
|
||||
};
|
||||
|
||||
|
||||
// ErrorHandle is a helper class that lets you have an Error object
|
||||
// wrapped in a smart pointer. ErrorHandle's only data member is a
|
||||
// shared_ptr, which means that it does not cause too much data copying
|
||||
// when used as a function's return value.
|
||||
// Note, that the reason to have distinct class instead of a shared_ptr's
|
||||
// typedef is an inverted boolean comparison:
|
||||
// shared_ptr converts to 'true' when it contains an object, but ErrorHandle
|
||||
// returns 'true' when it *does NOT* contain an object, meaning there
|
||||
// is no error.
|
||||
template <class T> class ErrorHandle {
|
||||
public:
|
||||
static ErrorHandle<T> None() {
|
||||
return ErrorHandle();
|
||||
}
|
||||
|
||||
ErrorHandle() {}
|
||||
ErrorHandle(T *err) : _error(err) {
|
||||
}
|
||||
ErrorHandle(std::shared_ptr<T> err) : _error(err) {
|
||||
}
|
||||
|
||||
bool HasError() const {
|
||||
return _error.get() != NULL;
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return _error.get() == nullptr;
|
||||
}
|
||||
operator PError() const {
|
||||
return _error;
|
||||
}
|
||||
T *operator ->() const {
|
||||
return _error.operator->();
|
||||
}
|
||||
T &operator *() const {
|
||||
return _error.operator * ();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<T> _error;
|
||||
};
|
||||
|
||||
|
||||
// Basic error handle, containing Error object
|
||||
typedef ErrorHandle<Error> HError;
|
||||
|
||||
|
||||
// TypedCodeError is the Error's subclass, which only purpose is to override
|
||||
// error code type in constructor and Code() getter, that may be useful if
|
||||
// you'd like to restrict code values to particular enumerator.
|
||||
// TODO: a type identifier as a part of template (string, or perhaps a int16
|
||||
// to be placed at high-bytes in Code) to be able to distinguish error group.
|
||||
template <typename CodeType, String(*GetErrorText)(CodeType)>
|
||||
class TypedCodeError : public Error {
|
||||
public:
|
||||
TypedCodeError(CodeType code, PError inner_error = PError()) : Error(code, GetErrorText(code), inner_error) {
|
||||
}
|
||||
TypedCodeError(CodeType code, String comment, PError inner_error = PError()) :
|
||||
Error(code, GetErrorText(code), comment, inner_error) {
|
||||
}
|
||||
|
||||
CodeType Code() const {
|
||||
return (CodeType)Error::Code();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
222
engines/ags/shared/util/file.cpp
Normal file
222
engines/ags/shared/util/file.cpp
Normal file
@@ -0,0 +1,222 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/core/platform.h"
|
||||
#include "ags/shared/util/buffered_stream.h"
|
||||
#include "ags/shared/util/directory.h"
|
||||
#include "ags/shared/util/file.h"
|
||||
#include "ags/shared/util/file_stream.h"
|
||||
#include "ags/shared/util/path.h"
|
||||
#include "ags/shared/util/stdio_compat.h"
|
||||
#include "common/file.h"
|
||||
#include "common/savefile.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
bool File::IsDirectory(const String &filename) {
|
||||
// stat() does not like trailing slashes, remove them
|
||||
String fixed_path = Path::MakePathNoSlash(filename);
|
||||
return ags_directory_exists(fixed_path.GetCStr()) != 0;
|
||||
}
|
||||
|
||||
bool File::IsFile(const String &filename) {
|
||||
return ags_file_exists(filename.GetCStr()) != 0;
|
||||
}
|
||||
|
||||
bool File::IsFileOrDir(const String &filename) {
|
||||
// stat() does not like trailing slashes, remove them
|
||||
String fixed_path = Path::MakePathNoSlash(filename);
|
||||
return ags_path_exists(fixed_path.GetCStr()) != 0;
|
||||
}
|
||||
|
||||
soff_t File::GetFileSize(const String &filename) {
|
||||
if (filename.IsEmpty())
|
||||
return 0;
|
||||
return ags_file_size(filename.GetCStr());
|
||||
}
|
||||
|
||||
bool File::TestReadFile(const String &filename) {
|
||||
if (filename.IsEmpty())
|
||||
return false;
|
||||
return ags_file_exists(filename.GetCStr());
|
||||
}
|
||||
|
||||
bool File::TestWriteFile(const String &filename) {
|
||||
if (filename.IsEmpty())
|
||||
return false;
|
||||
return TestCreateFile(filename);
|
||||
}
|
||||
|
||||
bool File::TestCreateFile(const String &filename) {
|
||||
if (filename.IsEmpty())
|
||||
return false;
|
||||
|
||||
Common::OutSaveFile *sf = g_system->getSavefileManager()->openForSaving(filename);
|
||||
bool result = sf != nullptr;
|
||||
delete sf;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool File::DeleteFile(const String &filename) {
|
||||
// Only allow deleting files in the savegame folder
|
||||
if (filename.CompareLeftNoCase(SAVE_FOLDER_PREFIX) != 0) {
|
||||
warning("Cannot delete file %s. Only files in the savegame directory can be deleted", filename.GetCStr());
|
||||
return false;
|
||||
}
|
||||
Common::String file(filename.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
|
||||
return g_system->getSavefileManager()->removeSavefile(file);
|
||||
}
|
||||
|
||||
bool File::RenameFile(const String &old_name, const String &new_name) {
|
||||
// Only allow renaming files in the savegame folder
|
||||
if (old_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX) || new_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX)) {
|
||||
warning("Cannot rename file %s to %s. Only files in the savegame directory can be renamed", old_name.GetCStr(), new_name.GetCStr());
|
||||
return false;
|
||||
}
|
||||
Common::String file_old(old_name.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
|
||||
Common::String file_new(new_name.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
|
||||
return g_system->getSavefileManager()->renameSavefile(file_old, file_new);
|
||||
}
|
||||
|
||||
bool File::CopyFile(const String &src_path, const String &dst_path, bool overwrite) {
|
||||
// Only allow copying files to the savegame folder
|
||||
// In theory it should be possible to copy any file to to save folder, but
|
||||
// let's restrict only to files that are already in the save folder for now
|
||||
if (src_path.CompareLeftNoCase(SAVE_FOLDER_PREFIX) || dst_path.CompareLeftNoCase(SAVE_FOLDER_PREFIX)) {
|
||||
warning("Cannot copy file %s to %s. Source and destination files must be in the savegame directory", src_path.GetCStr(), dst_path.GetCStr());
|
||||
return false;
|
||||
}
|
||||
if (ags_file_exists(dst_path.GetCStr()) && !overwrite) {
|
||||
warning("Cannot copy file %s to %s. File exists", src_path.GetCStr(), dst_path.GetCStr());
|
||||
return false;
|
||||
}
|
||||
Common::String file_src(src_path.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
|
||||
Common::String file_dest(dst_path.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
|
||||
return g_system->getSavefileManager()->copySavefile(file_src, file_dest);
|
||||
}
|
||||
|
||||
bool File::GetFileModesFromCMode(const String &cmode, FileOpenMode &open_mode, FileWorkMode &work_mode) {
|
||||
// We do not test for 'b' and 't' here, because text mode reading/writing should be done with
|
||||
// the use of ITextReader and ITextWriter implementations.
|
||||
// The number of supported variants here is quite limited due the restrictions AGS makes on them.
|
||||
bool read_base_mode = false;
|
||||
// Default mode is open/read for safety reasons
|
||||
open_mode = kFile_Open;
|
||||
work_mode = kFile_Read;
|
||||
for (size_t c = 0; c < cmode.GetLength(); ++c) {
|
||||
if (read_base_mode) {
|
||||
if (cmode[c] == '+') {
|
||||
work_mode = kFile_ReadWrite;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
if (cmode[c] == 'r') {
|
||||
open_mode = kFile_Open;
|
||||
work_mode = kFile_Read;
|
||||
read_base_mode = true;
|
||||
} else if (cmode[c] == 'a') {
|
||||
open_mode = kFile_Create;
|
||||
work_mode = kFile_Write;
|
||||
read_base_mode = true;
|
||||
} else if (cmode[c] == 'w') {
|
||||
open_mode = kFile_CreateAlways;
|
||||
work_mode = kFile_Write;
|
||||
read_base_mode = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return read_base_mode;
|
||||
}
|
||||
|
||||
String File::GetCMode(FileOpenMode open_mode, FileWorkMode work_mode) {
|
||||
String mode;
|
||||
if (open_mode == kFile_Open) {
|
||||
if (work_mode == kFile_Read)
|
||||
mode.AppendChar('r');
|
||||
else if (work_mode == kFile_Write || work_mode == kFile_ReadWrite)
|
||||
mode.Append("r+");
|
||||
} else if (open_mode == kFile_Create) {
|
||||
if (work_mode == kFile_Write)
|
||||
mode.AppendChar('a');
|
||||
else if (work_mode == kFile_Read || work_mode == kFile_ReadWrite)
|
||||
mode.Append("a+");
|
||||
} else if (open_mode == kFile_CreateAlways) {
|
||||
if (work_mode == kFile_Write)
|
||||
mode.AppendChar('w');
|
||||
else if (work_mode == kFile_Read || work_mode == kFile_ReadWrite)
|
||||
mode.Append("w+");
|
||||
}
|
||||
mode.AppendChar('b');
|
||||
return mode;
|
||||
}
|
||||
|
||||
Stream *File::OpenFile(const String &filename, FileOpenMode open_mode, FileWorkMode work_mode) {
|
||||
Stream *fs = nullptr;
|
||||
// try {
|
||||
fs = new BufferedStream(filename, open_mode, work_mode);
|
||||
if (fs != nullptr && !fs->IsValid()) {
|
||||
delete fs;
|
||||
fs = nullptr;
|
||||
}
|
||||
// } catch (std::runtime_error) {
|
||||
// fs = nullptr;
|
||||
// }
|
||||
|
||||
return fs;
|
||||
}
|
||||
|
||||
Stream *File::OpenStdin() {
|
||||
error("TODO: File::OpenStdin");
|
||||
}
|
||||
|
||||
Stream *File::OpenStdout() {
|
||||
error("TODO: File::OpenStdout");
|
||||
}
|
||||
|
||||
Stream *File::OpenStderr() {
|
||||
error("TODO: File::OpenStderr");
|
||||
}
|
||||
|
||||
String File::FindFileCI(const String &dir_name, const String &file_name) {
|
||||
return Path::ConcatPaths(dir_name, file_name);
|
||||
}
|
||||
|
||||
Stream *File::OpenFileCI(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode) {
|
||||
return File::OpenFile(file_name, open_mode, work_mode);
|
||||
}
|
||||
|
||||
Stream *File::OpenFile(const String &filename, soff_t start_off, soff_t end_off) {
|
||||
Stream *fs = new BufferedSectionStream(filename, start_off, end_off, kFile_Open, kFile_Read);
|
||||
if (fs != nullptr && !fs->IsValid()) {
|
||||
delete fs;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return fs;
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
119
engines/ags/shared/util/file.h
Normal file
119
engines/ags/shared/util/file.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Platform-independent File functions
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_FILE_H
|
||||
#define AGS_SHARED_UTIL_FILE_H
|
||||
|
||||
#include "ags/shared/core/platform.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
// Forward declarations
|
||||
class Stream;
|
||||
|
||||
enum FileOpenMode {
|
||||
kFile_Open, // Open existing file
|
||||
kFile_Create, // Create new file, or open existing one
|
||||
kFile_CreateAlways // Always create a new file, replacing any existing one
|
||||
};
|
||||
|
||||
enum FileWorkMode {
|
||||
kFile_Read,
|
||||
kFile_Write,
|
||||
kFile_ReadWrite
|
||||
};
|
||||
|
||||
namespace File {
|
||||
// Tells if the given path is a directory
|
||||
bool IsDirectory(const String &directory);
|
||||
// Tells if the given path is a file
|
||||
bool IsFile(const String &filename);
|
||||
// Tells if the given path is file or directory;
|
||||
// may be used to check if it's valid to use
|
||||
bool IsFileOrDir(const String &filename);
|
||||
// Returns size of a file, or -1 if no such file found
|
||||
soff_t GetFileSize(const String &filename);
|
||||
// Tests if file could be opened for reading
|
||||
bool TestReadFile(const String &filename);
|
||||
// Opens a file for writing or creates new one if it does not exist; deletes file if it was created during test
|
||||
bool TestWriteFile(const String &filename);
|
||||
// Create new empty file and deletes it; returns TRUE if was able to create file
|
||||
bool TestCreateFile(const String &filename);
|
||||
// Deletes existing file; returns TRUE if was able to delete one
|
||||
bool DeleteFile(const String &filename);
|
||||
// Renames existing file to the new name; returns TRUE on success
|
||||
bool RenameFile(const String &old_name, const String &new_name);
|
||||
// Copies a file from src_path to dst_path; returns TRUE on success
|
||||
bool CopyFile(const String &src_path, const String &dst_path, bool overwrite);
|
||||
|
||||
// Sets FileOpenMode and FileWorkMode values corresponding to C-style file open mode string
|
||||
bool GetFileModesFromCMode(const String &cmode, FileOpenMode &open_mode, FileWorkMode &work_mode);
|
||||
// Gets C-style file mode from FileOpenMode and FileWorkMode
|
||||
String GetCMode(FileOpenMode open_mode, FileWorkMode work_mode);
|
||||
|
||||
// Opens file in the given mode
|
||||
Stream *OpenFile(const String &filename, FileOpenMode open_mode, FileWorkMode work_mode);
|
||||
// Opens file for reading restricted to the arbitrary offset range
|
||||
Stream *OpenFile(const String &filename, soff_t start_off, soff_t end_off);
|
||||
// Convenience helpers
|
||||
// Create a totally new file, overwrite existing one
|
||||
inline Stream *CreateFile(const String &filename) {
|
||||
return OpenFile(filename, kFile_CreateAlways, kFile_Write);
|
||||
}
|
||||
// Open existing file for reading
|
||||
inline Stream *OpenFileRead(const String &filename) {
|
||||
return OpenFile(filename, kFile_Open, kFile_Read);
|
||||
}
|
||||
// Open existing file for writing (append) or create if it does not exist
|
||||
inline Stream *OpenFileWrite(const String &filename) {
|
||||
return OpenFile(filename, kFile_Create, kFile_Write);
|
||||
}
|
||||
|
||||
// Opens stdin stream for reading
|
||||
Stream *OpenStdin();
|
||||
// Opens stdout stream for writing
|
||||
Stream *OpenStdout();
|
||||
// Opens stderr stream for writing
|
||||
Stream *OpenStderr();
|
||||
|
||||
// Case insensitive find file
|
||||
String FindFileCI(const String &dir_name, const String &file_name);
|
||||
// Case insensitive file open: looks up for the file using FindFileCI
|
||||
Stream *OpenFileCI(const String &file_name,
|
||||
FileOpenMode open_mode = kFile_Open,
|
||||
FileWorkMode work_mode = kFile_Read);
|
||||
|
||||
} // namespace File
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
239
engines/ags/shared/util/file_stream.cpp
Normal file
239
engines/ags/shared/util/file_stream.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/util/file_stream.h"
|
||||
#include "ags/shared/util/stdio_compat.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
#include "ags/shared/util/directory.h"
|
||||
#include "ags/ags.h"
|
||||
#include "common/file.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
FileStream::FileStream(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode,
|
||||
DataEndianess stream_endianess)
|
||||
: DataStream(stream_endianess), _workMode(work_mode), _file(nullptr) {
|
||||
Open(file_name, open_mode, work_mode);
|
||||
}
|
||||
|
||||
FileStream::~FileStream() {
|
||||
FileStream::Close();
|
||||
}
|
||||
|
||||
bool FileStream::GetError() const {
|
||||
if (!_file)
|
||||
return false;
|
||||
bool err = _file->err();
|
||||
_file->clearErr();
|
||||
return err;
|
||||
}
|
||||
|
||||
void FileStream::Close() {
|
||||
delete _file;
|
||||
_file = nullptr;
|
||||
}
|
||||
|
||||
bool FileStream::Flush() {
|
||||
Common::WriteStream *ws = dynamic_cast<Common::WriteStream *>(_file);
|
||||
if (ws)
|
||||
ws->flush();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileStream::IsValid() const {
|
||||
return _file != nullptr;
|
||||
}
|
||||
|
||||
bool FileStream::EOS() const {
|
||||
Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(_file);
|
||||
return rs && rs->eos();
|
||||
}
|
||||
|
||||
soff_t FileStream::GetLength() const {
|
||||
soff_t pos = (soff_t)ags_ftell(_file);
|
||||
ags_fseek(_file, 0, SEEK_END);
|
||||
soff_t end = (soff_t)ags_ftell(_file);
|
||||
ags_fseek(_file, pos, SEEK_SET);
|
||||
return end;
|
||||
}
|
||||
|
||||
soff_t FileStream::GetPosition() const {
|
||||
if (IsValid()) {
|
||||
return (soff_t)ags_ftell(_file);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool FileStream::CanRead() const {
|
||||
return IsValid() && _workMode != kFile_Write;
|
||||
}
|
||||
|
||||
bool FileStream::CanWrite() const {
|
||||
return IsValid() && _workMode != kFile_Read;
|
||||
}
|
||||
|
||||
bool FileStream::CanSeek() const {
|
||||
return IsValid();
|
||||
}
|
||||
|
||||
size_t FileStream::Read(void *buffer, size_t size) {
|
||||
Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(_file);
|
||||
|
||||
if (rs && buffer) {
|
||||
return rs->read(buffer, size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t FileStream::ReadByte() {
|
||||
Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(_file);
|
||||
|
||||
if (rs) {
|
||||
return rs->eos() ? -1 : (int32_t)rs->readByte();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t FileStream::Write(const void *buffer, size_t size) {
|
||||
Common::WriteStream *ws = dynamic_cast<Common::WriteStream *>(_file);
|
||||
|
||||
if (ws && buffer) {
|
||||
return ws->write(buffer, size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t FileStream::WriteByte(uint8_t val) {
|
||||
Common::WriteStream *ws = dynamic_cast<Common::WriteStream *>(_file);
|
||||
|
||||
if (ws) {
|
||||
ws->writeByte(val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
soff_t FileStream::Seek(soff_t offset, StreamSeek origin) {
|
||||
int stdclib_origin;
|
||||
switch (origin) {
|
||||
case kSeekBegin:
|
||||
stdclib_origin = SEEK_SET;
|
||||
break;
|
||||
case kSeekCurrent:
|
||||
stdclib_origin = SEEK_CUR;
|
||||
break;
|
||||
case kSeekEnd:
|
||||
stdclib_origin = SEEK_END;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (ags_fseek(_file, (file_off_t)offset, stdclib_origin) == 0) ? ags_ftell(_file) : -1;
|
||||
}
|
||||
|
||||
void FileStream::Open(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode) {
|
||||
if (open_mode == kFile_Open) {
|
||||
if (!file_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX)) {
|
||||
String saveName = getSaveName(file_name);
|
||||
_file = g_system->getSavefileManager()->openForLoading(saveName);
|
||||
|
||||
} else {
|
||||
// First try to open file in game folder
|
||||
Common::ArchiveMemberPtr desc = getFile(file_name.GetCStr());
|
||||
_file = desc ? desc->createReadStream() : nullptr;
|
||||
}
|
||||
|
||||
} else {
|
||||
String saveName;
|
||||
|
||||
if (!file_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX)) {
|
||||
saveName = getSaveName(file_name);
|
||||
|
||||
} else {
|
||||
Common::String fname = file_name;
|
||||
if (fname.hasPrefix("./"))
|
||||
fname = fname.substr(2);
|
||||
else if (fname.hasPrefix("/"))
|
||||
fname.deleteChar(0);
|
||||
else if (fname.findFirstOf('/') != Common::String::npos)
|
||||
error("Invalid attempt to create file - %s", fname.c_str());
|
||||
|
||||
saveName = fname;
|
||||
}
|
||||
|
||||
_file = openForWriting(saveName, open_mode, work_mode);
|
||||
|
||||
if (!_file)
|
||||
error("Invalid attempt to create file - %s", file_name.GetCStr());
|
||||
|
||||
_path = file_name;
|
||||
}
|
||||
}
|
||||
|
||||
String FileStream::getSaveName(const String &filename) {
|
||||
return String(filename.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
|
||||
}
|
||||
|
||||
Common::OutSaveFile *FileStream::openForWriting(const String &saveName, FileOpenMode open_mode, FileWorkMode work_mode) {
|
||||
assert(open_mode != kFile_Open);
|
||||
|
||||
if (work_mode == kFile_Read || work_mode == kFile_ReadWrite)
|
||||
// In the original these modes result in [aw]+b, which seems to result
|
||||
// in a file with arbitrary reading, but writing always appending
|
||||
warning("FileOpen: independent read/write positions not supported");
|
||||
|
||||
Common::InSaveFile *existing = nullptr;
|
||||
if (open_mode == kFile_Create &&
|
||||
(existing = g_system->getSavefileManager()->openForLoading(saveName)) != nullptr) {
|
||||
// kFile_Create mode allows opening existing files for read/write.
|
||||
// Since ScummVM doesn't support this via the save file manager,
|
||||
// we need to do a bit of a hack and load the existing contents,
|
||||
// then recreate the file and write out the contents so that further
|
||||
// writes will be possible without affecting the old data
|
||||
size_t fileSize = existing->size();
|
||||
byte *data = new byte[fileSize];
|
||||
existing->read(data, fileSize);
|
||||
delete existing;
|
||||
|
||||
Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving(saveName, false);
|
||||
assert(out);
|
||||
|
||||
out->write(data, fileSize);
|
||||
delete[] data;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
return g_system->getSavefileManager()->openForSaving(saveName, false);
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
90
engines/ags/shared/util/file_stream.h
Normal file
90
engines/ags/shared/util/file_stream.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_FILE_STREAM_H
|
||||
#define AGS_SHARED_UTIL_FILE_STREAM_H
|
||||
|
||||
#include "common/savefile.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/std/functional.h"
|
||||
#include "ags/shared/util/data_stream.h"
|
||||
#include "ags/shared/util/file.h" // TODO: extract filestream mode constants
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class FileStream : public DataStream {
|
||||
public:
|
||||
struct CloseNotifyArgs {
|
||||
String Filepath;
|
||||
FileWorkMode WorkMode;
|
||||
};
|
||||
|
||||
// Represents an open file object
|
||||
// The constructor may raise std::runtime_error if
|
||||
// - there is an issue opening the file (does not exist, locked, permissions, etc)
|
||||
// - the open mode could not be determined
|
||||
FileStream(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode,
|
||||
DataEndianess stream_endianess = kLittleEndian);
|
||||
~FileStream() override;
|
||||
|
||||
FileWorkMode GetWorkMode() const { return _workMode; }
|
||||
|
||||
// Tells if there were errors during previous io operation(s);
|
||||
// the call to GetError() *resets* the error record.
|
||||
bool GetError() const override;
|
||||
void Close() override;
|
||||
bool Flush() override;
|
||||
|
||||
// Is stream valid (underlying data initialized properly)
|
||||
bool IsValid() const override;
|
||||
// Is end of stream
|
||||
bool EOS() const override;
|
||||
// Total length of stream (if known)
|
||||
soff_t GetLength() const override;
|
||||
// Current position (if known)
|
||||
soff_t GetPosition() const override;
|
||||
bool CanRead() const override;
|
||||
bool CanWrite() const override;
|
||||
bool CanSeek() const override;
|
||||
|
||||
size_t Read(void *buffer, size_t size) override;
|
||||
int32_t ReadByte() override;
|
||||
size_t Write(const void *buffer, size_t size) override;
|
||||
int32_t WriteByte(uint8_t b) override;
|
||||
|
||||
soff_t Seek(soff_t offset, StreamSeek origin) override;
|
||||
|
||||
private:
|
||||
void Open(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode);
|
||||
String getSaveName(const String &filename);
|
||||
Common::OutSaveFile *openForWriting(const String &saveName, FileOpenMode open_mode, FileWorkMode work_mode);
|
||||
|
||||
Common::Stream *_file;
|
||||
const FileWorkMode _workMode;
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
133
engines/ags/shared/util/geometry.cpp
Normal file
133
engines/ags/shared/util/geometry.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/util/geometry.h"
|
||||
#include "common/std/algorithm.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
bool AreRectsIntersecting(const Rect &r1, const Rect &r2) {
|
||||
// NOTE: remember that in AGS Y axis is pointed downwards (top < bottom)
|
||||
return r1.Left <= r2.Right && r1.Right >= r2.Left &&
|
||||
r1.Top <= r2.Bottom && r1.Bottom >= r2.Top;
|
||||
}
|
||||
|
||||
bool IsRectInsideRect(const Rect &place, const Rect &item) {
|
||||
return item.Left >= place.Left && item.Right <= place.Right &&
|
||||
item.Top >= place.Top && item.Bottom <= place.Bottom;
|
||||
}
|
||||
|
||||
float DistanceBetween(const Rect &r1, const Rect &r2) {
|
||||
// https://gamedev.stackexchange.com/a/154040
|
||||
Rect rect_outer(
|
||||
MIN(r1.Left, r2.Left),
|
||||
MIN(r1.Top, r2.Top),
|
||||
MAX(r1.Right, r2.Right),
|
||||
MAX(r1.Bottom, r2.Bottom)
|
||||
);
|
||||
int inner_width = MAX(0, rect_outer.GetWidth() - r1.GetWidth() - r2.GetWidth());
|
||||
int inner_height = MAX(0, rect_outer.GetHeight() - r1.GetHeight() - r2.GetHeight());
|
||||
return static_cast<float>(std::sqrt((inner_width * inner_width) + (inner_height * inner_height)));
|
||||
}
|
||||
|
||||
Size ProportionalStretch(int dest_w, int dest_h, int item_w, int item_h) {
|
||||
int width = item_w ? dest_w : 0;
|
||||
int height = item_w ? (dest_w * item_h / item_w) : 0;
|
||||
if (height > dest_h) {
|
||||
width = item_h ? (dest_h * item_w / item_h) : 0;
|
||||
height = dest_h;
|
||||
}
|
||||
return Size(width, height);
|
||||
}
|
||||
|
||||
Size ProportionalStretch(const Size &dest, const Size &item) {
|
||||
return ProportionalStretch(dest.Width, dest.Height, item.Width, item.Height);
|
||||
}
|
||||
|
||||
int AlignInHRange(int x1, int x2, int off_x, int width, FrameAlignment align) {
|
||||
if (align & kMAlignRight)
|
||||
return off_x + x2 - width;
|
||||
else if (align & kMAlignHCenter)
|
||||
return off_x + x1 + ((x2 - x1 + 1) >> 1) - (width >> 1);
|
||||
return off_x + x1; // kAlignLeft is default
|
||||
}
|
||||
|
||||
int AlignInVRange(int y1, int y2, int off_y, int height, FrameAlignment align) {
|
||||
if (align & kMAlignBottom)
|
||||
return off_y + y2 - height;
|
||||
else if (align & kMAlignVCenter)
|
||||
return off_y + y1 + ((y2 - y1 + 1) >> 1) - (height >> 1);
|
||||
return off_y + y1; // kAlignTop is default
|
||||
}
|
||||
|
||||
Rect AlignInRect(const Rect &frame, const Rect &item, FrameAlignment align) {
|
||||
int x = AlignInHRange(frame.Left, frame.Right, item.Left, item.GetWidth(), align);
|
||||
int y = AlignInVRange(frame.Top, frame.Bottom, item.Top, item.GetHeight(), align);
|
||||
|
||||
Rect dst_item = item;
|
||||
dst_item.MoveTo(Point(x, y));
|
||||
return dst_item;
|
||||
}
|
||||
|
||||
Rect OffsetRect(const Rect &r, const Point off) {
|
||||
return Rect(r.Left + off.X, r.Top + off.Y, r.Right + off.X, r.Bottom + off.Y);
|
||||
}
|
||||
|
||||
Rect CenterInRect(const Rect &place, const Rect &item) {
|
||||
return RectWH((place.GetWidth() >> 1) - (item.GetWidth() >> 1),
|
||||
(place.GetHeight() >> 1) - (item.GetHeight() >> 1),
|
||||
item.GetWidth(), item.GetHeight());
|
||||
}
|
||||
|
||||
Rect ClampToRect(const Rect &place, const Rect &item) {
|
||||
return Rect(
|
||||
AGSMath::Clamp(item.Left, place.Left, place.Right),
|
||||
AGSMath::Clamp(item.Top, place.Top, place.Bottom),
|
||||
AGSMath::Clamp(item.Right, place.Left, place.Right),
|
||||
AGSMath::Clamp(item.Bottom, place.Top, place.Bottom)
|
||||
);
|
||||
}
|
||||
|
||||
Rect PlaceInRect(const Rect &place, const Rect &item, const RectPlacement &placement) {
|
||||
switch (placement) {
|
||||
case kPlaceCenter:
|
||||
return CenterInRect(place, item);
|
||||
case kPlaceStretch:
|
||||
return place;
|
||||
case kPlaceStretchProportional:
|
||||
return CenterInRect(place,
|
||||
RectWH(ProportionalStretch(place.GetWidth(), place.GetHeight(), item.GetWidth(), item.GetHeight())));
|
||||
default:
|
||||
return RectWH(place.Left + item.Left, place.Top + item.Top, item.GetWidth(), item.GetHeight());
|
||||
}
|
||||
}
|
||||
|
||||
Rect SumRects(const Rect &r1, const Rect &r2) { // NOTE: remember that in AGS Y axis is pointed downwards (top < bottom)
|
||||
return Rect(MIN(r1.Left, r2.Left), MIN(r1.Top, r2.Top),
|
||||
MAX(r1.Right, r2.Right), MAX(r1.Bottom, r2.Bottom));
|
||||
}
|
||||
|
||||
Rect IntersectRects(const Rect &r1, const Rect &r2) { // NOTE: the result may be empty (negative) rect if there's no intersection
|
||||
return Rect(MAX(r1.Left, r2.Left), MAX(r1.Top, r2.Top),
|
||||
MIN(r1.Right, r2.Right), MIN(r1.Bottom, r2.Bottom));
|
||||
}
|
||||
|
||||
} // namespace AGS3
|
||||
388
engines/ags/shared/util/geometry.h
Normal file
388
engines/ags/shared/util/geometry.h
Normal file
@@ -0,0 +1,388 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Geometry data structures and helper functions
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_GEOMETRY_H
|
||||
#define AGS_SHARED_UTIL_GEOMETRY_H
|
||||
|
||||
#include "ags/shared/util/math.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
namespace AGSMath = AGS::Shared::Math;
|
||||
//namespace AGS
|
||||
//{
|
||||
//namespace Shared
|
||||
//{
|
||||
|
||||
// Type of alignment of a geometric item of rectangular boundaries.
|
||||
enum FrameAlignment {
|
||||
kAlignNone = 0,
|
||||
|
||||
// Alignment options are representing 8 sides of a frame (rectangle);
|
||||
// they are implemented as flags that may be combined together if it
|
||||
// is wanted to define alignment to multiple sides at once.
|
||||
kAlignTopLeft = 0x0001,
|
||||
kAlignTopCenter = 0x0002,
|
||||
kAlignTopRight = 0x0004,
|
||||
kAlignMiddleLeft = 0x0008,
|
||||
kAlignMiddleCenter = 0x0010,
|
||||
kAlignMiddleRight = 0x0020,
|
||||
kAlignBottomLeft = 0x0040,
|
||||
kAlignBottomCenter = 0x0080,
|
||||
kAlignBottomRight = 0x0100,
|
||||
|
||||
// Masks are helping to determine whether alignment parameter contains
|
||||
// particular horizontal or vertical component (for example: left side
|
||||
// or bottom side)
|
||||
kMAlignLeft = kAlignTopLeft | kAlignMiddleLeft | kAlignBottomLeft,
|
||||
kMAlignRight = kAlignTopRight | kAlignMiddleRight | kAlignBottomRight,
|
||||
kMAlignTop = kAlignTopLeft | kAlignTopCenter | kAlignTopRight,
|
||||
kMAlignBottom = kAlignBottomLeft | kAlignBottomCenter | kAlignBottomRight,
|
||||
kMAlignHCenter = kAlignTopCenter | kAlignMiddleCenter | kAlignBottomCenter,
|
||||
kMAlignVCenter = kAlignMiddleLeft | kAlignMiddleCenter | kAlignMiddleRight
|
||||
};
|
||||
|
||||
// Horizontal alignment; based on FrameAlignment, used to restrict alignment
|
||||
// setting to left/right/center option, while keeping compatibility with any
|
||||
// alignment in case it will be supported in the future.
|
||||
enum HorAlignment {
|
||||
kHAlignNone = kAlignNone,
|
||||
kHAlignLeft = kAlignTopLeft,
|
||||
kHAlignRight = kAlignTopRight,
|
||||
kHAlignCenter = kAlignTopCenter
|
||||
};
|
||||
|
||||
enum RectPlacement {
|
||||
kPlaceOffset,
|
||||
kPlaceCenter,
|
||||
kPlaceStretch,
|
||||
kPlaceStretchProportional,
|
||||
kNumRectPlacement
|
||||
};
|
||||
|
||||
struct Point {
|
||||
int X;
|
||||
int Y;
|
||||
|
||||
Point() {
|
||||
X = 0;
|
||||
Y = 0;
|
||||
}
|
||||
|
||||
Point(int x, int y) {
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
inline bool operator ==(const Point &p) const {
|
||||
return X == p.X && Y == p.Y;
|
||||
}
|
||||
|
||||
inline bool operator !=(const Point &p) const {
|
||||
return X != p.X || Y != p.Y;
|
||||
}
|
||||
|
||||
inline Point operator +(const Point &p) const {
|
||||
return Point(X + p.X, Y + p.Y);
|
||||
}
|
||||
|
||||
inline bool Equals(const int x, const int y) const {
|
||||
return X == x && Y == y;
|
||||
}
|
||||
};
|
||||
|
||||
struct Line {
|
||||
int X1;
|
||||
int Y1;
|
||||
int X2;
|
||||
int Y2;
|
||||
|
||||
Line() {
|
||||
X1 = 0;
|
||||
Y1 = 0;
|
||||
X2 = 0;
|
||||
Y2 = 0;
|
||||
}
|
||||
|
||||
Line(int x1, int y1, int x2, int y2) {
|
||||
X1 = x1;
|
||||
Y1 = y1;
|
||||
X2 = x2;
|
||||
Y2 = y2;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper factory functions
|
||||
inline Line HLine(int x1, int x2, int y) {
|
||||
return Line(x1, y, x2, y);
|
||||
}
|
||||
|
||||
inline Line VLine(int x, int y1, int y2) {
|
||||
return Line(x, y1, x, y2);
|
||||
}
|
||||
|
||||
struct Size {
|
||||
int Width;
|
||||
int Height;
|
||||
|
||||
Size() {
|
||||
Width = 0;
|
||||
Height = 0;
|
||||
}
|
||||
|
||||
Size(int width, int height) {
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
inline bool IsNull() const {
|
||||
return Width <= 0 || Height <= 0;
|
||||
}
|
||||
|
||||
inline static Size Clamp(const Size &sz, const Size &floor, const Size &ceil) {
|
||||
return Size(AGSMath::Clamp(sz.Width, floor.Width, ceil.Width),
|
||||
AGSMath::Clamp(sz.Height, floor.Height, ceil.Height));
|
||||
}
|
||||
|
||||
// Indicates if current size exceeds other size by any metric
|
||||
inline bool ExceedsByAny(const Size &size) const {
|
||||
return Width > size.Width || Height > size.Height;
|
||||
}
|
||||
|
||||
inline bool operator==(const Size &size) const {
|
||||
return Width == size.Width && Height == size.Height;
|
||||
}
|
||||
|
||||
inline bool operator!=(const Size &size) const {
|
||||
return Width != size.Width || Height != size.Height;
|
||||
}
|
||||
|
||||
inline bool operator<(const Size &other) const { // TODO: this implementation is silly and not universally useful; make a realistic one and replace with another function where necessary
|
||||
return Width < other.Width || (Width == other.Width && Height < other.Height);
|
||||
}
|
||||
|
||||
inline Size operator+(const Size &size) const {
|
||||
return Size(Width + size.Width, Height + size.Height);
|
||||
}
|
||||
|
||||
inline Size operator-(const Size &size) const {
|
||||
return Size(Width - size.Width, Height - size.Height);
|
||||
}
|
||||
|
||||
inline Size operator *(int x) const {
|
||||
return Size(Width * x, Height * x);
|
||||
}
|
||||
|
||||
inline Size operator /(int x) const {
|
||||
return Size(Width / x, Height / x);
|
||||
}
|
||||
|
||||
inline Size &operator *=(int x) {
|
||||
Width *= x;
|
||||
Height *= x;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Size &operator /=(int x) {
|
||||
Width /= x;
|
||||
Height /= x;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: consider making Rect have right-bottom coordinate with +1 offset
|
||||
// to comply with many other libraries (i.e. Right - Left == Width)
|
||||
struct Rect {
|
||||
int Left;
|
||||
int Top;
|
||||
int Right;
|
||||
int Bottom;
|
||||
|
||||
Rect() {
|
||||
Left = 0;
|
||||
Top = 0;
|
||||
Right = -1;
|
||||
Bottom = -1;
|
||||
}
|
||||
|
||||
Rect(int l, int t, int r, int b) {
|
||||
Left = l;
|
||||
Top = t;
|
||||
Right = r;
|
||||
Bottom = b;
|
||||
}
|
||||
|
||||
inline Point GetLT() const {
|
||||
return Point(Left, Top);
|
||||
}
|
||||
|
||||
inline Point GetCenter() const {
|
||||
return Point(Left + GetWidth() / 2, Top + GetHeight() / 2);
|
||||
}
|
||||
|
||||
inline int GetWidth() const {
|
||||
return Right - Left + 1;
|
||||
}
|
||||
|
||||
inline int GetHeight() const {
|
||||
return Bottom - Top + 1;
|
||||
}
|
||||
|
||||
inline Size GetSize() const {
|
||||
return Size(GetWidth(), GetHeight());
|
||||
}
|
||||
|
||||
inline bool IsEmpty() const {
|
||||
return Right < Left || Bottom < Top;
|
||||
}
|
||||
|
||||
inline bool IsInside(int x, int y) const {
|
||||
return x >= Left && y >= Top && (x <= Right) && (y <= Bottom);
|
||||
}
|
||||
|
||||
inline bool IsInside(const Point &pt) const {
|
||||
return IsInside(pt.X, pt.Y);
|
||||
}
|
||||
|
||||
inline void MoveToX(int x) {
|
||||
Right += x - Left;
|
||||
Left = x;
|
||||
}
|
||||
|
||||
inline void MoveToY(int y) {
|
||||
Bottom += y - Top;
|
||||
Top = y;
|
||||
}
|
||||
|
||||
inline void MoveTo(const Point &pt) {
|
||||
MoveToX(pt.X);
|
||||
MoveToY(pt.Y);
|
||||
}
|
||||
|
||||
inline void SetWidth(int width) {
|
||||
Right = Left + width - 1;
|
||||
}
|
||||
|
||||
inline void SetHeight(int height) {
|
||||
Bottom = Top + height - 1;
|
||||
}
|
||||
|
||||
inline static Rect MoveBy(const Rect &r, int x, int y) {
|
||||
return Rect(r.Left + x, r.Top + y, r.Right + x, r.Bottom + y);
|
||||
}
|
||||
|
||||
inline bool operator ==(const Rect &r) const {
|
||||
return Left == r.Left && Top == r.Top &&
|
||||
Right == r.Right && Bottom == r.Bottom;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper factory function
|
||||
inline Rect RectWH(int x, int y, int width, int height) {
|
||||
return Rect(x, y, x + width - 1, y + height - 1);
|
||||
}
|
||||
|
||||
inline Rect RectWH(const Size &sz) {
|
||||
return Rect(0, 0, sz.Width - 1, sz.Height - 1);
|
||||
}
|
||||
|
||||
|
||||
struct Triangle {
|
||||
int X1;
|
||||
int Y1;
|
||||
int X2;
|
||||
int Y2;
|
||||
int X3;
|
||||
int Y3;
|
||||
|
||||
Triangle() {
|
||||
X1 = 0;
|
||||
Y1 = 0;
|
||||
X2 = 0;
|
||||
Y2 = 0;
|
||||
X3 = 0;
|
||||
Y3 = 0;
|
||||
}
|
||||
|
||||
Triangle(int x1, int y1, int x2, int y2, int x3, int y3) {
|
||||
X1 = x1;
|
||||
Y1 = y1;
|
||||
X2 = x2;
|
||||
Y2 = y2;
|
||||
X3 = x3;
|
||||
Y3 = y3;
|
||||
}
|
||||
};
|
||||
|
||||
struct Circle {
|
||||
int X;
|
||||
int Y;
|
||||
int Radius;
|
||||
|
||||
Circle() {
|
||||
X = 0;
|
||||
Y = 0;
|
||||
Radius = 0;
|
||||
}
|
||||
|
||||
Circle(int x, int y, int radius) {
|
||||
X = x;
|
||||
Y = y;
|
||||
Radius = radius;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Tells if two rectangles intersect (overlap) at least partially
|
||||
bool AreRectsIntersecting(const Rect &r1, const Rect &r2);
|
||||
// Tells if the item is completely inside place
|
||||
bool IsRectInsideRect(const Rect &place, const Rect &item);
|
||||
// Calculates a distance between two axis-aligned rectangles, returns 0 if they intersect
|
||||
float DistanceBetween(const Rect &r1, const Rect &r2);
|
||||
|
||||
int AlignInHRange(int x1, int x2, int off_x, int width, FrameAlignment align);
|
||||
int AlignInVRange(int y1, int y2, int off_y, int height, FrameAlignment align);
|
||||
Rect AlignInRect(const Rect &frame, const Rect &item, FrameAlignment align);
|
||||
|
||||
Size ProportionalStretch(int dest_w, int dest_h, int item_w, int item_h);
|
||||
Size ProportionalStretch(const Size &dest, const Size &item);
|
||||
|
||||
Rect OffsetRect(const Rect &r, const Point off);
|
||||
Rect CenterInRect(const Rect &place, const Rect &item);
|
||||
Rect ClampToRect(const Rect &place, const Rect &item);
|
||||
Rect PlaceInRect(const Rect &place, const Rect &item, const RectPlacement &placement);
|
||||
// Sum two rectangles, the result is the rectangle bounding them both
|
||||
Rect SumRects(const Rect &r1, const Rect &r2);
|
||||
// Intersect two rectangles, the resolt is the rectangle bounding their intersection
|
||||
Rect IntersectRects(const Rect &r1, const Rect &r2);
|
||||
|
||||
//} // namespace Shared
|
||||
//} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
111
engines/ags/shared/util/iags_stream.h
Normal file
111
engines/ags/shared/util/iags_stream.h
Normal file
@@ -0,0 +1,111 @@
|
||||
//=============================================================================
|
||||
//
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// IAGSStream is a contract for stream class, provided by engine to plugin
|
||||
// on the need, such as saving/restoring the game.
|
||||
// The user is advised to use advanced helper methods, such as Read/WriteX
|
||||
// and Read/WriteArrayOfX to allow the stream implementation properly control
|
||||
// endianness conversions and data padding, when needed.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_API_STREAM_API_H
|
||||
#define AGS_SHARED_API_STREAM_API_H
|
||||
|
||||
// TODO: it would probably be better to not include core definition headers
|
||||
// in API class headers, but make separate core headers specifically for
|
||||
// plugins, and let plugin developers include them manually in plugin sources.
|
||||
#include "ags/shared/core/types.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
enum StreamSeek {
|
||||
kSeekBegin,
|
||||
kSeekCurrent,
|
||||
kSeekEnd
|
||||
};
|
||||
|
||||
class IAGSStream {
|
||||
public:
|
||||
virtual ~IAGSStream() {}
|
||||
|
||||
virtual void Close() = 0;
|
||||
|
||||
virtual bool IsValid() const = 0;
|
||||
virtual bool EOS() const = 0;
|
||||
virtual soff_t GetLength() const = 0;
|
||||
virtual soff_t GetPosition() const = 0;
|
||||
virtual bool CanRead() const = 0;
|
||||
virtual bool CanWrite() const = 0;
|
||||
virtual bool CanSeek() const = 0;
|
||||
|
||||
// Reads number of bytes in the provided buffer
|
||||
virtual size_t Read(void *buffer, size_t size) = 0;
|
||||
// ReadByte conforms to fgetc behavior:
|
||||
// - if stream is valid, then returns an *unsigned char* packed in the int
|
||||
// - if EOS, then returns -1
|
||||
virtual int32_t ReadByte() = 0;
|
||||
// Writes number of bytes from the provided buffer
|
||||
virtual size_t Write(const void *buffer, size_t size) = 0;
|
||||
// WriteByte conforms to fputc behavior:
|
||||
// - on success, returns the unsigned char packed in the int
|
||||
// - on failure, returns -1
|
||||
virtual int32_t WriteByte(uint8_t b) = 0;
|
||||
|
||||
// Convenience methods for reading values of particular size
|
||||
virtual int8_t ReadInt8() = 0;
|
||||
virtual int16_t ReadInt16() = 0;
|
||||
virtual int32_t ReadInt32() = 0;
|
||||
virtual int64_t ReadInt64() = 0;
|
||||
virtual bool ReadBool() = 0;
|
||||
virtual size_t ReadArray(void *buffer, size_t elem_size, size_t count) = 0;
|
||||
virtual size_t ReadArrayOfInt8(int8_t *buffer, size_t count) = 0;
|
||||
virtual size_t ReadArrayOfInt16(int16_t *buffer, size_t count) = 0;
|
||||
virtual size_t ReadArrayOfInt32(int32_t *buffer, size_t count) = 0;
|
||||
virtual size_t ReadArrayOfInt64(int64_t *buffer, size_t count) = 0;
|
||||
|
||||
// Convenience methods for writing values of particular size
|
||||
virtual size_t WriteInt8(int8_t val) = 0;
|
||||
virtual size_t WriteInt16(int16_t val) = 0;
|
||||
virtual size_t WriteInt32(int32_t val) = 0;
|
||||
virtual size_t WriteInt64(int64_t val) = 0;
|
||||
virtual size_t WriteBool(bool val) = 0;
|
||||
virtual size_t WriteArray(const void *buffer, size_t elem_size, size_t count) = 0;
|
||||
virtual size_t WriteArrayOfInt8(const int8_t *buffer, size_t count) = 0;
|
||||
virtual size_t WriteArrayOfInt16(const int16_t *buffer, size_t count) = 0;
|
||||
virtual size_t WriteArrayOfInt32(const int32_t *buffer, size_t count) = 0;
|
||||
virtual size_t WriteArrayOfInt64(const int64_t *buffer, size_t count) = 0;
|
||||
|
||||
virtual soff_t Seek(soff_t offset, StreamSeek origin = kSeekCurrent) = 0;
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
261
engines/ags/shared/util/ini_file.cpp
Normal file
261
engines/ags/shared/util/ini_file.cpp
Normal file
@@ -0,0 +1,261 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/util.h"
|
||||
#include "ags/shared/util/ini_file.h"
|
||||
#include "ags/shared/util/text_stream_reader.h"
|
||||
#include "ags/shared/util/text_stream_writer.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
inline static void ReplaceSubString(String &line, IniFile::StrPos &sub_pos, const String &new_sub) {
|
||||
line.ReplaceMid(sub_pos.first, sub_pos.second - sub_pos.first, new_sub);
|
||||
}
|
||||
|
||||
IniFile::ItemDef::ItemDef(const String &key, const String &value) {
|
||||
Line = String::FromFormat("%s=%s", key.GetCStr(), value.GetCStr());
|
||||
Key.first = 0;
|
||||
Key.second = Key.first + key.GetLength();
|
||||
SepAt = Key.second;
|
||||
Value.first = Key.second + 1;
|
||||
Value.second = Value.first + value.GetLength();
|
||||
}
|
||||
|
||||
IniFile::ItemDef::ItemDef(const String &line, const StrPos &key, const StrPos &value, size_t sep_at) {
|
||||
Line = line;
|
||||
Key = key;
|
||||
Value = value;
|
||||
SepAt = sep_at;
|
||||
}
|
||||
|
||||
void IniFile::ItemDef::SetKey(const String &key) {
|
||||
if (key.IsEmpty())
|
||||
return;
|
||||
|
||||
if (IsKeyValue()) {
|
||||
size_t diff = key.GetLength() - (Key.second - Key.first);
|
||||
ReplaceSubString(Line, Key, key);
|
||||
Key.second += diff;
|
||||
Value.first += diff;
|
||||
Value.second += diff;
|
||||
} else {
|
||||
*this = ItemDef(key, "");
|
||||
}
|
||||
}
|
||||
|
||||
void IniFile::ItemDef::SetValue(const String &value) {
|
||||
if (!IsKeyValue())
|
||||
return; // no key
|
||||
|
||||
if (SepAt != String::NoIndex) { // replacing existing value
|
||||
size_t diff = value.GetLength() - (Value.second - Value.first);
|
||||
ReplaceSubString(Line, Value, value);
|
||||
Value.second += diff;
|
||||
} else { // inserting value behind the key
|
||||
StrPos valpos(Key.second, Key.second);
|
||||
ReplaceSubString(Line, valpos, String::FromFormat("=%s", value.GetCStr()));
|
||||
}
|
||||
}
|
||||
|
||||
IniFile::SectionDef::SectionDef(const String &name) {
|
||||
if (name.IsEmpty()) {
|
||||
// global section
|
||||
Name = StrPos(0, 0);
|
||||
} else {
|
||||
// named section
|
||||
Header = String::FromFormat("[%s]", name.GetCStr());
|
||||
Name.first = 1;
|
||||
Name.second = 1 + Header.GetLength();
|
||||
}
|
||||
}
|
||||
|
||||
IniFile::SectionDef::SectionDef(const String &line, const StrPos &name) {
|
||||
Header = line;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
void IniFile::SectionDef::SetName(const String &sec_name) {
|
||||
if (sec_name.IsEmpty())
|
||||
return;
|
||||
|
||||
size_t diff = sec_name.GetLength() - (Name.second - Name.first);
|
||||
ReplaceSubString(Header, Name, sec_name);
|
||||
Name.second += diff;
|
||||
}
|
||||
|
||||
void IniFile::SectionDef::Clear() {
|
||||
Items.clear();
|
||||
}
|
||||
|
||||
IniFile::ItemIterator IniFile::SectionDef::InsertItem(ItemIterator item, const ItemDef &itemdef) {
|
||||
return Items.insert(item, itemdef);
|
||||
}
|
||||
|
||||
void IniFile::SectionDef::EraseItem(ItemIterator item) {
|
||||
Items.erase(item);
|
||||
}
|
||||
|
||||
IniFile::ItemIterator IniFile::InsertItem(SectionIterator sec, ItemIterator item, const String &key, const String &value) {
|
||||
ItemDef itemdef(key, value);
|
||||
return sec->InsertItem(item, itemdef);
|
||||
}
|
||||
|
||||
IniFile::SectionIterator IniFile::InsertSection(SectionIterator sec, const String &name) {
|
||||
if (name.IsEmpty())
|
||||
return _sections.end(); // do not allow adding random global sections
|
||||
|
||||
SectionDef secdef(name);
|
||||
return _sections.insert(sec, secdef);
|
||||
}
|
||||
|
||||
void IniFile::RemoveItem(SectionIterator sec, ItemIterator item) {
|
||||
sec->EraseItem(item);
|
||||
}
|
||||
|
||||
void IniFile::RemoveSection(SectionIterator sec) {
|
||||
if (sec == _sections.begin())
|
||||
// do not remove global section, clear items instead
|
||||
sec->Clear();
|
||||
else
|
||||
_sections.erase(sec);
|
||||
}
|
||||
|
||||
|
||||
// Moves string pointer forward to the first non-space character
|
||||
const char *SkipSpace(const char *line, const char *endl) {
|
||||
for (; line != endl && Common::isSpace(*line); ++line);
|
||||
return line;
|
||||
}
|
||||
|
||||
// Parse given line and extract a meaningful string;
|
||||
// Parses line from 'line' to 'endl', skips padding (spaces)
|
||||
// at the beginning and the end. Assignes the starting and ending
|
||||
// pointers of the string. Returns pointer to where parsing stopped.
|
||||
// The 'endl' must point beyond the last character of the string
|
||||
// (e.g. terminator).
|
||||
const char *ParsePaddedString(const char *line, const char *endl,
|
||||
const char *&str_at, const char *&str_end) {
|
||||
// skip left padding
|
||||
for (; line != endl && Common::isBlank(*line); ++line);
|
||||
str_at = line;
|
||||
// skip right padding
|
||||
const char *p_value = line;
|
||||
for (line = endl; line != p_value && Common::isBlank(*(line - 1)); --line);
|
||||
str_end = line;
|
||||
return line;
|
||||
}
|
||||
|
||||
IniFile::IniFile() {
|
||||
// precreate global section
|
||||
_sections.push_back(SectionDef(""));
|
||||
}
|
||||
|
||||
void IniFile::Read(Stream *in) {
|
||||
TextStreamReader reader(in);
|
||||
|
||||
_sections.clear();
|
||||
// Create a global section;
|
||||
// all the items we meet before explicit section declaration
|
||||
// will be treated as "global" items.
|
||||
_sections.push_back(SectionDef(""));
|
||||
SectionDef *cur_section = &_sections.back();
|
||||
|
||||
do {
|
||||
String line = reader.ReadLine();
|
||||
if (line.IsEmpty() && reader.EOS())
|
||||
break;
|
||||
|
||||
const char *cstr = line.GetCStr();
|
||||
const char *pstr = cstr;
|
||||
const char *endl = cstr + line.GetLength();
|
||||
|
||||
// Find first non-space character
|
||||
pstr = SkipSpace(pstr, endl);
|
||||
if (pstr == endl)
|
||||
continue; // empty line
|
||||
|
||||
// Detect the kind of string we found
|
||||
if ((endl - pstr >= 2 && *pstr == '/' && *(pstr + 1) == '/') ||
|
||||
(endl - pstr >= 1 && (*pstr == '#' || *pstr == ';'))) {
|
||||
StrPos nullpos(0, 0);
|
||||
cur_section->InsertItem(cur_section->End(), ItemDef(line, nullpos, nullpos, String::NoIndex));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*pstr == '[') {
|
||||
// Parse this as section
|
||||
const char *pstr_end = strrchr(pstr, ']');
|
||||
if (pstr_end < pstr)
|
||||
continue; // no closing bracket
|
||||
// Parse the section name
|
||||
const char *str_at, *str_end;
|
||||
ParsePaddedString(++pstr, pstr_end, str_at, str_end);
|
||||
if (str_end == str_at)
|
||||
continue; // inappropriate data or empty string
|
||||
StrPos namepos(str_at - cstr, str_end - cstr);
|
||||
_sections.push_back(SectionDef(line, namepos));
|
||||
cur_section = &_sections.back();
|
||||
} else {
|
||||
// Parse this as a key-value pair
|
||||
const char *pstr_end = strchr(pstr, '=');
|
||||
if (pstr_end == pstr)
|
||||
continue; // no key part, skip the line
|
||||
if (!pstr_end)
|
||||
pstr_end = endl; // no value part
|
||||
// Parse key
|
||||
const char *str_at, *str_end;
|
||||
ParsePaddedString(pstr, pstr_end, str_at, str_end);
|
||||
pstr = pstr_end;
|
||||
if (str_end == str_at)
|
||||
continue; // inappropriate data or empty string
|
||||
// Create an item and parse value, if any
|
||||
StrPos keypos(str_at - cstr, str_end - cstr);
|
||||
StrPos valpos(0, 0);
|
||||
size_t sep_at = String::NoIndex;
|
||||
if (pstr != endl) {
|
||||
sep_at = pstr - cstr;
|
||||
ParsePaddedString(++pstr, endl, str_at, str_end);
|
||||
valpos.first = str_at - cstr;
|
||||
valpos.second = str_end - cstr;
|
||||
}
|
||||
cur_section->InsertItem(cur_section->End(), ItemDef(line, keypos, valpos, sep_at));
|
||||
}
|
||||
} while (!reader.EOS());
|
||||
|
||||
reader.ReleaseStream();
|
||||
}
|
||||
|
||||
void IniFile::Write(Stream *out) const {
|
||||
TextStreamWriter writer(out);
|
||||
for (ConstSectionIterator sec = _sections.begin(); sec != _sections.end(); ++sec) {
|
||||
if (sec != _sections.begin()) // do not write global section's name
|
||||
writer.WriteLine(sec->GetLine());
|
||||
for (ConstItemIterator item = sec->CBegin(); item != sec->CEnd(); ++item)
|
||||
writer.WriteLine(item->GetLine());
|
||||
}
|
||||
writer.ReleaseStream();
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
182
engines/ags/shared/util/ini_file.h
Normal file
182
engines/ags/shared/util/ini_file.h
Normal file
@@ -0,0 +1,182 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// IniFile class defines contents of the configuration file.
|
||||
// It serves as a INI parser and plain enumerator of all the sections and items
|
||||
// found in file, or, oppositely, as INI file constructor.
|
||||
// But is not much suitable for regular key/value lookup. It is suggested to
|
||||
// create a proper map to store items, from IniFile contents.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_INIFILE_H
|
||||
#define AGS_SHARED_UTIL_INIFILE_H
|
||||
|
||||
#include "common/std/list.h"
|
||||
#include "common/std/utility.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class IniFile {
|
||||
public:
|
||||
// Position of a string in the line of text:
|
||||
// is defined by a pair of first and next-after-last character indices
|
||||
typedef std::pair<size_t, size_t> StrPos;
|
||||
// Location of section in the array of text lines:
|
||||
// is defined by a pair of first and next-after-last line indices
|
||||
typedef std::pair<size_t, size_t> SectionPos;
|
||||
|
||||
inline static bool IsValidStrPos(const StrPos &pos) {
|
||||
return pos.first < pos.second;
|
||||
}
|
||||
|
||||
// Item definition
|
||||
// Valid key indicates a key-value line; no key means unparsed
|
||||
// line of text, e.g. comment or incorrectly formatted item.
|
||||
class ItemDef {
|
||||
public:
|
||||
ItemDef(const String &key, const String &value);
|
||||
ItemDef(const String &line, const StrPos &key, const StrPos &value, size_t sep_at);
|
||||
String GetLine() const {
|
||||
return Line;
|
||||
}
|
||||
String GetKey() const {
|
||||
return SubString(Line, Key);
|
||||
}
|
||||
String GetValue() const {
|
||||
return SubString(Line, Value);
|
||||
}
|
||||
// Tells if this is a valid key/value item, which means that it has a valid key
|
||||
bool IsKeyValue() const {
|
||||
return IsValidStrPos(Key);
|
||||
}
|
||||
void SetKey(const String &key);
|
||||
void SetValue(const String &value);
|
||||
|
||||
private:
|
||||
String Line; // actual text
|
||||
StrPos Key; // position of item key
|
||||
size_t SepAt; // position of the separator (assignment) symbol
|
||||
StrPos Value; // position of item value
|
||||
};
|
||||
// Linked list of items
|
||||
typedef std::list<ItemDef> LItems;
|
||||
typedef LItems::iterator ItemIterator;
|
||||
typedef LItems::const_iterator ConstItemIterator;
|
||||
|
||||
// Section definition
|
||||
class SectionDef {
|
||||
public:
|
||||
SectionDef(const String &name);
|
||||
SectionDef(const String &line, const StrPos &name);
|
||||
String GetLine() const {
|
||||
return Header;
|
||||
}
|
||||
String GetName() const {
|
||||
return SubString(Header, Name);
|
||||
}
|
||||
size_t GetItemCount() const {
|
||||
return Items.size();
|
||||
}
|
||||
// Tells if this is a "global" section, which means that it has no name
|
||||
bool IsGlobal() const {
|
||||
return !IsValidStrPos(Name);
|
||||
}
|
||||
ItemIterator Begin() {
|
||||
return Items.begin();
|
||||
}
|
||||
ItemIterator End() {
|
||||
return Items.end();
|
||||
}
|
||||
ConstItemIterator CBegin() const {
|
||||
return Items.begin();
|
||||
}
|
||||
ConstItemIterator CEnd() const {
|
||||
return Items.end();
|
||||
}
|
||||
void SetName(const String &sec_name);
|
||||
void Clear();
|
||||
ItemIterator InsertItem(ItemIterator item, const ItemDef &itemdef);
|
||||
void EraseItem(ItemIterator item);
|
||||
|
||||
private:
|
||||
String Header;// section's heading line
|
||||
StrPos Name; // location of section name in the header line
|
||||
LItems Items; // linked list of items belonging to the section
|
||||
};
|
||||
|
||||
// Linked list of sections
|
||||
typedef std::list<SectionDef> LSections;
|
||||
typedef LSections::iterator SectionIterator;
|
||||
typedef LSections::const_iterator ConstSectionIterator;
|
||||
|
||||
private:
|
||||
inline static String SubString(const String &line, const StrPos &pos) {
|
||||
return line.Mid(pos.first, pos.second - pos.first);
|
||||
}
|
||||
|
||||
public:
|
||||
IniFile();
|
||||
|
||||
SectionIterator Begin() {
|
||||
return _sections.begin();
|
||||
}
|
||||
SectionIterator End() {
|
||||
return _sections.end();
|
||||
}
|
||||
ConstSectionIterator CBegin() const {
|
||||
return _sections.begin();
|
||||
}
|
||||
ConstSectionIterator CEnd() const {
|
||||
return _sections.end();
|
||||
}
|
||||
|
||||
void Read(Stream *in);
|
||||
void Write(Stream *out) const;
|
||||
|
||||
// Return number of sections
|
||||
size_t GetSectionCount() const {
|
||||
return _sections.size();
|
||||
}
|
||||
// Insert new item *before* existing item
|
||||
ItemIterator InsertItem(SectionIterator sec, ItemIterator item, const String &key, const String &value);
|
||||
// Insert new section *before* existing section
|
||||
SectionIterator InsertSection(SectionIterator sec, const String &name);
|
||||
// Remove a single item
|
||||
void RemoveItem(SectionIterator sec, ItemIterator item);
|
||||
// Completely remove whole section; this removes all lines between section
|
||||
// header and the last item found in that section (inclusive).
|
||||
void RemoveSection(SectionIterator sec);
|
||||
|
||||
private:
|
||||
LSections _sections;
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
281
engines/ags/shared/util/ini_util.cpp
Normal file
281
engines/ags/shared/util/ini_util.cpp
Normal file
@@ -0,0 +1,281 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/std/memory.h"
|
||||
#include "ags/shared/util/file.h"
|
||||
#include "ags/shared/util/ini_util.h"
|
||||
#include "ags/shared/util/ini_file.h"
|
||||
#include "ags/shared/util/stream.h"
|
||||
#include "ags/shared/util/string_utils.h"
|
||||
#include "ags/shared/util/text_stream_writer.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// ConfigReader
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool CfgReadItem(const ConfigTree &cfg, const String §n, const String &item, String &value) {
|
||||
const auto sec_it = cfg.find(sectn);
|
||||
if (sec_it != cfg.end()) {
|
||||
const auto item_it = sec_it->_value.find(item);
|
||||
if (item_it != sec_it->_value.end()) {
|
||||
value = item_it->_value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int CfgReadInt(const ConfigTree &cfg, const String §n, const String &item, int def) {
|
||||
String str;
|
||||
if (!CfgReadItem(cfg, sectn, item, str))
|
||||
return def;
|
||||
return StrUtil::StringToInt(str, def);
|
||||
}
|
||||
|
||||
int CfgReadInt(const ConfigTree &cfg, const String §n, const String &item, int min, int max, int def) {
|
||||
int val = CfgReadInt(cfg, sectn, item, def);
|
||||
if ((val < min) || (val > max))
|
||||
return def;
|
||||
return val;
|
||||
}
|
||||
|
||||
float CfgReadFloat(const ConfigTree &cfg, const String §n, const String &item, float def) {
|
||||
String str;
|
||||
if (!CfgReadItem(cfg, sectn, item, str))
|
||||
return def;
|
||||
return StrUtil::StringToFloat(str, def);
|
||||
}
|
||||
|
||||
float CfgReadFloat(const ConfigTree &cfg, const String §n, const String &item, float min, float max, float def) {
|
||||
float val = CfgReadFloat(cfg, sectn, item, def);
|
||||
if ((val < min) || (val > max))
|
||||
return def;
|
||||
return val;
|
||||
}
|
||||
|
||||
String CfgReadString(const ConfigTree &cfg, const String §n, const String &item, const String &def) {
|
||||
String str;
|
||||
if (!CfgReadItem(cfg, sectn, item, str))
|
||||
return def;
|
||||
return str;
|
||||
}
|
||||
|
||||
String CfgFindKey(const ConfigTree &cfg, const String §n, const String &item, bool nocase) {
|
||||
const auto sec_it = cfg.find(sectn);
|
||||
if (sec_it == cfg.end())
|
||||
return "";
|
||||
if (nocase) {
|
||||
for (auto item_it : sec_it->_value) {
|
||||
if (item_it._key.CompareNoCase(item) == 0)
|
||||
return item_it._key;
|
||||
}
|
||||
} else {
|
||||
const auto item_it = sec_it->_value.find(item);
|
||||
if (item_it != sec_it->_value.end())
|
||||
return item_it->_key;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// ConfigWriter
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void CfgWriteInt(ConfigTree &cfg, const String §n, const String &item, int value) {
|
||||
cfg[sectn][item].Format("%d", value);
|
||||
}
|
||||
|
||||
void CfgWriteFloat(ConfigTree &cfg, const String §n, const String &item, float value) {
|
||||
cfg[sectn][item].Format("%f", value);
|
||||
}
|
||||
|
||||
void CfgWriteFloat(ConfigTree &cfg, const String §n, const String &item, float value, unsigned precision) {
|
||||
char fmt[10];
|
||||
snprintf(fmt, sizeof(fmt), "%%0.%df", precision);
|
||||
cfg[sectn][item].Format(fmt, value);
|
||||
}
|
||||
|
||||
void CfgWriteString(ConfigTree &cfg, const String §n, const String &item, const String &value) {
|
||||
cfg[sectn][item] = value;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// IniUtil
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
typedef std::unique_ptr<Stream> UStream;
|
||||
typedef StringOrderMap::const_iterator StrStrOIter;
|
||||
typedef ConfigTree::const_iterator ConfigNode;
|
||||
typedef IniFile::SectionIterator SectionIterator;
|
||||
typedef IniFile::ConstSectionIterator CSectionIterator;
|
||||
typedef IniFile::ItemIterator ItemIterator;
|
||||
typedef IniFile::ConstItemIterator CItemIterator;
|
||||
|
||||
static bool ReadIni(const String &file, IniFile &ini) {
|
||||
UStream fs(File::OpenFileRead(file));
|
||||
if (fs.get()) {
|
||||
ini.Read(fs.get());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IniUtil::Read(const String &file, ConfigTree &tree) {
|
||||
// Read ini content
|
||||
IniFile ini;
|
||||
if (!ReadIni(file, ini))
|
||||
return false;
|
||||
|
||||
// Copy items into key-value tree
|
||||
for (CSectionIterator sec = ini.CBegin(); sec != ini.CEnd(); ++sec) {
|
||||
if (!sec->GetItemCount())
|
||||
continue; // skip empty sections
|
||||
StringOrderMap &subtree = tree[sec->GetName()];
|
||||
for (CItemIterator item = sec->CBegin(); item != sec->CEnd(); ++item) {
|
||||
if (!item->IsKeyValue())
|
||||
continue; // skip non key-value items
|
||||
subtree[item->GetKey()] = item->GetValue();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void IniUtil::Write(const String &file, const ConfigTree &tree) {
|
||||
UStream fs(File::CreateFile(file));
|
||||
TextStreamWriter writer(fs.get());
|
||||
|
||||
for (ConfigNode it_sec = tree.begin(); it_sec != tree.end(); ++it_sec) {
|
||||
const String &sec_key = it_sec->_key;
|
||||
const StringOrderMap &sec_tree = it_sec->_value;
|
||||
|
||||
if (!sec_tree.size())
|
||||
continue; // skip empty sections
|
||||
// write section name
|
||||
if (!sec_key.IsEmpty()) {
|
||||
writer.WriteFormat("[%s]", sec_key.GetCStr());
|
||||
writer.WriteLineBreak();
|
||||
}
|
||||
// write all items
|
||||
for (StrStrOIter keyval = sec_tree.begin(); keyval != sec_tree.end(); ++keyval) {
|
||||
const String &item_key = keyval->_key;
|
||||
const String &item_value = keyval->_value;
|
||||
|
||||
writer.WriteFormat("%s = %s", item_key.GetCStr(), item_value.GetCStr());
|
||||
writer.WriteLineBreak();
|
||||
}
|
||||
}
|
||||
|
||||
writer.ReleaseStream();
|
||||
}
|
||||
|
||||
void IniUtil::WriteToString(String &s, const ConfigTree &tree) {
|
||||
for (ConfigNode it_sec = tree.begin(); it_sec != tree.end(); ++it_sec) {
|
||||
const String &sec_key = it_sec->_key;
|
||||
const StringOrderMap &sec_tree = it_sec->_value;
|
||||
if (!sec_tree.size())
|
||||
continue; // skip empty sections
|
||||
// write section name
|
||||
if (!sec_key.IsEmpty())
|
||||
s.Append(String::FromFormat("[%s]\n", sec_key.GetCStr()));
|
||||
// write all items
|
||||
for (StrStrOIter keyval = sec_tree.begin(); keyval != sec_tree.end(); ++keyval)
|
||||
s.Append(String::FromFormat("%s = %s\n", keyval->_key.GetCStr(), keyval->_value.GetCStr()));
|
||||
}
|
||||
}
|
||||
|
||||
bool IniUtil::Merge(const String &file, const ConfigTree &tree) {
|
||||
// Read ini content
|
||||
IniFile ini;
|
||||
ReadIni(file, ini); // NOTE: missing file is a valid case
|
||||
|
||||
// Remember the sections we find in file, if some sections are not found,
|
||||
// they will be appended to the end of file.
|
||||
std::map<String, bool> sections_found;
|
||||
for (ConfigNode it = tree.begin(); it != tree.end(); ++it)
|
||||
sections_found[it->_key] = false;
|
||||
|
||||
// Merge existing sections
|
||||
for (SectionIterator sec = ini.Begin(); sec != ini.End(); ++sec) {
|
||||
if (!sec->GetItemCount())
|
||||
continue; // skip empty sections
|
||||
String secname = sec->GetName();
|
||||
ConfigNode tree_node = tree.find(secname);
|
||||
if (tree_node == tree.end())
|
||||
continue; // this section is not interesting for us
|
||||
|
||||
// Remember the items we find in this section, if some items are not found,
|
||||
// they will be appended to the end of section.
|
||||
const StringOrderMap &subtree = tree_node->_value;
|
||||
std::map<String, bool> items_found;
|
||||
for (StrStrOIter keyval = subtree.begin(); keyval != subtree.end(); ++keyval)
|
||||
items_found[keyval->_key] = false;
|
||||
|
||||
// Replace matching items
|
||||
for (ItemIterator item = sec->Begin(); item != sec->End(); ++item) {
|
||||
String key = item->GetKey();
|
||||
StrStrOIter keyval = subtree.find(key);
|
||||
if (keyval == subtree.end())
|
||||
continue; // this item is not interesting for us
|
||||
|
||||
String old_value = item->GetValue();
|
||||
String new_value = keyval->_value;
|
||||
if (old_value != new_value)
|
||||
item->SetValue(new_value);
|
||||
items_found[key] = true;
|
||||
}
|
||||
|
||||
// Append new items
|
||||
if (!sections_found[secname]) {
|
||||
for (std::map<String, bool>::const_iterator item_f = items_found.begin(); item_f != items_found.end(); ++item_f) {
|
||||
if (item_f->_value)
|
||||
continue; // item was already found
|
||||
StrStrOIter keyval = subtree.find(item_f->_key);
|
||||
ini.InsertItem(sec, sec->End(), keyval->_key, keyval->_value);
|
||||
}
|
||||
sections_found[secname] = true; // mark section as known
|
||||
}
|
||||
}
|
||||
|
||||
// Add new sections
|
||||
for (std::map<String, bool>::const_iterator sec_f = sections_found.begin(); sec_f != sections_found.end(); ++sec_f) {
|
||||
if (sec_f->_value)
|
||||
continue;
|
||||
SectionIterator sec = ini.InsertSection(ini.End(), sec_f->_key);
|
||||
const StringOrderMap &subtree = tree.find(sec_f->_key)->_value;
|
||||
for (StrStrOIter keyval = subtree.begin(); keyval != subtree.end(); ++keyval)
|
||||
ini.InsertItem(sec, sec->End(), keyval->_key, keyval->_value);
|
||||
}
|
||||
|
||||
// Write the resulting set of lines
|
||||
UStream fs(File::CreateFile(file));
|
||||
if (!fs.get())
|
||||
return false;
|
||||
ini.Write(fs.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
104
engines/ags/shared/util/ini_util.h
Normal file
104
engines/ags/shared/util/ini_util.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Functions for exchanging configuration data between key-value tree and
|
||||
// INI file.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_INI_UTIL_H
|
||||
#define AGS_SHARED_UTIL_INI_UTIL_H
|
||||
|
||||
#include "common/std/map.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
typedef std::map<String, String> StringOrderMap;
|
||||
typedef std::map<String, StringOrderMap> ConfigTree;
|
||||
|
||||
//
|
||||
// Helper functions for parsing values in a ConfigTree
|
||||
bool CfgReadItem(const ConfigTree &cfg, const String §n, const String &item, String &value);
|
||||
int CfgReadInt(const ConfigTree &cfg, const String §n, const String &item, int def = 0);
|
||||
int CfgReadInt(const ConfigTree &cfg, const String §n, const String &item, int min, int max, int def = 0);
|
||||
inline bool CfgReadBoolInt(const ConfigTree &cfg, const String §n, const String &item, bool def = false) {
|
||||
return CfgReadInt(cfg, sectn, item, 0, 1, def) != 0;
|
||||
}
|
||||
float CfgReadFloat(const ConfigTree &cfg, const String §n, const String &item, float def = 0.f);
|
||||
float CfgReadFloat(const ConfigTree &cfg, const String §n, const String &item, float min, float max, float def = 0.f);
|
||||
String CfgReadString(const ConfigTree &cfg, const String §n, const String &item, const String &def = "");
|
||||
// Looks up for a item key in a given section, returns actual key if one exists, or empty string otherwise,
|
||||
// optionally compares item name in case-insensitive way.
|
||||
// NOTE: this is a compatibility hack, in case we cannot enforce key case-sensitivity in some case.
|
||||
String CfgFindKey(const ConfigTree &cfg, const String §n, const String &item, bool nocase = false);
|
||||
|
||||
//
|
||||
// Helper functions for writing values into a ConfigTree
|
||||
void CfgWriteInt(ConfigTree &cfg, const String §n, const String &item, int value);
|
||||
inline void CfgWriteBoolInt(ConfigTree &cfg, const String §n, const String &item, bool value) {
|
||||
CfgWriteInt(cfg, sectn, item, static_cast<int>(value));
|
||||
}
|
||||
void CfgWriteFloat(ConfigTree &cfg, const String §n, const String &item, float value);
|
||||
void CfgWriteFloat(ConfigTree &cfg, const String §n, const String &item, float value, unsigned precision);
|
||||
void CfgWriteString(ConfigTree &cfg, const String §n, const String &item, const String &value);
|
||||
|
||||
|
||||
class IniFile;
|
||||
|
||||
// Utility functions that exchange data between ConfigTree and INI file.
|
||||
namespace IniUtil {
|
||||
|
||||
// Parse the contents of given file as INI format and insert values
|
||||
// into the tree. The pre-existing tree items, if any, are NOT erased.
|
||||
// Returns FALSE if the file could not be opened.
|
||||
bool Read(const String &file, ConfigTree &tree);
|
||||
// Serialize given tree to the stream in INI text format.
|
||||
// The INI format suggests only one nested level (group - items).
|
||||
// The first level values are treated as a global section items.
|
||||
// The sub-nodes beyond 2nd level are ignored completely.
|
||||
void Write(const String &file, const ConfigTree &tree);
|
||||
// Serialize given tree to the string in INI text format.
|
||||
// TODO: implement proper memory/string stream compatible with base Stream
|
||||
// class and merge this with Write function.
|
||||
void WriteToString(String &s, const ConfigTree &tree);
|
||||
// Parse the contents of given source stream as INI format and merge
|
||||
// with values of the given tree while doing only minimal replaces;
|
||||
// write the result into destination stream.
|
||||
// If item already exists, only value is overwrited, if section exists,
|
||||
// new items are appended to the end of it; completely new sections are
|
||||
// appended to the end of text.
|
||||
// Source and destination streams may refer either to different objects,
|
||||
// or same stream opened for both reading and writing.
|
||||
// Returns FALSE if the file could not be opened for writing.
|
||||
bool Merge(const String &file, const ConfigTree &tree);
|
||||
|
||||
} // namespace IniUtil
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
259
engines/ags/shared/util/lzw.cpp
Normal file
259
engines/ags/shared/util/lzw.cpp
Normal file
@@ -0,0 +1,259 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// LZW compression.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "ags/shared/util/lzw.h"
|
||||
#include "ags/shared/ac/common.h" // quit
|
||||
#include "ags/shared/util/bbop.h"
|
||||
#include "ags/shared/util/memory.h"
|
||||
#include "ags/shared/util/stream.h"
|
||||
#include "ags/globals.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
using namespace AGS::Shared;
|
||||
|
||||
#ifdef _MANAGED
|
||||
// ensure this doesn't get compiled to .NET IL
|
||||
#pragma unmanaged
|
||||
#endif
|
||||
|
||||
int insert(int, int);
|
||||
void _delete(int);
|
||||
|
||||
#define N 4096
|
||||
#define F 16
|
||||
#define THRESHOLD 3
|
||||
#define min(xx,yy) ((yy<xx) ? yy : xx)
|
||||
|
||||
#define dad (_G(node)+1)
|
||||
#define lson (_G(node)+1+N)
|
||||
#define rson (_G(node)+1+N+N)
|
||||
#define root (_G(node)+1+N+N+N)
|
||||
#define NIL -1
|
||||
|
||||
int insert(int i, int run) {
|
||||
int c, j, k, l, n, match;
|
||||
int *p;
|
||||
|
||||
c = NIL;
|
||||
|
||||
k = l = 1;
|
||||
match = THRESHOLD - 1;
|
||||
p = &root[_G(lzbuffer)[i]];
|
||||
lson[i] = rson[i] = NIL;
|
||||
while ((j = *p) != NIL) {
|
||||
for (n = min(k, l); n < run && (c = (_G(lzbuffer)[j + n] - _G(lzbuffer)[i + n])) == 0; n++);
|
||||
|
||||
if (n > match) {
|
||||
match = n;
|
||||
_G(pos) = j;
|
||||
}
|
||||
|
||||
if (c < 0) {
|
||||
p = &lson[j];
|
||||
k = n;
|
||||
} else if (c > 0) {
|
||||
p = &rson[j];
|
||||
l = n;
|
||||
} else {
|
||||
dad[j] = NIL;
|
||||
dad[lson[j]] = lson + i - _G(node);
|
||||
dad[rson[j]] = rson + i - _G(node);
|
||||
lson[i] = lson[j];
|
||||
rson[i] = rson[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dad[i] = p - _G(node);
|
||||
*p = i;
|
||||
return match;
|
||||
}
|
||||
|
||||
void _delete(int z) {
|
||||
int j;
|
||||
|
||||
if (dad[z] != NIL) {
|
||||
if (rson[z] == NIL)
|
||||
j = lson[z];
|
||||
else if (lson[z] == NIL)
|
||||
j = rson[z];
|
||||
else {
|
||||
j = lson[z];
|
||||
if (rson[j] != NIL) {
|
||||
do {
|
||||
j = rson[j];
|
||||
} while (rson[j] != NIL);
|
||||
|
||||
_G(node)[dad[j]] = lson[j];
|
||||
dad[lson[j]] = dad[j];
|
||||
lson[j] = lson[z];
|
||||
dad[lson[z]] = lson + j - _G(node);
|
||||
}
|
||||
|
||||
rson[j] = rson[z];
|
||||
dad[rson[z]] = rson + j - _G(node);
|
||||
}
|
||||
|
||||
dad[j] = dad[z];
|
||||
_G(node)[dad[z]] = j;
|
||||
dad[z] = NIL;
|
||||
}
|
||||
}
|
||||
|
||||
bool lzwcompress(Stream *lzw_in, Stream *out) {
|
||||
int ch, i, run, len, match, size, mask;
|
||||
uint8_t buf[17];
|
||||
|
||||
_G(lzbuffer) = (uint8_t *)malloc(N + F + (N + 1 + N + N + 256) * sizeof(int)); // 28.5 k !
|
||||
if (_G(lzbuffer) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_G(node) = (int *)(_G(lzbuffer) + N + F);
|
||||
for (i = 0; i < 256; i++)
|
||||
root[i] = NIL;
|
||||
|
||||
for (i = NIL; i < N; i++)
|
||||
dad[i] = NIL;
|
||||
|
||||
size = mask = 1;
|
||||
buf[0] = 0;
|
||||
i = N - F - F;
|
||||
|
||||
for (len = 0; len < F && (ch = lzw_in->ReadByte()) != -1; len++) {
|
||||
_G(lzbuffer)[i + F] = static_cast<uint8_t>(ch);
|
||||
i = (i + 1) & (N - 1);
|
||||
}
|
||||
|
||||
run = len;
|
||||
|
||||
do {
|
||||
ch = lzw_in->ReadByte();
|
||||
if (i >= N - F) {
|
||||
_delete(i + F - N);
|
||||
_G(lzbuffer)[i + F] = _G(lzbuffer)[i + F - N] = static_cast<uint8_t>(ch);
|
||||
} else {
|
||||
_delete(i + F);
|
||||
_G(lzbuffer)[i + F] = static_cast<uint8_t>(ch);
|
||||
}
|
||||
|
||||
match = insert(i, run);
|
||||
if (ch == -1) {
|
||||
run--;
|
||||
len--;
|
||||
}
|
||||
|
||||
if (len++ >= run) {
|
||||
if (match >= THRESHOLD) {
|
||||
buf[0] |= mask;
|
||||
// possible fix: change int* to short* ??
|
||||
*(short *)(buf + size) = static_cast<short>(((match - 3) << 12) | ((i - _G(pos) - 1) & (N - 1)));
|
||||
size += 2;
|
||||
len -= match;
|
||||
} else {
|
||||
buf[size++] = _G(lzbuffer)[i];
|
||||
len--;
|
||||
}
|
||||
|
||||
if (!((mask += mask) & 0xFF)) {
|
||||
out->Write(buf, size);
|
||||
_G(outbytes) += size;
|
||||
size = mask = 1;
|
||||
buf[0] = 0;
|
||||
}
|
||||
}
|
||||
i = (i + 1) & (N - 1);
|
||||
} while (len > 0);
|
||||
|
||||
if (size > 1) {
|
||||
out->Write(buf, size);
|
||||
_G(outbytes) += size;
|
||||
}
|
||||
|
||||
free(_G(lzbuffer));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lzwexpand(const uint8_t *src, size_t src_sz, uint8_t *dst, size_t dst_sz) {
|
||||
int bits, ch, i, j, len, mask;
|
||||
uint8_t *dst_ptr = dst;
|
||||
const uint8_t *src_ptr = src;
|
||||
|
||||
if (dst_sz == 0)
|
||||
return false; // nowhere to expand to
|
||||
|
||||
_G(lzbuffer) = (uint8_t *)malloc(N);
|
||||
if (_G(lzbuffer) == nullptr) {
|
||||
return false; // not enough memory
|
||||
}
|
||||
i = N - F;
|
||||
|
||||
// Read from the src and expand, until either src or dst runs out of space
|
||||
while ((static_cast<size_t>(src_ptr - src) < src_sz) &&
|
||||
(static_cast<size_t>(dst_ptr - dst) < dst_sz)) {
|
||||
bits = *(src_ptr++);
|
||||
for (mask = 0x01; mask & 0xFF; mask <<= 1) {
|
||||
if (bits & mask) {
|
||||
if (static_cast<size_t>(src_ptr - src) > (src_sz - sizeof(int16_t)))
|
||||
break;
|
||||
|
||||
short jshort = 0;
|
||||
jshort = Memory::ReadInt16LE(src_ptr);
|
||||
src_ptr += sizeof(int16_t);
|
||||
j = jshort;
|
||||
|
||||
len = ((j >> 12) & 15) + 3;
|
||||
j = (i - j - 1) & (N - 1);
|
||||
|
||||
if (static_cast<size_t>(dst_ptr - dst) > (dst_sz - len))
|
||||
break; // not enough dest buffer
|
||||
|
||||
while (len--) {
|
||||
*(dst_ptr++) = (_G(lzbuffer)[i] = _G(lzbuffer)[j]);
|
||||
j = (j + 1) & (N - 1);
|
||||
i = (i + 1) & (N - 1);
|
||||
}
|
||||
} else {
|
||||
ch = *(src_ptr++);
|
||||
*(dst_ptr++) = (_G(lzbuffer)[i] = static_cast<uint8_t>(ch));
|
||||
i = (i + 1) & (N - 1);
|
||||
}
|
||||
|
||||
if ((static_cast<size_t>(dst_ptr - dst) >= dst_sz) ||
|
||||
(static_cast<size_t>(src_ptr - src) >= src_sz)) {
|
||||
break; // not enough dest buffer for the next pass
|
||||
}
|
||||
} // end for mask
|
||||
|
||||
}
|
||||
|
||||
free(_G(lzbuffer));
|
||||
return static_cast<size_t>(src_ptr - src) == src_sz;
|
||||
}
|
||||
|
||||
} // namespace AGS3
|
||||
44
engines/ags/shared/util/lzw.h
Normal file
44
engines/ags/shared/util/lzw.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_LZW_H
|
||||
#define AGS_SHARED_UTIL_LZW_H
|
||||
|
||||
#include "ags/shared/core/types.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
class Stream;
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
|
||||
using namespace AGS; // FIXME later
|
||||
|
||||
bool lzwcompress(Shared::Stream *lzw_in, Shared::Stream *out);
|
||||
// Expands lzw-compressed data from src to dst.
|
||||
// the dst buffer should be large enough, or the uncompression will not be complete.
|
||||
bool lzwexpand(const uint8_t *src, size_t src_sz, uint8_t *dst, size_t dst_sz);
|
||||
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
103
engines/ags/shared/util/math.h
Normal file
103
engines/ags/shared/util/math.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Helper math functions
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_MATH_H
|
||||
#define AGS_SHARED_UTIL_MATH_H
|
||||
|
||||
#include "common/std/limits.h"
|
||||
|
||||
#include "ags/lib/std.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
namespace Math {
|
||||
template <class T>
|
||||
inline const T &Max(const T &a, const T &b) {
|
||||
return a < b ? b : a;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline const T &Min(const T &a, const T &b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline const T &Clamp(const T &val, const T &floor, const T &ceil) {
|
||||
return Max<T>(floor, Min<T>(val, ceil));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline void ClampLength(T &from, T &length, const T &floor, const T &height) {
|
||||
if (from < floor) {
|
||||
length -= floor - from;
|
||||
from = floor;
|
||||
} else if (from >= floor + height) {
|
||||
from = floor + height;
|
||||
length = 0;
|
||||
}
|
||||
|
||||
length = Max<T>(length, 0);
|
||||
length = Min<T>(length, height - from);
|
||||
}
|
||||
|
||||
// Get a measure of how value A is greater than value B;
|
||||
// if A is smaller than or equal to B, returns 0.
|
||||
template <class T>
|
||||
inline T Surplus(const T &larger, const T &smaller) {
|
||||
return larger > smaller ? larger - smaller : 0;
|
||||
}
|
||||
|
||||
// Tests if the big-type value is in range of the result type;
|
||||
// returns same value converted if it's in range, or provided replacement if it's not.
|
||||
template <typename T, typename TBig>
|
||||
inline T InRangeOrDef(const TBig &val, const T &def) {
|
||||
return (val >= std::numeric_limits<T>::min() && val <= std::numeric_limits<T>::max()) ?
|
||||
static_cast<T>(val) : def;
|
||||
}
|
||||
|
||||
inline float RadiansToDegrees(float rads) {
|
||||
return rads * (float)(180.0 / M_PI);
|
||||
}
|
||||
|
||||
inline float DegreesToRadians(float deg) {
|
||||
return deg * (float)(M_PI / 180.0);
|
||||
}
|
||||
|
||||
} // namespace Math
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
234
engines/ags/shared/util/memory.h
Normal file
234
engines/ags/shared/util/memory.h
Normal file
@@ -0,0 +1,234 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Memory utils and algorithms
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_MEMORY_H
|
||||
#define AGS_SHARED_UTIL_MEMORY_H
|
||||
|
||||
//include <string.h>
|
||||
#include "ags/shared/util/bbop.h"
|
||||
#include "ags/shared/util/math.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
#if defined (AGS_STRICT_ALIGNMENT) || defined (TEST_STRICT_ALIGNMENT)
|
||||
#define MEMORY_STRICT_ALIGNMENT
|
||||
#endif
|
||||
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
namespace Memory {
|
||||
//-------------------------------------------------------------------------
|
||||
// Converts pointer to 32-bit integer value and vice-versa.
|
||||
// Only for use in special cases for compatibility with 32-bit plugin API.
|
||||
// Dangerous on 64-bit systems.
|
||||
//-------------------------------------------------------------------------
|
||||
template <typename T>
|
||||
inline int32_t PtrToInt32(T *ptr) {
|
||||
return static_cast<int32_t>(reinterpret_cast<intptr_t>(ptr));
|
||||
}
|
||||
template <typename T>
|
||||
inline T *Int32ToPtr(int32_t value) {
|
||||
return reinterpret_cast<T *>(static_cast<intptr_t>(value));
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Functions for reading and writing basic types from/to the memory.
|
||||
// Implement safety workaround for CPUs with alignment restrictions
|
||||
// (e.g. MIPS).
|
||||
//-------------------------------------------------------------------------
|
||||
inline int16_t ReadInt16(const void *ptr) {
|
||||
#if defined (MEMORY_STRICT_ALIGNMENT)
|
||||
const uint8_t *b = (const uint8_t *)ptr;
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
return (b[0] << 8) | b[1];
|
||||
#else
|
||||
return (b[1] << 8) | b[0];
|
||||
#endif
|
||||
#else
|
||||
return *(const int16_t *)ptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int32_t ReadInt32(const void *ptr) {
|
||||
#if defined (MEMORY_STRICT_ALIGNMENT)
|
||||
const uint8_t *b = (const uint8_t *)ptr;
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
|
||||
#else
|
||||
return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0];
|
||||
#endif
|
||||
#else
|
||||
return *(const int32_t *)ptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int64_t ReadInt64(const void *ptr) {
|
||||
#if defined (MEMORY_STRICT_ALIGNMENT)
|
||||
const uint8_t *b = (const uint8_t *)ptr;
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
return ((uint64_t)b[0] << 56) | ((uint64_t)b[1] << 48) | ((uint64_t)b[2] << 40) | ((uint64_t)b[3] << 32) |
|
||||
(b[4] << 24) | (b[5] << 16) | (b[6] << 8) | b[7];
|
||||
#else
|
||||
return ((uint64_t)b[7] << 56) | ((uint64_t)b[6] << 48) | ((uint64_t)b[5] << 40) | ((uint64_t)b[4] << 32) |
|
||||
(b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0];
|
||||
#endif
|
||||
#else
|
||||
return *(const int64_t *)ptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void WriteInt16(void *ptr, int16_t value) {
|
||||
#if defined (MEMORY_STRICT_ALIGNMENT)
|
||||
uint8_t *b = (uint8_t *)ptr;
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
b[0] = (uint8_t)(value >> 8);
|
||||
b[1] = (uint8_t)(value);
|
||||
#else
|
||||
b[1] = (uint8_t)(value >> 8);
|
||||
b[0] = (uint8_t)(value);
|
||||
#endif
|
||||
#else
|
||||
*(int16_t *)ptr = value;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void WriteInt32(void *ptr, int32_t value) {
|
||||
#if defined (MEMORY_STRICT_ALIGNMENT)
|
||||
uint8_t *b = (uint8_t *)ptr;
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
b[0] = (uint8_t)(value >> 24);
|
||||
b[1] = (uint8_t)(value >> 16);
|
||||
b[2] = (uint8_t)(value >> 8);
|
||||
b[3] = (uint8_t)(value);
|
||||
#else
|
||||
b[3] = (uint8_t)(value >> 24);
|
||||
b[2] = (uint8_t)(value >> 16);
|
||||
b[1] = (uint8_t)(value >> 8);
|
||||
b[0] = (uint8_t)(value);
|
||||
#endif
|
||||
#else
|
||||
*(int32_t *)ptr = value;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void WriteInt64(void *ptr, int64_t value) {
|
||||
#if defined (MEMORY_STRICT_ALIGNMENT)
|
||||
uint8_t *b = (uint8_t *)ptr;
|
||||
#if defined (BITBYTE_BIG_ENDIAN)
|
||||
b[0] = (uint8_t)(value >> 56);
|
||||
b[1] = (uint8_t)(value >> 48);
|
||||
b[2] = (uint8_t)(value >> 40);
|
||||
b[3] = (uint8_t)(value >> 32);
|
||||
b[4] = (uint8_t)(value >> 24);
|
||||
b[5] = (uint8_t)(value >> 16);
|
||||
b[6] = (uint8_t)(value >> 8);
|
||||
b[7] = (uint8_t)(value);
|
||||
#else
|
||||
b[7] = (uint8_t)(value >> 56);
|
||||
b[6] = (uint8_t)(value >> 48);
|
||||
b[5] = (uint8_t)(value >> 40);
|
||||
b[4] = (uint8_t)(value >> 32);
|
||||
b[3] = (uint8_t)(value >> 24);
|
||||
b[2] = (uint8_t)(value >> 16);
|
||||
b[1] = (uint8_t)(value >> 8);
|
||||
b[0] = (uint8_t)(value);
|
||||
#endif
|
||||
#else
|
||||
*(int64_t *)ptr = value;
|
||||
#endif
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Helpers for reading and writing from memory with respect for endianess.
|
||||
//-------------------------------------------------------------------------
|
||||
inline int16_t ReadInt16LE(const void *ptr) {
|
||||
return BBOp::Int16FromLE(ReadInt16(ptr));
|
||||
}
|
||||
|
||||
inline int32_t ReadInt32LE(const void *ptr) {
|
||||
return BBOp::Int32FromLE(ReadInt32(ptr));
|
||||
}
|
||||
|
||||
inline int64_t ReadInt64LE(const void *ptr) {
|
||||
return BBOp::Int64FromLE(ReadInt64(ptr));
|
||||
}
|
||||
|
||||
inline void WriteInt16LE(void *ptr, int16_t value) {
|
||||
WriteInt16(ptr, BBOp::Int16FromLE(value));
|
||||
}
|
||||
|
||||
inline void WriteInt32LE(void *ptr, int32_t value) {
|
||||
WriteInt32(ptr, BBOp::Int32FromLE(value));
|
||||
}
|
||||
|
||||
inline void WriteInt64LE(void *ptr, int64_t value) {
|
||||
WriteInt64(ptr, BBOp::Int64FromLE(value));
|
||||
}
|
||||
|
||||
inline int16_t ReadInt16BE(const void *ptr) {
|
||||
return BBOp::Int16FromBE(ReadInt16(ptr));
|
||||
}
|
||||
|
||||
inline int32_t ReadInt32BE(const void *ptr) {
|
||||
return BBOp::Int32FromBE(ReadInt32(ptr));
|
||||
}
|
||||
|
||||
inline int64_t ReadInt64BE(const void *ptr) {
|
||||
return BBOp::Int64FromBE(ReadInt64(ptr));
|
||||
}
|
||||
|
||||
inline void WriteInt16BE(void *ptr, int16_t value) {
|
||||
WriteInt16(ptr, BBOp::Int16FromBE(value));
|
||||
}
|
||||
|
||||
inline void WriteInt32BE(void *ptr, int32_t value) {
|
||||
WriteInt32(ptr, BBOp::Int32FromBE(value));
|
||||
}
|
||||
|
||||
inline void WriteInt64BE(void *ptr, int64_t value) {
|
||||
WriteInt64(ptr, BBOp::Int64FromBE(value));
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Memory data manipulation
|
||||
//-------------------------------------------------------------------------
|
||||
// Copies block of 2d data from source into destination.
|
||||
inline void BlockCopy(uint8_t *dst, const size_t dst_pitch, const size_t dst_offset,
|
||||
const uint8_t *src, const size_t src_pitch, const size_t src_offset,
|
||||
const size_t height) {
|
||||
for (size_t y = 0; y < height; ++y, src += src_pitch, dst += dst_pitch)
|
||||
memcpy(dst + dst_offset, src + src_offset, MIN(dst_pitch - dst_offset, src_pitch - src_offset));
|
||||
}
|
||||
|
||||
} // namespace Memory
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
41
engines/ags/shared/util/memory_compat.h
Normal file
41
engines/ags/shared/util/memory_compat.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_MEMORY_COMPAT_H
|
||||
#define AGS_SHARED_UTIL_MEMORY_COMPAT_H
|
||||
|
||||
#include "common/std/memory.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
// C++14 features
|
||||
#if __cplusplus < 201402L && !((defined(_MSC_VER) && _MSC_VER >= 1900))
|
||||
namespace std {
|
||||
template<typename T, typename... Args>
|
||||
std::unique_ptr<T> make_unique(Args &&... args) {
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
} // std
|
||||
#endif
|
||||
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
202
engines/ags/shared/util/memory_stream.cpp
Normal file
202
engines/ags/shared/util/memory_stream.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/std/algorithm.h"
|
||||
#include "ags/shared/util/memory_stream.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
MemoryStream::MemoryStream(const uint8_t *cbuf, size_t buf_sz, DataEndianess stream_endianess)
|
||||
: DataStream(stream_endianess)
|
||||
, _cbuf(cbuf)
|
||||
, _buf_sz(buf_sz)
|
||||
, _len(buf_sz)
|
||||
, _mode(kStream_Read)
|
||||
, _pos(0)
|
||||
, _buf(nullptr) {
|
||||
}
|
||||
|
||||
MemoryStream::MemoryStream(uint8_t *buf, size_t buf_sz, StreamWorkMode mode, DataEndianess stream_endianess)
|
||||
: DataStream(stream_endianess)
|
||||
, _cbuf(nullptr)
|
||||
, _buf_sz(buf_sz)
|
||||
, _len(0)
|
||||
, _mode(mode)
|
||||
, _pos(0)
|
||||
, _buf(nullptr) {
|
||||
if (mode == kStream_Read) {
|
||||
_cbuf = buf;
|
||||
_len = buf_sz;
|
||||
} else {
|
||||
_buf = buf;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryStream::Close() {
|
||||
_cbuf = nullptr;
|
||||
_buf = nullptr;
|
||||
_buf_sz = 0;
|
||||
_len = 0;
|
||||
_pos = 0;
|
||||
}
|
||||
|
||||
bool MemoryStream::Flush() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemoryStream::IsValid() const {
|
||||
return _cbuf != nullptr || _buf != nullptr;
|
||||
}
|
||||
|
||||
bool MemoryStream::EOS() const {
|
||||
return _pos >= _len;
|
||||
}
|
||||
|
||||
soff_t MemoryStream::GetLength() const {
|
||||
return _len;
|
||||
}
|
||||
|
||||
soff_t MemoryStream::GetPosition() const {
|
||||
return _pos;
|
||||
}
|
||||
|
||||
bool MemoryStream::CanRead() const {
|
||||
return (_cbuf != nullptr) && (_mode == kStream_Read);
|
||||
}
|
||||
|
||||
bool MemoryStream::CanWrite() const {
|
||||
return (_buf != nullptr) && (_mode == kStream_Write);
|
||||
}
|
||||
|
||||
bool MemoryStream::CanSeek() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t MemoryStream::Read(void *buffer, size_t size) {
|
||||
if (EOS()) {
|
||||
return 0;
|
||||
}
|
||||
assert(_len > _pos);
|
||||
size_t remain = _len - _pos;
|
||||
size_t read_sz = MIN(remain, size);
|
||||
memcpy(buffer, _cbuf + _pos, read_sz);
|
||||
_pos += read_sz;
|
||||
return read_sz;
|
||||
}
|
||||
|
||||
int32_t MemoryStream::ReadByte() {
|
||||
if (EOS()) {
|
||||
return -1;
|
||||
}
|
||||
return _cbuf[_pos++];
|
||||
}
|
||||
|
||||
soff_t MemoryStream::Seek(soff_t offset, StreamSeek origin) {
|
||||
if (!CanSeek()) {
|
||||
return false;
|
||||
}
|
||||
soff_t want_pos = -1;
|
||||
switch (origin) {
|
||||
case kSeekBegin: want_pos = 0 + offset; break;
|
||||
case kSeekCurrent: want_pos = _pos + offset; break;
|
||||
case kSeekEnd: want_pos = _len + offset; break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
// clamp to a valid range
|
||||
_pos = static_cast<size_t>(MAX<soff_t>(0, want_pos));
|
||||
_pos = std::min(_len, _pos); // clamp to EOS
|
||||
return static_cast<soff_t>(_pos);
|
||||
}
|
||||
|
||||
size_t MemoryStream::Write(const void *buffer, size_t size) {
|
||||
if (!_buf || (_pos >= _buf_sz)) {
|
||||
return 0;
|
||||
}
|
||||
size = MIN(size, _buf_sz - _pos);
|
||||
memcpy(_buf + _pos, buffer, size);
|
||||
_pos += size;
|
||||
// will increase len if writing after eos, otherwise = overwrite at pos
|
||||
_len = MAX(_len, _pos);
|
||||
return size;
|
||||
}
|
||||
|
||||
int32_t MemoryStream::WriteByte(uint8_t val) {
|
||||
if (!_buf || (_pos >= _buf_sz)) {
|
||||
return -1;
|
||||
}
|
||||
*(_buf + _pos) = val;
|
||||
_pos++;
|
||||
// will increase len if writing after eos, otherwise = overwrite at pos
|
||||
_len = MAX(_len, _pos);
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
VectorStream::VectorStream(const std::vector<uint8_t> &cbuf, DataEndianess stream_endianess)
|
||||
: MemoryStream(&cbuf.front(), cbuf.size(), stream_endianess)
|
||||
, _vec(nullptr) {
|
||||
}
|
||||
|
||||
VectorStream::VectorStream(std::vector<uint8_t> &buf, StreamWorkMode mode, DataEndianess stream_endianess)
|
||||
: MemoryStream(((mode == kStream_Read) && (buf.size() > 0)) ? &buf.front() : nullptr, buf.size(), mode, stream_endianess)
|
||||
, _vec(&buf) {
|
||||
}
|
||||
|
||||
void VectorStream::Close() {
|
||||
_vec = nullptr;
|
||||
MemoryStream::Close();
|
||||
}
|
||||
|
||||
bool VectorStream::CanRead() const {
|
||||
return _mode == kStream_Read;
|
||||
}
|
||||
|
||||
bool VectorStream::CanWrite() const {
|
||||
return _mode == kStream_Write;
|
||||
}
|
||||
|
||||
size_t VectorStream::Write(const void *buffer, size_t size) {
|
||||
if (_pos + size > _len) {
|
||||
_vec->resize(_pos + size);
|
||||
_len = _pos + size;
|
||||
}
|
||||
memcpy(_vec->data() + _pos, buffer, size);
|
||||
_pos += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
int32_t VectorStream::WriteByte(uint8_t val) {
|
||||
if (_pos == _len) {
|
||||
_vec->push_back(val);
|
||||
_len++;
|
||||
} else {
|
||||
(*_vec)[_pos] = val;
|
||||
}
|
||||
_pos++;
|
||||
return val;
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
118
engines/ags/shared/util/memory_stream.h
Normal file
118
engines/ags/shared/util/memory_stream.h
Normal file
@@ -0,0 +1,118 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// MemoryStream does reading and writing over the buffer of bytes stored in
|
||||
// memory. Currently has rather trivial implementation. Does not own a buffer
|
||||
// itself, but works with the provided C-buffer pointer, which means that the
|
||||
// buffer object *must* persist until stream is closed.
|
||||
//
|
||||
// VectorStream is a specialized implementation that works with std::vector.
|
||||
// Unlike base MemoryStream provides continiously resizing buffer for writing.
|
||||
// TODO: separate StringStream for reading & writing String object?
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_MEMORY_STREAM_H
|
||||
#define AGS_SHARED_UTIL_MEMORY_STREAM_H
|
||||
|
||||
#include "common/std/vector.h"
|
||||
#include "ags/shared/util/data_stream.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class MemoryStream : public DataStream {
|
||||
public:
|
||||
// Construct memory stream in the read-only mode over a const C-buffer;
|
||||
// reading will never exceed buf_sz bytes;
|
||||
// buffer must persist in memory until the stream is closed.
|
||||
MemoryStream(const uint8_t *cbuf, size_t buf_sz, DataEndianess stream_endianess = kLittleEndian);
|
||||
// Construct memory stream in the chosen mode over a given C-buffer;
|
||||
// neither reading nor writing will ever exceed buf_sz bytes;
|
||||
// buffer must persist in memory until the stream is closed.
|
||||
MemoryStream(uint8_t *buf, size_t buf_sz, StreamWorkMode mode, DataEndianess stream_endianess = kLittleEndian);
|
||||
~MemoryStream() override {}
|
||||
|
||||
void Close() override;
|
||||
bool Flush() override;
|
||||
|
||||
// Is stream valid (underlying data initialized properly)
|
||||
bool IsValid() const override;
|
||||
// Is end of stream
|
||||
bool EOS() const override;
|
||||
// Total length of stream (if known)
|
||||
soff_t GetLength() const override;
|
||||
// Current position (if known)
|
||||
soff_t GetPosition() const override;
|
||||
bool CanRead() const override;
|
||||
bool CanWrite() const override;
|
||||
bool CanSeek() const override;
|
||||
|
||||
size_t Read(void *buffer, size_t size) override;
|
||||
int32_t ReadByte() override;
|
||||
size_t Write(const void *buffer, size_t size) override;
|
||||
int32_t WriteByte(uint8_t b) override;
|
||||
|
||||
soff_t Seek(soff_t offset, StreamSeek origin) override;
|
||||
|
||||
protected:
|
||||
const uint8_t *_cbuf = nullptr; // readonly buffer ptr
|
||||
size_t _buf_sz = 0u; // hard buffer limit
|
||||
size_t _len = 0u; // calculated length of stream
|
||||
const StreamWorkMode _mode;
|
||||
size_t _pos = 0u; // current stream pos
|
||||
|
||||
private:
|
||||
uint8_t *_buf = nullptr; // writeable buffer ptr
|
||||
};
|
||||
|
||||
|
||||
class VectorStream : public MemoryStream {
|
||||
public:
|
||||
// Construct memory stream in the read-only mode over a const std::vector;
|
||||
// vector must persist in memory until the stream is closed.
|
||||
VectorStream(const std::vector<uint8_t> &cbuf, DataEndianess stream_endianess = kLittleEndian);
|
||||
// Construct memory stream in the chosen mode over a given std::vector;
|
||||
// vector must persist in memory until the stream is closed.
|
||||
VectorStream(std::vector<uint8_t> &buf, StreamWorkMode mode, DataEndianess stream_endianess = kLittleEndian);
|
||||
~VectorStream() override {}
|
||||
|
||||
void Close() override;
|
||||
|
||||
bool CanRead() const override;
|
||||
bool CanWrite() const override;
|
||||
|
||||
size_t Write(const void *buffer, size_t size) override;
|
||||
int32_t WriteByte(uint8_t b) override;
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> *_vec = nullptr; // writeable vector (may be null)
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
422
engines/ags/shared/util/multi_file_lib.cpp
Normal file
422
engines/ags/shared/util/multi_file_lib.cpp
Normal file
@@ -0,0 +1,422 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/util/bbop.h"
|
||||
#include "ags/shared/util/multi_file_lib.h"
|
||||
#include "ags/shared/util/stream.h"
|
||||
#include "ags/shared/util/string_utils.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
namespace MFLUtil {
|
||||
const char *HeadSig = "CLIB\x1a";
|
||||
const char *HeadSigMinimal = "CLIB";
|
||||
const char *TailSig = "CLIB\x1\x2\x3\x4SIGE";
|
||||
|
||||
static const size_t SingleFilePswLen = 13;
|
||||
|
||||
static const size_t MaxAssetFileLen = 100;
|
||||
static const size_t MaxDataFileLen = 50;
|
||||
static const size_t V10LibFileLen = 20;
|
||||
static const size_t V10AssetFileLen = 25;
|
||||
|
||||
static const int EncryptionRandSeed = 9338638;
|
||||
static const char *EncryptionString = "My\x1\xde\x4Jibzle";
|
||||
|
||||
MFLError ReadSigsAndVersion(Stream *in, MFLVersion *p_lib_version, soff_t *p_abs_offset);
|
||||
MFLError ReadSingleFileLib(AssetLibInfo &lib, Stream *in);
|
||||
MFLError ReadMultiFileLib(AssetLibInfo &lib, Stream *in, MFLVersion lib_version);
|
||||
MFLError ReadV10(AssetLibInfo &lib, Stream *in, MFLVersion lib_version);
|
||||
MFLError ReadV20(AssetLibInfo &lib, Stream *in);
|
||||
MFLError ReadV21(AssetLibInfo &lib, Stream *in);
|
||||
MFLError ReadV30(AssetLibInfo &lib, Stream *in, MFLVersion lib_version);
|
||||
|
||||
void WriteV30(const AssetLibInfo &lib, Stream *out);
|
||||
|
||||
// Encryption / decryption
|
||||
int GetNextPseudoRand(int &rand_val);
|
||||
void DecryptText(char *text);
|
||||
void ReadEncArray(void *data, size_t size, size_t count, Stream *in, int &rand_val);
|
||||
int8_t ReadEncInt8(Stream *in, int &rand_val);
|
||||
int32_t ReadEncInt32(Stream *in, int &rand_val);
|
||||
void ReadEncString(char *buffer, size_t max_len, Stream *in, int &rand_val);
|
||||
} // namespace MFLUtil
|
||||
|
||||
|
||||
MFLUtil::MFLError MFLUtil::TestIsMFL(Stream *in, bool test_is_main) {
|
||||
MFLVersion lib_version;
|
||||
MFLError err = ReadSigsAndVersion(in, &lib_version, nullptr);
|
||||
if (err == kMFLNoError) {
|
||||
if (lib_version >= kMFLVersion_MultiV10 && test_is_main) {
|
||||
// this version supports multiple data files, check if it is the first one
|
||||
if (in->ReadInt8() != 0)
|
||||
return kMFLErrNoLibBase; // not first datafile in chain
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
MFLUtil::MFLError MFLUtil::ReadHeader(AssetLibInfo &lib, Stream *in) {
|
||||
MFLVersion lib_version;
|
||||
soff_t abs_offset;
|
||||
MFLError err = ReadSigsAndVersion(in, &lib_version, &abs_offset);
|
||||
if (err != kMFLNoError)
|
||||
return err;
|
||||
|
||||
if (lib_version >= kMFLVersion_MultiV10) {
|
||||
// read newer clib versions (versions 10+)
|
||||
err = ReadMultiFileLib(lib, in, lib_version);
|
||||
} else {
|
||||
// read older clib versions (versions 1 to 9)
|
||||
err = ReadSingleFileLib(lib, in);
|
||||
}
|
||||
|
||||
// apply absolute offset for the assets contained in base data file
|
||||
// (since only base data file may be EXE file, other clib parts are always on their own)
|
||||
if (abs_offset > 0) {
|
||||
for (auto &asset : lib.AssetInfos) {
|
||||
if (asset.LibUid == 0)
|
||||
asset.Offset += abs_offset;
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
MFLUtil::MFLError MFLUtil::ReadSigsAndVersion(Stream *in, MFLVersion *p_lib_version, soff_t *p_abs_offset) {
|
||||
soff_t abs_offset = 0; // library offset in this file
|
||||
String sig;
|
||||
// check multifile lib signature at the beginning of file
|
||||
sig.ReadCount(in, strlen(HeadSig));
|
||||
if ((sig.Compare(HeadSig) != 0) && (sig.Compare(HeadSigMinimal) != 0)) {
|
||||
// signature not found, check signature at the end of file
|
||||
in->Seek(-(soff_t)strlen(TailSig), kSeekEnd);
|
||||
// by definition, tail marks the max absolute offset value
|
||||
auto tail_abs_offset = in->GetPosition();
|
||||
sig.ReadCount(in, strlen(TailSig));
|
||||
// signature not found, return error code
|
||||
if (sig.Compare(TailSig) != 0)
|
||||
return kMFLErrNoLibSig;
|
||||
|
||||
// it's an appended-to-end-of-exe thing;
|
||||
// now we need to read multifile lib offset value, but we do not know
|
||||
// if its 32-bit or 64-bit yet, so we'll have to test both
|
||||
in->Seek(-(soff_t)strlen(TailSig) - sizeof(int64_t), kSeekEnd);
|
||||
abs_offset = in->ReadInt64();
|
||||
in->Seek(-(soff_t)sizeof(int32_t), kSeekCurrent);
|
||||
soff_t abs_offset_32 = in->ReadInt32();
|
||||
|
||||
// test for header signature again, with 64-bit and 32-bit offsets if necessary
|
||||
sig.Empty();
|
||||
if (abs_offset > 0 && abs_offset < (soff_t)(tail_abs_offset - strlen(HeadSig))) {
|
||||
in->Seek(abs_offset, kSeekBegin);
|
||||
sig.ReadCount(in, strlen(HeadSig));
|
||||
}
|
||||
|
||||
// try again with 32-bit offset
|
||||
if ((sig.Compare(HeadSig) != 0) && (sig.Compare(HeadSigMinimal) != 0)) {
|
||||
abs_offset = abs_offset_32;
|
||||
if (abs_offset > 0 && abs_offset < (soff_t)(tail_abs_offset - strlen(HeadSig))) {
|
||||
in->Seek(abs_offset, kSeekBegin);
|
||||
sig.ReadCount(in, strlen(HeadSig));
|
||||
}
|
||||
if (sig.Compare(HeadSig) != 0) {
|
||||
// nope, no luck, bad / unknown format
|
||||
return kMFLErrNoLibSig;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we've reached this point we must be right behind the header signature
|
||||
|
||||
// read library header
|
||||
MFLVersion lib_version = static_cast<MFLVersion>(in->ReadInt8());
|
||||
if ((lib_version != kMFLVersion_SingleLib) && (lib_version != kMFLVersion_MultiV10) &&
|
||||
(lib_version != kMFLVersion_MultiV11) && (lib_version != kMFLVersion_MultiV15) &&
|
||||
(lib_version != kMFLVersion_MultiV20) && (lib_version != kMFLVersion_MultiV21) &&
|
||||
lib_version != kMFLVersion_MultiV30)
|
||||
return kMFLErrLibVersion; // unsupported version
|
||||
|
||||
if (p_lib_version)
|
||||
*p_lib_version = lib_version;
|
||||
if (p_abs_offset)
|
||||
*p_abs_offset = abs_offset;
|
||||
return kMFLNoError;
|
||||
}
|
||||
|
||||
MFLUtil::MFLError MFLUtil::ReadSingleFileLib(AssetLibInfo &lib, Stream *in) {
|
||||
char passwmodifier = in->ReadInt8();
|
||||
in->ReadInt8(); // unused byte
|
||||
lib.LibFileNames.resize(1); // only one library part
|
||||
size_t asset_count = (uint16)in->ReadInt16();
|
||||
lib.AssetInfos.resize(asset_count);
|
||||
|
||||
in->Seek(SingleFilePswLen, kSeekCurrent); // skip password dooberry
|
||||
char fn_buf[SingleFilePswLen + 1];
|
||||
// read information on contents
|
||||
for (size_t i = 0; i < asset_count; ++i) {
|
||||
in->Read(fn_buf, SingleFilePswLen);
|
||||
fn_buf[SingleFilePswLen] = 0;
|
||||
for (char *c = fn_buf; *c; ++c)
|
||||
*c -= passwmodifier;
|
||||
lib.AssetInfos[i].FileName = fn_buf;
|
||||
lib.AssetInfos[i].LibUid = 0;
|
||||
}
|
||||
for (size_t i = 0; i < asset_count; ++i) {
|
||||
lib.AssetInfos[i].Size = (uint32)in->ReadInt32();
|
||||
}
|
||||
in->Seek(2 * asset_count, kSeekCurrent); // skip flags & ratio
|
||||
lib.AssetInfos[0].Offset = in->GetPosition();
|
||||
// set offsets (assets are positioned in sequence)
|
||||
for (size_t i = 1; i < asset_count; ++i) {
|
||||
lib.AssetInfos[i].Offset = lib.AssetInfos[i - 1].Offset + lib.AssetInfos[i - 1].Size;
|
||||
}
|
||||
// return success
|
||||
return kMFLNoError;
|
||||
}
|
||||
|
||||
MFLUtil::MFLError MFLUtil::ReadMultiFileLib(AssetLibInfo &lib, Stream *in, MFLVersion lib_version) {
|
||||
if (in->ReadInt8() != 0)
|
||||
return kMFLErrNoLibBase; // not first datafile in chain
|
||||
|
||||
if (lib_version >= kMFLVersion_MultiV30) {
|
||||
// read new clib format with 64-bit files support
|
||||
return ReadV30(lib, in, lib_version);
|
||||
}
|
||||
if (lib_version >= kMFLVersion_MultiV21) {
|
||||
// read new clib format with encoding support (versions 21+)
|
||||
return ReadV21(lib, in);
|
||||
} else if (lib_version == kMFLVersion_MultiV20) {
|
||||
// read new clib format without encoding support (version 20)
|
||||
return ReadV20(lib, in);
|
||||
}
|
||||
// read older clib format (versions 10 to 19), the ones with shorter filenames
|
||||
return ReadV10(lib, in, lib_version);
|
||||
}
|
||||
|
||||
MFLUtil::MFLError MFLUtil::ReadV10(AssetLibInfo &lib, Stream *in, MFLVersion lib_version) {
|
||||
// number of clib parts
|
||||
size_t mf_count = (uint32)in->ReadInt32();
|
||||
lib.LibFileNames.resize(mf_count);
|
||||
// filenames for all clib parts; filenames are only 20 chars long in this format version
|
||||
for (size_t i = 0; i < mf_count; ++i) {
|
||||
lib.LibFileNames[i].ReadCount(in, V10LibFileLen);
|
||||
}
|
||||
|
||||
// number of files in clib
|
||||
size_t asset_count = (uint32)in->ReadInt32();
|
||||
// read information on clib contents
|
||||
lib.AssetInfos.resize(asset_count);
|
||||
// filename array is only 25 chars long in this format version
|
||||
char fn_buf[V10AssetFileLen];
|
||||
for (size_t i = 0; i < asset_count; ++i) {
|
||||
in->Read(fn_buf, V10AssetFileLen);
|
||||
if (lib_version >= kMFLVersion_MultiV11)
|
||||
DecryptText(fn_buf);
|
||||
lib.AssetInfos[i].FileName = fn_buf;
|
||||
}
|
||||
for (size_t i = 0; i < asset_count; ++i)
|
||||
lib.AssetInfos[i].Offset = (uint32)in->ReadInt32();
|
||||
for (size_t i = 0; i < asset_count; ++i)
|
||||
lib.AssetInfos[i].Size = (uint32)in->ReadInt32();
|
||||
for (size_t i = 0; i < asset_count; ++i)
|
||||
lib.AssetInfos[i].LibUid = (uint32)in->ReadInt8();
|
||||
return kMFLNoError;
|
||||
}
|
||||
|
||||
MFLUtil::MFLError MFLUtil::ReadV20(AssetLibInfo &lib, Stream *in) {
|
||||
// number of clib parts
|
||||
size_t mf_count = (uint32)in->ReadInt32();
|
||||
lib.LibFileNames.resize(mf_count);
|
||||
// filenames for all clib parts
|
||||
for (size_t i = 0; i < mf_count; ++i) {
|
||||
lib.LibFileNames[i].Read(in, MaxDataFileLen);
|
||||
}
|
||||
|
||||
// number of files in clib
|
||||
size_t asset_count = (uint32)in->ReadInt32();
|
||||
// read information on clib contents
|
||||
lib.AssetInfos.resize(asset_count);
|
||||
char fn_buf[MaxAssetFileLen];
|
||||
for (size_t i = 0; i < asset_count; ++i) {
|
||||
size_t len = in->ReadInt16();
|
||||
len /= 5; // CHECKME: why 5?
|
||||
if (len > MaxAssetFileLen)
|
||||
return kMFLErrAssetNameLong;
|
||||
in->Read(fn_buf, len);
|
||||
// decrypt filenames
|
||||
DecryptText(fn_buf);
|
||||
lib.AssetInfos[i].FileName = fn_buf;
|
||||
}
|
||||
for (size_t i = 0; i < asset_count; ++i)
|
||||
lib.AssetInfos[i].Offset = (uint32)in->ReadInt32();
|
||||
for (size_t i = 0; i < asset_count; ++i)
|
||||
lib.AssetInfos[i].Size = (uint32)in->ReadInt32();
|
||||
for (size_t i = 0; i < asset_count; ++i)
|
||||
lib.AssetInfos[i].LibUid = (uint32)in->ReadInt8();
|
||||
return kMFLNoError;
|
||||
}
|
||||
|
||||
MFLUtil::MFLError MFLUtil::ReadV21(AssetLibInfo &lib, Stream *in) {
|
||||
// init randomizer
|
||||
int rand_val = in->ReadInt32() + EncryptionRandSeed;
|
||||
// number of clib parts
|
||||
size_t mf_count = (uint32)ReadEncInt32(in, rand_val);
|
||||
lib.LibFileNames.resize(mf_count);
|
||||
// filenames for all clib parts
|
||||
char fn_buf[MaxDataFileLen > MaxAssetFileLen ? MaxDataFileLen : MaxAssetFileLen];
|
||||
for (size_t i = 0; i < mf_count; ++i) {
|
||||
ReadEncString(fn_buf, MaxDataFileLen, in, rand_val);
|
||||
lib.LibFileNames[i] = fn_buf;
|
||||
}
|
||||
|
||||
// number of files in clib
|
||||
size_t asset_count = (uint32)ReadEncInt32(in, rand_val);
|
||||
// read information on clib contents
|
||||
lib.AssetInfos.resize(asset_count);
|
||||
for (size_t i = 0; i < asset_count; ++i) {
|
||||
ReadEncString(fn_buf, MaxAssetFileLen, in, rand_val);
|
||||
lib.AssetInfos[i].FileName = fn_buf;
|
||||
}
|
||||
for (size_t i = 0; i < asset_count; ++i)
|
||||
lib.AssetInfos[i].Offset = (uint32)ReadEncInt32(in, rand_val);
|
||||
for (size_t i = 0; i < asset_count; ++i)
|
||||
lib.AssetInfos[i].Size = (uint32)ReadEncInt32(in, rand_val);
|
||||
for (size_t i = 0; i < asset_count; ++i)
|
||||
lib.AssetInfos[i].LibUid = (uint32)ReadEncInt8(in, rand_val);
|
||||
return kMFLNoError;
|
||||
}
|
||||
|
||||
MFLUtil::MFLError MFLUtil::ReadV30(AssetLibInfo &lib, Stream *in, MFLVersion /* lib_version */) {
|
||||
// NOTE: removed encryption like in v21, because it makes little sense
|
||||
// with open-source program. But if really wanted it may be restored
|
||||
// as one of the options here.
|
||||
/* int flags = */ in->ReadInt32(); // reserved options
|
||||
// number of clib parts
|
||||
size_t mf_count = (uint32)in->ReadInt32();
|
||||
lib.LibFileNames.resize(mf_count);
|
||||
// filenames for all clib parts
|
||||
for (size_t i = 0; i < mf_count; ++i)
|
||||
lib.LibFileNames[i] = String::FromStream(in);
|
||||
|
||||
// number of files in clib
|
||||
size_t asset_count = (uint32)in->ReadInt32();
|
||||
// read information on clib contents
|
||||
lib.AssetInfos.resize(asset_count);
|
||||
for (auto &asset : lib.AssetInfos) {
|
||||
asset.FileName = String::FromStream(in);
|
||||
asset.LibUid = (uint8)in->ReadInt8();
|
||||
asset.Offset = in->ReadInt64();
|
||||
asset.Size = in->ReadInt64();
|
||||
}
|
||||
return kMFLNoError;
|
||||
}
|
||||
|
||||
void MFLUtil::WriteHeader(const AssetLibInfo &lib, MFLVersion lib_version, int lib_index, Stream *out) {
|
||||
out->Write(HeadSig, strlen(HeadSig));
|
||||
out->WriteInt8(static_cast<uint8_t>(lib_version));
|
||||
out->WriteInt8(static_cast<uint8_t>(lib_index)); // file number
|
||||
|
||||
// First datafile in chain: write the table of contents
|
||||
if (lib_index == 0) {
|
||||
WriteV30(lib, out);
|
||||
}
|
||||
}
|
||||
|
||||
void MFLUtil::WriteV30(const AssetLibInfo &lib, Stream *out) {
|
||||
out->WriteInt32(0); // reserved options
|
||||
// filenames for all library parts
|
||||
out->WriteInt32(lib.LibFileNames.size());
|
||||
for (size_t i = 0; i < lib.LibFileNames.size(); ++i) {
|
||||
StrUtil::WriteCStr(lib.LibFileNames[i], out);
|
||||
}
|
||||
|
||||
// table of contents for all assets in library
|
||||
out->WriteInt32(lib.AssetInfos.size());
|
||||
for (const auto &asset : lib.AssetInfos) {
|
||||
StrUtil::WriteCStr(asset.FileName, out);
|
||||
out->WriteInt8(static_cast<uint8_t>(asset.LibUid));
|
||||
out->WriteInt64(asset.Offset);
|
||||
out->WriteInt64(asset.Size);
|
||||
}
|
||||
}
|
||||
|
||||
void MFLUtil::WriteEnder(soff_t lib_offset, MFLVersion lib_index, Stream *out) {
|
||||
if (lib_index < kMFLVersion_MultiV30)
|
||||
out->WriteInt32((int32_t)lib_offset);
|
||||
else
|
||||
out->WriteInt64(lib_offset);
|
||||
out->Write(TailSig, strlen(TailSig));
|
||||
}
|
||||
|
||||
void MFLUtil::DecryptText(char *text) {
|
||||
size_t adx = 0;
|
||||
while (true) {
|
||||
text[0] -= EncryptionString[adx];
|
||||
if (text[0] == 0)
|
||||
break;
|
||||
|
||||
adx++;
|
||||
text++;
|
||||
|
||||
if (adx > 10) // CHECKME: why 10?
|
||||
adx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int MFLUtil::GetNextPseudoRand(int &rand_val) {
|
||||
return (((rand_val = rand_val * 214013L
|
||||
+ 2531011L) >> 16) & 0x7fff);
|
||||
}
|
||||
|
||||
void MFLUtil::ReadEncArray(void *data, size_t size, size_t count, Stream *in, int &rand_val) {
|
||||
in->ReadArray(data, size, count);
|
||||
uint8_t *ch = (uint8_t *)data;
|
||||
const size_t len = size * count;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
ch[i] = static_cast<uint8_t>(ch[i] - GetNextPseudoRand(rand_val));
|
||||
}
|
||||
}
|
||||
|
||||
int8_t MFLUtil::ReadEncInt8(Stream *in, int &rand_val) {
|
||||
return static_cast<int8_t>(in->ReadInt8() - GetNextPseudoRand(rand_val));
|
||||
}
|
||||
|
||||
int32_t MFLUtil::ReadEncInt32(Stream *in, int &rand_val) {
|
||||
int val;
|
||||
ReadEncArray(&val, sizeof(int32_t), 1, in, rand_val);
|
||||
return BBOp::Int32FromLE(val);
|
||||
}
|
||||
|
||||
void MFLUtil::ReadEncString(char *buffer, size_t max_len, Stream *in, int &rand_val) {
|
||||
size_t i = 0;
|
||||
while ((i == 0) || (buffer[i - 1] != 0)) {
|
||||
buffer[i] = static_cast<int8_t>(in->ReadInt8() - GetNextPseudoRand(rand_val));
|
||||
if (i < max_len - 1)
|
||||
i++;
|
||||
else
|
||||
break; // avoid an endless loop
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AGS
|
||||
} // namespace Shared
|
||||
} // namespace AGS3
|
||||
80
engines/ags/shared/util/multi_file_lib.h
Normal file
80
engines/ags/shared/util/multi_file_lib.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// A packed data file header and functions for reading it from the stream.
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// The code is based on CLIB32, by Chris Jones (1998-99), DJGPP implementation
|
||||
// of the CLIB reader.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_MULTI_FILE_LIB_H
|
||||
#define AGS_SHARED_UTIL_MULTI_FILE_LIB_H
|
||||
|
||||
#include "ags/shared/core/asset.h"
|
||||
#include "ags/shared/util/stream.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
//
|
||||
// MultiFileLib utilities: (de)serialization of asset library in MFL format
|
||||
//
|
||||
namespace MFLUtil {
|
||||
enum MFLError {
|
||||
kMFLNoError = 0,
|
||||
kMFLErrNoLibSig = -1, // library signature does not match
|
||||
kMFLErrLibVersion = -2, // library version unsupported
|
||||
kMFLErrNoLibBase = -3, // file is not library base (head)
|
||||
kMFLErrLibAssetCount = -4, // too many assets in library
|
||||
kMFLErrAssetNameLong = -5 // asset name is too long (old formats only)
|
||||
};
|
||||
|
||||
enum MFLVersion {
|
||||
kMFLVersion_SingleLib = 6,
|
||||
kMFLVersion_MultiV10 = 10,
|
||||
kMFLVersion_MultiV11 = 11,
|
||||
kMFLVersion_MultiV15 = 15, // unknown differences
|
||||
kMFLVersion_MultiV20 = 20,
|
||||
kMFLVersion_MultiV21 = 21,
|
||||
kMFLVersion_MultiV30 = 30 // 64-bit file support, lose limits
|
||||
};
|
||||
|
||||
// Maximal number of the data files in one library chain (1-byte index)
|
||||
const size_t MaxMultiLibFiles = 256;
|
||||
|
||||
MFLError TestIsMFL(Stream *in, bool test_is_main = false);
|
||||
MFLError ReadHeader(AssetLibInfo &lib, Stream *in);
|
||||
|
||||
void WriteHeader(const AssetLibInfo &lib, MFLVersion lib_version, int lib_index, Stream *out);
|
||||
void WriteEnder(soff_t lib_offset, MFLVersion lib_index, Stream *out);
|
||||
} // namespace MFLUtil
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
302
engines/ags/shared/util/path.cpp
Normal file
302
engines/ags/shared/util/path.cpp
Normal file
@@ -0,0 +1,302 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/core/platform.h"
|
||||
#if AGS_PLATFORM_OS_WINDOWS
|
||||
#define NOMINMAX
|
||||
//include <windows.h>
|
||||
#endif
|
||||
#include "ags/lib/allegro/file.h"
|
||||
#include "ags/shared/util/path.h"
|
||||
#include "ags/shared/util/stdio_compat.h"
|
||||
#include "ags/shared/util/file.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
namespace Path {
|
||||
|
||||
String get_filename(const String &path) {
|
||||
Common::String p = path;
|
||||
size_t i = p.findLastOf('/');
|
||||
return (i == Common::String::npos) ? path : String(p.c_str() + i + 1);
|
||||
}
|
||||
|
||||
String get_extension(const String &path) {
|
||||
Common::String filename = get_filename(path);
|
||||
size_t i = filename.findLastOf('.');
|
||||
return (i == Common::String::npos) ?
|
||||
filename : Common::String(filename.c_str() + i + 1);
|
||||
}
|
||||
|
||||
String GetParent(const String &path) {
|
||||
const char *cstr = path.GetCStr();
|
||||
const char *ptr_end = cstr + path.GetLength();
|
||||
for (const char *ptr = ptr_end; ptr >= cstr; --ptr) {
|
||||
if (*ptr == '/' || *ptr == PATH_ALT_SEPARATOR)
|
||||
return String(cstr, ptr - cstr);
|
||||
}
|
||||
return ".";
|
||||
}
|
||||
|
||||
String GetFilename(const String &path) {
|
||||
return get_filename(path.GetCStr());
|
||||
}
|
||||
|
||||
String GetFileExtension(const String &path) {
|
||||
return get_extension(path.GetCStr());
|
||||
}
|
||||
|
||||
int ComparePaths(const String &path1, const String &path2) {
|
||||
// Make minimal absolute paths
|
||||
String fixed_path1 = MakeAbsolutePath(path1);
|
||||
String fixed_path2 = MakeAbsolutePath(path2);
|
||||
|
||||
#if AGS_PLATFORM_OS_WINDOWS
|
||||
// On Windows make sure both are represented as short names (at least until we support wide paths)
|
||||
fixed_path1 = GetPathInASCII(fixed_path1);
|
||||
fixed_path2 = GetPathInASCII(fixed_path2);
|
||||
#endif
|
||||
|
||||
fixed_path1.TrimRight('/');
|
||||
fixed_path2.TrimRight('/');
|
||||
|
||||
int cmp_result =
|
||||
fixed_path1.CompareNoCase(fixed_path2);
|
||||
return cmp_result;
|
||||
}
|
||||
|
||||
String GetDirectoryPath(const String &path) {
|
||||
if (File::IsDirectory(path))
|
||||
return path;
|
||||
|
||||
String dir = path;
|
||||
FixupPath(dir);
|
||||
size_t slash_at = dir.FindCharReverse('/');
|
||||
if (slash_at != String::NoIndex) {
|
||||
dir.ClipMid(slash_at + 1);
|
||||
return dir;
|
||||
}
|
||||
return "./";
|
||||
}
|
||||
|
||||
bool IsSameOrSubDir(const String &parent, const String &path) {
|
||||
char can_parent[MAX_PATH_SZ];
|
||||
char can_path[MAX_PATH_SZ];
|
||||
char relative[MAX_PATH_SZ];
|
||||
// canonicalize_filename treats "." as "./." (file in working dir)
|
||||
const char *use_parent = parent == "." ? "./" : parent.GetCStr();
|
||||
const char *use_path = path == "." ? "./" : path.GetCStr();
|
||||
canonicalize_filename(can_parent, use_parent, sizeof(can_parent));
|
||||
canonicalize_filename(can_path, use_path, sizeof(can_path));
|
||||
const char *pstr = make_relative_filename(relative, can_parent, can_path, sizeof(relative));
|
||||
if (!pstr)
|
||||
return false;
|
||||
for (pstr = strstr(pstr, ".."); pstr && *pstr; pstr = strstr(pstr, "..")) {
|
||||
pstr += 2;
|
||||
if (*pstr == '/' || *pstr == '\\' || *pstr == 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsRelativePath(const String &path) {
|
||||
return is_relative_filename(path.GetCStr()) != 0;
|
||||
}
|
||||
|
||||
void FixupPath(String &path) {
|
||||
//#if AGS_PLATFORM_OS_WINDOWS
|
||||
path.Replace('\\', '/'); // bring Windows path separators to uniform style
|
||||
//#endif
|
||||
path.MergeSequences('/');
|
||||
}
|
||||
|
||||
String MakePathNoSlash(const String &path) {
|
||||
String dir_path = path;
|
||||
FixupPath(dir_path);
|
||||
#if AGS_PLATFORM_OS_WINDOWS
|
||||
// if the path is 'x:/' don't strip the slash
|
||||
if (path.GetLength() == 3 && path[1u] == ':')
|
||||
;
|
||||
else
|
||||
#endif
|
||||
// if the path is '/' don't strip the slash
|
||||
if (dir_path.GetLength() > 1)
|
||||
dir_path.TrimRight('/');
|
||||
return dir_path;
|
||||
}
|
||||
|
||||
String MakeTrailingSlash(const String &path) {
|
||||
if (path.GetLast() == '/' || path.GetLast() == '\\')
|
||||
return path;
|
||||
String dir_path = String::FromFormat("%s/", path.GetCStr());
|
||||
FixupPath(dir_path);
|
||||
return dir_path;
|
||||
}
|
||||
|
||||
String MakeAbsolutePath(const String &path) {
|
||||
if (path.IsEmpty()) {
|
||||
return "";
|
||||
}
|
||||
// canonicalize_filename treats "." as "./." (file in working dir)
|
||||
String abs_path = path == "." ? "./" : path;
|
||||
#if AGS_PLATFORM_OS_WINDOWS
|
||||
// NOTE: cannot use long path names in the engine, because it does not have unicode strings support
|
||||
//
|
||||
//char long_path_buffer[MAX_PATH_SZ];
|
||||
//if (GetLongPathNameA(path, long_path_buffer, MAX_PATH_SZ) > 0)
|
||||
//{
|
||||
// abs_path = long_path_buffer;
|
||||
//}
|
||||
#endif
|
||||
char buf[MAX_PATH_SZ];
|
||||
canonicalize_filename(buf, abs_path.GetCStr(), sizeof(buf));
|
||||
abs_path = buf;
|
||||
FixupPath(abs_path);
|
||||
return abs_path;
|
||||
}
|
||||
|
||||
String MakeRelativePath(const String &base, const String &path) {
|
||||
char can_parent[MAX_PATH_SZ];
|
||||
char can_path[MAX_PATH_SZ];
|
||||
char relative[MAX_PATH_SZ];
|
||||
// canonicalize_filename treats "." as "./." (file in working dir)
|
||||
const char *use_parent = base == "." ? "./" : base.GetCStr();
|
||||
const char *use_path = path == "." ? "./" : path.GetCStr(); // FIXME?
|
||||
canonicalize_filename(can_parent, use_parent, sizeof(can_parent));
|
||||
canonicalize_filename(can_path, use_path, sizeof(can_path));
|
||||
String rel_path = make_relative_filename(relative, can_parent, can_path, sizeof(relative));
|
||||
FixupPath(rel_path);
|
||||
return rel_path;
|
||||
}
|
||||
|
||||
String &AppendPath(String &path, const String &child) {
|
||||
if (path.IsEmpty())
|
||||
path = child;
|
||||
else if (!child.IsEmpty())
|
||||
path.AppendFmt("/%s", child.GetCStr());
|
||||
FixupPath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
String ConcatPaths(const String &parent, const String &child) {
|
||||
if (parent.IsEmpty())
|
||||
return child;
|
||||
if (child.IsEmpty())
|
||||
return parent;
|
||||
String path = String::FromFormat("%s/%s", parent.GetCStr(), child.GetCStr());
|
||||
FixupPath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
String ConcatPaths(String &buf, const String &parent, const String &child) {
|
||||
if (parent.IsEmpty())
|
||||
buf = child;
|
||||
else if (child.IsEmpty())
|
||||
buf = parent;
|
||||
else
|
||||
buf.Format("%s/%s", parent.GetCStr(), child.GetCStr());
|
||||
FixupPath(buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
String MakePath(const String &parent, const String &filename) {
|
||||
String path = String::FromFormat("%s/%s", parent.GetCStr(), filename.GetCStr());
|
||||
FixupPath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
String MakePath(const String &parent, const String &filename, const String &ext) {
|
||||
String path = String::FromFormat("%s/%s.%s", parent.GetCStr(), filename.GetCStr(), ext.GetCStr());
|
||||
FixupPath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
std::vector<String> Split(const String &path) {
|
||||
return path.Split('/');
|
||||
}
|
||||
|
||||
String FixupSharedFilename(const String &filename) {
|
||||
const char *illegal_chars = "\\/:?\"<>|*";
|
||||
String fixed_name = filename;
|
||||
for (size_t i = 0; i < filename.GetLength(); ++i) {
|
||||
if (filename[i] < ' ') {
|
||||
fixed_name.SetAt(i, '_');
|
||||
} else {
|
||||
for (const char *ch_ptr = illegal_chars; *ch_ptr; ++ch_ptr)
|
||||
if (filename[i] == *ch_ptr)
|
||||
fixed_name.SetAt(i, '_');
|
||||
}
|
||||
}
|
||||
return fixed_name;
|
||||
}
|
||||
|
||||
String GetPathInASCII(const String &path) {
|
||||
#if AGS_PLATFORM_OS_WINDOWS
|
||||
char ascii_buffer[MAX_PATH_SZ];
|
||||
if (GetShortPathNameA(path.GetCStr(), ascii_buffer, MAX_PATH_SZ) == 0)
|
||||
return "";
|
||||
return ascii_buffer;
|
||||
#else
|
||||
// TODO: implement conversion for other platforms!
|
||||
return path;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if AGS_PLATFORM_OS_WINDOWS
|
||||
String WidePathNameToAnsi(LPCWSTR pathw) {
|
||||
WCHAR short_path[MAX_PATH_SZ];
|
||||
char ascii_buffer[MAX_PATH_SZ];
|
||||
LPCWSTR arg_path = pathw;
|
||||
if (GetShortPathNameW(arg_path, short_path, MAX_PATH_SZ) == 0)
|
||||
return "";
|
||||
WideCharToMultiByte(CP_ACP, 0, short_path, -1, ascii_buffer, MAX_PATH_SZ, NULL, NULL);
|
||||
return ascii_buffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
String GetCmdLinePathInASCII(const char *arg, int arg_index) {
|
||||
#if AGS_PLATFORM_OS_WINDOWS
|
||||
// Hack for Windows in case there are unicode chars in the path.
|
||||
// The normal argv[] array has ????? instead of the unicode chars
|
||||
// and fails, so instead we manually get the short file name, which
|
||||
// is always using ASCII chars.
|
||||
int wargc = 0;
|
||||
LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
|
||||
if (wargv == nullptr)
|
||||
return "";
|
||||
String path;
|
||||
if (arg_index <= wargc)
|
||||
path = WidePathNameToAnsi(wargv[arg_index]);
|
||||
LocalFree(wargv);
|
||||
return path;
|
||||
#else
|
||||
// TODO: implement conversion for other platforms!
|
||||
return arg;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Path
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
110
engines/ags/shared/util/path.h
Normal file
110
engines/ags/shared/util/path.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Platform-independent Path functions
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_PATH_H
|
||||
#define AGS_SHARED_UTIL_PATH_H
|
||||
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
#define PATH_ALT_SEPARATOR ('\\')
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
namespace Path {
|
||||
|
||||
// Get a filename from a path
|
||||
String get_filename(const String &path);
|
||||
// Get an extension from a filename
|
||||
String get_extension(const String &path);
|
||||
|
||||
// Returns parent directory of the given path;
|
||||
// returns "." (current dir) if the path does not contain a parent segment
|
||||
String GetParent(const String &path);
|
||||
// Returns parent directory of the given path;
|
||||
// returns "." (current dir) if the path does not contain a parent segment
|
||||
String GetFilename(const String &path);
|
||||
// Returns file's extension; file may be a fully qualified path too
|
||||
String GetFileExtension(const String &path);
|
||||
|
||||
// Makes a platform-dependant path comparison.
|
||||
// This takes into consideration platform's filename case (in)sensivity and
|
||||
// DOS-compatible 8.3 filenames;
|
||||
// The result value corresponds to stdlib strcmp function.
|
||||
int ComparePaths(const String &path1, const String &path2);
|
||||
|
||||
// Returns path to the actual directory, referenced by given path;
|
||||
// if path is a directory, returns path unchanged, if path is a file, returns
|
||||
// parent directory containing that file.
|
||||
String GetDirectoryPath(const String &path);
|
||||
// Tells if the path points to the parent path's location or lower directory;
|
||||
// return FALSE if the path points to outside of the parent location.
|
||||
bool IsSameOrSubDir(const String &parent, const String &path);
|
||||
// Tells if the path is relative.
|
||||
bool IsRelativePath(const String &path);
|
||||
|
||||
// Makes a path have only '/' slashes; this is to make it easier to work
|
||||
// with path, knowing it contains only one type of directory separators
|
||||
void FixupPath(String &path);
|
||||
// Fixups path and removes trailing slash
|
||||
String MakePathNoSlash(const String &path);
|
||||
// Fixups path and adds trailing slash if it's missing
|
||||
String MakeTrailingSlash(const String &path);
|
||||
// Converts any path to an absolute path; relative paths are assumed to
|
||||
// refer to the current working directory.
|
||||
String MakeAbsolutePath(const String &path);
|
||||
// Tries to create a relative path that would point to 'path' location
|
||||
// if walking out of the 'base'. Returns empty string on failure.
|
||||
// NOTE: the 'base' is only considered a directory if it has a trailing slash.
|
||||
String MakeRelativePath(const String &base, const String &path);
|
||||
// Creates path by combining directory, file name and extension
|
||||
String MakePath(const String &parent, const String &filename, const String &ext);
|
||||
// Appends another section to existing path
|
||||
String &AppendPath(String &path, const String &child);
|
||||
// Concatenates parent and relative paths
|
||||
String ConcatPaths(const String &parent, const String &child);
|
||||
String ConcatPaths(String &buf, const String &parent, const String &child);
|
||||
// Splits path into components, divided by path separator
|
||||
std::vector<String> Split(const String &path);
|
||||
|
||||
// Subsitutes illegal characters with '_'. This function uses a combined set
|
||||
// of illegal chars from all the supported platforms to make a name that
|
||||
// could be copied across systems without problems.
|
||||
String FixupSharedFilename(const String &filename);
|
||||
|
||||
// Converts filepath into ASCII variant; returns empty string on failure
|
||||
String GetPathInASCII(const String &path);
|
||||
// Converts filepath from command line's argument into ASCII variant
|
||||
String GetCmdLinePathInASCII(const char *arg, int arg_index);
|
||||
} // namespace Path
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
366
engines/ags/shared/util/resource_cache.h
Normal file
366
engines/ags/shared/util/resource_cache.h
Normal file
@@ -0,0 +1,366 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// ResourceCache is an abstract storage that tracks use history with MRU list.
|
||||
// Cache is limited to a certain size, in bytes.
|
||||
// When a total size of items reaches the limit, and more items are put into,
|
||||
// the Cache uses MRU list to find the least used items and disposes them
|
||||
// one by one until the necessary space is freed.
|
||||
// ResourceCache's implementations must provide a method for calculating an
|
||||
// item's size.
|
||||
//
|
||||
// Supports copyable and movable items, have 2 variants of Put function for
|
||||
// each of them. This lets it store both std::shared_ptr and std::unique_ptr.
|
||||
//
|
||||
// TODO: support data Priority, which tells which items may be disposed
|
||||
// when adding new item and surpassing the cache limit.
|
||||
//
|
||||
// TODO: as an option, consider to have Locked items separate from the normal
|
||||
// cache limit, and probably have their own limit setting as a safety measure.
|
||||
// (after reaching this limit ResourceCache would simply ignore any further
|
||||
// Lock commands until some items are unlocked.)
|
||||
// Rethink this when it's time to design a better resource handling in AGS.
|
||||
//
|
||||
// TODO: as an option, consider supporting a specialized container type that
|
||||
// has an associative container's interface, but is optimized for having most
|
||||
// keys allocated in large continious sequences by default.
|
||||
// This may be suitable for e.g. sprites, and may (or may not?) save some mem.
|
||||
//
|
||||
// Only for the reference: one of the ideas is for container to have a table
|
||||
// of arrays of fixed size internally. When getting an item the hash would be
|
||||
// first divided on array size to find the array the item resides in, then the
|
||||
// item is taken from item from slot index = (hash - arrsize * arrindex).
|
||||
// Find out if there is already a hash table kind that follows similar
|
||||
// principle.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_RESOURCE_CACHE_H
|
||||
#define AGS_SHARED_UTIL_RESOURCE_CACHE_H
|
||||
|
||||
#include "common/std/list.h"
|
||||
#include "common/std/map.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
template<typename TKey, typename TValue,
|
||||
typename TSize = size_t, typename HashFn = std::hash<TKey> >
|
||||
class ResourceCache {
|
||||
public:
|
||||
// Flags determine management rules for the particular item
|
||||
enum ItemFlags {
|
||||
// Locked items are temporarily saved from disposal when freeing cache space;
|
||||
// they still count towards the cache size though.
|
||||
kCacheItem_Locked = 0x0001,
|
||||
// External items are managed strictly by the external user;
|
||||
// do not count towards the cache size, do not prevent caching normal items.
|
||||
// They cannot be locked or released (considered permanently locked),
|
||||
// only removed by request.
|
||||
kCacheItem_External = 0x0002,
|
||||
};
|
||||
|
||||
ResourceCache(TSize max_size = 0u)
|
||||
: _maxSize(max_size), _sectionLocked(_mru.end()) {}
|
||||
|
||||
// Get the MRU cache size limit
|
||||
inline size_t GetMaxCacheSize() const { return _maxSize; }
|
||||
// Get the current total MRU cache size
|
||||
inline size_t GetCacheSize() const { return _cacheSize; }
|
||||
// Get the summed size of locked items (included in total cache size)
|
||||
inline size_t GetLockedSize() const { return _lockedSize; }
|
||||
// Get the summed size of external items (excluded from total cache size)
|
||||
inline size_t GetExternalSize() const { return _externalSize; }
|
||||
|
||||
// Set the MRU cache size limit
|
||||
void SetMaxCacheSize(TSize size) {
|
||||
_maxSize = size;
|
||||
FreeMem(0u); // makes sure it does not exceed max size
|
||||
}
|
||||
|
||||
// Tells if particular key is in the cache
|
||||
bool Exists(const TKey &key) const {
|
||||
return _storage.find(key) != _storage.end();
|
||||
}
|
||||
|
||||
// Gets the item with the given key if it exists;
|
||||
// reorders the item as recently used.
|
||||
const TValue &Get(const TKey &key) {
|
||||
auto it = _storage.find(key);
|
||||
if (it == _storage.end())
|
||||
return _dummy; // no such key
|
||||
|
||||
// Unless locked, move the item ref to the beginning of the MRU list
|
||||
const auto &item = it->second;
|
||||
if ((item.Flags & kCacheItem_Locked) == 0)
|
||||
_mru.splice(_mru.begin(), _mru, item.MruIt);
|
||||
return item.Value;
|
||||
}
|
||||
|
||||
// Add particular item into the cache, disposes existing item if such key is already taken.
|
||||
// If a new item will exceed the cache size limit, cache will remove oldest items
|
||||
// in order to free mem.
|
||||
void Put(const TKey &key, const TValue &value, uint32_t flags = 0u) {
|
||||
if (_maxSize == 0)
|
||||
return; // cache is disabled
|
||||
auto it = _storage.find(key);
|
||||
if (it != _storage.end()) {
|
||||
// Remove previous cached item
|
||||
RemoveImpl(it);
|
||||
}
|
||||
PutImpl(key, TValue(value), flags); // make a temp local copy for safe std::move
|
||||
}
|
||||
|
||||
void Put(const TKey &key, TValue &&value, uint32_t flags = 0u) {
|
||||
if (_maxSize == 0)
|
||||
return; // cache is disabled
|
||||
auto it = _storage.find(key);
|
||||
if (it != _storage.end()) {
|
||||
// Remove previous cached item
|
||||
RemoveImpl(it);
|
||||
}
|
||||
PutImpl(key, std::move(value), flags);
|
||||
}
|
||||
|
||||
// Locks the item with the given key,
|
||||
// temporarily excluding it from MRU disposal rules
|
||||
void Lock(const TKey &key) {
|
||||
auto it = _storage.find(key);
|
||||
if (it == _storage.end())
|
||||
return; // no such key
|
||||
auto &item = it->second;
|
||||
if ((item.Flags & kCacheItem_Locked) != 0)
|
||||
return; // already locked
|
||||
|
||||
// Lock item and move to the locked section
|
||||
item.Flags |= kCacheItem_Locked;
|
||||
_mru.splice(_sectionLocked, _mru, item.MruIt); // CHECKME: TEST!!
|
||||
_sectionLocked = item.MruIt;
|
||||
_lockedSize += item.Size;
|
||||
}
|
||||
|
||||
// Releases (unlocks) the item with the given key,
|
||||
// adds it back to MRU disposal rules
|
||||
void Release(const TKey &key) {
|
||||
auto it = _storage.find(key);
|
||||
if (it == _storage.end())
|
||||
return; // no such key
|
||||
|
||||
auto &item = it->second;
|
||||
if ((item.Flags & kCacheItem_External) != 0)
|
||||
return; // never release external data, must be removed by user
|
||||
if ((item.Flags & kCacheItem_Locked) == 0)
|
||||
return; // not locked
|
||||
|
||||
// Unlock, and move the item to the beginning of the MRU list
|
||||
item.Flags &= ~kCacheItem_Locked;
|
||||
if (_sectionLocked == item.MruIt)
|
||||
_sectionLocked = std::next(item.MruIt);
|
||||
_mru.splice(_mru.begin(), _mru, item.MruIt); // CHECKME: TEST!!
|
||||
_lockedSize -= item.Size;
|
||||
}
|
||||
|
||||
// Deletes the cached item
|
||||
void Dispose(const TKey &key) {
|
||||
auto it = _storage.find(key);
|
||||
if (it == _storage.end())
|
||||
return; // no such key
|
||||
RemoveImpl(it);
|
||||
}
|
||||
|
||||
// Removes the item from the cache and returns to the caller.
|
||||
TValue Remove(const TKey &key) {
|
||||
auto it = _storage.find(key);
|
||||
if (it == _storage.end())
|
||||
return TValue(); // no such key
|
||||
TValue value = std::move(it->second.Value);
|
||||
RemoveImpl(it);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Disposes all items that are not locked or external
|
||||
void DisposeFreeItems() {
|
||||
for (auto mru_it = _sectionLocked; mru_it != _mru.end(); ++mru_it) {
|
||||
auto it = _storage.find(*mru_it);
|
||||
assert(it != _storage.end());
|
||||
auto &item = it->second;
|
||||
_cacheSize -= item.Size;
|
||||
_storage.erase(it);
|
||||
_mru.erase(mru_it);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the cache, dispose all items
|
||||
void Clear() {
|
||||
_storage.clear();
|
||||
_mru.clear();
|
||||
_sectionLocked = _mru.end();
|
||||
_cacheSize = 0u;
|
||||
_lockedSize = 0u;
|
||||
_externalSize = 0u;
|
||||
}
|
||||
|
||||
protected:
|
||||
struct TItem;
|
||||
// MRU list type
|
||||
typedef std::list<TKey> TMruList;
|
||||
// MRU list reference type
|
||||
typedef typename TMruList::iterator TMruIt;
|
||||
// Storage type
|
||||
typedef std::unordered_map<TKey, TItem, HashFn> TStorage;
|
||||
|
||||
struct TItem {
|
||||
TMruIt MruIt; // MRU list reference
|
||||
TValue Value;
|
||||
TSize Size = 0u;
|
||||
uint32_t Flags = 0u; // flags determine management rules for this item
|
||||
|
||||
TItem() = default;
|
||||
TItem(const TItem &item) = default;
|
||||
TItem(TItem &&item) = default;
|
||||
TItem(const TMruIt &mru_it, const TValue &value, const TSize size, uint32_t flags)
|
||||
: MruIt(mru_it), Value(value), Size(size), Flags(flags) {}
|
||||
TItem(const TMruIt &mru_it, TValue &&value, const TSize size, uint32_t flags)
|
||||
: MruIt(mru_it), Value(std::move(value)), Size(size), Flags(flags) {}
|
||||
TItem &operator=(const TItem &item) = default;
|
||||
TItem &operator=(TItem &&item) = default;
|
||||
};
|
||||
|
||||
// Calculates item size; expects to return 0 if an item is invalid
|
||||
// and should not be added to the cache.
|
||||
virtual TSize CalcSize(const TValue &item) = 0;
|
||||
|
||||
private:
|
||||
// Add particular item into the cache.
|
||||
// If a new item will exceed the cache size limit, cache will remove oldest items
|
||||
// in order to free mem.
|
||||
void PutImpl(const TKey &key, TValue &&value, uint32_t flags) {
|
||||
// Request item's size, and test if it's a valid item
|
||||
TSize size = CalcSize(value);
|
||||
if (size == 0u)
|
||||
return; // invalid item
|
||||
|
||||
if ((flags & kCacheItem_External) == 0) {
|
||||
// clear up space before adding
|
||||
if (_cacheSize + size > _maxSize)
|
||||
FreeMem(size);
|
||||
_cacheSize += size;
|
||||
} else {
|
||||
// always mark external data as locked, easier to handle
|
||||
flags |= kCacheItem_Locked;
|
||||
_externalSize += size;
|
||||
}
|
||||
|
||||
// Prepare a MRU slot, then add an item
|
||||
TMruIt mru_it = _mru.end();
|
||||
// only normal items are added to MRU at all
|
||||
if ((flags & kCacheItem_External) == 0) {
|
||||
if ((flags & kCacheItem_Locked) == 0) {
|
||||
// normal item, add to the list
|
||||
mru_it = _mru.insert(_mru.begin(), key);
|
||||
} else {
|
||||
// locked item, add to the dedicated list section
|
||||
mru_it = _mru.insert(_sectionLocked, key);
|
||||
_sectionLocked = mru_it;
|
||||
_lockedSize += size;
|
||||
}
|
||||
}
|
||||
TItem item = TItem(mru_it, std::move(value), size, flags);
|
||||
_storage[key] = std::move(item);
|
||||
}
|
||||
// Removes the item from the container
|
||||
void RemoveImpl(typename TStorage::iterator it) {
|
||||
auto &item = it->second;
|
||||
// normal items are removed from MRU, and discounted from cache size
|
||||
if ((item.Flags & kCacheItem_External) == 0) {
|
||||
TMruIt mru_it = item.MruIt;
|
||||
if (_sectionLocked == mru_it)
|
||||
_sectionLocked = std::next(mru_it);
|
||||
_cacheSize -= item.Size;
|
||||
if ((item.Flags & kCacheItem_Locked) != 0)
|
||||
_lockedSize -= item.Size;
|
||||
_mru.erase(mru_it);
|
||||
} else {
|
||||
_externalSize -= item.Size;
|
||||
}
|
||||
_storage.erase(it);
|
||||
}
|
||||
// Remove the oldest (least recently used) item in cache
|
||||
void DisposeOldest() {
|
||||
assert(_mru.begin() != _sectionLocked);
|
||||
if (_mru.begin() == _sectionLocked)
|
||||
return;
|
||||
// Remove from the storage and mru list
|
||||
auto mru_it = std::prev(_sectionLocked);
|
||||
auto it = _storage.find(*mru_it);
|
||||
assert(it != _storage.end());
|
||||
auto &item = it->second;
|
||||
assert((item.Flags & (kCacheItem_Locked | kCacheItem_External)) == 0);
|
||||
_cacheSize -= item.Size;
|
||||
_storage.erase(it);
|
||||
_mru.erase(mru_it);
|
||||
}
|
||||
// Keep disposing oldest elements until cache has at least the given free space
|
||||
void FreeMem(size_t space) {
|
||||
// TODO: consider sprite cache's behavior where it would just clear
|
||||
// whole cache in case disposing one by one were taking too much iterations
|
||||
while ((_mru.begin() != _sectionLocked) && (_cacheSize + space > _maxSize)) {
|
||||
DisposeOldest();
|
||||
}
|
||||
}
|
||||
|
||||
// Size of tracked data stored in this cache;
|
||||
// note that this is an abstract value, which may or not refer to an
|
||||
// actual size in bytes, and depends on the implementation.
|
||||
TSize _cacheSize = 0u;
|
||||
// Size of data locked (forbidden from disposal),
|
||||
// this size is *included* in _cacheSize; provided for stats.
|
||||
TSize _lockedSize = 0u;
|
||||
// Size of the external data, that is - data that does not count towards
|
||||
// cache limit, and which is not our reponsibility; provided for stats.
|
||||
TSize _externalSize = 0u;
|
||||
// Maximal size of tracked data.
|
||||
// When the inserted item increases the cache size past this limit,
|
||||
// the cache will try to free the space by removing oldest items.
|
||||
// "External" data does not count towards this limit.
|
||||
TSize _maxSize = 0u;
|
||||
// MRU list: the way to track which items were used recently.
|
||||
// When clearing up space for new items, cache first deletes the items
|
||||
// that were last time used long ago.
|
||||
TMruList _mru;
|
||||
// A locked section border iterator, points to the *last* locked item
|
||||
// starting from the end of the list, or equals _mru.end() if there's none.
|
||||
TMruIt _sectionLocked;
|
||||
// Key-to-mru lookup map
|
||||
TStorage _storage;
|
||||
// Dummy value, return in case of a missing key
|
||||
TValue _dummy;
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif // AGS_SHARED_UTIL_RESOURCE_CACHE_H
|
||||
161
engines/ags/shared/util/scaling.h
Normal file
161
engines/ags/shared/util/scaling.h
Normal file
@@ -0,0 +1,161 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Helper struct for scaling coordinates
|
||||
//
|
||||
// TODO: rewrite into proper Transform class.
|
||||
// Maybe implement as real matrix and/or floats if that is better/runs faster.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_SCALING_H
|
||||
#define AGS_SHARED_UTIL_SCALING_H
|
||||
|
||||
#include "ags/shared/core/types.h"
|
||||
#include "ags/shared/util/geometry.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class AxisScaling {
|
||||
public:
|
||||
AxisScaling()
|
||||
: _scale(kUnit)
|
||||
, _unscale(kUnit)
|
||||
, _srcOffset(0)
|
||||
, _dstOffset(0) {
|
||||
}
|
||||
|
||||
bool IsIdentity() const {
|
||||
return _scale == kUnit && _srcOffset == 0 && _dstOffset == 0;
|
||||
}
|
||||
|
||||
bool IsTranslateOnly() const {
|
||||
return _scale == kUnit;
|
||||
}
|
||||
|
||||
void Init(const int32_t src_length, const int32_t dst_length, const int32_t src_offset = 0, const int32_t dst_offset = 0) {
|
||||
_scale = kUnit;
|
||||
_unscale = kUnit;
|
||||
_srcOffset = src_offset;
|
||||
_dstOffset = dst_offset;
|
||||
|
||||
if (src_length != 0) {
|
||||
int32_t scale = (dst_length << kShift) / src_length;
|
||||
if (scale != 0) {
|
||||
_scale = scale;
|
||||
_unscale = scale;
|
||||
int32_t scaled_val = ScaleDistance(src_length);
|
||||
if (scaled_val < dst_length)
|
||||
_scale++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetSrcOffset(int32_t x) {
|
||||
_srcOffset = x;
|
||||
}
|
||||
|
||||
void SetDstOffset(int32_t x) {
|
||||
_dstOffset = x;
|
||||
}
|
||||
|
||||
inline int32_t GetSrcOffset() const {
|
||||
return _srcOffset;
|
||||
}
|
||||
|
||||
inline int32_t ScalePt(int32_t x) const {
|
||||
return (((x - _srcOffset) * _scale) >> kShift) + _dstOffset;
|
||||
}
|
||||
inline int32_t ScaleDistance(int32_t x) const {
|
||||
return ((x * _scale) >> kShift);
|
||||
}
|
||||
inline int32_t UnScalePt(int32_t x) const {
|
||||
return ((x - _dstOffset) << kShift) / _unscale + _srcOffset;
|
||||
}
|
||||
inline int32_t UnScaleDistance(int32_t x) const {
|
||||
return (x << kShift) / _unscale;
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t _scale;
|
||||
int32_t _unscale;
|
||||
int32_t _srcOffset;
|
||||
int32_t _dstOffset;
|
||||
};
|
||||
|
||||
struct PlaneScaling {
|
||||
AxisScaling X;
|
||||
AxisScaling Y;
|
||||
|
||||
bool IsIdentity() const {
|
||||
return X.IsIdentity() && Y.IsIdentity();
|
||||
}
|
||||
|
||||
bool IsTranslateOnly() const {
|
||||
return X.IsTranslateOnly() && Y.IsTranslateOnly();
|
||||
}
|
||||
|
||||
void Init(const Size &src_size, const Rect &dst_rect) {
|
||||
X.Init(src_size.Width, dst_rect.GetWidth(), 0, dst_rect.Left);
|
||||
Y.Init(src_size.Height, dst_rect.GetHeight(), 0, dst_rect.Top);
|
||||
}
|
||||
|
||||
void Init(const Rect &src_rect, const Rect &dst_rect) {
|
||||
X.Init(src_rect.GetWidth(), dst_rect.GetWidth(), src_rect.Left, dst_rect.Left);
|
||||
Y.Init(src_rect.GetHeight(), dst_rect.GetHeight(), src_rect.Top, dst_rect.Top);
|
||||
}
|
||||
|
||||
void SetSrcOffsets(int x, int y) {
|
||||
X.SetSrcOffset(x);
|
||||
Y.SetSrcOffset(y);
|
||||
}
|
||||
|
||||
void SetDestOffsets(int x, int y) {
|
||||
X.SetDstOffset(x);
|
||||
Y.SetDstOffset(y);
|
||||
}
|
||||
|
||||
inline Point Scale(const Point &p) const {
|
||||
return Point(X.ScalePt(p.X), Y.ScalePt(p.Y));
|
||||
}
|
||||
|
||||
inline Rect ScaleRange(const Rect &r) const {
|
||||
return RectWH(X.ScalePt(r.Left), Y.ScalePt(r.Top), X.ScaleDistance(r.GetWidth()), Y.ScaleDistance(r.GetHeight()));
|
||||
}
|
||||
|
||||
inline Point UnScale(const Point &p) const {
|
||||
return Point(X.UnScalePt(p.X), Y.UnScalePt(p.Y));
|
||||
}
|
||||
|
||||
inline Rect UnScaleRange(const Rect &r) const {
|
||||
return RectWH(X.UnScalePt(r.Left), Y.UnScalePt(r.Top), X.UnScaleDistance(r.GetWidth()), Y.UnScaleDistance(r.GetHeight()));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
152
engines/ags/shared/util/stdio_compat.cpp
Normal file
152
engines/ags/shared/util/stdio_compat.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/util/stdio_compat.h"
|
||||
#include "ags/shared/core/platform.h"
|
||||
#include "ags/shared/util/directory.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/system.h"
|
||||
#include "common/file.h"
|
||||
#include "common/fs.h"
|
||||
#include "common/savefile.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
int ags_fseek(Common::Stream *stream, file_off_t offset, int whence) {
|
||||
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(stream);
|
||||
Common::SeekableWriteStream *ws = dynamic_cast<Common::SeekableWriteStream *>(stream);
|
||||
|
||||
if (rs)
|
||||
return rs->seek(offset, whence) ? 0 : 1;
|
||||
else if (ws)
|
||||
return ws->seek(offset, whence) ? 0 : 1;
|
||||
else
|
||||
error("Seek on null stream");
|
||||
}
|
||||
|
||||
file_off_t ags_ftell(Common::Stream *stream) {
|
||||
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(stream);
|
||||
Common::SeekableWriteStream *ws = dynamic_cast<Common::SeekableWriteStream *>(stream);
|
||||
assert(rs || ws);
|
||||
return rs ? rs->pos() : ws->pos();
|
||||
}
|
||||
|
||||
static Common::FSNode getFSNode(const char *path) {
|
||||
Common::FSNode node;
|
||||
Common::String filePath(path);
|
||||
if (filePath.empty() || filePath == "." || filePath == "./")
|
||||
return Common::FSNode(ConfMan.getPath("path"));
|
||||
else if (filePath.hasPrefix("./")) {
|
||||
filePath = filePath.substr(2);
|
||||
node = Common::FSNode(ConfMan.getPath("path"));
|
||||
} else if (filePath.hasPrefixIgnoreCase(AGS::Shared::SAVE_FOLDER_PREFIX)) {
|
||||
filePath = filePath.substr(strlen(AGS::Shared::SAVE_FOLDER_PREFIX));
|
||||
node = Common::FSNode(ConfMan.getPath("savepath"));
|
||||
} else {
|
||||
node = Common::FSNode(Common::Path(filePath, '/'));
|
||||
if (node.isReadable())
|
||||
return node;
|
||||
node = Common::FSNode(ConfMan.getPath("path"));
|
||||
}
|
||||
|
||||
// Use FSDirectory for case-insensitive search
|
||||
Common::SharedPtr<Common::FSDirectory> dir(new Common::FSDirectory(node));
|
||||
|
||||
// Iterate through any further subfolders or filename
|
||||
size_t separator;
|
||||
while ((separator = filePath.find('/')) != Common::String::npos) {
|
||||
Common::Path member(filePath.substr(0, separator));
|
||||
dir.reset(dir->getSubDirectory(member));
|
||||
if (!dir)
|
||||
return Common::FSNode();
|
||||
filePath = Common::String(filePath.c_str() + separator + 1);
|
||||
}
|
||||
|
||||
if (filePath.empty())
|
||||
return dir->getFSNode();
|
||||
|
||||
Common::Path member(filePath);
|
||||
if (dir->hasFile(member)) {
|
||||
Common::ArchiveMemberPtr file = dir->getMember(member);
|
||||
if (file)
|
||||
return dir->getFSNode().getChild(file->getName());
|
||||
}
|
||||
|
||||
Common::FSDirectory *subDir = dir->getSubDirectory(member);
|
||||
if (subDir) {
|
||||
dir.reset(subDir);
|
||||
return dir->getFSNode();
|
||||
}
|
||||
|
||||
// The files does not exist, but create the FSNode anyway so that
|
||||
// the code using this can report the correct error rather than assert.
|
||||
return dir->getFSNode().getChild(filePath);
|
||||
}
|
||||
|
||||
Common::ArchiveMemberPtr getFile(const char *path) {
|
||||
Common::ArchiveMemberPtr archMember = SearchMan.getMember(path);
|
||||
if (archMember)
|
||||
return archMember;
|
||||
Common::FSNode node(getFSNode(path));
|
||||
if (!node.exists())
|
||||
return Common::ArchiveMemberPtr();
|
||||
return Common::ArchiveMemberPtr(new Common::FSNode(node));
|
||||
}
|
||||
|
||||
int ags_file_exists(const char *path) {
|
||||
Common::String sPath(path);
|
||||
|
||||
if (sPath.hasPrefix(AGS::Shared::SAVE_FOLDER_PREFIX)) {
|
||||
sPath = path + strlen(AGS::Shared::SAVE_FOLDER_PREFIX);
|
||||
Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(sPath);
|
||||
bool result = saveFile != nullptr;
|
||||
delete saveFile;
|
||||
|
||||
return result ? 1 : 0;
|
||||
} else {
|
||||
if (SearchMan.hasFile(path))
|
||||
return 1;
|
||||
Common::FSNode node = getFSNode(path);
|
||||
return node.exists() && !node.isDirectory() ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
int ags_directory_exists(const char *path) {
|
||||
Common::FSNode node = getFSNode(path);
|
||||
return node.exists() && node.isDirectory() ? 1 : 0;
|
||||
}
|
||||
|
||||
int ags_path_exists(const char *path) {
|
||||
if (SearchMan.hasFile(path))
|
||||
return 1;
|
||||
Common::FSNode node = getFSNode(path);
|
||||
return node.exists() ? 1 : 0;
|
||||
}
|
||||
|
||||
file_off_t ags_file_size(const char *path) {
|
||||
Common::ArchiveMemberPtr file(getFile(path));
|
||||
Common::ScopedPtr<Common::SeekableReadStream> stream(file->createReadStream());
|
||||
|
||||
return stream ? stream->size() : (file_off_t)-1;
|
||||
}
|
||||
|
||||
} // namespace AGS3
|
||||
47
engines/ags/shared/util/stdio_compat.h
Normal file
47
engines/ags/shared/util/stdio_compat.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_STDIO_COMPAT_H
|
||||
#define AGS_SHARED_UTIL_STDIO_COMPAT_H
|
||||
|
||||
#include "common/stream.h"
|
||||
#include "common/fs.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
typedef int64 file_off_t;
|
||||
|
||||
// Size of the buffer enough to accommodate a UTF-8 path
|
||||
const size_t MAX_PATH_SZ = 1024;
|
||||
|
||||
extern Common::ArchiveMemberPtr getFile(const char *path);
|
||||
|
||||
extern int ags_fseek(Common::Stream *stream, file_off_t offset, int whence);
|
||||
extern file_off_t ags_ftell(Common::Stream *stream);
|
||||
|
||||
extern int ags_file_exists(const char *path);
|
||||
extern int ags_directory_exists(const char *path);
|
||||
extern int ags_path_exists(const char *path);
|
||||
extern file_off_t ags_file_size(const char *path);
|
||||
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
41
engines/ags/shared/util/stream.cpp
Normal file
41
engines/ags/shared/util/stream.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/util/stream.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
size_t Stream::WriteByteCount(uint8_t b, size_t count) {
|
||||
if (!CanWrite())
|
||||
return 0;
|
||||
size_t size = 0;
|
||||
for (; count > 0; --count, ++size) {
|
||||
if (WriteByte(b) < 0)
|
||||
break;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
317
engines/ags/shared/util/stream.h
Normal file
317
engines/ags/shared/util/stream.h
Normal file
@@ -0,0 +1,317 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Base stream class.
|
||||
//
|
||||
// Provides default implementation for a few helper methods.
|
||||
//
|
||||
// Only streams with uncommon behavior should be derived directly from Stream.
|
||||
// Most I/O devices should inherit DataStream instead.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_STREAM_H
|
||||
#define AGS_SHARED_UTIL_STREAM_H
|
||||
|
||||
#include "ags/shared/util/iags_stream.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
#include "ags/lib/allegro/file.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
// TODO: merge with FileWorkMode (historical mistake)
|
||||
enum StreamWorkMode {
|
||||
kStream_Read,
|
||||
kStream_Write
|
||||
};
|
||||
|
||||
class Stream : public IAGSStream {
|
||||
public:
|
||||
Stream() = default;
|
||||
Stream(const String &path)
|
||||
: _path(path) {}
|
||||
virtual ~Stream() {}
|
||||
|
||||
// Returns an optional path of a stream's source, such as a filepath;
|
||||
// primarily for diagnostic purposes
|
||||
const String &GetPath() const { return _path; }
|
||||
// Tells if there were errors during previous io operation(s);
|
||||
// the call to GetError() *resets* the error record.
|
||||
virtual bool GetError() const { return false; }
|
||||
// Flush stream buffer to the underlying device
|
||||
virtual bool Flush() = 0;
|
||||
|
||||
//-----------------------------------------------------
|
||||
// Helper methods
|
||||
//-----------------------------------------------------
|
||||
int8_t ReadInt8() override {
|
||||
return ReadByte();
|
||||
}
|
||||
|
||||
size_t WriteInt8(int8_t val) override {
|
||||
int32_t ival = WriteByte(val);
|
||||
return ival >= 0 ? ival : 0;
|
||||
}
|
||||
|
||||
bool ReadBool() override {
|
||||
return ReadInt8() != 0;
|
||||
}
|
||||
|
||||
size_t WriteBool(bool val) override {
|
||||
return WriteInt8(val ? 1 : 0);
|
||||
}
|
||||
|
||||
// Practically identical to Read() and Write(), these two helpers' only
|
||||
// meaning is to underline the purpose of data being (de)serialized
|
||||
size_t ReadArrayOfInt8(int8_t *buffer, size_t count) override {
|
||||
return Read(buffer, count);
|
||||
}
|
||||
size_t WriteArrayOfInt8(const int8_t *buffer, size_t count) override {
|
||||
return Write(buffer, count);
|
||||
}
|
||||
|
||||
// Fill the requested number of bytes with particular value
|
||||
size_t WriteByteCount(uint8_t b, size_t count);
|
||||
|
||||
protected:
|
||||
String _path; // optional name of the stream's source (e.g. filepath)
|
||||
};
|
||||
|
||||
class ScummVMReadStream : public Common::SeekableReadStream {
|
||||
private:
|
||||
IAGSStream *_stream;
|
||||
DisposeAfterUse::Flag _disposeAfterUse;
|
||||
public:
|
||||
ScummVMReadStream(IAGSStream *src, DisposeAfterUse::Flag disposeAfterUse =
|
||||
DisposeAfterUse::YES) : _stream(src), _disposeAfterUse(disposeAfterUse) {
|
||||
}
|
||||
~ScummVMReadStream() override {
|
||||
if (_disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete _stream;
|
||||
}
|
||||
|
||||
bool eos() const override {
|
||||
return _stream->EOS();
|
||||
}
|
||||
|
||||
uint32 read(void *dataPtr, uint32 dataSize) override {
|
||||
return _stream->Read(dataPtr, dataSize);
|
||||
}
|
||||
|
||||
int64 pos() const override {
|
||||
return _stream->GetPosition();
|
||||
}
|
||||
|
||||
int64 size() const override {
|
||||
return _stream->GetLength();
|
||||
}
|
||||
|
||||
bool seek(int64 offset, int whence = SEEK_SET) override {
|
||||
StreamSeek origin = kSeekBegin;
|
||||
if (whence == SEEK_CUR)
|
||||
origin = kSeekCurrent;
|
||||
if (whence == SEEK_END)
|
||||
origin = kSeekEnd;
|
||||
|
||||
return _stream->Seek(offset, origin);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class ScummVMPackReadStream : public Common::SeekableReadStream {
|
||||
private:
|
||||
PACKFILE *_file;
|
||||
DisposeAfterUse::Flag _disposeAfterUse;
|
||||
public:
|
||||
ScummVMPackReadStream(PACKFILE *src, DisposeAfterUse::Flag disposeAfterUse =
|
||||
DisposeAfterUse::YES) : _file(src), _disposeAfterUse(disposeAfterUse) {
|
||||
}
|
||||
~ScummVMPackReadStream() override {
|
||||
if (_disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete _file;
|
||||
}
|
||||
|
||||
bool eos() const override {
|
||||
return _file->pack_feof();
|
||||
}
|
||||
|
||||
uint32 read(void *dataPtr, uint32 dataSize) override {
|
||||
return _file->pack_fread(dataPtr, dataSize);
|
||||
}
|
||||
|
||||
int64 pos() const override {
|
||||
error("Unsupported");
|
||||
}
|
||||
|
||||
int64 size() const override {
|
||||
error("Unsupported");
|
||||
}
|
||||
|
||||
bool seek(int64 offset, int whence = SEEK_SET) override {
|
||||
error("Unsupported");
|
||||
}
|
||||
};
|
||||
|
||||
class StreamScummVMFile : public Stream {
|
||||
private:
|
||||
Common::SeekableReadStream *_stream;
|
||||
DisposeAfterUse::Flag _disposeAfterUse;
|
||||
public:
|
||||
StreamScummVMFile(Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::NO) :
|
||||
_stream(stream), _disposeAfterUse(disposeAfterUse) {
|
||||
}
|
||||
~StreamScummVMFile() override {
|
||||
Close();
|
||||
}
|
||||
|
||||
void Close() override {
|
||||
if (_disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete _stream;
|
||||
_stream = nullptr;
|
||||
}
|
||||
|
||||
bool IsValid() const override {
|
||||
return _stream != nullptr;
|
||||
}
|
||||
bool EOS() const override {
|
||||
return _stream->eos();
|
||||
}
|
||||
soff_t GetLength() const override {
|
||||
return _stream->size();
|
||||
}
|
||||
soff_t GetPosition() const override {
|
||||
return _stream->pos();
|
||||
}
|
||||
bool CanRead() const override {
|
||||
return true;
|
||||
}
|
||||
bool CanWrite() const override {
|
||||
return false;
|
||||
}
|
||||
bool CanSeek() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t Read(void *buffer, size_t size) override {
|
||||
return _stream->read(buffer, size);
|
||||
}
|
||||
int32_t ReadByte() override {
|
||||
return _stream->readByte();
|
||||
}
|
||||
size_t Write(const void *buffer, size_t size) override {
|
||||
return 0;
|
||||
}
|
||||
int32_t WriteByte(uint8_t b) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t ReadInt8() override {
|
||||
return (int8)_stream->readByte();
|
||||
}
|
||||
int16_t ReadInt16() override {
|
||||
return _stream->readSint16LE();
|
||||
}
|
||||
int32_t ReadInt32() override {
|
||||
return _stream->readSint32LE();
|
||||
}
|
||||
int64_t ReadInt64() override {
|
||||
return _stream->readSint64LE();
|
||||
}
|
||||
bool ReadBool() override {
|
||||
return _stream->readByte() != 0;
|
||||
}
|
||||
size_t ReadArray(void *buffer, size_t elem_size, size_t count) override {
|
||||
return _stream->read(buffer, elem_size * count) / elem_size;
|
||||
}
|
||||
size_t ReadArrayOfInt8(int8_t *buffer, size_t count) override {
|
||||
return _stream->read(buffer, count);
|
||||
}
|
||||
size_t ReadArrayOfInt16(int16_t *buffer, size_t count) override {
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
*buffer++ = _stream->readSint16LE();
|
||||
return count;
|
||||
}
|
||||
size_t ReadArrayOfInt32(int32_t *buffer, size_t count) override {
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
*buffer++ = _stream->readSint32LE();
|
||||
return count;
|
||||
}
|
||||
size_t ReadArrayOfInt64(int64_t *buffer, size_t count) override {
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
*buffer++ = _stream->readSint64LE();
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t WriteInt8(int8_t val) override {
|
||||
return 0;
|
||||
}
|
||||
size_t WriteInt16(int16_t val) override {
|
||||
return 0;
|
||||
}
|
||||
size_t WriteInt32(int32_t val) override {
|
||||
return 0;
|
||||
}
|
||||
size_t WriteInt64(int64_t val) override {
|
||||
return 0;
|
||||
}
|
||||
size_t WriteBool(bool val) override {
|
||||
return 0;
|
||||
}
|
||||
size_t WriteArray(const void *buffer, size_t elem_size, size_t count) override {
|
||||
return 0;
|
||||
}
|
||||
size_t WriteArrayOfInt8(const int8_t *buffer, size_t count) override {
|
||||
return 0;
|
||||
}
|
||||
size_t WriteArrayOfInt16(const int16_t *buffer, size_t count) override {
|
||||
return 0;
|
||||
}
|
||||
size_t WriteArrayOfInt32(const int32_t *buffer, size_t count) override {
|
||||
return 0;
|
||||
}
|
||||
size_t WriteArrayOfInt64(const int64_t *buffer, size_t count) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
soff_t Seek(soff_t offset, StreamSeek origin = kSeekCurrent) override {
|
||||
return _stream->seek(offset, origin);
|
||||
}
|
||||
|
||||
bool GetError() const override {
|
||||
return _stream->err();
|
||||
}
|
||||
bool Flush() override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
1001
engines/ags/shared/util/string.cpp
Normal file
1001
engines/ags/shared/util/string.cpp
Normal file
File diff suppressed because it is too large
Load Diff
470
engines/ags/shared/util/string.h
Normal file
470
engines/ags/shared/util/string.h
Normal file
@@ -0,0 +1,470 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// String class with simple memory management and copy-on-write behavior.
|
||||
//
|
||||
// String objects do reference counting and share data buffer on assignment.
|
||||
// The reallocation and copying is done only when the string is modified.
|
||||
//
|
||||
// The copying of memory inside buffer is reduced to minimum. If the string is
|
||||
// truncated, it is not aligned to buffer head each time, instead the c-str
|
||||
// pointer is advanced, or null-terminator is put on the new place. Similarly,
|
||||
// when string is enlarged and new characters are prepended or appended, only
|
||||
// c-str pointer and null-terminator's position are changed, if there's enough
|
||||
// space before and after meaningful string data.
|
||||
//
|
||||
// The class provides means to reserve large amount of buffer space before
|
||||
// making modifications, as well as compacting buffer to minimal size.
|
||||
//
|
||||
// For all methods that expect C-string as parameter - if the null pointer is
|
||||
// passed in place of C-string it is treated in all aspects as a valid empty
|
||||
// string.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_STRING_H
|
||||
#define AGS_SHARED_UTIL_STRING_H
|
||||
|
||||
//include <stdarg.h>
|
||||
|
||||
#include "common/str.h"
|
||||
|
||||
#include "common/std/vector.h"
|
||||
#include "ags/shared/core/platform.h"
|
||||
#include "ags/shared/core/types.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class Stream;
|
||||
|
||||
class String {
|
||||
public:
|
||||
static const size_t NoIndex = (size_t)-1;
|
||||
|
||||
// Standard constructor: intialize empty string
|
||||
String();
|
||||
// Copy constructor
|
||||
String(const String &);
|
||||
// Move constructor
|
||||
String(String &&);
|
||||
// Initialize with C-string
|
||||
String(const char *cstr);
|
||||
// Initialize by copying up to N chars from C-string
|
||||
String(const char *cstr, size_t length);
|
||||
// Initialize by filling N chars with certain value
|
||||
String(char c, size_t count);
|
||||
// Initialize from a ScummVM string
|
||||
String(const Common::String &s);
|
||||
~String();
|
||||
|
||||
// Get underlying C-string for reading; this method guarantees valid C-string
|
||||
inline const char *GetCStr() const {
|
||||
return _cstr;
|
||||
}
|
||||
// Get character count
|
||||
inline size_t GetLength() const {
|
||||
return _len;
|
||||
}
|
||||
// Know if the string is empty (has no meaningful characters)
|
||||
inline bool IsEmpty() const {
|
||||
return _len == 0;
|
||||
}
|
||||
// Tells if the string is either empty or has only whitespace characters
|
||||
bool IsNullOrSpace() const;
|
||||
|
||||
// Those getters are for tests only, hence if AGS_PLATFORM_TEST
|
||||
#if defined(AGS_PLATFORM_TEST) && AGS_PLATFORM_TEST
|
||||
inline const char *GetBuffer() const {
|
||||
return _buf;
|
||||
}
|
||||
|
||||
inline size_t GetCapacity() const {
|
||||
return _bufHead ? _bufHead->Capacity : 0;
|
||||
}
|
||||
|
||||
inline size_t GetRefCount() const {
|
||||
return _bufHead ? _bufHead->RefCount : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Read() method implies that string length is initially unknown.
|
||||
// max_chars parameter determine the buffer size limit.
|
||||
// If stop_at_limit flag is set, it will read only up to the max_chars.
|
||||
// Otherwise (by default) hitting the limit won't stop stream reading;
|
||||
// the data will be read until null-terminator or EOS is met, and buffer
|
||||
// will contain only leftmost part of the longer string that fits in.
|
||||
// This method is better fit for reading from binary streams.
|
||||
void Read(Stream *in, size_t max_chars = 5 * 1024 * 1024, bool stop_at_limit = false);
|
||||
// ReadCount() reads up to N characters from stream, ignoring null-
|
||||
// terminator. This method is better fit for reading from text
|
||||
// streams, or when the length of string is known beforehand.
|
||||
void ReadCount(Stream *in, size_t count);
|
||||
// Write() puts the null-terminated string into the stream.
|
||||
void Write(Stream *out) const;
|
||||
// WriteCount() writes N characters to stream, filling the remaining
|
||||
// space with null-terminators when needed.
|
||||
void WriteCount(Stream *out, size_t count) const;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// String analysis methods
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Compares with given string
|
||||
int Compare(const String &str) const {
|
||||
return Compare(str._cstr);
|
||||
}
|
||||
int Compare(const char *cstr) const;
|
||||
int CompareNoCase(const String &str) const {
|
||||
return CompareNoCase(str._cstr);
|
||||
}
|
||||
int CompareNoCase(const char *cstr) const;
|
||||
// Compares the leftmost part of this string with given string
|
||||
int CompareLeft(const String &str, size_t count = -1) const {
|
||||
return CompareLeft(str._cstr, count != NoIndex ? count : str._len);
|
||||
}
|
||||
int CompareLeft(const char *cstr, size_t count = -1) const;
|
||||
int CompareLeftNoCase(const String &str, size_t count = -1) const {
|
||||
return CompareLeftNoCase(str._cstr, count != NoIndex ? count : str._len);
|
||||
}
|
||||
int CompareLeftNoCase(const char *cstr, size_t count = -1) const;
|
||||
// Compares any part of this string with given string
|
||||
int CompareMid(const String &str, size_t from, size_t count = -1) const {
|
||||
return CompareMid(str._cstr, from, count != NoIndex ? count : str._len);
|
||||
}
|
||||
int CompareMid(const char *cstr, size_t from, size_t count = -1) const;
|
||||
int CompareMidNoCase(const String &str, size_t from, size_t count = -1) const {
|
||||
return CompareMidNoCase(str._cstr, from, count != NoIndex ? count : str._len);
|
||||
}
|
||||
int CompareMidNoCase(const char *cstr, size_t from, size_t count = -1) const;
|
||||
// Compares the rightmost part of this string with given C-string
|
||||
int CompareRight(const String &str, size_t count = -1) const {
|
||||
return CompareRight(str._cstr, count != NoIndex ? count : str._len);
|
||||
}
|
||||
int CompareRight(const char *cstr, size_t count = -1) const;
|
||||
int CompareRightNoCase(const String &str, size_t count = -1) const {
|
||||
return CompareRightNoCase(str._cstr, count != NoIndex ? count : str._len);
|
||||
}
|
||||
int CompareRightNoCase(const char *cstr, size_t count = -1) const;
|
||||
// Convenience aliases for Compare functions
|
||||
inline bool Equals(const String &str) const {
|
||||
return Compare(str) == 0;
|
||||
}
|
||||
inline bool Equals(const char *cstr) const {
|
||||
return Compare(cstr) == 0;
|
||||
}
|
||||
inline bool StartsWith(const String &str) const {
|
||||
return CompareLeft(str) == 0;
|
||||
}
|
||||
inline bool StartsWith(const char *cstr) const {
|
||||
return CompareLeft(cstr) == 0;
|
||||
}
|
||||
inline bool EndsWidth(const String &str) const {
|
||||
return CompareRight(str) == 0;
|
||||
}
|
||||
inline bool EndsWidth(const char *cstr) const {
|
||||
return CompareRight(cstr) == 0;
|
||||
}
|
||||
|
||||
// These functions search for character or substring inside this string
|
||||
// and return the index of the (first) character, or -1 if nothing found.
|
||||
size_t FindChar(char c, size_t from = 0) const;
|
||||
size_t FindCharReverse(char c, size_t from = -1) const;
|
||||
size_t FindString(const String &str, size_t from = 0) const {
|
||||
return FindString(str._cstr, from);
|
||||
}
|
||||
size_t FindString(const char *cstr, size_t from = 0) const;
|
||||
|
||||
// Section methods treat string as a sequence of 'fields', separated by
|
||||
// special character. They search for a substring consisting of all such
|
||||
// 'fields' from the 'first' to the 'last', inclusive; the bounding
|
||||
// separators are optionally included too.
|
||||
// Section indexes are zero-based. The first (0th) section is always
|
||||
// located before the first separator and the last section is always
|
||||
// located after the last separator, meaning that if the outermost
|
||||
// character in string is separator char, there's still an empty trailing
|
||||
// field beyond that.
|
||||
// This also means that there's always at least one section in any string,
|
||||
// even if there are no separating chars.
|
||||
bool FindSection(char separator, size_t first, size_t last, bool exclude_first_sep, bool exclude_last_sep,
|
||||
size_t &from, size_t &to) const;
|
||||
|
||||
// Get Nth character with bounds check (as opposed to subscript operator)
|
||||
inline char GetAt(size_t index) const {
|
||||
return (index < _len) ? _cstr[index] : 0;
|
||||
}
|
||||
inline char GetLast() const {
|
||||
return (_len > 0) ? _cstr[_len - 1] : 0;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Value cast methods
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
int ToInt() const;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Factory methods
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Wraps the given string buffer without owning it, won't count references,
|
||||
// won't delete it at destruction. Can be used with string literals.
|
||||
static String Wrapper(const char *cstr);
|
||||
|
||||
// TODO: investigate C++11 solution for variadic templates (would that be more convenient here?)
|
||||
|
||||
static String FromFormat(const char *fcstr, ...);
|
||||
static String FromFormatV(const char *fcstr, va_list argptr);
|
||||
// Reads stream until null-terminator or EOS
|
||||
static String FromStream(Stream *in, size_t max_chars = 5 * 1024 * 1024, bool stop_at_limit = false);
|
||||
// Reads up to N chars from stream
|
||||
static String FromStreamCount(Stream *in, size_t count);
|
||||
|
||||
// Creates a lowercased copy of the string
|
||||
String Lower() const;
|
||||
// Creates an uppercased copy of the string
|
||||
String Upper() const;
|
||||
|
||||
// Extract N leftmost characters as a new string
|
||||
String Left(size_t count) const;
|
||||
// Extract up to N characters starting from given index
|
||||
String Mid(size_t from, size_t count = -1) const;
|
||||
// Extract N rightmost characters
|
||||
String Right(size_t count) const;
|
||||
|
||||
// Extract leftmost part, separated by the given char; if no separator was
|
||||
// found returns the whole string
|
||||
String LeftSection(char separator, bool exclude_separator = true) const;
|
||||
// Extract rightmost part, separated by the given char; if no separator was
|
||||
// found returns the whole string
|
||||
String RightSection(char separator, bool exclude_separator = true) const;
|
||||
// Extract the range of Xth to Yth fields, separated by the given character
|
||||
String Section(char separator, size_t first, size_t last,
|
||||
bool exclude_first_sep = true, bool exclude_last_sep = true) const;
|
||||
// Splits the string into segments divided by the instances of a given character,
|
||||
// including empty segments e.g. if separators follow each other;
|
||||
// returns at least one segment (equal to full string if no separator was found)
|
||||
std::vector<String> Split(char separator) const;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// String modification methods
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Ensure string has at least space to store N chars;
|
||||
// this does not change string contents, nor length
|
||||
void Reserve(size_t max_length);
|
||||
// Ensure string has at least space to store N additional chars
|
||||
void ReserveMore(size_t more_length);
|
||||
// Make string's buffer as small as possible to hold current data
|
||||
void Compact();
|
||||
|
||||
// Append* methods add content at the string's end, increasing its length
|
||||
// Appends another string to this string
|
||||
void Append(const String &str);
|
||||
void Append(const char *cstr) {
|
||||
String str = String::Wrapper(cstr);
|
||||
Append(str);
|
||||
}
|
||||
void Append(const char *cstr, size_t len);
|
||||
// Appends a single character
|
||||
void AppendChar(char c);
|
||||
// Appends a formatted string
|
||||
void AppendFmt(MSVC_PRINTF const char *fcstr, ...) GCC_PRINTF(2, 3);
|
||||
void AppendFmtv(const char *fcstr, va_list argptr);
|
||||
// Clip* methods decrease the string, removing defined part
|
||||
// Cuts off leftmost N characters
|
||||
void ClipLeft(size_t count);
|
||||
// Cuts out N characters starting from given index
|
||||
void ClipMid(size_t from, size_t count = -1);
|
||||
// Cuts off rightmost N characters
|
||||
void ClipRight(size_t count);
|
||||
// Cuts off leftmost part, separated by the given char; if no separator was
|
||||
// found cuts whole string, leaving empty string
|
||||
void ClipLeftSection(char separator, bool include_separator = true);
|
||||
// Cuts off rightmost part, separated by the given char; if no separator
|
||||
// was found cuts whole string, leaving empty string
|
||||
void ClipRightSection(char separator, bool include_separator = true);
|
||||
// Cuts out the range of Xth to Yth fields separated by the given character
|
||||
void ClipSection(char separator, size_t first, size_t last,
|
||||
bool include_first_sep = true, bool include_last_sep = true);
|
||||
// Sets string length to zero
|
||||
void Empty();
|
||||
// Makes a new string by filling N chars with certain value
|
||||
void FillString(char c, size_t count);
|
||||
// Makes a new string by putting in parameters according to format string
|
||||
void Format(const char *fcstr, ...);
|
||||
void FormatV(const char *fcstr, va_list argptr);
|
||||
// Decrement ref counter and deallocate data if must.
|
||||
// Free() should be called only when buffer is not needed anymore;
|
||||
// if string must be truncated to zero length, but retain the allocated
|
||||
// memory, call Empty() instead.
|
||||
void Free();
|
||||
// Convert string to lowercase equivalent
|
||||
void MakeLower();
|
||||
// Convert string to uppercase equivalent
|
||||
void MakeUpper();
|
||||
// Merges sequences of same characters into one
|
||||
void MergeSequences(char c = 0);
|
||||
// Prepend* methods add content before the string's head, increasing its length
|
||||
// Prepends another string to this string
|
||||
void Prepend(const String &str);
|
||||
void Prepend(const char *cstr) {
|
||||
String str = String::Wrapper(cstr); Prepend(str);
|
||||
}
|
||||
// Prepends a single character
|
||||
void PrependChar(char c);
|
||||
// Replaces all occurrences of one character with another character
|
||||
void Replace(char what, char with);
|
||||
// Replaces all occurrences of one substring with another substring
|
||||
void Replace(const String &what, const String &with);
|
||||
void Replace(const char *what, const char *with) {
|
||||
String whats = String::Wrapper(what), withs = String::Wrapper(with); Replace(whats, withs);
|
||||
}
|
||||
// Replaces particular substring with another substring; new substring
|
||||
// may have different length
|
||||
void ReplaceMid(size_t from, size_t count, const String &str);
|
||||
void ReplaceMid(size_t from, size_t count, const char *cstr) {
|
||||
String str = String::Wrapper(cstr); ReplaceMid(from, count, str);
|
||||
}
|
||||
// Reverses the string
|
||||
void Reverse();
|
||||
// Reverse the multibyte unicode string
|
||||
// FIXME: name? invent some consistent naming for necessary multibyte funcs,
|
||||
// proper utf8 support where necessary
|
||||
void ReverseUTF8();
|
||||
// Overwrite the Nth character of the string; does not change string's length
|
||||
void SetAt(size_t index, char c);
|
||||
// Makes a new string by copying up to N chars from C-string
|
||||
void SetString(const char *cstr, size_t length = -1);
|
||||
// For all Trim functions, if given character value is 0, all whitespace
|
||||
// characters (space, tabs, CRLF) are removed.
|
||||
// Remove heading and trailing characters from the string
|
||||
void Trim(char c = 0);
|
||||
// Remove heading characters from the string;
|
||||
void TrimLeft(char c = 0);
|
||||
// Remove trailing characters from the string
|
||||
void TrimRight(char c = 0);
|
||||
// Truncate* methods decrease the string to the part of itself
|
||||
// Truncate the string to the leftmost N characters
|
||||
void TruncateToLeft(size_t count);
|
||||
// Truncate the string to the middle N characters
|
||||
void TruncateToMid(size_t from, size_t count = -1);
|
||||
// Truncate the string to the rightmost N characters
|
||||
void TruncateToRight(size_t count);
|
||||
// Truncate the string to the leftmost part, separated by the given char;
|
||||
// if no separator was found leaves string unchanged
|
||||
void TruncateToLeftSection(char separator, bool exclude_separator = true);
|
||||
// Truncate the string to the rightmost part, separated by the given char;
|
||||
// if no separator was found leaves string unchanged
|
||||
void TruncateToRightSection(char separator, bool exclude_separator = true);
|
||||
// Truncate the string to range of Xth to Yth fields separated by the
|
||||
// given character
|
||||
void TruncateToSection(char separator, size_t first, size_t last,
|
||||
bool exclude_first_sep = true, bool exclude_last_sep = true);
|
||||
// Wraps the given string buffer without owning it, won't count references,
|
||||
// won't delete it at destruction. Can be used with string literals.
|
||||
void Wrap(const char *cstr);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Operators
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Assign String by sharing data reference
|
||||
String &operator=(const String &str);
|
||||
// Move operator
|
||||
String &operator=(String &&str);
|
||||
// Assign C-string by copying contents
|
||||
String &operator=(const char *cstr);
|
||||
inline char operator[](size_t index) const {
|
||||
assert(index < _len);
|
||||
return _cstr[index];
|
||||
}
|
||||
inline bool operator ==(const String &str) const {
|
||||
return Compare(str) == 0;
|
||||
}
|
||||
inline bool operator ==(const char *cstr) const {
|
||||
return Compare(cstr) == 0;
|
||||
}
|
||||
inline bool operator !=(const String &str) const {
|
||||
return Compare(str) != 0;
|
||||
}
|
||||
inline bool operator !=(const char *cstr) const {
|
||||
return Compare(cstr) != 0;
|
||||
}
|
||||
inline bool operator <(const String &str) const {
|
||||
return Compare(str) < 0;
|
||||
}
|
||||
inline bool operator <(const char *cstr) const {
|
||||
return Compare(cstr) < 0;
|
||||
}
|
||||
// Converts an AGS string to a ScummVM one
|
||||
operator Common::String() const {
|
||||
return Common::String(_cstr);
|
||||
}
|
||||
// Fixes compilation error in script_set
|
||||
operator bool() const {
|
||||
return !IsEmpty();
|
||||
}
|
||||
operator const char *() const {
|
||||
return GetCStr();
|
||||
}
|
||||
|
||||
private:
|
||||
// Creates new empty string with buffer enough to fit given length
|
||||
void Create(size_t buffer_length);
|
||||
// Release string and copy data to the new buffer
|
||||
void Copy(size_t buffer_length, size_t offset = 0);
|
||||
// Aligns data at given offset
|
||||
void Align(size_t offset);
|
||||
|
||||
// Tells if this object shares its string buffer with others
|
||||
bool IsShared() const;
|
||||
// Ensure this string is a writeable independent copy, with ref counter = 1
|
||||
void BecomeUnique();
|
||||
// Ensure this string is independent, and there's enough space before
|
||||
// or after the current string data
|
||||
void ReserveAndShift(bool left, size_t more_length);
|
||||
|
||||
// Internal String data
|
||||
char *_cstr; // pointer to actual string data; always valid, never null
|
||||
size_t _len; // valid string length, in characters, excluding null-term
|
||||
|
||||
// Header of a reference-counted buffer
|
||||
struct BufHeader {
|
||||
size_t RefCount = 0; // reference count
|
||||
size_t Capacity = 0; // available space, in characters
|
||||
};
|
||||
|
||||
// Union that groups mutually exclusive data (currently only ref counted buffer)
|
||||
union {
|
||||
char *_buf; // reference-counted data (raw ptr)
|
||||
BufHeader *_bufHead; // the header of a reference-counted data
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
82
engines/ags/shared/util/string_compat.cpp
Normal file
82
engines/ags/shared/util/string_compat.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/util/string_compat.h"
|
||||
#include "ags/shared/core/platform.h"
|
||||
#include "ags/lib/allegro/error.h"
|
||||
#include "common/str.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
char *ags_strlwr(char *s) {
|
||||
char *p = s;
|
||||
for (; *p; p++)
|
||||
*p = (char)tolower(*p);
|
||||
return s;
|
||||
}
|
||||
|
||||
char *ags_strupr(char *s) {
|
||||
char *p = s;
|
||||
for (; *p; p++)
|
||||
*p = (char)toupper(*p);
|
||||
return s;
|
||||
}
|
||||
|
||||
int ags_stricmp(const char *s1, const char *s2) {
|
||||
return scumm_stricmp(s1, s2);
|
||||
}
|
||||
|
||||
int ags_strnicmp(const char *s1, const char *s2, size_t n) {
|
||||
return scumm_strnicmp(s1, s2, n);
|
||||
}
|
||||
|
||||
char *ags_strdup(const char *s) {
|
||||
size_t len = strlen(s);
|
||||
char *result = (char *)malloc(len + 1);
|
||||
memcpy(result, s, len + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
int ags_strncpy_s(char *dest, size_t dest_sz, const char *src, size_t count) {
|
||||
// NOTE: implementation approximately mimics explanation for "strncpy_s":
|
||||
// https://en.cppreference.com/w/c/string/byte/strncpy
|
||||
assert(dest && dest_sz > 0 && ((dest + dest_sz - 1 < src) || (dest > src + count)));
|
||||
if (!dest || dest_sz == 0 || ((dest <= src) && (dest + dest_sz - 1 >= src)) || ((src <= dest) && (src + count - 1 >= dest)))
|
||||
return AL_EINVAL; // null buffer, or dest and src overlap
|
||||
if (!src) {
|
||||
dest[0] = 0; // ensure null terminator
|
||||
return AL_EINVAL;
|
||||
}
|
||||
|
||||
const size_t copy_len = (count < dest_sz - 1) ? count : dest_sz - 1; // reserve null-terminator
|
||||
const char *psrc = src;
|
||||
const char *src_end = src + copy_len;
|
||||
char *pdst = dest;
|
||||
for (; *psrc && (psrc != src_end); ++psrc, ++pdst)
|
||||
*pdst = *psrc;
|
||||
*pdst = 0; // ensure null terminator
|
||||
assert((*psrc == 0) || ((psrc - src) == (int)count)); // assert that no *unintended* truncation occurred
|
||||
if ((*psrc != 0) && ((psrc - src) < (int)count))
|
||||
return AL_ERANGE; // not enough dest buffer - error
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
} // namespace AGS3
|
||||
38
engines/ags/shared/util/string_compat.h
Normal file
38
engines/ags/shared/util/string_compat.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_STRING_COMPAT_H
|
||||
#define AGS_SHARED_UTIL_STRING_COMPAT_H
|
||||
|
||||
#include "ags/shared/core/types.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
char *ags_strlwr(char *s);
|
||||
char *ags_strupr(char *s);
|
||||
int ags_stricmp(const char *, const char *);
|
||||
int ags_strnicmp(const char *, const char *, size_t);
|
||||
char *ags_strdup(const char *s);
|
||||
int ags_strncpy_s(char *dest, size_t dest_sz, const char *src, size_t count);
|
||||
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
116
engines/ags/shared/util/string_types.h
Normal file
116
engines/ags/shared/util/string_types.h
Normal file
@@ -0,0 +1,116 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_STRING_TYPES_H
|
||||
#define AGS_SHARED_UTIL_STRING_TYPES_H
|
||||
|
||||
#include "common/std/map.h"
|
||||
#include "common/std/vector.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
#include "common/hash-str.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace FNV {
|
||||
|
||||
const uint32_t PRIME_NUMBER = 2166136261U;
|
||||
const uint32_t SECONDARY_NUMBER = 16777619U;
|
||||
|
||||
inline size_t Hash(const char *data, const size_t len) {
|
||||
uint32_t hash = PRIME_NUMBER;
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
hash = (SECONDARY_NUMBER * hash) ^ (uint8_t)(data[i]);
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline size_t Hash_LowerCase(const char *data, const size_t len) {
|
||||
uint32_t hash = PRIME_NUMBER;
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
hash = (SECONDARY_NUMBER * hash) ^ (uint8_t)(tolower(data[i]));
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace FNV
|
||||
} // namespace AGS3
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
struct CaseSensitiveString_EqualTo {
|
||||
bool operator()(const ::AGS3::AGS::Shared::String &x, const ::AGS3::AGS::Shared::String &y) const {
|
||||
return x.Compare(y) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct CaseSensitiveString_Hash {
|
||||
uint operator()(const ::AGS3::AGS::Shared::String &x) const {
|
||||
return Common::hashit(x.GetCStr());
|
||||
}
|
||||
};
|
||||
|
||||
struct IgnoreCase_EqualTo {
|
||||
bool operator()(const ::AGS3::AGS::Shared::String &x, const ::AGS3::AGS::Shared::String &y) const {
|
||||
return x.CompareNoCase(y) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct IgnoreCase_Hash {
|
||||
uint operator()(const ::AGS3::AGS::Shared::String &x) const {
|
||||
return Common::hashit_lower(x.GetCStr());
|
||||
}
|
||||
};
|
||||
|
||||
struct IgnoreCase_LessThan {
|
||||
bool operator()(const ::AGS3::AGS::Shared::String &x, const ::AGS3::AGS::Shared::String &y) const {
|
||||
return x.CompareNoCase(y) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace AGS3
|
||||
|
||||
namespace Common {
|
||||
|
||||
// Specalization of the Hash functor for String objects.
|
||||
// We do case sensitve hashing here, because that is what
|
||||
// the default EqualTo is compatible with. If one wants to use
|
||||
// case insensitve hashing, then only because one wants to use
|
||||
// IgnoreCase_EqualTo, and then one has to specify a custom
|
||||
// hash anyway.
|
||||
template<>
|
||||
struct Hash<AGS3::AGS::Shared::String> {
|
||||
uint operator()(const AGS3::AGS::Shared::String &s) const {
|
||||
return Common::hashit(s.GetCStr());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
typedef std::vector<String> StringV;
|
||||
typedef std::unordered_map<String, String> StringMap;
|
||||
typedef std::unordered_map<String, String, IgnoreCase_Hash, IgnoreCase_EqualTo> StringIMap;
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
312
engines/ags/shared/util/string_utils.cpp
Normal file
312
engines/ags/shared/util/string_utils.cpp
Normal file
@@ -0,0 +1,312 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/util/string_utils.h"
|
||||
#include "ags/shared/util/utf8.h"
|
||||
#include "ags/shared/core/platform.h"
|
||||
#include "common/std/regex.h"
|
||||
#include "ags/shared/util/math.h"
|
||||
#include "ags/shared/util/stream.h"
|
||||
#include "ags/shared/util/string_compat.h"
|
||||
#include "ags/globals.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
using namespace AGS::Shared;
|
||||
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
String StrUtil::IntToString(int d) {
|
||||
return String::FromFormat("%d", d);
|
||||
}
|
||||
|
||||
int StrUtil::StringToInt(const String &s, int def_val) {
|
||||
if (s.IsEmpty())
|
||||
return def_val;
|
||||
char *stop_ptr;
|
||||
int val = strtol(s.GetCStr(), &stop_ptr, 0);
|
||||
return (stop_ptr == s.GetCStr() + s.GetLength()) ? val : def_val;
|
||||
}
|
||||
|
||||
StrUtil::ConversionError StrUtil::StringToInt(const String &s, int &val, int def_val) {
|
||||
val = def_val;
|
||||
if (s.IsEmpty())
|
||||
return StrUtil::kFailed;
|
||||
char *stop_ptr;
|
||||
_G(errnum) = 0;
|
||||
long lval = strtol(s.GetCStr(), &stop_ptr, 0);
|
||||
if (stop_ptr != s.GetCStr() + s.GetLength())
|
||||
return StrUtil::kFailed;
|
||||
if (lval > INT_MAX || lval < INT_MIN || _G(errnum) == AL_ERANGE)
|
||||
return StrUtil::kOutOfRange;
|
||||
val = static_cast<int>(lval);
|
||||
return StrUtil::kNoError;
|
||||
}
|
||||
|
||||
float StrUtil::StringToFloat(const String &s, float def_val) {
|
||||
if (s.IsEmpty())
|
||||
return def_val;
|
||||
char *stop_ptr;
|
||||
float val = strtof(s.GetCStr(), &stop_ptr);
|
||||
return (stop_ptr == s.GetCStr() + s.GetLength()) ? val : def_val;
|
||||
}
|
||||
|
||||
String StrUtil::Unescape(const String &s) {
|
||||
size_t at = s.FindChar('\\');
|
||||
if (at == String::NoIndex)
|
||||
return s; // no unescaping necessary, return original string
|
||||
char *buf = new char[s.GetLength()];
|
||||
strncpy(buf, s.GetCStr(), at);
|
||||
char *pb = buf + at;
|
||||
for (const char *ptr = s.GetCStr() + at; *ptr; ++ptr) {
|
||||
if (*ptr != '\\') {
|
||||
*(pb++) = *ptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
char next = *(++ptr);
|
||||
switch (next) {
|
||||
case 'a': *(pb++) = '\a'; break;
|
||||
case 'b': *(pb++) = '\b'; break;
|
||||
case 'f': *(pb++) = '\f'; break;
|
||||
case 'n': *(pb++) = '\n'; break;
|
||||
case 'r': *(pb++) = '\r'; break;
|
||||
case 't': *(pb++) = '\t'; break;
|
||||
case 'v': *(pb++) = '\v'; break;
|
||||
case '\\': *(pb++) = '\\'; break;
|
||||
case '\'': *(pb++) = '\''; break;
|
||||
case '\"': *(pb++) = '\"'; break;
|
||||
case '\?': *(pb++) = '\?'; break;
|
||||
default: *(pb++) = next; break;
|
||||
}
|
||||
}
|
||||
*pb = 0;
|
||||
String dst(buf);
|
||||
delete[] buf;
|
||||
return dst;
|
||||
}
|
||||
|
||||
String StrUtil::WildcardToRegex(const String &wildcard) {
|
||||
// https://stackoverflow.com/questions/40195412/c11-regex-search-for-exact-string-escape
|
||||
// matches any characters that need to be escaped in RegEx
|
||||
std::regex esc{ R"([-[\]{}()*+?.,\^$|#\s])" };
|
||||
Common::String sanitized = std::regex_replace(wildcard.GetCStr(), esc, R"(\$&)");
|
||||
// convert (now escaped) wildcard "\\*" and "\\?" into ".*" and "." respectively
|
||||
String pattern(sanitized.c_str());
|
||||
pattern.Replace("\\*", ".*");
|
||||
pattern.Replace("\\?", ".");
|
||||
return pattern;
|
||||
}
|
||||
|
||||
String StrUtil::ReadString(Stream *in) {
|
||||
size_t len = in->ReadInt32();
|
||||
if (len > 0)
|
||||
return String::FromStreamCount(in, len);
|
||||
return String();
|
||||
}
|
||||
|
||||
void StrUtil::ReadString(char *cstr, Stream *in, size_t buf_limit) {
|
||||
size_t len = in->ReadInt32();
|
||||
if (buf_limit == 0) {
|
||||
in->Seek(len);
|
||||
return;
|
||||
}
|
||||
|
||||
len = MIN(len, buf_limit - 1);
|
||||
if (len > 0)
|
||||
in->Read(cstr, len);
|
||||
cstr[len] = 0;
|
||||
}
|
||||
|
||||
void StrUtil::ReadString(String &s, Stream *in) {
|
||||
size_t len = in->ReadInt32();
|
||||
s.ReadCount(in, len);
|
||||
}
|
||||
|
||||
void StrUtil::ReadString(char **cstr, Stream *in) {
|
||||
size_t len = in->ReadInt32();
|
||||
*cstr = new char[len + 1];
|
||||
if (len > 0)
|
||||
in->Read(*cstr, len);
|
||||
(*cstr)[len] = 0;
|
||||
}
|
||||
|
||||
String StrUtil::ReadStringAligned(Stream *in) {
|
||||
String s = ReadString(in);
|
||||
size_t rem = s.GetLength() % sizeof(int32_t);
|
||||
if (rem > 0) {
|
||||
size_t pad = sizeof(int32_t) - rem;
|
||||
for (size_t i = 0; i < pad; ++i)
|
||||
in->ReadByte();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void StrUtil::SkipString(Stream *in) {
|
||||
size_t len = in->ReadInt32();
|
||||
in->Seek(len);
|
||||
}
|
||||
|
||||
void StrUtil::WriteString(const String &s, Stream *out) {
|
||||
size_t len = s.GetLength();
|
||||
out->WriteInt32(len);
|
||||
if (len > 0)
|
||||
out->Write(s.GetCStr(), len);
|
||||
}
|
||||
|
||||
void StrUtil::WriteString(const char *cstr, Stream *out) {
|
||||
size_t len = strlen(cstr);
|
||||
out->WriteInt32(len);
|
||||
if (len > 0)
|
||||
out->Write(cstr, len);
|
||||
}
|
||||
|
||||
void StrUtil::WriteString(const char *cstr, size_t len, Stream *out) {
|
||||
out->WriteInt32(len);
|
||||
if (len > 0)
|
||||
out->Write(cstr, len);
|
||||
}
|
||||
|
||||
void StrUtil::ReadCStr(char *buf, Stream *in, size_t buf_limit) {
|
||||
if (buf_limit == 0) {
|
||||
while (in->ReadByte() > 0);
|
||||
return;
|
||||
}
|
||||
|
||||
auto ptr = buf;
|
||||
auto last = buf + buf_limit - 1;
|
||||
for (;;) {
|
||||
if (ptr >= last) {
|
||||
*ptr = 0;
|
||||
while (in->ReadByte() > 0); // must still read until 0
|
||||
break;
|
||||
}
|
||||
|
||||
auto ichar = in->ReadByte();
|
||||
if (ichar <= 0) {
|
||||
*ptr = 0;
|
||||
break;
|
||||
}
|
||||
*ptr = static_cast<char>(ichar);
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
void StrUtil::ReadCStrCount(char *buf, Stream *in, size_t count) {
|
||||
in->Read(buf, count);
|
||||
buf[count - 1] = 0; // for safety
|
||||
}
|
||||
|
||||
char *StrUtil::ReadMallocCStrOrNull(Stream *in) {
|
||||
char buf[1024];
|
||||
for (auto ptr = buf; (ptr < buf + sizeof(buf)); ++ptr) {
|
||||
auto ichar = in->ReadByte();
|
||||
if (ichar <= 0) {
|
||||
*ptr = 0;
|
||||
break;
|
||||
}
|
||||
*ptr = static_cast<char>(ichar);
|
||||
}
|
||||
return buf[0] != 0 ? ags_strdup(buf) : nullptr;
|
||||
}
|
||||
|
||||
void StrUtil::SkipCStr(Stream *in) {
|
||||
while (in->ReadByte() > 0);
|
||||
}
|
||||
|
||||
void StrUtil::WriteCStr(const char *cstr, Stream *out) {
|
||||
if (cstr)
|
||||
out->Write(cstr, strlen(cstr) + 1);
|
||||
else
|
||||
out->WriteByte(0);
|
||||
}
|
||||
|
||||
void StrUtil::WriteCStr(const String &s, Stream *out) {
|
||||
out->Write(s.GetCStr(), s.GetLength() + 1);
|
||||
}
|
||||
|
||||
void StrUtil::ReadStringMap(StringMap &map, Stream *in) {
|
||||
size_t count = in->ReadInt32();
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
String key = StrUtil::ReadString(in);
|
||||
String value = StrUtil::ReadString(in);
|
||||
map.insert(std::make_pair(key, value));
|
||||
}
|
||||
}
|
||||
|
||||
void StrUtil::WriteStringMap(const StringMap &map, Stream *out) {
|
||||
out->WriteInt32(map.size());
|
||||
for (const auto &kv : map) {
|
||||
StrUtil::WriteString(kv._key, out);
|
||||
StrUtil::WriteString(kv._value, out);
|
||||
}
|
||||
}
|
||||
|
||||
size_t StrUtil::ConvertUtf8ToAscii(const char *mbstr, const char *loc_name, char *out_cstr, size_t out_sz) {
|
||||
// TODO: later consider using alternative conversion methods
|
||||
// (e.g. see C++11 features), as setlocale is unreliable.
|
||||
char old_locale[64];
|
||||
snprintf(old_locale, sizeof(old_locale), "%s", setlocale(LC_CTYPE, nullptr));
|
||||
if (setlocale(LC_CTYPE, loc_name) == nullptr) { // If failed setlocale, then resort to plain copy the mb string
|
||||
return static_cast<size_t>(snprintf(out_cstr, out_sz, "%s", mbstr));
|
||||
}
|
||||
// First convert utf-8 string into widestring;
|
||||
std::vector<wchar_t> wcsbuf; // widechar buffer
|
||||
wcsbuf.resize(Utf8::GetLength(mbstr) + 1);
|
||||
// NOTE: we don't use mbstowcs, because unfortunately ".utf-8" locale
|
||||
// is not normally supported on all systems (e.g. Windows 7 and earlier)
|
||||
for (size_t at = 0, chr_sz = 0; *mbstr; mbstr += chr_sz, ++at) {
|
||||
Utf8::Rune r;
|
||||
chr_sz = Utf8::GetChar(mbstr, Utf8::UtfSz, &r);
|
||||
wcsbuf[at] = static_cast<wchar_t>(r);
|
||||
}
|
||||
// Then convert widestring to single-byte string using specified locale
|
||||
size_t res_sz = wcstombs(out_cstr, &wcsbuf[0], out_sz);
|
||||
setlocale(LC_CTYPE, old_locale);
|
||||
return res_sz;
|
||||
}
|
||||
|
||||
size_t StrUtil::ConvertUtf8ToWstr(const char *mbstr, wchar_t *out_wcstr, size_t out_sz) {
|
||||
size_t len = 0;
|
||||
for (size_t mb_sz = 1; *mbstr && (mb_sz > 0) && (len < out_sz);
|
||||
mbstr += mb_sz, ++out_wcstr, ++len) {
|
||||
Utf8::Rune r;
|
||||
mb_sz = Utf8::GetChar(mbstr, Utf8::UtfSz, &r);
|
||||
*out_wcstr = static_cast<wchar_t>(r);
|
||||
}
|
||||
*out_wcstr = 0;
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t StrUtil::ConvertWstrToUtf8(const wchar_t *wcstr, char *out_mbstr, size_t out_sz) {
|
||||
size_t len = 0;
|
||||
for (size_t mb_sz = 1; *wcstr && (mb_sz > 0) && (len + mb_sz < out_sz);
|
||||
++wcstr, out_mbstr += mb_sz, len += mb_sz) {
|
||||
mb_sz = Utf8::SetChar(*wcstr, out_mbstr, out_sz - len);
|
||||
}
|
||||
*out_mbstr = 0;
|
||||
return len;
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
121
engines/ags/shared/util/string_utils.h
Normal file
121
engines/ags/shared/util/string_utils.h
Normal file
@@ -0,0 +1,121 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_STRING_UTILS_H
|
||||
#define AGS_SHARED_UTIL_STRING_UTILS_H
|
||||
|
||||
#include "ags/shared/util/string_types.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
class Stream;
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
|
||||
using namespace AGS; // FIXME later
|
||||
|
||||
//=============================================================================
|
||||
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
namespace StrUtil {
|
||||
|
||||
enum ConversionError {
|
||||
kNoError, // conversion successful
|
||||
kFailed, // conversion failed (e.g. wrong format)
|
||||
kOutOfRange // the resulting value is out of range
|
||||
};
|
||||
|
||||
// Convert integer to string, by printing its value
|
||||
String IntToString(int val);
|
||||
// Tries to convert whole string into integer value;
|
||||
// returns def_val on failure
|
||||
int StringToInt(const String &s, int def_val = 0);
|
||||
// Tries to convert whole string into integer value;
|
||||
// Returns error code if any non-digit character was met or if value is out
|
||||
// of range; the 'val' variable will be set with resulting integer, or
|
||||
// def_val on failure
|
||||
ConversionError StringToInt(const String &s, int &val, int def_val);
|
||||
// Tries to convert whole string into float value;
|
||||
// returns def_val on failure
|
||||
float StringToFloat(const String &s, float def_val = 0.f);
|
||||
|
||||
// A simple unescape string implementation, unescapes '\\x' into '\x'.
|
||||
String Unescape(const String &s);
|
||||
// Converts a classic wildcard search pattern into C++11 compatible regex pattern
|
||||
String WildcardToRegex(const String &wildcard);
|
||||
|
||||
// Serialize and unserialize unterminated string prefixed with 32-bit length;
|
||||
// length is presented as 32-bit integer integer
|
||||
String ReadString(Stream *in);
|
||||
void ReadString(char *cstr, Stream *in, size_t buf_limit);
|
||||
void ReadString(char **cstr, Stream *in);
|
||||
void ReadString(String &s, Stream *in);
|
||||
// Read a string and trailing padding, aligning total read data to int32
|
||||
// this is a special case used strictly for legacy save format
|
||||
String ReadStringAligned(Stream *in);
|
||||
void SkipString(Stream *in);
|
||||
void WriteString(const String &s, Stream *out);
|
||||
void WriteString(const char *cstr, Stream *out);
|
||||
void WriteString(const char *cstr, size_t len, Stream *out);
|
||||
|
||||
// Serialize and unserialize string as c-string (null-terminated sequence)
|
||||
//
|
||||
// Reads a null-terminated string until getting a null-terminator.
|
||||
// writes into the buffer up to the buf_limit.
|
||||
// Note that this will keep reading the stream out until 0 is read,
|
||||
// even if buffer is already full.
|
||||
// Guarantees that output buffer will contain a null-terminator.
|
||||
void ReadCStr(char *buf, Stream *in, size_t buf_limit);
|
||||
// Reads N characters into the provided buffer.
|
||||
// Guarantees that output buffer will contain a null-terminator.
|
||||
void ReadCStrCount(char *buf, Stream *in, size_t count);
|
||||
// Reads a null-terminated string and !! mallocs !! a char buffer for it;
|
||||
// returns nullptr if the read string is empty.
|
||||
// Buffer is hard-limited to 1024 bytes, including null-terminator.
|
||||
// Strictly for compatibility with the C lib code!
|
||||
char * ReadMallocCStrOrNull(Stream *in);
|
||||
void SkipCStr(Stream *in);
|
||||
void WriteCStr(const char *cstr, Stream *out);
|
||||
void WriteCStr(const String &s, Stream *out);
|
||||
|
||||
// Serialize and unserialize a string map, both keys and values are read using ReadString
|
||||
void ReadStringMap(StringMap &map, Stream *in);
|
||||
void WriteStringMap(const StringMap &map, Stream *out);
|
||||
|
||||
// Convert utf-8 string to ascii/ansi representation;
|
||||
// writes into out_cstr buffer limited by out_sz bytes; returns bytes written.
|
||||
size_t ConvertUtf8ToAscii(const char *mbstr, const char *loc_name, char *out_cstr, size_t out_sz);
|
||||
// Convert utf-8 string to wide-string (16-bit char);
|
||||
// writes into out_wcstr buffer limited by out_sz *wchars*; returns *wchars* written.
|
||||
size_t ConvertUtf8ToWstr(const char *mbstr, wchar_t *out_wcstr, size_t out_sz);
|
||||
// Convert wide-string to utf-8 string;
|
||||
// writes into out_mbstr buffer limited by out_sz *bytes*; returns *bytes* written.
|
||||
size_t ConvertWstrToUtf8(const wchar_t *wcstr, char *out_mbstr, size_t out_sz);
|
||||
|
||||
} // namespace StrUtil
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
57
engines/ags/shared/util/text_reader.h
Normal file
57
engines/ags/shared/util/text_reader.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Specialized interface for reading plain text from the underlying source
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_TEXT_READER_H
|
||||
#define AGS_SHARED_UTIL_TEXT_READER_H
|
||||
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class TextReader {
|
||||
public:
|
||||
virtual ~TextReader() {}
|
||||
|
||||
virtual bool IsValid() const = 0;
|
||||
|
||||
// Read single character
|
||||
virtual char ReadChar() = 0;
|
||||
// Read defined number of characters
|
||||
virtual String ReadString(size_t length) = 0;
|
||||
// Read till line break
|
||||
virtual String ReadLine() = 0;
|
||||
// Read till end of available data
|
||||
virtual String ReadAll() = 0;
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
119
engines/ags/shared/util/text_stream_reader.cpp
Normal file
119
engines/ags/shared/util/text_stream_reader.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/util/math.h"
|
||||
#include "ags/shared/util/stream.h"
|
||||
#include "ags/shared/util/text_stream_reader.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
TextStreamReader::TextStreamReader(Stream *stream)
|
||||
: _stream(stream) {
|
||||
}
|
||||
|
||||
TextStreamReader::~TextStreamReader() {
|
||||
// TODO: use shared ptr
|
||||
delete _stream;
|
||||
}
|
||||
|
||||
bool TextStreamReader::IsValid() const {
|
||||
return _stream && _stream->CanRead();
|
||||
}
|
||||
|
||||
const Stream *TextStreamReader::GetStream() const {
|
||||
return _stream;
|
||||
}
|
||||
|
||||
void TextStreamReader::ReleaseStream() {
|
||||
_stream = nullptr;
|
||||
}
|
||||
|
||||
bool TextStreamReader::EOS() const {
|
||||
return _stream ? _stream->EOS() : true;
|
||||
}
|
||||
|
||||
char TextStreamReader::ReadChar() {
|
||||
return _stream->ReadInt8();
|
||||
}
|
||||
|
||||
String TextStreamReader::ReadString(size_t length) {
|
||||
// TODO: remove carriage-return characters
|
||||
return String::FromStreamCount(_stream, length);
|
||||
}
|
||||
|
||||
String TextStreamReader::ReadLine() {
|
||||
// TODO
|
||||
// Probably it is possible to group Stream::ReadString with this,
|
||||
// both use similar algorythm, difference is only in terminator chars
|
||||
|
||||
String str;
|
||||
int chars_read_last = 0;
|
||||
int line_break_position = -1;
|
||||
// Read a chunk of memory to buffer and seek for null-terminator,
|
||||
// if not found, repeat until EOS
|
||||
const int single_chunk_length = 3000;
|
||||
const int max_chars = 5000000;
|
||||
char char_buffer[single_chunk_length + 1];
|
||||
do {
|
||||
chars_read_last = _stream->Read(char_buffer, single_chunk_length);
|
||||
char *seek_ptr = char_buffer;
|
||||
int c;
|
||||
for (c = 0; c < chars_read_last && *seek_ptr != '\n'; ++c, ++seek_ptr);
|
||||
|
||||
int append_length = 0;
|
||||
int str_len = str.GetLength();
|
||||
if (c < chars_read_last && *seek_ptr == '\n') {
|
||||
line_break_position = seek_ptr - char_buffer;
|
||||
if (str_len < max_chars) {
|
||||
append_length = MIN(line_break_position, max_chars - str_len);
|
||||
}
|
||||
} else {
|
||||
append_length = MIN(chars_read_last, max_chars - str_len);
|
||||
}
|
||||
|
||||
if (append_length > 0) {
|
||||
char_buffer[append_length] = '\0';
|
||||
str.Append(char_buffer);
|
||||
}
|
||||
} while (!EOS() && line_break_position < 0);
|
||||
|
||||
// If null-terminator was found make sure stream is positioned at the next
|
||||
// byte after line end
|
||||
if (line_break_position >= 0) {
|
||||
// CHECKME: what if stream does not support seek? need an algorythm fork for that
|
||||
// the seek offset should be negative
|
||||
_stream->Seek(line_break_position - chars_read_last + 1 /* beyond line feed */);
|
||||
}
|
||||
|
||||
str.TrimRight('\r'); // skip carriage-return, if any
|
||||
return str;
|
||||
}
|
||||
|
||||
String TextStreamReader::ReadAll() {
|
||||
size_t len = Math::InRangeOrDef<size_t>((size_t)(_stream->GetLength() - _stream->GetPosition()), SIZE_MAX);
|
||||
return ReadString(len);
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
69
engines/ags/shared/util/text_stream_reader.h
Normal file
69
engines/ags/shared/util/text_stream_reader.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Class for reading plain text from the stream
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_TEXT_STREAM_READER_H
|
||||
#define AGS_SHARED_UTIL_TEXT_STREAM_READER_H
|
||||
|
||||
#include "ags/shared/util/text_reader.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class Stream;
|
||||
|
||||
class TextStreamReader : public TextReader {
|
||||
public:
|
||||
// TODO: use shared ptr
|
||||
TextStreamReader(Stream *stream);
|
||||
~TextStreamReader() override;
|
||||
|
||||
bool IsValid() const override;
|
||||
const Stream *GetStream() const;
|
||||
// TODO: use shared ptr instead
|
||||
void ReleaseStream();
|
||||
|
||||
bool EOS() const;
|
||||
|
||||
// Read single character
|
||||
char ReadChar() override;
|
||||
// Read defined number of characters
|
||||
String ReadString(size_t length) override;
|
||||
// Read till line break
|
||||
String ReadLine() override;
|
||||
// Read till end of available data
|
||||
String ReadAll() override;
|
||||
|
||||
private:
|
||||
Stream *_stream;
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
91
engines/ags/shared/util/text_stream_writer.cpp
Normal file
91
engines/ags/shared/util/text_stream_writer.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/core/platform.h"
|
||||
#include "ags/shared/util/text_stream_writer.h"
|
||||
#include "ags/shared/util/stream.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
// TODO: perhaps let configure line break character per TextWriter object?
|
||||
#if AGS_PLATFORM_OS_WINDOWS
|
||||
static const char Endl[2] = { '\r', '\n' };
|
||||
#else
|
||||
static const char Endl[1] = { '\n' };
|
||||
#endif
|
||||
|
||||
|
||||
TextStreamWriter::TextStreamWriter(Stream *stream)
|
||||
: _stream(stream) {
|
||||
}
|
||||
|
||||
TextStreamWriter::~TextStreamWriter() {
|
||||
// TODO use shared ptr
|
||||
delete _stream;
|
||||
}
|
||||
|
||||
bool TextStreamWriter::IsValid() const {
|
||||
return _stream && _stream->CanWrite();
|
||||
}
|
||||
|
||||
const Stream *TextStreamWriter::GetStream() const {
|
||||
return _stream;
|
||||
}
|
||||
|
||||
void TextStreamWriter::ReleaseStream() {
|
||||
_stream = nullptr;
|
||||
}
|
||||
|
||||
bool TextStreamWriter::EOS() const {
|
||||
return _stream->EOS();
|
||||
}
|
||||
|
||||
void TextStreamWriter::WriteChar(char c) {
|
||||
_stream->WriteByte(c);
|
||||
}
|
||||
|
||||
void TextStreamWriter::WriteString(const String &str) {
|
||||
_stream->Write(str.GetCStr(), str.GetLength());
|
||||
}
|
||||
|
||||
void TextStreamWriter::WriteLine(const String &str) {
|
||||
// TODO: perhaps let configure line break character?
|
||||
_stream->Write(str.GetCStr(), str.GetLength());
|
||||
_stream->Write(Endl, sizeof(Endl));
|
||||
}
|
||||
|
||||
void TextStreamWriter::WriteFormat(const char *fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, fmt);
|
||||
_buf.FormatV(fmt, argptr);
|
||||
va_end(argptr);
|
||||
_stream->Write(_buf.GetCStr(), _buf.GetLength());
|
||||
}
|
||||
|
||||
void TextStreamWriter::WriteLineBreak() {
|
||||
_stream->Write(Endl, sizeof(Endl));
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
71
engines/ags/shared/util/text_stream_writer.h
Normal file
71
engines/ags/shared/util/text_stream_writer.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Class for writing plain text to the stream
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_TEXT_STREAM_WRITER_H
|
||||
#define AGS_SHARED_UTIL_TEXT_STREAM_WRITER_H
|
||||
|
||||
#include "ags/shared/util/text_writer.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class Stream;
|
||||
|
||||
class TextStreamWriter : public TextWriter {
|
||||
public:
|
||||
// TODO: use shared ptr
|
||||
TextStreamWriter(Stream *stream);
|
||||
~TextStreamWriter() override;
|
||||
|
||||
bool IsValid() const override;
|
||||
const Stream *GetStream() const;
|
||||
// TODO: use shared ptr instead
|
||||
void ReleaseStream();
|
||||
|
||||
bool EOS() const;
|
||||
|
||||
// Write single character
|
||||
void WriteChar(char c) override;
|
||||
// Write string as a plain text (without null-terminator)
|
||||
void WriteString(const String &str) override;
|
||||
// Write string and add line break at the end
|
||||
void WriteLine(const String &str) override;
|
||||
// Write formatted string (see *printf)
|
||||
void WriteFormat(const char *fmt, ...) override;
|
||||
void WriteLineBreak() override;
|
||||
|
||||
private:
|
||||
Stream *_stream;
|
||||
String _buf; // formatting string buffer
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
58
engines/ags/shared/util/text_writer.h
Normal file
58
engines/ags/shared/util/text_writer.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Specialized interface for writing plain text to the underlying source
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_TEXT_WRITER_H
|
||||
#define AGS_SHARED_UTIL_TEXT_WRITER_H
|
||||
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
class TextWriter {
|
||||
public:
|
||||
virtual ~TextWriter() {}
|
||||
|
||||
virtual bool IsValid() const = 0;
|
||||
|
||||
// Write single character
|
||||
virtual void WriteChar(char c) = 0;
|
||||
// Write string as a plain text (without null-terminator)
|
||||
virtual void WriteString(const String &str) = 0;
|
||||
// Write string and add line break at the end
|
||||
virtual void WriteLine(const String &str) = 0;
|
||||
// Write formatted string (see *printf)
|
||||
virtual void WriteFormat(const char *fmt, ...) = 0;
|
||||
virtual void WriteLineBreak() = 0;
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
115
engines/ags/shared/util/utf8.h
Normal file
115
engines/ags/shared/util/utf8.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// UTF-8 utilities.
|
||||
// Based on utf8 code from https://c9x.me/irc/ (public domain)
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_UTF8_H
|
||||
#define AGS_SHARED_UTIL_UTF8_H
|
||||
|
||||
#include "common/std/algorithm.h"
|
||||
#include "ags/shared/core/types.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace Utf8 {
|
||||
|
||||
typedef int32_t Rune;
|
||||
const size_t UtfSz = 4;
|
||||
const Rune RuneInvalid = 0xFFFD;
|
||||
|
||||
const unsigned char utfbyte[UtfSz + 1] = { 0x80, 0, 0xC0, 0xE0, 0xF0 };
|
||||
const unsigned char utfmask[UtfSz + 1] = { 0xC0, 0x80, 0xE0, 0xF0, 0xF8 };
|
||||
const Rune utfmin[UtfSz + 1] = { 0, 0, 0x80, 0x800, 0x10000 };
|
||||
const Rune utfmax[UtfSz + 1] = { 0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF };
|
||||
|
||||
|
||||
inline size_t Validate(Rune *u, size_t i) {
|
||||
if (*u < utfmin[i] || *u > utfmax[i] || (0xD800 <= *u && *u <= 0xDFFF))
|
||||
*u = RuneInvalid;
|
||||
for (i = 1; *u > utfmax[i]; ++i)
|
||||
;
|
||||
return i;
|
||||
}
|
||||
|
||||
inline Rune DecodeByte(unsigned char c, size_t *i) {
|
||||
for (*i = 0; *i < UtfSz + 1; ++(*i))
|
||||
if ((c & utfmask[*i]) == utfbyte[*i])
|
||||
return c & ~utfmask[*i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline char EncodeByte(Rune u, size_t i) {
|
||||
return utfbyte[i] | (u & ~utfmask[i]);
|
||||
}
|
||||
|
||||
// Read a single utf8 codepoint from the c-string;
|
||||
// returns codepoint's size in bytes (may be used to advance string pos)
|
||||
inline size_t GetChar(const char *c, size_t clen, Rune *u) {
|
||||
size_t i, j, len, type;
|
||||
Rune udecoded;
|
||||
*u = RuneInvalid;
|
||||
if (!clen || !*c)
|
||||
return 0;
|
||||
udecoded = DecodeByte(c[0], &len);
|
||||
if (len < 1 || len > UtfSz)
|
||||
return 1;
|
||||
for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
|
||||
udecoded = (udecoded << 6) | DecodeByte(c[i], &type);
|
||||
if (type != 0)
|
||||
return j;
|
||||
}
|
||||
if (j < len)
|
||||
return 0;
|
||||
*u = udecoded;
|
||||
Validate(u, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Convert utf8 codepoint to the string representation and write to the buffer
|
||||
inline size_t SetChar(Rune u, char *c, size_t clen) {
|
||||
size_t len, i;
|
||||
len = Validate(&u, 0);
|
||||
if (len > UtfSz || len > clen)
|
||||
return 0;
|
||||
for (i = len - 1; i != 0; --i) {
|
||||
c[i] = EncodeByte(u, 0);
|
||||
u >>= 6;
|
||||
}
|
||||
c[0] = EncodeByte(u, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Calculates utf8 string length in characters
|
||||
inline size_t GetLength(const char *c) {
|
||||
size_t len = 0;
|
||||
Rune r;
|
||||
for (size_t chr_sz = 0; (chr_sz = GetChar(c, UtfSz, &r)) > 0; c += chr_sz, ++len);
|
||||
return len;
|
||||
}
|
||||
|
||||
} // namespace Utf8
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
135
engines/ags/shared/util/version.cpp
Normal file
135
engines/ags/shared/util/version.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/util.h"
|
||||
#include "ags/shared/util/version.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
Version::Version()
|
||||
: Major(0)
|
||||
, Minor(0)
|
||||
, Release(0)
|
||||
, Revision(0) {
|
||||
MakeString();
|
||||
}
|
||||
|
||||
Version::Version(int32_t major, int32_t minor, int32_t release)
|
||||
: Major(major)
|
||||
, Minor(minor)
|
||||
, Release(release)
|
||||
, Revision(0) {
|
||||
MakeString();
|
||||
}
|
||||
|
||||
Version::Version(int32_t major, int32_t minor, int32_t release, int32_t revision)
|
||||
: Major(major)
|
||||
, Minor(minor)
|
||||
, Release(release)
|
||||
, Revision(revision) {
|
||||
MakeString();
|
||||
}
|
||||
|
||||
Version::Version(int32_t major, int32_t minor, int32_t release, int32_t revision, const String &special)
|
||||
: Major(major)
|
||||
, Minor(minor)
|
||||
, Release(release)
|
||||
, Revision(revision)
|
||||
, Special(special) {
|
||||
MakeString();
|
||||
}
|
||||
|
||||
Version::Version(int32_t major, int32_t minor, int32_t release, int32_t revision, const String &special, const String &build_info)
|
||||
: Major(major)
|
||||
, Minor(minor)
|
||||
, Release(release)
|
||||
, Revision(revision)
|
||||
, Special(special)
|
||||
, BuildInfo(build_info) {
|
||||
MakeString();
|
||||
}
|
||||
|
||||
Version::Version(const String &version_string)
|
||||
: Major(0)
|
||||
, Minor(0)
|
||||
, Release(0)
|
||||
, Revision(0) {
|
||||
SetFromString(version_string);
|
||||
}
|
||||
|
||||
void Version::SetFromString(const String &version_string) {
|
||||
Major = version_string.LeftSection('.').ToInt();
|
||||
String second_section = version_string.Section('.', 1, 1);
|
||||
Minor = second_section.ToInt();
|
||||
String third_section = version_string.Section('.', 2, 2);
|
||||
String fourth_section = version_string.Section('.', 3, 3);
|
||||
String revision_section;
|
||||
|
||||
bool old_version_format = Major < 3 || fourth_section.IsEmpty();
|
||||
if (old_version_format) {
|
||||
if (second_section.GetLength() > 1) {
|
||||
Release = Minor % 10;
|
||||
Minor /= 10;
|
||||
} else {
|
||||
Release = 0;
|
||||
}
|
||||
revision_section = third_section;
|
||||
} else {
|
||||
Release = third_section.ToInt();
|
||||
revision_section = fourth_section;
|
||||
}
|
||||
|
||||
int revision_length = 0;
|
||||
if (!revision_section.IsEmpty()) {
|
||||
const char *seek_ptr = revision_section.GetCStr();
|
||||
const char *end_ptr = revision_section.GetCStr() + revision_section.GetLength();
|
||||
while (seek_ptr != end_ptr) {
|
||||
if (!Common::isDigit(*seek_ptr)) {
|
||||
break;
|
||||
}
|
||||
revision_length++;
|
||||
seek_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
Revision = revision_section.Left(revision_length).ToInt();
|
||||
// In old version format a special tag was added right after revision digits.
|
||||
// In new version format a special tag is separated from revision digits with single space char.
|
||||
Special = revision_section.Mid(revision_length + (old_version_format ? 0 : 1));
|
||||
|
||||
MakeString();
|
||||
}
|
||||
|
||||
void Version::MakeString() {
|
||||
if (Special.IsEmpty()) {
|
||||
LongString.Format("%d.%d.%d.%d", Major, Minor, Release, Revision);
|
||||
} else {
|
||||
LongString.Format("%d.%d.%d.%d %s", Major, Minor, Release, Revision, Special.GetCStr());
|
||||
}
|
||||
BackwardCompatibleString.Format("%d.%02d.%d%s", Major, Minor * 10 + Release, Revision, Special.GetCStr());
|
||||
ShortString.Format("%d.%d", Major, Minor);
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
104
engines/ags/shared/util/version.h
Normal file
104
engines/ags/shared/util/version.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Class, depicting version of the AGS engine
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_VERSION_H
|
||||
#define AGS_SHARED_UTIL_VERSION_H
|
||||
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
using Shared::String;
|
||||
|
||||
struct Version {
|
||||
int32_t Major;
|
||||
int32_t Minor;
|
||||
int32_t Release;
|
||||
int32_t Revision;
|
||||
String Special;
|
||||
String BuildInfo;
|
||||
|
||||
String LongString;
|
||||
String ShortString;
|
||||
String BackwardCompatibleString;
|
||||
|
||||
Version();
|
||||
Version(int32_t major, int32_t minor, int32_t release);
|
||||
Version(int32_t major, int32_t minor, int32_t release, int32_t revision);
|
||||
Version(int32_t major, int32_t minor, int32_t release, int32_t revision, const String &special);
|
||||
Version(int32_t major, int32_t minor, int32_t release, int32_t revision, const String &special, const String &build_info);
|
||||
Version(const String &version_string);
|
||||
|
||||
inline int32_t AsNumber() const {
|
||||
return Major * 10000 + Minor * 100 + Release;
|
||||
}
|
||||
|
||||
inline int64_t AsLongNumber() const {
|
||||
return (int64_t)Major * 100000000L + (int64_t)Minor * 1000000L + (int64_t)Release * 10000L + Revision;
|
||||
}
|
||||
|
||||
inline int32_t AsSmallNumber() const {
|
||||
return Major * 100 + Minor;
|
||||
}
|
||||
|
||||
void SetFromString(const String &version_string);
|
||||
|
||||
inline bool operator < (const Version &other) const {
|
||||
return AsLongNumber() < other.AsLongNumber();
|
||||
}
|
||||
|
||||
inline bool operator <= (const Version &other) const {
|
||||
return AsLongNumber() <= other.AsLongNumber();
|
||||
}
|
||||
|
||||
inline bool operator > (const Version &other) const {
|
||||
return AsLongNumber() > other.AsLongNumber();
|
||||
}
|
||||
|
||||
inline bool operator >= (const Version &other) const {
|
||||
return AsLongNumber() >= other.AsLongNumber();
|
||||
}
|
||||
|
||||
inline bool operator == (const Version &other) const {
|
||||
return AsLongNumber() == other.AsLongNumber();
|
||||
}
|
||||
|
||||
inline bool operator != (const Version &other) const {
|
||||
return AsLongNumber() != other.AsLongNumber();
|
||||
}
|
||||
|
||||
private:
|
||||
void MakeString();
|
||||
};
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
137
engines/ags/shared/util/wgt2_allg.cpp
Normal file
137
engines/ags/shared/util/wgt2_allg.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/util/wgt2_allg.h"
|
||||
#include "ags/shared/gfx/bitmap.h"
|
||||
#include "ags/globals.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
using namespace AGS::Shared;
|
||||
|
||||
void wsetrgb(int coll, int r, int g, int b, RGB *pall) {
|
||||
pall[coll].r = r;
|
||||
pall[coll].g = g;
|
||||
pall[coll].b = b;
|
||||
}
|
||||
|
||||
void wcolrotate(unsigned char start, unsigned char finish, int dir, RGB *pall) {
|
||||
int jj;
|
||||
if (dir == 0) {
|
||||
// rotate left
|
||||
RGB tempp = pall[start];
|
||||
|
||||
for (jj = start; jj < finish; jj++)
|
||||
pall[jj] = pall[jj + 1];
|
||||
|
||||
pall[finish] = tempp;
|
||||
} else {
|
||||
// rotate right
|
||||
RGB tempp = pall[finish];
|
||||
|
||||
for (jj = finish - 1; jj >= start; jj--)
|
||||
pall[jj + 1] = pall[jj];
|
||||
|
||||
pall[start] = tempp;
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap *wnewblock(Bitmap *src, int x1, int y1, int x2, int y2) {
|
||||
Bitmap *tempbitm;
|
||||
int twid = (x2 - x1) + 1, thit = (y2 - y1) + 1;
|
||||
|
||||
if (twid < 1)
|
||||
twid = 1;
|
||||
|
||||
if (thit < 1)
|
||||
thit = 1;
|
||||
|
||||
tempbitm = BitmapHelper::CreateBitmap(twid, thit);
|
||||
|
||||
if (tempbitm == nullptr)
|
||||
return nullptr;
|
||||
|
||||
tempbitm->Blit(src, x1, y1, 0, 0, tempbitm->GetWidth(), tempbitm->GetHeight());
|
||||
return tempbitm;
|
||||
}
|
||||
|
||||
void wputblock(Bitmap *ds, int xx, int yy, Bitmap *bll, int xray) {
|
||||
if (xray)
|
||||
ds->Blit(bll, xx, yy, kBitmap_Transparency);
|
||||
else
|
||||
ds->Blit(bll, 0, 0, xx, yy, bll->GetWidth(), bll->GetHeight());
|
||||
}
|
||||
|
||||
void wputblock_raw(Bitmap *ds, int xx, int yy, BITMAP *bll, int xray) {
|
||||
_G(wputblock_wrapper).WrapAllegroBitmap(bll, true);
|
||||
if (xray)
|
||||
ds->Blit(&_G(wputblock_wrapper), xx, yy, kBitmap_Transparency);
|
||||
else
|
||||
ds->Blit(&_G(wputblock_wrapper), 0, 0, xx, yy, _G(wputblock_wrapper).GetWidth(), _G(wputblock_wrapper).GetHeight());
|
||||
}
|
||||
|
||||
const int col_lookups[32] = {
|
||||
0x000000, 0x0000A0, 0x00A000, 0x00A0A0, 0xA00000, // 4
|
||||
0xA000A0, 0xA05000, 0xA0A0A0, 0x505050, 0x5050FF, 0x50FF50, 0x50FFFF, // 11
|
||||
0xFF5050, 0xFF50FF, 0xFFFF50, 0xFFFFFF, 0x000000, 0x101010, 0x202020, // 18
|
||||
0x303030, 0x404040, 0x505050, 0x606060, 0x707070, 0x808080, 0x909090, // 25
|
||||
0xA0A0A0, 0xB0B0B0, 0xC0C0C0, 0xD0D0D0, 0xE0E0E0, 0xF0F0F0
|
||||
};
|
||||
|
||||
int __wremap_keep_transparent = 1;
|
||||
|
||||
void wremap(RGB *pal1, Bitmap *picc, RGB *pal2) {
|
||||
int jj;
|
||||
unsigned char color_mapped_table[256];
|
||||
|
||||
for (jj = 0; jj < 256; jj++) {
|
||||
if ((pal1[jj].r == 0) && (pal1[jj].g == 0) && (pal1[jj].b == 0)) {
|
||||
color_mapped_table[jj] = 0;
|
||||
} else {
|
||||
color_mapped_table[jj] = bestfit_color(pal2, pal1[jj].r, pal1[jj].g, pal1[jj].b);
|
||||
}
|
||||
}
|
||||
|
||||
if (__wremap_keep_transparent > 0) {
|
||||
// keep transparency
|
||||
color_mapped_table[0] = 0;
|
||||
// any other pixels which are being mapped to 0, map to 16 instead
|
||||
for (jj = 1; jj < 256; jj++) {
|
||||
if (color_mapped_table[jj] == 0)
|
||||
color_mapped_table[jj] = 16;
|
||||
}
|
||||
}
|
||||
|
||||
int pic_size = picc->GetWidth() * picc->GetHeight();
|
||||
for (jj = 0; jj < pic_size; jj++) {
|
||||
int xxl = jj % (picc->GetWidth()), yyl = jj / (picc->GetWidth());
|
||||
int rr = picc->GetPixel(xxl, yyl);
|
||||
picc->PutPixel(xxl, yyl, color_mapped_table[rr]);
|
||||
}
|
||||
}
|
||||
|
||||
void wremapall(RGB *pal1, Bitmap *picc, RGB *pal2) {
|
||||
__wremap_keep_transparent--;
|
||||
wremap(pal1, picc, pal2);
|
||||
__wremap_keep_transparent++;
|
||||
}
|
||||
|
||||
} // namespace AGS3
|
||||
64
engines/ags/shared/util/wgt2_allg.h
Normal file
64
engines/ags/shared/util/wgt2_allg.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Few graphic utility functions, remains of a bigger deprecated api.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_SHARED_UTIL_WGT2_ALIG_H
|
||||
#define AGS_SHARED_UTIL_WGT2_ALIG_H
|
||||
|
||||
#include "ags/lib/allegro.h" // RGB
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
class Bitmap;
|
||||
}
|
||||
}
|
||||
using namespace AGS; // FIXME later
|
||||
|
||||
//=============================================================================
|
||||
|
||||
// [IKM] 2012-09-13: this function is now defined in engine and editor separately
|
||||
extern void __my_setcolor(int *ctset, int newcol, int wantColDep);
|
||||
|
||||
extern void wsetrgb(int coll, int r, int g, int b, RGB *pall);
|
||||
extern void wcolrotate(unsigned char start, unsigned char finish, int dir, RGB *pall);
|
||||
|
||||
extern Shared::Bitmap *wnewblock(Shared::Bitmap *src, int x1, int y1, int x2, int y2);
|
||||
|
||||
extern void wputblock(Shared::Bitmap *ds, int xx, int yy, Shared::Bitmap *bll, int xray);
|
||||
// CHECKME: temporary solution for plugin system
|
||||
extern void wputblock_raw(Shared::Bitmap *ds, int xx, int yy, BITMAP *bll, int xray);
|
||||
extern const int col_lookups[32];
|
||||
|
||||
// TODO: these are used only in the Editor's agsnative.cpp
|
||||
extern int __wremap_keep_transparent;
|
||||
extern void wremap(RGB *pal1, Shared::Bitmap *picc, RGB *pal2);
|
||||
extern void wremapall(RGB *pal1, Shared::Bitmap *picc, RGB *pal2);
|
||||
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user