Files
metin2-client/src/port/UserInterface/PythonAccountRegister.cpp
T
shenandshen 507e687867 feat: add MeshPort overlay networking, item actions window, and AVA2 client enhancements
- add MeshPort peer-to-peer overlay networking SDK, C++ integration, and Python bindings
- add item actions quick use/split/delete/lock overlay window (mt_item_actions)
- add shop name range and NPC name visibility settings to system display options
- support battlepass ticket activation and month validation in mt_battlepass
- support boss tracking teleport destination button in mt_bosses
- support asynchronous account registration cancellation and pending login cancellation
- update AVA2 item conversion for attr lock scrolls and battlepass tickets
- update .gitignore for local build outputs and caches
2026-10-16 01:29:57 -07:00

266 lines
7.8 KiB
C++

// PORT: in-client account registration. 40250 has no counterpart: its accounts were made on the web.
//
// net.RegisterAccount(host, port, login, password, socialId) starts one HTTP POST /register to the
// server's registration service (tools/server/register_server.py) on a worker thread;
// net.GetRegisterAccountResult() polls it from the login window's OnUpdate. The reply is a single
// line, "OK" or "ERR <CODE>", returned to Python as-is.
#include "StdAfx.h"
#include <atomic>
#include <cerrno>
#include <cstring>
#include <mutex>
#include <string>
#include <thread>
#include <chrono>
#ifdef MT_HAS_MESHPORT
#include "transport.h" // PORT: registration shares the same LAN/mesh policy as every game socket.
#endif
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/socket.h>
#include <unistd.h>
namespace
{
enum ERegisterState
{
REGISTER_IDLE,
REGISTER_PENDING,
REGISTER_DONE,
};
const int c_iRegisterTimeoutMs = 8000;
std::mutex gs_kRegisterMutex;
std::atomic<int> gs_iRegisterState(REGISTER_IDLE);
std::string gs_strRegisterResult;
// PORT: registration must stop on dialog cancellation and before the mesh runtime is torn down.
std::thread gs_registerWorker;
std::atomic<bool> gs_registerCancelled(false);
std::mutex gs_registerSocketMutex;
int gs_registerSocket = -1;
std::string __UrlEncode(const std::string& c_rstText)
{
static const char c_szHex[] = "0123456789ABCDEF";
std::string stOut;
for (unsigned char c : c_rstText)
{
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~')
{
stOut += char(c);
}
else
{
stOut += '%';
stOut += c_szHex[c >> 4];
stOut += c_szHex[c & 15];
}
}
return stOut;
}
bool __WaitFor(int iSocket, short sEvents)
{
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(c_iRegisterTimeoutMs);
while (!gs_registerCancelled && std::chrono::steady_clock::now() < deadline) {
pollfd kPoll = {iSocket,sEvents,0};
int result = poll(&kPoll,1,100);
if (result > 0) return (kPoll.revents & sEvents) != 0;
if (result < 0 && errno != EINTR) return false;
}
return false;
}
std::string __Post(const std::string& c_rstHost, int iPort, const std::string& c_rstBody)
{
addrinfo kHints;
memset(&kHints, 0, sizeof(kHints));
kHints.ai_family = AF_INET;
kHints.ai_socktype = SOCK_STREAM;
addrinfo* pkAddr = NULL;
if (getaddrinfo(c_rstHost.c_str(), std::to_string(iPort).c_str(), &kHints, &pkAddr) != 0 || !pkAddr)
return "ERR CONNECT";
if (gs_registerCancelled) { freeaddrinfo(pkAddr); return "ERR CANCELLED"; }
const int iSocket = socket(pkAddr->ai_family, pkAddr->ai_socktype, pkAddr->ai_protocol);
if (iSocket < 0)
{
freeaddrinfo(pkAddr);
return "ERR CONNECT";
}
#ifdef SO_NOSIGPIPE
const int iOne = 1;
setsockopt(iSocket, SOL_SOCKET, SO_NOSIGPIPE, &iOne, sizeof(iOne));
#endif
{
std::lock_guard<std::mutex> lock(gs_registerSocketMutex);
gs_registerSocket = iSocket;
}
fcntl(iSocket, F_SETFL, fcntl(iSocket, F_GETFL, 0) | O_NONBLOCK);
std::string stResult = "ERR CONNECT";
int iError = 0;
socklen_t iErrorLen = sizeof(iError);
#ifdef MT_HAS_MESHPORT
const int connected = mt::meshport::connect_socket(iSocket, *reinterpret_cast<sockaddr_in*>(pkAddr->ai_addr));
const bool throughMesh = mt::meshport::pending(iSocket);
bool meshReady = true;
if (mt::meshport::pending(iSocket)) {
int state = 0;
while (state == 0 && !gs_registerCancelled) {
state = mt::meshport::poll_socket(iSocket);
if (!state) std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
meshReady = state == 1;
}
if (!gs_registerCancelled && meshReady && (throughMesh || connected == 0 ||
#else
if (connect(iSocket, pkAddr->ai_addr, pkAddr->ai_addrlen) == 0 ||
#endif
(errno == EINPROGRESS && __WaitFor(iSocket, POLLOUT) &&
getsockopt(iSocket, SOL_SOCKET, SO_ERROR, &iError, &iErrorLen) == 0 && iError == 0)
#ifdef MT_HAS_MESHPORT
)
#endif
)
{
const std::string stRequest =
"POST /register HTTP/1.0\r\n"
"Host: " + c_rstHost + "\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: " + std::to_string(c_rstBody.size()) + "\r\n"
"Connection: close\r\n\r\n" + c_rstBody;
size_t uSent = 0;
while (uSent < stRequest.size() && __WaitFor(iSocket, POLLOUT))
{
#ifdef MSG_NOSIGNAL
const ssize_t n = send(iSocket, stRequest.data() + uSent, stRequest.size() - uSent, MSG_NOSIGNAL);
#else
const ssize_t n = send(iSocket, stRequest.data() + uSent, stRequest.size() - uSent, 0);
#endif
if (n <= 0)
break;
uSent += size_t(n);
}
std::string stResponse;
if (uSent == stRequest.size())
{
char acBuf[512];
while (stResponse.size() < 4096 && __WaitFor(iSocket, POLLIN))
{
const ssize_t n = recv(iSocket, acBuf, sizeof(acBuf), 0);
if (n <= 0)
break;
stResponse.append(acBuf, size_t(n));
}
}
const size_t uBody = stResponse.find("\r\n\r\n");
if (uBody == std::string::npos)
{
stResult = uSent == stRequest.size() ? "ERR TIMEOUT" : "ERR CONNECT";
}
else
{
stResult = stResponse.substr(uBody + 4);
const size_t uEnd = stResult.find_first_of("\r\n");
if (uEnd != std::string::npos)
stResult.resize(uEnd);
if (stResult.empty())
stResult = "ERR SERVER";
}
}
#ifdef MT_HAS_MESHPORT
mt::meshport::forget_socket(iSocket);
#endif
{
std::lock_guard<std::mutex> lock(gs_registerSocketMutex);
gs_registerSocket = -1;
close(iSocket);
}
freeaddrinfo(pkAddr);
return gs_registerCancelled ? "ERR CANCELLED" : stResult;
}
}
PyObject* netRegisterAccount(PyObject* poSelf, PyObject* poArgs)
{
char* szHost;
int iPort;
char* szLogin;
char* szPassword;
char* szSocialID;
if (!PyTuple_GetString(poArgs, 0, &szHost))
return Py_BuildException();
if (!PyTuple_GetInteger(poArgs, 1, &iPort))
return Py_BuildException();
if (!PyTuple_GetString(poArgs, 2, &szLogin))
return Py_BuildException();
if (!PyTuple_GetString(poArgs, 3, &szPassword))
return Py_BuildException();
if (!PyTuple_GetString(poArgs, 4, &szSocialID))
return Py_BuildException();
int iExpected = REGISTER_IDLE;
if (!gs_iRegisterState.compare_exchange_strong(iExpected, REGISTER_PENDING))
{
iExpected = REGISTER_DONE;
if (!gs_iRegisterState.compare_exchange_strong(iExpected, REGISTER_PENDING))
return Py_BuildValue("i", 0); // one request at a time
}
if (gs_registerWorker.joinable()) gs_registerWorker.join();
gs_registerCancelled = false;
const std::string stHost = szHost;
const std::string stBody = "login=" + __UrlEncode(szLogin) + "&password=" + __UrlEncode(szPassword) +
"&social_id=" + __UrlEncode(szSocialID);
gs_registerWorker = std::thread([stHost, iPort, stBody]()
{
const std::string stResult = __Post(stHost, iPort, stBody);
{
std::lock_guard<std::mutex> kLock(gs_kRegisterMutex);
gs_strRegisterResult = stResult;
}
gs_iRegisterState = REGISTER_DONE;
});
return Py_BuildValue("i", 1);
}
// Returns "" while the request runs (or none was started), else its result once.
PyObject* netGetRegisterAccountResult(PyObject* poSelf, PyObject* poArgs)
{
int iExpected = REGISTER_DONE;
if (!gs_iRegisterState.compare_exchange_strong(iExpected, REGISTER_IDLE))
return Py_BuildValue("s", "");
std::lock_guard<std::mutex> kLock(gs_kRegisterMutex);
return Py_BuildValue("s", gs_strRegisterResult.c_str());
}
// PORT: cancellation interrupts both route preparation and HTTP I/O; the worker owns close().
void CancelAccountRegistration()
{
gs_registerCancelled = true;
std::lock_guard<std::mutex> lock(gs_registerSocketMutex);
if (gs_registerSocket >= 0) ::shutdown(gs_registerSocket,SHUT_RDWR);
}
void StopAccountRegistration()
{
CancelAccountRegistration();
if (gs_registerWorker.joinable()) gs_registerWorker.join();
gs_iRegisterState = REGISTER_IDLE;
}
PyObject* netCancelRegisterAccount(PyObject*,PyObject*)
{
CancelAccountRegistration();
return Py_BuildNone();
}