/* -*- mode: c; tab-width: 4; c-basic-offset: 3; c-file-style: "linux" -*- */
//
// Copyright (c) 2008, Wei Mingzhi <whistler_wmz@users.sf.net>.
// All rights reserved.
//
// Portions based on PALx Project by palxex.
// Copyright (c) 2006-2008, Pal Lockheart <palxex@gmail.com>.
//
// This file is part of SDLPAL.
//
// SDLPAL 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/>.
//
// Modified by Lou Yihua <louyihua@21cn.com> with Unicode support, 2015
//

#include "main.h"

#define   FONT_COLOR_DEFAULT        0x4F
#define   FONT_COLOR_YELLOW         0x2D
#define   FONT_COLOR_RED            0x1A
#define   FONT_COLOR_CYAN           0x8D
#define   FONT_COLOR_CYAN_ALT       0x8C

BOOL      g_fUpdatedInBattle      = FALSE;

#define   MESSAGE_MAX_BUFFER_SIZE   512

#define INCLUDE_CODEPAGE_H
#include "codepage.h"

#ifndef PAL_CLASSIC
# define ATB_WORD_COUNT             6
static LPWSTR gc_rgszAdditionalWords[CP_MAX][ATB_WORD_COUNT] = {
   { L"\x6230\x9B25\x901F\x5EA6", L"\x4E00", L"\x4E8C", L"\x4E09", L"\x56DB", L"\x4E94" },
   { L"\x6218\x6597\x901F\x5EA6", L"\x4E00", L"\x4E8C", L"\x4E09", L"\x56DB", L"\x4E94" },
   //{ L"\x6226\x95D8\x901F\x5EA6", L"\x4E00", L"\x4E8C", L"\x4E09", L"\x56DB", L"\x4E94" },
};
static LPWSTR gc_rgszDefaultAdditionalWords[ATB_WORD_COUNT] = { NULL, L"\xFF11", L"\xFF12", L"\xFF13", L"\xFF14", L"\xFF15" };
#endif

#define SDLPAL_EXTRA_WORD_COUNT     1
static LPWSTR gc_rgszSDLPalWords[CP_MAX][SDLPAL_EXTRA_WORD_COUNT] = {
	{ L"\x555F\x52D5\x8A2D\x5B9A" },
	{ L"\x542F\x52A8\x8BBE\x7F6E" },
};

LPWSTR g_rcCredits[12];

typedef struct tagTEXTLIB
{
   LPWSTR         *lpWordBuf;
   LPWSTR         *lpMsgBuf;
   int           **lpIndexBuf;

   int             nWords;
   int             nMsgs;
   int             nIndices;

   int             nCurrentDialogLine;
   BYTE            bCurrentFontColor;
   PAL_POS         posIcon;
   PAL_POS         posDialogTitle;
   PAL_POS         posDialogText;
   BYTE            bDialogPosition;
   BYTE            bIcon;
   int             iDelayTime;
   BOOL            fUserSkip;
   BOOL            fPlayingRNG;

   BYTE            bufDialogIcons[282];
} TEXTLIB, *LPTEXTLIB;

static TEXTLIB         g_TextLib;

PAL_FORCE_INLINE int
PAL_ParseLine(
	char     *line,
	char    **value,
	int      *length,
	int       deltrail
	)
{
	//
	// Remove the leading spaces
	//
	while (*line && iswspace(*line)) line++;
	//
	// Skip comments starting with '#'
	//
	if (*line && *line != '#')
	{
		//
		// Split the index and value
		//
		LPSTR val = strchr(line, '=');
		if (val)
		{
			//
			// Remove the trailing spaces
			//
			LPSTR end = line + strlen(line);
			int index;
			if (end > line && end[-1] == '\n') *(--end) = 0;
			if (deltrail) while (end > line && iswspace(end[-1])) *(--end) = 0;

			//
			// Parse the index and pass out value
			//
			if (sscanf(line, "%d", &index) == 1)
			{
				*value = val + 1;
				*length = end - *value;
				return index;
			}
		}
	}
	return 0;
}

PAL_FORCE_INLINE char *
PAL_ReadOneLine(
	char     *temp,
	int      limit,
	FILE     *fp
	)
{
	if (fgets(temp, limit, fp))
	{
		int n = strlen(temp);
		if (n == limit - 1 && temp[n - 1] != '\n' && !feof(fp))
		{
			// Line too long, try to read it as a whole
			int nn = 2;
			char *tmp = strdup(temp);
			while (!feof(fp))
			{
				if (!(tmp = (char *)realloc(tmp, nn * limit)))
				{
					TerminateOnError("PAL_ReadOneLine(): failed to allocate memory for long line!");
				}
				if (fgets(tmp + n, limit + 1, fp))
				{
					n += strlen(tmp + n);
					if (n < limit - 1 || temp[n - 1] == '\n')
						break;
					else
						nn++;
				}
			}
			if (tmp[n - 1] == '\n') tmp[n - 1] = 0;
			return tmp;
		}
		else
		{
			while (n > 0 && (temp[n - 1] == '\n' || temp[n - 1] == '\r')) temp[--n] = 0;
			return temp;
		}
	}
	else
		return NULL;
}

