view src/stdlib/SDL_iconv.c @ 4284:261ad7b40a31 SDL-1.2

Allow Unicode filenames in RWOPS on Windows. Fixes Bugzilla #733.
author Ryan C. Gordon <icculus@icculus.org>
date Thu, 08 Oct 2009 07:48:37 +0000
parents 22aec2e85b86
children 1524d3237820
line wrap: on
line source

/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2009 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org
*/
#include "SDL_config.h"

/* This file contains portable iconv functions for SDL */

#include "SDL_stdinc.h"
#include "SDL_endian.h"

#ifdef HAVE_ICONV

/* Depending on which standard the iconv() was implemented with,
   iconv() may or may not use const char ** for the inbuf param.
   If we get this wrong, it's just a warning, so no big deal.
*/
#if defined(_XGP6) || \
    defined(__GLIBC__) && ((__GLIBC__ > 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2))
#define ICONV_INBUF_NONCONST
#endif

#include <errno.h>

size_t SDL_iconv(SDL_iconv_t cd,
                 const char **inbuf, size_t *inbytesleft,
                 char **outbuf, size_t *outbytesleft)
{
	size_t retCode;
#ifdef ICONV_INBUF_NONCONST
	retCode = iconv(cd, (char **)inbuf, inbytesleft, outbuf, outbytesleft);
#else
	retCode = iconv(cd, inbuf, inbytesleft, outbuf, outbytesleft);
#endif
	if ( retCode == (size_t)-1 ) {
		switch(errno) {
		    case E2BIG:
			return SDL_ICONV_E2BIG;
		    case EILSEQ:
			return SDL_ICONV_EILSEQ;
		    case EINVAL:
			return SDL_ICONV_EINVAL;
		    default:
			return SDL_ICONV_ERROR;
		}
	}
	return retCode;
}

#else

/* Lots of useful information on Unicode at:
	http://www.cl.cam.ac.uk/~mgk25/unicode.html
*/

#define UNICODE_BOM	0xFEFF

#define UNKNOWN_ASCII	'?'
#define UNKNOWN_UNICODE	0xFFFD

enum {
	ENCODING_UNKNOWN,
	ENCODING_ASCII,
	ENCODING_LATIN1,
	ENCODING_UTF8,
	ENCODING_UTF16,		/* Needs byte order marker */
	ENCODING_UTF16BE,
	ENCODING_UTF16LE,
	ENCODING_UTF32,		/* Needs byte order marker */
	ENCODING_UTF32BE,
	ENCODING_UTF32LE,
	ENCODING_UCS2,		/* Native byte order assumed */
	ENCODING_UCS4,		/* Native byte order assumed */
};
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define ENCODING_UTF16NATIVE	ENCODING_UTF16BE
#define ENCODING_UTF32NATIVE	ENCODING_UTF32BE
#else
#define ENCODING_UTF16NATIVE	ENCODING_UTF16LE
#define ENCODING_UTF32NATIVE	ENCODING_UTF32LE
#endif

struct _SDL_iconv_t
{
	int src_fmt;
	int dst_fmt;
};

static struct {
	const char *name;
	int format;
} encodings[] = {
	{ "ASCII",	ENCODING_ASCII },
	{ "US-ASCII",	ENCODING_ASCII },
	{ "8859-1",	ENCODING_LATIN1 },
	{ "ISO-8859-1",	ENCODING_LATIN1 },
	{ "UTF8",	ENCODING_UTF8 },
	{ "UTF-8",	ENCODING_UTF8 },
	{ "UTF16",	ENCODING_UTF16 },
	{ "UTF-16",	ENCODING_UTF16 },
	{ "UTF16BE",	ENCODING_UTF16BE },
	{ "UTF-16BE",	ENCODING_UTF16BE },
	{ "UTF16LE",	ENCODING_UTF16LE },
	{ "UTF-16LE",	ENCODING_UTF16LE },
	{ "UTF32",	ENCODING_UTF32 },
	{ "UTF-32",	ENCODING_UTF32 },
	{ "UTF32BE",	ENCODING_UTF32BE },
	{ "UTF-32BE",	ENCODING_UTF32BE },
	{ "UTF32LE",	ENCODING_UTF32LE },
	{ "UTF-32LE",	ENCODING_UTF32LE },
	{ "UCS2",	ENCODING_UCS2 },
	{ "UCS-2",	ENCODING_UCS2 },
	{ "UCS4",	ENCODING_UCS4 },
	{ "UCS-4",	ENCODING_UCS4 },
};

