/** * This file is part of Hercules. * http://herc.ws - http://github.com/HerculesWS/Hercules * * Copyright (C) 2012-2015 Hercules Dev Team * Copyright (C) Athena Dev Teams * * 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 . */ #define HERCULES_CORE #include "common/cbasetypes.h" #include "common/core.h" #include "common/grfio.h" #include "common/memmgr.h" #include "common/mmo.h" #include "common/showmsg.h" #include "common/strlib.h" #include "common/utils.h" #include #include #ifndef _WIN32 #include #endif #define NO_WATER 1000000 char *grf_list_file; char *map_list_file; char *map_cache_file; int rebuild = 0; FILE *map_cache_fp; unsigned long file_size; // Used internally, this structure contains the physical map cells struct map_data { int16 xs; int16 ys; unsigned char *cells; }; // This is the main header found at the very beginning of the file struct main_header { uint32 file_size; uint16 map_count; } header; // This is the header appended before every compressed map cells info struct map_info { char name[MAP_NAME_LENGTH]; int16 xs; int16 ys; int32 len; }; /************************************* * Big-endian compatibility functions * * Moved to utils.h * *************************************/ // Reads a map from GRF's GAT and RSW files int read_map(char *name, struct map_data *m) { char filename[256]; unsigned char *gat, *rsw; int water_height; size_t xy, off, num_cells; // Open map GAT sprintf(filename,"data\\%s.gat", name); gat = (unsigned char *)grfio_read(filename); if (gat == NULL) return 0; // Open map RSW sprintf(filename,"data\\%s.rsw", name); rsw = (unsigned char *)grfio_read(filename); // Read water height if (rsw) { water_height = (int)GetFloat(rsw+166); aFree(rsw); } else water_height = NO_WATER; // Read map size and allocate needed memory m->xs = (int16)GetULong(gat+6); m->ys = (int16)GetULong(gat+10); if (m->xs <= 0 || m->ys <= 0) { aFree(gat); return 0; } num_cells = (size_t)m->xs*(size_t)m->ys; m->cells = (unsigned char *)aMalloc(num_cells); // Set cell properties off = 14; for (xy = 0; xy < num_cells; xy++) { // Height of the bottom-left corner float height = GetFloat(gat + off); // Type of cell uint32 type = GetULong(gat + off + 16); off += 20; if (type == 0 && water_height != NO_WATER && height > water_height) type = 3; // Cell is 0 (walkable) but under water level, set to 3 (walkable water) m->cells[xy] = (unsigned char)type; } aFree(gat); return 1; } /** * Adds a map to the cache. * * @param name The map name. * @param m Map data to cache. * @retval true if the map was successfully added to the cache. */ bool cache_map(char *name, struct map_data *m) { struct map_info info; unsigned long len; unsigned char *write_buf; // Create an output buffer twice as big as the uncompressed map... this way we're sure it fits len = (unsigned long)m->xs*(unsigned long)m->ys*2; write_buf = (unsigned char *)aMalloc(len); // Compress the cells and get the compressed length encode_zip(write_buf, &len, m->cells, m->xs*m->ys); // Fill the map header safestrncpy(info.name, name, MAP_NAME_LENGTH); if (strlen(name) > MAP_NAME_LENGTH) // It does not hurt to warn that there are maps with name longer than allowed. ShowWarning("Map name '%s' (length %"PRIuS") is too long. Truncating to '%s' (length %d).\n", name, strlen(name), info.name, MAP_NAME_LENGTH); info.xs = MakeShortLE(m->xs); info.ys = MakeShortLE(m->ys); info.len = MakeLongLE((uint32)len); // Append map header then compressed cells at the end of the file if (fseek(map_cache_fp, header.file_size, SEEK_SET) != 0) { aFree(write_buf); aFree(m->cells); return false; } fwrite(&info, sizeof(struct map_info), 1, map_cache_fp); fwrite(write_buf, 1, len, map_cache_fp); header.file_size += sizeof(struct map_info) + len; header.map_count++; aFree(write_buf); aFree(m->cells); return true; } /** * Checks whether a map is already is the cache. * * @param name The map name. * @retval true if the map is already cached. */ bool find_map(char *name) { int i; struct map_info info; if (fseek(map_cache_fp, sizeof(struct main_header), SEEK_SET) != 0) return false; for (i = 0; i < header.map_count; i++) { if (fread(&info, sizeof(info), 1, map_cache_fp) != 1) printf("An error as occured in fread while reading map_cache\n"); if (strcmp(name, info.name) == 0) // Map found return true; // Map not found, jump to the beginning of the next map info header if (fseek(map_cache_fp, GetLong((unsigned char *)&(info.len)), SEEK_CUR) != 0) return false; } return false; } // Cuts the extension from a map name char *remove_extension(char *mapname) { char *ptr, *ptr2; ptr = strchr(mapname, '.'); if (ptr) { //Check and remove extension. while (ptr[1] && (ptr2 = strchr(ptr+1, '.')) != NULL) ptr = ptr2; //Skip to the last dot. if (strcmp(ptr,".gat") == 0) *ptr = '\0'; //Remove extension. } return mapname; } /** * --grf-list handler * * Overrides the default grf list filename. * @see cmdline->exec */ static CMDLINEARG(grflist) { aFree(grf_list_file); grf_list_file = aStrdup(params); return true; } /** * --map-list handler * * Overrides the default map list filename. * @see cmdline->exec */ static CMDLINEARG(maplist) { aFree(map_list_file); map_list_file = aStrdup(params); return true; } /** * --map-cache handler * * Overrides the default map cache filename. * @see cmdline->exec */ static CMDLINEARG(mapcache) { aFree(map_cache_file); map_cache_file = aStrdup(params); return true; } /** * --rebuild handler * * Forces a rebuild of the mapcache, rather than only adding missing maps. * @see cmdline->exec */ static CMDLINEARG(rebuild) { rebuild = 1; return true; } /** * Defines the local command line arguments */ void cmdline_args_init_local(void) { CMDLINEARG_DEF2(grf-list, grflist, "Alternative grf list file", CMDLINE_OPT_NORMAL|CMDLINE_OPT_PARAM); CMDLINEARG_DEF2(map-list, maplist, "Alternative map list file", CMDLINE_OPT_NORMAL|CMDLINE_OPT_PARAM); CMDLINEARG_DEF2(map-cache, mapcache, "Alternative map cache file", CMDLINE_OPT_NORMAL|CMDLINE_OPT_PARAM); CMDLINEARG_DEF2(rebuild, rebuild, "Forces a rebuild of the map cache, rather than only adding missing maps", CMDLINE_OPT_NORMAL); } int do_init(int argc, char** argv) { FILE *list; char line[1024]; struct map_data map; char name[MAP_NAME_LENGTH_EXT]; grf_list_file = aStrdup("conf/grf-files.txt"); map_list_file = aStrdup("db/map_index.txt"); /* setup pre-defined, #define-dependant */ map_cache_file = aStrdup("db/"DBPATH"map_cache.dat"); cmdline->exec(argc, argv, CMDLINE_OPT_PREINIT); cmdline->exec(argc, argv, CMDLINE_OPT_NORMAL); ShowStatus("Initializing grfio with %s\n", grf_list_file); grfio_init(grf_list_file); // Attempt to open the map cache file and force rebuild if not found ShowStatus("Opening map cache: %s\n", map_cache_file); if(!rebuild) { map_cache_fp = fopen(map_cache_file, "rb"); if(map_cache_fp == NULL) { ShowNotice("Existing map cache not found, forcing rebuild mode\n"); rebuild = 1; } else fclose(map_cache_fp); } if(rebuild) map_cache_fp = fopen(map_cache_file, "w+b"); else map_cache_fp = fopen(map_cache_file, "r+b"); if(map_cache_fp == NULL) { ShowError("Failure when opening map cache file %s\n", map_cache_file); exit(EXIT_FAILURE); } // Open the map list ShowStatus("Opening map list: %s\n", map_list_file); list = fopen(map_list_file, "r"); if(list == NULL) { ShowError("Failure when opening maps list file %s\n", map_list_file); exit(EXIT_FAILURE); } // Initialize the main header if(rebuild) { header.file_size = sizeof(struct main_header); header.map_count = 0; } else { if(fread(&header, sizeof(struct main_header), 1, map_cache_fp) != 1){ printf("An error as occured while reading map_cache_fp \n"); } header.file_size = GetULong((unsigned char *)&(header.file_size)); header.map_count = GetUShort((unsigned char *)&(header.map_count)); } // Read and process the map list while(fgets(line, sizeof(line), list)) { if(line[0] == '/' && line[1] == '/') continue; if(sscanf(line, "%15s", name) < 1) continue; if(strcmp("map:", name) == 0 && sscanf(line, "%*s %15s", name) < 1) continue; name[MAP_NAME_LENGTH_EXT-1] = '\0'; remove_extension(name); if (find_map(name)) { ShowInfo("Map '"CL_WHITE"%s"CL_RESET"' already in cache.\n", name); } else if(!read_map(name, &map)) { ShowError("Map '"CL_WHITE"%s"CL_RESET"' not found!\n", name); } else if (!cache_map(name, &map)) { ShowError("Map '"CL_WHITE"%s"CL_RESET"' failed to cache (write error).\n", name); } else { ShowInfo("Map '"CL_WHITE"%s"CL_RESET"' successfully cached.\n", name); } } ShowStatus("Closing map list: %s\n", map_list_file); fclose(list); // Write the main header and close the map cache ShowStatus("Closing map cache: %s\n", map_cache_file); fseek(map_cache_fp, 0, SEEK_SET); fwrite(&header, sizeof(struct main_header), 1, map_cache_fp); fclose(map_cache_fp); ShowStatus("Finalizing grfio\n"); grfio_final(); ShowInfo("%d maps now in cache\n", header.map_count); aFree(grf_list_file); aFree(map_list_file); aFree(map_cache_file); return 0; } int do_final(void) { return EXIT_SUCCESS; }