1 // SoftEther VPN Source Code - Stable Edition Repository
2 // Mayaqua Kernel
3 //
4 // SoftEther VPN Server, Client and Bridge are free software under the Apache License, Version 2.0.
5 //
6 // Copyright (c) Daiyuu Nobori.
7 // Copyright (c) SoftEther VPN Project, University of Tsukuba, Japan.
8 // Copyright (c) SoftEther Corporation.
9 // Copyright (c) all contributors on SoftEther VPN project in GitHub.
10 //
11 // All Rights Reserved.
12 //
13 // http://www.softether.org/
14 //
15 // This stable branch is officially managed by Daiyuu Nobori, the owner of SoftEther VPN Project.
16 // Pull requests should be sent to the Developer Edition Master Repository on https://github.com/SoftEtherVPN/SoftEtherVPN
17 //
18 // License: The Apache License, Version 2.0
19 // https://www.apache.org/licenses/LICENSE-2.0
20 //
21 // DISCLAIMER
22 // ==========
23 //
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 // SOFTWARE.
31 //
32 // THIS SOFTWARE IS DEVELOPED IN JAPAN, AND DISTRIBUTED FROM JAPAN, UNDER
33 // JAPANESE LAWS. YOU MUST AGREE IN ADVANCE TO USE, COPY, MODIFY, MERGE, PUBLISH,
34 // DISTRIBUTE, SUBLICENSE, AND/OR SELL COPIES OF THIS SOFTWARE, THAT ANY
35 // JURIDICAL DISPUTES WHICH ARE CONCERNED TO THIS SOFTWARE OR ITS CONTENTS,
36 // AGAINST US (SOFTETHER PROJECT, SOFTETHER CORPORATION, DAIYUU NOBORI OR OTHER
37 // SUPPLIERS), OR ANY JURIDICAL DISPUTES AGAINST US WHICH ARE CAUSED BY ANY KIND
38 // OF USING, COPYING, MODIFYING, MERGING, PUBLISHING, DISTRIBUTING, SUBLICENSING,
39 // AND/OR SELLING COPIES OF THIS SOFTWARE SHALL BE REGARDED AS BE CONSTRUED AND
40 // CONTROLLED BY JAPANESE LAWS, AND YOU MUST FURTHER CONSENT TO EXCLUSIVE
41 // JURISDICTION AND VENUE IN THE COURTS SITTING IN TOKYO, JAPAN. YOU MUST WAIVE
42 // ALL DEFENSES OF LACK OF PERSONAL JURISDICTION AND FORUM NON CONVENIENS.
43 // PROCESS MAY BE SERVED ON EITHER PARTY IN THE MANNER AUTHORIZED BY APPLICABLE
44 // LAW OR COURT RULE.
45 //
46 // USE ONLY IN JAPAN. DO NOT USE THIS SOFTWARE IN ANOTHER COUNTRY UNLESS YOU HAVE
47 // A CONFIRMATION THAT THIS SOFTWARE DOES NOT VIOLATE ANY CRIMINAL LAWS OR CIVIL
48 // RIGHTS IN THAT PARTICULAR COUNTRY. USING THIS SOFTWARE IN OTHER COUNTRIES IS
49 // COMPLETELY AT YOUR OWN RISK. THE SOFTETHER VPN PROJECT HAS DEVELOPED AND
50 // DISTRIBUTED THIS SOFTWARE TO COMPLY ONLY WITH THE JAPANESE LAWS AND EXISTING
51 // CIVIL RIGHTS INCLUDING PATENTS WHICH ARE SUBJECTS APPLY IN JAPAN. OTHER
52 // COUNTRIES' LAWS OR CIVIL RIGHTS ARE NONE OF OUR CONCERNS NOR RESPONSIBILITIES.
53 // WE HAVE NEVER INVESTIGATED ANY CRIMINAL REGULATIONS, CIVIL LAWS OR
54 // INTELLECTUAL PROPERTY RIGHTS INCLUDING PATENTS IN ANY OF OTHER 200+ COUNTRIES
55 // AND TERRITORIES. BY NATURE, THERE ARE 200+ REGIONS IN THE WORLD, WITH
56 // DIFFERENT LAWS. IT IS IMPOSSIBLE TO VERIFY EVERY COUNTRIES' LAWS, REGULATIONS
57 // AND CIVIL RIGHTS TO MAKE THE SOFTWARE COMPLY WITH ALL COUNTRIES' LAWS BY THE
58 // PROJECT. EVEN IF YOU WILL BE SUED BY A PRIVATE ENTITY OR BE DAMAGED BY A
59 // PUBLIC SERVANT IN YOUR COUNTRY, THE DEVELOPERS OF THIS SOFTWARE WILL NEVER BE
60 // LIABLE TO RECOVER OR COMPENSATE SUCH DAMAGES, CRIMINAL OR CIVIL
61 // RESPONSIBILITIES. NOTE THAT THIS LINE IS NOT LICENSE RESTRICTION BUT JUST A
62 // STATEMENT FOR WARNING AND DISCLAIMER.
63 //
64 // READ AND UNDERSTAND THE 'WARNING.TXT' FILE BEFORE USING THIS SOFTWARE.
65 // SOME SOFTWARE PROGRAMS FROM THIRD PARTIES ARE INCLUDED ON THIS SOFTWARE WITH
66 // LICENSE CONDITIONS WHICH ARE DESCRIBED ON THE 'THIRD_PARTY.TXT' FILE.
67 //
68 //
69 // SOURCE CODE CONTRIBUTION
70 // ------------------------
71 //
72 // Your contribution to SoftEther VPN Project is much appreciated.
73 // Please send patches to us through GitHub.
74 // Read the SoftEther VPN Patch Acceptance Policy in advance:
75 // http://www.softether.org/5-download/src/9.patch
76 //
77 //
78 // DEAR SECURITY EXPERTS
79 // ---------------------
80 //
81 // If you find a bug or a security vulnerability please kindly inform us
82 // about the problem immediately so that we can fix the security problem
83 // to protect a lot of users around the world as soon as possible.
84 //
85 // Our e-mail address for security reports is:
86 // softether-vpn-security [at] softether.org
87 //
88 // Please note that the above e-mail address is not a technical support
89 // inquiry address. If you need technical assistance, please visit
90 // http://www.softether.org/ and ask your question on the users forum.
91 //
92 // Thank you for your cooperation.
93 //
94 //
95 // NO MEMORY OR RESOURCE LEAKS
96 // ---------------------------
97 //
98 // The memory-leaks and resource-leaks verification under the stress
99 // test has been passed before release this source code.
100 
101 
102 // Table.c
103 // Read and management routines for string table
104 
105 #include <GlobalConst.h>
106 
107 #include <stdio.h>
108 #include <stdlib.h>
109 #include <string.h>
110 #include <wchar.h>
111 #include <stdarg.h>
112 #include <time.h>
113 #include <errno.h>
114 #include <Mayaqua/Mayaqua.h>
115 
116 // List of TABLE
117 static LIST *TableList = NULL;
118 static wchar_t old_table_name[MAX_SIZE] = {0};		// Old table name
119 static LANGLIST current_lang = {0};
120 static LANGLIST current_os_lang = {0};
121 
122 // Initialization of string table routine
InitTable()123 void InitTable()
124 {
125 	LIST *o;
126 	char tmp[MAX_SIZE];
127 	LANGLIST *e = NULL;
128 	LANGLIST *os_lang = NULL;
129 	char table_name[MAX_SIZE];
130 	if (MayaquaIsMinimalMode())
131 	{
132 		// Not to load in case of minimum mode
133 		return;
134 	}
135 
136 	o = LoadLangList();
137 	if (o == NULL)
138 	{
139 LABEL_FATAL_ERROR:
140 		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);
141 		exit(-1);
142 		return;
143 	}
144 
145 	// Read the lang.config
146 	if (LoadLangConfigCurrentDir(tmp, sizeof(tmp)))
147 	{
148 		e = GetBestLangByName(o, tmp);
149 	}
150 
151 	os_lang = GetBestLangForCurrentEnvironment(o);
152 
153 	if (e == NULL)
154 	{
155 		e = os_lang;
156 	}
157 
158 	if (e == NULL)
159 	{
160 		goto LABEL_FATAL_ERROR;
161 	}
162 
163 	SaveLangConfigCurrentDir(e->Name);
164 
165 	Copy(&current_lang, e, sizeof(LANGLIST));
166 	Copy(&current_os_lang, os_lang, sizeof(LANGLIST));
167 
168 	current_lang.LangList = current_lang.LcidList = NULL;
169 	current_os_lang.LangList = current_os_lang.LcidList = NULL;
170 
171 	// Read the corresponding string table
172 	Format(table_name, sizeof(table_name), "|strtable_%s.stb", current_lang.Name);
173 	if (LoadTable(table_name) == false)
174 	{
175 		goto LABEL_FATAL_ERROR;
176 	}
177 
178 	FreeLangList(o);
179 }
180 
181 // Get the language of the current OS
GetCurrentOsLang(LANGLIST * e)182 void GetCurrentOsLang(LANGLIST *e)
183 {
184 	// Validate arguments
185 	if (e == NULL)
186 	{
187 		return;
188 	}
189 
190 	Copy(e, &current_os_lang, sizeof(LANGLIST));
191 }
192 
193 // Get the language ID of the current OS
GetCurrentOsLangId()194 UINT GetCurrentOsLangId()
195 {
196 	LANGLIST e;
197 
198 	Zero(&e, sizeof(e));
199 
200 	GetCurrentOsLang(&e);
201 
202 	return e.Id;
203 }
204 
205 // Get the current language
GetCurrentLang(LANGLIST * e)206 void GetCurrentLang(LANGLIST *e)
207 {
208 	// Validate arguments
209 	if (e == NULL)
210 	{
211 		return;
212 	}
213 
214 	Copy(e, &current_lang, sizeof(LANGLIST));
215 }
216 
217 // Get the current language ID
GetCurrentLangId()218 UINT GetCurrentLangId()
219 {
220 	LANGLIST e;
221 
222 	Zero(&e, sizeof(e));
223 
224 	GetCurrentLang(&e);
225 
226 	return e.Id;
227 }
228 
229 // Write to the lang.config file in the current directory
SaveLangConfigCurrentDir(char * str)230 bool SaveLangConfigCurrentDir(char *str)
231 {
232 	// Validate arguments
233 	if (str == NULL)
234 	{
235 		return false;
236 	}
237 
238 	return SaveLangConfig(LANG_CONFIG_FILENAME, str);
239 }
240 
241 // Write to the lang.config file
SaveLangConfig(wchar_t * filename,char * str)242 bool SaveLangConfig(wchar_t *filename, char *str)
243 {
244 	BUF *b;
245 	LIST *o;
246 	UINT i;
247 	bool ret;
248 	// Validate arguments
249 	if (filename == NULL)
250 	{
251 		return false;
252 	}
253 
254 	// Read the template
255 	b = ReadDump(LANG_CONFIG_TEMPLETE);
256 	if (b == NULL)
257 	{
258 		return false;
259 	}
260 
261 	SeekBuf(b, b->Size, 0);
262 
263 	o = LoadLangList();
264 	if (o != NULL)
265 	{
266 		wchar_t tmp[MAX_SIZE];
267 
268 		AppendBufStr(b, "# Available Language IDs are:\r\n");
269 
270 		for (i = 0;i < LIST_NUM(o);i++)
271 		{
272 			LANGLIST *e = LIST_DATA(o, i);
273 
274 			UniFormat(tmp, sizeof(tmp), L"#  %S: %s (%s)\r\n",
275 				e->Name, e->TitleEnglish, e->TitleLocal);
276 
277 			AppendBufUtf8(b, tmp);
278 		}
279 
280 		AppendBufStr(b, "\r\n\r\n# Specify a Language ID here.\r\n");
281 		AppendBufStr(b, str);
282 		AppendBufStr(b, "\r\n\r\n");
283 
284 		FreeLangList(o);
285 	}
286 
287 	ret = DumpBufWIfNecessary(b, filename);
288 
289 	FreeBuf(b);
290 
291 	return ret;
292 }
293 
294 // Read the lang.config file in the current directory
LoadLangConfigCurrentDir(char * str,UINT str_size)295 bool LoadLangConfigCurrentDir(char *str, UINT str_size)
296 {
297 	// Validate arguments
298 	if (str == NULL)
299 	{
300 		return false;
301 	}
302 
303 	return LoadLangConfig(LANG_CONFIG_FILENAME, str, str_size);
304 }
305 
306 // Read the lang.config file
LoadLangConfig(wchar_t * filename,char * str,UINT str_size)307 bool LoadLangConfig(wchar_t *filename, char *str, UINT str_size)
308 {
309 	BUF *b;
310 	bool ret = false;
311 	// Validate arguments
312 	if (filename == NULL || str == NULL)
313 	{
314 		return false;
315 	}
316 
317 	b = ReadDumpW(filename);
318 	if (b == NULL)
319 	{
320 		return false;
321 	}
322 
323 	while (true)
324 	{
325 		char *line = CfgReadNextLine(b);
326 
327 		if (line == NULL)
328 		{
329 			break;
330 		}
331 
332 		Trim(line);
333 
334 		if (IsEmptyStr(line) == false)
335 		{
336 			if (StartWith(line, "#") == false && StartWith(line, "//") == false && StartWith(line, ";") == false &&
337 				InStr(line, "#") == false)
338 			{
339 				StrCpy(str, str_size, line);
340 				ret = true;
341 			}
342 		}
343 
344 		Free(line);
345 	}
346 
347 	FreeBuf(b);
348 
349 	return ret;
350 }
351 
352 // Choose the language from the ID
GetLangById(LIST * o,UINT id)353 LANGLIST *GetLangById(LIST *o, UINT id)
354 {
355 	UINT i;
356 	// Validate arguments
357 	if (o == NULL)
358 	{
359 		return NULL;
360 	}
361 
362 	for (i = 0;i < LIST_NUM(o);i++)
363 	{
364 		LANGLIST *e = LIST_DATA(o, i);
365 
366 		if (e->Id == id)
367 		{
368 			return e;
369 		}
370 	}
371 
372 	return NULL;
373 }
374 
375 // Choice the best language for the current environment
GetBestLangForCurrentEnvironment(LIST * o)376 LANGLIST *GetBestLangForCurrentEnvironment(LIST *o)
377 {
378 	LANGLIST *ret = NULL;
379 	// Validate arguments
380 	if (o == NULL)
381 	{
382 		return NULL;
383 	}
384 
385 #ifdef	OS_WIN32
386 	ret = GetBestLangByLcid(o, MsGetUserLocaleId());
387 #else	// OS_WIN32
388 	if (true)
389 	{
390 		char lang[MAX_SIZE];
391 
392 		if (GetEnv("LANG", lang, sizeof(lang)))
393 		{
394 			ret = GetBestLangByLangStr(o, lang);
395 		}
396 		else
397 		{
398 			ret = GetBestLangByLangStr(o, "C");
399 		}
400 	}
401 #endif	// OS_WIN32
402 
403 	return ret;
404 }
405 
406 // Search for the best language from LANG string of UNIX
GetBestLangByLangStr(LIST * o,char * str)407 LANGLIST *GetBestLangByLangStr(LIST *o, char *str)
408 {
409 	UINT i;
410 	LANGLIST *ret;
411 	// Validate arguments
412 	if (o == NULL)
413 	{
414 		return NULL;
415 	}
416 
417 	for (i = 0;i < LIST_NUM(o);i++)
418 	{
419 		LANGLIST *e = LIST_DATA(o, i);
420 		UINT j;
421 
422 		for (j = 0;j < LIST_NUM(e->LangList);j++)
423 		{
424 			char *v = LIST_DATA(e->LangList, j);
425 
426 			if (StrCmpi(v, str) == 0)
427 			{
428 				return e;
429 			}
430 		}
431 	}
432 
433 	for (i = 0;i < LIST_NUM(o);i++)
434 	{
435 		LANGLIST *e = LIST_DATA(o, i);
436 		UINT j;
437 
438 		for (j = 0;j < LIST_NUM(e->LangList);j++)
439 		{
440 			char *v = LIST_DATA(e->LangList, j);
441 
442 			if (StartWith(str, v) || StartWith(v, str))
443 			{
444 				return e;
445 			}
446 		}
447 	}
448 
449 	ret = GetBestLangByName(o, "en");
450 
451 	return ret;
452 }
453 
454 // Search for the best language from LCID
GetBestLangByLcid(LIST * o,UINT lcid)455 LANGLIST *GetBestLangByLcid(LIST *o, UINT lcid)
456 {
457 	LANGLIST *ret;
458 	UINT i;
459 	// Validate arguments
460 	if (o == NULL)
461 	{
462 		return NULL;
463 	}
464 
465 	for (i = 0;i < LIST_NUM(o);i++)
466 	{
467 		LANGLIST *e = LIST_DATA(o, i);
468 
469 		if (IsIntInList(e->LcidList, lcid))
470 		{
471 			return e;
472 		}
473 	}
474 
475 	ret = GetBestLangByName(o, "en");
476 
477 	return ret;
478 }
479 
480 // Search for the best language from the name
GetBestLangByName(LIST * o,char * name)481 LANGLIST *GetBestLangByName(LIST *o, char *name)
482 {
483 	UINT i;
484 	LANGLIST *ret = NULL;
485 	// Validate arguments
486 	if (o == NULL)
487 	{
488 		return NULL;
489 	}
490 
491 	for (i = 0;i < LIST_NUM(o);i++)
492 	{
493 		LANGLIST *e = LIST_DATA(o, i);
494 
495 		if (StrCmpi(e->Name, name) == 0)
496 		{
497 			ret = e;
498 			break;
499 		}
500 	}
501 
502 	if (ret != NULL)
503 	{
504 		return ret;
505 	}
506 
507 	for (i = 0;i < LIST_NUM(o);i++)
508 	{
509 		LANGLIST *e = LIST_DATA(o, i);
510 
511 		if (StartWith(e->Name, name) || StartWith(name, e->Name))
512 		{
513 			ret = e;
514 			break;
515 		}
516 	}
517 
518 	if (ret != NULL)
519 	{
520 		return ret;
521 	}
522 
523 	return ret;
524 }
525 
526 // Release the language list
FreeLangList(LIST * o)527 void FreeLangList(LIST *o)
528 {
529 	UINT i;
530 	// Validate arguments
531 	if (o == NULL)
532 	{
533 		return;
534 	}
535 
536 	for (i = 0;i < LIST_NUM(o);i++)
537 	{
538 		LANGLIST *e = LIST_DATA(o, i);
539 
540 		FreeStrList(e->LangList);
541 		ReleaseIntList(e->LcidList);
542 
543 		Free(e);
544 	}
545 
546 	ReleaseList(o);
547 }
548 
549 // Read the language list
LoadLangList()550 LIST *LoadLangList()
551 {
552 	LIST *o = NewListFast(NULL);
553 	char *filename = LANGLIST_FILENAME;
554 	BUF *b;
555 
556 #ifdef	OS_WIN32
557 	if (MsIsWine())
558 	{
559 		filename = LANGLIST_FILENAME_WINE;
560 	}
561 #endif	// OS_WIN32
562 
563 	b = ReadDump(filename);
564 	if (b == NULL)
565 	{
566 		return NULL;
567 	}
568 
569 	while (true)
570 	{
571 		char *line = CfgReadNextLine(b);
572 
573 		if (line == NULL)
574 		{
575 			break;
576 		}
577 
578 		Trim(line);
579 
580 		if (IsEmptyStr(line) == false && StartWith(line, "#") == false)
581 		{
582 			TOKEN_LIST *t = ParseToken(line, "\t ");
583 			if (t != NULL)
584 			{
585 				if (t->NumTokens == 6)
586 				{
587 					LANGLIST *e = ZeroMalloc(sizeof(LANGLIST));
588 					TOKEN_LIST *t2;
589 
590 					e->Id = ToInt(t->Token[0]);
591 					StrCpy(e->Name, sizeof(e->Name), t->Token[1]);
592 					Utf8ToUni(e->TitleEnglish, sizeof(e->TitleEnglish), t->Token[2], StrLen(t->Token[2]));
593 					Utf8ToUni(e->TitleLocal, sizeof(e->TitleLocal), t->Token[3], StrLen(t->Token[3]));
594 
595 					UniReplaceStrEx(e->TitleEnglish, sizeof(e->TitleEnglish), e->TitleEnglish,
596 						L"_", L" ", true);
597 
598 					UniReplaceStrEx(e->TitleLocal, sizeof(e->TitleLocal), e->TitleLocal,
599 						L"_", L" ", true);
600 
601 					e->LcidList = NewIntList(false);
602 
603 					t2 = ParseToken(t->Token[4], ",");
604 					if (t2 != NULL)
605 					{
606 						UINT i;
607 
608 						for (i = 0;i < t2->NumTokens;i++)
609 						{
610 							UINT id = ToInt(t2->Token[i]);
611 
612 							AddIntDistinct(e->LcidList, id);
613 						}
614 
615 						FreeToken(t2);
616 					}
617 
618 					e->LangList = NewListFast(NULL);
619 
620 					t2 = ParseToken(t->Token[5], ",");
621 					if (t2 != NULL)
622 					{
623 						UINT i;
624 
625 						for (i = 0;i < t2->NumTokens;i++)
626 						{
627 							Add(e->LangList, CopyStr(t2->Token[i]));
628 						}
629 
630 						FreeToken(t2);
631 					}
632 
633 					Add(o, e);
634 				}
635 
636 				FreeToken(t);
637 			}
638 		}
639 
640 		Free(line);
641 	}
642 
643 	FreeBuf(b);
644 
645 	return o;
646 }
647 
648 // Get an error string in Unicode
GetUniErrorStr(UINT err)649 wchar_t *GetUniErrorStr(UINT err)
650 {
651 	wchar_t *ret;
652 	char name[MAX_SIZE];
653 	Format(name, sizeof(name), "ERR_%u", err);
654 
655 	ret = GetTableUniStr(name);
656 	if (UniStrLen(ret) != 0)
657 	{
658 		return ret;
659 	}
660 	else
661 	{
662 		return _UU("ERR_UNKNOWN");
663 	}
664 }
665 
666 // Get an error string
GetErrorStr(UINT err)667 char *GetErrorStr(UINT err)
668 {
669 	char *ret;
670 	char name[MAX_SIZE];
671 	Format(name, sizeof(name), "ERR_%u", err);
672 
673 	ret = GetTableStr(name);
674 	if (StrLen(ret) != 0)
675 	{
676 		return ret;
677 	}
678 	else
679 	{
680 		return _SS("ERR_UNKNOWN");
681 	}
682 }
683 
684 // Load the integer value from the table
GetTableInt(char * name)685 UINT GetTableInt(char *name)
686 {
687 	char *str;
688 	// Validate arguments
689 	if (name == NULL)
690 	{
691 		return 0;
692 	}
693 
694 	str = GetTableStr(name);
695 	return ToInt(str);
696 }
697 
698 // Load a Unicode string from the table
GetTableUniStr(char * name)699 wchar_t *GetTableUniStr(char *name)
700 {
701 	TABLE *t;
702 	// Validate arguments
703 	if (name == NULL)
704 	{
705 //		Debug("%s: ************\n", name);
706 		return L"";
707 	}
708 
709 	// Search
710 	t = FindTable(name);
711 	if (t == NULL)
712 	{
713 		//Debug("%s: UNICODE STRING NOT FOUND\n", name);
714 		return L"";
715 	}
716 
717 	return t->unistr;
718 }
719 
720 // Load the string from the table
GetTableStr(char * name)721 char *GetTableStr(char *name)
722 {
723 	TABLE *t;
724 	// Validate arguments
725 	if (name == NULL)
726 	{
727 		return "";
728 	}
729 
730 #ifdef	OS_WIN32
731 	if (StrCmpi(name, "DEFAULT_FONT") == 0)
732 	{
733 		if (_II("LANG") == 2)
734 		{
735 			UINT os_type = GetOsType();
736 			if (OS_IS_WINDOWS_9X(os_type) ||
737 				GET_KETA(os_type, 100) <= 4)
738 			{
739 				// Use the SimSun font in Windows 9x, Windows NT 4.0, Windows 2000, Windows XP, and Windows Server 2003
740 				return "SimSun";
741 			}
742 		}
743 	}
744 #endif	// OS_WIN32
745 
746 	// Search
747 	t = FindTable(name);
748 	if (t == NULL)
749 	{
750 		//Debug("%s: ANSI STRING NOT FOUND\n", name);
751 		return "";
752 	}
753 
754 	return t->str;
755 }
756 
757 // Get the string name that begins with the specified name
GetTableNameStartWith(char * str)758 TOKEN_LIST *GetTableNameStartWith(char *str)
759 {
760 	UINT i;
761 	UINT len;
762 	LIST *o;
763 	TOKEN_LIST *t;
764 	char tmp[MAX_SIZE];
765 	// Validate arguments
766 	if (str == NULL)
767 	{
768 		return NullToken();
769 	}
770 
771 	StrCpy(tmp, sizeof(tmp), str);
772 	StrUpper(tmp);
773 
774 	len = StrLen(tmp);
775 
776 	o = NewListFast(NULL);
777 
778 	for (i = 0;i < LIST_NUM(TableList);i++)
779 	{
780 		TABLE *t = LIST_DATA(TableList, i);
781 		UINT len2 = StrLen(t->name);
782 
783 		if (len2 >= len)
784 		{
785 			if (Cmp(t->name, tmp, len) == 0)
786 			{
787 				Insert(o, CopyStr(t->name));
788 			}
789 		}
790 	}
791 
792 	t = ZeroMalloc(sizeof(TOKEN_LIST));
793 	t->NumTokens = LIST_NUM(o);
794 	t->Token = ZeroMalloc(sizeof(char *) * t->NumTokens);
795 
796 	for (i = 0;i < t->NumTokens;i++)
797 	{
798 		t->Token[i] = LIST_DATA(o, i);
799 	}
800 
801 	ReleaseList(o);
802 
803 	return t;
804 }
805 
806 // Search the table
FindTable(char * name)807 TABLE *FindTable(char *name)
808 {
809 	TABLE *t, tt;
810 	// Validate arguments
811 	if (name == NULL || TableList == NULL)
812 	{
813 		return NULL;
814 	}
815 
816 	tt.name = CopyStr(name);
817 	t = Search(TableList, &tt);
818 	Free(tt.name);
819 
820 	return t;
821 }
822 
823 // A function that compares the table name
CmpTableName(void * p1,void * p2)824 int CmpTableName(void *p1, void *p2)
825 {
826 	TABLE *t1, *t2;
827 	if (p1 == NULL || p2 == NULL)
828 	{
829 		return 0;
830 	}
831 	t1 = *(TABLE **)p1;
832 	t2 = *(TABLE **)p2;
833 	if (t1 == NULL || t2 == NULL)
834 	{
835 		return 0;
836 	}
837 
838 	return StrCmpi(t1->name, t2->name);
839 }
840 
841 // Interpret a line
ParseTableLine(char * line,char * prefix,UINT prefix_size,LIST * replace_list)842 TABLE *ParseTableLine(char *line, char *prefix, UINT prefix_size, LIST *replace_list)
843 {
844 	UINT i, len;
845 	UINT len_name;
846 	UINT string_start;
847 	char *name;
848 	char *name2;
849 	UINT name2_size;
850 	wchar_t *unistr;
851 	char *str;
852 	UINT unistr_size, str_size;
853 	TABLE *t;
854 	// Validate arguments
855 	if (line == NULL || prefix == NULL)
856 	{
857 		return NULL;
858 	}
859 	TrimLeft(line);
860 
861 	// No line
862 	len = StrLen(line);
863 	if (len == 0)
864 	{
865 		return NULL;
866 	}
867 
868 	// Comment
869 	if (line[0] == '#' || (line[0] == '/' && line[1] == '/'))
870 	{
871 		return NULL;
872 	}
873 
874 	// Search to the end position of the name
875 	len_name = 0;
876 	for (i = 0;;i++)
877 	{
878 		if (line[i] == 0)
879 		{
880 			// There is only one token
881 			return NULL;
882 		}
883 		if (line[i] == ' ' || line[i] == '\t')
884 		{
885 			break;
886 		}
887 		len_name++;
888 	}
889 
890 	name = Malloc(len_name + 1);
891 	StrCpy(name, len_name + 1, line);
892 
893 	string_start = len_name;
894 	for (i = len_name;i < len;i++)
895 	{
896 		if (line[i] != ' ' && line[i] != '\t')
897 		{
898 			break;
899 		}
900 		string_start++;
901 	}
902 	if (i == len)
903 	{
904 		Free(name);
905 		return NULL;
906 	}
907 
908 	// Unescape
909 	UnescapeStr(&line[string_start]);
910 
911 	// Convert to Unicode
912 	unistr_size = CalcUtf8ToUni(&line[string_start], StrLen(&line[string_start]));
913 	if (unistr_size == 0)
914 	{
915 		Free(name);
916 		return NULL;
917 	}
918 	unistr = Malloc(unistr_size);
919 	Utf8ToUni(unistr, unistr_size, &line[string_start], StrLen(&line[string_start]));
920 
921 	if (UniInChar(unistr, L'$'))
922 	{
923 		// Replace the replacement string
924 		wchar_t *tmp;
925 		UINT tmp_size = (UniStrSize(unistr) + 1024) * 2;
926 		UINT i;
927 
928 		tmp = Malloc(tmp_size);
929 
930 		UniStrCpy(tmp, tmp_size, unistr);
931 
932 		for (i = 0; i < LIST_NUM(replace_list);i++)
933 		{
934 			TABLE *r = LIST_DATA(replace_list, i);
935 
936 			UniReplaceStrEx(tmp, tmp_size, tmp, (wchar_t *)r->name, r->unistr, false);
937 		}
938 
939 		Free(unistr);
940 
941 		unistr = CopyUniStr(tmp);
942 
943 		Free(tmp);
944 	}
945 
946 	// Convert to ANSI
947 	str_size = CalcUniToStr(unistr);
948 	if (str_size == 0)
949 	{
950 		str_size = 1;
951 		str = Malloc(1);
952 		str[0] = 0;
953 	}
954 	else
955 	{
956 		str = Malloc(str_size);
957 		UniToStr(str, str_size, unistr);
958 	}
959 
960 	if (StrCmpi(name, "PREFIX") == 0)
961 	{
962 		// Prefix is specified
963 		StrCpy(prefix, prefix_size, str);
964 		Trim(prefix);
965 
966 		if (StrCmpi(prefix, "$") == 0 || StrCmpi(prefix, "NULL") == 0)
967 		{
968 			prefix[0] = 0;
969 		}
970 
971 		Free(name);
972 		Free(str);
973 		Free(unistr);
974 
975 		return NULL;
976 	}
977 
978 	name2_size = StrLen(name) + StrLen(prefix) + 2;
979 	name2 = ZeroMalloc(name2_size);
980 
981 	if (prefix[0] != 0)
982 	{
983 		StrCat(name2, name2_size, prefix);
984 		StrCat(name2, name2_size, "@");
985 	}
986 
987 	StrCat(name2, name2_size, name);
988 
989 	Free(name);
990 
991 	// Create a TABLE
992 	t = Malloc(sizeof(TABLE));
993 	StrUpper(name2);
994 	t->name = name2;
995 	t->str = str;
996 	t->unistr = unistr;
997 
998 	return t;
999 }
1000 
1001 // Unescape the string
UnescapeStr(char * src)1002 void UnescapeStr(char *src)
1003 {
1004 	UINT i, len, wp;
1005 	char *tmp;
1006 	// Validate arguments
1007 	if (src == NULL)
1008 	{
1009 		return;
1010 	}
1011 
1012 	len = StrLen(src);
1013 	tmp = Malloc(len + 1);
1014 	wp = 0;
1015 	for (i = 0;i < len;i++)
1016 	{
1017 		if (src[i] == '\\')
1018 		{
1019 			i++;
1020 			switch (src[i])
1021 			{
1022 			case 0:
1023 				goto FINISH;
1024 			case '\\':
1025 				tmp[wp++] = '\\';
1026 				break;
1027 			case ' ':
1028 				tmp[wp++] = ' ';
1029 				break;
1030 			case 'n':
1031 			case 'N':
1032 				tmp[wp++] = '\n';
1033 				break;
1034 			case 'r':
1035 			case 'R':
1036 				tmp[wp++] = '\r';
1037 				break;
1038 			case 't':
1039 			case 'T':
1040 				tmp[wp++] = '\t';
1041 				break;
1042 			}
1043 		}
1044 		else
1045 		{
1046 			tmp[wp++] = src[i];
1047 		}
1048 	}
1049 FINISH:
1050 	tmp[wp++] = 0;
1051 	StrCpy(src, 0, tmp);
1052 	Free(tmp);
1053 }
1054 
1055 // Release the table
FreeTable()1056 void FreeTable()
1057 {
1058 	UINT i, num;
1059 	TABLE **tables;
1060 	if (TableList == NULL)
1061 	{
1062 		return;
1063 	}
1064 
1065 	TrackingDisable();
1066 
1067 	num = LIST_NUM(TableList);
1068 	tables = ToArray(TableList);
1069 	for (i = 0;i < num;i++)
1070 	{
1071 		TABLE *t = tables[i];
1072 		Free(t->name);
1073 		Free(t->str);
1074 		Free(t->unistr);
1075 		Free(t);
1076 	}
1077 	ReleaseList(TableList);
1078 	TableList = NULL;
1079 	Free(tables);
1080 
1081 	Zero(old_table_name, sizeof(old_table_name));
1082 
1083 	TrackingEnable();
1084 }
1085 
1086 // Read a string table from the buffer
LoadTableFromBuf(BUF * b)1087 bool LoadTableFromBuf(BUF *b)
1088 {
1089 	char *tmp;
1090 	char prefix[MAX_SIZE];
1091 	LIST *replace_list = NULL;
1092 	UINT i;
1093 	// Validate arguments
1094 	if (b == NULL)
1095 	{
1096 		return false;
1097 	}
1098 
1099 	// If the table already exists, delete it
1100 	FreeTable();
1101 
1102 	// Create a list
1103 	TableList = NewList(CmpTableName);
1104 
1105 	Zero(prefix, sizeof(prefix));
1106 
1107 	replace_list = NewListFast(NULL);
1108 
1109 	// Read the contents of the buffer line by line
1110 	while (true)
1111 	{
1112 		TABLE *t;
1113 		bool ok = true;
1114 
1115 		tmp = CfgReadNextLine(b);
1116 		if (tmp == NULL)
1117 		{
1118 			break;
1119 		}
1120 
1121 		if (tmp[0] == '$')
1122 		{
1123 			char key[128];
1124 			char value[MAX_SIZE];
1125 			if (GetKeyAndValue(tmp, key, sizeof(key), value, sizeof(value), " \t"))
1126 			{
1127 				if (StartWith(key, "$") && EndWith(key, "$") && StrLen(key) >= 3)
1128 				{
1129 					TABLE *t;
1130 					wchar_t univalue[MAX_SIZE];
1131 					wchar_t uniname[MAX_SIZE];
1132 
1133 					t = ZeroMalloc(sizeof(TABLE));
1134 
1135 					Zero(univalue, sizeof(univalue));
1136 					Utf8ToUni(univalue, sizeof(univalue), value, StrLen(value));
1137 
1138 					StrToUni(uniname, sizeof(uniname), key);
1139 
1140 					t->name = (char *)CopyUniStr(uniname);
1141 					t->unistr = CopyUniStr(univalue);
1142 
1143 					Add(replace_list, t);
1144 
1145 					// Found a replacement definition
1146 					ok = false;
1147 				}
1148 			}
1149 		}
1150 
1151 		if (ok)
1152 		{
1153 			t = ParseTableLine(tmp, prefix, sizeof(prefix), replace_list);
1154 			if (t != NULL)
1155 			{
1156 				// Register
1157 				Insert(TableList, t);
1158 			}
1159 		}
1160 
1161 		Free(tmp);
1162 	}
1163 
1164 	for (i = 0;i < LIST_NUM(replace_list);i++)
1165 	{
1166 		TABLE *t = LIST_DATA(replace_list, i);
1167 
1168 		Free(t->name);
1169 		Free(t->str);
1170 		Free(t->unistr);
1171 
1172 		Free(t);
1173 	}
1174 
1175 	ReleaseList(replace_list);
1176 
1177 	return true;
1178 }
1179 
1180 // Generate the Unicode string cache file name
GenerateUnicodeCacheFileName(wchar_t * name,UINT size,wchar_t * strfilename,UINT strfilesize,UCHAR * filehash)1181 void GenerateUnicodeCacheFileName(wchar_t *name, UINT size, wchar_t *strfilename, UINT strfilesize, UCHAR *filehash)
1182 {
1183 	wchar_t tmp[MAX_SIZE];
1184 	wchar_t hashstr[64];
1185 	wchar_t hashtemp[MAX_SIZE];
1186 	wchar_t exe[MAX_SIZE];
1187 	UCHAR hash[SHA1_SIZE];
1188 	// Validate arguments
1189 	if (name == NULL || strfilename == NULL || filehash == NULL)
1190 	{
1191 		return;
1192 	}
1193 
1194 	GetExeDirW(exe, sizeof(exe));
1195 	UniStrCpy(hashtemp, sizeof(hashtemp), strfilename);
1196 	BinToStrW(tmp, sizeof(tmp), filehash, MD5_SIZE);
1197 	UniStrCat(hashtemp, sizeof(hashtemp), tmp);
1198 	UniStrCat(hashtemp, sizeof(hashtemp), exe);
1199 	UniStrLower(hashtemp);
1200 
1201 	Hash(hash, hashtemp, UniStrLen(hashtemp) * sizeof(wchar_t), true);
1202 	BinToStrW(hashstr, sizeof(hashstr), hash, 4);
1203 	UniFormat(tmp, sizeof(tmp), UNICODE_CACHE_FILE, hashstr);
1204 	UniStrLower(tmp);
1205 
1206 #ifndef	OS_WIN32
1207 	UniStrCpy(exe, sizeof(exe), L"/tmp");
1208 #else	// OS_WIN32
1209 	StrToUni(exe, sizeof(exe), MsGetTempDir());
1210 #endif	// OS_WIN32
1211 
1212 	UniFormat(name, size, L"%s/%s", exe, tmp);
1213 	NormalizePathW(name, size, name);
1214 }
1215 
1216 // Save the Unicode cache
SaveUnicodeCache(wchar_t * strfilename,UINT strfilesize,UCHAR * hash)1217 void SaveUnicodeCache(wchar_t *strfilename, UINT strfilesize, UCHAR *hash)
1218 {
1219 	UNICODE_CACHE c;
1220 	BUF *b;
1221 	UINT i;
1222 	IO *io;
1223 	wchar_t name[MAX_PATH];
1224 	UCHAR binhash[MD5_SIZE];
1225 	// Validate arguments
1226 	if (strfilename == NULL || hash == NULL)
1227 	{
1228 		return;
1229 	}
1230 
1231 	Zero(&c, sizeof(c));
1232 	UniToStr(c.StrFileName, sizeof(c.StrFileName), strfilename);
1233 	c.StrFileSize = strfilesize;
1234 	GetMachineName(c.MachineName, sizeof(c.MachineName));
1235 	c.OsType = GetOsInfo()->OsType;
1236 	Copy(c.hash, hash, MD5_SIZE);
1237 
1238 #ifdef	OS_UNIX
1239 	GetCurrentCharSet(c.CharSet, sizeof(c.CharSet));
1240 #else	// OS_UNIX
1241 	{
1242 		UINT id = MsGetThreadLocale();
1243 		Copy(c.CharSet, &id, sizeof(id));
1244 	}
1245 #endif	// OS_UNIX
1246 
1247 	b = NewBuf();
1248 	WriteBuf(b, &c, sizeof(c));
1249 
1250 	WriteBufInt(b, LIST_NUM(TableList));
1251 	for (i = 0;i < LIST_NUM(TableList);i++)
1252 	{
1253 		TABLE *t = LIST_DATA(TableList, i);
1254 		WriteBufInt(b, StrLen(t->name));
1255 		WriteBuf(b, t->name, StrLen(t->name));
1256 		WriteBufInt(b, StrLen(t->str));
1257 		WriteBuf(b, t->str, StrLen(t->str));
1258 		WriteBufInt(b, UniStrLen(t->unistr));
1259 		WriteBuf(b, t->unistr, UniStrLen(t->unistr) * sizeof(wchar_t));
1260 	}
1261 
1262 	Hash(binhash, b->Buf, b->Size, false);
1263 	WriteBuf(b, binhash, MD5_SIZE);
1264 
1265 	GenerateUnicodeCacheFileName(name, sizeof(name), strfilename, strfilesize, hash);
1266 
1267 	io = FileCreateW(name);
1268 	if (io != NULL)
1269 	{
1270 		SeekBuf(b, 0, 0);
1271 		BufToFile(io, b);
1272 		FileClose(io);
1273 	}
1274 
1275 	FreeBuf(b);
1276 }
1277 
1278 // Reading the Unicode cache
LoadUnicodeCache(wchar_t * strfilename,UINT strfilesize,UCHAR * hash)1279 bool LoadUnicodeCache(wchar_t *strfilename, UINT strfilesize, UCHAR *hash)
1280 {
1281 	UNICODE_CACHE c, t;
1282 	BUF *b;
1283 	UINT i, num;
1284 	IO *io;
1285 	wchar_t name[MAX_PATH];
1286 	UCHAR binhash[MD5_SIZE];
1287 	UCHAR binhash_2[MD5_SIZE];
1288 	// Validate arguments
1289 	if (strfilename == NULL || hash == NULL)
1290 	{
1291 		return false;
1292 	}
1293 
1294 	GenerateUnicodeCacheFileName(name, sizeof(name), strfilename, strfilesize, hash);
1295 
1296 	io = FileOpenW(name, false);
1297 	if (io == NULL)
1298 	{
1299 		return false;
1300 	}
1301 
1302 	b = FileToBuf(io);
1303 	if (b == NULL)
1304 	{
1305 		FileClose(io);
1306 		return false;
1307 	}
1308 
1309 	SeekBuf(b, 0, 0);
1310 	FileClose(io);
1311 
1312 	Hash(binhash, b->Buf, b->Size >= MD5_SIZE ? (b->Size - MD5_SIZE) : 0, false);
1313 	Copy(binhash_2, ((UCHAR *)b->Buf) + (b->Size >= MD5_SIZE ? (b->Size - MD5_SIZE) : 0), MD5_SIZE);
1314 	if (Cmp(binhash, binhash_2, MD5_SIZE) != 0)
1315 	{
1316 		FreeBuf(b);
1317 		return false;
1318 	}
1319 
1320 	Zero(&c, sizeof(c));
1321 	UniToStr(c.StrFileName, sizeof(c.StrFileName), strfilename);
1322 	c.StrFileSize = strfilesize;
1323 	DisableNetworkNameCache();
1324 	GetMachineName(c.MachineName, sizeof(c.MachineName));
1325 	EnableNetworkNameCache();
1326 	c.OsType = GetOsInfo()->OsType;
1327 	Copy(c.hash, hash, MD5_SIZE);
1328 
1329 #ifdef	OS_UNIX
1330 	GetCurrentCharSet(c.CharSet, sizeof(c.CharSet));
1331 #else	// OS_UNIX
1332 	{
1333 		UINT id = MsGetThreadLocale();
1334 		Copy(c.CharSet, &id, sizeof(id));
1335 	}
1336 #endif	// OS_UNIX
1337 
1338 	Zero(&t, sizeof(t));
1339 	ReadBuf(b, &t, sizeof(t));
1340 
1341 	if (Cmp(&c, &t, sizeof(UNICODE_CACHE)) != 0)
1342 	{
1343 		FreeBuf(b);
1344 		return false;
1345 	}
1346 
1347 	num = ReadBufInt(b);
1348 
1349 	FreeTable();
1350 	TableList = NewList(CmpTableName);
1351 
1352 	for (i = 0;i < num;i++)
1353 	{
1354 		UINT len;
1355 		TABLE *t = ZeroMalloc(sizeof(TABLE));
1356 
1357 		len = ReadBufInt(b);
1358 		t->name = ZeroMalloc(len + 1);
1359 		ReadBuf(b, t->name, len);
1360 
1361 		len = ReadBufInt(b);
1362 		t->str = ZeroMalloc(len + 1);
1363 		ReadBuf(b, t->str, len);
1364 
1365 		len = ReadBufInt(b);
1366 		t->unistr = ZeroMalloc((len + 1) * sizeof(wchar_t));
1367 		ReadBuf(b, t->unistr, len * sizeof(wchar_t));
1368 
1369 		Add(TableList, t);
1370 	}
1371 
1372 	FreeBuf(b);
1373 
1374 	Sort(TableList);
1375 
1376 	return true;
1377 }
1378 
1379 // Read the string table
LoadTableMain(wchar_t * filename)1380 bool LoadTableMain(wchar_t *filename)
1381 {
1382 	BUF *b;
1383 	UINT64 t1, t2;
1384 	UCHAR hash[MD5_SIZE];
1385 	// Validate arguments
1386 	if (filename == NULL)
1387 	{
1388 		return false;
1389 	}
1390 
1391 	if (MayaquaIsMinimalMode())
1392 	{
1393 		return true;
1394 	}
1395 
1396 	if (UniStrCmpi(old_table_name, filename) == 0)
1397 	{
1398 		// Already loaded
1399 		return true;
1400 	}
1401 
1402 	t1 = Tick64();
1403 
1404 	// Open the file
1405 	b = ReadDumpW(filename);
1406 	if (b == NULL)
1407 	{
1408 		char tmp[MAX_SIZE];
1409 		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.)");
1410 		Alert(tmp, NULL);
1411 		exit(-1);
1412 		return false;
1413 	}
1414 
1415 	Hash(hash, b->Buf, b->Size, false);
1416 
1417 	if (LoadUnicodeCache(filename, b->Size, hash) == false)
1418 	{
1419 		if (LoadTableFromBuf(b) == false)
1420 		{
1421 			FreeBuf(b);
1422 			return false;
1423 		}
1424 
1425 		SaveUnicodeCache(filename, b->Size, hash);
1426 
1427 		//Debug("Unicode Source: strtable.stb\n");
1428 	}
1429 	else
1430 	{
1431 		//Debug("Unicode Source: unicode_cache\n");
1432 	}
1433 
1434 	FreeBuf(b);
1435 
1436 	SetLocale(_UU("DEFAULE_LOCALE"));
1437 
1438 	UniStrCpy(old_table_name, sizeof(old_table_name), filename);
1439 
1440 	t2 = Tick64();
1441 
1442 	if (StrCmpi(_SS("STRTABLE_ID"), STRTABLE_ID) != 0)
1443 	{
1444 		char tmp[MAX_SIZE];
1445 		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.)",
1446 			_SS("STRTABLE_ID"), STRTABLE_ID);
1447 		Alert(tmp, NULL);
1448 		exit(-1);
1449 		return false;
1450 	}
1451 
1452 	//Debug("Unicode File Read Cost: %u (%u Lines)\n", (UINT)(t2 - t1), LIST_NUM(TableList));
1453 
1454 	return true;
1455 }
LoadTable(char * filename)1456 bool LoadTable(char *filename)
1457 {
1458 	wchar_t *filename_a = CopyStrToUni(filename);
1459 	bool ret = LoadTableW(filename_a);
1460 
1461 	Free(filename_a);
1462 
1463 	return ret;
1464 }
LoadTableW(wchar_t * filename)1465 bool LoadTableW(wchar_t *filename)
1466 {
1467 	bool ret;
1468 	BUF *b;
1469 	wchar_t replace_name[MAX_PATH];
1470 
1471 	Zero(replace_name, sizeof(replace_name));
1472 
1473 	TrackingDisable();
1474 
1475 	b = ReadDump("@table_name.txt");
1476 	if (b != NULL)
1477 	{
1478 		char *s = CfgReadNextLine(b);
1479 		if (s != NULL)
1480 		{
1481 			if (IsEmptyStr(s) == false)
1482 			{
1483 				StrToUni(replace_name, sizeof(replace_name), s);
1484 				filename = replace_name;
1485 			}
1486 
1487 			Free(s);
1488 		}
1489 		FreeBuf(b);
1490 	}
1491 
1492 	ret = LoadTableMain(filename);
1493 
1494 	TrackingEnable();
1495 
1496 	return ret;
1497 }
1498 
1499 
1500