/** * Tibia GIMUD Server - a free and open-source MMORPG server emulator * Copyright (C) 2019 Sabrehaven and Mark Samman * * 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 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "otpch.h" #include "fileloader.h" FileLoader::~FileLoader() { if (file) { fclose(file); file = nullptr; } NodeStruct::clearNet(root); delete[] buffer; for (auto& i : cached_data) { delete[] i.data; } } bool FileLoader::openFile(const char* filename, const char* accept_identifier) { file = fopen(filename, "rb"); if (!file) { lastError = ERROR_CAN_NOT_OPEN; return false; } char identifier[4]; if (fread(identifier, 1, 4, file) < 4) { fclose(file); file = nullptr; lastError = ERROR_EOF; return false; } // The first four bytes must either match the accept identifier or be 0x00000000 (wildcard) if (memcmp(identifier, accept_identifier, 4) != 0 && memcmp(identifier, "\0\0\0\0", 4) != 0) { fclose(file); file = nullptr; lastError = ERROR_INVALID_FILE_VERSION; return false; } fseek(file, 0, SEEK_END); int32_t file_size = ftell(file); cache_size = std::min(32768, std::max(file_size / 20, 8192)) & ~0x1FFF; if (!safeSeek(4)) { lastError = ERROR_INVALID_FORMAT; return false; } delete root; root = new NodeStruct(); root->start = 4; int32_t byte; if (safeSeek(4) && readByte(byte) && byte == NODE_START) { return parseNode(root); } return false; } bool FileLoader::parseNode(NODE node) { int32_t byte, pos; NODE currentNode = node; while (readByte(byte)) { currentNode->type = byte; bool setPropsSize = false; while (true) { if (!readByte(byte)) { return false; } bool skipNode = false; switch (byte) { case NODE_START: { //child node start if (!safeTell(pos)) { return false; } NODE childNode = new NodeStruct(); childNode->start = pos; currentNode->propsSize = pos - currentNode->start - 2; currentNode->child = childNode; setPropsSize = true; if (!parseNode(childNode)) { return false; } break; } case NODE_END: { //current node end if (!setPropsSize) { if (!safeTell(pos)) { return false; } currentNode->propsSize = pos - currentNode->start - 2; } if (!readByte(byte)) { return true; } switch (byte) { case NODE_START: { //starts next node if (!safeTell(pos)) { return false; } skipNode = true; NODE nextNode = new NodeStruct(); nextNode->start = pos; currentNode->next = nextNode; currentNode = nextNode; break; } case NODE_END: return safeTell(pos) && safeSeek(pos); default: lastError = ERROR_INVALID_FORMAT; return false; } break; } case ESCAPE_CHAR: { if (!readByte(byte)) { return false; } break; } default: break; } if (skipNode) { break; } } } return false; } const uint8_t* FileLoader::getProps(const NODE node, size_t& size) { if (!node) { return nullptr; } if (node->propsSize >= buffer_size) { delete[] buffer; while (node->propsSize >= buffer_size) { buffer_size *= 2; } buffer = new uint8_t[buffer_size]; } //get buffer if (!readBytes(node->propsSize, node->start + 2)) { return nullptr; } //unscape buffer size_t j = 0; bool escaped = false; for (uint32_t i = 0; i < node->propsSize; ++i, ++j) { if (buffer[i] == ESCAPE_CHAR) { //escape char found, skip it and write next buffer[j] = buffer[++i]; //is neede a displacement for next bytes escaped = true; } else if (escaped) { //perform that displacement buffer[j] = buffer[i]; } } size = j; return buffer; } bool FileLoader::getProps(const NODE node, PropStream& props) { size_t size; if (const uint8_t* a = getProps(node, size)) { props.init(reinterpret_cast(a), size); // does not break strict aliasing return true; } props.init(nullptr, 0); return false; } NODE FileLoader::getChildNode(const NODE parent, uint32_t& type) { if (parent) { NODE child = parent->child; if (child) { type = child->type; } return child; } type = root->type; return root; } NODE FileLoader::getNextNode(const NODE prev, uint32_t& type) { if (!prev) { return NO_NODE; } NODE next = prev->next; if (next) { type = next->type; } return next; } inline bool FileLoader::readByte(int32_t& value) { if (cache_index == NO_VALID_CACHE) { lastError = ERROR_CACHE_ERROR; return false; } if (cache_offset >= cached_data[cache_index].size) { int32_t pos = cache_offset + cached_data[cache_index].base; int32_t tmp = getCacheBlock(pos); if (tmp < 0) { return false; } cache_index = tmp; cache_offset = pos - cached_data[cache_index].base; if (cache_offset >= cached_data[cache_index].size) { return false; } } value = cached_data[cache_index].data[cache_offset++]; return true; } inline bool FileLoader::readBytes(uint32_t size, int32_t pos) { //seek at pos uint32_t remain = size; uint8_t* buf = this->buffer; do { //prepare cache uint32_t i = getCacheBlock(pos); if (i == NO_VALID_CACHE) { return false; } cache_index = i; cache_offset = pos - cached_data[i].base; //get maximum read block size and calculate remaining bytes uint32_t reading = std::min(remain, cached_data[i].size - cache_offset); remain -= reading; //read it memcpy(buf, cached_data[cache_index].data + cache_offset, reading); //update variables cache_offset += reading; buf += reading; pos += reading; } while (remain > 0); return true; } inline bool FileLoader::safeSeek(uint32_t pos) { uint32_t i = getCacheBlock(pos); if (i == NO_VALID_CACHE) { return false; } cache_index = i; cache_offset = pos - cached_data[i].base; return true; } inline bool FileLoader::safeTell(int32_t& pos) { if (cache_index == NO_VALID_CACHE) { lastError = ERROR_CACHE_ERROR; return false; } pos = cached_data[cache_index].base + cache_offset - 1; return true; } inline uint32_t FileLoader::getCacheBlock(uint32_t pos) { bool found = false; uint32_t i, base_pos = pos & ~(cache_size - 1); for (i = 0; i < CACHE_BLOCKS; i++) { if (cached_data[i].loaded) { if (cached_data[i].base == base_pos) { found = true; break; } } } if (!found) { i = loadCacheBlock(pos); } return i; } int32_t FileLoader::loadCacheBlock(uint32_t pos) { int32_t i, loading_cache = -1, base_pos = pos & ~(cache_size - 1); for (i = 0; i < CACHE_BLOCKS; i++) { if (!cached_data[i].loaded) { loading_cache = i; break; } } if (loading_cache == -1) { for (i = 0; i < CACHE_BLOCKS; i++) { if (std::abs(static_cast(cached_data[i].base) - base_pos) > static_cast(2 * cache_size)) { loading_cache = i; break; } } if (loading_cache == -1) { loading_cache = 0; } } if (cached_data[loading_cache].data == nullptr) { cached_data[loading_cache].data = new uint8_t[cache_size]; } cached_data[loading_cache].base = base_pos; if (fseek(file, cached_data[loading_cache].base, SEEK_SET) != 0) { lastError = ERROR_SEEK_ERROR; return -1; } uint32_t size = fread(cached_data[loading_cache].data, 1, cache_size, file); cached_data[loading_cache].size = size; if (size < (pos - cached_data[loading_cache].base)) { lastError = ERROR_SEEK_ERROR; return -1; } cached_data[loading_cache].loaded = 1; return loading_cache; }