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
This commit is contained in:
@@ -12,6 +12,10 @@
|
||||
#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>
|
||||
@@ -35,6 +39,11 @@ 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)
|
||||
{
|
||||
@@ -58,8 +67,14 @@ std::string __UrlEncode(const std::string& c_rstText)
|
||||
|
||||
bool __WaitFor(int iSocket, short sEvents)
|
||||
{
|
||||
pollfd kPoll = { iSocket, sEvents, 0 };
|
||||
return poll(&kPoll, 1, c_iRegisterTimeoutMs) == 1 && (kPoll.revents & 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)
|
||||
@@ -72,6 +87,7 @@ std::string __Post(const std::string& c_rstHost, int iPort, const std::string& c
|
||||
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)
|
||||
{
|
||||
@@ -82,14 +98,37 @@ std::string __Post(const std::string& c_rstHost, int iPort, const std::string& c
|
||||
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))
|
||||
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"
|
||||
@@ -140,9 +179,16 @@ std::string __Post(const std::string& c_rstHost, int iPort, const std::string& c
|
||||
}
|
||||
}
|
||||
|
||||
close(iSocket);
|
||||
#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 stResult;
|
||||
return gs_registerCancelled ? "ERR CANCELLED" : stResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +218,12 @@ PyObject* netRegisterAccount(PyObject* poSelf, PyObject* poArgs)
|
||||
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);
|
||||
std::thread([stHost, iPort, stBody]()
|
||||
gs_registerWorker = std::thread([stHost, iPort, stBody]()
|
||||
{
|
||||
const std::string stResult = __Post(stHost, iPort, stBody);
|
||||
{
|
||||
@@ -183,7 +231,7 @@ PyObject* netRegisterAccount(PyObject* poSelf, PyObject* poArgs)
|
||||
gs_strRegisterResult = stResult;
|
||||
}
|
||||
gs_iRegisterState = REGISTER_DONE;
|
||||
}).detach();
|
||||
});
|
||||
return Py_BuildValue("i", 1);
|
||||
}
|
||||
|
||||
@@ -196,3 +244,22 @@ PyObject* netGetRegisterAccountResult(PyObject* poSelf, PyObject* poArgs)
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user