static int
PAL_ReadMessageFile(
	FILE     *fp
	)
{
	char temp[MESSAGE_MAX_BUFFER_SIZE];
	struct _msg_entry
	{
		struct _msg_entry *next;
		wchar_t *value;
	} *cur_val = NULL;
	struct _msg_list_entry
	{
		struct _msg_list_entry *next;
		struct _msg_entry *value;
		int index;
		int count;
	} *head = NULL, *item = NULL;
	struct _word_list_entry
	{
		struct _word_list_entry *next;
		wchar_t *value;
		int index;
	} whead = { NULL, NULL }, *witem = NULL;
	enum _message_state
	{
		ST_OUTSIDE,
		ST_DIALOG,
		ST_WORD,
		ST_CREDIT,
		ST_LAYOUT
	} state = ST_OUTSIDE;
	int idx_cnt = 0, msg_cnt = 0, word_cnt = 0, sid, eid = -1;

	while (!feof(fp))
	{
		char *buffer;
		if (buffer = PAL_ReadOneLine(temp, MESSAGE_MAX_BUFFER_SIZE, fp))
		{
			switch(state)
			{
			case ST_OUTSIDE:
				//
				// Skip comments starting with '#'
				//
				if (*buffer && *buffer != '#')
				{
					if (strncmp(buffer, "[BEGIN MESSAGE]", 15) == 0 &&
						sscanf(buffer + 15, "%d", &sid) == 1 && sid > eid)
					{
						state = ST_DIALOG;
						//
						// First save values (converted wide string) into a linked list
						//
						if (head)
						{
							item->next = (struct _msg_list_entry *)UTIL_malloc(sizeof(struct _msg_list_entry));
							item = item->next;
						}
						else
						{
							head = (struct _msg_list_entry *)UTIL_malloc(sizeof(struct _msg_list_entry));
							item = head;
						}
						item->value = NULL; item->index = sid;
						item->count = 0; item->next = NULL; cur_val = NULL;
						if (idx_cnt < item->index) idx_cnt = item->index;
					}
					else if (strncmp(buffer, "[BEGIN WORDS]", 13) == 0 && !witem)
					{
						state = ST_WORD;
						//
						// First save values (converted wide string) into a linked list
						//
						witem = &whead;
					}
					else if (strncmp(buffer, "[BEGIN CREDITS]", 15) == 0 && !witem)
					{
						state = ST_CREDIT;
					}
					else if (strncmp(buffer, "[BEGIN LAYOUT]", 14) == 0 && !witem)
					{
						state = ST_LAYOUT;
						gConfig.fUseCustomScreenLayout = TRUE;
					}
					else
					{
						// Just ignore invalid lines
#ifdef ENABLE_LOG
						UTIL_WriteLog(LOG_ERR, "PAL_ReadMessageFile(): encounter invalid line '%s'!\n", line);
#endif
					}
				}
				break;
			case ST_DIALOG:
				//
				// Check if to end one dialog
				//
				if (strncmp(buffer, "[END MESSAGE]", 13) == 0 &&
					sscanf(buffer + 13, "%d", &eid) == 1 && eid >= sid)
				{
					// End dialog
					state = ST_OUTSIDE;
				}
				else
				{
					if (cur_val)
					{
						cur_val->next = (struct _msg_entry *)UTIL_malloc(sizeof(struct _msg_entry));
						cur_val = cur_val->next;
					}
					else
						cur_val = (struct _msg_entry *)UTIL_malloc(sizeof(struct _msg_entry));
					if (strncmp(buffer, "[CLEAR MESSAGE]", 15) == 0)
					{
						cur_val->value = NULL;
					}
					else
					{
						int len = PAL_MultiByteToWideCharCP(CP_UTF_8, buffer, -1, NULL, 0);
						cur_val->value = (wchar_t *)UTIL_malloc(len * sizeof(wchar_t));
						PAL_MultiByteToWideCharCP(CP_UTF_8, buffer, -1, cur_val->value, len);
						msg_cnt++;
					}
					if (!item->value) item->value = cur_val;
					cur_val->next = NULL; item->count++;
				}
				break;
			case ST_WORD:
				//
				// Check if to end word list
				//
				if (strncmp(buffer, "[END WORDS]", 11) == 0)
				{
					// End word list
					state = ST_OUTSIDE;
				}
				else
				{
					char *v;
					int l, i = PAL_ParseLine(buffer, &v, &l, TRUE);
					if (i > 0)
					{
						int len = PAL_MultiByteToWideCharCP(CP_UTF_8, v, -1, NULL, 0);
						struct _word_list_entry *val = (struct _word_list_entry *)UTIL_malloc(sizeof(struct _word_list_entry));
						val->value = (wchar_t *)UTIL_malloc(len * sizeof(wchar_t));
						PAL_MultiByteToWideCharCP(CP_UTF_8, v, -1, val->value, len);
						val->index = i; val->next = NULL;
						witem->next = val; witem = witem->next;
						if (word_cnt < i) word_cnt = i;
					}
				}
				break;
			case ST_CREDIT:
				//
				// Check if to end credit list
				//
				if (strncmp(buffer, "[END CREDITS]", 13) == 0)
				{
					// End credit list
					state = ST_OUTSIDE;
				}
				else
				{
					char *v;
					int l, i = PAL_ParseLine(buffer, &v, &l, FALSE);
					if ((i == 1 || (i >= 6 && i <= 11)) && !g_rcCredits[i])
					{
						int limit = (i == 1) ? 24 * 8 : 40 * 8, w = 0, j = 0, len;
						if (i == 6 || i == 7)
						{
							if (PAL_PLATFORM && PAL_CREDIT && PAL_PORTYEAR)
							{
								const char *templates[] = { "${platform}", "${author}", "${year}" };
								const char *values[] = { PAL_PLATFORM, PAL_CREDIT, PAL_PORTYEAR };
								const int matchlen[] = { 11, 9, 7 };
								const int valuelen[] = { sizeof(PAL_PLATFORM) - 1, sizeof(PAL_CREDIT) - 1, sizeof(PAL_PORTYEAR) - 1 };
								char *tmp = (char *)alloca(valuelen[0] + valuelen[1] + valuelen[2] + l + 1);
								char *dst = tmp, *src = v;
								while (*src)
								{
									if (*src == '$')
									{
										int k;
										for (k = 0; k < 3 && strncmp(src, templates[k], matchlen[k]); k++);
										if (k < 3)
										{
											strcpy(dst, values[k]);
											dst += valuelen[k];
											src += matchlen[k];
											continue;
										}
									}
									*dst++ = *src++;
								}
								*dst = 0;
								len = PAL_MultiByteToWideCharCP(CP_UTF_8, tmp, -1, NULL, 0);
								g_rcCredits[i] = (wchar_t *)UTIL_malloc(len * sizeof(wchar_t));
								PAL_MultiByteToWideCharCP(CP_UTF_8, tmp, -1, g_rcCredits[i], len);
							}
						}
						else
						{
							len = PAL_MultiByteToWideCharCP(CP_UTF_8, v, -1, NULL, 0);
							g_rcCredits[i] = (wchar_t *)UTIL_malloc(len * sizeof(wchar_t));
							PAL_MultiByteToWideCharCP(CP_UTF_8, v, -1, g_rcCredits[i], len);
						}
						if (g_rcCredits[i])
						{
							// Limit the length of texts
							while (w < limit && j < len - 1) w += PAL_CharWidth(g_rcCredits[i][j++]);
							if (w >= limit) g_rcCredits[i][w > limit ? j - 1 : j] = 0;
						}
					}
				}
				break;
			case ST_LAYOUT:
				if (strncmp(buffer, "[END LAYOUT]", 12) == 0)
				{
					// End layout
					state = ST_OUTSIDE;
				}
				else
				{
					char *v;
					int x, y, f, n, l, i = PAL_ParseLine(buffer, &v, &l, FALSE);
					if (i >= 1 && i <= (sizeof(SCREENLAYOUT) / sizeof(PAL_POS)))
					{
						if ((n = sscanf(v, "%d,%d,%d", &x, &y, &f)) >= 2 && x < 320 && y < 200)
						{
							gConfig.ScreenLayoutArray[i - 1] = PAL_XY(x, y);
							if (n == 3) gConfig.ScreenLayoutFlag[i - 1] = f;
						}
					}
				}
				break;
			default:
				TerminateOnError("PAL_ReadMessageFile(): Reached an unknown state. Something really wrong may have happened!");
				break;
			}

			if (buffer != temp) free(buffer);
		}
	}

	if (msg_cnt > 0)
	{
		//
		// Move values from linked list to array
		//
		int idx_msg = 1;
		g_TextLib.nIndices = (idx_cnt += 1);
		g_TextLib.nMsgs = (msg_cnt += 1);
		g_TextLib.lpIndexBuf = (int **)UTIL_calloc(idx_cnt, sizeof(int *));
		g_TextLib.lpMsgBuf = (LPWSTR *)UTIL_calloc(msg_cnt, sizeof(LPWSTR));
		for (item = head; item; )
		{
			struct _msg_list_entry *temp = item->next;
			struct _msg_entry *msg = item->value;
			int index = 0;
			g_TextLib.lpIndexBuf[item->index] = (int *)UTIL_calloc(item->count + 1, sizeof(int));
			while (msg)
			{
				struct _msg_entry *tmp = msg->next;
				if (msg->value)
				{
					g_TextLib.lpIndexBuf[item->index][index++] = idx_msg;
					g_TextLib.lpMsgBuf[idx_msg++] = msg->value;
				}
				else
					g_TextLib.lpIndexBuf[item->index][index++] = 0;
				free(msg); msg = tmp;
			}
			g_TextLib.lpIndexBuf[item->index][item->count] = -1;
			free(item); item = temp;
		}
	}

	if (word_cnt > 0)
	{
		//
		// Move values from linked list to array
		//
#ifndef PAL_CLASSIC
		int i;
#endif
		if (word_cnt < MINIMAL_WORD_COUNT - 1) word_cnt = MINIMAL_WORD_COUNT - 1;
		g_TextLib.nWords = (word_cnt += 1);
		g_TextLib.lpWordBuf = (LPWSTR *)UTIL_calloc(word_cnt, sizeof(LPWSTR));
		for (witem = whead.next; witem; )
		{
			struct _word_list_entry *temp = witem->next;
			g_TextLib.lpWordBuf[witem->index] = witem->value;
			free(witem); witem = temp;
		}
#ifndef PAL_CLASSIC
		for (i = 1; i < ATB_WORD_COUNT; i++)
			if (!g_TextLib.lpWordBuf[i + SYSMENU_LABEL_BATTLEMODE])
				g_TextLib.lpWordBuf[i + SYSMENU_LABEL_BATTLEMODE] = gc_rgszDefaultAdditionalWords[i];
#endif
	}

	fclose(fp);

	return (msg_cnt > 0 && word_cnt > 0) ? 1 : 0;
}

