/*
img_mip.c - hl1 and q1 image mips
Copyright (C) 2007 Uncle Mike

This program 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.
*/

#include "imagelib.h"
#include "xash3d_mathlib.h"
#include "wadfile.h"
#include "studio.h"
#include "sprite.h"
#include "qfont.h"
#include "swaplib.h"

le_struct_begin( charinfo_swap )
	le_struct_field( charinfo, startoffset )
	le_struct_field( charinfo, charwidth )
le_struct_end();

le_struct_begin( qfont_swap )
	le_struct_field( qfont_t, width )
	le_struct_field( qfont_t, height )
	le_struct_field( qfont_t, rowcount )
	le_struct_field( qfont_t, rowheight )
	le_struct_array_child( qfont_t, fontinfo, charinfo_swap, NUM_GLYPHS )
le_struct_end();

le_struct_begin( lmp_swap )
	le_struct_field( lmp_t, width )
	le_struct_field( lmp_t, height )
le_struct_end();

le_struct_begin( mip_swap )
	le_struct_field( mip_t, width )
	le_struct_field( mip_t, height )
	le_struct_array( mip_t, offsets, 4 )
le_struct_end();

le_struct_begin( dwadinfo_swap )
	le_struct_field( dwadinfo_t, ident )
	le_struct_field( dwadinfo_t, numlumps )
	le_struct_field( dwadinfo_t, infotableofs )
le_struct_end();

le_struct_begin( dlumpinfo_swap )
	le_struct_field( dlumpinfo_t, filepos )
	le_struct_field( dlumpinfo_t, disksize )
	le_struct_field( dlumpinfo_t, size )
le_struct_end();

/*
============
Image_LoadPAL
============
*/
qboolean Image_LoadPAL( const char *name, const byte *buffer, fs_offset_t filesize )
{
	int	rendermode = LUMP_NORMAL;
	byte pal[768];

	if( filesize > sizeof( pal ))
	{
		Con_DPrintf( S_ERROR "%s: (%s) have invalid size (%li should be less or equal than %zu)\n", __func__, name, (long)filesize, sizeof( pal ));
		return false;
	}
	else if( filesize < sizeof( pal ) && buffer != NULL )
	{
		// palette might be truncated, fill it with zeros
		memset( pal, 0, sizeof( pal ));
		memcpy( pal, buffer, filesize );
		buffer = pal;
	}

	if( name[0] == '#' )
	{
		// using palette name as rendermode
		if( Q_stristr( name, "normal" ))
			rendermode = LUMP_NORMAL;
		else if( Q_stristr( name, "masked" ))
			rendermode = LUMP_MASKED;
		else if( Q_stristr( name, "gradient" ))
			rendermode = LUMP_GRADIENT;
		else if( Q_stristr( name, "texgamma" ))
			rendermode = LUMP_TEXGAMMA;
		else if( Q_stristr( name, "valve" ))
		{
			rendermode = LUMP_HALFLIFE;
			buffer = NULL; // force to get HL palette
		}
		else if( Q_stristr( name, "id" ))
		{
			rendermode = LUMP_QUAKE1;
			buffer = NULL; // force to get Q1 palette
		}
	}

	// NOTE: image.d_currentpal not cleared with Image_Reset()
	// and stay valid any time before new call of Image_SetPalette
	Image_GetPaletteLMP( buffer, rendermode );
	Image_CopyPalette32bit();

	image.rgba = NULL;	// only palette, not real image
	image.size = 1024;	// expanded palette
	image.width = image.height = 0;
	image.depth = 1;

	return true;
}