static const char *getlocale(char *buffer, size_t bufsize)
{
	const char *lang;
	char *ptr;

	lang = SDL_getenv("LC_ALL");
	if ( !lang ) {
		lang = SDL_getenv("LC_CTYPE");
	}
	if ( !lang ) {
		lang = SDL_getenv("LC_MESSAGES");
	}
	if ( !lang ) {
		lang = SDL_getenv("LANG");
	}
	if ( !lang || !*lang || SDL_strcmp(lang, "C") == 0 ) {
		lang = "ASCII";
	}

	/* We need to trim down strings like "en_US.UTF-8@blah" to "UTF-8" */
	ptr = SDL_strchr(lang, '.');
	if (ptr != NULL) {
		lang = ptr + 1;
	}

	SDL_strlcpy(buffer, lang, bufsize);
	ptr = SDL_strchr(buffer, '@');
	if (ptr != NULL) {
		*ptr = '\0';  /* chop end of string. */
	}

	return buffer;
}

SDL_iconv_t SDL_iconv_open(const char *tocode, const char *fromcode)
{
	int src_fmt = ENCODING_UNKNOWN;
	int dst_fmt = ENCODING_UNKNOWN;
	int i;
	char fromcode_buffer[64];
	char tocode_buffer[64];

	if ( !fromcode || !*fromcode ) {
		fromcode = getlocale(fromcode_buffer, sizeof(fromcode_buffer));
	}
	if ( !tocode || !*tocode ) {
		tocode = getlocale(tocode_buffer, sizeof(tocode_buffer));
	}
	for ( i = 0; i < SDL_arraysize(encodings); ++i ) {
		if ( SDL_strcasecmp(fromcode, encodings[i].name) == 0 ) {
			src_fmt = encodings[i].format;
			if ( dst_fmt != ENCODING_UNKNOWN ) {
				break;
			}
		}
		if ( SDL_strcasecmp(tocode, encodings[i].name) == 0 ) {
			dst_fmt = encodings[i].format;
			if ( src_fmt != ENCODING_UNKNOWN ) {
				break;
			}
		}
	}
	if ( src_fmt != ENCODING_UNKNOWN && dst_fmt != ENCODING_UNKNOWN ) {
		SDL_iconv_t cd = (SDL_iconv_t)SDL_malloc(sizeof(*cd));
		if ( cd ) {
			cd->src_fmt = src_fmt;
			cd->dst_fmt = dst_fmt;
			return cd;
		}
	}
	return (SDL_iconv_t)-1;
}