INT
PAL_InitText(
   VOID
)
/*++
  Purpose:

    Initialize the in-game texts.

  Parameters:

    None.

  Return value:

    0 = success.
    -1 = memory allocation error.

--*/
{
   if (gConfig.pszMsgFile)
   {
	   //
	   // Open the message, index and word data files.
	   //
	   FILE *fp = UTIL_OpenRequiredFileForMode(gConfig.pszMsgFile, "r");

	   //
	   // Read the contents of the message, index and word data files.
	   //
	   if (!PAL_ReadMessageFile(fp))
	   {
		   return -1;
	   }
	   else
	   {
		   DWORD dwWordLength = 0;
		   int i;
		   for (i = 1; i < g_TextLib.nWords; i++)
		   {
			   if (g_TextLib.lpWordBuf[i])
			   {
				   LPWSTR ptr = g_TextLib.lpWordBuf[i];
				   DWORD n = 0;
				   while (*ptr) n += PAL_CharWidth(*ptr++) >> 3;
				   if (dwWordLength < n) dwWordLength = n;
			   }
		   }
		   gConfig.dwWordLength = dwWordLength;
		   for (i = 0; i < 12; i++)
		   {
			   if (!g_rcCredits[i])
				   g_rcCredits[i] = L"";
		   }
	   }
   }
   else
   {
	   FILE       *fpMsg, *fpWord;
	   DWORD      *offsets;
	   LPWSTR      tmp;
	   LPBYTE      temp;
	   int         wpos, wlen, i;

	   //
	   // Open the message and word data files.
	   //
	   fpMsg = UTIL_OpenRequiredFile("m.msg");
	   fpWord = UTIL_OpenRequiredFile("word.dat");

	   //
	   // See how many words we have
	   //
	   fseek(fpWord, 0, SEEK_END);
	   i = ftell(fpWord);

	   //
	   // Each word has 10 bytes
	   //
	   g_TextLib.nWords = (i + (gConfig.dwWordLength - 1)) / gConfig.dwWordLength;
	   if (g_TextLib.nWords < MINIMAL_WORD_COUNT) g_TextLib.nWords = MINIMAL_WORD_COUNT;

	   //
	   // Read the words
	   //
	   temp = (LPBYTE)malloc(gConfig.dwWordLength * g_TextLib.nWords);
	   if (temp == NULL)
	   {
		   fclose(fpWord);
		   fclose(fpMsg);
		   return -1;
	   }
	   fseek(fpWord, 0, SEEK_SET);
	   fread(temp, i, 1, fpWord);
	   memset(temp + i, 0, gConfig.dwWordLength * g_TextLib.nWords - i);

	   //
	   // Close the words file
	   //
	   fclose(fpWord);

	   // Split the words and do code page conversion
	   for (i = 0, wlen = 0; i < g_TextLib.nWords; i++)
	   {
		   int base = i * gConfig.dwWordLength;
		   int pos = base + gConfig.dwWordLength - 1;
		   while (pos >= base && temp[pos] == ' ') temp[pos--] = 0;
		   wlen += PAL_MultiByteToWideChar((LPCSTR)temp + base, gConfig.dwWordLength, NULL, 0) + 1;
	   }
	   g_TextLib.lpWordBuf = (LPWSTR*)malloc(g_TextLib.nWords * sizeof(LPWSTR));
	   if (g_TextLib.lpWordBuf == NULL)
	   {
		   free(temp);
		   fclose(fpWord);
		   fclose(fpMsg);
		   return -1;
	   }
	   tmp = (LPWSTR)malloc(wlen * sizeof(WCHAR));
	   if (tmp == NULL)
	   {
		   free(g_TextLib.lpWordBuf);
		   free(temp);
		   fclose(fpWord);
		   fclose(fpMsg);
		   return -1;
	   }
	   for (i = 0, wpos = 0; i < g_TextLib.nWords; i++)
	   {
		   int l;
		   g_TextLib.lpWordBuf[i] = tmp + wpos;
		   l = PAL_MultiByteToWideChar((LPCSTR)temp + i * gConfig.dwWordLength, gConfig.dwWordLength, g_TextLib.lpWordBuf[i], wlen - wpos);
		   if (l > 0 && g_TextLib.lpWordBuf[i][l - 1] == '1')
			   g_TextLib.lpWordBuf[i][l - 1] = 0;
		   g_TextLib.lpWordBuf[i][l] = 0;
		   wpos += l + 1;
	   }
	   free(temp);

	   //
	   // Read the message offsets. The message offsets are in SSS.MKF #3
	   //
	   i = PAL_MKFGetChunkSize(3, gpGlobals->f.fpSSS) / sizeof(DWORD);
	   g_TextLib.nMsgs = i - 1;

	   offsets = (LPDWORD)malloc(i * sizeof(DWORD));
	   if (offsets == NULL)
	   {
		   free(g_TextLib.lpWordBuf);
		   fclose(fpMsg);
		   return -1;
	   }

	   PAL_MKFReadChunk((LPBYTE)(offsets), i * sizeof(DWORD), 3, gpGlobals->f.fpSSS);

	   //
	   // Read the messages.
	   //
	   fseek(fpMsg, 0, SEEK_END);
	   i = ftell(fpMsg);

	   temp = (LPBYTE)malloc(i);
	   if (temp == NULL)
	   {
		   free(offsets);
		   free(g_TextLib.lpWordBuf[0]);
		   free(g_TextLib.lpWordBuf);
		   fclose(fpMsg);
		   return -1;
	   }

	   fseek(fpMsg, 0, SEEK_SET);
	   fread(temp, 1, i, fpMsg);

	   fclose(fpMsg);

	   // Split messages and do code page conversion here
	   for (i = 0, wlen = 0; i < g_TextLib.nMsgs; i++)
	   {
		   wlen += PAL_MultiByteToWideChar((LPCSTR)temp + SDL_SwapLE32(offsets[i]), SDL_SwapLE32(offsets[i + 1]) - SDL_SwapLE32(offsets[i]), NULL, 0) + 1;
	   }
	   g_TextLib.lpMsgBuf = (LPWSTR*)malloc(g_TextLib.nMsgs * sizeof(LPWSTR));
	   if (g_TextLib.lpMsgBuf == NULL)
	   {
		   free(g_TextLib.lpWordBuf);
		   free(offsets);
		   return -1;
	   }
	   tmp = (LPWSTR)malloc(wlen * sizeof(WCHAR));
	   if (tmp == NULL)
	   {
		   free(g_TextLib.lpMsgBuf);
		   free(g_TextLib.lpWordBuf);
		   free(offsets);
		   return -1;
	   }
	   for (i = 0, wpos = 0; i < g_TextLib.nMsgs; i++)
	   {
		   int l;
		   g_TextLib.lpMsgBuf[i] = tmp + wpos;
		   l = PAL_MultiByteToWideChar((LPCSTR)temp + SDL_SwapLE32(offsets[i]), SDL_SwapLE32(offsets[i + 1]) - SDL_SwapLE32(offsets[i]), g_TextLib.lpMsgBuf[i], wlen - wpos);
		   g_TextLib.lpMsgBuf[i][l] = 0;
		   wpos += l + 1;
	   }
	   free(temp);
	   free(offsets);

	   g_TextLib.lpIndexBuf = NULL;

	   memcpy(g_TextLib.lpWordBuf + SYSMENU_LABEL_LAUNCHSETTING, gc_rgszSDLPalWords[gConfig.uCodePage], SDLPAL_EXTRA_WORD_COUNT * sizeof(LPCWSTR));

#ifndef PAL_CLASSIC
	   memcpy(g_TextLib.lpWordBuf + SYSMENU_LABEL_BATTLEMODE, gc_rgszAdditionalWords[gConfig.uCodePage], ATB_WORD_COUNT * sizeof(LPCWSTR));
#endif
   }

   g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT;
   g_TextLib.bIcon = 0;
   g_TextLib.posIcon = 0;
   g_TextLib.nCurrentDialogLine = 0;
   g_TextLib.iDelayTime = 3;
   g_TextLib.posDialogTitle = PAL_XY(12, 8);
   g_TextLib.posDialogText = PAL_XY(44, 26);
   g_TextLib.bDialogPosition = kDialogUpper;
   g_TextLib.fUserSkip = FALSE;

   PAL_MKFReadChunk(g_TextLib.bufDialogIcons, 282, 12, gpGlobals->f.fpDATA);

   return 0;
}

