WingHexExplorer2/3rdparty/QHexView/QHexEdit2/chunks.cpp

328 lines
9.5 KiB
C++

/*
* QHexEdit is a Hex Editor Widget for the Qt Framework
* Copyright (C) 2010-2025 Winfried Simon
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* https://www.gnu.org/licenses/
*/
#include "chunks.h"
#include <limits.h>
#include <QBuffer>
#define NORMAL 0
#define HIGHLIGHTED 1
#define BUFFER_SIZE 0x10000
#define CHUNK_SIZE 0x1000
#define READ_CHUNK_MASK Q_INT64_C(0xfffffffffffff000)
/*this file is modified by wingsummer in order to fit the QHexView*/
// ***************************************** Constructors and file settings
Chunks::Chunks(QObject *parent) : QObject(parent) {
QBuffer *buf = new QBuffer(this);
setIODevice(buf);
}
Chunks::Chunks(QIODevice *ioDevice, QObject *parent) : QObject(parent) {
setIODevice(ioDevice);
}
bool Chunks::setIODevice(QIODevice *ioDevice) {
_ioDevice = ioDevice;
bool ok = _ioDevice->open(QIODevice::ReadOnly);
if (ok) // Try to open IODevice
{
ioDevice->setParent(this);
_size = _ioDevice->size();
_ioDevice->close();
} else // Fallback is an empty buffer
{
QBuffer *buf = new QBuffer(this);
_ioDevice = buf;
_size = 0;
}
_chunks.clear();
return ok;
}
// ***************************************** Getting data out of Chunks
QByteArray Chunks::data(qint64 pos, qint64 maxSize) const {
qint64 ioDelta = 0;
int chunkIdx = 0;
Chunk chunk;
QByteArray buffer;
// Do some checks and some arrangements
if (pos >= _size)
return buffer;
if (maxSize < 0)
maxSize = _size;
else if ((pos + maxSize) > _size)
maxSize = _size - pos;
_ioDevice->open(QIODevice::ReadOnly);
while (maxSize > 0) {
chunk.absPos = LLONG_MAX;
bool chunksLoopOngoing = true;
while ((chunkIdx < _chunks.count()) && chunksLoopOngoing) {
// In this section, we track changes before our required data and
// we take the editdet data, if availible. ioDelta is a difference
// counter to justify the read pointer to the original data, if
// data in between was deleted or inserted.
chunk = _chunks[chunkIdx];
if (chunk.absPos > pos)
chunksLoopOngoing = false;
else {
chunkIdx += 1;
qint64 count;
qint64 chunkOfs = pos - chunk.absPos;
if (maxSize > ((qint64)chunk.data.size() - chunkOfs)) {
count = (qint64)chunk.data.size() - chunkOfs;
ioDelta += CHUNK_SIZE - chunk.data.size();
} else
count = maxSize;
if (count > 0) {
buffer += chunk.data.mid(chunkOfs, (int)count);
maxSize -= count;
pos += count;
}
}
}
if ((maxSize > 0) && (pos < chunk.absPos)) {
// In this section, we read data from the original source. This only
// will happen, whe no copied data is available
qint64 byteCount;
QByteArray readBuffer;
if ((chunk.absPos - pos) > maxSize)
byteCount = maxSize;
else
byteCount = chunk.absPos - pos;
maxSize -= byteCount;
_ioDevice->seek(pos + ioDelta);
readBuffer = _ioDevice->read(byteCount);
buffer += readBuffer;
pos += readBuffer.size();
}
}
_ioDevice->close();
return buffer;
}
bool Chunks::write(QIODevice *iODevice, qint64 pos, qint64 count) {
if (count == -1)
count = _size;
if (iODevice->isOpen()) {
if (iODevice->isWritable()) {
for (qint64 idx = pos; idx < count; idx += BUFFER_SIZE) {
QByteArray ba = data(idx, BUFFER_SIZE);
iODevice->write(ba);
}
return true;
}
return false;
} else {
bool ok = iODevice->open(QIODevice::WriteOnly);
if (ok) {
for (qint64 idx = pos; idx < count; idx += BUFFER_SIZE) {
QByteArray ba = data(idx, BUFFER_SIZE);
iODevice->write(ba);
}
iODevice->close();
}
return ok;
}
}
// ***************************************** Search API
qint64 Chunks::indexOf(const QByteArray &ba, qint64 from) const {
qint64 result = -1;
QByteArray buffer;
for (qint64 pos = from; (pos < _size) && (result < 0); pos += BUFFER_SIZE) {
buffer = data(pos, BUFFER_SIZE + ba.size() - 1);
int findPos = buffer.indexOf(ba);
if (findPos >= 0)
result = pos + (qint64)findPos;
}
return result;
}
qint64 Chunks::lastIndexOf(const QByteArray &ba, qint64 from) const {
qint64 result = -1;
QByteArray buffer;
for (qint64 pos = from; (pos > 0) && (result < 0); pos -= BUFFER_SIZE) {
qint64 sPos = pos - BUFFER_SIZE - (qint64)ba.size() + 1;
if (sPos < 0)
sPos = 0;
buffer = data(sPos, pos - sPos);
int findPos = buffer.lastIndexOf(ba);
if (findPos >= 0)
result = sPos + (qint64)findPos;
}
return result;
}
// ***************************************** Char manipulations
bool Chunks::insert(qint64 pos, char b) {
return insert(pos, QByteArray(1, b));
}
bool Chunks::overwrite(qint64 pos, char b) {
return overwrite(pos, QByteArray(1, b));
}
bool Chunks::removeAt(qint64 pos) { return remove(pos, 1); }
bool Chunks::insert(qint64 pos, const QByteArray &ba) {
if ((pos < 0) || (pos > _size))
return false;
if (ba.isEmpty()) {
return true;
}
auto length = ba.length();
int chunkIdx;
if (pos == _size)
chunkIdx = getChunkIndex(pos - 1);
else
chunkIdx = getChunkIndex(pos);
qint64 posInBa = pos - _chunks[chunkIdx].absPos;
_chunks[chunkIdx].data.insert(posInBa, ba);
for (int idx = chunkIdx + 1; idx < _chunks.size(); idx++)
_chunks[idx].absPos += length;
_size += length;
return true;
}
bool Chunks::overwrite(qint64 pos, const QByteArray &ba) {
if ((pos < 0) || (pos >= _size))
return false;
int chunkIdx = getChunkIndex(pos);
auto &chunk = _chunks[chunkIdx];
qint64 posInBa = pos - chunk.absPos;
auto length = ba.length();
auto clen = chunk.data.size();
auto dist = length + posInBa - clen;
if (dist <= 0) {
chunk.data.replace(posInBa, length, ba);
} else {
auto len = clen - posInBa;
chunk.data.replace(posInBa, len, ba.left(len));
if (!overwrite(pos + clen, ba.right(dist))) {
return false;
}
}
return true;
}
bool Chunks::remove(qint64 pos, qint64 length) {
if ((pos < 0) || (pos >= _size))
return false;
int chunkIdx = getChunkIndex(pos);
auto &chunk = _chunks[chunkIdx];
qint64 posInBa = pos - chunk.absPos;
auto clen = chunk.data.size();
auto dist = length + posInBa - clen;
if (dist <= 0) {
chunk.data.remove(posInBa, length);
for (int idx = chunkIdx + 1; idx < _chunks.size(); idx++)
_chunks[idx].absPos += length;
_size -= length;
} else {
auto len = clen - posInBa;
chunk.data.remove(posInBa, len);
for (int idx = chunkIdx + 1; idx < _chunks.size(); idx++)
_chunks[idx].absPos -= len;
_size -= len;
if (!remove(pos + clen, dist)) {
return false;
}
}
return true;
}
// ***************************************** Utility functions
char Chunks::at(qint64 pos) const { return data(pos, 1).at(0); }
char Chunks::operator[](qint64 pos) const { return this->at(pos); }
qint64 Chunks::size() const { return _size; }
int Chunks::getChunkIndex(qint64 absPos) {
// This routine checks, if there is already a copied chunk available. If os,
// it returns a reference to it. If there is no copied chunk available,
// original data will be copied into a new chunk.
int foundIdx = -1;
int insertIdx = 0;
qint64 ioDelta = 0;
for (int idx = 0; idx < _chunks.size(); idx++) {
Chunk chunk = _chunks[idx];
if ((absPos >= chunk.absPos) &&
(absPos < (chunk.absPos + chunk.data.size()))) {
foundIdx = idx;
break;
}
if (absPos < chunk.absPos) {
insertIdx = idx;
break;
}
ioDelta += chunk.data.size() - CHUNK_SIZE;
insertIdx = idx + 1;
}
if (foundIdx == -1) {
Chunk newChunk;
qint64 readAbsPos = absPos - ioDelta;
qint64 readPos = (readAbsPos & READ_CHUNK_MASK);
_ioDevice->open(QIODevice::ReadOnly);
_ioDevice->seek(readPos);
newChunk.data = _ioDevice->read(CHUNK_SIZE);
_ioDevice->close();
newChunk.absPos = absPos - (readAbsPos - readPos);
_chunks.insert(insertIdx, newChunk);
foundIdx = insertIdx;
}
return foundIdx;
}
QIODevice *Chunks::ioDevice() const { return _ioDevice; }