/**
* This file is part of Hercules.
* http://herc.ws - http://github.com/HerculesWS/Hercules
*
* Copyright (C) 2013-2015 Hercules Dev Team
*
* Hercules 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/>.
*/
/**
* Mapcache Plugin
* This Plugin is made to handle the creation and update the new format of mapcache
* it also handles the convertion from the old to the new mapcache format
**/
#include "common/hercules.h" /* Should always be the first Hercules file included! (if you don't make it first, you won't be able to use interfaces) */
#include "common/memmgr.h"
#include "common/md5calc.h"
#include "common/nullpo.h"
#include "common/grfio.h"
#include "common/utils.h"
#include "map/map.h"
#include "common/HPMDataCheck.h" /* should always be the last Hercules file included! (if you don't make it last, it'll intentionally break compile time) */
#include <stdio.h>
#include <string.h>
HPExport struct hplugin_info pinfo = {
"Mapcache", ///< Plugin name
SERVER_TYPE_MAP, ///< Which server types this plugin works with?
"1.0.0", ///< Plugin version
HPM_VERSION, ///< HPM Version (don't change, macro is automatically updated)
};
/**
* Yes.. old mapcache was never packed, and we loaded and wrote a compiler paded structs
* DON'T BLAME IF SOMETHING EXPLODED [hemagx]
**/
// This is the main header found at the very beginning of the map cache
struct old_mapcache_main_header {
uint32 file_size;
uint16 map_count;
};
// This is the header appended before every compressed map cells info in the map cache
struct old_mapcache_map_info {
char name[MAP_NAME_LENGTH];
int16 xs;
int16 ys;
int32 len;
};
/**
*
*/
#define NO_WATER 1000000
VECTOR_DECL(char *) maplist;
bool needs_grfio;
/**
* code from utlis.c until it's interfaced
**/
#ifdef WIN32
# ifndef F_OK
# define F_OK 0x0
# endif /* F_OK */
#else
# include <unistd.h>
#endif
// Reads an uint32 in little-endian from the buffer
uint32 GetULong(const unsigned char* buf)
{
return (((uint32)(buf[0])))
| (((uint32)(buf[1])) << 0x08)
| (((uint32)(buf[2])) << 0x10)
| (((uint32)(buf[3])) << 0x18);
}
// Reads a float (32 bits) from the buffer
float GetFloat(const unsigned char* buf)
{
uint32 val = GetULong(buf);
return *((float*)(void*)&val);
}
bool write_mapcache(const uint8 *buf, int32 buf_len, bool is_compressed, const char *mapname, int16 xs, int16 ys)
{
struct map_cache_header header = { 0 };
char file_path[255];
int mapname_len;
unsigned long compressed_buf_len;
uint8 *compressed_buf = NULL;
FILE *new_mapcache_fp;
nullpo_retr(false, buf);
nullpo_retr(false, mapname);
mapname_len = (int)strlen(mapname);
if (mapname_len > MAP_NAME_LENGTH || mapname_len < 1) {
ShowError("write_mapcache: A map with invalid name length has beed passed '%s' size (%d)\n", mapname, mapname_len);
return false;
}
if ((xs < 0 || ys < 0)) {
ShowError("write_mapcache: '%s' given with invalid coords xs = %d, ys = %d\n", mapname, xs, ys);
return false;
}
if (((int)xs * ys) > MAX_MAP_SIZE) {
ShowError("write_mapcache: map '%s' exceeded MAX_MAP_SIZE of %d\n", mapname, MAX_MAP_SIZE);
return false;
}
snprintf(file_path, sizeof(file_path), "%s%s%s.%s", "maps/", DBPATH, mapname, "mcache");
new_mapcache_fp = fopen(file_path, "wb");
if (new_mapcache_fp == NULL) {
ShowWarning("Could not open file '%s', map cache creating failed.\n", file_path);
return false;
}
header.version = 0x1;
header.xs = xs;
header.ys = ys;
if (is_compressed == false) {
compressed_buf_len = buf_len * 2; //Creating big enough buffer to ensure ability to hold compressed data
CREATE(compressed_buf, uint8, compressed_buf_len);
grfio->encode_zip(compressed_buf, &compressed_buf_len, buf, buf_len);
header.len = (int)compressed_buf_len;
md5->binary(compressed_buf, (int)compressed_buf_len, header.md5_checksum);
} else {
header.len = buf_len;
md5->binary(buf, buf_len, header.md5_checksum);
}
fwrite(&header, sizeof(header), 1, new_mapcache_fp);
if (is_compressed == false)
fwrite(compressed_buf, compressed_buf_len, 1, new_mapcache_fp);
else
fwrite(buf, buf_len, 1, new_mapcache_fp);
fclose(new_mapcache_fp);
if (compressed_buf != NULL)
aFree(compressed_buf);
return true;
}
bool convert_old_mapcache(void)
{
const char *path = "db/"DBPATH"map_cache.dat";
FILE *mapcache_fp = fopen(path, "rb");
struct old_mapcache_main_header header = { 0 };
uint8 *p, *cursor;
uint32 file_size;
int i;
if (mapcache_fp == NULL) {
ShowError("Could not open mapcache file in the following path '%s' \n", path);
return false;
}
if (fread(&header, sizeof(header), 1, mapcache_fp) != 1) {
ShowError("Failed to read mapcache header \n");
fclose(mapcache_fp);
return false;
}
fseek(mapcache_fp, 0, SEEK_END);
file_size = (int)ftell(mapcache_fp);
fseek(mapcache_fp, 0, SEEK_SET);
if (file_size != header.file_size) {
ShowError("File size in mapcache header doesn't match actual mapcache file size \n");
fclose(mapcache_fp);
return false;
}
CREATE(p, uint8, header.file_size);
cursor = p + sizeof(header);
if (fread(p, header.file_size, 1, mapcache_fp) != 1) {
ShowError("Could not load mapcache file into memory, fread failed.\n");
aFree(p);
fclose(mapcache_fp);
return false;
}
for (i = 0; i < header.map_count; ++i) {
struct old_mapcache_map_info *info = (struct old_mapcache_map_info *)cursor;
ShowStatus("Creating mapcache: %s"CL_CLL"\n", info->name);
if (write_mapcache((uint8 *)info + sizeof(*info), info->len, true, info->name, info->xs, info->ys) == false) {
ShowError("failed To convert map '%s'\n", info->name);
}
cursor += sizeof(*info) + info->len;
}
aFree(p);
fclose(mapcache_fp);
return true;
}
bool mapcache_read_maplist(const char *filepath)
{
char line[4096] = { 0 };
FILE *fp;
nullpo_retr(false, filepath);
fp = fopen(filepath, "r");
if (fp == NULL)
return false;
while (fgets(line, sizeof(line), fp)) {
char map_name[MAP_NAME_LENGTH];
if (line[0] == '/' && line[1] == '/')
continue;
if (sscanf(line, "%11s", map_name) == 1) {
VECTOR_ENSURE(maplist, 1, 1);
VECTOR_PUSH(maplist, aStrdup(map_name));
}
}
ShowStatus("%d map loaded from map_index.txt\n", VECTOR_LENGTH(maplist));
fclose(fp);
return true;
}
bool mapcache_cache_map(const char *mapname)
{
char filepath[255] = { 0 };
uint8 *gat, *rsw, *gat_cursor;
uint8 *cells;
int water_height, map_size, xy;
int16 xs, ys;
nullpo_retr(false, mapname);
snprintf(filepath, sizeof(filepath), "data\\%s.gat", mapname);
gat = grfio_read(filepath);
if (gat == NULL) {
ShowError("mapcache_cache_map: Could not read %s, aborting caching map %s\n", filepath, mapname);
return false;
}
snprintf(filepath, sizeof(filepath), "data\\%s.rsw", mapname);
rsw = grfio_read(filepath);
if (rsw == NULL) {
water_height = NO_WATER;
} else {
water_height = (int)GetFloat(rsw + 166);
aFree(rsw);
}
xs = (int16)GetULong(gat + 6);
ys = (int16)GetULong(gat + 10);
if (xs <= 0 || ys <= 0) {
ShowError("mapcache_cache_map: map '%s' doesn't have valid size xs = %d, ys = %d\n", mapname, xs, ys);
aFree(gat);
return false;
}
map_size = xs * ys;
CREATE(cells, uint8, map_size);
gat_cursor = gat;
for (xy = 0; xy < map_size; ++xy) {
float height = GetFloat(gat_cursor + 14);
uint32 type = GetULong(gat_cursor + 30);
gat_cursor += 20;
if (type == 0 && water_height != NO_WATER && height > water_height)
type = 3;
cells[xy] = (uint8)type;
}
write_mapcache(cells, map_size, false, mapname, xs, ys);
aFree(gat);
aFree(cells);
return true;
}
bool mapcache_rebuild(void)
{
int i;
char file_path[255];
if (mapcache_read_maplist("db/map_index.txt") == false) {
ShowError("mapcache_rebuild: Could not read maplist, aborting\n");
return false;
}
for (i = 0; i < VECTOR_LENGTH(maplist); ++i) {
snprintf(file_path, sizeof(file_path), "%s%s%s.%s", "maps/", DBPATH, VECTOR_INDEX(maplist, i), "mcache");
if (access(file_path, F_OK) == 0 && remove(file_path) != 0) {
ShowWarning("mapcache_rebuild: Could not remove file '%s' \n", file_path);
}
}
for (i = 0; i < VECTOR_LENGTH(maplist); ++i) {
ShowStatus("Creating mapcache: %s"CL_CLL"\r", VECTOR_INDEX(maplist, i));
mapcache_cache_map(VECTOR_INDEX(maplist, i));
}
return true;
}
bool fix_md5_truncation_sub(FILE *fp, const char *map_name)
{
unsigned int file_size;
struct map_cache_header mheader = { 0 };
uint8 *buf = NULL;
nullpo_retr(false, fp);
nullpo_retr(false, map_name);
fseek(fp, 0, SEEK_END);
file_size = (unsigned int)ftell(fp);
fseek(fp, 0, SEEK_SET); // Rewind file pointer before passing it to the read function.
if (file_size <= sizeof(mheader) || fread(&mheader, sizeof(mheader), 1, fp) < 1) {
ShowError("fix_md5_truncation: Failed to read cache header for map '%s'.\n", map_name);
return false;
}
if (mheader.len <= 0) {
ShowError("fix_md5_truncation: A file with negative or zero compressed length passed '%d'.\n", mheader.len);
return false;
}
if (file_size < sizeof(mheader) + mheader.len) {
ShowError("fix_md5_truncation: An incomplete file passed for map '%s'.\n", map_name);
return false;
}
CREATE(buf, uint8, mheader.len);
if (fread(buf, mheader.len, 1, fp) < 1) {
ShowError("fix_md5_truncation: Could not load the compressed cell data for map '%s'.\n", map_name);
aFree(buf);
return false;
}
md5->binary(buf, mheader.len, mheader.md5_checksum);
aFree(buf);
fseek(fp, 0, SEEK_SET);
fwrite(&mheader, sizeof(mheader), 1, fp);
fclose(fp);
return true;
}
bool fix_md5_truncation(void)
{
int i;
bool retval = true;
if (mapcache_read_maplist("db/map_index.txt") == false) {
ShowError("mapcache_rebuild: Could not read maplist, aborting\n");
return false;
}
for (i = 0; i < VECTOR_LENGTH(maplist); ++i) {
const char *map_name = VECTOR_INDEX(maplist, i);
char file_path[255];
FILE *fp = NULL;
int16 version;
snprintf(file_path, sizeof(file_path), "%s%s%s.%s", "maps/", DBPATH, map_name, "mcache");
fp = fopen(file_path, "r+b");
if (fp == NULL) {
ShowWarning("fix_md5_truncation: Could not open the mapcache file for map '%s' at path '%s'.\n", map_name, file_path);
retval = false;
continue;
}
if (fread(&version, sizeof(version), 1, fp) < 1) {
ShowError("fix_md5_truncation: Could not read file version for map '%s'.\n", map_name);
fclose(fp);
retval = false;
continue;
}
if (version != 1) {
ShowError("fix_md5_truncation: Mapcache for map '%s' has version %d. The update is only applied to version 1.\n", map_name, version);
fclose(fp);
continue;
}
ShowStatus("Updating mapcache: %s'\n", map_name);
if (!fix_md5_truncation_sub(fp, map_name))
retval = false;
fclose(fp);
}
return retval;
}
CMDLINEARG(convertmapcache)
{
map->minimal = true;
return convert_old_mapcache();
}
CMDLINEARG(rebuild)
{
needs_grfio = true;
grfio->init("conf/grf-files.txt");
map->minimal = true;
return mapcache_rebuild();
}
CMDLINEARG(cachemap)
{
needs_grfio = true;
grfio->init("conf/grf-files.txt");
map->minimal = true;
return mapcache_cache_map(params);
}
CMDLINEARG(fixmd5)
{
map->minimal = true;
return fix_md5_truncation();
}
HPExport void server_preinit(void)
{
addArg("--convert-old-mapcache", false, convertmapcache,
"Converts an old db/"DBPATH"map_cache.dat file to the new format.");
addArg("--rebuild-mapcache", false, rebuild,
"Rebuilds the entire mapcache folder (maps/"DBPATH"), using db/map_index.txt as index.");
addArg("--map", true, cachemap,
"Rebuilds an individual map's cache into maps/"DBPATH" (usage: --map <map_name_without_extension>).");
addArg("--fix-md5", false, fixmd5,
"Updates the checksum for the files in maps/"DBPATH", using db/map_index.txt as index (see PR #1981).");
needs_grfio = false;
VECTOR_INIT(maplist);
}
HPExport void plugin_final(void)
{
while (VECTOR_LENGTH(maplist) > 0) {
char *name = VECTOR_POP(maplist);
aFree(name);
}
VECTOR_CLEAR(maplist);
if (needs_grfio)
grfio->final();
}