VOID
PAL_FreeText(
   VOID
)
/*++
  Purpose:

    Free the memory used by the texts.

  Parameters:

    None.

  Return value:

    None.

--*/
{
   int i;
   if (g_TextLib.lpMsgBuf != NULL)
   {
      if (gConfig.pszMsgFile)
         for(i = 0; i < g_TextLib.nMsgs; i++) free(g_TextLib.lpMsgBuf[i]);
      else
         free(g_TextLib.lpMsgBuf[0]);
      free(g_TextLib.lpMsgBuf);
      g_TextLib.lpMsgBuf = NULL;
   }
   if (g_TextLib.lpWordBuf != NULL)
   {
      if (gConfig.pszMsgFile)
         for(i = 0; i < g_TextLib.nWords; i++) free(g_TextLib.lpWordBuf[i]);
      else
         free(g_TextLib.lpWordBuf[0]);
      free(g_TextLib.lpWordBuf);
      g_TextLib.lpWordBuf = NULL;
   }
   if (g_TextLib.lpIndexBuf != NULL)
   {
      if (gConfig.pszMsgFile)
         for(i = 0; i < g_TextLib.nIndices; i++) free(g_TextLib.lpIndexBuf[i]);
      else
         free(g_TextLib.lpIndexBuf[0]);
      free(g_TextLib.lpIndexBuf);
      g_TextLib.lpIndexBuf = NULL;
   }
}

LPCWSTR
PAL_GetWord(
   int        iNumWord
)
/*++
  Purpose:

    Get the specified word.

  Parameters:

    [IN]  wNumWord - the number of the requested word.

  Return value:

    Pointer to the requested word. NULL if not found.

--*/
{
   return (iNumWord >= g_TextLib.nWords || !g_TextLib.lpWordBuf[iNumWord]) ? L"" : g_TextLib.lpWordBuf[iNumWord];
}