size_t SDL_iconv(SDL_iconv_t cd,
                 const char **inbuf, size_t *inbytesleft,
                 char **outbuf, size_t *outbytesleft)
{
	/* For simplicity, we'll convert everything to and from UCS-4 */
	const char *src;
	char *dst;
	size_t srclen, dstlen;
	Uint32 ch = 0;
	size_t total;

	if ( !inbuf || !*inbuf ) {
		/* Reset the context */
		return 0;
	}
	if ( !outbuf || !*outbuf || !outbytesleft || !*outbytesleft ) {
		return SDL_ICONV_E2BIG;
	}
	src = *inbuf;
	srclen = (inbytesleft ? *inbytesleft : 0);
	dst = *outbuf;
	dstlen = *outbytesleft;

	switch ( cd->src_fmt ) {
	    case ENCODING_UTF16:
		/* Scan for a byte order marker */
		{
			Uint8 *p = (Uint8 *)src;
			size_t n = srclen / 2;
			while ( n ) {
				if ( p[0] == 0xFF && p[1] == 0xFE ) {
					cd->src_fmt = ENCODING_UTF16BE;
					break;
				} else if ( p[0] == 0xFE && p[1] == 0xFF ) {
					cd->src_fmt = ENCODING_UTF16LE;
					break;
				}
				p += 2;
				--n;
			}
			if ( n == 0 ) {
				/* We can't tell, default to host order */
				cd->src_fmt = ENCODING_UTF16NATIVE;
			}
		}
		break;
	    case ENCODING_UTF32:
		/* Scan for a byte order marker */
		{
			Uint8 *p = (Uint8 *)src;
			size_t n = srclen / 4;
			while ( n ) {
				if ( p[0] == 0xFF && p[1] == 0xFE &&
				     p[2] == 0x00 && p[3] == 0x00 ) {
					cd->src_fmt = ENCODING_UTF32BE;
					break;
				} else if ( p[0] == 0x00 && p[1] == 0x00 &&
				            p[2] == 0xFE && p[3] == 0xFF ) {
					cd->src_fmt = ENCODING_UTF32LE;
					break;
				}
				p += 4;
				--n;
			}
			if ( n == 0 ) {
				/* We can't tell, default to host order */
				cd->src_fmt = ENCODING_UTF32NATIVE;
			}
		}
		break;
	}

	switch ( cd->dst_fmt ) {
	    case ENCODING_UTF16:
		/* Default to host order, need to add byte order marker */
		if ( dstlen < 2 ) {
			return SDL_ICONV_E2BIG;
		}
		*(Uint16 *)dst = UNICODE_BOM;
		dst += 2;
		dstlen -= 2;
		cd->dst_fmt = ENCODING_UTF16NATIVE;
		break;
	    case ENCODING_UTF32:
		/* Default to host order, need to add byte order marker */
		if ( dstlen < 4 ) {
			return SDL_ICONV_E2BIG;
		}
		*(Uint32 *)dst = UNICODE_BOM;
		dst += 4;
		dstlen -= 4;
		cd->dst_fmt = ENCODING_UTF32NATIVE;
		break;
	}

	total = 0;
	while ( srclen > 0 ) {
		/* Decode a character */
		switch ( cd->src_fmt ) {
		    case ENCODING_ASCII:
			{
				Uint8 *p = (Uint8 *)src;
				ch = (Uint32)(p[0] & 0x7F);
				++src;
				--srclen;
			}
			break;
		    case ENCODING_LATIN1:
			{
				Uint8 *p = (Uint8 *)src;
				ch = (Uint32)p[0];
				++src;
				--srclen;
			}
			break;
		    case ENCODING_UTF8: /* RFC 3629 */
			{
				Uint8 *p = (Uint8 *)src;
				size_t left = 0;
				SDL_bool overlong = SDL_FALSE;
				if ( p[0] >= 0xFC ) {
					if ( (p[0] & 0xFE) != 0xFC ) {
						/* Skip illegal sequences
						return SDL_ICONV_EILSEQ;
						*/
						ch = UNKNOWN_UNICODE;
					} else {
						if ( p[0] == 0xFC ) {
							overlong = SDL_TRUE;
						}
						ch = (Uint32)(p[0] & 0x01);
						left = 5;
					}
				} else if ( p[0] >= 0xF8 ) {
					if ( (p[0] & 0xFC) != 0xF8 ) {
						/* Skip illegal sequences
						return SDL_ICONV_EILSEQ;
						*/
						ch = UNKNOWN_UNICODE;
					} else {
						if ( p[0] == 0xF8 ) {
							overlong = SDL_TRUE;
						}
						ch = (Uint32)(p[0] & 0x03);
						left = 4;
					}
				} else if ( p[0] >= 0xF0 ) {
					if ( (p[0] & 0xF8) != 0xF0 ) {
						/* Skip illegal sequences
						return SDL_ICONV_EILSEQ;
						*/
						ch = UNKNOWN_UNICODE;
					} else {
						if ( p[0] == 0xF0 ) {
							overlong = SDL_TRUE;
						}
						ch = (Uint32)(p[0] & 0x07);
						left = 3;
					}
				} else if ( p[0] >= 0xE0 ) {
					if ( (p[0] & 0xF0) != 0xE0 ) {
						/* Skip illegal sequences
						return SDL_ICONV_EILSEQ;
						*/
						ch = UNKNOWN_UNICODE;
					} else {
						if ( p[0] == 0xE0 ) {
							overlong = SDL_TRUE;
						}
						ch = (Uint32)(p[0] & 0x0F);
						left = 2;
					}
				} else if ( p[0] >= 0xC0 ) {
					if ( (p[0] & 0xE0) != 0xC0 ) {
						/* Skip illegal sequences
						return SDL_ICONV_EILSEQ;
						*/
						ch = UNKNOWN_UNICODE;
					} else {
						if ( (p[0] & 0xCE) == 0xC0 ) {
							overlong = SDL_TRUE;
						}
						ch = (Uint32)(p[0] & 0x1F);
						left = 1;
					}
				} else {
					if ( (p[0] & 0x80) != 0x00 ) {
						/* Skip illegal sequences
						return SDL_ICONV_EILSEQ;
						*/
						ch = UNKNOWN_UNICODE;
					} else {
						ch = (Uint32)p[0];
					}
				}
				++src;
				--srclen;
				if ( srclen < left ) {
					return SDL_ICONV_EINVAL;
				}
				while ( left-- ) {
					++p;
					if ( (p[0] & 0xC0) != 0x80 ) {
						/* Skip illegal sequences
						return SDL_ICONV_EILSEQ;
						*/
						ch = UNKNOWN_UNICODE;
						break;
					}
					ch <<= 6;
					ch |= (p[0] & 0x3F);
					++src;
					--srclen;
				}
				if ( overlong ) {
					/* Potential security risk
					return SDL_ICONV_EILSEQ;
					*/
					ch = UNKNOWN_UNICODE;
				}
				if ( (ch >= 0xD800 && ch <= 0xDFFF) ||
				     (ch == 0xFFFE || ch == 0xFFFF) ||
				     ch > 0x10FFFF ) {
					/* Skip illegal sequences
					return SDL_ICONV_EILSEQ;
					*/
					ch = UNKNOWN_UNICODE;
				}
			}
			break;
		    case ENCODING_UTF16BE: /* RFC 2781 */
			{
				Uint8 *p = (Uint8 *)src;
				Uint16 W1, W2;
				if ( srclen < 2 ) {
					return SDL_ICONV_EINVAL;
				}
				W1 = ((Uint16)p[0] << 8) |
				      (Uint16)p[1];
				src += 2;
				srclen -= 2;
				if ( W1 < 0xD800 || W1 > 0xDFFF ) {
					ch = (Uint32)W1;
					break;
				}
				if ( W1 > 0xDBFF ) {
					/* Skip illegal sequences
					return SDL_ICONV_EILSEQ;
					*/
					ch = UNKNOWN_UNICODE;
					break;
				}
				if ( srclen < 2 ) {
					return SDL_ICONV_EINVAL;
				}
				p = (Uint8 *)src;
				W2 = ((Uint16)p[0] << 8) |
				      (Uint16)p[1];
				src += 2;
				srclen -= 2;
				if ( W2 < 0xDC00 || W2 > 0xDFFF ) {
					/* Skip illegal sequences
					return SDL_ICONV_EILSEQ;
					*/
					ch = UNKNOWN_UNICODE;
					break;
				}
				ch = (((Uint32)(W1 & 0x3FF) << 10) |
				      (Uint32)(W2 & 0x3FF)) + 0x10000;
			}
			break;
		    case ENCODING_UTF16LE: /* RFC 2781 */
			{
				Uint8 *p = (Uint8 *)src;
				Uint16 W1, W2;
				if ( srclen < 2 ) {
					return SDL_ICONV_EINVAL;
				}
				W1 = ((Uint16)p[1] << 8) |
				      (Uint16)p[0];
				src += 2;
				srclen -= 2;
				if ( W1 < 0xD800 || W1 > 0xDFFF ) {
					ch = (Uint32)W1;
					break;
				}
				if ( W1 > 0xDBFF ) {
					/* Skip illegal sequences
					return SDL_ICONV_EILSEQ;
					*/
					ch = UNKNOWN_UNICODE;
					break;
				}
				if ( srclen < 2 ) {
					return SDL_ICONV_EINVAL;
				}
				p = (Uint8 *)src;
				W2 = ((Uint16)p[1] << 8) |
				      (Uint16)p[0];
				src += 2;
				srclen -= 2;
				if ( W2 < 0xDC00 || W2 > 0xDFFF ) {
					/* Skip illegal sequences
					return SDL_ICONV_EILSEQ;
					*/
					ch = UNKNOWN_UNICODE;
					break;
				}
				ch = (((Uint32)(W1 & 0x3FF) << 10) |
				      (Uint32)(W2 & 0x3FF)) + 0x10000;
			}
			break;
		    case ENCODING_UTF32BE:
			{
				Uint8 *p = (Uint8 *)src;
				if ( srclen < 4 ) {
					return SDL_ICONV_EINVAL;
				}
				ch = ((Uint32)p[0] << 24) |
				     ((Uint32)p[1] << 16) |
				     ((Uint32)p[2] << 8) |
				      (Uint32)p[3];
				src += 4;
				srclen -= 4;
			}
			break;
		    case ENCODING_UTF32LE:
			{
				Uint8 *p = (Uint8 *)src;
				if ( srclen < 4 ) {
					return SDL_ICONV_EINVAL;
				}
				ch = ((Uint32)p[3] << 24) |
				     ((Uint32)p[2] << 16) |
				     ((Uint32)p[1] << 8) |
				      (Uint32)p[0];
				src += 4;
				srclen -= 4;
			}
			break;
		    case ENCODING_UCS2:
			{
				Uint16 *p = (Uint16 *)src;
				if ( srclen < 2 ) {
					return SDL_ICONV_EINVAL;
				}
				ch = *p;
				src += 2;
				srclen -= 2;
			}
			break;
		    case ENCODING_UCS4:
			{
				Uint32 *p = (Uint32 *)src;
				if ( srclen < 4 ) {
					return SDL_ICONV_EINVAL;
				}
				ch = *p;
				src += 4;
				srclen -= 4;
			}
			break;
		}

		/* Encode a character */
		switch ( cd->dst_fmt ) {
		    case ENCODING_ASCII:
			{
				Uint8 *p = (Uint8 *)dst;
				if ( dstlen < 1 ) {
					return SDL_ICONV_E2BIG;
				}
				if ( ch > 0x7F ) {
					*p = UNKNOWN_ASCII;
				} else {
					*p = (Uint8)ch;
				}
				++dst;
				--dstlen;
			}
			break;
		    case ENCODING_LATIN1:
			{
				Uint8 *p = (Uint8 *)dst;
				if ( dstlen < 1 ) {
					return SDL_ICONV_E2BIG;
				}
				if ( ch > 0xFF ) {
					*p = UNKNOWN_ASCII;
				} else {
					*p = (Uint8)ch;
				}
				++dst;
				--dstlen;
			}
			break;
		    case ENCODING_UTF8: /* RFC 3629 */
			{
				Uint8 *p = (Uint8 *)dst;
				if ( ch > 0x10FFFF ) {
					ch = UNKNOWN_UNICODE;
				}
				if ( ch <= 0x7F ) {
					if ( dstlen < 1 ) {
						return SDL_ICONV_E2BIG;
					}
					*p = (Uint8)ch;
					++dst;
					--dstlen;
				} else if ( ch <= 0x7FF ) {
					if ( dstlen < 2 ) {
						return SDL_ICONV_E2BIG;
					}
					p[0] = 0xC0 | (Uint8)((ch >> 6) & 0x1F);
					p[1] = 0x80 | (Uint8)(ch & 0x3F);
					dst += 2;
					dstlen -= 2;
				} else if ( ch <= 0xFFFF ) {
					if ( dstlen < 3 ) {
						return SDL_ICONV_E2BIG;
					}
					p[0] = 0xE0 | (Uint8)((ch >> 12) & 0x0F);
					p[1] = 0x80 | (Uint8)((ch >> 6) & 0x3F);
					p[2] = 0x80 | (Uint8)(ch & 0x3F);
					dst += 3;
					dstlen -= 3;
				} else if ( ch <= 0x1FFFFF ) {
					if ( dstlen < 4 ) {
						return SDL_ICONV_E2BIG;
					}
					p[0] = 0xF0 | (Uint8)((ch >> 18) & 0x07);
					p[1] = 0x80 | (Uint8)((ch >> 12) & 0x3F);
					p[2] = 0x80 | (Uint8)((ch >> 6) & 0x3F);
					p[3] = 0x80 | (Uint8)(ch & 0x3F);
					dst += 4;
					dstlen -= 4;
				} else if ( ch <= 0x3FFFFFF ) {
					if ( dstlen < 5 ) {
						return SDL_ICONV_E2BIG;
					}
					p[0] = 0xF8 | (Uint8)((ch >> 24) & 0x03);
					p[1] = 0x80 | (Uint8)((ch >> 18) & 0x3F);
					p[2] = 0x80 | (Uint8)((ch >> 12) & 0x3F);
					p[3] = 0x80 | (Uint8)((ch >> 6) & 0x3F);
					p[4] = 0x80 | (Uint8)(ch & 0x3F);
					dst += 5;
					dstlen -= 5;
				} else {
					if ( dstlen < 6 ) {
						return SDL_ICONV_E2BIG;
					}
					p[0] = 0xFC | (Uint8)((ch >> 30) & 0x01);
					p[1] = 0x80 | (Uint8)((ch >> 24) & 0x3F);
					p[2] = 0x80 | (Uint8)((ch >> 18) & 0x3F);
					p[3] = 0x80 | (Uint8)((ch >> 12) & 0x3F);
					p[4] = 0x80 | (Uint8)((ch >> 6) & 0x3F);
					p[5] = 0x80 | (Uint8)(ch & 0x3F);
					dst += 6;
					dstlen -= 6;
				}
			}
			break;
		    case ENCODING_UTF16BE: /* RFC 2781 */
			{
				Uint8 *p = (Uint8 *)dst;
				if ( ch > 0x10FFFF ) {
					ch = UNKNOWN_UNICODE;
				}
				if ( ch < 0x10000 ) {
					if ( dstlen < 2 ) {
						return SDL_ICONV_E2BIG;
					}
					p[0] = (Uint8)(ch >> 8);
					p[1] = (Uint8)ch;
					dst += 2;
					dstlen -= 2;
				} else {
					Uint16 W1, W2;
					if ( dstlen < 4 ) {
						return SDL_ICONV_E2BIG;
					}
					ch = ch - 0x10000;
					W1 = 0xD800 | (Uint16)((ch >> 10) & 0x3FF);
					W2 = 0xDC00 | (Uint16)(ch & 0x3FF);
					p[0] = (Uint8)(W1 >> 8);
					p[1] = (Uint8)W1;
					p[2] = (Uint8)(W2 >> 8);
					p[3] = (Uint8)W2;
					dst += 4;
					dstlen -= 4;
				}
			}
			break;
		    case ENCODING_UTF16LE: /* RFC 2781 */
			{
				Uint8 *p = (Uint8 *)dst;
				if ( ch > 0x10FFFF ) {
					ch = UNKNOWN_UNICODE;
				}
				if ( ch < 0x10000 ) {
					if ( dstlen < 2 ) {
						return SDL_ICONV_E2BIG;
					}
					p[1] = (Uint8)(ch >> 8);
					p[0] = (Uint8)ch;
					dst += 2;
					dstlen -= 2;
				} else {
					Uint16 W1, W2;
					if ( dstlen < 4 ) {
						return SDL_ICONV_E2BIG;
					}
					ch = ch - 0x10000;
					W1 = 0xD800 | (Uint16)((ch >> 10) & 0x3FF);
					W2 = 0xDC00 | (Uint16)(ch & 0x3FF);
					p[1] = (Uint8)(W1 >> 8);
					p[0] = (Uint8)W1;
					p[3] = (Uint8)(W2 >> 8);
					p[2] = (Uint8)W2;
					dst += 4;
					dstlen -= 4;
				}
			}
			break;
		    case ENCODING_UTF32BE:
			{
				Uint8 *p = (Uint8 *)dst;
				if ( ch > 0x10FFFF ) {
					ch = UNKNOWN_UNICODE;
				}
				if ( dstlen < 4 ) {
					return SDL_ICONV_E2BIG;
				}
				p[0] = (Uint8)(ch >> 24);
				p[1] = (Uint8)(ch >> 16);
				p[2] = (Uint8)(ch >> 8);
				p[3] = (Uint8)ch;
				dst += 4;
				dstlen -= 4;
			}
			break;
		    case ENCODING_UTF32LE:
			{
				Uint8 *p = (Uint8 *)dst;
				if ( ch > 0x10FFFF ) {
					ch = UNKNOWN_UNICODE;
				}
				if ( dstlen < 4 ) {
					return SDL_ICONV_E2BIG;
				}
				p[3] = (Uint8)(ch >> 24);
				p[2] = (Uint8)(ch >> 16);
				p[1] = (Uint8)(ch >> 8);
				p[0] = (Uint8)ch;
				dst += 4;
				dstlen -= 4;
			}
			break;
		    case ENCODING_UCS2:
			{
				Uint16 *p = (Uint16 *)dst;
				if ( ch > 0xFFFF ) {
					ch = UNKNOWN_UNICODE;
				}
				if ( dstlen < 2 ) {
					return SDL_ICONV_E2BIG;
				}
				*p = (Uint16)ch;
				dst += 2;
				dstlen -= 2;
			}
			break;
		    case ENCODING_UCS4:
			{
				Uint32 *p = (Uint32 *)dst;
				if ( ch > 0x7FFFFFFF ) {
					ch = UNKNOWN_UNICODE;
				}
				if ( dstlen < 4 ) {
					return SDL_ICONV_E2BIG;
				}
				*p = ch;
				dst += 4;
				dstlen -= 4;
			}
			break;
		}

		/* Update state */
		*inbuf = src;
		*inbytesleft = srclen;
		*outbuf = dst;
		*outbytesleft = dstlen;
		++total;
	}
	return total;
}

