// SoftEther VPN Source Code - Developer Edition Master Branch
// Mayaqua Kernel


// Table.c
// Read and management routines for string table

#include <GlobalConst.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <stdarg.h>
#include <time.h>
#include <errno.h>
#include <Mayaqua/Mayaqua.h>

// List of TABLE
static LIST *TableList = NULL;
static wchar_t old_table_name[MAX_SIZE] = {0};		// Old table name
static LANGLIST current_lang = {0};
static LANGLIST current_os_lang = {0};

// Initialization of string table routine
void InitTable()
{
	LIST *o;
	char tmp[MAX_SIZE];
	LANGLIST *e = NULL;
	LANGLIST *os_lang = NULL;
	char table_name[MAX_SIZE];
	if (MayaquaIsMinimalMode())
	{
		// Not to load in case of minimum mode
		return;
	}

	o = LoadLangList();
	if (o == NULL)
	{
LABEL_FATAL_ERROR:
		Alert("Fatal Error: The file \"hamcore.se2\" is missing or broken.\r\nPlease check hamcore.se2.\r\n\r\n(First, reboot the computer. If this problem occurs again, please reinstall VPN software files.)", NULL);
		exit(-1);
		return;
	}

	// Read the lang.config
	if (LoadLangConfigCurrentDir(tmp, sizeof(tmp)))
	{
		e = GetBestLangByName(o, tmp);
	}

	os_lang = GetBestLangForCurrentEnvironment(o);

	if (e == NULL)
	{
		e = os_lang;
	}

	if (e == NULL)
	{
		goto LABEL_FATAL_ERROR;
	}

	SaveLangConfigCurrentDir(e->Name);

	Copy(&current_lang, e, sizeof(LANGLIST));
	Copy(&current_os_lang, os_lang, sizeof(LANGLIST));

	current_lang.LangList = current_lang.LcidList = NULL;
	current_os_lang.LangList = current_os_lang.LcidList = NULL;

	// Read the corresponding string table
	Format(table_name, sizeof(table_name), "|strtable_%s.stb", current_lang.Name);
	if (LoadTable(table_name) == false)
	{
		goto LABEL_FATAL_ERROR;
	}

	FreeLangList(o);
}

// Get the language of the current OS
void GetCurrentOsLang(LANGLIST *e)
{
	// Validate arguments
	if (e == NULL)
	{
		return;
	}

	Copy(e, &current_os_lang, sizeof(LANGLIST));
}

// Get the language ID of the current OS
UINT GetCurrentOsLangId()
{
	LANGLIST e;

	Zero(&e, sizeof(e));

	GetCurrentOsLang(&e);

	return e.Id;
}

// Get the current language
void GetCurrentLang(LANGLIST *e)
{
	// Validate arguments
	if (e == NULL)
	{
		return;
	}

	Copy(e, &current_lang, sizeof(LANGLIST));
}

// Get the current language ID
UINT GetCurrentLangId()
{
	LANGLIST e;

	Zero(&e, sizeof(e));

	GetCurrentLang(&e);

	return e.Id;
}

// Write to the lang.config file in the current directory
bool SaveLangConfigCurrentDir(char *str)
{
	// Validate arguments
	if (str == NULL)
	{
		return false;
	}

	return SaveLangConfig(LANG_CONFIG_FILENAME, str);
}

// Write to the lang.config file
bool SaveLangConfig(wchar_t *filename, char *str)
{
	BUF *b;
	LIST *o;
	UINT i;
	bool ret;
	// Validate arguments
	if (filename == NULL)
	{
		return false;
	}

	// Read the template
	b = ReadDump(LANG_CONFIG_TEMPLETE);
	if (b == NULL)
	{
		return false;
	}

	SeekBuf(b, b->Size, 0);

	o = LoadLangList();
	if (o != NULL)
	{
		wchar_t tmp[MAX_SIZE];

		AppendBufStr(b, "# Available Language IDs are:\r\n");

		for (i = 0;i < LIST_NUM(o);i++)
		{
			LANGLIST *e = LIST_DATA(o, i);

			UniFormat(tmp, sizeof(tmp), L"#  %S: %s (%s)\r\n",
				e->Name, e->TitleEnglish, e->TitleLocal);

			AppendBufUtf8(b, tmp);
		}

		AppendBufStr(b, "\r\n\r\n# Specify a Language ID here.\r\n");
		AppendBufStr(b, str);
		AppendBufStr(b, "\r\n\r\n");

		FreeLangList(o);
	}

	ret = DumpBufWIfNecessary(b, filename);

	FreeBuf(b);

	return ret;
}