LPCWSTR
PAL_GetMsg(
   int        iNumMsg
)
/*++
  Purpose:

    Get the specified message.

  Parameters:

    [IN]  wNumMsg - the number of the requested message.

  Return value:

    Pointer to the requested message. NULL if not found.

--*/
{
   return (iNumMsg >= g_TextLib.nMsgs || !g_TextLib.lpMsgBuf[iNumMsg]) ? L"" : g_TextLib.lpMsgBuf[iNumMsg];
}

int
PAL_GetMsgNum(
   int        iIndex,
   int        iOrder
)
/*++
  Purpose:

    Get the number of specified message from index & order.

  Parameters:

    [IN]  iMsgIndex - index.
	[IN]  iOrder - order inside the index.

  Return value:

    The number of message. Zero means pausing for key, and -1 means end.

--*/
{
   return (iIndex >= g_TextLib.nMsgs || !g_TextLib.lpIndexBuf[iIndex]) ? -1 : g_TextLib.lpIndexBuf[iIndex][iOrder];
}

VOID
PAL_DrawText(
   LPCWSTR    lpszText,
   PAL_POS    pos,
   BYTE       bColor,
   BOOL       fShadow,
   BOOL       fUpdate,
   BOOL       fUse8x8Font
)
/*++
  Purpose:

    Draw text on the screen.

  Parameters:

    [IN]  lpszText - the text to be drawn.

    [IN]  pos - Position of the text.

    [IN]  bColor - Color of the text.

    [IN]  fShadow - TRUE if the text is shadowed or not.

    [IN]  fUpdate - TRUE if update the screen area.

	[IN]  fUse8x8Font - TRUE if use 8x8 font.

  Return value:

    None.

--*/
{
   SDL_Rect   rect, urect;

   urect.x = rect.x = PAL_X(pos);
   urect.y = rect.y = PAL_Y(pos);
   urect.h = (fUse8x8Font ? 8 : PAL_FontHeight()) + (fShadow ? 1 : 0);
   urect.w = 0;

   while (*lpszText)
   {
      //
      // Draw the character
      //
	  int char_width = fUse8x8Font ? 8 : PAL_CharWidth(*lpszText);

      if (fShadow)
      {
		  PAL_DrawCharOnSurface(*lpszText, gpScreen, PAL_XY(rect.x + 1, rect.y + 1), 0, fUse8x8Font);
		  PAL_DrawCharOnSurface(*lpszText, gpScreen, PAL_XY(rect.x + 1, rect.y), 0, fUse8x8Font);
      }
	  PAL_DrawCharOnSurface(*lpszText++, gpScreen, PAL_XY(rect.x, rect.y), bColor, fUse8x8Font);
	  rect.x += char_width; urect.w += char_width;
   }

   //
   // Update the screen area
   //
   if (fUpdate && urect.w > 0)
   {
      if (fShadow) urect.w++;
      if (urect.x + urect.w > 320)
      {
         urect.w = 320 - urect.x;
      }
      VIDEO_UpdateScreen(&urect);
   }
}

VOID
PAL_DialogSetDelayTime(
   INT          iDelayTime
)
/*++
  Purpose:

    Set the delay time for dialog.

  Parameters:

    [IN]  iDelayTime - the delay time to be set.

  Return value:

    None.

--*/
{
   g_TextLib.iDelayTime = iDelayTime;
}

VOID
PAL_StartDialog(
   BYTE         bDialogLocation,
   BYTE         bFontColor,
   INT          iNumCharFace,
   BOOL         fPlayingRNG
)
/*++
  Purpose:

    Start a new dialog.

  Parameters:

    [IN]  bDialogLocation - the location of the text on the screen.

    [IN]  bFontColor - the font color of the text.

    [IN]  iNumCharFace - number of the character face in RGM.MKF.

    [IN]  fPlayingRNG - whether we are playing a RNG video or not.

  Return value:

    None.

--*/
{
   PAL_LARGE BYTE buf[16384];
   SDL_Rect       rect;

   if (gpGlobals->fInBattle && !g_fUpdatedInBattle)
   {
      //
      // Update the screen in battle, or the graphics may seem messed up
      //
      VIDEO_UpdateScreen(NULL);
      g_fUpdatedInBattle = TRUE;
   }

   g_TextLib.bIcon = 0;
   g_TextLib.posIcon = 0;
   g_TextLib.nCurrentDialogLine = 0;
   g_TextLib.posDialogTitle = PAL_XY(12, 8);
   g_TextLib.fUserSkip = FALSE;

   if (bFontColor != 0)
   {
      g_TextLib.bCurrentFontColor = bFontColor;
   }

   if (fPlayingRNG && iNumCharFace)
   {
      VIDEO_BackupScreen();
      g_TextLib.fPlayingRNG = TRUE;
   }

   switch (bDialogLocation)
   {
   case kDialogUpper:
      if (iNumCharFace > 0)
      {
         //
         // Display the character face at the upper part of the screen
         //
         if (PAL_MKFReadChunk(buf, 16384, iNumCharFace, gpGlobals->f.fpRGM) > 0)
         {
            rect.w = PAL_RLEGetWidth((LPCBITMAPRLE)buf);
            rect.h = PAL_RLEGetHeight((LPCBITMAPRLE)buf);
            rect.x = 48 - rect.w / 2;
            rect.y = 55 - rect.h / 2;

            if (rect.x < 0)
            {
               rect.x = 0;
            }

            if (rect.y < 0)
            {
               rect.y = 0;
            }

            PAL_RLEBlitToSurface((LPCBITMAPRLE)buf, gpScreen, PAL_XY(rect.x, rect.y));

            if (rect.x < 0)
            {
               rect.x = 0;
            }
            if (rect.y < 0)
            {
               rect.y = 0;
            }

            VIDEO_UpdateScreen(&rect);
         }
      }
      g_TextLib.posDialogTitle = PAL_XY(iNumCharFace > 0 ? 80 : 12, 8);
      g_TextLib.posDialogText = PAL_XY(iNumCharFace > 0 ? 96 : 44, 26);
      break;

   case kDialogCenter:
      g_TextLib.posDialogText = PAL_XY(80, 40);
      break;

   case kDialogLower:
      if (iNumCharFace > 0)
      {
         //
         // Display the character face at the lower part of the screen
         //
         if (PAL_MKFReadChunk(buf, 16384, iNumCharFace, gpGlobals->f.fpRGM) > 0)
         {
            rect.x = 270 - PAL_RLEGetWidth((LPCBITMAPRLE)buf) / 2;
            rect.y = 144 - PAL_RLEGetHeight((LPCBITMAPRLE)buf) / 2;

            PAL_RLEBlitToSurface((LPCBITMAPRLE)buf, gpScreen, PAL_XY(rect.x, rect.y));

            VIDEO_UpdateScreen(NULL);
         }
      }
      g_TextLib.posDialogTitle = PAL_XY(iNumCharFace > 0 ? 4 : 12, 108);
      g_TextLib.posDialogText = PAL_XY(iNumCharFace > 0 ? 20 : 44, 126);
      break;

   case kDialogCenterWindow:
      g_TextLib.posDialogText = PAL_XY(160, 40);
      break;
   }

   g_TextLib.bDialogPosition = bDialogLocation;
}

