/** * 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 . */ /** * 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 #include 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 #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 )."); 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(); }