// Read the lang.config file in the current directory
bool LoadLangConfigCurrentDir(char *str, UINT str_size)
{
	// Validate arguments
	if (str == NULL)
	{
		return false;
	}

	return LoadLangConfig(LANG_CONFIG_FILENAME, str, str_size);
}

// Read the lang.config file
bool LoadLangConfig(wchar_t *filename, char *str, UINT str_size)
{
	BUF *b;
	bool ret = false;
	// Validate arguments
	if (filename == NULL || str == NULL)
	{
		return false;
	}

	b = ReadDumpW(filename);
	if (b == NULL)
	{
		return false;
	}

	while (true)
	{
		char *line = CfgReadNextLine(b);

		if (line == NULL)
		{
			break;
		}

		Trim(line);

		if (IsEmptyStr(line) == false)
		{
			if (StartWith(line, "#") == false && StartWith(line, "//") == false && StartWith(line, ";") == false &&
				InStr(line, "#") == false)
			{
				StrCpy(str, str_size, line);
				ret = true;
			}
		}

		Free(line);
	}

	FreeBuf(b);

	return ret;
}

// Choose the language from the ID
LANGLIST *GetLangById(LIST *o, UINT id)
{
	UINT i;
	// Validate arguments
	if (o == NULL)
	{
		return NULL;
	}

	for (i = 0;i < LIST_NUM(o);i++)
	{
		LANGLIST *e = LIST_DATA(o, i);

		if (e->Id == id)
		{
			return e;
		}
	}

	return NULL;
}

// Choice the best language for the current environment
LANGLIST *GetBestLangForCurrentEnvironment(LIST *o)
{
	LANGLIST *ret = NULL;
	// Validate arguments
	if (o == NULL)
	{
		return NULL;
	}

#ifdef	OS_WIN32
	ret = GetBestLangByLcid(o, MsGetUserLocaleId());
#else	// OS_WIN32
	if (true)
	{
		char lang[MAX_SIZE];

		if (GetEnv("LANG", lang, sizeof(lang)))
		{
			ret = GetBestLangByLangStr(o, lang);
		}
		else
		{
			ret = GetBestLangByLangStr(o, "C");
		}
	}
#endif	// OS_WIN32

	return ret;
}

// Search for the best language from LANG string of UNIX
LANGLIST *GetBestLangByLangStr(LIST *o, char *str)
{
	UINT i;
	LANGLIST *ret;
	// Validate arguments
	if (o == NULL)
	{
		return NULL;
	}

	for (i = 0;i < LIST_NUM(o);i++)
	{
		LANGLIST *e = LIST_DATA(o, i);
		UINT j;

		for (j = 0;j < LIST_NUM(e->LangList);j++)
		{
			char *v = LIST_DATA(e->LangList, j);

			if (StrCmpi(v, str) == 0)
			{
				return e;
			}
		}
	}

	for (i = 0;i < LIST_NUM(o);i++)
	{
		LANGLIST *e = LIST_DATA(o, i);
		UINT j;

		for (j = 0;j < LIST_NUM(e->LangList);j++)
		{
			char *v = LIST_DATA(e->LangList, j);

			if (StartWith(str, v) || StartWith(v, str))
			{
				return e;
			}
		}
	}

	ret = GetBestLangByName(o, "en");

	return ret;
}

// Search for the best language from LCID
LANGLIST *GetBestLangByLcid(LIST *o, UINT lcid)
{
	LANGLIST *ret;
	UINT i;
	// Validate arguments
	if (o == NULL)
	{
		return NULL;
	}

	for (i = 0;i < LIST_NUM(o);i++)
	{
		LANGLIST *e = LIST_DATA(o, i);

		if (IsIntInList(e->LcidList, lcid))
		{
			return e;
		}
	}

	ret = GetBestLangByName(o, "en");

	return ret;
}

