// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2005-2009 by Andrey "entryway" Budko.
// Copyright (C) 2018-2024 by Lactozilla.
// Copyright (C) 2019-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  r_picformats.c
/// \brief Picture generation.

#include "byteptr.h"
#include "dehacked.h"
#include "i_video.h"
#include "r_data.h"
#include "r_patch.h"
#include "r_picformats.h"
#include "r_textures.h"
#include "r_things.h"
#include "r_draw.h"
#include "v_video.h"
#include "z_zone.h"
#include "w_wad.h"

#ifdef HWRENDER
#include "hardware/hw_glob.h"
#endif

#ifdef HAVE_PNG

#ifndef _MSC_VER
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif
#endif

#ifndef _LFS64_LARGEFILE
#define _LFS64_LARGEFILE
#endif

#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 0
#endif

#include "png.h"
#ifndef PNG_READ_SUPPORTED
#undef HAVE_PNG
#endif
#endif

#ifdef PICTURE_PNG_USELOOKUP
static colorlookup_t png_colorlookup;
#endif

/** Converts a picture between two formats.
  *
  * \param informat Input picture format.
  * \param picture Input picture data.
  * \param outformat Output picture format.
  * \param insize Input picture size.
  * \param outsize Output picture size, as a pointer.
  * \param inwidth Input picture width.
  * \param inheight Input picture height.
  * \param inleftoffset Input picture left offset, for patches.
  * \param intopoffset Input picture top offset, for patches.
  * \param flags Input picture flags.
  * \return A pointer to the converted picture.
  * \sa Picture_PatchConvert
  * \sa Picture_FlatConvert
  */
void *Picture_Convert(
	pictureformat_t informat, void *picture, pictureformat_t outformat,
	size_t insize, size_t *outsize,
	INT32 inwidth, INT32 inheight, INT32 inleftoffset, INT32 intopoffset,
	pictureflags_t flags)
{
	if (informat == PICFMT_NONE)
		I_Error("Picture_Convert: input format was PICFMT_NONE!");
	else if (outformat == PICFMT_NONE)
		I_Error("Picture_Convert: output format was PICFMT_NONE!");
	else if (informat == outformat)
		I_Error("Picture_Convert: input and output formats were the same!");

	(void)insize;

	if (Picture_IsPatchFormat(outformat))
		return Picture_PatchConvert(informat, picture, outformat, outsize, inwidth, inheight, inleftoffset, intopoffset, flags);
	else if (Picture_IsFlatFormat(outformat))
		return Picture_FlatConvert(informat, picture, outformat, outsize, inwidth, intopoffset, flags);
	else
		I_Error("Picture_Convert: unsupported input format!");

	return NULL;
}

static void *ReadPixelFunc_Patch(void *picture, pictureformat_t informat, INT32 x, INT32 y, INT32 inwidth, INT32 inheight, pictureflags_t flags)
{
	(void)inwidth;
	(void)inheight;
	return Picture_GetPatchPixel((patch_t*)picture, informat, x, y, flags);
}

static void *ReadPixelFunc_Flat_8bpp(void *picture, pictureformat_t informat, INT32 x, INT32 y, INT32 inwidth, INT32 inheight, pictureflags_t flags)
{
	(void)informat;
	(void)flags;
	(void)inheight;
	return (UINT8 *)picture + ((y * inwidth) + x);
}

static void *ReadPixelFunc_Flat_16bpp(void *picture, pictureformat_t informat, INT32 x, INT32 y, INT32 inwidth, INT32 inheight, pictureflags_t flags)
{
	(void)informat;
	(void)flags;
	(void)inheight;
	return (UINT16 *)picture + ((y * inwidth) + x);
}

static void *ReadPixelFunc_Flat_32bpp(void *picture, pictureformat_t informat, INT32 x, INT32 y, INT32 inwidth, INT32 inheight, pictureflags_t flags)
{
	(void)informat;
	(void)flags;
	(void)inheight;
	return (UINT32 *)picture + ((y * inwidth) + x);
}

static UINT8 GetAlphaFunc_32bpp(void *input, pictureflags_t flags)
{
	(void)flags;
	RGBA_t px = *(RGBA_t *)input;
	return px.s.alpha;
}

static UINT8 GetAlphaFunc_16bpp(void *input, pictureflags_t flags)
{
	(void)flags;
	UINT16 px = *(UINT16 *)input;
	return (px & 0xFF00) >> 8;
}

static UINT8 GetAlphaFunc_8bpp(void *input, pictureflags_t flags)
{
	UINT8 px = *(UINT8 *)input;
	if (px == TRANSPARENTPIXEL && (flags & PICFLAGS_USE_TRANSPARENTPIXEL))
		return 0;
	else
		return 255;
}

// input 32bpp output 32bpp
static void *WritePatchPixel_i32o32(void *ptr, void *input)
{
	RGBA_t px = *(RGBA_t *)input;
	WRITEUINT32(ptr, px.rgba);
	return ptr;
}

// input 16bpp output 32bpp
static void *WritePatchPixel_i16o32(void *ptr, void *input)
{
	RGBA_t px = pMasterPalette[*((UINT16 *)input) & 0xFF];
	WRITEUINT32(ptr, px.rgba);
	return ptr;
}

// input 8bpp output 32bpp
static void *WritePatchPixel_i8o32(void *ptr, void *input)
{
	RGBA_t px = pMasterPalette[*((UINT8 *)input) & 0xFF];
	WRITEUINT32(ptr, px.rgba);
	return ptr;
}

// input 32bpp output 16bpp
static void *WritePatchPixel_i32o16(void *ptr, void *input)
{
	RGBA_t in = *(RGBA_t *)input;
	UINT8 px = NearestColor(in.s.red, in.s.green, in.s.blue);
	WRITEUINT16(ptr, (0xFF00 | px));
	return ptr;
}

// input 16bpp output 16bpp
static void *WritePatchPixel_i16o16(void *ptr, void *input)
{
	WRITEUINT16(ptr, *(UINT16 *)input);
	return ptr;
}

// input 8bpp output 16bpp
static void *WritePatchPixel_i8o16(void *ptr, void *input)
{
	WRITEUINT16(ptr, (0xFF00 | (*(UINT8 *)input)));
	return ptr;
}

// input 32bpp output 8bpp
static void *WritePatchPixel_i32o8(void *ptr, void *input)
{
	RGBA_t in = *(RGBA_t *)input;
	UINT8 px = NearestColor(in.s.red, in.s.green, in.s.blue);
	WRITEUINT8(ptr, px);
	return ptr;
}

// input 16bpp output 8bpp
static void *WritePatchPixel_i16o8(void *ptr, void *input)
{
	UINT16 px = *(UINT16 *)input;
	WRITEUINT8(ptr, (px & 0xFF));
	return ptr;
}

// input 8bpp output 8bpp
static void *WritePatchPixel_i8o8(void *ptr, void *input)
{
	WRITEUINT8(ptr, *(UINT8 *)input);
	return ptr;
}