/*
============
Image_LoadFNT
============
*/
qboolean Image_LoadFNT( const char *name, const byte *buffer, fs_offset_t filesize )
{
	qfont_t		font;
	const byte	*pal, *fin;
	size_t		size;
	int		numcolors;

	if( image.hint == IL_HINT_Q1 )
		return false; // Quake1 doesn't have qfonts

	if( filesize < sizeof( font ))
		return false;

	memcpy( &font, buffer, sizeof( font ));
	le_struct_swap( qfont_swap, &font );

	// last sixty four bytes - what the hell ????
	size = sizeof( qfont_t ) - 4 + ( font.height * font.width * QCHAR_WIDTH ) + sizeof( short ) + 768 + 64;

	if( size != filesize )
	{
		// oldstyle font: "conchars" or "creditsfont"
		image.width = 256;		// hardcoded
		image.height = font.height;
	}
	else
	{
		// Half-Life 1.1.0.0 font style (qfont_t)
		image.width = font.width * QCHAR_WIDTH;
		image.height = font.height;
	}

	if( !Image_LumpValidSize( name ))
		return false;

	fin = buffer + sizeof( font ) - 4;
	pal = fin + (image.width * image.height);
	numcolors = pal[0] | (pal[1] << 8);
	pal += sizeof( short );

	if( numcolors == 768 || numcolors == 256 )
	{
		// g-cont. make sure that is didn't hit anything
		Image_GetPaletteLMP( pal, LUMP_MASKED );
		image.flags |= IMAGE_HAS_ALPHA; // fonts always have transparency
	}
	else
	{
		return false;
	}

	image.type = PF_INDEXED_32;	// 32-bit palette
	image.depth = 1;

	return Image_AddIndexedImageToPack( fin, image.width, image.height );
}

/*
======================
Image_SetMDLPointer

Transfer buffer pointer before Image_LoadMDL
======================
*/
static void *g_mdltexdata;
void Image_SetMDLPointer( byte *p )
{
	g_mdltexdata = p;
}

/*
============
Image_LoadMDL
============
*/
qboolean Image_LoadMDL( const char *name, const byte *buffer, fs_offset_t filesize )
{
	byte		*fin;
	size_t		pixels;
	mstudiotexture_t	*pin;
	int		flags;

	pin = (mstudiotexture_t *)buffer;
	flags = pin->flags;

	image.width = pin->width;
	image.height = pin->height;
	pixels = image.width * image.height;
	fin = (byte *)g_mdltexdata;
	ASSERT(fin);
	g_mdltexdata = NULL;

	if( !Image_ValidSize( name ))
		return false;

	if( image.hint == IL_HINT_HL )
	{
		if( filesize < ( sizeof( *pin ) + pixels + 768 ))
			return false;

		if( FBitSet( flags, STUDIO_NF_MASKED ))
		{
			byte	*pal = fin + pixels;

			Image_GetPaletteLMP( pal, LUMP_MASKED );
			image.flags |= IMAGE_HAS_ALPHA|IMAGE_ONEBIT_ALPHA;
		}
		else Image_GetPaletteLMP( fin + pixels, LUMP_TEXGAMMA );
	}
	else
	{
		return false; // unknown or unsupported mode rejected
	}

	image.type = PF_INDEXED_32;	// 32-bit palete
	image.depth = 1;

	return Image_AddIndexedImageToPack( fin, image.width, image.height );
}