// Search for the best language from the name
LANGLIST *GetBestLangByName(LIST *o, char *name)
{
	UINT i;
	LANGLIST *ret = NULL;
	// Validate arguments
	if (o == NULL)
	{
		return NULL;
	}

	for (i = 0;i < LIST_NUM(o);i++)
	{
		LANGLIST *e = LIST_DATA(o, i);

		if (StrCmpi(e->Name, name) == 0)
		{
			ret = e;
			break;
		}
	}

	if (ret != NULL)
	{
		return ret;
	}

	for (i = 0;i < LIST_NUM(o);i++)
	{
		LANGLIST *e = LIST_DATA(o, i);

		if (StartWith(e->Name, name) || StartWith(name, e->Name))
		{
			ret = e;
			break;
		}
	}

	if (ret != NULL)
	{
		return ret;
	}

	return ret;
}

// Release the language list
void FreeLangList(LIST *o)
{
	UINT i;
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	for (i = 0;i < LIST_NUM(o);i++)
	{
		LANGLIST *e = LIST_DATA(o, i);

		FreeStrList(e->LangList);
		ReleaseIntList(e->LcidList);

		Free(e);
	}

	ReleaseList(o);
}

// Read the language list
LIST *LoadLangList()
{
	LIST *o = NewListFast(NULL);
	char *filename = LANGLIST_FILENAME;
	BUF *b;

#ifdef	OS_WIN32
	if (MsIsWine())
	{
		filename = LANGLIST_FILENAME_WINE;
	}
#endif	// OS_WIN32

	b = ReadDump(filename);
	if (b == NULL)
	{
		return NULL;
	}

	while (true)
	{
		char *line = CfgReadNextLine(b);

		if (line == NULL)
		{
			break;
		}

		Trim(line);

		if (IsEmptyStr(line) == false && StartWith(line, "#") == false)
		{
			TOKEN_LIST *t = ParseToken(line, "\t ");
			if (t != NULL)
			{
				if (t->NumTokens == 6)
				{
					LANGLIST *e = ZeroMalloc(sizeof(LANGLIST));
					TOKEN_LIST *t2;

					e->Id = ToInt(t->Token[0]);
					StrCpy(e->Name, sizeof(e->Name), t->Token[1]);
					Utf8ToUni(e->TitleEnglish, sizeof(e->TitleEnglish), t->Token[2], StrLen(t->Token[2]));
					Utf8ToUni(e->TitleLocal, sizeof(e->TitleLocal), t->Token[3], StrLen(t->Token[3]));

					UniReplaceStrEx(e->TitleEnglish, sizeof(e->TitleEnglish), e->TitleEnglish,
						L"_", L" ", true);

					UniReplaceStrEx(e->TitleLocal, sizeof(e->TitleLocal), e->TitleLocal,
						L"_", L" ", true);

					e->LcidList = NewIntList(false);

					t2 = ParseToken(t->Token[4], ",");
					if (t2 != NULL)
					{
						UINT i;

						for (i = 0;i < t2->NumTokens;i++)
						{
							UINT id = ToInt(t2->Token[i]);

							AddIntDistinct(e->LcidList, id);
						}

						FreeToken(t2);
					}

					e->LangList = NewListFast(NULL);

					t2 = ParseToken(t->Token[5], ",");
					if (t2 != NULL)
					{
						UINT i;

						for (i = 0;i < t2->NumTokens;i++)
						{
							Add(e->LangList, CopyStr(t2->Token[i]));
						}

						FreeToken(t2);
					}

					Add(o, e);
				}

				FreeToken(t);
			}
		}

		Free(line);
	}

	FreeBuf(b);

	return o;
}

// Get an error string in Unicode
wchar_t *GetUniErrorStr(UINT err)
{
	wchar_t *ret;
	char name[MAX_SIZE];
	Format(name, sizeof(name), "ERR_%u", err);

	ret = GetTableUniStr(name);
	if (UniStrLen(ret) != 0)
	{
		return ret;
	}
	else
	{
		return _UU("ERR_UNKNOWN");
	}
}

