Browse Source

External messages support. Remove the option 'WordLength' because it can be automatically determined by the .dat file.

louyihua 8 years ago
parent
commit
d789107800
9 changed files with 712 additions and 225 deletions
  1. 9 2
      common.h
  2. 18 13
      global.c
  3. 1 0
      global.h
  4. 53 34
      script.c
  5. 13 4
      sdlpal.cfg.example
  6. 533 129
      text.c
  7. 8 2
      text.h
  8. 65 41
      util.c
  9. 12 0
      util.h

+ 9 - 2
common.h

@@ -237,7 +237,7 @@ extern "C"
 # if SDL_VERSION_ATLEAST(2,0,0)
 #  define PAL_VIDEO_INIT_FLAGS  (SDL_WINDOW_SHOWN)
 # else
-#  define PAL_VIDEO_INIT_FLAGS  (SDL_HWSURFACE | SDL_FULLSCREEN)
+#  define PAL_VIDEO_INIT_FLAGS  (SDL_HWSURFACE | SDL_RESIZABLE | (gpGlobals->fFullScreen ? SDL_FULLSCREEN : 0))
 # endif
 
 #endif
@@ -294,6 +294,7 @@ typedef const BYTE *LPCBYTE;
 #else
 
 #include <unistd.h>
+#include <dirent.h>
 
 #ifndef FALSE
 #define FALSE               0
@@ -343,6 +344,11 @@ typedef const WCHAR        *LPCWSTR;
 #define __WIDETEXT(quote) L##quote
 #define WIDETEXT(quote) __WIDETEXT(quote)
 
+// For SDL 1.2 compatibility
+#ifndef SDL_TICKS_PASSED
+#define SDL_TICKS_PASSED(A, B)  ((Sint32)((B) - (A)) <= 0)
+#endif
+
 #define PAL_DelayUntil(t) \
    PAL_ProcessEvent(); \
    while (!SDL_TICKS_PASSED(SDL_GetTicks(), (t))) \
@@ -357,7 +363,8 @@ typedef enum tagCODEPAGE {
 	CP_BIG5 = 0,
 	CP_GBK = 1,
 	CP_SHIFTJIS = 2,
-	CP_MAX = 3
+	CP_MAX = 3,
+	CP_UTF_8 = CP_MAX + 1
 } CODEPAGE;
 
 #ifdef __cplusplus

+ 18 - 13
global.c

