summaryrefslogblamecommitdiff
path: root/src/tool/mapcache.c
blob: ec735e4e6c108debb6da2051b2207824e211f584 (plain) (tree)



















                                                                        

                     


                              
                          



                           


                   



                   

                        


                     
                
 


                        


                                                                  


                             

  
                                                                  
                    

                         
         
 
                                                                     
                 



                                   

  



                                       
 
                                           

                                            



                                  





























                                                       
                                            
                                                   
                                                   
                               
                                                       










                                                                                                          

 







                                                               
 










                                                                                                      
                                                      
                                                                                                                          
                                                                                                             
                                                                            

                                     
                                           

                                                                         




                                                                   







                                                                
                    

 






                                                
 

                             
 










                                                                                              
         
 
                     




                                     


                                                
                                                                     




                                                           

 






                                           
 



                                        
 











                                           
 

































                                                                                                                                         

 
                                  
 



                                       
 

                                                      
                                                  
                                                             
 

                                                       






















































                                                                                                                                                    
                                     
                                                                                           
                                                  
                                                                                     




                                                                                                         















                                                                     



                              
                 
 
 
                  
 
                            
 
/**
 * 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 <http://www.gnu.org/licenses/>.
 */
#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 <stdio.h>
#include <stdlib.h>
#ifndef _WIN32
#include <unistd.h>
#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;
}