// Get an error string
char *GetErrorStr(UINT err)
{
	char *ret;
	char name[MAX_SIZE];
	Format(name, sizeof(name), "ERR_%u", err);

	ret = GetTableStr(name);
	if (StrLen(ret) != 0)
	{
		return ret;
	}
	else
	{
		return _SS("ERR_UNKNOWN");
	}
}

// Load the integer value from the table
UINT GetTableInt(char *name)
{
	char *str;
	// Validate arguments
	if (name == NULL)
	{
		return 0;
	}

	str = GetTableStr(name);
	return ToInt(str);
}

// Load a Unicode string from the table
wchar_t *GetTableUniStr(char *name)
{
	TABLE *t;
	// Validate arguments
	if (name == NULL)
	{
//		Debug("%s: ************\n", name);
		return L"";
	}

	// Search
	t = FindTable(name);
	if (t == NULL)
	{
		//Debug("%s: UNICODE STRING NOT FOUND\n", name);
		return L"";
	}

	return t->unistr;
}

// Load the string from the table
char *GetTableStr(char *name)
{
	TABLE *t;
	// Validate arguments
	if (name == NULL)
	{
		return "";
	}

#ifdef	OS_WIN32
	if (StrCmpi(name, "DEFAULT_FONT") == 0)
	{
		if (_II("LANG") == 2)
		{
			UINT os_type = GetOsType();
			if (OS_IS_WINDOWS_9X(os_type) ||
				GET_KETA(os_type, 100) <= 4)
			{
				// Use the SimSun font in Windows 9x, Windows NT 4.0, Windows 2000, Windows XP, and Windows Server 2003
				return "SimSun";
			}
		}
	}
#endif	// OS_WIN32

	// Search
	t = FindTable(name);
	if (t == NULL)
	{
		//Debug("%s: ANSI STRING NOT FOUND\n", name);
		return "";
	}

	return t->str;
}

// Get the string name that begins with the specified name
TOKEN_LIST *GetTableNameStartWith(char *str)
{
	UINT i;
	UINT len;
	LIST *o;
	TOKEN_LIST *t;
	char tmp[MAX_SIZE];
	// Validate arguments
	if (str == NULL)
	{
		return NullToken();
	}

	StrCpy(tmp, sizeof(tmp), str);
	StrUpper(tmp);

	len = StrLen(tmp);

	o = NewListFast(NULL);

	for (i = 0;i < LIST_NUM(TableList);i++)
	{
		TABLE *t = LIST_DATA(TableList, i);
		UINT len2 = StrLen(t->name);

		if (len2 >= len)
		{
			if (Cmp(t->name, tmp, len) == 0)
			{
				Insert(o, CopyStr(t->name));
			}
		}
	}

	t = ZeroMalloc(sizeof(TOKEN_LIST));
	t->NumTokens = LIST_NUM(o);
	t->Token = ZeroMalloc(sizeof(char *) * t->NumTokens);

	for (i = 0;i < t->NumTokens;i++)
	{
		t->Token[i] = LIST_DATA(o, i);
	}

	ReleaseList(o);

	return t;
}

// Search the table
TABLE *FindTable(char *name)
{
	TABLE *t, tt;
	// Validate arguments
	if (name == NULL || TableList == NULL)
	{
		return NULL;
	}

	tt.name = CopyStr(name);
	t = Search(TableList, &tt);
	Free(tt.name);

	return t;
}

// A function that compares the table name
int CmpTableName(void *p1, void *p2)
{
	TABLE *t1, *t2;
	if (p1 == NULL || p2 == NULL)
	{
		return 0;
	}
	t1 = *(TABLE **)p1;
	t2 = *(TABLE **)p2;
	if (t1 == NULL || t2 == NULL)
	{
		return 0;
	}

	return StrCmpi(t1->name, t2->name);
}