/*
============
Image_LoadSPR
============
*/
qboolean Image_LoadSPR( const char *name, const byte *buffer, fs_offset_t filesize )
{
	dspriteframe_t	pin;	// identical for q1\hl sprites
	qboolean		truecolor = false;
	byte *fin;

	if( image.hint == IL_HINT_HL )
	{
		if( !image.d_currentpal )
			return false;
	}
	else if( image.hint == IL_HINT_Q1 )
	{
		Image_GetPaletteQ1();
	}
	else
	{
		// unknown mode rejected
		return false;
	}

	memcpy( &pin, buffer, sizeof( dspriteframe_t ));
	image.width = pin.width;
	image.height = pin.height;

	if( filesize < image.width * image.height )
		return false;

	if( filesize == ( image.width * image.height * 4 ))
		truecolor = true;

	// sorry, can't validate palette rendermode
	if( !Image_LumpValidSize( name )) return false;
	image.type = (truecolor) ? PF_RGBA_32 : PF_INDEXED_32;	// 32-bit palete
	image.depth = 1;

	// detect alpha-channel by palette type
	switch( image.d_rendermode )
	{
	case LUMP_MASKED:
		SetBits( image.flags, IMAGE_ONEBIT_ALPHA );
		// intentionally fallthrough
	case LUMP_GRADIENT:
		SetBits( image.flags, IMAGE_HAS_ALPHA );
		break;
	case LUMP_QUAKE1:
		SetBits( image.flags, IMAGE_HAS_ALPHA );
		break;
	}

	fin =  (byte *)(buffer + sizeof(dspriteframe_t));

	if( truecolor )
	{
		// spr32 support
		image.size = image.width * image.height * 4;
		image.rgba = Mem_Malloc( host.imagepool, image.size );
		memcpy( image.rgba, fin, image.size );
		SetBits( image.flags, IMAGE_HAS_COLOR ); // Color. True Color!
		return true;
	}
	return Image_AddIndexedImageToPack( fin, image.width, image.height );
}

/*
============
Image_LoadLMP
============
*/
qboolean Image_LoadLMP( const char *name, const byte *buffer, fs_offset_t filesize )
{
	lmp_t	lmp;
	byte	*fin, *pal;
	int	rendermode;
	int	i, pixels;

	if( filesize < sizeof( lmp ))
		return false;

	// valve software trick (particle palette)
	if( Q_stristr( name, "palette.lmp" ))
		return Image_LoadPAL( name, buffer, filesize );

	// id software trick (image without header)
	if( Q_stristr( name, "conchars" ) && filesize == 16384 )
	{
		image.width = image.height = 128;
		rendermode = LUMP_QUAKE1;
		filesize += sizeof( lmp );
		fin = (byte *)buffer;

		// need to remap transparent color from first to last entry
		for( i = 0; i < 16384; i++ ) if( !fin[i] ) fin[i] = 0xFF;
	}
	else
	{
		fin = (byte *)buffer;
		memcpy( &lmp, fin, sizeof( lmp ));
		le_struct_swap( lmp_swap, &lmp );
		image.width = lmp.width;
		image.height = lmp.height;
		rendermode = LUMP_NORMAL;
		fin += sizeof( lmp );
	}

	pixels = image.width * image.height;

	if( filesize < sizeof( lmp ) + pixels )
		return false;

	if( !Image_ValidSize( name ))
		return false;

	if( image.hint != IL_HINT_Q1 && filesize > (int)sizeof(lmp) + pixels )
	{
		int	numcolors;

		// HACKHACK: console background image shouldn't be transparent
		if( !Q_stristr( name, "conback" ))
		{
			for( i = 0; i < pixels; i++ )
			{
				if( fin[i] == 255 )
				{
					image.flags |= IMAGE_HAS_ALPHA;
					rendermode = LUMP_MASKED;
					break;
				}
			}
		}
		pal = fin + pixels;
		numcolors = pal[0] | (pal[1] << 8);
		if( numcolors != 256 ) pal = NULL; // corrupted lump ?
		else pal += sizeof( short );
	}
	else if( image.hint != IL_HINT_HL )
	{
		image.flags |= IMAGE_HAS_ALPHA;
		rendermode = LUMP_QUAKE1;
		pal = NULL;
	}
	else
	{
		// unknown mode rejected
		return false;
	}

	Image_GetPaletteLMP( pal, rendermode );
	image.type = PF_INDEXED_32; // 32-bit palete
	image.depth = 1;

	return Image_AddIndexedImageToPack( fin, image.width, image.height );
}

static int Image_FindBestBlack( const uint *pal )
{
	int min_color = 32;
	int best_black = -1;

	for( int i = 0; i < 256; i++ )
	{
		int color = pal[i] & 0xFFFFFF;

		if( color < min_color )
		{
			min_color = color;
			best_black = i;
		}
	}

	return best_black;
}