/** Converts a picture to a patch.
  *
  * \param informat Input picture format.
  * \param picture Input picture data.
  * \param outformat Output picture format.
  * \param outsize Output picture size, as a pointer.
  * \param inwidth Input picture width.
  * \param inheight Input picture height.
  * \param inleftoffset Input picture left offset, for patches.
  * \param intopoffset Input picture top offset, for patches.
  * \param flags Input picture flags.
  * \return A pointer to the converted picture.
  */
void *Picture_PatchConvert(
	pictureformat_t informat, void *picture, pictureformat_t outformat,
	size_t *outsize,
	INT32 inwidth, INT32 inheight, INT32 inleftoffset, INT32 intopoffset,
	pictureflags_t flags)
{
	// Shortcut: If converting a Doom patch into a regular patch, use Patch_Create
	if (informat == PICFMT_DOOMPATCH && outformat == PICFMT_PATCH && flags == 0)
	{
		if (outsize != NULL)
			*outsize = sizeof(patch_t);
		return Patch_CreateFromDoomPatch(picture);
	}

	INT32 outbpp = Picture_FormatBPP(outformat);
	INT32 inbpp = Picture_FormatBPP(informat);
	patch_t *inpatch = NULL;

	if (informat == PICFMT_NONE)
		I_Error("Picture_PatchConvert: input format was PICFMT_NONE!");
	else if (outformat == PICFMT_NONE)
		I_Error("Picture_PatchConvert: output format was PICFMT_NONE!");
	else if (Picture_IsDoomPatchFormat(outformat))
		I_Error("Picture_PatchConvert: cannot convert to Doom patch!");
	else if (informat == outformat)
		I_Error("Picture_PatchConvert: input and output formats were the same!");

	if (inbpp == PICDEPTH_NONE)
		I_Error("Picture_PatchConvert: unknown input bits per pixel!");
	if (outbpp == PICDEPTH_NONE)
		I_Error("Picture_PatchConvert: unknown output bits per pixel!");

	// If it's a patch, we can just figure out the dimensions from the header.
	if (Picture_IsPatchFormat(informat))
	{
		inpatch = (patch_t *)picture;
		if (Picture_IsDoomPatchFormat(informat))
		{
			softwarepatch_t *doompatch = (softwarepatch_t *)picture;
			inwidth = SHORT(doompatch->width);
			inheight = SHORT(doompatch->height);
			inleftoffset = SHORT(doompatch->leftoffset);
			intopoffset = SHORT(doompatch->topoffset);
		}
		else
		{
			inwidth = inpatch->width;
			inheight = inpatch->height;
			inleftoffset = inpatch->leftoffset;
			intopoffset = inpatch->topoffset;
		}
	}

	void *(*readPixelFunc)(void *, pictureformat_t, INT32, INT32, INT32, INT32, pictureflags_t) = NULL;
	UINT8 (*getAlphaFunc)(void *, pictureflags_t) = NULL;
	void *(*writePatchPixel)(void *, void *) = NULL;

	if (Picture_IsPatchFormat(informat))
		readPixelFunc = ReadPixelFunc_Patch;
	else if (Picture_IsFlatFormat(informat))
	{
		switch (informat)
		{
			case PICFMT_FLAT32:
				readPixelFunc = ReadPixelFunc_Flat_32bpp;
				break;
			case PICFMT_FLAT16:
				readPixelFunc = ReadPixelFunc_Flat_16bpp;
				break;
			case PICFMT_FLAT:
				readPixelFunc = ReadPixelFunc_Flat_8bpp;
				break;
			default:
				I_Error("Picture_PatchConvert: unsupported flat input format!");
				break;
		}
	}
	else
		I_Error("Picture_PatchConvert: unsupported input format!");

	if (inbpp == PICDEPTH_32BPP)
		getAlphaFunc = GetAlphaFunc_32bpp;
	else if (inbpp == PICDEPTH_16BPP)
		getAlphaFunc = GetAlphaFunc_16bpp;
	else if (inbpp == PICDEPTH_8BPP)
		getAlphaFunc = GetAlphaFunc_8bpp;

	switch (outformat)
	{
		case PICFMT_PATCH32:
		case PICFMT_DOOMPATCH32:
		{
			if (inbpp == PICDEPTH_32BPP)
				writePatchPixel = WritePatchPixel_i32o32;
			else if (inbpp == PICDEPTH_16BPP)
				writePatchPixel = WritePatchPixel_i16o32;
			else // PICFMT_PATCH
				writePatchPixel = WritePatchPixel_i8o32;
			break;
		}
		case PICFMT_PATCH16:
		case PICFMT_DOOMPATCH16:
			if (inbpp == PICDEPTH_32BPP)
				writePatchPixel = WritePatchPixel_i32o16;
			else if (inbpp == PICDEPTH_16BPP)
				writePatchPixel = WritePatchPixel_i16o16;
			else // PICFMT_PATCH
				writePatchPixel = WritePatchPixel_i8o16;
			break;
		default: // PICFMT_PATCH
		{
			if (inbpp == PICDEPTH_32BPP)
				writePatchPixel = WritePatchPixel_i32o8;
			else if (inbpp == PICDEPTH_16BPP)
				writePatchPixel = WritePatchPixel_i16o8;
			else // PICFMT_PATCH
				writePatchPixel = WritePatchPixel_i8o8;
			break;
		}
	}

	patch_t *out = Z_Calloc(sizeof(patch_t), PU_PATCH, NULL);

	out->width = inwidth;
	out->height = inheight;
	out->leftoffset = inleftoffset;
	out->topoffset = intopoffset;

	size_t max_pixels = out->width * out->height;
	unsigned num_posts = 0;

	out->columns = Z_Calloc(sizeof(column_t) * out->width, PU_PATCH_DATA, NULL);
	out->pixels = Z_Calloc(max_pixels * (outbpp / 8), PU_PATCH_DATA, NULL);
	out->posts = NULL;

	UINT8 *imgptr = out->pixels;

	unsigned *column_posts = Z_Calloc(sizeof(unsigned) * inwidth, PU_STATIC, NULL);

	// Write columns
	for (INT32 x = 0; x < inwidth; x++)
	{
		post_t *post = NULL;
		size_t post_data_offset = 0;
		boolean was_opaque = false;

		column_t *column = &out->columns[x];
		column->pixels = imgptr;
		column->posts = NULL;
		column->num_posts = 0;

		column_posts[x] = (unsigned)-1;

		// Write pixels
		for (INT32 y = 0; y < inheight; y++)
		{
			boolean opaque = false;

			// Read pixel
			void *input = readPixelFunc(picture, informat, x, y, inwidth, inheight, flags);

			// Determine opacity
			if (input != NULL)
				opaque = getAlphaFunc(input, flags) > 0;

			// End span if we have a transparent pixel
			if (!opaque)
			{
				was_opaque = false;
				continue;
			}

			if (!was_opaque)
			{
				num_posts++;

				out->posts = Z_Realloc(out->posts, sizeof(post_t) * num_posts, PU_PATCH_DATA, NULL);
				post = &out->posts[num_posts - 1];
				post->topdelta = (size_t)y;
				post->length = 0;
				post->data_offset = post_data_offset;
				if (column_posts[x] == (unsigned)-1)
					column_posts[x] = num_posts - 1;
				column->num_posts++;
			}

			was_opaque = true;

			// Write the pixel
			UINT8 *last_ptr = imgptr;
			imgptr = writePatchPixel(last_ptr, input);

			post->length++;
			post_data_offset += imgptr - last_ptr;
		}
	}

	UINT8 *old_pixels = out->pixels;
	size_t total_pixels = imgptr - out->pixels;
	if (total_pixels != max_pixels)
		out->pixels = Z_Realloc(out->pixels, total_pixels, PU_PATCH_DATA, NULL);

	for (INT16 x = 0; x < inwidth; x++)
	{
		column_t *column = &out->columns[x];
		if (column->num_posts > 0)
			column->posts = &out->posts[column_posts[x]];
		if (old_pixels != out->pixels)
			column->pixels = out->pixels + (column->pixels - old_pixels);
	}

	Z_Free(column_posts);

	if (outsize != NULL)
		*outsize = sizeof(patch_t);

	return (void *)out;
}

