Full Unicode patch with RTL Support & BiDi logic.

This commit is well documented, so no need to tell you my life story.

Full Unicode patch with RTL Support & BiDi logic.

Removed the legacy codePage, normalised to UTF8 (65001).

It also comes with:

CTRL + A : select text (highlighted)
CTRL + C : copy
CTRL + V : paste
CTRL + X : cut
CTRL + Y : redo
CTRL + Z : undo
This commit is contained in:
rtw1x1
2025-12-26 12:32:43 +00:00
parent d37607baa1
commit a955c50744
86 changed files with 4076 additions and 3839 deletions

View File

@@ -13,21 +13,24 @@
#include "EterBase/lzo.h"
#include "PackLib/PackManager.h"
#include <filesystem>
#include <format>
extern "C" {
extern int _fltused;
volatile int _AVOID_FLOATING_POINT_LIBRARY_BUG = _fltused;
};
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
#include <stdlib.h>
#include <utf8.h>
extern "C" {
extern int _fltused;
FILE __iob_func[3] = { *stdin, *stdout, *stderr };
volatile int _AVOID_FLOATING_POINT_LIBRARY_BUG = _fltused;
__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
};
#pragma comment(linker, "/NODEFAULTLIB:libci.lib")
#include <stdlib.h>
bool __IS_TEST_SERVER_MODE__=false;
extern bool SetDefaultCodePage(DWORD codePage);
int Setup(LPSTR lpCmdLine);
static const char * sc_apszPythonLibraryFilenames[] =
{
"UserDict.pyc",
@@ -44,98 +47,30 @@ static const char * sc_apszPythonLibraryFilenames[] =
"\n",
};
char gs_szErrorString[512] = "";
void ApplicationSetErrorString(const char* szErrorString)
{
strcpy(gs_szErrorString, szErrorString);
}
bool CheckPythonLibraryFilenames()
{
for (int i = 0; *sc_apszPythonLibraryFilenames[i] != '\n'; ++i)
{
std::string stFilename = "lib\\";
stFilename += sc_apszPythonLibraryFilenames[i];
std::string stFilenameUtf8 = "lib\\";
stFilenameUtf8 += sc_apszPythonLibraryFilenames[i];
if (_access(stFilename.c_str(), 0) != 0)
std::wstring stFilenameW = Utf8ToWide(stFilenameUtf8);
// Check existence
if (GetFileAttributesW(stFilenameW.c_str()) == INVALID_FILE_ATTRIBUTES)
{
return false;
}
MoveFile(stFilename.c_str(), stFilename.c_str());
// Keep original behavior (forces Windows path normalization)
MoveFileW(stFilenameW.c_str(), stFilenameW.c_str());
}
return true;
}
struct ApplicationStringTable
{
HINSTANCE m_hInstance;
std::map<DWORD, std::string> m_kMap_dwID_stLocale;
} gs_kAppStrTable;
void ApplicationStringTable_Initialize(HINSTANCE hInstance)
{
gs_kAppStrTable.m_hInstance=hInstance;
}
const std::string& ApplicationStringTable_GetString(DWORD dwID, LPCSTR szKey)
{
char szBuffer[512];
char szIniFileName[256];
char szLocale[256];
::GetCurrentDirectory(sizeof(szIniFileName), szIniFileName);
if(szIniFileName[lstrlen(szIniFileName)-1] != '\\')
strcat(szIniFileName, "\\");
strcat(szIniFileName, "metin2client.dat");
strcpy(szLocale, LocaleService_GetLocalePath());
if(strnicmp(szLocale, "locale/", strlen("locale/")) == 0)
strcpy(szLocale, LocaleService_GetLocalePath() + strlen("locale/"));
::GetPrivateProfileString(szLocale, szKey, NULL, szBuffer, sizeof(szBuffer)-1, szIniFileName);
if(szBuffer[0] == '\0')
LoadString(gs_kAppStrTable.m_hInstance, dwID, szBuffer, sizeof(szBuffer)-1);
if(szBuffer[0] == '\0')
::GetPrivateProfileString("en", szKey, NULL, szBuffer, sizeof(szBuffer)-1, szIniFileName);
if(szBuffer[0] == '\0')
strcpy(szBuffer, szKey);
std::string& rstLocale=gs_kAppStrTable.m_kMap_dwID_stLocale[dwID];
rstLocale=szBuffer;
return rstLocale;
}
const std::string& ApplicationStringTable_GetString(DWORD dwID)
{
char szBuffer[512];
LoadString(gs_kAppStrTable.m_hInstance, dwID, szBuffer, sizeof(szBuffer)-1);
std::string& rstLocale=gs_kAppStrTable.m_kMap_dwID_stLocale[dwID];
rstLocale=szBuffer;
return rstLocale;
}
const char* ApplicationStringTable_GetStringz(DWORD dwID, LPCSTR szKey)
{
return ApplicationStringTable_GetString(dwID, szKey).c_str();
}
const char* ApplicationStringTable_GetStringz(DWORD dwID)
{
return ApplicationStringTable_GetString(dwID).c_str();
}
////////////////////////////////////////////
int Setup(LPSTR lpCmdLine); // Internal function forward
bool PackInitialize(const char * c_pszFolder)
{
NANOBEGIN
if (_access(c_pszFolder, 0) != 0)
return false;
@@ -225,28 +160,7 @@ bool PackInitialize(const char * c_pszFolder)
"sound_m",
"sound2",
"bgm",
"locale_ca",
"locale_ae",
"locale_de",
"locale_es",
"locale_fr",
"locale_gr",
"locale_it",
"locale_nl",
"locale_pl",
"locale_pt",
"locale_tr",
"locale_uk",
"locale_bg",
"locale_en",
"locale_mx",
"locale_ro",
"locale_ru",
"locale_dk",
"locale_cz",
"locale_hu",
"locale_us",
"locale_pa",
"locale",
"uiscript",
"ETC",
"uiloading",
@@ -257,7 +171,6 @@ bool PackInitialize(const char * c_pszFolder)
CPackManager::instance().AddPack(std::format("{}/{}.pck", c_pszFolder, packFileName));
}
NANOEND
return true;
}
@@ -270,7 +183,7 @@ bool RunMainScript(CPythonLauncher& pyLauncher, const char* lpCmdLine)
initgrpImage();
initgrpText();
initwndMgr();
/////////////////////////////////////////////
initudp();
initapp();
initsystem();
@@ -298,113 +211,36 @@ bool RunMainScript(CPythonLauncher& pyLauncher, const char* lpCmdLine)
initsafebox();
initguild();
initServerStateChecker();
std::string stRegisterDebugFlag;
NANOBEGIN
#ifdef _DISTRIBUTE
stRegisterDebugFlag = "__DEBUG__ = 0";
#else
stRegisterDebugFlag = "__DEBUG__ = 1";
#endif
// RegisterDebugFlag
if (!pyLauncher.RunLine(stRegisterDebugFlag.c_str()))
{
std::string stRegisterDebugFlag;
#ifdef _DISTRIBUTE
stRegisterDebugFlag ="__DEBUG__ = 0";
#else
stRegisterDebugFlag ="__DEBUG__ = 1";
#endif
if (!pyLauncher.RunLine(stRegisterDebugFlag.c_str()))
{
TraceError("RegisterDebugFlag Error");
return false;
}
TraceError("RegisterDebugFlag Error");
return false;
}
// RegisterCommandLine
if (!pyLauncher.RunFile("system.py"))
{
std::string stRegisterCmdLine;
const char * loginMark = "-cs";
const char * loginMark_NonEncode = "-ncs";
const char * seperator = " ";
std::string stCmdLine;
const int CmdSize = 3;
std::vector<std::string> stVec;
SplitLine(lpCmdLine,seperator,&stVec);
if (CmdSize == stVec.size() && stVec[0]==loginMark)
{
char buf[MAX_PATH]; //TODO 아래 함수 string 형태로 수정
base64_decode(stVec[2].c_str(),buf);
stVec[2] = buf;
string_join(seperator,stVec,&stCmdLine);
}
else if (CmdSize <= stVec.size() && stVec[0]==loginMark_NonEncode)
{
stVec[0] = loginMark;
string_join(" ",stVec,&stCmdLine);
}
else
stCmdLine = lpCmdLine;
stRegisterCmdLine ="__COMMAND_LINE__ = ";
stRegisterCmdLine+='"';
stRegisterCmdLine+=stCmdLine;
stRegisterCmdLine+='"';
const CHAR* c_szRegisterCmdLine=stRegisterCmdLine.c_str();
if (!pyLauncher.RunLine(c_szRegisterCmdLine))
{
TraceError("RegisterCommandLine Error");
return false;
}
}
{
std::vector<std::string> stVec;
SplitLine(lpCmdLine," " ,&stVec);
if (stVec.size() != 0 && "--pause-before-create-window" == stVec[0])
{
system("pause");
}
if (!pyLauncher.RunFile("system.py"))
{
TraceError("RunMain Error");
return false;
}
TraceError("RunMain Error");
return false;
}
NANOEND
return true;
}
bool Main(HINSTANCE hInstance, LPSTR lpCmdLine)
static bool Main(HINSTANCE hInstance, LPSTR lpCmdLine)
{
#ifdef LOCALE_SERVICE_YMIR
extern bool g_isScreenShotKey;
g_isScreenShotKey = true;
#endif
DWORD dwRandSeed=time(NULL)+DWORD(GetCurrentProcess());
DWORD dwRandSeed = (DWORD)time(NULL) ^ GetCurrentProcessId() ^ GetTickCount();
srandom(dwRandSeed);
srand(random());
SetLogLevel(1);
#ifdef LOCALE_SERVICE_VIETNAM_MILD
extern BOOL USE_VIETNAM_CONVERT_WEAPON_VNUM;
USE_VIETNAM_CONVERT_WEAPON_VNUM = true;
#endif
if (_access("perf_game_update.txt", 0)==0)
{
DeleteFile("perf_game_update.txt");
}
if (_access("newpatch.exe", 0)==0)
{
system("patchupdater.exe");
return false;
}
if (!Setup(lpCmdLine))
return false;
@@ -415,8 +251,8 @@ bool Main(HINSTANCE hInstance, LPSTR lpCmdLine)
OpenLogFile(false); // false == uses syserr.txt only
#endif
static CLZO lzo;
CPackManager packMgr;
static CLZO lzo;
CPackManager packMgr;
if (!PackInitialize("pack"))
{
@@ -424,57 +260,22 @@ bool Main(HINSTANCE hInstance, LPSTR lpCmdLine)
return false;
}
if(LocaleService_LoadGlobal(hInstance))
SetDefaultCodePage(LocaleService_GetCodePage());
auto app = new CPythonApplication;
app->Initialize (hInstance);
CPythonLauncher pyLauncher;
CPythonApplication * app = new CPythonApplication;
app->Initialize(hInstance);
bool ret=false;
if (pyLauncher.Create())
{
CPythonLauncher pyLauncher;
CPythonExceptionSender pyExceptionSender;
SetExceptionSender(&pyExceptionSender);
if (pyLauncher.Create())
{
ret=RunMainScript(pyLauncher, lpCmdLine); //게임 실행중엔 함수가 끝나지 않는다.
}
//ProcessScanner_ReleaseQuitEvent();
//게임 종료시.
app->Clear();
timeEndPeriod(1);
pyLauncher.Clear();
RunMainScript (pyLauncher, lpCmdLine);
}
app->Clear();
timeEndPeriod (1);
pyLauncher.Clear();
app->Destroy();
delete app;
return ret;
}
HANDLE CreateMetin2GameMutex()
{
SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = FALSE;
return CreateMutex(&sa, FALSE, "Metin2GameMutex");
}
void DestroyMetin2GameMutex(HANDLE hMutex)
{
if (hMutex)
{
ReleaseMutex(hMutex);
hMutex = NULL;
}
return 0;
}
void __ErrorPythonLibraryIsNotExist()
@@ -482,100 +283,12 @@ void __ErrorPythonLibraryIsNotExist()
LogBoxf("FATAL ERROR!! Python Library file not exist!");
}
bool __IsTimeStampOption(LPSTR lpCmdLine)
{
const char* TIMESTAMP = "/timestamp";
return (strncmp(lpCmdLine, TIMESTAMP, strlen(TIMESTAMP))==0);
}
void __PrintTimeStamp()
{
#ifdef _DEBUG
if (__IS_TEST_SERVER_MODE__)
LogBoxf("METIN2 BINARY TEST DEBUG VERSION %s ( MS C++ %d Compiled )", __TIMESTAMP__, _MSC_VER);
else
LogBoxf("METIN2 BINARY DEBUG VERSION %s ( MS C++ %d Compiled )", __TIMESTAMP__, _MSC_VER);
#else
if (__IS_TEST_SERVER_MODE__)
LogBoxf("METIN2 BINARY TEST VERSION %s ( MS C++ %d Compiled )", __TIMESTAMP__, _MSC_VER);
else
LogBoxf("METIN2 BINARY DISTRIBUTE VERSION %s ( MS C++ %d Compiled )", __TIMESTAMP__, _MSC_VER);
#endif
}
bool __IsLocaleOption(LPSTR lpCmdLine)
{
return (strcmp(lpCmdLine, "--locale") == 0);
}
bool __IsLocaleVersion(LPSTR lpCmdLine)
{
return (strcmp(lpCmdLine, "--perforce-revision") == 0);
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
#ifdef _DEBUG
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_CRT_DF | _CRTDBG_LEAK_CHECK_DF );
//_CrtSetBreakAlloc( 110247 );
#endif
LoadConfig("config/locale.cfg");
ApplicationStringTable_Initialize(hInstance);
LocaleService_LoadConfig("config/locale.cfg");
SetDefaultCodePage(LocaleService_GetCodePage());
bool bQuit = false;
int nArgc = 0;
PCHAR* szArgv = CommandLineToArgv( lpCmdLine, &nArgc );
for( int i=0; i < nArgc; i++ ) {
if(szArgv[i] == 0)
continue;
if (__IsLocaleVersion(szArgv[i])) // #0000829: [M2EU] 버전 파일이 항상 생기지 않도록 수정
{
char szModuleName[MAX_PATH];
char szVersionPath[MAX_PATH];
GetModuleFileName(NULL, szModuleName, sizeof(szModuleName));
sprintf(szVersionPath, "%s.version", szModuleName);
FILE* fp = fopen(szVersionPath, "wt");
if (fp)
{
extern int METIN2_GET_VERSION();
fprintf(fp, "r%d\n", METIN2_GET_VERSION());
fclose(fp);
}
bQuit = true;
} else if (__IsLocaleOption(szArgv[i]))
{
FILE* fp=fopen("locale.txt", "wt");
fprintf(fp, "service[%s] code_page[%d]",
LocaleService_GetName(), LocaleService_GetCodePage());
fclose(fp);
bQuit = true;
} else if (__IsTimeStampOption(szArgv[i]))
{
__PrintTimeStamp();
bQuit = true;
} else if ((strcmp(szArgv[i], "--force-set-locale") == 0))
{
// locale 설정엔 인자가 두 개 더 필요함 (로케일 명칭, 데이터 경로)
if (nArgc <= i + 2)
{
MessageBox(NULL, "Invalid arguments", ApplicationStringTable_GetStringz(IDS_APP_NAME, "APP_NAME"), MB_ICONSTOP);
goto Clean;
}
const char* localeName = szArgv[++i];
const char* localePath = szArgv[++i];
LocaleService_ForceSetLocale(localeName, localePath);
}
}
if(bQuit)
goto Clean;
auto szArgv = CommandLineToArgv (lpCmdLine, &nArgc);
if (!CheckPythonLibraryFilenames())
{
@@ -583,36 +296,23 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi
goto Clean;
}
Main(hInstance, lpCmdLine);
Main (hInstance, lpCmdLine);
::CoUninitialize();
if(gs_szErrorString[0])
MessageBox(NULL, gs_szErrorString, ApplicationStringTable_GetStringz(IDS_APP_NAME, "APP_NAME"), MB_ICONSTOP);
Clean:
SAFE_FREE_GLOBAL(szArgv);
SAFE_FREE_GLOBAL (szArgv);
return 0;
}
static void GrannyError(granny_log_message_type Type,
granny_log_message_origin Origin,
char const* File,
granny_int32x Line,
char const* Message,
void* UserData)
static void GrannyError(granny_log_message_type Type, granny_log_message_origin Origin, char const* File, granny_int32x Line, char const* Message, void* UserData)
{
TraceError("GRANNY: %s", Message);
TraceError("GRANNY: %s", Message);
}
int Setup(LPSTR lpCmdLine)
{
/*
* 타이머 정밀도를 올린다.
*/
TIMECAPS tc;
UINT wTimerRes;
TIMECAPS tc;
UINT wTimerRes;
if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR)
return 0;
@@ -620,13 +320,9 @@ int Setup(LPSTR lpCmdLine)
wTimerRes = MINMAX(tc.wPeriodMin, 1, tc.wPeriodMax);
timeBeginPeriod(wTimerRes);
/*
* 그래니 에러 핸들링
*/
granny_log_callback Callback;
Callback.Function = nullptr;
Callback.UserData = 0;
GrannySetLogCallback(&Callback);
Callback.Function = nullptr;
Callback.UserData = 0;
GrannySetLogCallback(&Callback);
return 1;
}