/*
=============
Image_LoadMIP
=============
*/
qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesize )
{
	mip_t	mip;
	qboolean	hl_texture;
	byte	*fin, *pal;
	int	ofs[4], rendermode;
	int	i, pixels, numcolors;
	uint	reflectivity[3] = { 0, 0, 0 };

	if( filesize < sizeof( mip ))
		return false;

	memcpy( &mip, buffer, sizeof( mip ));
	if( !Image_CheckFlag( IL_HOST_ENDIAN ))
		le_struct_swap( mip_swap, &mip );
	image.width = mip.width;
	image.height = mip.height;

	if( !Image_ValidSize( name ))
		return false;

	memcpy( ofs, mip.offsets, sizeof( ofs ));
	pixels = image.width * image.height;

	if( image.hint != IL_HINT_Q1 && filesize >= (int)sizeof(mip) + ((pixels * 85)>>6) + sizeof(short) + 768)
	{
		// half-life 1.0.0.1 mip version with palette
		fin = (byte *)buffer + mip.offsets[0];
		pal = (byte *)buffer + mip.offsets[0] + (((image.width * image.height) * 85)>>6);
		numcolors = pal[0] | (pal[1] << 8);
		if( numcolors != 256 ) pal = NULL; // corrupted mip ?
		else pal += sizeof( short ); // skip colorsize

		hl_texture = true;

		// setup rendermode
		if( Q_strrchr( name, '{' ))
		{
			// NOTE: decals with 'blue base' can be interpret as colored decals
			if( !Image_CheckFlag( IL_LOAD_DECAL ) || ( pal && pal[765] == 0 && pal[766] == 0 && pal[767] == 255 ))
			{
				SetBits( image.flags, IMAGE_ONEBIT_ALPHA );
				rendermode = LUMP_MASKED;
			}
			else
			{
				// classic gradient decals
				SetBits( image.flags, IMAGE_COLORINDEX );
				rendermode = LUMP_GRADIENT;
			}

			SetBits( image.flags, IMAGE_HAS_ALPHA );
		}
		else
		{
			// NOTE: we can have luma-pixels if quake1 texture
			// converted into the hl texture but palette leave unchanged
			// this is a good reason for using fullbright pixels
			const int pal_type = Image_ComparePalette( pal );

			if( pal_type == PAL_QUAKE1 )
			{
				// check for luma pixels (but ignore liquid textures because they have no lightmap)
				if( mip.name[0] != '*' && mip.name[0] != '!' )
				{
					for( i = 0; i < image.width * image.height; i++ )
					{
						if( fin[i] > 224 )
						{
							SetBits( image.flags, IMAGE_HAS_LUMA );
							image.black_pixel = 0;
							break;
						}
					}
				}

				SetBits( image.flags, IMAGE_QUAKEPAL );

				// if texture was converted from quake to half-life with no palette changes
				// then applying texgamma might make it too dark or even outright broken
				rendermode = LUMP_NORMAL;
			}
			else
			{
				// half-life mips need texgamma applied
				rendermode = LUMP_TEXGAMMA;

				// three checks here: check if we can load luma, check the texture name and, finally,
				// validate that palette isn't NULL
				if( Image_CheckFlag( IL_ALLOW_WAD3_LUMA )
					&& ( mip.name[0] == '~' || ( mip.name[0] == '+' && isdigit((byte)mip.name[1] ) && mip.name[2] == '~' ))
					&& pal != NULL )
				{
					SetBits( image.flags, IMAGE_HAS_LUMA );
				}
			}
		}

		Image_GetPaletteLMP( pal, rendermode );
		image.d_currentpal[255] &= 0xFFFFFF;

		if( rendermode == LUMP_TEXGAMMA && FBitSet( image.flags, IMAGE_HAS_LUMA ))
		{
			// thanks to Unkle Mike for an idea of looking best black pixel
			image.black_pixel = Image_FindBestBlack( image.d_currentpal );

			// failed to find good pixel, refuse to load luma
			if( image.black_pixel == -1 )
				ClearBits( image.flags, IMAGE_HAS_LUMA );
		}
	}
	else if( image.hint != IL_HINT_HL && filesize >= (int)sizeof(mip) + ((pixels * 85)>>6))
	{
		// quake1 1.01 mip version without palette
		fin = (byte *)buffer + mip.offsets[0];
		pal = NULL; // clear palette
		rendermode = LUMP_NORMAL;

		hl_texture = false;

		// check for luma and alpha pixels
		// don't apply luma to water surfaces because they have no lightmap
		if( !image.custom_palette && mip.name[0] != '*' && mip.name[0] != '!' )
		{
			for( i = 0; i < image.width * image.height; i++ )
			{
				if( fin[i] > 224 && fin[i] != 255 )
				{
					SetBits( image.flags, IMAGE_HAS_LUMA );
					image.black_pixel = 0;
					break;
				}
			}
		}

		// Arcane Dimensions has the transparent textures
		if( Q_strrchr( name, '{' ))
		{
			for( i = 0; i < image.width * image.height; i++ )
			{
				if( fin[i] == 255 )
				{
					// don't set ONEBIT_ALPHA flag for some reasons
					image.flags |= IMAGE_HAS_ALPHA;
					break;
				}
			}
		}

		SetBits( image.flags, IMAGE_QUAKEPAL );
		Image_GetPaletteQ1();
	}
	else
	{
		return false; // unknown or unsupported mode rejected
	}

	// check for quake-sky texture
	if( !Q_strncmp( mip.name, "sky", 3 ) && image.width == ( image.height * 2 ))
	{
		// g-cont: we need to run additional checks for palette type and colors ?
		image.flags |= IMAGE_QUAKESKY;
	}

	// check for half-life water texture
	if( pal != NULL )
	{
		if( hl_texture && ( mip.name[0] == '!' || !Q_strnicmp( mip.name, "water", 5 )))
		{
			// grab the fog color
			image.fogParams[0] = pal[3*3+0];
			image.fogParams[1] = pal[3*3+1];
			image.fogParams[2] = pal[3*3+2];

			// grab the fog density
			image.fogParams[3] = pal[4*3+0];
		}
		else if( hl_texture && ( rendermode == LUMP_GRADIENT ))
		{
			// grab the decal color
			image.fogParams[0] = pal[255*3+0];
			image.fogParams[1] = pal[255*3+1];
			image.fogParams[2] = pal[255*3+2];

			// calc the decal reflectivity
			image.fogParams[3] = VectorAvg( image.fogParams );
		}
		else
		{
			// calc texture reflectivity
			for( i = 0; i < 256; i++ )
			{
				reflectivity[0] += pal[i*3+0];
				reflectivity[1] += pal[i*3+1];
				reflectivity[2] += pal[i*3+2];
			}

			VectorDivide( reflectivity, 256, image.fogParams );
		}
	}

	image.type = PF_INDEXED_32;	// 32-bit palete
	image.depth = 1;

	return Image_AddIndexedImageToPack( fin, image.width, image.height );
}