/** Converts a picture to a flat.
  *
  * \param informat Input picture format.
  * \param picture Input picture data.
  * \param outformat Output picture format.
  * \param outsize Output picture size, as a pointer.
  * \param inwidth Input picture width.
  * \param inheight Input picture height.
  * \param flags Input picture flags.
  * \return A pointer to the converted picture.
  */
void *Picture_FlatConvert(
	pictureformat_t informat, void *picture, pictureformat_t outformat,
	size_t *outsize,
	INT32 inwidth, INT32 inheight,
	pictureflags_t flags)
{
	void *outflat;
	patch_t *inpatch = NULL;
	INT32 inbpp = Picture_FormatBPP(informat);
	INT32 outbpp = Picture_FormatBPP(outformat);
	INT32 x, y;
	size_t size;

	if (informat == PICFMT_NONE)
		I_Error("Picture_FlatConvert: input format was PICFMT_NONE!");
	else if (outformat == PICFMT_NONE)
		I_Error("Picture_FlatConvert: output format was PICFMT_NONE!");
	else if (informat == outformat)
		I_Error("Picture_FlatConvert: input and output formats were the same!");

	if (inbpp == PICDEPTH_NONE)
		I_Error("Picture_FlatConvert: unknown input bits per pixel?!");
	if (outbpp == PICDEPTH_NONE)
		I_Error("Picture_FlatConvert: unknown output bits per pixel?!");

	// If it's a patch, you can just figure out
	// the dimensions from the header.
	if (Picture_IsPatchFormat(informat))
	{
		inpatch = (patch_t *)picture;
		if (Picture_IsDoomPatchFormat(informat))
		{
			softwarepatch_t *doompatch = ((softwarepatch_t *)picture);
			inwidth = SHORT(doompatch->width);
			inheight = SHORT(doompatch->height);
		}
		else
		{
			inwidth = inpatch->width;
			inheight = inpatch->height;
		}
	}

	size = (inwidth * inheight) * (outbpp / 8);
	outflat = Z_Calloc(size, PU_STATIC, NULL);
	if (outsize)
		*outsize = size;

	// Set transparency
	if (outbpp == PICDEPTH_8BPP)
		memset(outflat, TRANSPARENTPIXEL, size);

	for (y = 0; y < inheight; y++)
		for (x = 0; x < inwidth; x++)
		{
			void *input = NULL;
			int sx = x;
			int sy = y;

			if (flags & PICFLAGS_XFLIP)
				sx = inwidth - x - 1;
			if (flags & PICFLAGS_YFLIP)
				sy = inheight - y - 1;

			size_t in_offs = ((sy * inwidth) + sx);
			size_t out_offs = ((y * inwidth) + x);

			// Read pixel
			if (Picture_IsPatchFormat(informat))
				input = Picture_GetPatchPixel(inpatch, informat, sx, sy, 0);
			else if (Picture_IsFlatFormat(informat))
				input = (UINT8 *)picture + (in_offs * (inbpp / 8));
			else
				I_Error("Picture_FlatConvert: unsupported input format!");

			if (!input)
				continue;

			switch (outformat)
			{
				case PICFMT_FLAT32:
				{
					UINT32 *f32 = (UINT32 *)outflat;
					if (inbpp == PICDEPTH_32BPP)
					{
						RGBA_t out = *(RGBA_t *)input;
						f32[out_offs] = out.rgba;
					}
					else if (inbpp == PICDEPTH_16BPP)
					{
						RGBA_t out = pMasterPalette[*((UINT16 *)input) & 0xFF];
						f32[out_offs] = out.rgba;
					}
					else // PICFMT_PATCH
					{
						RGBA_t out = pMasterPalette[*((UINT8 *)input) & 0xFF];
						f32[out_offs] = out.rgba;
					}
					break;
				}
				case PICFMT_FLAT16:
				{
					UINT16 *f16 = (UINT16 *)outflat;
					if (inbpp == PICDEPTH_32BPP)
					{
						RGBA_t in = *(RGBA_t *)input;
						UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
						f16[out_offs] = (0xFF00 | out);
					}
					else if (inbpp == PICDEPTH_16BPP)
						f16[out_offs] = *(UINT16 *)input;
					else // PICFMT_PATCH
						f16[out_offs] = (0xFF00 | *((UINT8 *)input));
					break;
				}
				case PICFMT_FLAT:
				{
					UINT8 *f8 = (UINT8 *)outflat;
					if (inbpp == PICDEPTH_32BPP)
					{
						RGBA_t in = *(RGBA_t *)input;
						UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
						f8[out_offs] = out;
					}
					else if (inbpp == PICDEPTH_16BPP)
					{
						UINT16 out = *(UINT16 *)input;
						f8[out_offs] = (out & 0xFF);
					}
					else // PICFMT_PATCH
						f8[out_offs] = *(UINT8 *)input;
					break;
				}
				default:
					I_Error("Picture_FlatConvert: unsupported output format!");
			}
		}

	return outflat;
}

/** Returns a pixel from a patch.
  *
  * \param patch Input patch.
  * \param informat Input picture format.
  * \param x Pixel X position.
  * \param y Pixel Y position.
  * \param flags Input picture flags.
  * \return A pointer to a pixel in the patch. Returns NULL if not opaque.
  */
