summaryrefslogtreecommitdiff
path: root/src/plugins/mapcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/mapcache.c')
-rw-r--r--src/plugins/mapcache.c490
1 files changed, 490 insertions, 0 deletions
diff --git a/src/plugins/mapcache.c b/src/plugins/mapcache.c
new file mode 100644
index 000000000..5e44492f6
--- /dev/null
+++ b/src/plugins/mapcache.c
@@ -0,0 +1,490 @@
+/**
+* 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();
+}