static VOID
PAL_DialogWaitForKey(
   VOID
)
/*++
  Purpose:

    Wait for player to press a key after showing a dialog.

  Parameters:

    None.

  Return value:

    None.

--*/
{
   PAL_LARGE SDL_Color   palette[256];
   SDL_Color   *pCurrentPalette, t;
   int         i;

   //
   // get the current palette
   //
   pCurrentPalette = PAL_GetPalette(gpGlobals->wNumPalette, gpGlobals->fNightPalette);
   memcpy(palette, pCurrentPalette, sizeof(palette));

   if (g_TextLib.bDialogPosition != kDialogCenterWindow &&
      g_TextLib.bDialogPosition != kDialogCenter)
   {
      //
      // show the icon
      //
      LPCBITMAPRLE p = PAL_SpriteGetFrame(g_TextLib.bufDialogIcons, g_TextLib.bIcon);
      if (p != NULL)
      {
         SDL_Rect rect;

         rect.x = PAL_X(g_TextLib.posIcon);
         rect.y = PAL_Y(g_TextLib.posIcon);
         rect.w = 16;
         rect.h = 16;

         PAL_RLEBlitToSurface(p, gpScreen, g_TextLib.posIcon);
         VIDEO_UpdateScreen(&rect);
      }
   }

   PAL_ClearKeyState();

   while (TRUE)
   {
      UTIL_Delay(100);

      if (g_TextLib.bDialogPosition != kDialogCenterWindow &&
         g_TextLib.bDialogPosition != kDialogCenter)
      {
         //
         // palette shift
         //
         t = palette[0xF9];
         for (i = 0xF9; i < 0xFE; i++)
         {
            palette[i] = palette[i + 1];
         }
         palette[0xFE] = t;

         VIDEO_SetPalette(palette);
      }

      if (g_InputState.dwKeyPress != 0)
      {
         break;
      }
   }

   if (g_TextLib.bDialogPosition != kDialogCenterWindow &&
      g_TextLib.bDialogPosition != kDialogCenter)
   {
      PAL_SetPalette(gpGlobals->wNumPalette, gpGlobals->fNightPalette);
   }

   PAL_ClearKeyState();

   g_TextLib.fUserSkip = FALSE;
}

VOID
PAL_ShowDialogText(
   LPCWSTR      lpszText
)
/*++
  Purpose:

    Show one line of the dialog text.

  Parameters:

    [IN]  lpszText - the text to be shown.

  Return value:

    None.

--*/
{
   SDL_Rect        rect;
   int             x, y;

   PAL_ClearKeyState();
   g_TextLib.bIcon = 0;

   if (gpGlobals->fInBattle && !g_fUpdatedInBattle)
   {
      //
      // Update the screen in battle, or the graphics may seem messed up
      //
      VIDEO_UpdateScreen(NULL);
      g_fUpdatedInBattle = TRUE;
   }

   if (g_TextLib.nCurrentDialogLine > 3)
   {
      //
      // The rest dialogs should be shown in the next page.
      //
      PAL_DialogWaitForKey();
      g_TextLib.nCurrentDialogLine = 0;
      VIDEO_RestoreScreen();
      VIDEO_UpdateScreen(NULL);
   }

   x = PAL_X(g_TextLib.posDialogText);
   y = PAL_Y(g_TextLib.posDialogText) + g_TextLib.nCurrentDialogLine * 18;

   if (g_TextLib.bDialogPosition == kDialogCenterWindow)
   {
      //
      // The text should be shown in a small window at the center of the screen
      //
#ifndef PAL_CLASSIC
      if (gpGlobals->fInBattle && g_Battle.BattleResult == kBattleResultOnGoing)
      {
         PAL_BattleUIShowText(lpszText, 1400);
      }
      else
#endif
      {
         PAL_POS    pos;
         LPBOX      lpBox;
		 int        i, w = wcslen(lpszText), len = 0;

		 for (i = 0; i < w; i++)
            len += PAL_CharWidth(lpszText[i]) >> 3;
         //
         // Create the window box
         //
         pos = PAL_XY(PAL_X(g_TextLib.posDialogText) - len * 4, PAL_Y(g_TextLib.posDialogText));
         lpBox = PAL_CreateSingleLineBox(pos, (len + 1) / 2, TRUE);

         rect.x = PAL_X(pos);
         rect.y = PAL_Y(pos);
         rect.w = 320 - rect.x * 2 + 32;
         rect.h = 64;

         //
         // Show the text on the screen
         //
         pos = PAL_XY(PAL_X(pos) + 8 + ((len & 1) << 2), PAL_Y(pos) + 10);
         PAL_DrawText(lpszText, pos, 0, FALSE, FALSE, FALSE);
         VIDEO_UpdateScreen(&rect);

         PAL_DialogWaitForKey();

         //
         // Delete the box
         //
         PAL_DeleteBox(lpBox);
         VIDEO_UpdateScreen(&rect);

         PAL_EndDialog();
      }
   }
   else
   {
      int len = wcslen(lpszText);
      if (g_TextLib.nCurrentDialogLine == 0 &&
          g_TextLib.bDialogPosition != kDialogCenter &&
		  (lpszText[len - 1] == 0xff1a ||
		   lpszText[len - 1] == 0x2236 || // Special case for Pal WIN95 Simplified Chinese version
		   lpszText[len - 1] == ':')
		 )
      {
         //
         // name of character
         //
         PAL_DrawText(lpszText, g_TextLib.posDialogTitle, FONT_COLOR_CYAN_ALT, TRUE, TRUE, FALSE);
      }
      else
      {
         //
         // normal texts
         //
         WCHAR text[2];

         if (!g_TextLib.fPlayingRNG && g_TextLib.nCurrentDialogLine == 0)
         {
            //
            // Save the screen before we show the first line of dialog
            //
            VIDEO_BackupScreen();
         }

         while (lpszText != NULL && *lpszText != '\0')
         {
            switch (*lpszText)
            {
            case '-':
               //
               // Set the font color to Cyan
               //
               if (g_TextLib.bCurrentFontColor == FONT_COLOR_CYAN)
               {
                  g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT;
               }
               else
               {
                  g_TextLib.bCurrentFontColor = FONT_COLOR_CYAN;
               }
               lpszText++;
               break;
#if 0
			/* Not used */
			case '\'':
               //
               // Set the font color to Red
               //
               if (g_TextLib.bCurrentFontColor == FONT_COLOR_RED)
               {
                  g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT;
               }
               else
               {
                  g_TextLib.bCurrentFontColor = FONT_COLOR_RED;
               }
               lpszText++;
               break;
#endif
            case '\"':
               //
               // Set the font color to Yellow
               //
               if (g_TextLib.bCurrentFontColor == FONT_COLOR_YELLOW)
               {
                  g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT;
               }
               else
               {
                  g_TextLib.bCurrentFontColor = FONT_COLOR_YELLOW;
               }
               lpszText++;
               break;

            case '$':
               //
               // Set the delay time of text-displaying
               //
               g_TextLib.iDelayTime = wcstol(lpszText + 1, NULL, 10) * 10 / 7;
               lpszText += 3;
               break;

            case '~':
               //
               // Delay for a period and quit
               //
               UTIL_Delay(wcstol(lpszText + 1, NULL, 10) * 80 / 7);
			   g_TextLib.nCurrentDialogLine = 0;
               g_TextLib.fUserSkip = FALSE;
               return; // don't go further

            case ')':
               //
               // Set the waiting icon
               //
               g_TextLib.bIcon = 1;
               lpszText++;
               break;

            case '(':
               //
               // Set the waiting icon
               //
               g_TextLib.bIcon = 2;
               lpszText++;
               break;

            case '\\':
               lpszText++;

            default:
               text[0] = *lpszText++;
			   text[1] = 0;

               PAL_DrawText(text, PAL_XY(x, y), g_TextLib.bCurrentFontColor, TRUE, TRUE, FALSE);
			   x += PAL_CharWidth(text[0]);

               if (!g_TextLib.fUserSkip)
               {
                  PAL_ClearKeyState();
                  UTIL_Delay(g_TextLib.iDelayTime * 8);

                  if (g_InputState.dwKeyPress & (kKeySearch | kKeyMenu))
                  {
                     //
                     // User pressed a key to skip the dialog
                     //
                     g_TextLib.fUserSkip = TRUE;
                  }
               }
            }
         }

         g_TextLib.posIcon = PAL_XY(x, y);
         g_TextLib.nCurrentDialogLine++;
      }
   }
}

