android-native: auto hunt, in-client account registration, mobile UI polish
- Auto hunt (client-only): PythonPlayerAutoHunt.cpp + AutoHuntScript.inc as the embedded mt_autohunt module, AUTO touch button, F9/F8. - Account registration: tools/40250/register_server.py (rc.d mt_register, :11080) inserts into account.account; net.RegisterAccount/GetRegisterAccountResult (PythonAccountRegister.cpp) and the mt_register login-window dialog (RegisterScript.inc). - --py-exec also runs in --login-screen mode for scripted login-screen tests. - Touch controller, window manager and render command updates for the Android native client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
53d06c3c26
commit
3e3708ef06
@@ -0,0 +1,198 @@
|
||||
// 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/40250/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 <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;
|
||||
|
||||
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)
|
||||
{
|
||||
pollfd kPoll = { iSocket, sEvents, 0 };
|
||||
return poll(&kPoll, 1, c_iRegisterTimeoutMs) == 1 && (kPoll.revents & sEvents);
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
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
|
||||
fcntl(iSocket, F_SETFL, fcntl(iSocket, F_GETFL, 0) | O_NONBLOCK);
|
||||
|
||||
std::string stResult = "ERR CONNECT";
|
||||
int iError = 0;
|
||||
socklen_t iErrorLen = sizeof(iError);
|
||||
if (connect(iSocket, pkAddr->ai_addr, pkAddr->ai_addrlen) == 0 ||
|
||||
(errno == EINPROGRESS && __WaitFor(iSocket, POLLOUT) &&
|
||||
getsockopt(iSocket, SOL_SOCKET, SO_ERROR, &iError, &iErrorLen) == 0 && iError == 0))
|
||||
{
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
close(iSocket);
|
||||
freeaddrinfo(pkAddr);
|
||||
return 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
|
||||
}
|
||||
|
||||
const std::string stHost = szHost;
|
||||
const std::string stBody = "login=" + __UrlEncode(szLogin) + "&password=" + __UrlEncode(szPassword) +
|
||||
"&social_id=" + __UrlEncode(szSocialID);
|
||||
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;
|
||||
}).detach();
|
||||
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());
|
||||
}
|
||||
Reference in New Issue
Block a user