int SDL_iconv_close(SDL_iconv_t cd)
{
	if ( cd && cd != (SDL_iconv_t)-1 ) {
		SDL_free(cd);
	}
	return 0;
}

#endif /* !HAVE_ICONV */

char *SDL_iconv_string(const char *tocode, const char *fromcode, const char *inbuf, size_t inbytesleft)
{
	SDL_iconv_t cd;
	char *string;
	size_t stringsize;
	char *outbuf;
	size_t outbytesleft;
	size_t retCode = 0;

	cd = SDL_iconv_open(tocode, fromcode);
	if ( cd == (SDL_iconv_t)-1 ) {
		/* See if we can recover here (fixes iconv on Solaris 11) */
		if ( !tocode || !*tocode ) {
			tocode = "UTF-8";
		}
		if ( !fromcode || !*fromcode ) {
			fromcode = "UTF-8";
		}
		cd = SDL_iconv_open(tocode, fromcode);
	}
	if ( cd == (SDL_iconv_t)-1 ) {
		return NULL;
	}

	stringsize = inbytesleft > 4 ? inbytesleft : 4;
	string = SDL_malloc(stringsize);
	if ( !string ) {
		SDL_iconv_close(cd);
		return NULL;
	}
	outbuf = string;
	outbytesleft = stringsize;
	SDL_memset(outbuf, 0, 4);

	while ( inbytesleft > 0 ) {
		retCode = SDL_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
		switch (retCode) {
		    case SDL_ICONV_E2BIG:
			{
				char *oldstring = string;
				stringsize *= 2;
				string = SDL_realloc(string, stringsize);
				if ( !string ) {
					SDL_iconv_close(cd);
					return NULL;
				}
				outbuf = string + (outbuf - oldstring);
				outbytesleft = stringsize - (outbuf - string);
				SDL_memset(outbuf, 0, 4);
			}
			break;
		    case SDL_ICONV_EILSEQ:
			/* Try skipping some input data - not perfect, but... */
			++inbuf;
			--inbytesleft;
			break;
		    case SDL_ICONV_EINVAL:
		    case SDL_ICONV_ERROR:
			/* We can't continue... */
			inbytesleft = 0;
			break;
		}
	}
	SDL_iconv_close(cd);

	return string;
}