void *Picture_GetPatchPixel(
	patch_t *patch, pictureformat_t informat,
	INT32 x, INT32 y,
	pictureflags_t flags)
{
	INT32 inbpp = Picture_FormatBPP(informat);
	softwarepatch_t *doompatch = (softwarepatch_t *)patch;
	boolean isdoompatch = Picture_IsDoomPatchFormat(informat);

	if (patch == NULL)
		I_Error("Picture_GetPatchPixel: patch == NULL");

	INT16 width = (isdoompatch ? SHORT(doompatch->width) : patch->width);
	INT16 height = (isdoompatch ? SHORT(doompatch->height) : patch->height);

	if (x < 0 || x >= width || y < 0 || y >= height)
		return NULL;

	INT32 sx = (flags & PICFLAGS_XFLIP) ? (width-1)-x : x;
	INT32 sy = (flags & PICFLAGS_YFLIP) ? (height-1)-y : y;
	UINT8 *s8 = NULL;
	UINT16 *s16 = NULL;
	UINT32 *s32 = NULL;

	if (isdoompatch)
	{
		INT32 prevdelta = -1;
		INT32 colofs = LONG(doompatch->columnofs[sx]);

		// Column offsets are pointers, so no casting is required.
		doompost_t *column = (doompost_t *)((UINT8 *)doompatch + colofs);

		while (column->topdelta != 0xff)
		{
			INT32 topdelta = column->topdelta;
			if (topdelta <= prevdelta)
				topdelta += prevdelta;
			prevdelta = topdelta;

			size_t ofs = sy - topdelta;

			if (sy >= topdelta && ofs < column->length)
			{
				s8 = (UINT8 *)(column) + 3;
				switch (inbpp)
				{
					case PICDEPTH_32BPP:
						s32 = (UINT32 *)s8;
						return &s32[ofs];
					case PICDEPTH_16BPP:
						s16 = (UINT16 *)s8;
						return &s16[ofs];
					default: // PICDEPTH_8BPP
						return &s8[ofs];
				}
			}

			if (inbpp == PICDEPTH_32BPP)
				column = (doompost_t *)((UINT32 *)column + column->length);
			else if (inbpp == PICDEPTH_16BPP)
				column = (doompost_t *)((UINT16 *)column + column->length);
			else
				column = (doompost_t *)((UINT8 *)column + column->length);
			column = (doompost_t *)((UINT8 *)column + 4);
		}
	}
	else
	{
		column_t *column = &patch->columns[sx];
		for (unsigned i = 0; i < column->num_posts; i++)
		{
			post_t *post = &column->posts[i];

			size_t ofs = sy - post->topdelta;

			if (sy >= (INT32)post->topdelta && ofs < post->length)
			{
				s8 = column->pixels + post->data_offset;
				switch (inbpp)
				{
					case PICDEPTH_32BPP:
						s32 = (UINT32 *)s8;
						return &s32[ofs];
					case PICDEPTH_16BPP:
						s16 = (UINT16 *)s8;
						return &s16[ofs];
					default: // PICDEPTH_8BPP
						return &s8[ofs];
				}
			}
		}
	}

	return NULL;
}

/** Returns the amount of bits per pixel in the specified picture format.
  *
  * \param format Input picture format.
  * \return The bits per pixel amount of the picture format.
  */
INT32 Picture_FormatBPP(pictureformat_t format)
{
	INT32 bpp = PICDEPTH_NONE;
	switch (format)
	{
		case PICFMT_PATCH32:
		case PICFMT_FLAT32:
		case PICFMT_DOOMPATCH32:
		case PICFMT_PNG:
			bpp = PICDEPTH_32BPP;
			break;
		case PICFMT_PATCH16:
		case PICFMT_FLAT16:
		case PICFMT_DOOMPATCH16:
			bpp = PICDEPTH_16BPP;
			break;
		case PICFMT_PATCH:
		case PICFMT_FLAT:
		case PICFMT_DOOMPATCH:
			bpp = PICDEPTH_8BPP;
			break;
		default:
			break;
	}
	return bpp;
}

/** Checks if the specified picture format is a patch.
  *
  * \param format Input picture format.
  * \return True if the picture format is a patch, false if not.
  */
boolean Picture_IsPatchFormat(pictureformat_t format)
{
	return (Picture_IsInternalPatchFormat(format) || Picture_IsDoomPatchFormat(format));
}

/** Checks if the specified picture format is an internal patch.
  *
  * \param format Input picture format.
  * \return True if the picture format is an internal patch, false if not.
  */
boolean Picture_IsInternalPatchFormat(pictureformat_t format)
{
	switch (format)
	{
		case PICFMT_PATCH:
		case PICFMT_PATCH16:
		case PICFMT_PATCH32:
			return true;
		default:
			return false;
	}
}

/** Checks if the specified picture format is a Doom patch.
  *
  * \param format Input picture format.
  * \return True if the picture format is a Doom patch, false if not.
  */
boolean Picture_IsDoomPatchFormat(pictureformat_t format)
{
	switch (format)
	{
		case PICFMT_DOOMPATCH:
		case PICFMT_DOOMPATCH16:
		case PICFMT_DOOMPATCH32:
			return true;
		default:
			return false;
	}
}

/** Checks if the specified picture format is a flat.
  *
  * \param format Input picture format.
  * \return True if the picture format is a flat, false if not.
  */
boolean Picture_IsFlatFormat(pictureformat_t format)
{
	return (format == PICFMT_FLAT || format == PICFMT_FLAT16 || format == PICFMT_FLAT32);
}

/** Returns true if the lump is a valid Doom patch.
  * PICFMT_DOOMPATCH only.
  *
  * \param patch Input patch.
  * \param picture Input patch size.
  * \return True if the input patch is valid.
  */
boolean Picture_CheckIfDoomPatch(softwarepatch_t *patch, size_t size)
{
	INT16 width, height;
	boolean result;

	// minimum length of a valid Doom patch
	if (size < 13)
		return false;

	width = SHORT(patch->width);
	height = SHORT(patch->height);
	result = (height > 0 && height <= 16384 && width > 0 && width <= 16384);

	if (result)
	{
		// The dimensions seem like they might be valid for a patch, so
		// check the column directory for extra security. All columns
		// must begin after the column directory, and none of them must
		// point past the end of the patch.
		INT16 x;

		for (x = 0; x < width; x++)
		{
			UINT32 ofs = LONG(patch->columnofs[x]);

			// Need one byte for an empty column (but there's patches that don't know that!)
			if (ofs < (UINT32)width * 4 + 8 || ofs >= (UINT32)size)
			{
				result = false;
				break;
			}
		}
	}

	return result;
}

/** Converts a texture to a flat.
  *
  * \param texnum The texture number.
  * \return The converted flat.
  */
void *Picture_TextureToFlat(size_t texnum)
{
	texture_t *texture;

	UINT8 *converted;
	size_t flatsize;
	UINT8 *desttop, *dest, *deststop;
	UINT8 *source;

	if (texnum >= (unsigned)numtextures)
		I_Error("Picture_TextureToFlat: invalid texture number!");

	// Check the texture cache
	// If the texture's not there, it'll be generated right now
	texture = textures[texnum];
	R_CheckTextureCache(texnum);

	// Allocate the flat
	flatsize = texture->width * texture->height;
	converted = Z_Malloc(flatsize, PU_STATIC, NULL);
	memset(converted, TRANSPARENTPIXEL, flatsize);

	// Now we're gonna write to it
	desttop = converted;
	deststop = desttop + flatsize;

	for (size_t col = 0; col < (size_t)texture->width; col++, desttop++)
	{
		column_t *column = (column_t *)R_GetColumn(texnum, col);
		for (unsigned i = 0; i < column->num_posts; i++)
		{
			post_t *post = &column->posts[i];
			dest = desttop + (post->topdelta * texture->width);
			source = column->pixels + post->data_offset;
			for (size_t ofs = 0; dest < deststop && ofs < post->length; ofs++)
			{
				if (source[ofs] != TRANSPARENTPIXEL)
					*dest = source[ofs];
				dest += texture->width;
			}
		}
	}

	return converted;
}

