From 400076f4e26a121746545cf9f6483ca8e93896b5 Mon Sep 17 00:00:00 2001 From: hemagx Date: Mon, 16 Jan 2017 19:02:15 +0200 Subject: New mapcache system * Now each map is in separated file so now it will be easier to know which map got updated or delete on updates * Now there's md5 checksum check for each map * Now the map cache is platform safe, the old format was not packed which may result in undefined behavior * The map cache tool got converted into hercules plugin Signed-off-by: hemagx --- src/map/map.c | 221 +++++++++++++++++++++++++++++++--------------------------- src/map/map.h | 32 +++++---- 2 files changed, 136 insertions(+), 117 deletions(-) (limited to 'src/map') diff --git a/src/map/map.c b/src/map/map.c index bb367a08d..780e6f535 100644 --- a/src/map/map.c +++ b/src/map/map.c @@ -66,6 +66,7 @@ #include "common/core.h" #include "common/ers.h" #include "common/grfio.h" +#include "common/md5calc.h" #include "common/memmgr.h" #include "common/nullpo.h" #include "common/random.h" @@ -2899,21 +2900,26 @@ int map_cell2gat(struct mapcell cell) { ShowWarning("map_cell2gat: cell has no matching gat type\n"); return 1; // default to 'wall' } -void map_cellfromcache(struct map_data *m) { - struct map_cache_map_info *info; +/** + * Extracts a map's cell data from its compressed mapcache. + * + * @param[in, out] m The target map. + */ +void map_cellfromcache(struct map_data *m) +{ nullpo_retv(m); - info = (struct map_cache_map_info *)m->cellPos; - if (info) { + if (m->cell_buf.data != NULL) { char decode_buffer[MAX_MAP_SIZE]; unsigned long size, xy; int i; - size = (unsigned long)info->xs*(unsigned long)info->ys; + size = (unsigned long)m->xs * (unsigned long)m->ys; // TO-DO: Maybe handle the scenario, if the decoded buffer isn't the same size as expected? [Shinryo] - grfio->decode_zip(decode_buffer, &size, m->cellPos+sizeof(struct map_cache_map_info), info->len); + grfio->decode_zip(decode_buffer, &size, m->cell_buf.data, m->cell_buf.len); + CREATE(m->cell, struct mapcell, size); // Set cell properties @@ -3253,97 +3259,125 @@ int map_eraseipport(unsigned short map_index, uint32 ip, uint16 port) { return 0; } -/*========================================== - * [Shinryo]: Init the mapcache - *------------------------------------------*/ -char *map_init_mapcache(FILE *fp) { - struct map_cache_main_header header; - size_t size = 0; - char *buffer; - - // No file open? Return.. - nullpo_ret(fp); +/** + * Reads a map's compressed cell data from its mapcache file. + * + * @param[in,out] m The target map. + * @return The loading success state. + * @retval false in case of errors. + */ +bool map_readfromcache(struct map_data *m) +{ + unsigned int file_size; + char file_path[256]; + FILE *fp = NULL; + bool retval = false; + int16 version; - // Get file size - fseek(fp, 0, SEEK_END); - size = ftell(fp); - fseek(fp, 0, SEEK_SET); + nullpo_retr(false, m); - // Allocate enough space - CREATE(buffer, char, size); + snprintf(file_path, sizeof(file_path), "%s%s%s.%s", "maps/", DBPATH, m->name, "mcache"); + fp = fopen(file_path, "rb"); - // No memory? Return.. - nullpo_ret(buffer); + if (fp == NULL) { + ShowWarning("map_readfromcache: Could not open the mapcache file for map '%s' at path '%s'.\n", m->name, file_path); + return false; + } - // Read file into buffer.. - if(fread(buffer, sizeof(char), size, fp) != size) { - ShowError("map_init_mapcache: Could not read entire mapcache file\n"); - aFree(buffer); - return NULL; + if (fread(&version, sizeof(version), 1, fp) < 1) { + ShowError("map_readfromcache: Could not read file version for map '%s'.\n", m->name); + fclose(fp); + return false; } - rewind(fp); + 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. - // Get main header to verify if data is corrupted - if( fread(&header, sizeof(header), 1, fp) != 1 ) { - ShowError("map_init_mapcache: Error obtaining main header!\n"); - aFree(buffer); - return NULL; - } - if( GetULong((unsigned char *)&(header.file_size)) != size ) { - ShowError("map_init_mapcache: Map cache is corrupted!\n"); - aFree(buffer); - return NULL; + switch(version) { + case 1: + retval = map->readfromcache_v1(fp, m, file_size); + break; + default: + ShowError("map_readfromcache: Mapcache file has unknown version '%d' for map '%s'.\n", version, m->name); + break; } - return buffer; + fclose(fp); + return retval; } -/*========================================== - * Map cache reading - * [Shinryo]: Optimized some behaviour to speed this up - *==========================================*/ -int map_readfromcache(struct map_data *m, char *buffer) { - int i; - struct map_cache_main_header *header = (struct map_cache_main_header *)buffer; - struct map_cache_map_info *info = NULL; - char *p = buffer + sizeof(struct map_cache_main_header); - - nullpo_ret(m); - nullpo_ret(buffer); - - for(i = 0; i < header->map_count; i++) { - info = (struct map_cache_map_info *)p; +/** + * Reads a map's compressed cell data from its mapcache file (file format + * version 1). + * + * @param[in] fp The file pointer to read from (opened and closed by + * the caller). + * @param[in,out] m The target map. + * @param[in] file_size The size of the file to load from. + * @return The loading success state. + * @retval false in case of errors. + */ +bool map_readfromcache_v1(FILE *fp, struct map_data *m, unsigned int file_size) +{ + struct map_cache_header mheader = { 0 }; + uint8 md5buf[16] = { 0 }; + int map_size; + nullpo_retr(false, fp); + nullpo_retr(false, m); + + if (file_size <= sizeof(mheader) || fread(&mheader, sizeof(mheader), 1, fp) < 1) { + ShowError("map_readfromcache: Failed to read cache header for map '%s'.\n", m->name); + return false; + } - if( strcmp(m->name, info->name) == 0 ) - break; // Map found + if (mheader.len <= 0) { + ShowError("map_readfromcache: A file with negative or zero compressed length passed '%d'.\n", mheader.len); + return false; + } - // Jump to next entry.. - p += sizeof(struct map_cache_map_info) + info->len; + if (file_size < sizeof(mheader) + mheader.len) { + ShowError("map_readfromcache: An incomplete file passed for map '%s'.\n", m->name); + return false; } - if( info && i < header->map_count ) { - unsigned long size; + if (mheader.ys <= 0 || mheader.xs <= 0) { + ShowError("map_readfromcache: A map with invalid size passed '%s' xs: '%d' ys: '%d'.\n", m->name, mheader.xs, mheader.ys); + return false; + } - if( info->xs <= 0 || info->ys <= 0 ) - return 0;// Invalid + m->xs = mheader.xs; + m->ys = mheader.ys; + map_size = (int)mheader.xs * (int)mheader.ys; - m->xs = info->xs; - m->ys = info->ys; - size = (unsigned long)info->xs*(unsigned long)info->ys; + if (map_size > MAX_MAP_SIZE) { + ShowWarning("map_readfromcache: %s exceeded MAX_MAP_SIZE of %d.\n", m->name, MAX_MAP_SIZE); + return false; + } - if(size > MAX_MAP_SIZE) { - ShowWarning("map_readfromcache: %s exceeded MAX_MAP_SIZE of %d\n", info->name, MAX_MAP_SIZE); - return 0; // Say not found to remove it from list.. [Shinryo] - } + CREATE(m->cell_buf.data, uint8, mheader.len); + m->cell_buf.len = mheader.len; + if (fread(m->cell_buf.data, mheader.len, 1, fp) < 1) { + ShowError("mapreadfromcache: Could not load the compressed cell data for map '%s'.\n", m->name); + aFree(m->cell_buf.data); + m->cell_buf.data = NULL; + m->cell_buf.len = 0; + return false; + } - m->cellPos = p; - m->cell = (struct mapcell *)0xdeadbeaf; + md5->binary(m->cell_buf.data, m->cell_buf.len, md5buf); - return 1; + if (memcmp(md5buf, mheader.md5_checksum, sizeof(md5buf)) != 0) { + ShowError("mapreadfromcache: md5 checksum check failed for map '%s'\n", m->name); + aFree(m->cell_buf.data); + m->cell_buf.data = NULL; + m->cell_buf.len = 0; + return false; } - return 0; // Not found + m->cell = (struct mapcell *)0xdeadbeaf; + + return true; } /** @@ -3747,26 +3781,12 @@ void map_removemapdb(struct map_data *m) { *--------------------------------------*/ int map_readallmaps (void) { int i; - FILE* fp=NULL; int maps_removed = 0; - if( map->enable_grf ) + if (map->enable_grf) { ShowStatus("Loading maps (using GRF files)...\n"); - else { - char mapcachefilepath[256]; - safesnprintf(mapcachefilepath, 256, "%s/%s%s", map->db_path, DBPATH, "map_cache.dat"); - ShowStatus("Loading maps (using %s as map cache)...\n", mapcachefilepath); - if( (fp = fopen(mapcachefilepath, "rb")) == NULL ) { - ShowFatalError("Unable to open map cache file "CL_WHITE"%s"CL_RESET"\n", mapcachefilepath); - exit(EXIT_FAILURE); //No use launching server if maps can't be read. - } - - // Init mapcache data.. [Shinryo] - map->cache_buffer = map->init_mapcache(fp); - if(!map->cache_buffer) { - ShowFatalError("Failed to initialize mapcache data (%s)..\n", mapcachefilepath); - exit(EXIT_FAILURE); - } + } else { + ShowStatus("Loading maps using map cache files...\n"); } for(i = 0; i < map->count; i++) { @@ -3780,7 +3800,7 @@ int map_readallmaps (void) { if( ! (map->enable_grf? map->readgat(&map->list[i]) - :map->readfromcache(&map->list[i], map->cache_buffer)) + :map->readfromcache(&map->list[i])) ) { map->delmapid(i); maps_removed++; @@ -3822,10 +3842,6 @@ int map_readallmaps (void) { // intialization and configuration-dependent adjustments of mapflags map->flags_init(); - if( !map->enable_grf ) { - fclose(fp); - } - // finished map loading ShowInfo("Successfully loaded '"CL_WHITE"%d"CL_RESET"' maps."CL_CLL"\n",map->count); instance->start_id = map->count; // Next Map Index will be instances @@ -6062,6 +6078,11 @@ int do_final(void) { ers_destroy(map->iterator_ers); ers_destroy(map->flooritem_ers); + for (i = 0; i < map->count; ++i) { + if (map->list[i].cell_buf.data != NULL) + aFree(map->list[i].cell_buf.data); + map->list[i].cell_buf.len = 0; + } aFree(map->list); if( map->block_free ) @@ -6069,9 +6090,6 @@ int do_final(void) { if( map->bl_list ) aFree(map->bl_list); - if( !map->enable_grf ) - aFree(map->cache_buffer); - aFree(map->MAP_CONF_NAME); aFree(map->BATTLE_CONF_FILENAME); aFree(map->ATCOMMAND_CONF_FILENAME); @@ -6687,7 +6705,6 @@ void map_defaults(void) { map->list = NULL; map->iterator_ers = NULL; - map->cache_buffer = NULL; map->flooritem_ers = NULL; /* */ @@ -6834,8 +6851,8 @@ void map_defaults(void) { map->iwall_nextxy = map_iwall_nextxy; map->create_map_data_other_server = create_map_data_other_server; map->eraseallipport_sub = map_eraseallipport_sub; - map->init_mapcache = map_init_mapcache; map->readfromcache = map_readfromcache; + map->readfromcache_v1 = map_readfromcache_v1; map->addmap = map_addmap; map->delmapid = map_delmapid; map->zone_db_clear = map_zone_db_clear; diff --git a/src/map/map.h b/src/map/map.h index facf1d921..5c4c6d59d 100644 --- a/src/map/map.h +++ b/src/map/map.h @@ -909,7 +909,10 @@ struct map_data { /* */ int (*getcellp)(struct map_data* m, const struct block_list *bl, int16 x, int16 y, cell_chk cellchk); void (*setcell) (int16 m, int16 x, int16 y, cell_t cell, bool flag); - char *cellPos; + struct { + uint8 *data; + int len; + } cell_buf; /* ShowEvent Data Cache */ struct questinfo *qi_data; @@ -1064,20 +1067,20 @@ struct charid2nick { struct charid_request* requests;// requests of notification on this nick }; -// This is the main header found at the very beginning of the map cache -struct map_cache_main_header { - uint32 file_size; - uint16 map_count; -}; - -// This is the header appended before every compressed map cells info in the map cache -struct map_cache_map_info { - char name[MAP_NAME_LENGTH]; +// New mcache file format header +#if !defined(sun) && (!defined(__NETBSD__) || __NetBSD_Version__ >= 600000000) // NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute +#pragma pack(push, 1) +#endif // not NetBSD < 6 / Solaris +struct map_cache_header { + int16 version; + uint8 md5_checksum[16]; int16 xs; int16 ys; int32 len; -}; - +} __attribute__((packed)); +#if !defined(sun) && (!defined(__NETBSD__) || __NetBSD_Version__ >= 600000000) // NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute +#pragma pack(pop) +#endif // not NetBSD < 6 / Solaris /*===================================== * Interface : map.h @@ -1167,7 +1170,6 @@ END_ZEROED_BLOCK; struct map_data *list; /* [Ind/Hercules] */ struct eri *iterator_ers; - char *cache_buffer; // Has the uncompressed gat data of all maps, so just one allocation has to be made /* */ struct eri *flooritem_ers; /* */ @@ -1317,8 +1319,8 @@ END_ZEROED_BLOCK; void (*iwall_nextxy) (int16 x, int16 y, int8 dir, int pos, int16 *x1, int16 *y1); struct DBData (*create_map_data_other_server) (union DBKey key, va_list args); int (*eraseallipport_sub) (union DBKey key, struct DBData *data, va_list va); - char* (*init_mapcache) (FILE *fp); - int (*readfromcache) (struct map_data *m, char *buffer); + bool (*readfromcache) (struct map_data *m); + bool (*readfromcache_v1) (FILE *fp, struct map_data *m, unsigned int file_size); int (*addmap) (const char *mapname); void (*delmapid) (int id); void (*zone_db_clear) (void); -- cgit v1.2.3-70-g09d2