port 4-a: text input through CPythonIME (login typed, in-game chat)

- UserInterface/PythonIME(+Module) ported verbatim; ime module stubs removed, initime() after initdbg()
- EterLib/IME.cpp in platform/: 40250 buffer/WMChar bodies, IMM/TSF processors no-op; committed
  multi-byte text via PythonBoot::UIChar emulating GCS_RESULTSTR
- GameApplicationAdapter RunIME* -> CWindowManager; Godot keys -> ui_ime_key/ui_char
- port.login_flow/login_live type the account through the IME; chat CG_CHAT round-trip on the fake server
- python_game_render_test logs in with Godot key events

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-24 08:35:53 +09:00
co-authored by Claude Opus 5.5
parent 1bda4390d6
commit 6ea0fa9889
21 changed files with 2117 additions and 277 deletions
+464 -179
View File
@@ -1,381 +1,666 @@
// Platform skeleton for EterLib/IME.h (40250 EterLib/IME.cpp), generated by platform_stub.py.
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
// Platform implementation of EterLib/IME.h (40250 EterLib/IME.cpp).
// The edit buffer (m_wText, cursor, SetText/GetText, OnChar/InsertString, IsMax, the colour-tag aware cursor
// moves) and WMChar are the 40250 bodies verbatim. What 40250 gets from Win32 IMM/TSF — composition strings,
// the candidate list, the reading window, keyboard-layout code pages — has no counterpart under Godot: the
// host delivers committed characters only (Metin2PythonHost.ui_char -> WMChar, or InsertString for text the
// input code page cannot hold, which is what 40250's ResultProcess does with a GCS_RESULTSTR). Those entry
// points keep 40250's "no IME present" behaviour: no composition, empty candidate/reading lists.
#include "EterLib/StdAfx.h"
#include "EterLib/IME.h"
#include "EterLib/TextTag.h"
#include "EterLib/GrpTextInstance.h"
#include "EterLib/Util.h"
#include "../PlatformStub.h"
#include <algorithm>
#include <cwctype>
namespace
{
// 40250 s_aszIndicator[INDICATOR_NON_IME] (IME.cpp:67); CheckInputLocale overwrites it with the locale's
// two-letter abbreviation, which here is the locale code page's language.
wchar_t s_aszNonImeIndicator[3] = L"En";
}
/*---------------------------------------------------------------------------*/ /* Public */
CIME::CIME()
{
MT_PLATFORM_STUB();
ms_hWnd = NULL;
ms_bCandidateList = false;
ms_bReadingInformation = false;
Clear();
m_max = 0;
m_userMax = 0;
m_bOnlyNumberMode = FALSE;
m_hOrgIMC = NULL;
m_bEnablePaste = false;
m_bUseDefaultIME = false;
}
CIME::~CIME()
{
MT_PLATFORM_STUB();
}
auto CIME::Initialize(HWND) -> bool
// PORT: 40250 loads imm32.dll, disables Cicero, reads the keyboard layout and sets up the TSF sinks. The
// Godot host has no IMM context; only the part the edit buffer depends on — the input/output code page from
// CheckInputLocale and SetSupportLevel(2) — is kept.
bool CIME::Initialize(HWND hWnd)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
}
if(ms_bInitialized)
return true;
ms_hWnd = hWnd;
auto CIME::Uninitialize() -> void
{
MT_PLATFORM_STUB();
}
ms_bDisableIMECompletely = true;
ms_bInitialized = true;
auto CIME::Clear() -> void
{
MT_PLATFORM_STUB();
}
CheckInputLocale();
ChangeInputLanguageWorker();
SetSupportLevel(2);
auto CIME::SetMax(int) -> void
{
MT_PLATFORM_STUB();
}
ms_bUILessMode = false;
CheckToggleState();
auto CIME::SetUserMax(int) -> void
{
MT_PLATFORM_STUB();
return true;
}
auto CIME::SetText(const char *, int) -> void
void CIME::Uninitialize()
{
MT_PLATFORM_STUB();
if ( !ms_bInitialized )
return;
ms_hWnd = NULL;
m_hOrgIMC = NULL;
ms_bInitialized = false;
}
auto CIME::GetText(std::string &, bool) -> int
void CIME::UseDefaultIME()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
m_bUseDefaultIME = true;
}
auto CIME::GetCodePageText() -> const char *
bool CIME::IsIMEEnabled()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<const char *>();
return ms_bImeEnabled;
}
auto CIME::GetCodePage() -> int
// PORT: 40250 ImmAssociateContext()s the window; ms_bDisableIMECompletely (no imm32) forces bEnable=false,
// which is always the case here.
void CIME::EnableIME(bool bEnable)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
if (!ms_bInitialized || !ms_hWnd)
return;
if (ms_bDisableIMECompletely)
bEnable = false;
ms_bImeEnabled = bEnable;
if (bEnable)
CheckToggleState();
}
auto CIME::GetCandidateCount() -> int
void CIME::DisableIME()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
EnableIME(false);
}
auto CIME::GetCandidatePageCount() -> int
void CIME::EnableCaptureInput()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
ms_bCaptureInput = true;
}
auto CIME::GetCandidate(DWORD, std::string &) -> int
void CIME::DisableCaptureInput()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
ms_bCaptureInput = false;
}
auto CIME::GetCandidateSelection() -> int
bool CIME::IsCaptureEnabled()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
return ms_bCaptureInput;
}
auto CIME::GetReading(std::string &) -> int
void CIME::Clear()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
}
ms_lastpos = 0;
ms_curpos = 0;
auto CIME::GetReadingError() -> int
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
ms_compLen = 0;
ms_ulbegin = 0;
ms_ulend = 0;
}
auto CIME::SetInputMode(DWORD) -> void
int CIME::GetReading(std::string & rstrText)
{
MT_PLATFORM_STUB();
}
char reading[IMEREADING_MAXLEN];
auto CIME::GetInputMode() -> DWORD
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<DWORD>();
if(ms_wstrReading.size() == 0)
return 0;
int readingLen = WideCharToMultiByte(ms_uOutputCodePage, 0, &ms_wstrReading[0], ms_wstrReading.size(), reading, sizeof(reading), NULL, NULL);
rstrText.append(GetCodePageText());
rstrText.append(reading, reading + readingLen);
return rstrText.size();
}
auto CIME::IsIMEEnabled() -> bool
int CIME::GetReadingError()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
return ms_iReadingError;
}
auto CIME::EnableIME(bool) -> void
void CIME::SetMax(int iMax)
{
MT_PLATFORM_STUB();
m_max = iMax;
}
auto CIME::DisableIME() -> void
void CIME::SetUserMax(int iMax)
{
MT_PLATFORM_STUB();
m_userMax = iMax;
}
auto CIME::EnableCaptureInput() -> void
void CIME::SetText(const char* szText, int len)
{
MT_PLATFORM_STUB();
ms_compLen = 0;
ms_ulbegin = 0;
ms_ulend = 0;
const char* begin = szText;
const char* end = begin + len;
const char* iter = FindToken(begin, end);
int m_wTextLen = sizeof(m_wText)/sizeof(wchar_t);
ms_lastpos = MultiByteToWideChar(ms_uInputCodePage, 0, begin, iter-begin, m_wText, m_wTextLen);
if (iter < end)
ms_lastpos += MultiByteToWideChar(ReadToken(iter), 0, (iter+5), end-(iter+5), m_wText+ms_lastpos, m_wTextLen-ms_lastpos);
ms_curpos = std::min(ms_curpos, ms_lastpos);
}
auto CIME::DisableCaptureInput() -> void
int CIME::GetText(std::string & rstrText, bool addCodePage)
{
MT_PLATFORM_STUB();
int outCodePage = ms_uOutputCodePage;
int dataCodePage;
switch (outCodePage)
{
//case 1256: // ARABIC
case 1268: // VIETNAM
dataCodePage = CP_UTF8;
break;
default:
dataCodePage = outCodePage;
}
int len = 0;
char text[IMESTR_MAXLEN];
len += WideCharToMultiByte(dataCodePage, 0, m_wText, ms_curpos, text, sizeof(text)-len, NULL, NULL);
len += WideCharToMultiByte(dataCodePage, 0, m_wszComposition, ms_compLen, text+len, sizeof(text)-len, NULL, NULL);
len += WideCharToMultiByte(dataCodePage, 0, m_wText+ms_curpos, ms_lastpos-ms_curpos, text+len, sizeof(text)-len, NULL, NULL);
int i;
for(i=0; i<len; ++i)
if((BYTE)text[i] > 0x7F) break;
if(i == len)
{
rstrText.append(text, text+len);
}
else
{
rstrText.append(text, text+i);
//if (addCodePage)
// rstrText.append(GetCodePageText());
rstrText.append(text+i, text+len);
}
return rstrText.size();
}
auto CIME::IsCaptureEnabled() -> bool
const char* CIME::GetCodePageText()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
static char szCodePage[16];
const int defCodePage = GetDefaultCodePage();
const int outCodePage = ms_uOutputCodePage;
if (outCodePage != defCodePage)
{
sprintf(szCodePage, "@%04d", outCodePage);
}
else
{
szCodePage[0] = 0;
}
return szCodePage;
}
auto CIME::SetNumberMode() -> void
int CIME::GetCodePage()
{
MT_PLATFORM_STUB();
return ms_uOutputCodePage;
}
auto CIME::SetStringMode() -> void
int CIME::GetCandidatePageCount()
{
MT_PLATFORM_STUB();
return ms_dwCandidatePageSize;
}
auto CIME::__IsWritable(wchar_t) -> bool
int CIME::GetCandidateCount()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
return ms_dwCandidateCount;
}
auto CIME::AddExceptKey(wchar_t) -> void
int CIME::GetCandidate(DWORD index, std::string & rstrText)
{
MT_PLATFORM_STUB();
if(index >= MAX_CANDLIST)
return 0;
LPCWSTR wszText = ms_wszCandidate[index];
if(wszText == NULL)
return 0;
int wTextLen = wcslen(wszText);
if(wTextLen == 0)
return 0;
char text[IMESTR_MAXLEN];
int len = ::WideCharToMultiByte(CP_UTF8, 0, wszText, wTextLen, text, sizeof(text), 0, 0);
rstrText.append("@9999");
rstrText.append(text, text+len);
return wTextLen;
}
auto CIME::ClearExceptKey() -> void
int CIME::GetCandidateSelection()
{
MT_PLATFORM_STUB();
return ms_dwCandidateSelection;
}
auto CIME::PasteTextFromClipBoard() -> void
// PORT: 40250 ImmSetConversionStatus / ImmGetConversionStatus. Without an IMM context the conversion mode is
// the alphanumeric one (IME_CMODE_ALPHANUMERIC == 0) and cannot be changed.
void CIME::SetInputMode(DWORD)
{
MT_PLATFORM_STUB();
}
auto CIME::EnablePaste(bool) -> void
DWORD CIME::GetInputMode()
{
MT_PLATFORM_STUB();
return 0;
}
auto CIME::PasteString(const char *) -> void
void CIME::SetNumberMode()
{
MT_PLATFORM_STUB();
m_bOnlyNumberMode = TRUE;
}
auto CIME::FinalizeString(bool) -> void
void CIME::SetStringMode()
{
MT_PLATFORM_STUB();
m_bOnlyNumberMode = FALSE;
}
auto CIME::UseDefaultIME() -> void
void CIME::AddExceptKey(wchar_t key)
{
MT_PLATFORM_STUB();
m_exceptKey.push_back(key);
}
auto CIME::GetCurPos() -> int
void CIME::ClearExceptKey()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
m_exceptKey.clear();
}
auto CIME::GetCompLen() -> int
bool CIME::__IsWritable(wchar_t key)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
if ( m_exceptKey.end() == std::find(m_exceptKey.begin(),m_exceptKey.end(),key) )
return true;
else
return false;
}
auto CIME::GetULBegin() -> int
void CIME::EnablePaste(bool bFlag)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
m_bEnablePaste = bFlag;
}
auto CIME::GetULEnd() -> int
// PORT: 40250 reads CF_TEXT from the Win32 clipboard. The host clipboard is not wired to the extension yet,
// so this pastes nothing (the same as an empty clipboard).
void CIME::PasteTextFromClipBoard()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
if (!m_bEnablePaste)
return;
}
auto CIME::CloseCandidateList() -> void
// PORT: 40250 cancels the IMM composition string and closes the candidate list. There is no composition
// here; the candidate-list close (and its event) is kept.
void CIME::FinalizeString(bool)
{
MT_PLATFORM_STUB();
static bool s_bProcessing = false; // to avoid infinite recursion
if ( !ms_bInitialized || s_bProcessing )
return;
s_bProcessing = true;
CloseCandidateList();
s_bProcessing = false;
}
auto CIME::CloseReadingInformation() -> void
int CIME::GetCompLen()
{
MT_PLATFORM_STUB();
return ms_compLen;
}
auto CIME::ChangeInputLanguage() -> void
int CIME::GetULBegin()
{
MT_PLATFORM_STUB();
return ms_ulbegin;
}
auto CIME::ChangeInputLanguageWorker() -> void
int CIME::GetULEnd()
{
MT_PLATFORM_STUB();
return ms_ulend;
}
auto CIME::WMInputLanguage(HWND, UINT, WPARAM, LPARAM) -> LRESULT
void CIME::CloseCandidateList()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<LRESULT>();
ms_bCandidateList = false;
ms_dwCandidateCount = 0;
memset(&ms_wszCandidate, 0, sizeof(ms_wszCandidate));
if(ms_pEvent)
ms_pEvent->OnCloseCandidateList();
}
auto CIME::WMStartComposition(HWND, UINT, WPARAM, LPARAM) -> LRESULT
void CIME::CloseReadingInformation()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<LRESULT>();
CIME::ms_bReadingInformation = false;
if(CIME::ms_pEvent)
CIME::ms_pEvent->OnCloseReadingWnd();
}
auto CIME::WMComposition(HWND, UINT, WPARAM, LPARAM) -> LRESULT
// PORT: 40250 re-reads the keyboard layout (GETLANG) and re-applies the IME support level when the language
// changed. The host has one fixed input locale, so only the toggle check and the code-page event remain.
void CIME::ChangeInputLanguage()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<LRESULT>();
CheckToggleState();
ChangeInputLanguageWorker();
if(ms_pEvent)
ms_pEvent->OnChangeCodePage();
}
auto CIME::WMEndComposition(HWND, UINT, WPARAM, LPARAM) -> LRESULT
void CIME::ChangeInputLanguageWorker()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<LRESULT>();
if ( !ms_bUILessMode )
ms_iCandListIndexBase = 1;
SetupImeApi();
}
auto CIME::WMNotify(HWND, UINT, WPARAM, LPARAM) -> LRESULT
// PORT: 40250 forces level 3 for the Korean keyboard layout; there is no keyboard layout here.
void CIME::SetSupportLevel( DWORD dwImeLevel )
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<LRESULT>();
if ( dwImeLevel < 2 || 3 < dwImeLevel )
return;
ms_dwIMELevel = dwImeLevel;
// cancel current composition string.
FinalizeString();
}
auto CIME::WMChar(HWND, UINT, WPARAM, LPARAM) -> LRESULT
LRESULT CIME::WMInputLanguage(HWND, UINT, WPARAM, LPARAM)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<LRESULT>();
ChangeInputLanguage();
return 0;
}
auto CIME::IncCurPos() -> void
LRESULT CIME::WMStartComposition(HWND, UINT, WPARAM, LPARAM)
{
MT_PLATFORM_STUB();
return 1L;
}
auto CIME::DecCurPos() -> void
// PORT: 40250 pulls GCS_RESULTSTR/GCS_COMPSTR out of the IMM context. The host never sends compositions.
LRESULT CIME::WMComposition(HWND, UINT, WPARAM, LPARAM)
{
MT_PLATFORM_STUB();
return 0;
}
auto CIME::SetCurPos(int) -> void
LRESULT CIME::WMEndComposition(HWND, UINT, WPARAM, LPARAM)
{
MT_PLATFORM_STUB();
ms_compLen = 0;
ms_ulbegin = 0;
ms_ulend = 0;
if(ms_pEvent)
ms_pEvent->OnUpdate();
return 0L;
}
auto CIME::DelCurPos() -> void
// PORT: 40250 handles IMN_* candidate/reading/toggle notifications from IMM; none are sent here.
LRESULT CIME::WMNotify(HWND, UINT, WPARAM, LPARAM)
{
MT_PLATFORM_STUB();
return 0;
}
auto CIME::CheckInputLocale() -> void
LRESULT CIME::WMChar(HWND /*hWnd*/, UINT /*uiMsg*/, WPARAM wParam, LPARAM lParam)
{
MT_PLATFORM_STUB();
unsigned char c = (unsigned char)(wParam & 0xff);
switch (c)
{
case 8:
if(ms_bCaptureInput == false)
return 0;
if (ms_curpos > 0)
{
DecCurPos();
DelCurPos();
}
if(ms_pEvent)
ms_pEvent->OnUpdate();
return 0;
break;
default:
if(ms_pEvent) {
if (ms_pEvent->OnWM_CHAR(wParam, lParam))
break;
}
if(ms_bCaptureInput == false)
return 0;
wchar_t w[10];
MultiByteToWideChar(ms_uInputCodePage, 0, (char*)&c, 1, w, 1);
OnChar(w[0]);
if (w[0] == L'|')
OnChar(w[0]);
if(ms_pEvent)
ms_pEvent->OnUpdate();
break;
}
return 0;
}
auto CIME::CheckToggleState() -> void
/*---------------------------------------------------------------------------*/ /* Protected */
void CIME::IncCurPos()
{
MT_PLATFORM_STUB();
if (ms_curpos < ms_lastpos)
{
int pos = FindColorTagEndPosition(m_wText + ms_curpos, ms_lastpos - ms_curpos);
if (pos > 0)
ms_curpos = std::min(ms_lastpos, std::max(0, ms_curpos + (pos + 1)));
else
++ms_curpos;
//++ms_curpos;
}
}
auto CIME::SetSupportLevel(DWORD) -> void
void CIME::DecCurPos()
{
MT_PLATFORM_STUB();
if (ms_curpos > 0)
{
int pos = FindColorTagStartPosition(m_wText + ms_curpos - 1, ms_curpos);
if (pos > 0)
ms_curpos = std::min(ms_lastpos, std::max(0, ms_curpos - (pos + 1)));
else
--ms_curpos;
//--ms_curpos;
}
}
auto CIME::InsertString(wchar_t *, int) -> void
int CIME::GetCurPos()
{
MT_PLATFORM_STUB();
int pos = GetTextTagOutputLen(m_wText, ms_curpos);
return pos;
//return ms_curpos;
}
auto CIME::OnChar(wchar_t) -> void
void CIME::SetCurPos(int offset)
{
MT_PLATFORM_STUB();
if (offset < 0 || offset > ms_lastpos)
{
ms_curpos = ms_lastpos;
return;
}
else
{
// offset is a position in the rendered text, so it has to be mapped back to the buffer.
//ms_curpos = min(ms_lastpos, offset);
ms_curpos = std::min(ms_lastpos, GetTextTagInternalPosFromRenderPos(m_wText, ms_lastpos, offset));
}
}
auto CIME::GetCodePageFromLang(LANGID) -> UINT
void CIME::DelCurPos()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<UINT>();
if (ms_curpos < ms_lastpos)
{
int eraseCount = FindColorTagEndPosition(m_wText + ms_curpos, ms_lastpos - ms_curpos) + 1;
// PORT: 40250 wcscpy()s the overlapping tail down; that is undefined for overlapping buffers, and the
// buffer is not guaranteed NUL-terminated at ms_lastpos, so move exactly the live tail.
memmove(m_wText + ms_curpos, m_wText + ms_curpos + eraseCount, sizeof(wchar_t) * (ms_lastpos - ms_curpos - eraseCount));
ms_lastpos -= eraseCount;
ms_curpos = std::min(ms_lastpos, ms_curpos);
}
}
auto CIME::ResultProcess(HIMC) -> void
void CIME::PasteString(const char * str)
{
MT_PLATFORM_STUB();
const char * begin = str;
const char * end = str + strlen(str);
wchar_t m_wText[IMESTR_MAXLEN];
int wstrLen = MultiByteToWideChar(ms_uInputCodePage, 0, begin, end - begin, m_wText, IMESTR_MAXLEN);
InsertString(m_wText, wstrLen);
if(ms_pEvent)
ms_pEvent->OnUpdate();
}
auto CIME::CompositionProcessBuilding(HIMC) -> void
// PORT: 40250 reads the keyboard layout's default ANSI code page (GetLocaleInfoA LOCALE_IDEFAULTANSICODEPAGE).
// The host's input locale is the client locale, so its code page (SetDefaultCodePage from locale.cfg) is used.
void CIME::CheckInputLocale()
{
MT_PLATFORM_STUB();
static UINT s_uPrevCodePage = 0xFFFF;
ms_uInputCodePage = GetDefaultCodePage();
if ( s_uPrevCodePage == ms_uInputCodePage )
return;
s_uPrevCodePage = ms_uInputCodePage;
ms_bVerticalCandidate = false;
ms_wszCurrentIndicator = s_aszNonImeIndicator;
if(ms_uOutputCodePage != 1256) {
ms_uOutputCodePage = ms_uInputCodePage;
Clear();
}
}
auto CIME::CompositionProcess(HIMC) -> void
// PORT: 40250 asks IMM whether an IME is open; there is none, so the state is IMEUI_STATE_OFF (0).
void CIME::CheckToggleState()
{
MT_PLATFORM_STUB();
CheckInputLocale();
ms_bChineseIME = false;
ms_dwImeState = 0;
}
auto CIME::AttributeProcess(HIMC) -> void
/*---------------------------------------------------------------------------*/ /* Private */
void CIME::InsertString(wchar_t* wString, int iSize)
{
MT_PLATFORM_STUB();
if (IsMax(wString, iSize))
return;
if (ms_curpos < ms_lastpos)
memmove(m_wText+ms_curpos+iSize, m_wText+ms_curpos, sizeof(wchar_t)*(ms_lastpos-ms_curpos));
memcpy(m_wText+ms_curpos, wString, sizeof(wchar_t)*iSize);
ms_curpos += iSize;
ms_lastpos += iSize;
}
auto CIME::CandidateProcess(HIMC) -> void
void CIME::OnChar(wchar_t c)
{
MT_PLATFORM_STUB();
if (m_bOnlyNumberMode)
if (!iswdigit(c))
return;
if (!__IsWritable(c))
return;
InsertString(&c, 1);
}
auto CIME::ReadingProcess(HIMC) -> void
// PORT: 40250 maps a keyboard-layout LANGID to its ANSI code page. Only CheckInputLocale's code page is used
// here (see there), so this answers with it.
UINT CIME::GetCodePageFromLang(LANGID)
{
MT_PLATFORM_STUB();
return GetDefaultCodePage();
}
auto CIME::IsMax(const wchar_t *, int) -> bool
// PORT: the IMM composition/candidate/reading processors (40250 IME.cpp:951-1227). No IMM context exists.
void CIME::ResultProcess(HIMC) {}
void CIME::CompositionProcessBuilding(HIMC) {}
void CIME::CompositionProcess(HIMC) {}
void CIME::AttributeProcess(HIMC) {}
void CIME::CandidateProcess(HIMC) {}
void CIME::ReadingProcess(HIMC) {}
bool CIME::IsMax(const wchar_t* wInput, int len)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
if (ms_lastpos + len > IMESTR_MAXLEN)
return true;
int textLen = WideCharToMultiByte(ms_uOutputCodePage, 0, m_wText, ms_lastpos, 0, 0, NULL, NULL);
int inputLen = WideCharToMultiByte(ms_uOutputCodePage, 0, wInput, len, 0, 0, NULL, NULL);
//return textLen + inputLen > m_max;
if (textLen + inputLen > m_max)
return true;
else if (m_userMax != 0 && m_max != m_userMax)
{
std::wstring str = GetTextTagOutputString(m_wText, ms_lastpos);
std::wstring input = GetTextTagOutputString(wInput, len);
int textLen = WideCharToMultiByte(ms_uOutputCodePage, 0, str.c_str(), str.length(), 0, 0, NULL, NULL);
int inputLen = WideCharToMultiByte(ms_uOutputCodePage, 0, input.c_str(), input.length(), 0, 0, NULL, NULL);
return textLen + inputLen > m_userMax;
}
return false;
}
auto CIME::GetImeId(UINT) -> DWORD
// PORT: 40250 identifies the CHT/CHS IME DLL version and the reading-window orientation. No IME DLL here.
DWORD CIME::GetImeId(UINT)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<DWORD>();
return 0;
}
auto CIME::GetReadingWindowOrientation() -> bool
bool CIME::GetReadingWindowOrientation()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
return true;
}
auto CIME::SetupImeApi() -> void
void CIME::SetupImeApi()
{
MT_PLATFORM_STUB();
_GetReadingString = NULL;
_ShowReadingWindow = NULL;
}
decltype(CIME::_ImmLockIMC) CIME::_ImmLockIMC{};
@@ -63,11 +63,6 @@ void AddStubConstants_guild(PyObject* poModule)
}
// 40250 UserInterface/PythonIMEModule.cpp
void AddStubConstants_ime(PyObject* poModule)
{
(void) poModule;
}
// 40250 UserInterface/PythonMessenger.cpp
void AddStubConstants_messenger(PyObject* poModule)
{
@@ -8,7 +8,6 @@
void AddStubConstants_event(PyObject* poModule);
void AddStubConstants_eventMgr(PyObject* poModule);
void AddStubConstants_guild(PyObject* poModule);
void AddStubConstants_ime(PyObject* poModule);
void AddStubConstants_messenger(PyObject* poModule);
void AddStubConstants_profiler(PyObject* poModule);
void AddStubConstants_quest(PyObject* poModule);
@@ -154,76 +153,6 @@ PyMethodDef methods_guild[] = {
{ "Destroy", stub_guild_Destroy, METH_VARARGS },
{ NULL, NULL }
};
// 40250 UserInterface/PythonIMEModule.cpp: ime export names.
PyObject* stub_ime_Enable(PyObject*, PyObject*) { return stub_call_named("ime.Enable", Py_BuildNone()); }
PyObject* stub_ime_Disable(PyObject*, PyObject*) { return stub_call_named("ime.Disable", Py_BuildNone()); }
PyObject* stub_ime_EnableCaptureInput(PyObject*, PyObject*) { return stub_call_named("ime.EnableCaptureInput", Py_BuildNone()); }
PyObject* stub_ime_DisableCaptureInput(PyObject*, PyObject*) { return stub_call_named("ime.DisableCaptureInput", Py_BuildNone()); }
PyObject* stub_ime_SetMax(PyObject*, PyObject*) { return stub_call_named("ime.SetMax", Py_BuildNone()); }
PyObject* stub_ime_SetUserMax(PyObject*, PyObject*) { return stub_call_named("ime.SetUserMax", Py_BuildNone()); }
PyObject* stub_ime_SetText(PyObject*, PyObject*) { return stub_call_named("ime.SetText", Py_BuildNone()); }
PyObject* stub_ime_GetText(PyObject*, PyObject*) { return stub_call_named("ime.GetText", Py_BuildValue("s", "")); }
PyObject* stub_ime_GetCodePage(PyObject*, PyObject*) { return stub_call_named("ime.GetCodePage", Py_BuildValue("i", 0)); }
PyObject* stub_ime_GetCandidateCount(PyObject*, PyObject*) { return stub_call_named("ime.GetCandidateCount", Py_BuildValue("i", 0)); }
PyObject* stub_ime_GetCandidate(PyObject*, PyObject*) { return stub_call_named("ime.GetCandidate", Py_BuildValue("si", "", 0)); }
PyObject* stub_ime_GetCandidateSelection(PyObject*, PyObject*) { return stub_call_named("ime.GetCandidateSelection", Py_BuildValue("i", 0)); }
PyObject* stub_ime_GetReading(PyObject*, PyObject*) { return stub_call_named("ime.GetReading", Py_BuildValue("s", "")); }
PyObject* stub_ime_GetReadingError(PyObject*, PyObject*) { return stub_call_named("ime.GetReadingError", Py_BuildValue("i", 0)); }
PyObject* stub_ime_EnableIME(PyObject*, PyObject*) { return stub_call_named("ime.EnableIME", Py_BuildNone()); }
PyObject* stub_ime_DisableIME(PyObject*, PyObject*) { return stub_call_named("ime.DisableIME", Py_BuildNone()); }
PyObject* stub_ime_GetInputMode(PyObject*, PyObject*) { return stub_call_named("ime.GetInputMode", Py_BuildValue("i", 0)); }
PyObject* stub_ime_SetInputMode(PyObject*, PyObject*) { return stub_call_named("ime.SetInputMode", Py_BuildNone()); }
PyObject* stub_ime_SetNumberMode(PyObject*, PyObject*) { return stub_call_named("ime.SetNumberMode", Py_BuildNone()); }
PyObject* stub_ime_SetStringMode(PyObject*, PyObject*) { return stub_call_named("ime.SetStringMode", Py_BuildNone()); }
PyObject* stub_ime_AddExceptKey(PyObject*, PyObject*) { return stub_call_named("ime.AddExceptKey", Py_BuildNone()); }
PyObject* stub_ime_ClearExceptKey(PyObject*, PyObject*) { return stub_call_named("ime.ClearExceptKey", Py_BuildNone()); }
PyObject* stub_ime_MoveLeft(PyObject*, PyObject*) { return stub_call_named("ime.MoveLeft", Py_BuildNone()); }
PyObject* stub_ime_MoveRight(PyObject*, PyObject*) { return stub_call_named("ime.MoveRight", Py_BuildNone()); }
PyObject* stub_ime_MoveHome(PyObject*, PyObject*) { return stub_call_named("ime.MoveHome", Py_BuildNone()); }
PyObject* stub_ime_MoveEnd(PyObject*, PyObject*) { return stub_call_named("ime.MoveEnd", Py_BuildNone()); }
PyObject* stub_ime_SetCursorPosition(PyObject*, PyObject*) { return stub_call_named("ime.SetCursorPosition", Py_BuildNone()); }
PyObject* stub_ime_Delete(PyObject*, PyObject*) { return stub_call_named("ime.Delete", Py_BuildNone()); }
PyObject* stub_ime_PasteString(PyObject*, PyObject*) { return stub_call_named("ime.PasteString", Py_BuildNone()); }
PyObject* stub_ime_PasteBackspace(PyObject*, PyObject*) { return stub_call_named("ime.PasteBackspace", Py_BuildNone()); }
PyObject* stub_ime_PasteReturn(PyObject*, PyObject*) { return stub_call_named("ime.PasteReturn", Py_BuildNone()); }
PyObject* stub_ime_PasteTextFromClipBoard(PyObject*, PyObject*) { return stub_call_named("ime.PasteTextFromClipBoard", Py_BuildNone()); }
PyObject* stub_ime_EnablePaste(PyObject*, PyObject*) { return stub_call_named("ime.EnablePaste", Py_BuildNone()); }
PyMethodDef methods_ime[] = {
{ "Enable", stub_ime_Enable, METH_VARARGS },
{ "Disable", stub_ime_Disable, METH_VARARGS },
{ "EnableCaptureInput", stub_ime_EnableCaptureInput, METH_VARARGS },
{ "DisableCaptureInput", stub_ime_DisableCaptureInput, METH_VARARGS },
{ "SetMax", stub_ime_SetMax, METH_VARARGS },
{ "SetUserMax", stub_ime_SetUserMax, METH_VARARGS },
{ "SetText", stub_ime_SetText, METH_VARARGS },
{ "GetText", stub_ime_GetText, METH_VARARGS },
{ "GetCodePage", stub_ime_GetCodePage, METH_VARARGS },
{ "GetCandidateCount", stub_ime_GetCandidateCount, METH_VARARGS },
{ "GetCandidate", stub_ime_GetCandidate, METH_VARARGS },
{ "GetCandidateSelection", stub_ime_GetCandidateSelection, METH_VARARGS },
{ "GetReading", stub_ime_GetReading, METH_VARARGS },
{ "GetReadingError", stub_ime_GetReadingError, METH_VARARGS },
{ "EnableIME", stub_ime_EnableIME, METH_VARARGS },
{ "DisableIME", stub_ime_DisableIME, METH_VARARGS },
{ "GetInputMode", stub_ime_GetInputMode, METH_VARARGS },
{ "SetInputMode", stub_ime_SetInputMode, METH_VARARGS },
{ "SetNumberMode", stub_ime_SetNumberMode, METH_VARARGS },
{ "SetStringMode", stub_ime_SetStringMode, METH_VARARGS },
{ "AddExceptKey", stub_ime_AddExceptKey, METH_VARARGS },
{ "ClearExceptKey", stub_ime_ClearExceptKey, METH_VARARGS },
{ "MoveLeft", stub_ime_MoveLeft, METH_VARARGS },
{ "MoveRight", stub_ime_MoveRight, METH_VARARGS },
{ "MoveHome", stub_ime_MoveHome, METH_VARARGS },
{ "MoveEnd", stub_ime_MoveEnd, METH_VARARGS },
{ "SetCursorPosition", stub_ime_SetCursorPosition, METH_VARARGS },
{ "Delete", stub_ime_Delete, METH_VARARGS },
{ "PasteString", stub_ime_PasteString, METH_VARARGS },
{ "PasteBackspace", stub_ime_PasteBackspace, METH_VARARGS },
{ "PasteReturn", stub_ime_PasteReturn, METH_VARARGS },
{ "PasteTextFromClipBoard", stub_ime_PasteTextFromClipBoard, METH_VARARGS },
{ "EnablePaste", stub_ime_EnablePaste, METH_VARARGS },
{ NULL, NULL }
};
// 40250 UserInterface/PythonMessenger.cpp: messenger export names.
PyObject* stub_messenger_RemoveFriend(PyObject*, PyObject*) { return stub_call_named("messenger.RemoveFriend", Py_BuildNone()); }
PyObject* stub_messenger_IsFriendByName(PyObject*, PyObject*) { return stub_call_named("messenger.IsFriendByName", Py_BuildValue("i", 0)); }
@@ -471,11 +400,6 @@ void init_2v0_gameplay_stubs() {
PyModule_AddIntConstant(module, "__port_stub__", 1);
AddStubConstants_guild(module);
}
{
PyObject* module = Py_InitModule("ime", methods_ime);
PyModule_AddIntConstant(module, "__port_stub__", 1);
AddStubConstants_ime(module);
}
{
PyObject* module = Py_InitModule("messenger", methods_messenger);
PyModule_AddIntConstant(module, "__port_stub__", 1);
@@ -88,14 +88,15 @@ public:
}
void BlendEventCamera(const SCameraSetting&, float) override {}
void SetDefaultCamera() override { CPythonApplication::Instance().SetDefaultCamera(); }
void RunIMEUpdate() override {}
void RunIMETabEvent() override {}
void RunIMEReturnEvent() override {}
void RunIMEChangeCodePage() override {}
void RunIMEOpenCandidateListEvent() override {}
void RunIMECloseCandidateListEvent() override {}
void RunIMEOpenReadingWndEvent() override {}
void RunIMECloseReadingWndEvent() override {}
// 40250 CPythonApplication::RunIME* (PythonApplicationEvent.cpp:148-195): CPythonIME's event sink.
void RunIMEUpdate() override { UI::CWindowManager::Instance().RunIMEUpdate(); }
void RunIMETabEvent() override { UI::CWindowManager::Instance().RunIMETabEvent(); }
void RunIMEReturnEvent() override { UI::CWindowManager::Instance().RunIMEReturnEvent(); }
void RunIMEChangeCodePage() override { UI::CWindowManager::Instance().RunChangeCodePage(); }
void RunIMEOpenCandidateListEvent() override { UI::CWindowManager::Instance().RunOpenCandidate(); }
void RunIMECloseCandidateListEvent() override { UI::CWindowManager::Instance().RunCloseCandidate(); }
void RunIMEOpenReadingWndEvent() override { UI::CWindowManager::Instance().RunOpenReading(); }
void RunIMECloseReadingWndEvent() override { UI::CWindowManager::Instance().RunCloseReading(); }
};
std::unique_ptr<GameApplicationAdapter> g_game_application;
// The CPythonApplication members the game phase reaches through Instance() (批次 2V2), in 40250's member
@@ -109,6 +110,7 @@ struct GameSingletons
CItemManager kItemMgr;
CPythonCharacterManager kChrMgr;
CPythonGraphic pyGraphic; // owns CCullingManager (m_CullingManager)
CPythonIME pyIme;
CPythonItem pyItem;
CPythonShop pyShop;
CPythonExchange pyExchange;
@@ -266,6 +268,47 @@ void UIKey(int key, bool pressed)
g_window_manager->RunKeyUp(key);
}
// 40250 CPythonApplication::WindowProcedure (PythonApplicationProcedure.cpp:111-117): WM_CHAR goes to
// CPythonIME::WMChar and WM_KEYDOWN to OnIMEKeyDown (the VK code, next to DirectInput's DIK stream). The host
// delivers characters as Unicode code points; the 40250 window is an ANSI window, so a character that the input
// code page holds as one byte arrives as that byte, like WM_CHAR. PORT: anything else is what a Windows IME
// commits through WM_IME_COMPOSITION/GCS_RESULTSTR, which CIME::ResultProcess inserts as a wide string.
void UIChar(unsigned codepoint)
{
if (!g_game_singletons || !codepoint) return;
CPythonIME& ime = CPythonIME::Instance();
if (codepoint < 0x80)
{
ime.WMChar(NULL, WM_CHAR, codepoint, 0);
return;
}
const wchar_t wide = wchar_t(codepoint);
char bytes[8];
BOOL used_default = FALSE;
const int n = WideCharToMultiByte(CIME::ms_uInputCodePage, 0, &wide, 1, bytes, sizeof(bytes), "?", &used_default);
if (n == 1 && !used_default)
{
ime.WMChar(NULL, WM_CHAR, (unsigned char)bytes[0], 0);
return;
}
// 40250 CIME::WMComposition with GCS_RESULTSTR: ResultProcess inserts the committed string, then OnUpdate.
if (!CIME::ms_bCaptureInput)
return;
struct ResultAccess : CPythonIME
{
static void Insert(CPythonIME& ime, wchar_t* text, int len) { (ime.*(&ResultAccess::InsertString))(text, len); }
};
wchar_t text[1] = { wide };
ResultAccess::Insert(ime, text, 1);
if (CIME::ms_pEvent)
CIME::ms_pEvent->OnUpdate();
}
void UIIMEKeyDown(int vkey)
{
if (g_window_manager) g_window_manager->RunIMEKeyDown(vkey);
}
void UIUpdate()
{
// While system.py is inside app.Loop(), one host frame is one CPythonApplication::Process(),
@@ -341,6 +384,7 @@ bool run_main_script_body(const char* lpCmdLine, std::string* error)
// The module initializers follow 40250's order; missing modules arrive in later 2V steps.
initpack();
initdbg();
initime();
initgrp();
initgrpImage();
initgrpText();
@@ -55,6 +55,9 @@ void SetUISize(int width, int height);
void UIMouseMove(int x, int y);
void UIMouseButton(int button, bool pressed, int x, int y);
void UIKey(int key, bool pressed);
// WM_CHAR (a Unicode code point) and WM_KEYDOWN (a Win32 VK code) for CPythonIME and OnIMEKeyDown.
void UIChar(unsigned codepoint);
void UIIMEKeyDown(int vkey);
// While IsAppLooping(), UIUpdate is AppFrame() and UIRender keeps the commands that Process() drew.
void UIUpdate();
void UIRender();
@@ -68,7 +68,7 @@ void CPythonApplication::SetFrameSkip(bool) { MT_PLATFORM_STUB(); }
// fonts and the IME here. The Godot window and its canvas already exist when the host starts the
// script, so the window size is the canvas size (PythonBoot::SetUISize) rather than the width/height
// passed from metin2.cfg, and the graphic device is the recording device (RecordingDevice.h) at that
// size. Cursors, sound, keyboard, fonts and the IME are not created here yet.
// size. Cursors, sound, keyboard and fonts are not created here yet.
bool CPythonApplication::Create(PyObject*, const char*, int, int, int)
{
unsigned width = 0, height = 0;
@@ -125,7 +125,11 @@ bool CPythonApplication::Create(PyObject*, const char*, int, int, int)
// 40250 m_pyItem.Create() (PythonApplication.cpp:1253): registers dropitem.mse with CEffectManager.
CPythonItem::Instance().Create();
// 40250 PythonApplication.cpp:1260, after DefaultFont_Startup and the IME.
// 40250 PythonApplication.cpp:1258-1259 (after DefaultFont_Startup).
CPythonIME::Instance().Create(GetWindowHandle());
CPythonIME::Instance().SetText("", 0);
// 40250 PythonApplication.cpp:1260.
CPythonTextTail::Instance().Initialize();
return true;
}
@@ -0,0 +1,119 @@
#include "StdAfx.h"
#include "PythonIME.h"
#include "AbstractApplication.h"
CPythonIME::CPythonIME()
: CIME()
{
ms_pEvent = this;
}
CPythonIME::~CPythonIME()
{
Tracen("PythonIME Clear");
}
void CPythonIME::Create(HWND hWnd)
{
Initialize(hWnd);
}
void CPythonIME::MoveLeft()
{
DecCurPos();
}
void CPythonIME::MoveRight()
{
IncCurPos();
}
void CPythonIME::MoveHome()
{
ms_curpos = 0;
}
void CPythonIME::MoveEnd()
{
ms_curpos = ms_lastpos;
}
void CPythonIME::SetCursorPosition(int iPosition)
{
SetCurPos(iPosition);
}
void CPythonIME::Delete()
{
DelCurPos();
}
void CPythonIME::OnUpdate()
{
IAbstractApplication::GetSingleton().RunIMEUpdate();
}
void CPythonIME::OnTab()
{
IAbstractApplication::GetSingleton().RunIMETabEvent();
}
void CPythonIME::OnReturn()
{
IAbstractApplication::GetSingleton().RunIMEReturnEvent();
}
void CPythonIME::OnEscape()
{
// IAbstractApplication::GetSingleton().RunIMEEscapeEvent();
}
bool CPythonIME::OnWM_CHAR( WPARAM wParam, LPARAM lParam )
{
unsigned char c = (unsigned char)(wParam & 0xff); // PORT: functional cast of a two-word type is MSVC-only.
switch (c)
{
case VK_RETURN:
OnReturn();
return true;
case VK_TAB:
if(ms_bCaptureInput == false)
return 0;
OnTab();
return true;
case VK_ESCAPE:
if(ms_bCaptureInput == false)
return 0;
OnEscape();
return true;
}
return false;
}
void CPythonIME::OnChangeCodePage()
{
IAbstractApplication::GetSingleton().RunIMEChangeCodePage();
}
void CPythonIME::OnOpenCandidateList()
{
IAbstractApplication::GetSingleton().RunIMEOpenCandidateListEvent();
}
void CPythonIME::OnCloseCandidateList()
{
IAbstractApplication::GetSingleton().RunIMECloseCandidateListEvent();
}
void CPythonIME::OnOpenReadingWnd()
{
IAbstractApplication::GetSingleton().RunIMEOpenReadingWndEvent();
}
void CPythonIME::OnCloseReadingWnd()
{
IAbstractApplication::GetSingleton().RunIMECloseReadingWndEvent();
}
@@ -0,0 +1,289 @@
#include "StdAfx.h"
#include "PythonIME.h"
#include "PythonApplication.h"
PyObject* imeEnable(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().Initialize(CPythonApplication::Instance().GetWindowHandle());
return Py_BuildNone();
}
PyObject* imeDisable(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().Uninitialize();
return Py_BuildNone();
}
PyObject* imeEnableCaptureInput(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().EnableCaptureInput();
return Py_BuildNone();
}
PyObject* imeDisableCaptureInput(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().DisableCaptureInput();
return Py_BuildNone();
}
PyObject* imeSetMax(PyObject* poSelf, PyObject* poArgs)
{
int iMax;
if (!PyTuple_GetInteger(poArgs, 0, &iMax))
return Py_BuildException();
CPythonIME::Instance().SetMax(iMax);
return Py_BuildNone();
}
PyObject* imeSetUserMax(PyObject* poSelf, PyObject* poArgs)
{
int iMax;
if (!PyTuple_GetInteger(poArgs, 0, &iMax))
return Py_BuildException();
CPythonIME::Instance().SetUserMax(iMax);
return Py_BuildNone();
}
PyObject* imeSetText(PyObject* poSelf, PyObject* poArgs)
{
char* szText;
if (!PyTuple_GetString(poArgs, 0, &szText))
return Py_BuildException();
CPythonIME::Instance().SetText(szText, strlen(szText));
return Py_BuildNone();
}
PyObject* imeGetText(PyObject* poSelf, PyObject* poArgs)
{
int bCodePage;
if (!PyTuple_GetInteger(poArgs, 0, &bCodePage))
bCodePage = 0;
std::string strText;
CPythonIME::Instance().GetText(strText, bCodePage ? true : false);
return Py_BuildValue("s", strText.c_str());
}
PyObject* imeGetCodePage(PyObject* poSelf, PyObject* poArgs)
{
return Py_BuildValue("i", CPythonIME::Instance().GetCodePage());
}
PyObject* imeGetCandidateCount(PyObject* poSelf, PyObject* poArgs)
{
return Py_BuildValue("i", CPythonIME::Instance().GetCandidatePageCount());
}
PyObject* imeGetCandidate(PyObject* poSelf, PyObject* poArgs)
{
int index;
if (!PyTuple_GetInteger(poArgs, 0, &index))
return Py_BuildException();
std::string strText;
int iLength = CPythonIME::Instance().GetCandidate(index, strText);
return Py_BuildValue("si", strText.c_str(), iLength);
}
PyObject* imeGetCandidateSelection(PyObject* poSelf, PyObject* poArgs)
{
return Py_BuildValue("i", CPythonIME::Instance().GetCandidateSelection());
}
PyObject* imeGetReading(PyObject* poSelf, PyObject* poArgs)
{
std::string strText;
CPythonIME::Instance().GetReading(strText);
return Py_BuildValue("s", strText.c_str());
}
PyObject* imeGetReadingError(PyObject* poSelf, PyObject* poArgs)
{
return Py_BuildValue("i", CPythonIME::Instance().GetReadingError());
}
PyObject* imeEnableIME(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().EnableIME();
return Py_BuildNone();
}
PyObject* imeDisableIME(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().DisableIME();
return Py_BuildNone();
}
PyObject* imeSetInputMode(PyObject* poSelf, PyObject* poArgs)
{
int mode;
if (!PyTuple_GetInteger(poArgs, 0, &mode))
return Py_BuildException();
CPythonIME::Instance().SetInputMode(mode);
return Py_BuildNone();
}
PyObject * imeSetNumberMode(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().SetNumberMode();
return Py_BuildNone();
}
PyObject * imeAddExceptKey(PyObject* poSelf, PyObject* poArgs)
{
int key;
if (!PyTuple_GetInteger(poArgs, 0, &key))
return Py_BuildException();
CPythonIME::Instance().AddExceptKey(key);
return Py_BuildNone();
}
PyObject * imeClearExceptKey(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().ClearExceptKey();
return Py_BuildNone();
}
PyObject * imeSetStringMode(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().SetStringMode();
return Py_BuildNone();
}
PyObject* imeGetInputMode(PyObject* poSelf, PyObject* poArgs)
{
return Py_BuildValue("i", CPythonIME::Instance().GetInputMode());
}
PyObject* imeMoveLeft(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().MoveLeft();
return Py_BuildNone();
}
PyObject* imeMoveRight(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().MoveRight();
return Py_BuildNone();
}
PyObject* imeMoveHome(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().MoveHome();
return Py_BuildNone();
}
PyObject* imeMoveEnd(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().MoveEnd();
return Py_BuildNone();
}
PyObject* imeSetCursorPosition(PyObject* poSelf, PyObject* poArgs)
{
int iPosition;
if (!PyTuple_GetInteger(poArgs, 0, &iPosition))
return Py_BuildException();
CPythonIME::Instance().SetCursorPosition(iPosition);
return Py_BuildNone();
}
PyObject* imeDelete(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().Delete();
return Py_BuildNone();
}
PyObject* imePasteTextFromClipBoard(PyObject* poSelf, PyObject* poArgs)
{
CPythonIME::Instance().PasteTextFromClipBoard();
return Py_BuildNone();
}
PyObject* imeEnablePaste(PyObject* poSelf, PyObject* poArgs)
{
int iFlag;
if (!PyTuple_GetInteger(poArgs, 0, &iFlag))
return Py_BuildException();
CPythonIME::Instance().EnablePaste(iFlag ? true : false);
return Py_BuildNone();
}
PyObject* imePasteString(PyObject * poSelf, PyObject * poArgs)
{
char* szText;
if (!PyTuple_GetString(poArgs, 0, &szText))
return Py_BuildException();
CPythonIME::Instance().PasteString(szText);
return Py_BuildNone();
}
PyObject* imePasteBackspace(PyObject * poSelf, PyObject * poArgs)
{
CPythonIME::Instance().WMChar(NULL, WM_CHAR, 0x08, NULL);
return Py_BuildNone();
}
PyObject* imePasteReturn(PyObject * poSelf, PyObject * poArgs)
{
CPythonIME::Instance().WMChar(NULL, WM_CHAR, 0x0D, NULL);
return Py_BuildNone();
}
void initime()
{
static PyMethodDef s_methods[] =
{
{ "Enable", imeEnable, METH_VARARGS },
{ "Disable", imeDisable, METH_VARARGS },
{ "EnableCaptureInput", imeEnableCaptureInput, METH_VARARGS },
{ "DisableCaptureInput", imeDisableCaptureInput, METH_VARARGS },
{ "SetMax", imeSetMax, METH_VARARGS },
{ "SetUserMax", imeSetUserMax, METH_VARARGS },
{ "SetText", imeSetText, METH_VARARGS },
{ "GetText", imeGetText, METH_VARARGS },
{ "GetCodePage", imeGetCodePage, METH_VARARGS },
{ "GetCandidateCount", imeGetCandidateCount, METH_VARARGS },
{ "GetCandidate", imeGetCandidate, METH_VARARGS },
{ "GetCandidateSelection", imeGetCandidateSelection, METH_VARARGS },
{ "GetReading", imeGetReading, METH_VARARGS },
{ "GetReadingError", imeGetReadingError, METH_VARARGS },
{ "EnableIME", imeEnableIME, METH_VARARGS },
{ "DisableIME", imeDisableIME, METH_VARARGS },
{ "GetInputMode", imeGetInputMode, METH_VARARGS },
{ "SetInputMode", imeSetInputMode, METH_VARARGS },
{ "SetNumberMode", imeSetNumberMode, METH_VARARGS },
{ "SetStringMode", imeSetStringMode, METH_VARARGS },
{ "AddExceptKey", imeAddExceptKey, METH_VARARGS },
{ "ClearExceptKey", imeClearExceptKey, METH_VARARGS },
{ "MoveLeft", imeMoveLeft, METH_VARARGS },
{ "MoveRight", imeMoveRight, METH_VARARGS },
{ "MoveHome", imeMoveHome, METH_VARARGS },
{ "MoveEnd", imeMoveEnd, METH_VARARGS },
{ "SetCursorPosition", imeSetCursorPosition, METH_VARARGS },
{ "Delete", imeDelete, METH_VARARGS },
{ "PasteString", imePasteString, METH_VARARGS },
{ "PasteBackspace", imePasteBackspace, METH_VARARGS },
{ "PasteReturn", imePasteReturn, METH_VARARGS },
{ "PasteTextFromClipBoard", imePasteTextFromClipBoard, METH_VARARGS },
{ "EnablePaste", imeEnablePaste, METH_VARARGS },
{ NULL, NULL, NULL },
};
Py_InitModule("ime", s_methods);
}
@@ -44,6 +44,7 @@ enum
// The Python module initializers RunMainScript calls, each defined in its own Python*Module.cpp.
// Listed here as 40250 lists them; only the ported ones are defined.
void initpack();
void initime();
void initapp();
void initchr();
void initchrmgr();
@@ -28,6 +28,12 @@
#define VK_HOME 0x24
#define VK_END 0x23
#define VK_DELETE 0x2E
#define VK_BACK 0x08
#define VK_TAB 0x09
#define VK_RETURN 0x0D
#define VK_ESCAPE 0x1B
#define WM_KEYDOWN 0x0100
#define WM_CHAR 0x0102
// winuser.h functions the platform layer implements (platform/ScriptLib/PythonBoot.cpp).
void PostQuitMessage(int nExitCode);
+6
View File
@@ -71,6 +71,8 @@ void Metin2PythonHost::ui_mouse_button(int button, bool pressed, int x, int y) {
PythonBoot::UIMouseButton(button, pressed, x, y);
}
void Metin2PythonHost::ui_key(int key, bool pressed) { PythonBoot::UIKey(key, pressed); }
void Metin2PythonHost::ui_char(int codepoint) { PythonBoot::UIChar(unsigned(codepoint)); }
void Metin2PythonHost::ui_ime_key(int vkey) { PythonBoot::UIIMEKeyDown(vkey); }
void Metin2PythonHost::ui_update() { PythonBoot::UIUpdate(); }
Array Metin2PythonHost::ui_render_commands() {
PythonBoot::UIRender();
@@ -264,6 +266,8 @@ void Metin2PythonHost::set_ui_size(int, int) {}
void Metin2PythonHost::ui_mouse_move(int, int) {}
void Metin2PythonHost::ui_mouse_button(int, bool, int, int) {}
void Metin2PythonHost::ui_key(int, bool) {}
void Metin2PythonHost::ui_char(int) {}
void Metin2PythonHost::ui_ime_key(int) {}
void Metin2PythonHost::ui_update() {}
Array Metin2PythonHost::ui_render_commands() { return Array(); }
Ref<Image> Metin2PythonHost::memory_texture(const String &, int64_t) { return Ref<Image>(); }
@@ -287,6 +291,8 @@ void Metin2PythonHost::_bind_methods() {
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_mouse_move", "x", "y"), &Metin2PythonHost::ui_mouse_move);
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_mouse_button", "button", "pressed", "x", "y"), &Metin2PythonHost::ui_mouse_button);
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_key", "key", "pressed"), &Metin2PythonHost::ui_key);
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_char", "codepoint"), &Metin2PythonHost::ui_char);
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_ime_key", "vkey"), &Metin2PythonHost::ui_ime_key);
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_update"), &Metin2PythonHost::ui_update);
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_render_commands"), &Metin2PythonHost::ui_render_commands);
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("memory_texture", "name", "known_revision"),
+3
View File
@@ -38,6 +38,9 @@ public:
static void ui_mouse_move(int x, int y);
static void ui_mouse_button(int button, bool pressed, int x, int y);
static void ui_key(int key, bool pressed);
// WM_CHAR (Unicode code point) and WM_KEYDOWN (Win32 VK code) for the 40250 IME.
static void ui_char(int codepoint);
static void ui_ime_key(int vkey);
static void ui_update();
static godot::Array ui_render_commands();
// The pixels of a "mem:<id>@<revision>" image command (the CGraphicFontTexture glyph pages) as an
@@ -655,6 +655,22 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
if (!ok)
return Fail(name + ": send GC_EXCHANGE");
}
// CInputMain::Chat (input_main.cpp): the line comes back to the speaker (PacketAround includes them)
// as a CHAT_TYPE_TALKING GC_CHAT "<name> : <text>" carrying their vid and empire.
if (header == HDR_CG_CHAT)
{
CGChatHead head;
std::memcpy(&head, packet, sizeof(head));
const size_t text_len = size > sizeof(head) ? size - sizeof(head) : 0;
const std::string text(reinterpret_cast<const char*>(packet + sizeof(head)),
strnlen(reinterpret_cast<const char*>(packet + sizeof(head)), text_len));
Event(name + ":chat " + std::to_string(head.type) + " " + text);
const std::string line = std::string(kCharacterName) + " : " + text;
GCChatHead chat = {HDR_GC_CHAT, static_cast<std::uint16_t>(sizeof(GCChatHead) + line.size() + 1),
0 /* CHAT_TYPE_TALKING */, kMainVID, 1};
if (!c.Send(chat) || !c.Send(line.c_str(), line.size() + 1))
return Fail(name + ": send GC_CHAT");
}
if (header == HDR_CG_MOVE)
{
CGMove move;
+40 -4
View File
@@ -244,9 +244,29 @@ int main(int argc, char** argv)
char ports[128];
std::snprintf(ports, sizeof(ports), "_auth_port, _game_port, _slot = %d, %d, %d", auth_port, game_port, slot);
CHECK(py(ports));
CHECK(py("_stream.SetConnectInfo(_host, _game_port, _host, _auth_port)\n"
"_stream.curPhaseWindow.Connect(_login, _password)\n"
"del __main__._login, __main__._password"));
CHECK(py("_stream.SetConnectInfo(_host, _game_port, _host, _auth_port)"));
// Typed into the login board through the 40250 IME (WM_CHAR -> CPythonIME::WMChar, WM_KEYDOWN ->
// OnIMEKeyDown): Tab moves to the password line, Return runs __OnClickLoginButton -> Connect.
// The IME only reaches a rendering EditLine: open the login board as the server list's OK button does.
CHECK(py("_w = _stream.curPhaseWindow\n"
"_w._LoginWindow__OpenLoginBoard()\n"
"_w.idEditLine.SetText('')\n"
"_w.pwdEditLine.SetText('')\n"
"_w.idEditLine.SetFocus()"));
auto type = [](const std::string& text) {
for (unsigned char c : text)
PythonBoot::UIChar(c);
};
type(login + "xy");
PythonBoot::UIIMEKeyDown(VK_LEFT);
PythonBoot::UIIMEKeyDown(VK_DELETE); // y
PythonBoot::UIChar(VK_BACK); // x
CHECK(py("assert _w.idEditLine.GetText() == _login, repr(_w.idEditLine.GetText())"));
PythonBoot::UIChar(VK_TAB);
type(password);
CHECK(py("assert _w.pwdEditLine.GetText() == _password, len(_w.pwdEditLine.GetText())\n"
"del __main__._login, __main__._password, __main__._w"));
PythonBoot::UIChar(VK_RETURN);
// 3. The character list.
const bool selecting = pump_until(live ? 30 : 20, [&] {
@@ -614,6 +634,22 @@ int main(int argc, char** argv)
// uiexchange.CloseDialog armed wndMgr.OnceIgnoreMouseLeftButtonUpEvent (in the client that release belongs
// to the click that closed the dialog); spend it so step 8's click-release is not swallowed.
PythonBoot::UIMouseButton(1, false, 400, 520);
// Chat (批次 4-a): Return on the focused game.GameWindow → OnIMEReturn → interface.ToggleChat opens the
// chat line; the typed text goes through CPythonIME, a second Return sends CG_CHAT (TALKING) and the fake
// server echoes GC_CHAT back.
CHECK(py_true("not _stream.curPhaseWindow.interface.IsOpenChat()"));
PythonBoot::UIChar(VK_RETURN);
CHECK(pump_until(1, [] { return py_true("_stream.curPhaseWindow.interface.IsOpenChat()"); }));
for (char c : std::string("hello"))
PythonBoot::UIChar((unsigned char)c);
pump_until(0.05, [] { return false; });
PythonBoot::UIChar(VK_RETURN);
CHECK(pump_until(2, [&] { return server->Has("game2:chat 0 hello"); }));
// uichat.__SendTalkingChatPacket only resets the line; a Return on the empty line closes it (eventReturn).
CHECK(py_true("_stream.curPhaseWindow.interface.IsOpenChat() and __import__('ime').GetText(0) == ''"));
PythonBoot::UIChar(VK_RETURN);
CHECK(pump_until(1, [] { return py_true("not _stream.curPhaseWindow.interface.IsOpenChat()"); }));
}
// 8. (批次 2V3) Click-to-move: a left click on the ground below the actor reaches game.GameWindow's
@@ -664,7 +700,7 @@ int main(int argc, char** argv)
for (const auto& e : events)
if (e.rfind(prefix, 0) == 0 && e.find(":move ") == std::string::npos &&
e.find(":pickup ") == std::string::npos &&
e.find(":exchange ") == std::string::npos)
e.find(":exchange ") == std::string::npos && e.find(":chat ") == std::string::npos)
got.push_back(e);
CHECK(want == got);
}