/** Returns true if the lump is a valid PNG.
  *
  * \param d The lump to be checked.
  * \param s The lump size.
  * \return True if the lump is a PNG image.
  */
boolean Picture_IsLumpPNG(const UINT8 *d, size_t s)
{
	if (s < 67) // https://web.archive.org/web/20230524232139/http://garethrees.org/2007/11/14/pngcrush/
		return false;
	// Check for PNG file signature using memcmp
	// As it may be faster on CPUs with slow unaligned memory access
	// Ref: http://www.libpng.org/pub/png/spec/1.2/PNG-Rationale.html#R.PNG-file-signature
	return (memcmp(&d[0], "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8) == 0);
}

#ifndef NO_PNG_LUMPS
#ifdef HAVE_PNG

/*#if PNG_LIBPNG_VER_DLLNUM < 14
typedef PNG_CONST png_byte *png_const_bytep;
#endif*/
typedef struct
{
	const UINT8 *buffer;
	UINT32 size;
	UINT32 position;
} png_io_t;

static void PNG_IOReader(png_structp png_ptr, png_bytep data, png_size_t length)
{
	png_io_t *f = png_get_io_ptr(png_ptr);
	if (length > (f->size - f->position))
		png_error(png_ptr, "PNG_IOReader: buffer overrun");
	memcpy(data, f->buffer + f->position, length);
	f->position += length;
}

typedef struct
{
	char name[4];
	void *data;
	size_t size;
} png_chunk_t;

static png_byte *chunkname = NULL;
static png_chunk_t chunk;

static int PNG_ChunkReader(png_structp png_ptr, png_unknown_chunkp chonk)
{
	(void)png_ptr;
	if (!memcmp(chonk->name, chunkname, 4))
	{
		memcpy(chunk.name, chonk->name, 4);
		chunk.size = chonk->size;
		chunk.data = Z_Malloc(chunk.size, PU_STATIC, NULL);
		memcpy(chunk.data, chonk->data, chunk.size);
		return 1;
	}
	return 0;
}

static void PNG_error(png_structp PNG, png_const_charp pngtext)
{
	CONS_Debug(DBG_RENDER, "libpng error at %p: %s", PNG, pngtext);
}

static void PNG_warn(png_structp PNG, png_const_charp pngtext)
{
	CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext);
}

static png_byte grAb_chunk[5] = {'g', 'r', 'A', 'b', (png_byte)'\0'};

static png_bytep *PNG_Read(
	const UINT8 *png,
	INT32 *w, INT32 *h, INT16 *topoffset, INT16 *leftoffset,
	boolean *use_palette, size_t size)
{
	png_structp png_ptr;
	png_infop png_info_ptr;
	png_uint_32 width, height;
	int bit_depth, color_type;
	png_uint_32 y;

	png_colorp palette;
	int palette_size;

	png_bytep trans = NULL;
	int num_trans = 0;

#ifdef PNG_SETJMP_SUPPORTED
#ifdef USE_FAR_KEYWORD
	jmp_buf jmpbuf;
#endif
#endif

	png_io_t png_io;
	png_bytep *row_pointers;
	png_voidp *user_chunk_ptr;

	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn);
	if (!png_ptr)
		I_Error("PNG_Read: Couldn't initialize libpng!");

	png_info_ptr = png_create_info_struct(png_ptr);
	if (!png_info_ptr)
	{
		png_destroy_read_struct(&png_ptr, NULL, NULL);
		I_Error("PNG_Read: libpng couldn't allocate memory!");
	}

#ifdef USE_FAR_KEYWORD
	if (setjmp(jmpbuf))
#else
	if (setjmp(png_jmpbuf(png_ptr)))
#endif
	{
		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
		I_Error("PNG_Read: libpng load error!");
	}
#ifdef USE_FAR_KEYWORD
	png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
#endif

	png_io.buffer = png;
	png_io.size = size;
	png_io.position = 0;
	png_set_read_fn(png_ptr, &png_io, PNG_IOReader);

	memset(&chunk, 0x00, sizeof(png_chunk_t));
	chunkname = grAb_chunk;

	user_chunk_ptr = png_get_user_chunk_ptr(png_ptr);
	png_set_read_user_chunk_fn(png_ptr, user_chunk_ptr, PNG_ChunkReader);
	png_set_keep_unknown_chunks(png_ptr, 2, chunkname, 1);

#ifdef PNG_SET_USER_LIMITS_SUPPORTED
	png_set_user_limits(png_ptr, 2048, 2048);
#endif

	png_read_info(png_ptr, png_info_ptr);
	png_get_IHDR(png_ptr, png_info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);

	if (bit_depth == 16)
		png_set_strip_16(png_ptr);

	palette = NULL;
	*use_palette = false;

	if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb(png_ptr);
	else if (color_type == PNG_COLOR_TYPE_PALETTE)
	{
		boolean usepal = false;

		// Lactozilla: Check if the PNG has a palette, and if its color count
		// matches the color count of SRB2's palette: 256 colors.
		if (png_get_PLTE(png_ptr, png_info_ptr, &palette, &palette_size))
		{
			if (palette_size == 256 && pMasterPalette)
			{
				png_colorp pal = palette;
				INT32 i;

				usepal = true;

				for (i = 0; i < 256; i++)
				{
					byteColor_t *curpal = &(pMasterPalette[i].s);
					if (pal->red != curpal->red || pal->green != curpal->green || pal->blue != curpal->blue)
					{
						usepal = false;
						break;
					}
					pal++;
				}
			}
		}

		// If any of the tRNS colors have an alpha lower than 0xFF, and that
		// color is present on the image, the palette flag is disabled.
		if (usepal)
		{
			png_uint_32 result = png_get_tRNS(png_ptr, png_info_ptr, &trans, &num_trans, NULL);

			if ((result & PNG_INFO_tRNS) && num_trans > 0 && trans != NULL)
			{
				INT32 i;
				for (i = 0; i < num_trans; i++)
				{
					// libpng will transform this image into RGBA even if
					// the transparent index does not exist in the image,
					// and there is no way around that.
					if (trans[i] < 0xFF)
					{
						usepal = false;
						break;
					}
				}
			}
		}

		if (usepal)
			*use_palette = true;
		else
			png_set_palette_to_rgb(png_ptr);
	}

	if (png_get_valid(png_ptr, png_info_ptr, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha(png_ptr);
	else if (color_type != PNG_COLOR_TYPE_RGB_ALPHA && color_type != PNG_COLOR_TYPE_GRAY_ALPHA)
	{
#if PNG_LIBPNG_VER < 10207
		png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
#else
		png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
#endif
	}

	png_read_update_info(png_ptr, png_info_ptr);

	// Read the image
	row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
	for (y = 0; y < height; y++)
		row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png_ptr, png_info_ptr));
	png_read_image(png_ptr, row_pointers);

	// Read grAB chunk
	if ((topoffset || leftoffset) && (chunk.data != NULL))
	{
		INT32 *offsets = (INT32 *)chunk.data;
		// read left offset
		if (leftoffset != NULL)
			*leftoffset = (INT16)BIGENDIAN_LONG(*offsets);
		offsets++;
		// read top offset
		if (topoffset != NULL)
			*topoffset = (INT16)BIGENDIAN_LONG(*offsets);
	}

	png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
	if (chunk.data)
		Z_Free(chunk.data);

	*w = (INT32)width;
	*h = (INT32)height;

	return row_pointers;
}

