tibia-rme/source/filehandle.cpp
2023-10-15 21:52:40 +00:00

717 lines
16 KiB
C++

//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// Remere's Map Editor 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.
//
// Remere's Map Editor 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 "main.h"
#include "filehandle.h"
uint8_t NodeFileWriteHandle::NODE_START = ::NODE_START;
uint8_t NodeFileWriteHandle::NODE_END = ::NODE_END;
uint8_t NodeFileWriteHandle::ESCAPE_CHAR = ::ESCAPE_CHAR;
bool FileHandle::seek(size_t offset, int origin) {
if (file) {
return fseek(file, static_cast<long>(offset), origin) == 0;
}
return false;
}
size_t FileHandle::tell() {
if (file) {
return ftell(file);
}
return 0;
}
void FileHandle::close() {
if (file) {
fclose(file);
file = nullptr;
}
}
std::string FileHandle::getErrorMessage() {
switch (error_code) {
case FILE_NO_ERROR:
return "No error";
case FILE_COULD_NOT_OPEN:
return "Could not open file";
case FILE_INVALID_IDENTIFIER:
return "File magic number not recognized";
case FILE_STRING_TOO_LONG:
return "Too long string encountered";
case FILE_READ_ERROR:
return "Failed to read from file";
case FILE_WRITE_ERROR:
return "Failed to write to file";
case FILE_SYNTAX_ERROR:
return "Node file syntax error";
case FILE_PREMATURE_END:
return "File end encountered unexpectedly";
}
if (file == nullptr) {
return "Could not open file (2)";
}
if (ferror(file)) {
return "Internal file error #" + i2s(ferror(file));
}
return "No error";
}
//=============================================================================
// File read handle
FileReadHandle::FileReadHandle(const std::string &name) :
file_size(0) {
#if defined __VISUALC__ && defined _UNICODE
file = _wfopen(string2wstring(name).c_str(), L"rb");
#else
file = fopen(name.c_str(), "rb");
#endif
if (!file || ferror(file)) {
error_code = FILE_COULD_NOT_OPEN;
} else {
fseek(file, 0, SEEK_END);
file_size = ftell(file);
fseek(file, 0, SEEK_SET);
}
}
FileReadHandle::~FileReadHandle() {
////
}
void FileReadHandle::close() {
file_size = 0;
FileHandle::close();
}
bool FileReadHandle::getRAW(void* ptr, size_t sz) {
size_t o = fread(ptr, 1, sz, file);
if (o != sz) {
error_code = FILE_READ_ERROR;
return false;
}
return true;
}
bool FileReadHandle::getRAW(std::string &str, size_t sz) {
str.resize(sz);
size_t o = fread(const_cast<char*>(str.data()), 1, sz, file);
if (o != sz) {
error_code = FILE_READ_ERROR;
return false;
}
return true;
}
bool FileReadHandle::getString(std::string &str) {
uint16_t sz;
if (!getU16(sz)) {
error_code = FILE_READ_ERROR;
return false;
}
return getRAW(str, sz);
}
bool FileReadHandle::getLongString(std::string &str) {
uint32_t sz;
if (!getU32(sz)) {
error_code = FILE_READ_ERROR;
return false;
}
return getRAW(str, sz);
}
//=============================================================================
// Node file read handle
NodeFileReadHandle::NodeFileReadHandle() :
last_was_start(false),
cache(nullptr),
cache_size(32768),
cache_length(0),
local_read_index(0),
root_node(nullptr) {
////
}
NodeFileReadHandle::~NodeFileReadHandle() {
while (!unused.empty()) {
free(unused.top());
unused.pop();
}
}
BinaryNode* NodeFileReadHandle::getNode(BinaryNode* parent) {
void* mem;
if (unused.empty()) {
mem = malloc(sizeof(BinaryNode));
} else {
mem = unused.top();
unused.pop();
}
return new (mem) BinaryNode(this, parent);
}
void NodeFileReadHandle::freeNode(BinaryNode* node) {
if (node) {
node->~BinaryNode();
unused.push(node);
}
}
//=============================================================================
// Memory based node file read handle
MemoryNodeFileReadHandle::MemoryNodeFileReadHandle(const uint8_t* data, size_t size) {
assign(data, size);
}
void MemoryNodeFileReadHandle::assign(const uint8_t* data, size_t size) {
freeNode(root_node);
root_node = nullptr;
// Highly volatile, but we know we're not gonna modify
cache = const_cast<uint8_t*>(data);
cache_size = cache_length = size;
local_read_index = 0;
}
MemoryNodeFileReadHandle::~MemoryNodeFileReadHandle() {
freeNode(root_node);
}
void MemoryNodeFileReadHandle::close() {
assign(nullptr, 0);
}
bool MemoryNodeFileReadHandle::renewCache() {
return false;
}
BinaryNode* MemoryNodeFileReadHandle::getRootNode() {
assert(root_node == nullptr); // You should never do this twice
local_read_index++; // Skip first NODE_START
last_was_start = true;
root_node = getNode(nullptr);
root_node->load();
return root_node;
}
//=============================================================================
// File based node file read handle
DiskNodeFileReadHandle::DiskNodeFileReadHandle(const std::string &name, const std::vector<std::string> &acceptable_identifiers) :
file_size(0) {
#if defined __VISUALC__ && defined _UNICODE
file = _wfopen(string2wstring(name).c_str(), L"rb");
#else
file = fopen(name.c_str(), "rb");
#endif
if (!file || ferror(file)) {
error_code = FILE_COULD_NOT_OPEN;
} else {
char ver[4];
if (fread(ver, 1, 4, file) != 4) {
fclose(file);
error_code = FILE_SYNTAX_ERROR;
return;
}
// 0x00 00 00 00 is accepted as a wildcard version
if (ver[0] != 0 || ver[1] != 0 || ver[2] != 0 || ver[3] != 0) {
bool accepted = false;
for (std::vector<std::string>::const_iterator id_iter = acceptable_identifiers.begin(); id_iter != acceptable_identifiers.end(); ++id_iter) {
if (memcmp(ver, id_iter->c_str(), 4) == 0) {
accepted = true;
break;
}
}
if (!accepted) {
fclose(file);
error_code = FILE_SYNTAX_ERROR;
return;
}
}
fseek(file, 0, SEEK_END);
file_size = ftell(file);
fseek(file, 4, SEEK_SET);
}
}
DiskNodeFileReadHandle::~DiskNodeFileReadHandle() {
close();
}
void DiskNodeFileReadHandle::close() {
freeNode(root_node);
file_size = 0;
FileHandle::close();
free(cache);
}
bool DiskNodeFileReadHandle::renewCache() {
if (!cache) {
cache = (uint8_t*)malloc(cache_size);
}
cache_length = fread(cache, 1, cache_size, file);
if (cache_length == 0 || ferror(file)) {
return false;
}
local_read_index = 0;
return true;
}
BinaryNode* DiskNodeFileReadHandle::getRootNode() {
assert(root_node == nullptr); // You should never do this twice
uint8_t first;
size_t numRead = fread(&first, 1, 1, file);
if (numRead != 1) {
error_code = FILE_READ_ERROR;
return nullptr;
}
if (first == NODE_START) {
root_node = getNode(nullptr);
root_node->load();
return root_node;
} else {
error_code = FILE_SYNTAX_ERROR;
return nullptr;
}
}
//=============================================================================
// Binary file node
BinaryNode::BinaryNode(NodeFileReadHandle* file, BinaryNode* parent) :
read_offset(0),
file(file),
parent(parent),
child(nullptr) {
////
}
BinaryNode::~BinaryNode() {
file->freeNode(child);
}
BinaryNode* BinaryNode::getChild() {
ASSERT(file);
ASSERT(child == nullptr);
if (file->last_was_start) {
child = file->getNode(this);
child->load();
return child;
}
return nullptr;
}
bool BinaryNode::getRAW(uint8_t* ptr, size_t sz) {
if (read_offset + sz > data.size()) {
read_offset = data.size();
return false;
}
memcpy(ptr, data.data() + read_offset, sz);
read_offset += sz;
return true;
}
bool BinaryNode::getRAW(std::string &str, size_t sz) {
if (read_offset + sz > data.size()) {
read_offset = data.size();
return false;
}
str.assign(data.data() + read_offset, sz);
read_offset += sz;
return true;
}
bool BinaryNode::getString(std::string &str) {
uint16_t len;
if (!getU16(len)) {
return false;
}
return getRAW(str, len);
}
bool BinaryNode::getLongString(std::string &str) {
uint32_t len;
if (!getU32(len)) {
return false;
}
return getRAW(str, len);
}
BinaryNode* BinaryNode::advance() {
// Advance this to the next position
ASSERT(file);
if (file->error_code != FILE_NO_ERROR) {
return nullptr;
}
if (child == nullptr) {
getChild();
}
// We need to move the cursor to the next node, since we're still iterating our child node!
while (child) {
// both functions modify ourselves and sets child to nullptr, so loop will be aborted
// possibly change to assignment ?
child->getChild();
child->advance();
}
if (file->last_was_start) {
return nullptr;
} else {
// Last was end (0xff)
// Read next byte to decide if there is another node following this
uint8_t*&cache = file->cache;
size_t &cache_length = file->cache_length;
size_t &local_read_index = file->local_read_index;
if (local_read_index >= cache_length) {
if (!file->renewCache()) {
// Failed to renew, exit
parent->child = nullptr;
file->freeNode(this);
return nullptr;
}
}
uint8_t op = cache[local_read_index];
++local_read_index;
if (op == NODE_START) {
// Another node follows this.
// Load this node as the next one
read_offset = 0;
data.clear();
load();
return this;
} else if (op == NODE_END) {
// End of this child-tree
parent->child = nullptr;
file->last_was_start = false;
file->freeNode(this);
return nullptr;
} else {
file->error_code = FILE_SYNTAX_ERROR;
return nullptr;
}
}
}
void BinaryNode::load() {
ASSERT(file);
// Read until next node starts
uint8_t*&cache = file->cache;
size_t &cache_length = file->cache_length;
size_t &local_read_index = file->local_read_index;
while (true) {
if (local_read_index >= cache_length) {
if (!file->renewCache()) {
// Failed to renew, exit
file->error_code = FILE_PREMATURE_END;
return;
}
}
uint8_t op = cache[local_read_index];
++local_read_index;
switch (op) {
case NODE_START: {
file->last_was_start = true;
return;
}
case NODE_END: {
file->last_was_start = false;
return;
}
case ESCAPE_CHAR: {
if (local_read_index >= cache_length) {
if (!file->renewCache()) {
// Failed to renew, exit
file->error_code = FILE_PREMATURE_END;
return;
}
}
op = cache[local_read_index];
++local_read_index;
break;
}
default:
break;
}
// std::cout << "Appending..." << std::endl;
data.append(1, op);
}
}
//=============================================================================
// node file binary write handle
FileWriteHandle::FileWriteHandle(const std::string &name) {
#if defined __VISUALC__ && defined _UNICODE
file = _wfopen(string2wstring(name).c_str(), L"wb");
#else
file = fopen(name.c_str(), "wb");
#endif
if (file == nullptr || ferror(file)) {
error_code = FILE_COULD_NOT_OPEN;
}
}
FileWriteHandle::~FileWriteHandle() {
////
}
bool FileWriteHandle::addString(const std::string &str) {
if (str.size() > 0xFFFF) {
error_code = FILE_STRING_TOO_LONG;
return false;
}
addU16(uint16_t(str.size()));
addRAW(str);
return true;
}
bool FileWriteHandle::addString(const char* str) {
size_t len = strlen(str);
if (len > 0xFFFF) {
error_code = FILE_STRING_TOO_LONG;
return false;
}
addU16(uint16_t(len));
fwrite(str, 1, len, file);
return true;
}
bool FileWriteHandle::addLongString(const std::string &str) {
addU32(uint32_t(str.size()));
addRAW(str);
return true;
}
bool FileWriteHandle::addRAW(const std::string &str) {
fwrite(str.c_str(), 1, str.size(), file);
return ferror(file) == 0;
}
bool FileWriteHandle::addRAW(const uint8_t* ptr, size_t sz) {
fwrite(ptr, 1, sz, file);
return ferror(file) == 0;
}
void FileWriteHandle::flush() {
if (file) {
fflush(file);
}
}
//=============================================================================
// Disk based node file write handle
DiskNodeFileWriteHandle::DiskNodeFileWriteHandle(const std::string &name, const std::string &identifier) {
#if defined __VISUALC__ && defined _UNICODE
file = _wfopen(string2wstring(name).c_str(), L"wb");
#else
file = fopen(name.c_str(), "wb");
#endif
if (!file || ferror(file)) {
error_code = FILE_COULD_NOT_OPEN;
return;
}
if (identifier.length() != 4) {
error_code = FILE_INVALID_IDENTIFIER;
return;
}
fwrite(identifier.c_str(), 1, 4, file);
if (!cache) {
cache = (uint8_t*)malloc(cache_size + 1);
}
local_write_index = 0;
}
DiskNodeFileWriteHandle::~DiskNodeFileWriteHandle() {
close();
}
void DiskNodeFileWriteHandle::close() {
if (file) {
renewCache();
fclose(file);
file = nullptr;
error_code = FILE_NO_ERROR;
}
}
void DiskNodeFileWriteHandle::renewCache() {
if (cache) {
fwrite(cache, local_write_index, 1, file);
if (ferror(file) != 0) {
error_code = FILE_WRITE_ERROR;
}
} else {
cache = (uint8_t*)malloc(cache_size + 1);
}
local_write_index = 0;
}
//=============================================================================
// Memory based node file write handle
MemoryNodeFileWriteHandle::MemoryNodeFileWriteHandle() {
if (!cache) {
cache = (uint8_t*)malloc(cache_size + 1);
}
local_write_index = 0;
}
MemoryNodeFileWriteHandle::~MemoryNodeFileWriteHandle() {
close();
}
void MemoryNodeFileWriteHandle::reset() {
memset(cache, 0xAA, cache_size);
local_write_index = 0;
}
void MemoryNodeFileWriteHandle::close() {
free(cache);
cache = nullptr;
}
uint8_t* MemoryNodeFileWriteHandle::getMemory() {
return cache;
}
size_t MemoryNodeFileWriteHandle::getSize() {
return local_write_index;
}
void MemoryNodeFileWriteHandle::renewCache() {
if (cache) {
cache_size = cache_size * 2;
cache = (uint8_t*)realloc(cache, cache_size);
if (!cache) {
exit(1);
}
} else {
cache = (uint8_t*)malloc(cache_size + 1);
}
}
//=============================================================================
// Node file write handle
NodeFileWriteHandle::NodeFileWriteHandle() :
cache(nullptr),
cache_size(0x7FFF),
local_write_index(0) {
////
}
NodeFileWriteHandle::~NodeFileWriteHandle() {
free(cache);
}
bool NodeFileWriteHandle::addNode(uint8_t nodetype) {
cache[local_write_index++] = NODE_START;
if (local_write_index >= cache_size) {
renewCache();
}
cache[local_write_index++] = nodetype;
if (local_write_index >= cache_size) {
renewCache();
}
return error_code == FILE_NO_ERROR;
}
bool NodeFileWriteHandle::endNode() {
cache[local_write_index++] = NODE_END;
if (local_write_index >= cache_size) {
renewCache();
}
return error_code == FILE_NO_ERROR;
}
bool NodeFileWriteHandle::addU8(uint8_t u8) {
writeBytes(&u8, sizeof(u8));
return error_code == FILE_NO_ERROR;
}
bool NodeFileWriteHandle::addByte(uint8_t u8) {
writeBytes(&u8, sizeof(u8));
return error_code == FILE_NO_ERROR;
}
bool NodeFileWriteHandle::addU16(uint16_t u16) {
writeBytes(reinterpret_cast<uint8_t*>(&u16), sizeof(u16));
return error_code == FILE_NO_ERROR;
}
bool NodeFileWriteHandle::addU32(uint32_t u32) {
writeBytes(reinterpret_cast<uint8_t*>(&u32), sizeof(u32));
return error_code == FILE_NO_ERROR;
}
bool NodeFileWriteHandle::addU64(uint64_t u64) {
writeBytes(reinterpret_cast<uint8_t*>(&u64), sizeof(u64));
return error_code == FILE_NO_ERROR;
}
bool NodeFileWriteHandle::addString(const std::string &str) {
if (str.size() > 0xFFFF) {
error_code = FILE_STRING_TOO_LONG;
return false;
}
addU16(uint16_t(str.size()));
addRAW((const uint8_t*)str.c_str(), str.size());
return error_code == FILE_NO_ERROR;
}
bool NodeFileWriteHandle::addLongString(const std::string &str) {
addU32(uint32_t(str.size()));
addRAW((const uint8_t*)str.c_str(), str.size());
return error_code == FILE_NO_ERROR;
}
bool NodeFileWriteHandle::addRAW(std::string &str) {
writeBytes(reinterpret_cast<uint8_t*>(const_cast<char*>(str.data())), str.size());
return error_code == FILE_NO_ERROR;
}
bool NodeFileWriteHandle::addRAW(const uint8_t* ptr, size_t sz) {
writeBytes(ptr, sz);
return error_code == FILE_NO_ERROR;
}