// Interpret a line
TABLE *ParseTableLine(char *line, char *prefix, UINT prefix_size, LIST *replace_list)
{
	UINT i, len;
	UINT len_name;
	UINT string_start;
	char *name;
	char *name2;
	UINT name2_size;
	wchar_t *unistr;
	char *str;
	UINT unistr_size, str_size;
	TABLE *t;
	// Validate arguments
	if (line == NULL || prefix == NULL)
	{
		return NULL;
	}
	TrimLeft(line);

	// No line
	len = StrLen(line);
	if (len == 0)
	{
		return NULL;
	}

	// Comment
	if (line[0] == '#' || (line[0] == '/' && line[1] == '/'))
	{
		return NULL;
	}

	// Search to the end position of the name
	len_name = 0;
	for (i = 0;;i++)
	{
		if (line[i] == 0)
		{
			// There is only one token
			return NULL;
		}
		if (line[i] == ' ' || line[i] == '\t')
		{
			break;
		}
		len_name++;
	}

	name = Malloc(len_name + 1);
	StrCpy(name, len_name + 1, line);

	string_start = len_name;
	for (i = len_name;i < len;i++)
	{
		if (line[i] != ' ' && line[i] != '\t')
		{
			break;
		}
		string_start++;
	}
	if (i == len)
	{
		Free(name);
		return NULL;
	}

	// Unescape
	UnescapeStr(&line[string_start]);

	// Convert to Unicode
	unistr_size = CalcUtf8ToUni(&line[string_start], StrLen(&line[string_start]));
	if (unistr_size == 0)
	{
		Free(name);
		return NULL;
	}
	unistr = Malloc(unistr_size);
	Utf8ToUni(unistr, unistr_size, &line[string_start], StrLen(&line[string_start]));

	if (UniInChar(unistr, L'$'))
	{
		// Replace the replacement string
		wchar_t *tmp;
		UINT tmp_size = (UniStrSize(unistr) + 1024) * 2;
		UINT i;

		tmp = Malloc(tmp_size);

		UniStrCpy(tmp, tmp_size, unistr);

		for (i = 0; i < LIST_NUM(replace_list);i++)
		{
			TABLE *r = LIST_DATA(replace_list, i);

			UniReplaceStrEx(tmp, tmp_size, tmp, (wchar_t *)r->name, r->unistr, false);
		}

		Free(unistr);

		unistr = CopyUniStr(tmp);

		Free(tmp);
	}

	// Convert to ANSI
	str_size = CalcUniToStr(unistr);
	if (str_size == 0)
	{
		str_size = 1;
		str = Malloc(1);
		str[0] = 0;
	}
	else
	{
		str = Malloc(str_size);
		UniToStr(str, str_size, unistr);
	}

	if (StrCmpi(name, "PREFIX") == 0)
	{
		// Prefix is specified
		StrCpy(prefix, prefix_size, str);
		Trim(prefix);

		if (StrCmpi(prefix, "$") == 0 || StrCmpi(prefix, "NULL") == 0)
		{
			prefix[0] = 0;
		}

		Free(name);
		Free(str);
		Free(unistr);

		return NULL;
	}

	name2_size = StrLen(name) + StrLen(prefix) + 2;
	name2 = ZeroMalloc(name2_size);

	if (prefix[0] != 0)
	{
		StrCat(name2, name2_size, prefix);
		StrCat(name2, name2_size, "@");
	}

	StrCat(name2, name2_size, name);

	Free(name);

	// Create a TABLE
	t = Malloc(sizeof(TABLE));
	StrUpper(name2);
	t->name = name2;
	t->str = str;
	t->unistr = unistr;

	return t;
}

// Unescape the string
void UnescapeStr(char *src)
{
	UINT i, len, wp;
	char *tmp;
	// Validate arguments
	if (src == NULL)
	{
		return;
	}
	
	len = StrLen(src);
	tmp = Malloc(len + 1);
	wp = 0;
	for (i = 0;i < len;i++)
	{
		if (src[i] == '\\')
		{
			i++;
			switch (src[i])
			{
			case 0:
				goto FINISH;
			case '\\':
				tmp[wp++] = '\\';
				break;
			case ' ':
				tmp[wp++] = ' ';
				break;
			case 'n':
			case 'N':
				tmp[wp++] = '\n';
				break;
			case 'r':
			case 'R':
				tmp[wp++] = '\r';
				break;
			case 't':
			case 'T':
				tmp[wp++] = '\t';
				break;
			}
		}
		else
		{
			tmp[wp++] = src[i];
		}
	}
FINISH:
	tmp[wp++] = 0;
	StrCpy(src, 0, tmp);
	Free(tmp);
}