/*
============
Image_LoadWAD
============
*/
qboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesize )
{
	dwadinfo_t whdr;
	const unsigned char *mipdata;
	int i, j;

	if( !buffer || filesize < sizeof( dwadinfo_t ))
		return false;

	memcpy( &whdr, buffer, sizeof( whdr ));
	le_struct_swap( dwadinfo_swap, &whdr );
	if( whdr.numlumps <= 0 || whdr.infotableofs <= 0 || whdr.infotableofs >= (int)filesize )
		return false;

	for( i = 0; i < whdr.numlumps; ++i )
	{
		const unsigned char *pixels, *palette, *use_palette;
		unsigned char grad_palette[256 * 3];
		dlumpinfo_t lump;
		int mip_size;
		mip_t mip;
		uint32_t      width, height, offset0;
		uint32_t      m0size, m1size, m2size, m3size;
		qboolean      alpha_mode = false;
		unsigned char frontR = 0, frontG = 0, frontB = 0;
		float t;
		byte  idx;

		memcpy( &lump, buffer + whdr.infotableofs + i * sizeof( dlumpinfo_t ), sizeof( lump ));
		le_struct_swap( dlumpinfo_swap, &lump );

		if( lump.type != TYP_MIPTEX && lump.type != TYP_PALETTE )
			continue;

		// get lump data and validate
		mipdata = (const unsigned char *)buffer + lump.filepos;
		mip_size = lump.disksize;
		if( lump.filepos < 0 || lump.filepos + mip_size > (int)filesize )
			continue;

		memcpy( &mip, mipdata, sizeof( mip ));
		le_struct_swap( mip_swap, &mip );
		width = mip.width;
		height = mip.height;

		if( width <= 0 || height <= 0 || width > 256 || height > 256 )
			continue;

		offset0 = mip.offsets[0];
		if( offset0 == 0 || offset0 + width * height > (uint32_t)mip_size )
			continue;

		pixels = mipdata + offset0;
		m0size = width * height;
		m1size = m0size / 4;
		m2size = m0size / 16;
		m3size = m0size / 64;
		palette = mipdata + 0x28 + m0size + m1size + m2size + m3size + 2;
		use_palette = palette;

		// handle gradient palette
		if( lump.type == TYP_PALETTE )
		{
			// gradient palette
			const unsigned char *frontColorPtr = palette + 255 * 3;
			frontR = frontColorPtr[0];
			frontG = frontColorPtr[1];
			frontB = frontColorPtr[2];
			for( j = 0; j < 256; ++j )
			{
				t = j / 255.0f;
				grad_palette[j * 3 + 0] = (unsigned char)( frontR * t );
				grad_palette[j * 3 + 1] = (unsigned char)( frontG * t );
				grad_palette[j * 3 + 2] = (unsigned char)( frontB * t );
			}
			use_palette = grad_palette;
			alpha_mode = true; // gradient
		}

		// prepare image structure
		Image_Reset();
		image.width = width;
		image.height = height;
		image.type = PF_RGBA_32;
		image.flags = IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA;
		image.size = width * height * 4;
		image.rgba = Mem_Malloc( host.imagepool, image.size );
		image.palette = NULL;

		// convert indexed pixels to RGBA
		for( j = 0; j < (int)( width * height ); ++j )
		{
			idx = pixels[j];
			image.rgba[j * 4 + 0] = use_palette[idx * 3 + 0];
			image.rgba[j * 4 + 1] = use_palette[idx * 3 + 1];
			image.rgba[j * 4 + 2] = use_palette[idx * 3 + 2];
			if( alpha_mode )
				image.rgba[j * 4 + 3] = idx; // soft transparency
			else
				image.rgba[j * 4 + 3] = ( idx == 255 ) ? 0 : 255; // classic
		}
		return true;
	}
	return false;
}