/** Converts a PNG to a picture.
  *
  * \param png The PNG image.
  * \param outformat The output picture's format.
  * \param w The output picture's width, as a pointer.
  * \param h The output picture's height, as a pointer.
  * \param topoffset The output picture's top offset, for sprites, as a pointer.
  * \param leftoffset The output picture's left offset, for sprites, as a pointer.
  * \param insize The input picture's size.
  * \param outsize A pointer to the output picture's size.
  * \param flags Input picture flags.
  * \return A pointer to the converted picture.
  */
void *Picture_PNGConvert(
	const UINT8 *png, pictureformat_t outformat,
	INT32 *w, INT32 *h,
	INT16 *topoffset, INT16 *leftoffset,
	size_t insize, size_t *outsize,
	pictureflags_t flags)
{
	void *flat;
	INT32 outbpp;
	size_t flatsize;
	png_uint_32 x, y;
	png_bytep row;
	boolean palette = false;
	png_bytep *row_pointers = NULL;
	png_uint_32 width, height;

	INT32 pngwidth, pngheight;
	INT16 loffs = 0, toffs = 0;

	if (png == NULL)
		I_Error("Picture_PNGConvert: picture was NULL!");

	if (w == NULL)
		w = &pngwidth;
	if (h == NULL)
		h = &pngheight;
	if (topoffset == NULL)
		topoffset = &toffs;
	if (leftoffset == NULL)
		leftoffset = &loffs;

	row_pointers = PNG_Read(png, w, h, topoffset, leftoffset, &palette, insize);
	width = *w;
	height = *h;

	if (row_pointers == NULL)
		I_Error("Picture_PNGConvert: row_pointers was NULL!");

	// Find the output format's bits per pixel amount
	outbpp = Picture_FormatBPP(outformat);

	// Hack for patches because you'll want to preserve transparency.
	if (Picture_IsPatchFormat(outformat))
	{
		// Force a higher bit depth
		if (outbpp == PICDEPTH_8BPP)
			outbpp = PICDEPTH_16BPP;
	}

	// Shouldn't happen.
	if (outbpp == PICDEPTH_NONE)
		I_Error("Picture_PNGConvert: unknown output bits per pixel?!");

	// Figure out the size
	flatsize = (width * height) * (outbpp / 8);
	if (outsize)
		*outsize = flatsize;

	// Convert the image
	flat = Z_Calloc(flatsize, PU_STATIC, NULL);

	// Set transparency
	if (outbpp == PICDEPTH_8BPP)
		memset(flat, TRANSPARENTPIXEL, (width * height));

#ifdef PICTURE_PNG_USELOOKUP
	if (outbpp != PICDEPTH_32BPP)
		InitColorLUT(&png_colorlookup, pMasterPalette, false);
#endif

	if (outbpp == PICDEPTH_32BPP)
	{
		RGBA_t out;
		UINT32 *outflat = (UINT32 *)flat;

		if (palette)
		{
			for (y = 0; y < height; y++)
			{
				row = row_pointers[y];
				for (x = 0; x < width; x++)
				{
					out = V_GetColor(row[x]);
					outflat[((y * width) + x)] = out.rgba;
				}
			}
		}
		else
		{
			for (y = 0; y < height; y++)
			{
				row = row_pointers[y];
				for (x = 0; x < width; x++)
				{
					png_bytep px = &(row[x * 4]);
					if ((UINT8)px[3])
					{
						out.s.red = (UINT8)px[0];
						out.s.green = (UINT8)px[1];
						out.s.blue = (UINT8)px[2];
						out.s.alpha = (UINT8)px[3];
						outflat[((y * width) + x)] = out.rgba;
					}
					else
						outflat[((y * width) + x)] = 0x00000000;
				}
			}
		}
	}
	else if (outbpp == PICDEPTH_16BPP)
	{
		UINT16 *outflat = (UINT16 *)flat;

		if (palette)
		{
			for (y = 0; y < height; y++)
			{
				row = row_pointers[y];
				for (x = 0; x < width; x++)
					outflat[((y * width) + x)] = (0xFF << 8) | row[x];
			}
		}
		else
		{
			for (y = 0; y < height; y++)
			{
				row = row_pointers[y];
				for (x = 0; x < width; x++)
				{
					png_bytep px = &(row[x * 4]);
					UINT8 red = (UINT8)px[0];
					UINT8 green = (UINT8)px[1];
					UINT8 blue = (UINT8)px[2];
					UINT8 alpha = (UINT8)px[3];

					if (alpha)
					{
#ifdef PICTURE_PNG_USELOOKUP
						UINT8 palidx = GetColorLUT(&png_colorlookup, red, green, blue);
#else
						UINT8 palidx = NearestColor(red, green, blue);
#endif
						outflat[((y * width) + x)] = (0xFF << 8) | palidx;
					}
					else
						outflat[((y * width) + x)] = 0x0000;
				}
			}
		}
	}
	else // 8bpp
	{
		UINT8 *outflat = (UINT8 *)flat;

		if (palette)
		{
			for (y = 0; y < height; y++)
			{
				row = row_pointers[y];
				for (x = 0; x < width; x++)
					outflat[((y * width) + x)] = row[x];
			}
		}
		else
		{
			for (y = 0; y < height; y++)
			{
				row = row_pointers[y];
				for (x = 0; x < width; x++)
				{
					png_bytep px = &(row[x * 4]);
					UINT8 red = (UINT8)px[0];
					UINT8 green = (UINT8)px[1];
					UINT8 blue = (UINT8)px[2];
					UINT8 alpha = (UINT8)px[3];

					if (alpha)
					{
#ifdef PICTURE_PNG_USELOOKUP
						UINT8 palidx = GetColorLUT(&png_colorlookup, red, green, blue);
#else
						UINT8 palidx = NearestColor(red, green, blue);
#endif
						outflat[((y * width) + x)] = palidx;
					}
				}
			}
		}
	}

	// Free the row pointers that we allocated for libpng.
	for (y = 0; y < height; y++)
		free(row_pointers[y]);
	free(row_pointers);

	// But wait, there's more!
	if (Picture_IsPatchFormat(outformat))
	{
		void *converted;
		pictureformat_t informat = PICFMT_NONE;

		// Figure out the format of the flat, from the bit depth of the output format
		switch (outbpp)
		{
			case 32:
				informat = PICFMT_FLAT32;
				break;
			case 16:
				informat = PICFMT_FLAT16;
				break;
			default:
				informat = PICFMT_FLAT;
				break;
		}

		// Now, convert it!
		converted = Picture_PatchConvert(informat, flat, outformat, outsize, (INT32)width, (INT32)height, *leftoffset, *topoffset, flags);
		Z_Free(flat);
		return converted;
	}

	// Return the converted flat!
	return flat;
}