VOID
PAL_ClearDialog(
   BOOL       fWaitForKey
)
/*++
  Purpose:

    Clear the state of the dialog.

  Parameters:

    [IN]  fWaitForKey - whether wait for any key or not.

  Return value:

    None.

--*/
{
   if (g_TextLib.nCurrentDialogLine > 0 && fWaitForKey)
   {
      PAL_DialogWaitForKey();
   }

   g_TextLib.nCurrentDialogLine = 0;

   if (g_TextLib.bDialogPosition == kDialogCenter)
   {
      g_TextLib.posDialogTitle = PAL_XY(12, 8);
      g_TextLib.posDialogText = PAL_XY(44, 26);
      g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT;
      g_TextLib.bDialogPosition = kDialogUpper;
   }
}

VOID
PAL_EndDialog(
   VOID
)
/*++
  Purpose:

    Ends a dialog.

  Parameters:

    None.

  Return value:

    None.

--*/
{
   PAL_ClearDialog(TRUE);

   //
   // Set some default parameters, as there are some parts of script
   // which doesn't have a "start dialog" instruction before showing the dialog.
   //
   g_TextLib.posDialogTitle = PAL_XY(12, 8);
   g_TextLib.posDialogText = PAL_XY(44, 26);
   g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT;
   g_TextLib.bDialogPosition = kDialogUpper;
   g_TextLib.fUserSkip = FALSE;
   g_TextLib.fPlayingRNG = FALSE;
}

BOOL
PAL_IsInDialog(
   VOID
)
/*++
  Purpose:

    Check if there are dialog texts on the screen.

  Parameters:

    None.

  Return value:

    TRUE if there are dialog texts on the screen, FALSE if not.

--*/
{
   return (g_TextLib.nCurrentDialogLine != 0);
}

BOOL
PAL_DialogIsPlayingRNG(
   VOID
)
/*++
  Purpose:

    Check if the script used the RNG playing parameter when displaying texts.

  Parameters:

    None.

  Return value:

    TRUE if the script used the RNG playing parameter, FALSE if not.

--*/
{
   return g_TextLib.fPlayingRNG;
}