// Release the table
void FreeTable()
{
	UINT i, num;
	TABLE **tables;
	if (TableList == NULL)
	{
		return;
	}

	num = LIST_NUM(TableList);
	tables = ToArray(TableList);
	for (i = 0;i < num;i++)
	{
		TABLE *t = tables[i];
		Free(t->name);
		Free(t->str);
		Free(t->unistr);
		Free(t);
	}
	ReleaseList(TableList);
	TableList = NULL;
	Free(tables);

	Zero(old_table_name, sizeof(old_table_name));
}

// Read a string table from the buffer
bool LoadTableFromBuf(BUF *b)
{
	char *tmp;
	char prefix[MAX_SIZE];
	LIST *replace_list = NULL;
	UINT i;
	// Validate arguments
	if (b == NULL)
	{
		return false;
	}

	// If the table already exists, delete it
	FreeTable();

	// Create a list
	TableList = NewList(CmpTableName);

	Zero(prefix, sizeof(prefix));

	replace_list = NewListFast(NULL);

	// Read the contents of the buffer line by line
	while (true)
	{
		TABLE *t;
		bool ok = true;

		tmp = CfgReadNextLine(b);
		if (tmp == NULL)
		{
			break;
		}

		if (tmp[0] == '$')
		{
			char key[128];
			char value[MAX_SIZE];
			if (GetKeyAndValue(tmp, key, sizeof(key), value, sizeof(value), " \t"))
			{
				if (StartWith(key, "$") && EndWith(key, "$") && StrLen(key) >= 3)
				{
					TABLE *t;
					wchar_t univalue[MAX_SIZE];
					wchar_t uniname[MAX_SIZE];

					t = ZeroMalloc(sizeof(TABLE));

					Zero(univalue, sizeof(univalue));
					Utf8ToUni(univalue, sizeof(univalue), value, StrLen(value));

					StrToUni(uniname, sizeof(uniname), key);

					t->name = (char *)CopyUniStr(uniname);
					t->unistr = CopyUniStr(univalue);

					Add(replace_list, t);

					// Found a replacement definition
					ok = false;
				}
			}
		}

		if (ok)
		{
			t = ParseTableLine(tmp, prefix, sizeof(prefix), replace_list);
			if (t != NULL)
			{
				// Register
				Insert(TableList, t);
			}
		}

		Free(tmp);
	}

	for (i = 0;i < LIST_NUM(replace_list);i++)
	{
		TABLE *t = LIST_DATA(replace_list, i);

		Free(t->name);
		Free(t->str);
		Free(t->unistr);

		Free(t);
	}

	ReleaseList(replace_list);

	return true;
}

// Generate the Unicode string cache file name
void GenerateUnicodeCacheFileName(wchar_t *name, UINT size, wchar_t *strfilename, UINT strfilesize, UCHAR *filehash)
{
	wchar_t tmp[MAX_SIZE];
	wchar_t hashstr[64];
	wchar_t hashtemp[MAX_SIZE];
	wchar_t exe[MAX_SIZE];
	UCHAR hash[SHA1_SIZE];
	// Validate arguments
	if (name == NULL || strfilename == NULL || filehash == NULL)
	{
		return;
	}

	GetExeDirW(exe, sizeof(exe));
	UniStrCpy(hashtemp, sizeof(hashtemp), strfilename);
	BinToStrW(tmp, sizeof(tmp), filehash, MD5_SIZE);
	UniStrCat(hashtemp, sizeof(hashtemp), tmp);
	UniStrCat(hashtemp, sizeof(hashtemp), exe);
	UniStrLower(hashtemp);

	Sha0(hash, hashtemp, UniStrLen(hashtemp) * sizeof(wchar_t));
	BinToStrW(hashstr, sizeof(hashstr), hash, 4);
	UniFormat(tmp, sizeof(tmp), UNICODE_CACHE_FILE, hashstr);
	UniStrLower(tmp);

#ifndef	OS_WIN32
	UniStrCpy(exe, sizeof(exe), L"/tmp");
#else	// OS_WIN32
	StrToUni(exe, sizeof(exe), MsGetTempDir());
#endif	// OS_WIN32

	UniFormat(name, size, L"%s/%s", exe, tmp);
	NormalizePathW(name, size, name);
}