/** Returns the dimensions of a PNG image, but doesn't perform any conversions.
  *
  * \param png The PNG image.
  * \param width A pointer to the input picture's width.
  * \param height A pointer to the input picture's height.
  * \param topoffset A pointer to the input picture's vertical offset.
  * \param leftoffset A pointer to the input picture's horizontal offset.
  * \param size The input picture's size.
  * \return True if reading the file succeeded, false if it failed.
  */
boolean Picture_PNGDimensions(UINT8 *png, INT32 *width, INT32 *height, INT16 *topoffset, INT16 *leftoffset, size_t size)
{
	png_structp png_ptr;
	png_infop png_info_ptr;
	png_uint_32 w, h;
	int bit_depth, color_type;
#ifdef PNG_SETJMP_SUPPORTED
#ifdef USE_FAR_KEYWORD
	jmp_buf jmpbuf;
#endif
#endif

	png_io_t png_io;
	png_voidp *user_chunk_ptr;

	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn);
	if (!png_ptr)
	{
		CONS_Alert(CONS_ERROR, "Picture_PNGDimensions: Couldn't initialize libpng!\n");
		return false;
	}

	png_info_ptr = png_create_info_struct(png_ptr);
	if (!png_info_ptr)
	{
		png_destroy_read_struct(&png_ptr, NULL, NULL);
		CONS_Alert(CONS_ERROR, "Picture_PNGDimensions: libpng couldn't allocate memory!\n");
		return false;
	}

#ifdef USE_FAR_KEYWORD
	if (setjmp(jmpbuf))
#else
	if (setjmp(png_jmpbuf(png_ptr)))
#endif
	{
		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
		CONS_Alert(CONS_ERROR, "Picture_PNGDimensions: libpng load error!\n");
		return false;
	}
#ifdef USE_FAR_KEYWORD
	png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
#endif

	png_io.buffer = png;
	png_io.size = size;
	png_io.position = 0;
	png_set_read_fn(png_ptr, &png_io, PNG_IOReader);

	memset(&chunk, 0x00, sizeof(png_chunk_t));
	chunkname = grAb_chunk;

	user_chunk_ptr = png_get_user_chunk_ptr(png_ptr);
	png_set_read_user_chunk_fn(png_ptr, user_chunk_ptr, PNG_ChunkReader);
	png_set_keep_unknown_chunks(png_ptr, 2, chunkname, 1);

#ifdef PNG_SET_USER_LIMITS_SUPPORTED
	png_set_user_limits(png_ptr, 2048, 2048);
#endif

	png_read_info(png_ptr, png_info_ptr);
	png_get_IHDR(png_ptr, png_info_ptr, &w, &h, &bit_depth, &color_type, NULL, NULL, NULL);

	// Read grAB chunk
	if ((topoffset || leftoffset) && (chunk.data != NULL))
	{
		INT32 *offsets = (INT32 *)chunk.data;
		// read left offset
		if (leftoffset != NULL)
			*leftoffset = (INT16)BIGENDIAN_LONG(*offsets);
		offsets++;
		// read top offset
		if (topoffset != NULL)
			*topoffset = (INT16)BIGENDIAN_LONG(*offsets);
	}

	png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
	if (chunk.data)
		Z_Free(chunk.data);

	*width = (INT32)w;
	*height = (INT32)h;
	return true;
}
#endif
#endif

//
// R_ParseSpriteInfoFrame
//
// Parse a SPRTINFO frame.
//
static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
{
	char *sprinfoToken;
	size_t sprinfoTokenLength;
	char *frameChar = NULL;
	UINT8 frameFrame = 0xFF;
	INT16 frameXPivot = 0;
	INT16 frameYPivot = 0;

	// Sprite identifier
	sprinfoToken = M_GetToken(NULL);
	if (sprinfoToken == NULL)
	{
		I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite frame should be");
	}
	sprinfoTokenLength = strlen(sprinfoToken);
	if (sprinfoTokenLength != 1)
	{
		I_Error("Error parsing SPRTINFO lump: Invalid frame \"%s\"",sprinfoToken);
	}
	else
		frameChar = sprinfoToken;

	frameFrame = R_Char2Frame(frameChar[0]);
	Z_Free(sprinfoToken);

	// Left Curly Brace
	sprinfoToken = M_GetToken(NULL);
	if (sprinfoToken == NULL)
		I_Error("Error parsing SPRTINFO lump: Missing sprite info");
	else
	{
		if (strcmp(sprinfoToken,"{")==0)
		{
			Z_Free(sprinfoToken);
			sprinfoToken = M_GetToken(NULL);
			if (sprinfoToken == NULL)
			{
				I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info should be");
			}
			while (strcmp(sprinfoToken,"}")!=0)
			{
				if (stricmp(sprinfoToken, "XPIVOT")==0)
				{
					Z_Free(sprinfoToken);
					sprinfoToken = M_GetToken(NULL);
					frameXPivot = atoi(sprinfoToken);
				}
				else if (stricmp(sprinfoToken, "YPIVOT")==0)
				{
					Z_Free(sprinfoToken);
					sprinfoToken = M_GetToken(NULL);
					frameYPivot = atoi(sprinfoToken);
				}
				else if (stricmp(sprinfoToken, "ROTAXIS")==0)
				{
					Z_Free(sprinfoToken);
					sprinfoToken = M_GetToken(NULL);
				}
				Z_Free(sprinfoToken);

				sprinfoToken = M_GetToken(NULL);
				if (sprinfoToken == NULL)
				{
					I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace should be");
				}
			}
		}
		Z_Free(sprinfoToken);
	}

	// set fields
	info->pivot[frameFrame].x = frameXPivot;
	info->pivot[frameFrame].y = frameYPivot;
}