INT
PAL_MultiByteToWideCharCP(
   CODEPAGE      cp,
   LPCSTR        mbs,
   int           mbslength,
   LPWSTR        wcs,
   int           wcslength
)
/*++
  Purpose:

    Convert multi-byte string into the corresponding unicode string.

  Parameters:

    [IN]  cp - Code page for conversion.
    [IN]  mbs - Pointer to the multi-byte string.
	[IN]  mbslength - Length of the multi-byte string, or -1 for auto-detect.
	[IN]  wcs - Pointer to the wide string buffer.
	[IN]  wcslength - Length of the wide string buffer.

  Return value:

    The length of converted wide string. If mbslength is set to -1, the returned
	value includes the terminal null-char; otherwise, the null-char is not included.
	If wcslength is set to 0, wcs can be set to NULL and the return value is the
	required length of the wide string buffer.

--*/
{
	int i = 0, state = 0, wlen = 0, null = 0;

	if (mbslength == -1)
	{
		mbslength = strlen(mbs);
		null = 1;
	}

	if (!wcs)
	{
		switch (cp)
		{
		//case CP_SHIFTJIS:
		//	for (i = 0; i < mbslength && mbs[i]; i++)
		//	{
		//		if (state == 0)
		//		{
		//			if ((BYTE)mbs[i] <= 0x80 || (BYTE)mbs[i] >= 0xfd || ((BYTE)mbs[i] >= 0xa0 && (BYTE)mbs[i] <= 0xdf))
		//				wlen++;
		//			else
		//				state = 1;
		//		}
		//		else
		//		{
		//			wlen++;
		//			state = 0;
		//		}
		//	}
		//	break;
		case CP_GBK:
		case CP_BIG5:
			for (i = 0; i < mbslength && mbs[i]; i++)
			{
				if (state == 0)
				{
					if ((BYTE)mbs[i] <= 0x80 || (BYTE)mbs[i] == 0xff)
						wlen++;
					else
						state = 1;
				}
				else
				{
					wlen++;
					state = 0;
				}
			}
			break;
		case CP_UTF_8:
			for (i = 0; i < mbslength && mbs[i]; i++)
			{
				if (state == 0)
				{
					if ((BYTE)mbs[i] >= 0x80)
					{
						BYTE s = (BYTE)mbs[i] << 1;
						while (s >= 0x80) { state++; s <<= 1; }
						if (state < 1 || state > 3)
						{
							state = 0;
							wlen++;
						}
					}
					else
						wlen++;
				}
				else
				{
					if ((BYTE)mbs[i] >= 0x80 && (BYTE)mbs[i] < 0xc0)
					{
						if (--state == 0) wlen++;
					}
					else
					{
						state = 0; wlen++;
					}
				}
			}
			break;
		default:
			return -1;
		}
		if (i < mbslength && !mbs[i]) null = 1;
		return wlen + null + (state != 0);
	}
	else
	{
		WCHAR invalid_char;
		switch (cp)
		{
		//case CP_SHIFTJIS:
		//	invalid_char = 0x30fb;
		//	for (i = 0; i < mbslength && wlen < wcslength && mbs[i]; i++)
		//	{
		//		if (state == 0)
		//		{
		//			if ((BYTE)mbs[i] <= 0x80)
		//				wcs[wlen++] = mbs[i];
		//			else if ((BYTE)mbs[i] >= 0xa0 && (BYTE)mbs[i] <= 0xdf)
		//				wcs[wlen++] = cptbl_jis_half[(BYTE)mbs[i] - 0xa0];
		//			else if ((BYTE)mbs[i] == 0xfd)
		//				wcs[wlen++] = 0xf8f1;
		//			else if ((BYTE)mbs[i] == 0xfe)
		//				wcs[wlen++] = 0xf8f2;
		//			else if ((BYTE)mbs[i] == 0xff)
		//				wcs[wlen++] = 0xf8f3;
		//			else
		//				state = 1;
		//		}
		//		else
		//		{
		//			if ((BYTE)mbs[i] < 0x40)
		//				wcs[wlen++] = 0x30fb;
		//			else if ((BYTE)mbs[i - 1] < 0xa0)
		//				wcs[wlen++] = cptbl_jis[(BYTE)mbs[i - 1] - 0x81][(BYTE)mbs[i] - 0x40];
		//			else
		//				wcs[wlen++] = cptbl_jis[(BYTE)mbs[i - 1] - 0xc1][(BYTE)mbs[i] - 0x40];
		//			state = 0;
		//		}
		//	}
		//	break;
		case CP_GBK:
			invalid_char = 0x3f;
			for (i = 0; i < mbslength && wlen < wcslength && mbs[i]; i++)
			{
				if (state == 0)
				{
					if ((BYTE)mbs[i] < 0x80)
						wcs[wlen++] = mbs[i];
					else if ((BYTE)mbs[i] == 0x80)
						wcs[wlen++] = 0x20ac;
					else if ((BYTE)mbs[i] == 0xff)
						wcs[wlen++] = 0xf8f5;
					else
						state = 1;
				}
				else
				{
					if ((BYTE)mbs[i] < 0x40)
						wcs[wlen++] = invalid_char;
					else
						wcs[wlen++] = cptbl_gbk[(BYTE)mbs[i - 1] - 0x81][(BYTE)mbs[i] - 0x40];
					state = 0;
				}
			}
			break;
		case CP_BIG5:
			invalid_char = 0x3f;
			for (i = 0; i < mbslength && wlen < wcslength && mbs[i]; i++)
			{
				if (state == 0)
				{
					if ((BYTE)mbs[i] <= 0x80)
						wcs[wlen++] = mbs[i];
					else if ((BYTE)mbs[i] == 0xff)
						wcs[wlen++] = 0xf8f8;
					else
						state = 1;
				}
				else
				{
					if ((BYTE)mbs[i] < 0x40 || ((BYTE)mbs[i] >= 0x7f && (BYTE)mbs[i] <= 0xa0))
						wcs[wlen++] = invalid_char;
					else if ((BYTE)mbs[i] <= 0x7e)
						wcs[wlen++] = cptbl_big5[(BYTE)mbs[i - 1] - 0x81][(BYTE)mbs[i] - 0x40];
					else
						wcs[wlen++] = cptbl_big5[(BYTE)mbs[i - 1] - 0x81][(BYTE)mbs[i] - 0x60];
					state = 0;
				}
			}
			break;
		case CP_UTF_8:
			invalid_char = 0x3f;
			for (i = 0; i < mbslength && wlen < wcslength && mbs[i]; i++)
			{
				if (state == 0)
				{
					if ((BYTE)mbs[i] >= 0x80)
					{
						BYTE s = (BYTE)mbs[i] << 1;
						while (s >= 0x80) { state++; s <<= 1; }
						if (state < 1 || state > 3)
						{
							state = 0;
							wcs[wlen++] = invalid_char;
						}
						else
						{
							wcs[wlen] = s >> (state + 1);
						}
					}
					else
						wcs[wlen++] = mbs[i];
				}
				else
				{
					if ((BYTE)mbs[i] >= 0x80 && (BYTE)mbs[i] < 0xc0)
					{
						wcs[wlen] <<= 6;
						wcs[wlen] |= (BYTE)mbs[i] & 0x3f;
						if (--state == 0) wlen++;
					}
					else
					{
						state = 0;
						wcs[wlen++] = invalid_char;
					}
				}
			}
			break;
		default:
			return -1;
		}
		if (state != 0 && wlen < wcslength)
		{
			wcs[wlen++] = invalid_char;
		}
		if (null || (i < mbslength && !mbs[i]))
		{
			if (wlen < wcslength)
				wcs[wlen++] = 0;
			else
				wcs[wlen - 1] = 0;
		}
		return wlen;
	}
}

INT
PAL_MultiByteToWideChar(
   LPCSTR        mbs,
   int           mbslength,
   LPWSTR        wcs,
   int           wcslength
)
/*++
  Purpose:

    Convert multi-byte string into the corresponding unicode string.

  Parameters:

    [IN]  mbs - Pointer to the multi-byte string.
	[IN]  mbslength - Length of the multi-byte string, or -1 for auto-detect.
	[IN]  wcs - Pointer to the wide string buffer.
	[IN]  wcslength - Length of the wide string buffer.

  Return value:

    The length of converted wide string. If mbslength is set to -1, the returned
	value includes the terminal null-char; otherwise, the null-char is not included.
	If wcslength is set to 0, wcs can be set to NULL and the return value is the
	required length of the wide string buffer.

--*/
{
	return PAL_MultiByteToWideCharCP(gConfig.uCodePage, mbs, mbslength, wcs, wcslength);
}

WCHAR
PAL_GetInvalidChar(
   CODEPAGE      uCodePage
)
{
   switch(uCodePage)
   {
   case CP_BIG5:     return 0x3f;
   case CP_GBK:      return 0x3f;
   //case CP_SHIFTJIS: return 0x30fb;
   case CP_UTF_8:    return 0x3f;
   default:          return 0;
   }
}