// Save the Unicode cache
void SaveUnicodeCache(wchar_t *strfilename, UINT strfilesize, UCHAR *hash)
{
	UNICODE_CACHE c;
	BUF *b;
	UINT i;
	IO *io;
	wchar_t name[MAX_PATH];
	UCHAR binhash[MD5_SIZE];
	// Validate arguments
	if (strfilename == NULL || hash == NULL)
	{
		return;
	}

	Zero(&c, sizeof(c));
	UniToStr(c.StrFileName, sizeof(c.StrFileName), strfilename);
	c.StrFileSize = strfilesize;
	GetMachineName(c.MachineName, sizeof(c.MachineName));
	c.OsType = GetOsInfo()->OsType;
	Copy(c.hash, hash, MD5_SIZE);

#ifdef	OS_UNIX
	GetCurrentCharSet(c.CharSet, sizeof(c.CharSet));
#else	// OS_UNIX
	{
		UINT id = MsGetThreadLocale();
		Copy(c.CharSet, &id, sizeof(id));
	}
#endif	// OS_UNIX

	b = NewBuf();
	WriteBuf(b, &c, sizeof(c));

	WriteBufInt(b, LIST_NUM(TableList));
	for (i = 0;i < LIST_NUM(TableList);i++)
	{
		TABLE *t = LIST_DATA(TableList, i);
		WriteBufInt(b, StrLen(t->name));
		WriteBuf(b, t->name, StrLen(t->name));
		WriteBufInt(b, StrLen(t->str));
		WriteBuf(b, t->str, StrLen(t->str));
		WriteBufInt(b, UniStrLen(t->unistr));
		WriteBuf(b, t->unistr, UniStrLen(t->unistr) * sizeof(wchar_t));
	}

	Md5(binhash, b->Buf, b->Size);
	WriteBuf(b, binhash, MD5_SIZE);

	GenerateUnicodeCacheFileName(name, sizeof(name), strfilename, strfilesize, hash);

	io = FileCreateW(name);
	if (io != NULL)
	{
		SeekBuf(b, 0, 0);
		BufToFile(io, b);
		FileClose(io);
	}

	FreeBuf(b);
}

// Reading the Unicode cache
bool LoadUnicodeCache(wchar_t *strfilename, UINT strfilesize, UCHAR *hash)
{
	UNICODE_CACHE c, t;
	BUF *b;
	UINT i, num;
	IO *io;
	wchar_t name[MAX_PATH];
	UCHAR binhash[MD5_SIZE];
	UCHAR binhash_2[MD5_SIZE];
	// Validate arguments
	if (strfilename == NULL || hash == NULL)
	{
		return false;
	}

	GenerateUnicodeCacheFileName(name, sizeof(name), strfilename, strfilesize, hash);

	io = FileOpenW(name, false);
	if (io == NULL)
	{
		return false;
	}

	b = FileToBuf(io);
	if (b == NULL)
	{
		FileClose(io);
		return false;
	}

	SeekBuf(b, 0, 0);
	FileClose(io);

	Md5(binhash, b->Buf, b->Size >= MD5_SIZE ? (b->Size - MD5_SIZE) : 0);
	Copy(binhash_2, ((UCHAR *)b->Buf) + (b->Size >= MD5_SIZE ? (b->Size - MD5_SIZE) : 0), MD5_SIZE);
	if (Cmp(binhash, binhash_2, MD5_SIZE) != 0)
	{
		FreeBuf(b);
		return false;
	}

	Zero(&c, sizeof(c));
	UniToStr(c.StrFileName, sizeof(c.StrFileName), strfilename);
	c.StrFileSize = strfilesize;
	DisableNetworkNameCache();
	GetMachineName(c.MachineName, sizeof(c.MachineName));
	EnableNetworkNameCache();
	c.OsType = GetOsInfo()->OsType;
	Copy(c.hash, hash, MD5_SIZE);

#ifdef	OS_UNIX
	GetCurrentCharSet(c.CharSet, sizeof(c.CharSet));
#else	// OS_UNIX
	{
		UINT id = MsGetThreadLocale();
		Copy(c.CharSet, &id, sizeof(id));
	}
#endif	// OS_UNIX

	Zero(&t, sizeof(t));
	ReadBuf(b, &t, sizeof(t));

	if (Cmp(&c, &t, sizeof(UNICODE_CACHE)) != 0)
	{
		FreeBuf(b);
		return false;
	}

	num = ReadBufInt(b);

	FreeTable();
	TableList = NewList(CmpTableName);

	for (i = 0;i < num;i++)
	{
		UINT len;
		TABLE *t = ZeroMalloc(sizeof(TABLE));

		len = ReadBufInt(b);
		t->name = ZeroMalloc(len + 1);
		ReadBuf(b, t->name, len);

		len = ReadBufInt(b);
		t->str = ZeroMalloc(len + 1);
		ReadBuf(b, t->str, len);

		len = ReadBufInt(b);
		t->unistr = ZeroMalloc((len + 1) * sizeof(wchar_t));
		ReadBuf(b, t->unistr, len * sizeof(wchar_t));

		Add(TableList, t);
	}

	FreeBuf(b);

	Sort(TableList);

	return true;
}