/*
============
Image_SaveWAD
============
*/
qboolean Image_SaveWAD( const char *name, rgbdata_t *pix )
{
	int         m0size, m1size, m2size, m3size;
	byte        *mip1_data = NULL, *mip2_data = NULL, *mip3_data = NULL;
	const byte  *palette;
	byte        grad_palette[256 * 3];
	file_t      *f;
	dwadinfo_t  header;
	mip_t       miptex;
	long        infotableofs;
	dlumpinfo_t lump;
	fs_offset_t pad;
	int         i;
	qboolean    result = false;
	int         lump_type = ( pix->flags & IMAGE_GRADIENT_DECAL ) ? TYP_PALETTE : TYP_MIPTEX;
	short       palette_size = 256;
	int         infotableofs32 = 0;

	if( !pix || !pix->buffer )
		return false;

	palette = pix->palette ? pix->palette : (const byte *)image.palette;

	m0size = pix->width * pix->height;
	m1size = m0size / 4;
	m2size = m0size / 16;
	m3size = m0size / 64;

	mip1_data = (byte *)Mem_Malloc( host.imagepool, m1size );
	mip2_data = (byte *)Mem_Malloc( host.imagepool, m2size );
	mip3_data = (byte *)Mem_Malloc( host.imagepool, m3size );
	if( !mip1_data || !mip2_data || !mip3_data )
		goto cleanup;

	Image_GenerateMipmaps( pix->buffer, pix->width, pix->height, mip1_data, mip2_data, mip3_data );

	memset( &miptex, 0, sizeof( mip_t ));
	Q_strncpy( miptex.name, "{LOGO", sizeof( miptex.name ));
	miptex.width = pix->width;
	miptex.height = pix->height;
	miptex.offsets[0] = sizeof( mip_t );
	miptex.offsets[1] = miptex.offsets[0] + m0size;
	miptex.offsets[2] = miptex.offsets[1] + m1size;
	miptex.offsets[3] = miptex.offsets[2] + m2size;

	f = FS_Open( name, "wb", false );
	if( !f )
		goto cleanup;

	memset( &header, 0, sizeof( header ));
	header.ident = IDWAD3HEADER;
	header.numlumps = 1;

	le_struct_swap( dwadinfo_swap, &header );
	FS_Write( f, &header, sizeof( header ));
	le_struct_swap( mip_swap, &miptex );
	FS_Write( f, &miptex, sizeof( mip_t ));
	le_struct_swap( mip_swap, &miptex );
	FS_Write( f, pix->buffer, m0size );
	FS_Write( f, mip1_data, m1size );
	FS_Write( f, mip2_data, m2size );
	FS_Write( f, mip3_data, m3size );
	palette_size = LittleShort( palette_size );
	FS_Write( f, &palette_size, sizeof( short ));

	if( lump_type == TYP_PALETTE )
	{
		const byte *frontColorPtr = palette + 255 * 3;
		for( i = 0; i < 256; ++i )
		{
			float t = i / 255.0f;
			grad_palette[i * 3 + 0] = (byte)( frontColorPtr[0] * t );
			grad_palette[i * 3 + 1] = (byte)( frontColorPtr[1] * t );
			grad_palette[i * 3 + 2] = (byte)( frontColorPtr[2] * t );
		}
		FS_Write( f, grad_palette, 256 * 3 );
	}
	else
	{
		FS_Write( f, palette, 256 * 3 );
	}

	// padding up to a multiple of 4
	pad = (( FS_Tell( f ) + 3 ) & ~3 ) - FS_Tell( f );
	for( i = 0; i < pad; ++i )
		FS_Write( f, (const void *)&(char){0}, 1 );

	infotableofs = FS_Tell( f );
	memset( &lump, 0, sizeof( lump ));
	lump.filepos = sizeof( dwadinfo_t );
	lump.disksize = (int)( miptex.offsets[3] + m3size + sizeof( short ) + 256 * 3 );
	lump.size = lump.disksize;
	lump.type = (char)lump_type;
	lump.attribs = 0;
	Q_strncpy( lump.name, "tempdecal", sizeof( lump.name ));
	le_struct_swap( dlumpinfo_swap, &lump );
	FS_Write( f, &lump, sizeof( lump ));

	FS_Seek( f, offsetof( dwadinfo_t, infotableofs ), SEEK_SET );
	infotableofs32 = LittleLong((int)infotableofs );
	FS_Write( f, &infotableofs32, sizeof( int ));

	FS_Close( f );
	result = true;

cleanup:
	if( mip1_data )
		Mem_Free( mip1_data );
	if( mip2_data )
		Mem_Free( mip2_data );
	if( mip3_data )
		Mem_Free( mip3_data );
	return result;
}