@@ -68,7 +68,6 @@ PAL_InitGlobals(
 {
    FILE     *fp;
    CODEPAGE  iCodePage = CP_BIG5;		// Default for BIG5
-   DWORD     dwWordLength = 10;			// Default for PAL DOS/WIN95
    DWORD     dwExtraMagicDescLines = 0;	// Default for PAL DOS/WIN95
    DWORD     dwExtraItemDescLines = 0;	// Default for PAL DOS/WIN95
    DWORD     dwScreenWidth = 0;
@@ -89,13 +88,7 @@ PAL_InitGlobals(
    MUSICTYPE eCDType = PAL_HAS_SDLCD ? MUSIC_SDLCD : MUSIC_OGG;
    OPLTYPE   eOPLType = OPL_DOSBOX;
 
-#if USE_RIX_EXTRA_INIT
-   gpGlobals->pExtraFMRegs = NULL;
-   gpGlobals->pExtraFMVals = NULL;
-   gpGlobals->dwExtraLength = 0;
-#endif
-
-   if (fp = UTIL_OpenFile("sdlpal.cfg"))
+   if (fp = UTIL_OpenFileForMode("sdlpal.cfg", "r"))
    {
 	   PAL_LARGE char buf[512];
 
@@ -131,10 +124,6 @@ PAL_InitGlobals(
 				   {
 					   sscanf(ptr, "%d", &iCodePage);
 				   }
-				   else if (SDL_strcasecmp(p, "WORDLENGTH") == 0)
-				   {
-					   sscanf(ptr, "%u", &dwWordLength);
-				   }
 				   else if (SDL_strcasecmp(p, "EXTRAMAGICDESCLINES") == 0)
 				   {
 					   sscanf(ptr, "%u", &dwExtraMagicDescLines);
@@ -215,6 +204,15 @@ PAL_InitGlobals(
 					   else if (iVolume < 0)
 						   iVolume = 0;
 				   }
+				   else if (SDL_strcasecmp(p, "MESSAGEFILENAME") == 0)
+				   {
+					   char *end = ptr + strlen(ptr);
+					   if (end > ptr)
+					   {
+						   if (end[-1] == '\n') end[-1] = 0;
+						   gpGlobals->pszMsgName = strdup(ptr);
+					   }
+				   }
 #if USE_RIX_EXTRA_INIT
 				   else if (SDL_strcasecmp(p, "RIXEXTRAINIT") == 0)
 				   {
@@ -314,7 +312,7 @@ PAL_InitGlobals(
    gpGlobals->eCDType = eCDType;
    gpGlobals->eOPLType = eOPLType;
    gpGlobals->iCodePage = iCodePage;
-   gpGlobals->dwWordLength = dwWordLength;
+   gpGlobals->dwWordLength = 10;	// This is the default value for Chinese version
    gpGlobals->dwExtraMagicDescLines = dwExtraMagicDescLines;
    gpGlobals->dwExtraItemDescLines = dwExtraItemDescLines;
    gpGlobals->wAudioBufferSize = (WORD)iAudioBufferSize;
@@ -415,6 +413,13 @@ PAL_FreeGlobals(
    if (!gpGlobals->fIsWIN95)
       PAL_FreeObjectDesc(gpGlobals->lpObjectDesc);
 
+#if USE_RIX_EXTRA_INIT
+   free(gpGlobals->pExtraFMRegs);
+   free(gpGlobals->pExtraFMVals);
+   free(gpGlobals->dwExtraLength);
+#endif
+   free(gpGlobals->pszMsgName);
+
    //
    // Clear the instance
    //

+ 1 - 0
global.h

@@ -611,6 +611,7 @@ typedef struct tagGLOBALVARS
    DWORD            dwFrameNum;
 
    /* Configurable options */
+   char            *pszMsgName;
 #if USE_RIX_EXTRA_INIT
    uint32_t        *pExtraFMRegs;
    uint8_t         *pExtraFMVals;

+ 53 - 34
script.c

@@ -3296,24 +3296,37 @@ PAL_RunTriggerScript(
          //
          // Print dialog text
          //
-		 // Support for Japanese
-		 // If the second parameter is zero, then follow the standard behavior
-		 // Otherwise, use the extended behavior
-		 if (pScript->rgwOperand[1] == 0)
-		 {
+         if (gpGlobals->pszMsgName)
+         {
+            int idx = 0, iMsg;
+            while ((iMsg = PAL_GetMsgNum(pScript->rgwOperand[0], idx++)) >= 0)
+			{
+               if (iMsg == 0)
+               {
+                  //
+                  // Restore the screen
+                  //
+                  PAL_ClearDialog(TRUE);
+                  VIDEO_RestoreScreen();
+                  VIDEO_UpdateScreen(NULL);
+               }
+			   else
+                  PAL_ShowDialogText(PAL_GetMsg(iMsg));
+            }
+            while (gpGlobals->g.lprgScriptEntry[wScriptEntry].wOperation == 0xFFFF
+                || gpGlobals->g.lprgScriptEntry[wScriptEntry].wOperation == 0x008E)
+            {
+               //
+               // Skip all following continuous 0xFFFF & 0x008E instructions
+               //
+               wScriptEntry++;
+            }
+         }
+		 else
+         {
             PAL_ShowDialogText(PAL_GetMsg(pScript->rgwOperand[0]));
             wScriptEntry++;
-		 }
-		 else
-		 {
-			// If the second parameter is set to 2, first display the text
-			if (pScript->rgwOperand[1] == 2)
-			{
-				PAL_ShowDialogText(PAL_GetMsg(pScript->rgwOperand[0]));
-			}
-			// Then, jump to script given by the third parameter.
-			wScriptEntry = pScript->rgwOperand[2];
-		 }
+         }
          break;
 
       default:
@@ -3452,32 +3465,38 @@ begin:
    case 0xFFFF:
 	   if (gpGlobals->fIsWIN95)
 	   {
-		   // Support for Japanese
-		   // If the second parameter is zero, then follow the standard behavior
-		   // Otherwise, use the extended behavior
-		   // Either zero or two displays text
-		   if (pScript->rgwOperand[1] == 0 || pScript->rgwOperand[1] == 2)
+		   iDescLine = (wEventObjectID & ~PAL_ITEM_DESC_BOTTOM);
+		   if (gpGlobals->pszMsgName)
 		   {
-			   iDescLine = (wEventObjectID & ~PAL_ITEM_DESC_BOTTOM);
-			   if (wEventObjectID & PAL_ITEM_DESC_BOTTOM)
+			   int idx = 0, iMsg;
+			   while ((iMsg = PAL_GetMsgNum(pScript->rgwOperand[0], idx++)) >= 0)
 			   {
-				   int YOffset = gpGlobals->dwExtraItemDescLines * 16;
-				   PAL_DrawText(PAL_GetMsg(pScript->rgwOperand[0]), PAL_XY(75, iDescLine * 16 + 150 - YOffset), DESCTEXT_COLOR, TRUE, FALSE);
+				   if (iMsg > 0)
+				   {
+					   if (wEventObjectID & PAL_ITEM_DESC_BOTTOM)
+					   {
+						   int YOffset = gpGlobals->dwExtraItemDescLines * 16;
+						   PAL_DrawText(PAL_GetMsg(iMsg), PAL_XY(75, iDescLine * 16 + 150 - YOffset), DESCTEXT_COLOR, TRUE, FALSE);
+					   }
+					   else
+					   {
+						   PAL_DrawText(PAL_GetMsg(iMsg), PAL_XY(100, iDescLine * 16 + 3), DESCTEXT_COLOR, TRUE, FALSE);
+					   }
+					   iDescLine++;
+				   }
 			   }
-			   else
+			   while (gpGlobals->g.lprgScriptEntry[wScriptEntry].wOperation == 0xFFFF)
 			   {
-				   PAL_DrawText(PAL_GetMsg(pScript->rgwOperand[0]), PAL_XY(100, iDescLine * 16 + 3), DESCTEXT_COLOR, TRUE, FALSE);
+				   //
+				   // Skip all following continuous 0xFFFF instructions
+				   //
+				   wScriptEntry++;
 			   }
-			   iDescLine++;
-		   }
-		   if (pScript->rgwOperand[1] != 0)
-		   {
-			   // Then, jump to script given by the third parameter.
-			   wScriptEntry = pScript->rgwOperand[2];
 		   }
 		   else
 		   {
-			   wScriptEntry++;
+			   PAL_ShowDialogText(PAL_GetMsg(pScript->rgwOperand[0]));
+			   iDescLine++; wScriptEntry++;
 		   }
 	   }
 	   else

+ 13 - 4
sdlpal.cfg.example

@@ -8,10 +8,6 @@
 #           2 for shift-jis.
 #CodePage=0
 
-# WordLength: Idicates the length of each word, 10 for chinese version (default),
-#             16 for japanese version
-#WordLength=10
-
 # ExtraMagicDescLines: Idicates number of extra lines for magic description,
 #                      0 for chinese version (default), 1 for japanese version.
 #ExtraMagicDescLines=0
@@ -103,3 +99,16 @@
 #                  value is only valid if the game is built with SDL 2.0 or higher, otherwise
 #                  this value is ignored.
 #KeepAspectRatio=1
+
+# MessageFileName: Indicates whether to use external message & word files. Only need to specify
+#                  main file name, the extensions are automatically appended by SDLPAL. The
+#                  '.msg' file is the message file, the '.idx' file is the index file, and the
+#                  '.wrd' file is the word file. All files have the format '<idx>=<content>',
+#                  where the start value of <idx> are 0 for the index file and 1 for the other
+#                  two files. Note that, the <content> of the index file is a series of numbers
+#                  spearated by commas, and each number is one of the <idx>s in the message
+#                  file, while value '0' means wait for key and value '-1' means end. And the
+#                  <idx> of the index file is the value of the first 0xFFFF instruction of one
+#                  dialog in SSS.MKF. By default, this option is disabled. All such files should
+#                  be UTF-8 encoded, without leading BOM bytes.
+#MessageFileName=jm

+ 533 - 129
text.c

@@ -34,6 +34,8 @@
 
 BOOL      g_fUpdatedInBattle      = FALSE;
 
+#define   MESSAGE_MAX_BUFFER_SIZE   512
+
 #define INCLUDE_CODEPAGE_H
 #include "codepage.h"
 
@@ -48,11 +50,13 @@ static const WCHAR** g_rgszAdditionalWords;
 
 typedef struct tagTEXTLIB
 {
-   LPWSTR*         lpWordBuf;
-   LPWSTR*         lpMsgBuf;
+   LPWSTR         *lpWordBuf;
+   LPWSTR         *lpMsgBuf;
+   int           **lpIndexBuf;
 
    int             nWords;
    int             nMsgs;
+   int             nIndices;
 
    int             nCurrentDialogLine;
    BYTE            bCurrentFontColor;
@@ -70,6 +74,248 @@ typedef struct tagTEXTLIB
 
 static TEXTLIB         g_TextLib;
 
+PAL_FORCE_INLINE int
+PAL_ParseLine(
+	char     *line,
+	char    **value,
+	int      *length
+	)
+{
+	//
+	// 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;
+			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_CheckLongLine(
+	FILE     *fp,
+	char     *temp
+	)
+{
+	int n = strlen(temp);
+	if (n == MESSAGE_MAX_BUFFER_SIZE - 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 * MESSAGE_MAX_BUFFER_SIZE)))
+			{
+				TerminateOnError("PAL_ReadFileAndSplitLine(): failed to allocate memory for long line!");
+			}
+			if (fgets(tmp + n, MESSAGE_MAX_BUFFER_SIZE + 1, fp))
+			{
+				n += strlen(tmp + n);
+				if (n < MESSAGE_MAX_BUFFER_SIZE - 1 || temp[n - 1] == '\n')
+					break;
+				else
+					nn++;
+			}
+		}
+		return tmp;
+	}
+	else
+		return temp;
+}
+
+static int
+PAL_ReadFileAndSplitLine(
+	FILE     *fp,
+	LPWSTR  **buffer
+	)
+{
+	char temp[MESSAGE_MAX_BUFFER_SIZE];
+	struct _list_entry_1
+	{
+		struct _list_entry_1 *next;
+		wchar_t *value;
+		int length;
+		int index;
+	} *head = NULL, *item;
+	int max = 0;
+
+	while (!feof(fp))
+	{
+		if (fgets(temp, MESSAGE_MAX_BUFFER_SIZE, fp))
+		{
+			//
+			// Read one line and parse it
+			//
+			char *buffer = PAL_CheckLongLine(fp, temp), *v;
+			int l, n = PAL_ParseLine(buffer, &v, &l);
+			if (n > 0)
+			{
+				//
+				// First save values (converted wide string) into a linked list
+				//
+				if (head)
+				{
+					item->next = (struct _list_entry_1 *)malloc(sizeof(struct _list_entry_1));
+					item = item->next;
+				}
+				else
+					item = head = (struct _list_entry_1 *)malloc(sizeof(struct _list_entry_1));
+				item->length = PAL_MultiByteToWideCharCP(CP_UTF_8, v, l, NULL, 0);
+				item->value = (LPWSTR)malloc((item->length + 1) * sizeof(WCHAR));
+				PAL_MultiByteToWideCharCP(CP_UTF_8, v, l + 1, item->value, item->length + 1);
+				item->index = (max < n) ? (max = n) : n; item->next = NULL;
+			}
+			if (buffer != temp) free(buffer);
+		}
+	}
+
+	if (max > 0)
+	{
+		//
+		// Move values from linked list to array
+		//
+		LPWSTR *array = (LPWSTR *)calloc(max += 1, sizeof(LPWSTR));
+		for (item = head; item; )
+		{
+			struct _list_entry_1 *temp = item->next;
+			array[item->index] = item->value;
+			free(item); item = temp;
+		}
+		*buffer = array;
+	}
+
+	fclose(fp);
+
+	return max > 0 ? max : -1;
+}
+
+static int
+PAL_ReadIndexFile(
+	FILE     *fp,
+	int    ***buffer
+	)
+{
+	char temp[MESSAGE_MAX_BUFFER_SIZE];
+	struct _index_entry
+	{
+		struct _index_entry *next;
+		int value;
+	};
+	struct _list_entry_2
+	{
+		struct _list_entry_2 *next;
+		struct _index_entry *value;
+		int length;
+		int index;
+	} *head = NULL, *item;
+	int max = 0;
+
+	while (!feof(fp))
+	{
+		if (fgets(temp, MESSAGE_MAX_BUFFER_SIZE, fp))
+		{
+			//
+			// Read one line and parse it
+			//
+			char *buffer = PAL_CheckLongLine(fp, temp), *v, *e;
+			int l, n = PAL_ParseLine(buffer, &v, &l), i;
+			if (n >= 0)
+			{
+				//
+				// First save values into a linked list
+				//
+				struct _index_entry *j;
+				if (head)
+				{
+					item->next = (struct _list_entry_2 *)malloc(sizeof(struct _list_entry_2));
+					item = item->next;
+				}
+				else
+					item = head = (struct _list_entry_2 *)malloc(sizeof(struct _list_entry_2));
+				item->index = (max < n) ? (max = n) : n;
+				item->value = NULL; item->next = NULL;
+				item->length = 0;
+				while (*v && (i = strtol(v, &e, 10)) >= 0)
+				{
+					//
+					// Each message index is also first stored into a linked list
+					//
+					if (e > v)
+					{
+						if (item->value)
+						{
+							j->next = (struct _index_entry *)malloc(sizeof(struct _index_entry));
+							j = j->next;
+						}
+						else
+							j = item->value = (struct _index_entry *)malloc(sizeof(struct _index_entry));
+						j->value = i; item->length++;
+					}
+					v = e + 1;
+				}
+				j->next = (struct _index_entry *)malloc(sizeof(struct _index_entry));
+				j->next->value = -1; j->next->next = NULL; item->length++;
+			}
+			if (buffer != temp) free(buffer);
+		}
+	}
+
+	if (max > 0)
+	{
+		//
+		// Move values from linked lists to arrays
+		//
+		int **array = (int **)calloc(max += 1, sizeof(int *));
+		for (item = head; item; )
+		{
+			struct _index_entry *j = item->value;
+			struct _list_entry_2 *temp = item->next;
+			int i = 0;
+			array[item->index] = (int *)malloc(item->length * sizeof(int));
+			while (j)
+			{
+				struct _index_entry *t = j->next;
+				array[item->index][i++] = j->value;
+				free(j); j = t;
+			}
+			free(item); item = temp;
+		}
+		*buffer = array;
+	}
+
+	fclose(fp);
+
+	return max > 0 ? max : -1;
+}
+
 INT
 PAL_InitText(
    VOID
@@ -91,123 +337,174 @@ PAL_InitText(
 --*/
 {
    FILE       *fpMsg, *fpWord;
-   DWORD      *offsets;
-   LPBYTE      temp;
-   LPWSTR      tmp;
-   int         wlen, wpos;
    int         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 or 16 bytes
-   //
-   g_TextLib.nWords = (i + (gpGlobals->dwWordLength - 1)) / gpGlobals->dwWordLength;
-
-   //
-   // Read the words
-   //
-   temp = (LPBYTE)malloc(i);
-   if (temp == NULL)
-   {
-      fclose(fpWord);
-      fclose(fpMsg);
-      return -1;
-   }
-   fseek(fpWord, 0, SEEK_SET);
-   fread(temp, i, 1, fpWord);
-
-   //
-   // 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 * gpGlobals->dwWordLength;
-	   int pos = base + gpGlobals->dwWordLength - 1;
-	   while (pos >= base && temp[pos] == ' ') temp[pos--] = 0;
-	   wlen += PAL_MultiByteToWideChar((LPCSTR)temp + base, gpGlobals->dwWordLength, NULL, 0) + 1;
-   }
-   g_TextLib.lpWordBuf = (LPWSTR*)malloc(g_TextLib.nWords * sizeof(LPWSTR));
-   tmp = (LPWSTR)malloc(wlen * sizeof(WCHAR));
-   for (i = 0, wpos = 0; i < g_TextLib.nWords; i++)
+   if (gpGlobals->pszMsgName)
    {
-	   int l;
-	   g_TextLib.lpWordBuf[i] = tmp + wpos;
-	   l = PAL_MultiByteToWideChar((LPCSTR)temp + i * gpGlobals->dwWordLength, gpGlobals->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;
+	   FILE       *fpIndex;
+	   char       *filename = (char *)alloca((i = strlen(gpGlobals->pszMsgName)) + 5);
+
+	   //
+	   // Open the message, index and word data files.
+	   //
+	   strcpy(filename, gpGlobals->pszMsgName);
+	   strcpy(filename + i, ".msg"); fpMsg = UTIL_OpenRequiredFileForMode(filename, "r");
+	   strcpy(filename + i, ".idx"); fpIndex = UTIL_OpenRequiredFileForMode(filename, "r");
+	   strcpy(filename + i, ".wrd"); fpWord = UTIL_OpenRequiredFileForMode(filename, "r");
+
+	   //
+	   // Read the contents of the message, index and word data files.
+	   //
+	   if ((g_TextLib.nWords = PAL_ReadFileAndSplitLine(fpWord, &g_TextLib.lpWordBuf)) <= 0)
+	   {
+		   fclose(fpIndex);
+		   fclose(fpMsg);
+		   return -1;
+	   }
+	   else
+	   {
+		   DWORD dwWordLength = 0;
+		   for (i = 1; i < g_TextLib.nWords; i++)
+		   {
+			   LPWSTR ptr = g_TextLib.lpWordBuf[i];
+			   DWORD n = 0;
+			   while (*ptr) n += PAL_CharWidth(*ptr++) >> 3;
+			   if (dwWordLength < n) dwWordLength = n;
+		   }
+		   gpGlobals->dwWordLength = dwWordLength;
+	   }
+	   if ((g_TextLib.nMsgs = PAL_ReadFileAndSplitLine(fpMsg, &g_TextLib.lpMsgBuf)) <= 0)
+	   {
+		   fclose(fpIndex);
+		   return -1;
+	   }
+	   if ((g_TextLib.nIndices = PAL_ReadIndexFile(fpIndex, &g_TextLib.lpIndexBuf)) <= 0)
+	   {
+		   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));
-   tmp = (LPWSTR)malloc(wlen * sizeof(WCHAR));
-   for (i = 0, wpos = 0; i < g_TextLib.nMsgs; i++)
+   else
    {
-	   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;
+	   FILE       *fpMsg, *fpWord;
+	   DWORD      *offsets;
+	   LPWSTR      tmp;
+	   LPBYTE      temp;
+	   int         wpos, wlen;
+
+	   //
+	   // 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 or 16 bytes
+	   //
+	   g_TextLib.nWords = (i + (gpGlobals->dwWordLength - 1)) / gpGlobals->dwWordLength;
+
+	   //
+	   // Read the words
+	   //
+	   temp = (LPBYTE)malloc(i);
+	   if (temp == NULL)
+	   {
+		   fclose(fpWord);
+		   fclose(fpMsg);
+		   return -1;
+	   }
+	   fseek(fpWord, 0, SEEK_SET);
+	   fread(temp, i, 1, fpWord);
+
+	   //
+	   // 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 * gpGlobals->dwWordLength;
+		   int pos = base + gpGlobals->dwWordLength - 1;
+		   while (pos >= base && temp[pos] == ' ') temp[pos--] = 0;
+		   wlen += PAL_MultiByteToWideChar((LPCSTR)temp + base, gpGlobals->dwWordLength, NULL, 0) + 1;
+	   }
+	   g_TextLib.lpWordBuf = (LPWSTR*)malloc(g_TextLib.nWords * sizeof(LPWSTR));
+	   tmp = (LPWSTR)malloc(wlen * sizeof(WCHAR));
+	   for (i = 0, wpos = 0; i < g_TextLib.nWords; i++)
+	   {
+		   int l;
+		   g_TextLib.lpWordBuf[i] = tmp + wpos;
+		   l = PAL_MultiByteToWideChar((LPCSTR)temp + i * gpGlobals->dwWordLength, gpGlobals->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));
+	   tmp = (LPWSTR)malloc(wlen * sizeof(WCHAR));
+	   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;
    }
-   free(temp);
-   free(offsets);
 
 #ifndef PAL_CLASSIC
    g_rgszAdditionalWords = gc_rgszAdditionalWords[gpGlobals->iCodePage];
@@ -247,23 +544,39 @@ PAL_FreeText(
 
 --*/
 {
+   int i;
    if (g_TextLib.lpMsgBuf != NULL)
    {
-      free(g_TextLib.lpMsgBuf[0]);
+      if (gpGlobals->pszMsgName)
+         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)
    {
-      free(g_TextLib.lpWordBuf[0]);
+      if (gpGlobals->pszMsgName)
+         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 (gpGlobals->pszMsgName)
+         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(
-   WORD       wNumWord
+   int        iNumWord
 )
 /*++
   Purpose:
@@ -287,17 +600,12 @@ PAL_GetWord(
    }
 #endif
 
-   if (wNumWord >= g_TextLib.nWords)
-   {
-      return NULL;
-   }
-
-   return g_TextLib.lpWordBuf[wNumWord];
+   return (iNumWord >= g_TextLib.nWords || !g_TextLib.lpWordBuf[iNumWord]) ? L"" : g_TextLib.lpWordBuf[iNumWord];
 }
 
 LPCWSTR
 PAL_GetMsg(
-   WORD       wNumMsg
+   int        iNumMsg
 )
 /*++
   Purpose:
@@ -314,7 +622,31 @@ PAL_GetMsg(
 
 --*/
 {
-   return (wNumMsg >= g_TextLib.nMsgs) ? NULL : g_TextLib.lpMsgBuf[wNumMsg];
+   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
@@ -1047,7 +1379,7 @@ PAL_MultiByteToWideCharCP(
 					state = 0;
 				}
 			}
-			return wlen + null + state;
+			break;
 		case CP_GBK:
 		case CP_BIG5:
 			for (i = 0; i < mbslength && mbs[i]; i++)
@@ -1065,10 +1397,43 @@ PAL_MultiByteToWideCharCP(
 					state = 0;
 				}
 			}
-			return wlen + null + state;
+			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
 	{
@@ -1156,14 +1521,53 @@ PAL_MultiByteToWideCharCP(
 				}
 			}
 			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 == 1 && wlen < wcslength)
+		if (state != 0 && wlen < wcslength)
 		{
 			wcs[wlen++] = invalid_char;
 		}
-		if (null)
+		if (null || (i < mbslength && !mbs[i]))
 		{
 			if (wlen < wcslength)
 				wcs[wlen++] = 0;
@@ -1171,7 +1575,6 @@ PAL_MultiByteToWideCharCP(
 				wcs[wlen - 1] = 0;
 		}
 		return wlen;
-
 	}
 }
 
@@ -1216,6 +1619,7 @@ PAL_GetInvalidChar(
    case CP_BIG5:     return 0x3f;
    case CP_GBK:      return 0x3f;
    case CP_SHIFTJIS: return 0x30fb;
+   case CP_UTF_8:    return 0x3f;
    default:          return 0;
    }
 }

+ 8 - 2
text.h

@@ -48,12 +48,18 @@ PAL_FreeText(
 
 LPCWSTR
 PAL_GetWord(
-   WORD       wNumWord
+   int        iNumWord
 );
 
 LPCWSTR
 PAL_GetMsg(
-   WORD       wNumMsg
+   int        iNumMsg
+);
+
+int
+PAL_GetMsgNum(
+   int        iIndex,
+   int        iOrder
 );
 
 VOID

+ 65 - 41
util.c

@@ -397,31 +397,31 @@ UTIL_OpenRequiredFile(
 
 --*/
 {
-   FILE         *fp;
+   return UTIL_OpenRequiredFileForMode(lpszFileName, "rb");
+}
 
-   fp = fopen(va("%s%s", PAL_PREFIX, lpszFileName), "rb");
+FILE *
+UTIL_OpenRequiredFileForMode(
+   LPCSTR            lpszFileName,
+   LPCSTR            szMode
+)
+/*++
+  Purpose:
 
-#ifndef _WIN32
-   if (fp == NULL)
-   {
-	  //
-	  // try converting the filename to upper-case.
-	  //
-	  char *pBuf = strdup(lpszFileName);
-	  char *p = pBuf;
-	  while (*p)
-	  {
-		 if (*p >= 'a' && *p <= 'z')
-		 {
-			*p -= 'a' - 'A';
-		 }
-		 p++;
-	  }
-
-	  fp = fopen(va("%s%s", PAL_PREFIX, pBuf), "rb");
-	  free(pBuf);
-   }
-#endif
+    Open a required file. If fails, quit the program.
+
+  Parameters:
+
+    [IN]  lpszFileName - file name to open.
+    [IN]  szMode - file open mode.
+
+  Return value:
+
+    Pointer to the file.
+
+--*/
+{
+   FILE *fp = UTIL_OpenFileForMode(lpszFileName, szMode);
 
    if (fp == NULL)
    {
@@ -450,29 +450,53 @@ UTIL_OpenFile(
 
 --*/
 {
-   FILE         *fp;
+   return UTIL_OpenFileForMode(lpszFileName, "rb");
+}
 
-   fp = fopen(va("%s%s", PAL_PREFIX, lpszFileName), "rb");
+FILE *
+UTIL_OpenFileForMode(
+   LPCSTR            lpszFileName,
+   LPCSTR            szMode
+)
+/*++
+  Purpose:
+
+    Open a file. If fails, return NULL.
+
+  Parameters:
+
+    [IN]  lpszFileName - file name to open.
+    [IN]  szMode - file open mode.
+
+  Return value:
+
+    Pointer to the file.
+
+--*/
+{
+	FILE         *fp;
+
+	fp = fopen(va("%s%s", PAL_PREFIX, lpszFileName), szMode);
 
 #ifndef _WIN32
-   if (fp == NULL)
-   {
-	  //
-	  // try converting the filename to upper-case.
-	  //
-	  char *pBuf = strdup(lpszFileName);
-	  char *p = pBuf;
-	  while (*p)
-	  {
-         *p++ = toupper(*p);
-	  }
-
-	  fp = fopen(va("%s%s", PAL_PREFIX, pBuf), "rb");
-	  free(pBuf);
-   }
+	if (fp == NULL)
+	{
+		//
+		// try to find the matching file in the directory.
+		//
+		struct dirent **list;
+		int n = scandir(PAL_PREFIX, &list, 0, alphasort);
+		while (n-- > 0)
+		{
+			if (!fp && strcasecmp(list[n]->d_name, lpszFileName) == 0)
+				fp = fopen(va("%s%s", PAL_PREFIX, list[n]->d_name), szMode);
+			free(list[n]);
+		}
+		free(list);
+	}
 #endif
 
-   return fp;
+	return fp;
 }
 
 VOID

+ 12 - 0
util.h

@@ -81,11 +81,23 @@ UTIL_OpenRequiredFile(
    LPCSTR               lpszFileName
 );
 
+FILE *
+UTIL_OpenRequiredFileForMode(
+   LPCSTR               lpszFileName,
+   LPCSTR               szMode
+);
+
 FILE *
 UTIL_OpenFile(
    LPCSTR               lpszFileName
 );
 
+FILE *
+UTIL_OpenFileForMode(
+   LPCSTR               lpszFileName,
+   LPCSTR               szMode
+);
+
 VOID
 UTIL_CloseFile(
    FILE                *fp