// Read the string table
bool LoadTableMain(wchar_t *filename)
{
	BUF *b;
	UINT64 t1, t2;
	UCHAR hash[MD5_SIZE];
	// Validate arguments
	if (filename == NULL)
	{
		return false;
	}

	if (MayaquaIsMinimalMode())
	{
		return true;
	}

	if (UniStrCmpi(old_table_name, filename) == 0)
	{
		// Already loaded
		return true;
	}

	t1 = Tick64();

	// Open the file
	b = ReadDumpW(filename);
	if (b == NULL)
	{
		char tmp[MAX_SIZE];
		StrCpy(tmp, sizeof(tmp), "Error: Can't read string tables (file not found).\r\nPlease check hamcore.se2.\r\n\r\n(First, reboot the computer. If this problem occurs again, please reinstall VPN software files.)");
		Alert(tmp, NULL);
		exit(-1);
		return false;
	}

	Md5(hash, b->Buf, b->Size);

	if (LoadUnicodeCache(filename, b->Size, hash) == false)
	{
		if (LoadTableFromBuf(b) == false)
		{
			FreeBuf(b);
			return false;
		}

		SaveUnicodeCache(filename, b->Size, hash);

		//Debug("Unicode Source: strtable.stb\n");
	}
	else
	{
		//Debug("Unicode Source: unicode_cache\n");
	}

	FreeBuf(b);

	SetLocale(_UU("DEFAULT_LOCALE"));

	UniStrCpy(old_table_name, sizeof(old_table_name), filename);

	t2 = Tick64();

	if (StrCmpi(_SS("STRTABLE_ID"), STRTABLE_ID) != 0)
	{
		char tmp[MAX_SIZE];
		Format(tmp, sizeof(tmp), "Error: Can't read string tables (invalid version: '%s'!='%s').\r\nPlease check hamcore.se2.\r\n\r\n(First, reboot the computer. If this problem occurs again, please reinstall VPN software files.)",
			_SS("STRTABLE_ID"), STRTABLE_ID);
		Alert(tmp, NULL);
		exit(-1);
		return false;
	}

	//Debug("Unicode File Read Cost: %u (%u Lines)\n", (UINT)(t2 - t1), LIST_NUM(TableList));

	return true;
}
bool LoadTable(char *filename)
{
	wchar_t *filename_a = CopyStrToUni(filename);
	bool ret = LoadTableW(filename_a);

	Free(filename_a);

	return ret;
}
bool LoadTableW(wchar_t *filename)
{
	bool ret;
	BUF *b;
	wchar_t replace_name[MAX_PATH];

	Zero(replace_name, sizeof(replace_name));

	b = ReadDump("@table_name.txt");
	if (b != NULL)
	{
		char *s = CfgReadNextLine(b);
		if (s != NULL)
		{
			if (IsEmptyStr(s) == false)
			{
				StrToUni(replace_name, sizeof(replace_name), s);
				filename = replace_name;
			}

			Free(s);
		}
		FreeBuf(b);
	}

	ret = LoadTableMain(filename);

	return ret;
}