//
// R_ParseSpriteInfo
//
// Parse a SPRTINFO lump.
//
static void R_ParseSpriteInfo(boolean spr2)
{
	spriteinfo_t *info;
	char *sprinfoToken;
	size_t sprinfoTokenLength;
	char newSpriteName[MAXSPRITENAME + 1]; // no longer dynamically allocated
	spritenum_t sprnum = NUMSPRITES;
	playersprite_t spr2num = NUMPLAYERSPRITES;
	INT32 i;
	UINT8 *skinnumbers = NULL;
	INT32 foundskins = 0;

	// Sprite name
	sprinfoToken = M_GetToken(NULL);
	if (sprinfoToken == NULL)
	{
		I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite name should be");
	}
	sprinfoTokenLength = strlen(sprinfoToken);
	if (sprinfoTokenLength > MAXSPRITENAME)
		I_Error("Error parsing SPRTINFO lump: Sprite name \"%s\" is longer than %d characters", sprinfoToken, MAXSPRITENAME);
	strcpy(newSpriteName, sprinfoToken);
	strupr(newSpriteName); // Just do this now so we don't have to worry about it
	Z_Free(sprinfoToken);

	if (!spr2)
	{
		sprnum = R_GetSpriteNumByName(newSpriteName);
		if (sprnum == NUMSPRITES)
			I_Error("Error parsing SPRTINFO lump: Unknown sprite name \"%s\"", newSpriteName);
	}
	else
	{
		for (i = 0; i <= NUMPLAYERSPRITES; i++)
		{
			if (i == NUMPLAYERSPRITES)
				I_Error("Error parsing SPRTINFO lump: Unknown sprite2 name \"%s\"", newSpriteName);
			if (!memcmp(newSpriteName,spr2names[i],4))
			{
				spr2num = i;
				break;
			}
		}
	}

	// allocate a spriteinfo
	info = Z_Calloc(sizeof(spriteinfo_t), PU_STATIC, NULL);
	info->available = true;

	// Left Curly Brace
	sprinfoToken = M_GetToken(NULL);
	if (sprinfoToken == NULL)
	{
		I_Error("Error parsing SPRTINFO lump: Unexpected end of file where open curly brace for sprite \"%s\" should be",newSpriteName);
	}
	if (strcmp(sprinfoToken,"{")==0)
	{
		Z_Free(sprinfoToken);
		sprinfoToken = M_GetToken(NULL);
		if (sprinfoToken == NULL)
		{
			I_Error("Error parsing SPRTINFO lump: Unexpected end of file where definition for sprite \"%s\" should be",newSpriteName);
		}
		while (strcmp(sprinfoToken,"}")!=0)
		{
			if (stricmp(sprinfoToken, "SKIN")==0)
			{
				INT32 skinnum;
				char *skinName = NULL;
				if (!spr2)
					I_Error("Error parsing SPRTINFO lump: \"SKIN\" token found outside of a sprite2 definition");

				Z_Free(sprinfoToken);

				// Skin name
				sprinfoToken = M_GetToken(NULL);
				if (sprinfoToken == NULL)
				{
					I_Error("Error parsing SPRTINFO lump: Unexpected end of file where skin frame should be");
				}

				// copy skin name yada yada
				sprinfoTokenLength = strlen(sprinfoToken);
				skinName = (char *)Z_Malloc((sprinfoTokenLength+1)*sizeof(char),PU_STATIC,NULL);
				M_Memcpy(skinName,sprinfoToken,sprinfoTokenLength*sizeof(char));
				skinName[sprinfoTokenLength] = '\0';
				strlwr(skinName);
				Z_Free(sprinfoToken);

				skinnum = R_SkinAvailable(skinName);
				if (skinnum == -1)
					I_Error("Error parsing SPRTINFO lump: Unknown skin \"%s\"", skinName);

				if (skinnumbers == NULL)
					skinnumbers = Z_Malloc(sizeof(UINT8) * numskins, PU_STATIC, NULL);
				skinnumbers[foundskins] = (UINT8)skinnum;
				foundskins++;
			}
			else if (stricmp(sprinfoToken, "FRAME")==0)
			{
				R_ParseSpriteInfoFrame(info);
				Z_Free(sprinfoToken);
				if (spr2)
				{
					if (!foundskins)
						I_Error("Error parsing SPRTINFO lump: No skins specified in this sprite2 definition");
					for (i = 0; i < foundskins; i++)
					{
						skin_t *skin = skins[skinnumbers[i]];
						spriteinfo_t *sprinfo = skin->sprinfo;
						M_Memcpy(&sprinfo[spr2num], info, sizeof(spriteinfo_t));
					}
				}
				else
					M_Memcpy(&spriteinfo[sprnum], info, sizeof(spriteinfo_t));
			}
			else
			{
				I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\" in sprite %s",sprinfoToken,newSpriteName);
			}

			sprinfoToken = M_GetToken(NULL);
			if (sprinfoToken == NULL)
			{
				I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace for sprite \"%s\" should be",newSpriteName);
			}
		}
	}
	else
	{
		I_Error("Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"",newSpriteName,sprinfoToken);
	}

	Z_Free(sprinfoToken);
	Z_Free(info);
	if (skinnumbers)
		Z_Free(skinnumbers);
}

//
// R_ParseSPRTINFOLump
//
// Read a SPRTINFO lump.
//
void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum)
{
	char *sprinfoLump;
	size_t sprinfoLumpLength;
	char *sprinfoText;
	char *sprinfoToken;

	// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
	// need to make a space of memory where I can ensure that it will terminate
	// correctly. Start by loading the relevant data from the WAD.
	sprinfoLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
	// If that didn't exist, we have nothing to do here.
	if (sprinfoLump == NULL) return;
	// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
	sprinfoLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
	sprinfoText = (char *)Z_Malloc((sprinfoLumpLength+1)*sizeof(char),PU_STATIC,NULL);
	// Now move the contents of the lump into this new location.
	memmove(sprinfoText,sprinfoLump,sprinfoLumpLength);
	// Make damn well sure the last character in our new memory location is \0.
	sprinfoText[sprinfoLumpLength] = '\0';
	// Finally, free up the memory from the first data load, because we really
	// don't need it.
	Z_Free(sprinfoLump);

	sprinfoToken = M_GetToken(sprinfoText);
	while (sprinfoToken != NULL)
	{
		if (!stricmp(sprinfoToken, "SPRITE"))
			R_ParseSpriteInfo(false);
		else if (!stricmp(sprinfoToken, "SPRITE2"))
			R_ParseSpriteInfo(true);
		else
			I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\"", sprinfoToken);
		Z_Free(sprinfoToken);
		sprinfoToken = M_GetToken(NULL);
	}
	Z_Free((void *)sprinfoText);
}

//
// R_LoadSpriteInfoLumps
//
// Load and read every SPRTINFO lump from the specified file.
//
void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps)
{
	lumpinfo_t *lumpinfo = wadfiles[wadnum]->lumpinfo;
	UINT16 i;
	char *name;

	for (i = 0; i < numlumps; i++, lumpinfo++)
	{
		name = lumpinfo->name;
		// Load SPRTINFO and SPR_ lumps as SpriteInfo
		if (!memcmp(name, "SPRTINFO", 8) || !memcmp(name, "SPR_", 4))
			R_ParseSPRTINFOLump(wadnum, i);
	}
}
