Remove sleeps and timed polling from networking

Socket waits now block in select() on the socket plus a wake or cancel
socket (Common/Net/Cancel), so a stop or cancel ends them at once.

- HTTP: cancel flags become net::CancelToken; Connect waits once on all
  addresses, receives wait instead of spinning, no DNS retry sleep.
- Adhoc: the friend finder and adhoc server block on their sockets with
  a wake socket and a deadline; matching runs on a CoreTiming tick
  instead of two threads (savestate section bumped).
- sceNetInet select/poll check without blocking and park the HLE thread,
  re-checked from a CoreTiming event until ready or the emulated
  timeout.
- Vendor the aemu_postoffice client (was a submodule) with no sleeps:
  waits on the socket, close via shutdown and an eventcount, cancelable
  connects, and lock-free session slot claims.
- PortManager waits without a timeout; PSP_InitWait replaces the
  headless boot poll.
- TimeUtil: sleep_us/sleep_ms only outside the core; sleep_precise,
  sleep_random, yield and the Windows timer period are gone.
- Unit test for the socket waits.
This commit is contained in:
libretroadmin committed 2026-10-08 17:58:46 +02:00
1 parent 9f13f0f051
commit 5f0f72bfda
58 files changed
+3822 -763

No files matched your search

-4
View File
@@ -53,10 +53,6 @@
[submodule "ext/nanosvg"]
path = ext/nanosvg
url = https://github.com/hrydgard/nanosvg.git
[submodule "ext/aemu_postoffice"]
path = ext/aemu_postoffice
url = https://github.com/Kethen/aemu_postoffice.git
branch = ppsspp
[submodule "frametests"]
path = frametests
url = https://github.com/hrydgard/ppsspp-frametest-ci.git
+1
View File
@@ -1155,6 +1155,7 @@ if(UNITTEST)
unittest/TestLoongArch64Emitter.cpp
unittest/TestSoftwareGPUJit.cpp
unittest/TestThreadManager.cpp
unittest/TestNetWait.cpp
unittest/JitHarness.cpp
Core/MIPS/ARM/ArmRegCache.cpp
Core/MIPS/ARM/ArmRegCacheFPU.cpp
+2
View File
@@ -243,6 +243,8 @@ add_library(Common STATIC
Net/HTTPNaettRequest.h
Net/HTTPRequest.cpp
Net/HTTPRequest.h
Net/Cancel.cpp
Net/Cancel.h
Net/NetBuffer.cpp
Net/NetBuffer.h
Net/Resolve.cpp
+19 -7
View File
@@ -18,7 +18,7 @@
#include "android/jni/app-android.h"
#endif
static bool LoadRemoteFileList(const Path &url, std::string_view userAgent, bool *cancel, std::vector<File::FileInfo> &files) {
static bool LoadRemoteFileList(const Path &url, std::string_view userAgent, const net::CancelToken *cancel, std::vector<File::FileInfo> &files) {
_dbg_assert_(url.Type() == PathType::HTTP);
http::Client http(nullptr);
@@ -43,7 +43,7 @@ static bool LoadRemoteFileList(const Path &url, std::string_view userAgent, bool
}
}
if (code != 200 || (cancel && *cancel)) {
if (code != 200 || (cancel && cancel->IsCancelled())) {
return false;
}
@@ -113,7 +113,7 @@ static bool LoadRemoteFileList(const Path &url, std::string_view userAgent, bool
PathBrowser::~PathBrowser() {
{
std::unique_lock<std::mutex> guard(pendingLock_);
pendingCancel_ = true;
CancelPending();
pendingStop_ = true;
pendingCond_.notify_all();
}
@@ -122,6 +122,13 @@ PathBrowser::~PathBrowser() {
}
}
// Under pendingLock_.
void PathBrowser::CancelPending() {
pendingCancel_ = true;
if (loadCancel_)
loadCancel_->Cancel();
}
void PathBrowser::SetPath(const Path &path) {
std::lock_guard<std::mutex> guard(pendingLock_);
path_ = path;
@@ -138,7 +145,7 @@ void PathBrowser::RestrictToRoot(const Path &root) {
void PathBrowser::HandlePath() {
if (!path_.empty() && path_.ToString()[0] == '!') {
if (pendingActive_) {
pendingCancel_ = true;
CancelPending();
pendingPath_.clear();
}
ready_ = true;
@@ -173,10 +180,15 @@ void PathBrowser::HandlePath() {
lastPath = pendingPath_;
if (lastPath.Type() == PathType::HTTP) {
std::string userAgentCopy = userAgent_;
std::shared_ptr<net::CancelToken> cancel = std::make_shared<net::CancelToken>();
if (pendingCancel_)
cancel->Cancel();
loadCancel_ = cancel;
guard.unlock();
results.clear();
bool tempSuccess = LoadRemoteFileList(lastPath, userAgentCopy, &pendingCancel_, results);
bool tempSuccess = LoadRemoteFileList(lastPath, userAgentCopy, cancel.get(), results);
guard.lock();
loadCancel_.reset();
success_ = tempSuccess;
} else if (lastPath.empty()) {
results.clear();
@@ -204,9 +216,9 @@ void PathBrowser::HandlePath() {
});
}
bool PathBrowser::GetListing(std::vector<File::FileInfo> &fileInfo, const char *extensionFilter, bool *cancel) {
bool PathBrowser::GetListing(std::vector<File::FileInfo> &fileInfo, const char *extensionFilter, const net::CancelToken *cancel) {
std::unique_lock<std::mutex> guard(pendingLock_);
while (!ready_ && (!cancel || !*cancel)) {
while (!ready_ && (!cancel || !cancel->IsCancelled())) {
// In case cancel changes, just sleep. TODO: Replace with condition variable.
guard.unlock();
sleep_ms(50, "pathbrowser-poll");
+5 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include <condition_variable>
#include <memory>
#include <mutex>
#include <string_view>
#include <cstring>
@@ -10,6 +11,7 @@
#include "Common/File/DirListing.h"
#include "Common/File/Path.h"
#include "Common/Net/Cancel.h"
// Abstraction above path that lets you navigate easily and get listings off-thread.
// "/" is a special path that means the root of the file system. On Windows,
@@ -29,7 +31,7 @@ public:
}
// If called before IsListingReady() returns true, will block (becomes synchronous). Don't do that.
bool GetListing(std::vector<File::FileInfo> &fileInfo, const char *filter = nullptr, bool *cancel = nullptr);
bool GetListing(std::vector<File::FileInfo> &fileInfo, const char *filter = nullptr, const net::CancelToken *cancel = nullptr);
bool CanNavigateUp() const;
void NavigateUp();
@@ -55,6 +57,7 @@ public:
private:
void HandlePath();
void CancelPending();
void ApplyRestriction();
Path path_;
@@ -67,6 +70,7 @@ private:
std::thread pendingThread_;
bool pendingActive_ = false;
bool pendingCancel_ = false;
std::shared_ptr<net::CancelToken> loadCancel_;
bool pendingStop_ = false;
bool ready_ = false;
bool success_ = true;
+2 -3
View File
@@ -200,12 +200,11 @@ bool GhidraClient::FetchResource(const std::string &path, std::string &outResult
pendingResult_.error = "can't resolve host";
return false;
}
bool cancelled = false;
if (!http.Connect(1, 5.0, &cancelled)) {
if (!http.Connect(1, 5.0)) {
pendingResult_.error = "can't connect to host";
return false;
}
net::RequestProgress progress(&cancelled);
net::RequestProgress progress(nullptr);
Buffer result;
const int code = http.GET(http::RequestParams(path.c_str()), &result, &progress);
http.Disconnect();
+141
View File
@@ -0,0 +1,141 @@
#include "ppsspp_config.h"
// First, so Windows gets its socket error codes instead of the CRT's.
#include "Common/Net/SocketCompat.h"
#include <cmath>
#include "Common/Net/Cancel.h"
#include "Common/File/FileDescriptor.h"
#include "Common/TimeUtil.h"
namespace net {
WakeSocket::WakeSocket() {
intptr_t s = (intptr_t)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s == (intptr_t)INVALID_SOCKET)
return;
#if !PPSSPP_PLATFORM(WINDOWS)
if (s >= FD_SETSIZE) {
closesocket(s);
return;
}
#endif
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = 0;
socklen_t len = sizeof(addr);
if (bind(s, (sockaddr *)&addr, sizeof(addr)) != 0 ||
getsockname(s, (sockaddr *)&addr, &len) != 0 ||
connect(s, (sockaddr *)&addr, sizeof(addr)) != 0) {
closesocket(s);
return;
}
fd_util::SetNonBlocking((int)s, true);
fd_ = s;
}
WakeSocket::~WakeSocket() {
if (fd_ != -1)
closesocket(fd_);
}
void WakeSocket::Wake() {
if (fd_ != -1) {
// Non-blocking: with the buffer full, a wake is already pending.
const char b = 1;
send(fd_, &b, 1, MSG_NOSIGNAL);
}
}
void WakeSocket::Drain() {
if (fd_ != -1) {
char buf[64];
while (recv(fd_, buf, sizeof(buf), 0) > 0) {
}
}
}
void CancelToken::Cancel() {
cancelled_.store(true, std::memory_order_release);
// Never drained, so the socket stays readable from here on.
wake_.Wake();
}
static WaitResult WaitInternal(const uintptr_t *socks, bool *ready, int count, bool forWrite, double timeout, intptr_t wake, const CancelToken *cancel) {
const double deadline = timeout >= 0.0 ? time_now_d() + timeout : 0.0;
for (;;) {
if (cancel && cancel->IsCancelled())
return WaitResult::CANCELLED;
fd_set rfds, wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
intptr_t maxfd = -1;
for (int i = 0; i < count; i++) {
if ((intptr_t)socks[i] == -1)
continue;
FD_SET((SOCKET)socks[i], forWrite ? &wfds : &rfds);
if ((intptr_t)socks[i] > maxfd)
maxfd = (intptr_t)socks[i];
}
if (wake != -1) {
FD_SET((SOCKET)wake, &rfds);
if (wake > maxfd)
maxfd = wake;
}
timeval tv{};
timeval *ptv = nullptr;
if (timeout >= 0.0) {
double left = deadline - time_now_d();
if (left < 0.0)
left = 0.0;
tv.tv_sec = (long)floor(left);
tv.tv_usec = (long)((left - floor(left)) * 1000000.0);
ptv = &tv;
}
if (maxfd == -1) {
// Only the timeout could end this, and that would be a sleep.
return WaitResult::FAILED;
}
const int r = select((int)maxfd + 1, &rfds, forWrite ? &wfds : nullptr, nullptr, ptv);
if (r < 0) {
if (socket_errno == EINTR)
continue;
return WaitResult::FAILED;
}
if (cancel && cancel->IsCancelled())
return WaitResult::CANCELLED;
if (r == 0)
return WaitResult::TIMEOUT;
bool any = false;
for (int i = 0; i < count; i++) {
const bool set = (intptr_t)socks[i] != -1 && FD_ISSET((SOCKET)socks[i], forWrite ? &wfds : &rfds) != 0;
if (ready)
ready[i] = set;
any = any || set;
}
if (any)
return WaitResult::READY;
if (!cancel && wake != -1 && FD_ISSET((SOCKET)wake, &rfds))
return WaitResult::CANCELLED;
}
}
WaitResult WaitSockets(const uintptr_t *socks, bool *ready, int count, bool forWrite, double timeout, const CancelToken *cancel) {
return WaitInternal(socks, ready, count, forWrite, timeout, cancel ? cancel->WakeFd() : -1, cancel);
}
WaitResult WaitSocket(uintptr_t sock, bool forWrite, double timeout, const CancelToken *cancel) {
return WaitSockets(&sock, nullptr, 1, forWrite, timeout, cancel);
}
WaitResult WaitSocketsOrWake(const uintptr_t *socks, bool *ready, int count, bool forWrite, double timeout, WakeSocket *wake) {
return WaitInternal(socks, ready, count, forWrite, timeout, wake ? wake->Fd() : -1, nullptr);
}
} // namespace net
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <atomic>
#include <cstdint>
namespace net {
// A socket that another thread can make readable, to wake a select() that
// includes it. It is a loopback UDP socket connected to itself. If one can't
// be made, Fd() is -1 and waits only see a wake when they return anyway.
class WakeSocket {
public:
WakeSocket();
~WakeSocket();
WakeSocket(const WakeSocket &) = delete;
WakeSocket &operator=(const WakeSocket &) = delete;
// Any thread. Wakes stack up into one readable state until drained.
void Wake();
// Called by the waiting thread after it wakes, before it looks at its state.
void Drain();
intptr_t Fd() const { return fd_; }
private:
intptr_t fd_ = -1;
};
// A cancel flag that socket waits block on. Cancel() sets the flag and
// wakes its socket for good, so every wait that includes it returns at once.
class CancelToken {
public:
// Any thread.
void Cancel();
bool IsCancelled() const { return cancelled_.load(std::memory_order_acquire); }
intptr_t WakeFd() const { return wake_.Fd(); }
private:
std::atomic<bool> cancelled_{ false };
WakeSocket wake_;
};
enum class WaitResult {
READY,
TIMEOUT,
CANCELLED, // or, for the WakeSocket form, woken
FAILED,
};
// Blocks until sock is readable (or writable), cancel is set, or timeout
// seconds pass. A negative timeout waits for the first two only.
WaitResult WaitSocket(uintptr_t sock, bool forWrite, double timeout, const CancelToken *cancel);
// The same over several sockets. ready[i] says which ones are ready on READY.
WaitResult WaitSockets(const uintptr_t *socks, bool *ready, int count, bool forWrite, double timeout, const CancelToken *cancel);
// For event loops: also returns CANCELLED when wake is woken. Any of socks
// may be -1 (skipped); count may be 0, to wait only for a wake or the timeout.
WaitResult WaitSocketsOrWake(const uintptr_t *socks, bool *ready, int count, bool forWrite, double timeout, WakeSocket *wake);
} // namespace net
+36 -68
View File
@@ -1,6 +1,7 @@
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <utility>
#include "Common/Net/HTTPClient.h"
@@ -105,7 +106,7 @@ static void FormatAddr(char *addrbuf, size_t bufsize, const addrinfo *info) {
}
}
bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) {
bool Connection::Connect(int maxTries, double timeout, const net::CancelToken *cancel) {
if (port_ <= 0) {
ERROR_LOG(Log::Net, "Bad port");
return false;
@@ -114,9 +115,6 @@ bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) {
for (int tries = maxTries; tries > 0; --tries) {
std::vector<uintptr_t> sockets;
fd_set fds;
int maxfd = 1;
FD_ZERO(&fds);
for (addrinfo *possible = resolved_; possible != nullptr; possible = possible->ai_next) {
if (possible->ai_family != AF_INET && possible->ai_family != AF_INET6)
continue;
@@ -156,68 +154,44 @@ bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) {
}
}
sockets.push_back(sock);
FD_SET(sock, &fds);
if (maxfd < sock + 1) {
maxfd = sock + 1;
}
}
if (cancel && cancel->IsCancelled()) {
for (uintptr_t sock : sockets)
closesocket(sock);
WARN_LOG(Log::Net, "connect: cancelled: %s:%d", host_.c_str(), port_);
break;
}
if (sockets.empty()) {
// No need to call 'select' if we don't have any plausible sockets.
if (cancelConnect && *cancelConnect) {
WARN_LOG(Log::Net, "connect: cancelled (2): %s:%d", host_.c_str(), port_);
break;
}
sleep_ms(1, "connect");
// No plausible address this time round; try the next one straight away.
continue;
}
// There is at least 1 socket candidate.
int selectResult = 0;
long timeoutHalfSeconds = floor(2 * timeout);
while (timeoutHalfSeconds >= 0 && selectResult == 0) {
struct timeval tv{};
tv.tv_sec = 0;
if (timeoutHalfSeconds > 0) {
// Wait up to 0.5 seconds between cancel checks.
tv.tv_usec = 500000;
} else {
// Wait the remaining <= 0.5 seconds. Possibly 0, but that's okay.
tv.tv_usec = (timeout - floor(2 * timeout) / 2) * 1000000.0;
}
--timeoutHalfSeconds;
selectResult = select(maxfd, nullptr, &fds, nullptr, &tv);
if (cancelConnect && *cancelConnect) {
WARN_LOG(Log::HTTP, "connect: cancelled (1): %s:%d", host_.c_str(), port_);
break;
}
}
if (selectResult > 0) {
// Wait for the first to connect, the timeout, or a cancel - no slicing, a cancel wakes the wait.
std::unique_ptr<bool[]> ready(new bool[sockets.size()]());
const net::WaitResult wait = net::WaitSockets(sockets.data(), ready.get(), (int)sockets.size(), true, timeout, cancel);
if (wait == net::WaitResult::READY) {
// Something connected. Pick the first one that did (if multiple.)
for (int sock : sockets) {
if ((intptr_t)sock_ == -1 && FD_ISSET(sock, &fds)) {
sock_ = sock;
for (size_t i = 0; i < sockets.size(); i++) {
if ((intptr_t)sock_ == -1 && ready[i]) {
sock_ = sockets[i];
} else {
closesocket(sock);
closesocket(sockets[i]);
}
}
// Great, now we're good to go.
return true;
} else {
// Fail. Close all the sockets.
for (int sock : sockets) {
closesocket(sock);
}
}
if (cancelConnect && *cancelConnect) {
WARN_LOG(Log::Net, "connect: cancelled (2): %s:%d", host_.c_str(), port_);
// Fail. Close all the sockets.
for (uintptr_t sock : sockets) {
closesocket(sock);
}
if (wait == net::WaitResult::CANCELLED) {
WARN_LOG(Log::HTTP, "connect: cancelled: %s:%d", host_.c_str(), port_);
break;
}
sleep_ms(1, "connect");
}
// Nothing connected, unfortunately.
@@ -386,7 +360,7 @@ int Client::SendRequestWithData(const char *method, const RequestParams &req, st
req.acceptMime,
otherHeaders ? otherHeaders : "");
buffer.Append(data);
bool flushed = buffer.FlushSocket(sock(), headerTimeout_, progress ? progress->cancelled : nullptr);
bool flushed = buffer.FlushSocket(sock(), headerTimeout_, progress ? progress->cancel : nullptr);
if (!flushed) {
return -1; // TODO error code.
}
@@ -395,18 +369,13 @@ int Client::SendRequestWithData(const char *method, const RequestParams &req, st
int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, net::RequestProgress *progress, std::string *statusLine) {
// Snarf all the data we can into RAM. A little unsafe but hey.
static constexpr float CANCEL_INTERVAL = 0.25f;
bool ready = false;
double endTimeout = time_now_d() + headerTimeout_;
while (!ready) {
if (progress && progress->cancelled && *progress->cancelled)
return -1;
ready = fd_util::WaitUntilReady(sock(), CANCEL_INTERVAL, false);
if (!ready && time_now_d() > endTimeout) {
ERROR_LOG(Log::HTTP, "HTTP headers timed out");
return -1;
}
};
const net::WaitResult wait = net::WaitSocket(sock(), false, headerTimeout_, progress ? progress->cancel : nullptr);
if (wait == net::WaitResult::TIMEOUT) {
ERROR_LOG(Log::HTTP, "HTTP headers timed out");
return -1;
} else if (wait != net::WaitResult::READY) {
return -1;
}
// Let's hope all the headers are available in a single packet...
if (readbuf->Read(sock(), 4096) < 0) {
ERROR_LOG(Log::HTTP, "Failed to read HTTP headers :(");
@@ -457,7 +426,6 @@ int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &
}
int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, net::RequestProgress *progress) {
_dbg_assert_(progress->cancelled);
bool gzip = false;
bool chunked = false;
@@ -531,7 +499,7 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::stri
}
HTTPRequest::HTTPRequest(RequestMethod method, std::string_view url, std::string_view postData, std::string_view postMime, const Path &outfile, RequestFlags flags, net::ResolveFunc customResolve, std::string_view name)
: Request(method, url, name, outfile, &cancelled_, flags), postData_(postData), postMime_(postMime), customResolve_(std::move(customResolve)) {
: Request(method, url, name, outfile, flags), postData_(postData), postMime_(postMime), customResolve_(std::move(customResolve)) {
}
HTTPRequest::~HTTPRequest() {
@@ -578,16 +546,16 @@ int HTTPRequest::Perform(const std::string &url) {
return -1;
}
if (cancelled_) {
if (IsCancelled()) {
return -1;
}
if (!client.Connect(2, 20.0, &cancelled_)) {
ERROR_LOG(Log::HTTP, "Failed connecting to server or cancelled (=%d).", cancelled_);
if (!client.Connect(2, 20.0, &cancel_)) {
ERROR_LOG(Log::HTTP, "Failed connecting to server or cancelled (=%d).", (int)IsCancelled());
return -1;
}
if (cancelled_) {
if (IsCancelled()) {
return -1;
}
+3 -3
View File
@@ -26,7 +26,7 @@ public:
// Inits the addrinfo chain.
bool Resolve(const char *host, int port, DNSType type = DNSType::ANY);
bool Connect(int maxTries = 2, double timeout = 20.0f, bool *cancelConnect = nullptr);
bool Connect(int maxTries = 2, double timeout = 20.0f, const net::CancelToken *cancel = nullptr);
void Disconnect();
// TODO: Try to expose this less.
@@ -137,8 +137,8 @@ private:
// This is simply a finished request, that can still be queried like a normal one so users don't know it came from the cache.
class CachedRequest : public Request {
public:
CachedRequest(RequestMethod method, std::string_view url, std::string_view name, bool *cancelled, RequestFlags flags, std::string_view responseData)
: Request(method, url, name, Path(), cancelled, flags)
CachedRequest(RequestMethod method, std::string_view url, std::string_view name, RequestFlags flags, std::string_view responseData)
: Request(method, url, name, Path(), flags)
{
buffer_.Append(responseData);
}
+2 -2
View File
@@ -16,7 +16,7 @@
namespace http {
HTTPSRequest::HTTPSRequest(RequestMethod method, std::string_view url, std::string_view postData, std::string_view postMime, const Path &outfile, RequestFlags flags, std::string_view name)
: Request(method, url, name, outfile, &cancelled_, flags), postData_(postData), postMime_(postMime) {
: Request(method, url, name, outfile, flags), postData_(postData), postMime_(postMime) {
}
HTTPSRequest::~HTTPSRequest() {
@@ -82,7 +82,7 @@ void HTTPSRequest::Start() {
// it once it finishes.
sink_ = std::make_unique<NaettBodySink>();
// In case someone managed to cancel us between construction and here.
sink_->cancelled = cancelled_;
sink_->cancelled = IsCancelled();
sink_->postData = postData_;
std::vector<naettOption *> options;
+3 -3
View File
@@ -10,8 +10,8 @@
namespace http {
Request::Request(RequestMethod method, std::string_view url, std::string_view name, const Path &outFile, bool *cancelled, RequestFlags flags)
: method_(method), url_(url), name_(name), outfile_(outFile), progress_(cancelled), flags_(flags) {
Request::Request(RequestMethod method, std::string_view url, std::string_view name, const Path &outFile, RequestFlags flags)
: method_(method), url_(url), name_(name), outfile_(outFile), progress_(&cancel_), flags_(flags) {
INFO_LOG(Log::HTTP, "HTTP %s request: %.*s (%.*s)", RequestMethodToString(method), (int)url.size(), url.data(), (int)name.size(), name.data());
progress_.callback = [this](int64_t bytes, int64_t contentLength, bool done) {
@@ -98,7 +98,7 @@ std::shared_ptr<Request> RequestManager::StartDownload(std::string_view url, con
if (File::ReadBinaryFileToString(cacheFile, &contents)) {
INFO_LOG(Log::HTTP, "Returning cached file for %.*s: %s", STR_VIEW(url), cacheFile.c_str());
// All is well, but we've indented a bit much here.
std::shared_ptr<Request> dl(new CachedRequest(RequestMethod::GET, url, KeepAfterLast(url, '/'), nullptr, flags, contents));
std::shared_ptr<Request> dl(new CachedRequest(RequestMethod::GET, url, KeepAfterLast(url, '/'), flags, contents));
newDownloads_.push_back(dl);
dl->SetCallback(completionCallback);
return dl;
+5 -4
View File
@@ -31,7 +31,7 @@ using RequestCompletionCallback = std::function<void(Request &)>;
// Abstract request.
class Request {
public:
Request(RequestMethod method, std::string_view url, std::string_view name, const Path &outFile, bool *cancelled, RequestFlags mode);
Request(RequestMethod method, std::string_view url, std::string_view name, const Path &outFile, RequestFlags mode);
virtual ~Request() {}
void SetAccept(const char *mime) {
@@ -77,8 +77,8 @@ public:
// Virtual so a backend can act on it. The HTTPS one has to tell naett, which is what actually
// stops a transfer in progress - see HTTPSRequest::Cancel.
virtual void Cancel() { cancelled_ = true; }
bool IsCancelled() const { return cancelled_; }
virtual void Cancel() { cancel_.Cancel(); }
bool IsCancelled() const { return cancel_.IsCancelled(); }
// If not downloading to a file, access this to get the result.
Buffer &buffer() { return buffer_; }
@@ -95,7 +95,8 @@ protected:
std::string userAgent_;
Path outfile_;
Buffer buffer_;
bool cancelled_ = false;
// Cancel() also wakes this request's socket waits.
net::CancelToken cancel_;
int resultCode_ = 0;
bool hasRunCallback_ = false;
std::vector<std::string> responseHeaders_;
+12 -23
View File
@@ -27,21 +27,16 @@ void RequestProgress::Update(int64_t downloaded, int64_t totalBytes, bool done)
}
}
bool Buffer::FlushSocket(uintptr_t sock, double timeout, bool *cancelled) {
static constexpr float CANCEL_INTERVAL = 0.25f;
bool Buffer::FlushSocket(uintptr_t sock, double timeout, const CancelToken *cancel) {
data_.iterate_blocks([&](const char *data, size_t size) {
for (size_t pos = 0, end = size; pos < end; ) {
bool ready = false;
double endTimeout = time_now_d() + timeout;
while (!ready) {
if (cancelled && *cancelled)
return false;
ready = fd_util::WaitUntilReady(sock, CANCEL_INTERVAL, true);
if (!ready && time_now_d() > endTimeout) {
ERROR_LOG(Log::IO, "FlushSocket timed out");
return false;
}
// The timeout applies to each send, not the whole flush.
const WaitResult wait = WaitSocket(sock, true, timeout, cancel);
if (wait == WaitResult::TIMEOUT) {
ERROR_LOG(Log::IO, "FlushSocket timed out");
return false;
} else if (wait != WaitResult::READY) {
return false;
}
int sent = send(sock, &data[pos], end - pos, MSG_NOSIGNAL);
// TODO: Do we need some retry logic here, instead of just giving up?
@@ -59,7 +54,6 @@ bool Buffer::FlushSocket(uintptr_t sock, double timeout, bool *cancelled) {
}
bool Buffer::ReadAllWithProgress(int fd, int knownSize, RequestProgress *progress) {
static constexpr float CANCEL_INTERVAL = 0.25f;
std::vector<char> buf;
// We're non-blocking and reading from an OS buffer, so try to read as much as we can at a time.
if (knownSize >= 65536 * 16) {
@@ -73,15 +67,10 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, RequestProgress *progres
double st = time_now_d();
int total = 0;
while (true) {
bool ready = false;
// If we might need to cancel, check on a timer for it to be ready.
// After this, we'll block on reading so we do this while first if we have a cancel pointer.
while (!ready && progress && progress->cancelled) {
if (*progress->cancelled)
return false;
ready = fd_util::WaitUntilReady(fd, CANCEL_INTERVAL, false);
}
// Block until there's data, the end of the stream, or a cancel.
const WaitResult wait = WaitSocket(fd, false, -1.0, progress ? progress->cancel : nullptr);
if (wait != WaitResult::READY)
return false;
int retval = recv(fd, &buf[0], buf.size(), MSG_NOSIGNAL);
if (retval == 0) {
+4 -3
View File
@@ -4,24 +4,25 @@
#include <functional>
#include "Common/Buffer.h"
#include "Common/Net/Cancel.h"
namespace net {
class RequestProgress {
public:
explicit RequestProgress(bool *c) : cancelled(c) {}
explicit RequestProgress(const CancelToken *c) : cancel(c) {}
void Update(int64_t downloaded, int64_t totalBytes, bool done);
float progress = 0.0f;
float kBps = 0.0f;
bool *cancelled = nullptr;
const CancelToken *cancel = nullptr;
std::function<void(int64_t, int64_t, bool)> callback;
};
class Buffer : public ::Buffer {
public:
bool FlushSocket(uintptr_t sock, double timeout, bool *cancelled = nullptr);
bool FlushSocket(uintptr_t sock, double timeout, const CancelToken *cancel = nullptr);
// If you know the size of the file to read, pass it in knownSize, for best performance and for progress reporting.
bool ReadAllWithProgress(int fd, int knownSize, RequestProgress *progress);
-1
View File
@@ -251,7 +251,6 @@ bool DNSResolve(const std::string &host, const std::string &service, addrinfo **
int result = getaddrinfo(host.c_str(), servicep, &hints, res);
if (result == EAI_AGAIN) {
// Temporary failure. Since this already blocks, let's just try once more.
sleep_ms(1, "dns-resolve-poll");
result = getaddrinfo(host.c_str(), servicep, &hints, res);
}
+3 -106
View File
@@ -4,7 +4,6 @@
#include <cstdint>
#include "Common/TimeUtil.h"
#include "Common/Data/Random/Rng.h"
#include "Common/Log.h"
#ifdef HAVE_LIBNX
@@ -17,18 +16,12 @@
#ifdef _WIN32
#include "CommonWindows.h"
#include <mmsystem.h>
#include <sys/timeb.h>
#else
#include <sys/time.h>
#include <unistd.h>
#endif
// for _mm_pause
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include <emmintrin.h>
#endif
#include <ctime>
// TODO: https://github.com/floooh/sokol/blob/9a6237fcdf213e6da48e4f9201f144bcb2dcb46f/sokol_time.h#L229-L248
@@ -47,10 +40,6 @@ static double frequencyMult;
static LARGE_INTEGER startTime;
static LARGE_INTEGER startFileTime;
HANDLE Timer;
int SchedulerPeriodMs = 10;
INT64 QpcPerSecond;
void TimeInit() {
FILETIME ft;
GetSystemTimeAsFileTime(&ft); //returns ticks in UTC
@@ -60,25 +49,11 @@ void TimeInit() {
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&startTime);
QpcPerSecond = frequency.QuadPart;
frequencyMult = 1.0 / frequency.QuadPart;
// The timer will be automatically deleted on process destruction. Don't need to CloseHandle.
Timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
// TODO: We probably don't need this anymore if we are using the high res waitable timers?
#if !PPSSPP_PLATFORM(UWP)
TIMECAPS caps;
timeGetDevCaps(&caps, sizeof caps);
timeBeginPeriod(caps.wPeriodMin);
SchedulerPeriodMs = (int)caps.wPeriodMin;
#endif
// No timeBeginPeriod: nothing sleeps, so the process's timer resolution is left alone.
}
void TimeShutdown() {
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
timeEndPeriod(1);
#endif
}
double time_now_d() {
@@ -123,10 +98,6 @@ double time_to_unix_utc(double timestamp) {
return (double)(li.QuadPart - UNIX_TIME_START + static_cast<int64_t>(timestamp * TICKS_PER_SECOND)) / TICKS_PER_SECOND;
}
void yield() {
YieldProcessor();
}
Instant::Instant() {
_dbg_assert_(frequencyMult != 0.0);
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&nativeStart_));
@@ -184,15 +155,6 @@ double time_now_unix_utc() {
return (double)tp.tv_sec + (double)tp.tv_nsec / 1000000000.0;
}
void yield() {
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
_mm_pause();
#elif PPSSPP_ARCH(ARM64)
// Took this out for now. See issue #17877
// __builtin_arm_isb(15);
#endif
}
Instant::Instant() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
@@ -254,8 +216,6 @@ double from_time_raw_relative(uint64_t raw_time) {
return from_time_raw(raw_time);
}
void yield() {}
double time_now_unix_utc() {
// Not time_now_raw() - that's a monotonic clock with no relation to the epoch.
struct timeval tv;
@@ -297,8 +257,8 @@ double Instant::ElapsedSeconds() const {
#endif
#define SLEEP_LOG_ENABLED 0
#ifndef __LIBRETRO__
// The headless tools and tests only; see TimeUtil.h.
void sleep_ms(int ms, const char *reason) {
if (ms <= 0) {
return;
@@ -334,63 +294,7 @@ void sleep_us(int us, const char *reason) {
usleep(us);
#endif
}
// This can be a little more expensive in some circumstances, so only use when necessary.
void sleep_precise(double seconds, const char *reason) {
if (seconds <= 0.0) {
return;
}
#if SLEEP_LOG_ENABLED
INFO_LOG(Log::System, "Sleep precise %f s: %s", seconds, reason);
#endif
#ifdef _WIN32
// Precise Windows sleep function from: https://github.com/blat-blatnik/Snippets/blob/main/precise_sleep.c
// Described in: https://blog.bearcats.nl/perfect-sleep-function/
LARGE_INTEGER qpc;
QueryPerformanceCounter(&qpc);
INT64 targetQpc = (INT64)(qpc.QuadPart + seconds * QpcPerSecond);
if (Timer) { // Try using a high resolution timer first.
const double TOLERANCE = 0.001'02;
INT64 maxTicks = (INT64)SchedulerPeriodMs * 9'500;
for (;;) // Break sleep up into parts that are lower than scheduler period.
{
double remainingSeconds = (targetQpc - qpc.QuadPart) / (double)QpcPerSecond;
INT64 sleepTicks = (INT64)((remainingSeconds - TOLERANCE) * 10'000'000);
if (sleepTicks <= 0)
break;
LARGE_INTEGER due;
due.QuadPart = -(sleepTicks > maxTicks ? maxTicks : sleepTicks);
// Note: SetWaitableTimerEx is not available on Vista.
if (!SetWaitableTimer(Timer, &due, 0, NULL, NULL, FALSE)) {
_dbg_assert_(false);
break;
}
WaitForSingleObject(Timer, INFINITE);
QueryPerformanceCounter(&qpc);
}
} else { // Fallback to Sleep.
const double TOLERANCE = 0.000'02;
double sleepMs = (seconds - TOLERANCE) * 1000 - SchedulerPeriodMs; // Sleep for 1 scheduler period less than requested.
int sleepSlices = (int)(sleepMs / SchedulerPeriodMs);
if (sleepSlices > 0)
Sleep((DWORD)sleepSlices * SchedulerPeriodMs);
QueryPerformanceCounter(&qpc);
}
while (qpc.QuadPart < targetQpc) // Spin for any remaining time.
{
YieldProcessor();
QueryPerformanceCounter(&qpc);
}
// On other platforms, we just do a conversion with more input precision than in sleep_ms which is restricted to whole milliseconds.
#elif defined(HAVE_LIBNX)
svcSleepThread((int64_t)(seconds * 1000000000.0));
#elif defined(__EMSCRIPTEN__)
emscripten_sleep(seconds * 1000.0);
#else
usleep(seconds * 1000000.0);
#endif
}
// Return the current time formatted as Minutes:Seconds:Milliseconds
// in the form 00:00:000.
@@ -408,10 +312,3 @@ void GetCurrentTimeFormatted(char formattedTime[13]) {
#endif
}
// We don't even bother synchronizing this, it's fine if threads stomp a bit.
static GMRng g_sleepRandom;
void sleep_random(double minSeconds, double maxSeconds, const char *reason) {
const double waitSeconds = minSeconds + (maxSeconds - minSeconds) * g_sleepRandom.F();
sleep_precise(waitSeconds, reason);
}
+4 -12
View File
@@ -19,20 +19,12 @@ double from_time_raw_relative(uint64_t raw_time);
double time_now_unix_utc();
double time_to_unix_utc(double timeNowSeconds);
// Sleep for milliseconds. Does not necessarily have millisecond granularity, especially on Windows.
// Requires a "reason" since sleeping generally should be very sparingly used. This
// can be logged if desired to figure out where we're wasting time.
#ifndef __LIBRETRO__
// Sleeps, for the standalone frontends, headless tools and tests. The core never sleeps and
// doesn't have this, so a sleep can't creep back into it.
void sleep_ms(int ms, const char *reason);
// Sleep for microseconds. Does not necessarily have microsecond granularity, especially on Windows.
void sleep_us(int us, const char *reason);
// Precise sleep. Can consume a little bit of CPU on Windows at least.
void sleep_precise(double seconds, const char *reason);
// Random sleep, used for debugging.
void sleep_random(double minSeconds, double maxSeconds, const char *reason);
// Yield. Signals that this thread is busy-waiting but wants to allow other hyperthreads to run.
void yield();
#endif
void GetCurrentTimeFormatted(char formattedTime[13]);
-1
View File
@@ -265,7 +265,6 @@ size_t HTTPFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags f
void HTTPFileLoader::Connect(double timeout) {
if (!connected_) {
cancel_ = false;
connected_ = client_.Connect(3, timeout, &cancel_);
}
}
+3 -2
View File
@@ -21,6 +21,7 @@
#include <vector>
#include "Common/File/Path.h"
#include "Common/Net/Cancel.h"
#include "Common/Net/HTTPClient.h"
#include "Common/Net/Resolve.h"
#include "Common/Net/URL.h"
@@ -47,7 +48,7 @@ public:
size_t ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags = Flags::NONE) override;
void Cancel() override {
cancel_ = true;
cancel_.Cancel();
}
std::string LatestError() const override {
@@ -74,7 +75,7 @@ private:
net::RequestProgress progress_;
::Path filename_;
bool connected_ = false;
bool cancel_ = false;
net::CancelToken cancel_;
const char *latestError_ = "";
std::once_flag preparedFlag_;
+71 -13
View File
@@ -54,10 +54,11 @@
#include "Core/HLE/sceNetAdhoc.h"
#include "Core/Instance.h"
#include "proAdhoc.h"
#include "Common/Net/Cancel.h"
#include "Core/HLE/NetAdhocCommon.h"
#include "ext/aemu_postoffice/client/postoffice_client.h"
#include "ext/aemu_postoffice_client/postoffice_client.h"
#ifdef _WIN32
#undef errno
@@ -110,6 +111,32 @@ std::atomic<int> metasocket((int)INVALID_SOCKET);
SceNetAdhocctlParameter parameter;
SceNetAdhocctlAdhocId product_code;
std::thread friendFinderThread;
// Wakes the friend finder's wait when the game side changes something it acts on.
// Made once before the first start and kept, so a wake never races a free.
static net::WakeSocket *friendFinderWake;
void FriendFinderPrepare() {
if (!friendFinderWake)
friendFinderWake = new net::WakeSocket();
}
void FriendFinderWake() {
net::WakeSocket *wake = friendFinderWake;
if (wake)
wake->Wake();
}
void JoinPostofficeConnect(AdhocSocket *sock, bool cancel) {
if (sock->connectThread != NULL) {
if (cancel && sock->connectCancel != NULL)
sock->connectCancel->Cancel();
sock->connectThread->join();
delete sock->connectThread;
sock->connectThread = NULL;
}
delete sock->connectCancel;
sock->connectCancel = NULL;
}
std::recursive_mutex peerlock;
AdhocSocket* adhocSockets[MAX_SOCKET];
bool isOriPort = false;
@@ -435,10 +462,7 @@ void deleteAllAdhocSockets() {
if (serverHasRelay) {
if (sock->type == SOCK_PTP) {
// sync
if (sock->connectThread != NULL) {
sock->connectThread->join();
delete sock->connectThread;
}
JoinPostofficeConnect(sock, true);
void *socket = sock->postofficeHandle;
if (socket != NULL) {
if (sock->data.ptp.state == ADHOC_PTP_STATE_LISTEN) {
@@ -1403,6 +1427,9 @@ int friendFinder() {
// Finder Loop. The flag was set by whoever started us, and cleared to stop us.
while (friendFinderRunning) {
// Bytes buffered before this pass handled a packet.
int rxQueued = 0;
// Acquire Network Lock
//_acquireNetworkLock();
@@ -1491,6 +1518,7 @@ int friendFinder() {
}
// Handle Packets
rxQueued = rxpos;
if (rxpos > 0) {
// BSSID Packet
if (rx[0] == OPCODE_CONNECT_BSSID) {
@@ -1767,12 +1795,31 @@ int friendFinder() {
}
}
}
// This delay time should be 100ms when there is an event otherwise 500ms ?
sleep_ms(10, "pro-adhoc-poll-2"); // Using 1ms for faster response just like AdhocServer?
// A pass handles one packet; another whole one may already be buffered.
if (rxpos > 0 && rxpos < rxQueued)
continue;
// Don't do anything if it's paused, otherwise the log will be flooded
while (Core_IsStepping() && coreState != CORE_POWERDOWN && friendFinderRunning)
sleep_ms(10, "pro-adhoc-paused-poll-2");
// Wait for the server, the game side (FriendFinderWake), or the next ping or game mode deadline.
uintptr_t sock = (uintptr_t)(intptr_t)-1;
bool forWrite = false;
double timeout = -1.0;
if (g_adhocServerConnected && metasocket != (int)INVALID_SOCKET) {
sock = (uintptr_t)(intptr_t)metasocket;
const u64 t = (u64)(time_now_d() * 1000000.0);
s64 left = PSP_ADHOCCTL_PING_TIMEOUT - static_cast<s64>(t - lastping);
// A ping that is due but couldn't go out waits for the socket to take it.
forWrite = left <= 0;
if (isAdhocctlBusy && adhocctlState == ADHOCCTL_STATE_DISCONNECTED && adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE && netAdhocGameModeEntered)
left = std::min(left, (s64)netAdhocEnterGameModeTimeout - static_cast<s64>(t - adhocctlStartTime) + 1);
timeout = forWrite ? -1.0 : std::max(left, (s64)0) / 1000000.0;
}
const net::WaitResult wait = net::WaitSocketsOrWake(&sock, nullptr, 1, forWrite, timeout, friendFinderWake);
if (wait == net::WaitResult::FAILED && sock == (uintptr_t)(intptr_t)-1) {
ERROR_LOG(Log::sceNet, "FriendFinder: nothing to wait on (no wake socket), stopping");
break;
}
if (friendFinderWake)
friendFinderWake->Drain();
}
// Groups/Networks should be deallocated isn't?
@@ -2248,9 +2295,17 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){
u64 startTime = (u64)(time_now_d() * 1000000.0);
bool done = false;
while (!done) {
if (coreState == CORE_POWERDOWN)
if (coreState == CORE_POWERDOWN || !friendFinderRunning)
return iResult;
// Writable, the timeout, or a wake to recheck the above.
const u64 waited = (u64)(time_now_d() * 1000000.0) - startTime;
uintptr_t sock = (uintptr_t)(intptr_t)metasocket;
const double left = waited < (u64)adhocDefaultTimeout ? (adhocDefaultTimeout - waited) / 1000000.0 : 0.0;
if (net::WaitSocketsOrWake(&sock, nullptr, 1, true, left, friendFinderWake) == net::WaitResult::CANCELLED) {
friendFinderWake->Drain();
continue;
}
done = (IsSocketReady((int)metasocket, false, true) > 0);
if (done) {
// Writable can also mean the attempt failed (refused, say). Then there's no point
@@ -2266,14 +2321,17 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){
socklen_t sinlen = sizeof(sin);
memset(&sin, 0, sinlen);
// Ensure that the connection really established or not, since "select" alone can't accurately detects it
const bool writable = done;
done &= (getpeername((int)metasocket, (struct sockaddr*)&sin, &sinlen) != SOCKET_ERROR);
// Writable but not connected is a failed attempt, and would stay writable.
if (writable && !done)
break;
u64 now = (u64)(time_now_d() * 1000000.0);
if (static_cast<s64>(now - startTime) > adhocDefaultTimeout) {
if (static_cast<s64>(now - startTime) >= adhocDefaultTimeout) {
if (connectInProgress(errorcode))
errorcode = ETIMEDOUT;
break;
}
sleep_ms(10, "pro-adhoc-socket-poll");
}
if (!done) {
ERROR_LOG(Log::sceNet, "Socket error (%i) when connecting to AdhocServer [%s/%s:%u]", errorcode, g_Config.sProAdhocServer.c_str(), ip2str(g_adhocServerIP.in.sin_addr).c_str(), ntohs(g_adhocServerIP.in.sin_port));
+13 -7
View File
@@ -39,6 +39,8 @@
#include "Core/HLE/sceKernelMutex.h"
#include "Core/HLE/sceUtility.h"
namespace net { class CancelToken; }
#define IsMatch(buf1, buf2) (memcmp(&buf1, &buf2, sizeof(buf1)) == 0)
// Server Listening Port
@@ -331,10 +333,15 @@ typedef struct AdhocSocket {
} data;
void *postofficeHandle; // aemu_postoffice mode handle
std::thread *connectThread;
// Cancelled before a join, so closing doesn't wait out a connect.
net::CancelToken *connectCancel;
bool connectThreadDone;
int connectThreadResult;
} PACK AdhocSocket;
// Joins a postoffice socket's connect thread (cancelling it first if asked) and frees it.
void JoinPostofficeConnect(AdhocSocket *sock, bool cancel);
// Gamemode Optional Peer Buffer Data
typedef struct SceNetAdhocGameModeOptData {
u32_le size;
@@ -496,16 +503,12 @@ typedef struct SceNetAdhocMatchingContext {
//HLEHelperThread *matchingThread;
int matching_thid;
// Event Caller Thread
std::thread eventThread;
//s32_le event_thid;
// Event and input loops, run by a CoreTiming tick on the emulator thread.
bool eventRunning = false;
bool IsMatchingInCB = false;
// IO Handler Thread
std::thread inputThread;
//s32_le input_thid;
bool inputRunning = false;
u64_le inputLastPing = 0;
u64_le inputLastHello = 0;
// Event Caller Thread Message Stack
std::recursive_mutex *eventlock; // s32_le event_stack_lock;
@@ -772,6 +775,9 @@ extern std::atomic<int> metasocket;
extern SceNetAdhocctlParameter parameter;
extern SceNetAdhocctlAdhocId product_code;
extern std::thread friendFinderThread;
// Make the wake before starting the thread; wake it after changing what it acts on.
void FriendFinderPrepare();
void FriendFinderWake();
extern std::recursive_mutex peerlock;
extern AdhocSocket* adhocSockets[MAX_SOCKET];
+41 -6
View File
@@ -26,6 +26,7 @@
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <vector>
#include <signal.h>
#include <sys/types.h>
@@ -37,6 +38,7 @@
#include "Common/File/FileUtil.h"
#include "Common/TimeUtil.h"
#include "Common/Net/Resolve.h"
#include "Common/Net/Cancel.h"
#include "Core/Util/PortManager.h"
#include "Core/Instance.h"
#include "Core/Core.h"
@@ -60,6 +62,19 @@ SceNetAdhocctlGameNode * _db_game = NULL;
// Server Status
std::atomic<bool> adhocServerRunning(false);
std::thread adhocServerThread;
// Made once before the first start and kept, so a wake never races a free.
static net::WakeSocket *adhocServerWake;
void AdhocServerPrepare() {
if (!adhocServerWake)
adhocServerWake = new net::WakeSocket();
}
void AdhocServerWake() {
net::WakeSocket *wake = adhocServerWake;
if (wake)
wake->Wake();
}
// Crosslink database for cross region Adhoc play
std::vector<db_crosslink> crosslinks;
@@ -2064,13 +2079,33 @@ int server_loop(int server)
user = next;
}
// Prevent needless CPU Overload (1ms Sleep)
// !!!! COMMENT NOT REFLECTING REALITY
sleep_ms(10, "pro-adhoc-poll");
// A pass handles one packet per user; another whole one may already be buffered.
bool more = false;
std::vector<uintptr_t> socks;
socks.push_back((uintptr_t)(intptr_t)server);
time_t nextTimeout = 0;
const time_t now = time(NULL);
for (SceNetAdhocctlUserNode *u = _db_user; u != NULL; u = u->next) {
if (u->rxpos > 0 && u->rxpos != u->rxseen)
more = true;
u->rxseen = u->rxpos;
socks.push_back((uintptr_t)(intptr_t)u->stream);
const time_t left = u->last_recv + SERVER_USER_TIMEOUT - now;
if (nextTimeout == 0 || left < nextTimeout)
nextTimeout = left < 1 ? 1 : left;
}
if (more)
continue;
// Don't do anything if it's paused, otherwise the log will be flooded
while (adhocServerRunning && Core_IsStepping() && coreState != CORE_POWERDOWN)
sleep_ms(10, "pro-adhot-paused-poll");
// Wait for a login, data from a user, the stop wake, or the first user timeout.
const double timeout = nextTimeout > 0 ? (double)nextTimeout : -1.0;
const net::WaitResult wait = net::WaitSocketsOrWake(socks.data(), nullptr, (int)socks.size(), false, timeout, adhocServerWake);
if (wait == net::WaitResult::FAILED) {
ERROR_LOG(Log::sceNet, "AdhocServer: wait failed, stopping");
break;
}
if (adhocServerWake)
adhocServerWake->Drain();
}
// Free User Database Memory
+5
View File
@@ -220,6 +220,8 @@ typedef struct SceNetAdhocctlUserNode {
// RX Buffer
uint8_t rx[1024];
uint32_t rxpos;
// rxpos at the end of the last pass: a change means another packet may be buffered.
uint32_t rxseen;
} SceNetAdhocctlUserNode;
// Double-Linked Game List
@@ -371,3 +373,6 @@ int proAdhocServerThread(int port); // (int argc, char * argv[])
//extern int _status;
extern std::atomic<bool> adhocServerRunning;
extern std::thread adhocServerThread;
// Make the wake before starting the thread; wake it after clearing adhocServerRunning.
void AdhocServerPrepare();
void AdhocServerWake();
+7 -8
View File
@@ -92,7 +92,6 @@ HTTPRequest::HTTPRequest(int connectionID, int method, const char *url, u64 cont
this->url = url ? url : "";
this->contentLength = contentLength;
//progress_.cancelled = &cancelled_;
responseContent_.clear();
}
@@ -121,7 +120,7 @@ int HTTPRequest::getResponseContentLength() {
}
int HTTPRequest::abortRequest() {
cancelled_ = true;
cancel_.Cancel();
// FIXME: Will sceHttpAbortRequest returns an error if the request was not sent yet?
//if (progress_.progress == 0.0f)
// return SCE_HTTP_ERROR_BEFORE_SEND;
@@ -238,11 +237,11 @@ int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) {
}
// Establish Connection
if (!client.Connect(getResolveRetryCount(), getConnectTimeout() / 1000000.0, &cancelled_)) {
if (!client.Connect(getResolveRetryCount(), getConnectTimeout() / 1000000.0, &cancel_)) {
ERROR_LOG(Log::sceNet, "Failed connecting to server or cancelled.");
return -1; // SCE_HTTP_ERROR_ABORTED
}
if (cancelled_) {
if (cancel_.IsCancelled()) {
return SCE_HTTP_ERROR_ABORTED;
}
@@ -259,13 +258,13 @@ int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) {
break;
}
net::Buffer buffer_;
net::RequestProgress progress_(&cancelled_);
net::RequestProgress progress_(&cancel_);
http::RequestParams req(fileUrl.Resource(), "*/*");
const char* postData = Memory::GetCharPointer(postDataPtr);
if (postDataSize > 0)
NotifyMemInfo(MemBlockFlags::READ, postDataPtr, postDataSize, "HttpSendRequest");
int err = client.SendRequestWithData(methodstr.c_str(), req, std::string(postData ? postData : "", postData ? postDataSize : 0), extraHeaders.c_str(), &progress_);
if (cancelled_) {
if (cancel_.IsCancelled()) {
return SCE_HTTP_ERROR_ABORTED;
}
if (err < 0) {
@@ -274,7 +273,7 @@ int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) {
// Retrieve Response's Status Code (and Headers too?)
responseCode_ = client.ReadResponseHeaders(&buffer_, responseHeaders_, &progress_, &httpLine_);
if (cancelled_) {
if (cancel_.IsCancelled()) {
return SCE_HTTP_ERROR_ABORTED;
}
@@ -285,7 +284,7 @@ int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) {
ERROR_LOG(Log::sceNet, "Unable to read HTTP response entity: %d", res);
}
entity_.TakeAll(&responseContent_);
if (cancelled_) {
if (cancel_.IsCancelled()) {
return SCE_HTTP_ERROR_ABORTED;
}
+2 -2
View File
@@ -257,12 +257,12 @@ private:
u32 headerAddr_ = 0;
u32 headerSize_ = 0;
bool cancelled_ = false;
// sceHttpAbortRequest sets it; it also wakes the socket waits.
net::CancelToken cancel_;
int responseCode_ = -1;
int entityLength_ = -1;
http::Client client;
//net::RequestProgress progress_(&cancelled_);
std::vector<std::string> responseHeaders_;
std::string httpLine_;
std::string responseContent_;
+4 -1
View File
@@ -656,6 +656,7 @@ void __NetInit() {
__ResetInitNetLib();
__NetApctlInit();
__NetCallbackInit();
__NetInetInit();
}
void __NetApctlShutdown() {
@@ -708,7 +709,7 @@ void netValidateLoopMemory() {
// This feels like a dubious proposition, mostly...
void __NetDoState(PointerWrap &p) {
auto s = p.Section("sceNet", 1, 7);
auto s = p.Section("sceNet", 1, 8);
if (!s)
return;
@@ -777,6 +778,8 @@ void __NetDoState(PointerWrap &p) {
apctlProdCodeAddr = 0;
}
__NetInetDoWaitEvent(p, s >= 8);
if (p.mode == p.MODE_READ) {
// Let's not change "Inited" value when Loading SaveState in the middle of multiplayer to prevent memory & port leaks
g_netApctlInited = cur_netApctlInited;
+27 -13
View File
@@ -59,7 +59,8 @@
#include "Core/HLE/KernelWaitHelpers.h"
#include "Core/HLE/NetAdhocCommon.h"
#include "ext/aemu_postoffice/client/postoffice_client.h"
#include "Common/Net/Cancel.h"
#include "ext/aemu_postoffice_client/postoffice_client.h"
#ifdef _WIN32
#undef errno
@@ -314,6 +315,7 @@ void deleteMatchingEvents(const int matchingId = -1);
void discardMatchingEvents();
void DoNetAdhocMatchingInited(PointerWrap &p);
void DoNetAdhocMatchingThreads(PointerWrap &p);
void DoNetAdhocMatchingTick(PointerWrap &p, bool present);
void ZeroNetAdhocMatchingThreads();
void SaveNetAdhocMatchingInited();
void RestoreNetAdhocMatchingInited();
@@ -325,6 +327,7 @@ bool __NetAdhocConnected() {
void __NetAdhocShutdown() {
// Kill AdhocServer Thread
adhocServerRunning = false;
AdhocServerWake();
if (adhocServerThread.joinable()) {
adhocServerThread.join();
}
@@ -623,6 +626,7 @@ int WaitBlockingAdhocctlSocket(AdhocctlRequest request, int usec, const char* re
u64 param = ((u64)__KernelGetCurThread()) << 32 | uid;
adhocctlStartTime = (u64)(time_now_d() * 1000000.0);
FriendFinderWake();
adhocctlRequests[uid] = request;
CoreTiming::ScheduleEvent(usToCycles(usec), adhocctlNotifyEvent, param);
__KernelWaitCurThread(WAITTYPE_NET, uid, request.opcode, 0, false, reason);
@@ -1318,6 +1322,7 @@ static int ptp_accept_postoffice(int idx, SceNetEtherAddr *saddr, uint16_t *spor
internal->data.ptp.id = AEMU_POSTOFFICE_ID_BASE + idx;
internal->flags = 0;
internal->connectThread = NULL;
internal->connectCancel = NULL;
AdhocSocket **slot = NULL;
int i;
@@ -1425,20 +1430,19 @@ static int ptp_connect_postoffice(int idx, const char *caller) {
}
if (internal->connectThreadDone) {
if (internal->connectThread != NULL) {
internal->connectThread->join();
delete internal->connectThread;
internal->connectThread = NULL;
}
// The last attempt is over, so this join returns at once.
JoinPostofficeConnect(internal, false);
internal->connectThreadDone = false;
internal->connectThreadResult = 0;
internal->connectThread = new std::thread([internal, addr, idx] {
internal->connectCancel = new net::CancelToken();
const int cancelFd = (int)internal->connectCancel->WakeFd();
internal->connectThread = new std::thread([internal, addr, idx, cancelFd] {
int state;
SceNetEtherAddr fixed_daddr = internal->data.ptp.paddr;
fixGameMac(&fixed_daddr);
void *ptp_socket = ptp_connect_v4(&addr, (const char *)&internal->data.ptp.laddr, offset_port_simple(internal->data.ptp.lport), (const char *)&fixed_daddr, offset_port_simple(internal->data.ptp.pport), &state);
void *ptp_socket = ptp_connect_v4(&addr, (const char *)&internal->data.ptp.laddr, offset_port_simple(internal->data.ptp.lport), (const char *)&fixed_daddr, offset_port_simple(internal->data.ptp.pport), &state, cancelFd);
if (ptp_socket == NULL) {
internal->connectThreadResult = SCE_NET_ADHOC_ERROR_CONNECTION_REFUSED;
ERROR_LOG(Log::sceNet, "%s: failed connecting to ptp socket, %d", __func__, state);
@@ -1786,7 +1790,7 @@ int WaitBlockingAdhocSocket(u64 threadSocketId, int type, int pspSocketId, void*
}
void __NetAdhocDoState(PointerWrap &p) {
auto s = p.Section("sceNetAdhoc", 1, 9);
auto s = p.Section("sceNetAdhoc", 1, 10);
if (!s)
return;
@@ -1876,6 +1880,7 @@ void __NetAdhocDoState(PointerWrap &p) {
} else if (p.mode == p.MODE_READ) {
adhocctlRequests.clear();
}
DoNetAdhocMatchingTick(p, s >= 10);
if (p.mode == p.MODE_READ) {
// Discard leftover events
@@ -1925,10 +1930,12 @@ void __NetAdhocInit() {
// Create built-in AdhocServer Thread. The flag is set here rather than by the thread, so that a
// shutdown that clears it before the thread gets going can't be undone (see friendFinder).
adhocServerRunning = false;
AdhocServerWake();
if (adhocServerThread.joinable()) {
adhocServerThread.join();
}
if (g_Config.bEnableWlan && g_Config.bEnableAdhocServer) {
AdhocServerPrepare();
adhocServerRunning = true;
adhocServerThread = std::thread(proAdhocServerThread, SERVER_PORT);
}
@@ -2004,6 +2011,7 @@ int sceNetAdhocctlInit(int stackSize, int prio, u32 productAddr) {
netAdhocctlInited = true; //needed for cleanup during AdhocctlTerm even when it failed to connect to Adhoc Server (since it's being faked as success)
g_adhocServerLoginFailed = false;
isAdhocctlNeedLogin = true;
FriendFinderWake();
// Create fake PSP Thread for callback
// TODO: Should use a separated threads for friendFinder, matchingEvent, and matchingInput and created on AdhocctlInit & AdhocMatchingStart instead of here
@@ -2020,6 +2028,7 @@ int sceNetAdhocctlInit(int stackSize, int prio, u32 productAddr) {
if (friendFinderThread.joinable()) {
friendFinderThread.join();
}
FriendFinderPrepare();
friendFinderRunning = true;
friendFinderThread = std::thread(friendFinder);
}
@@ -3195,6 +3204,7 @@ int sceNetAdhocctlScan() {
isAdhocctlBusy = true;
g_adhocServerLoginFailed = false;
isAdhocctlNeedLogin = true;
FriendFinderWake();
adhocctlState = ADHOCCTL_STATE_SCANNING;
adhocctlCurrentMode = ADHOCCTL_MODE_NORMAL;
@@ -3490,6 +3500,7 @@ int NetAdhocctl_Term() {
// Terminate Adhoc Threads
friendFinderRunning = false;
FriendFinderWake();
if (friendFinderThread.joinable()) {
friendFinderThread.join();
}
@@ -3689,6 +3700,7 @@ int NetAdhocctl_Create(const char *groupName) {
isAdhocctlBusy = true;
g_adhocServerLoginFailed = false;
isAdhocctlNeedLogin = true;
FriendFinderWake();
// Set Network Name
if (groupName) {
@@ -3709,6 +3721,7 @@ int NetAdhocctl_Create(const char *groupName) {
//Faking success, to prevent Full Auto 2 from freezing while Initializing Network
else {
adhocctlStartTime = (u64)(time_now_d() * 1000000.0);
FriendFinderWake();
if (adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE) {
adhocctlState = ADHOCCTL_STATE_GAMEMODE;
notifyAdhocctlHandlers(ADHOCCTL_EVENT_GAME, 0);
@@ -3842,6 +3855,7 @@ int NetAdhocctl_CreateEnterGameMode(const char* group_name, int game_type, int n
adhocConnectionType = ADHOC_CREATE;
netAdhocGameModeEntered = true;
netAdhocEnterGameModeTimeout = timeout;
FriendFinderWake();
return NetAdhocctl_Create(group_name);
}
@@ -3904,6 +3918,7 @@ static int sceNetAdhocctlJoinEnterGameMode(const char * group_name, const char *
adhocConnectionType = ADHOC_JOIN;
netAdhocGameModeEntered = true;
netAdhocEnterGameModeTimeout = timeout;
FriendFinderWake();
return hleLogDebug(Log::sceNet, NetAdhocctl_Create(group_name));
}
@@ -4320,6 +4335,7 @@ static int ptp_open_postoffice(const SceNetEtherAddr *saddr, uint16_t sport, con
internal->data.ptp.snd_sb_cc = 0;
internal->flags = 0;
internal->connectThread = NULL;
internal->connectCancel = NULL;
internal->connectThreadDone = true;
internal->lastAttempt = 0;
internal->internalLastAttempt = 0;
@@ -4918,10 +4934,7 @@ static int ptp_close_postoffice(int idx){
AdhocSocket *internal = adhocSockets[idx];
// sync
if (internal->connectThread != NULL) {
internal->connectThread->join();
delete internal->connectThread;
}
JoinPostofficeConnect(internal, true);
void *socket = internal->postofficeHandle;
if (socket != NULL) {
@@ -5015,6 +5028,7 @@ static int ptp_listen_postoffice(const SceNetEtherAddr *saddr, uint16_t sport, u
internal->data.ptp.snd_sb_cc = 0;
internal->flags = 0;
internal->connectThread = NULL;
internal->connectCancel = NULL;
internal->lastAttempt = 0;
internal->internalLastAttempt = 0;
+337 -388
View File
@@ -1151,413 +1151,361 @@ void actOnByePacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * send
* @param argp SceNetAdhocMatchingContext *
* @return Exit Point is never reached...
*/
int matchingEventThread(int matchingId) {
SetCurrentThreadName("MatchingEvent");
// Multithreading Lock
peerlock.lock();
// Cast Context
SceNetAdhocMatchingContext * context = findMatchingContext(matchingId);
// Multithreading Unlock
peerlock.unlock();
// The matching event and input loops run as one tick of emulated time on the
// emulator thread, instead of host threads polling the wall clock.
static constexpr int MATCHING_TICK_US = 10000;
static int matchingTickEvent = -1;
// Log Startup
INFO_LOG(Log::sceNet, "EventLoop: Begin of EventLoop[%i] Thread", matchingId);
// Hands every queued event to the game's handler.
static void MatchingEventPass(SceNetAdhocMatchingContext *context, int matchingId) {
u32 bufLen = context->rxbuflen;
u32 bufAddr = 0;
u32_le *args = context->handlerArgs;
// Run while needed...
if (context != NULL) {
u32 bufLen = context->rxbuflen; //0;
u32 bufAddr = 0; //= userMemory.Alloc(bufLen); //context->rxbuf;
u32_le * args = context->handlerArgs; //MatchingArgs
// Messages on Stack ready for processing
while (context != NULL && context->event_stack != NULL) {
// Claim Stack
context->eventlock->lock();
while (contexts != NULL && context->eventRunning) {
// Multithreading Lock
peerlock.lock();
// Cast Context
context = findMatchingContext(matchingId);
// Multithreading Unlock
peerlock.unlock();
// Iterate Message List
ThreadMessage * msg = context->event_stack;
if (msg != NULL) {
// Default Optional Data
void* opt = NULL;
// Messages on Stack ready for processing
while (context != NULL && context->event_stack != NULL) {
// Claim Stack
context->eventlock->lock();
// Grab Optional Data
if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); //&msg[1]
// Iterate Message List
ThreadMessage * msg = context->event_stack;
if (msg != NULL) {
// Default Optional Data
void* opt = NULL;
// Log Matching Events
INFO_LOG(Log::sceNet, "EventLoop[%d]: Matching Event [%d=%s][%s] OptSize=%d", matchingId, msg->opcode, getMatchingEventStr(msg->opcode), mac2str(&msg->mac).c_str(), msg->optlen);
// Grab Optional Data
if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); //&msg[1]
// Unlock to prevent race-condition with other threads due to recursive lock
//context->eventlock->unlock();
// Call Event Handler
//context->handler(context->id, msg->opcode, &msg->mac, msg->optlen, opt);
// Notify Event Handlers
notifyMatchingHandler(context, msg, opt, bufAddr, bufLen, args); // If we're using shared Buffer & Args for All Events We should wait for the Mipscall to be fully executed before processing the next event. GTA VCS need this delay/sleep.
// Log Matching Events
INFO_LOG(Log::sceNet, "EventLoop[%d]: Matching Event [%d=%s][%s] OptSize=%d", matchingId, msg->opcode, getMatchingEventStr(msg->opcode), mac2str(&msg->mac).c_str(), msg->optlen);
// Give some time before executing the next mipscall to prevent event ACCEPT(6)->ESTABLISH(7) getting reversed After Action ESTABLISH(7)->ACCEPT(6)
// Must Not be delayed too long to prevent desync/disconnect. Not longer than the delays on callback's HLE?
//sleep_ms(10); //sceKernelDelayThread(10000);
// Unlock to prevent race-condition with other threads due to recursive lock
//context->eventlock->unlock();
// Call Event Handler
//context->handler(context->id, msg->opcode, &msg->mac, msg->optlen, opt);
// Notify Event Handlers
notifyMatchingHandler(context, msg, opt, bufAddr, bufLen, args); // If we're using shared Buffer & Args for All Events We should wait for the Mipscall to be fully executed before processing the next event. GTA VCS need this delay/sleep.
// Lock again
//context->eventlock->lock();
// Give some time before executing the next mipscall to prevent event ACCEPT(6)->ESTABLISH(7) getting reversed After Action ESTABLISH(7)->ACCEPT(6)
// Must Not be delayed too long to prevent desync/disconnect. Not longer than the delays on callback's HLE?
//sleep_ms(10); //sceKernelDelayThread(10000);
// Lock again
//context->eventlock->lock();
// Pop event stack from front (this should be queue instead of stack?)
context->event_stack = msg->next;
free(msg);
msg = NULL;
}
// Unlock Stack
context->eventlock->unlock();
}
// Share CPU Time
sleep_ms(10, "pro-adhoc-poll-3"); //1 //sceKernelDelayThread(10000);
// Don't do anything if it's paused, otherwise the log will be flooded
while (Core_IsStepping() && coreState != CORE_POWERDOWN && contexts != NULL && context->eventRunning)
sleep_ms(10, "pro-adhoc-event-poll-3");
// Pop event stack from front (this should be queue instead of stack?)
context->event_stack = msg->next;
free(msg);
msg = NULL;
}
// Process Last Messages
if (contexts != NULL && context->event_stack != NULL) {
// Unlock Stack
context->eventlock->unlock();
}
}
// What the event loop did on its way out: the last queued events.
static void MatchingEventFinish(SceNetAdhocMatchingContext *context, int matchingId) {
u32 bufLen = context->rxbuflen;
u32 bufAddr = 0;
u32_le *args = context->handlerArgs;
// Process Last Messages
if (contexts != NULL && context->event_stack != NULL) {
// Claim Stack
context->eventlock->lock();
// Iterate Message List
int msg_count = 0;
ThreadMessage * msg = context->event_stack;
for (; msg != NULL; msg = msg->next) {
// Default Optional Data
void * opt = NULL;
// Grab Optional Data
if (msg->optlen > 0) opt = ((u8 *)msg) + sizeof(ThreadMessage); //&msg[1]
INFO_LOG(Log::sceNet, "EventLoop[%d]: Matching Event [EVENT=%d]\n", matchingId, msg->opcode);
//context->eventlock->unlock();
// Original Call Event Handler
//context->handler(context->id, msg->opcode, &msg->mac, msg->optlen, opt);
// Notify Event Handlers
notifyMatchingHandler(context, msg, opt, bufAddr, bufLen, args);
//context->eventlock->lock();
msg_count++;
}
// Clear Event Message Stack
clearStack(context, PSP_ADHOC_MATCHING_EVENT_STACK);
// Free Stack
context->eventlock->unlock();
INFO_LOG(Log::sceNet, "EventLoop[%d]: Finished (%d msg)", matchingId, msg_count);
}
}
// Hello and keepalive timers, queued sends, and every datagram that is waiting.
static void MatchingInputPass(SceNetAdhocMatchingContext *context, int matchingId) {
auto n = GetI18NCategory(I18NCat::NETWORKING);
u64_le &lastping = context->inputLastPing;
u64_le &lasthello = context->inputLastHello;
u64_le now;
SceNetEtherAddr sendermac;
u32_le senderport;
int rxbuflen;
while (context->inputRunning) {
now = CoreTiming::GetGlobalTimeUsScaled(); //time_now_d()*1000000.0;
// Hello Message Sending Context with unoccupied Slots
if ((context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && (countChildren(context) < (context->maxpeers - 1))) || (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && findP2P(context) == NULL)) {
// Hello Message Broadcast necessary because of Hello Interval
if (context->hello_int > 0)
if (static_cast<s64>(now - lasthello) >= static_cast<s64>(context->hello_int)) {
// Broadcast Hello Message
broadcastHelloMessage(context);
// Update Hello Timer
lasthello = now;
}
}
// Ping Required
if (context->keepalive_int > 0) {
if (static_cast<s64>(now - lastping) >= static_cast<s64>(context->keepalive_int)) {
// Handle Peer Timeouts
handleTimeout(context);
// Broadcast Ping Message
broadcastPingMessage(context);
// Update Ping Timer
lastping = now;
}
}
else {
// FIXME: Should we checks for Timeout too when the game doesn't set the keep alive interval?
handleTimeout(context);
}
// Messages on Stack ready for processing
if (context->input_stack != NULL) {
// Claim Stack
context->eventlock->lock();
context->inputlock->lock();
// Iterate Message List
ThreadMessage* msg = context->input_stack;
while (msg != NULL) {
// Default Optional Data
void* opt = NULL;
// Grab Optional Data
if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage);
//context->inputlock->unlock(); // Unlock to prevent race condition when locking peerlock
// Send Accept Packet
if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_ACCEPT) sendAcceptPacket(context, &msg->mac, msg->optlen, opt);
// Send Join Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_JOIN) sendJoinPacket(context, &msg->mac, msg->optlen, opt);
// Send Cancel Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_CANCEL) sendCancelPacket(context, &msg->mac, msg->optlen, opt);
// Send Bulk Data Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK) sendBulkDataPacket(context, &msg->mac, msg->optlen, opt);
// Send Birth Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BIRTH) sendBirthPacket(context, &msg->mac);
// Send Death Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_DEATH) sendDeathPacket(context, &msg->mac);
// Cancel Bulk Data Transfer (does nothing as of now as we fire and forget anyway) // Do we need to check DeathPacket and ByePacket here?
//else if(msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) sendAbortBulkDataPacket(context, &msg->mac, msg->optlen, opt);
//context->inputlock->lock(); // Lock again
// Pop input stack from front (this should be queue instead of stack?)
context->input_stack = msg->next;
free(msg);
msg = context->input_stack;
}
// Free Stack
context->inputlock->unlock();
}
// Receive PDP Datagram
// FIXME: When using JPCSP + prx files, the "SceNetAdhocMatchingInput" thread is using blocking PdpRecv with infinite(0) timeout, which can be stopped/aborted using SetSocketAlert, while "SceNetAdhocMatchingEvent" thread is using non-blocking for sending
rxbuflen = context->rxbuflen;
senderport = 0;
// Lock the peer first before locking the socket to avoid race condiion
peerlock.lock();
context->socketlock->lock();
int recvresult = sceNetAdhocPdpRecv(context->socket, &sendermac, &senderport, context->rxbuf, &rxbuflen, 0, ADHOC_F_NONBLOCK);
context->socketlock->unlock();
peerlock.unlock();
// Received Data from a Sender that interests us
// Note: There are cases where the sender port might be re-mapped by router or ISP, so we shouldn't check the source port.
if (recvresult == 0 && rxbuflen > 0) {
// Log Receive Success
if (context->rxbuf[0] > 1) {
INFO_LOG(Log::sceNet, "InputLoop[%d]: Received %d Bytes (Opcode[%d]=%s)", matchingId, rxbuflen, context->rxbuf[0], getMatchingOpcodeStr(context->rxbuf[0]));
}
// Update Peer Timestamp
peerlock.lock();
SceNetAdhocctlPeerInfo* peer = findFriend(&sendermac);
if (peer != NULL) {
now = CoreTiming::GetGlobalTimeUsScaled();
s64 delta = now - peer->last_recv;
DEBUG_LOG(Log::sceNet, "Timestamp LastRecv Delta: %lld (%llu - %llu) from %s", delta, now, peer->last_recv, mac2str(&sendermac).c_str());
if (peer->last_recv != 0) peer->last_recv = std::max(peer->last_recv, now - defaultLastRecvDelta);
}
else {
WARN_LOG(Log::sceNet, "InputLoop[%d]: Unknown Peer[%s:%u] (Recved=%i, Length=%i)", matchingId, mac2str(&sendermac).c_str(), senderport, recvresult, rxbuflen);
}
// Show a warning if other player is having their port being re-mapped, thus that other player may have issue with the communication.
// Note: That other player may need to switch side between host and join, or reboot their router to solve this issue.
if (context->port != senderport && senderport != (*context->peerPort)[sendermac]) {
char name[9] = {};
if (peer != NULL)
truncate_cpy(name, sizeof(name), (const char*)peer->nickname.data);
WARN_LOG(Log::sceNet, "InputLoop[%d]: Unknown Source Port from [%s][%s:%u -> %u] (Recved=%i, Length=%i)", matchingId, name, mac2str(&sendermac).c_str(), senderport, context->port, recvresult, rxbuflen);
g_OSD.Show(OSDType::MESSAGE_WARNING, std::string(n->T("AM: Data from Unknown Port")) + std::string(" [") + std::string(name) + std::string("]:") + std::to_string(senderport) + std::string(" -> ") + std::to_string(context->port) + std::string(" (") + std::to_string(portOffset) + std::string(")"), 0.0f, "unknowndata");
}
// Keep tracks of re-mapped peer's ports for further communication.
// Note: This will only works if this player were able to receives data on normal port from other players (ie. this player's port wasn't remapped)
(*context->peerPort)[sendermac] = senderport;
peerlock.unlock();
// Ping Packet
if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_PING) actOnPingPacket(context, &sendermac);
// Hello Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_HELLO) actOnHelloPacket(context, &sendermac, rxbuflen);
// Join Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_JOIN) actOnJoinPacket(context, &sendermac, rxbuflen);
// Accept Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_ACCEPT) actOnAcceptPacket(context, &sendermac, rxbuflen);
// Cancel Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_CANCEL) actOnCancelPacket(context, &sendermac, rxbuflen);
// Bulk Data Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BULK) actOnBulkDataPacket(context, &sendermac, rxbuflen);
// Abort Bulk Data Packet
//else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) actOnAbortBulkDataPacket(context, &sendermac, rxbuflen);
// Birth Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BIRTH) actOnBirthPacket(context, &sendermac, rxbuflen);
// Death Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_DEATH) actOnDeathPacket(context, &sendermac, rxbuflen);
// Bye Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BYE) actOnByePacket(context, &sendermac);
// Ignore Incoming Trash Data
}
else
break;
}
}
// What the input loop did on its way out: last sends, bye, and the peer list.
static void MatchingInputFinish(SceNetAdhocMatchingContext *context, int matchingId) {
if (contexts != NULL) {
// Process Last Messages
if (context->input_stack != NULL) {
// Claim Stack
context->inputlock->lock();
// Iterate Message List
int msg_count = 0;
ThreadMessage * msg = context->event_stack;
for (; msg != NULL; msg = msg->next) {
ThreadMessage* msg = context->input_stack;
while (msg != NULL) {
// Default Optional Data
void * opt = NULL;
void* opt = NULL;
// Grab Optional Data
if (msg->optlen > 0) opt = ((u8 *)msg) + sizeof(ThreadMessage); //&msg[1]
if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage);
INFO_LOG(Log::sceNet, "EventLoop[%d]: Matching Event [EVENT=%d]\n", matchingId, msg->opcode);
// Send Accept Packet
if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_ACCEPT) sendAcceptPacket(context, &msg->mac, msg->optlen, opt);
//context->eventlock->unlock();
// Original Call Event Handler
//context->handler(context->id, msg->opcode, &msg->mac, msg->optlen, opt);
// Notify Event Handlers
notifyMatchingHandler(context, msg, opt, bufAddr, bufLen, args);
//context->eventlock->lock();
// Send Join Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_JOIN) sendJoinPacket(context, &msg->mac, msg->optlen, opt);
// Send Cancel Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_CANCEL) sendCancelPacket(context, &msg->mac, msg->optlen, opt);
// Send Bulk Data Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK) sendBulkDataPacket(context, &msg->mac, msg->optlen, opt);
// Send Birth Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BIRTH) sendBirthPacket(context, &msg->mac);
// Send Death Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_DEATH) sendDeathPacket(context, &msg->mac);
// Cancel Bulk Data Transfer (does nothing as of now as we fire and forget anyway) // Do we need to check DeathPacket and ByePacket here?
//else if(msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) sendAbortBulkDataPacket(context, &msg->mac, msg->optlen, opt);
// Pop input stack from front (this should be queue instead of stack?)
context->input_stack = msg->next;
free(msg);
msg = context->input_stack;
msg_count++;
}
// Clear Event Message Stack
clearStack(context, PSP_ADHOC_MATCHING_EVENT_STACK);
// Free Stack
context->eventlock->unlock();
INFO_LOG(Log::sceNet, "EventLoop[%d]: Finished (%d msg)", matchingId, msg_count);
context->inputlock->unlock();
INFO_LOG(Log::sceNet, "InputLoop[%d]: Finished (%d msg)", matchingId, msg_count);
}
// Free memory
//if (Memory::IsValidAddress(bufAddr)) userMemory.Free(bufAddr);
// Clear IO Message Stack
clearStack(context, PSP_ADHOC_MATCHING_INPUT_STACK);
// Send Bye Messages. FIXME: Official prx seems to be sending DEATH instead of BYE packet during MatchingStop? But DEATH packet doesn't works with DBZ Team Tag
sendByePacket(context);
// Free Peer List Buffer
clearPeerList(context); //deleteAllMembers(context);
// Delete Pointer Reference (and notify caller about finished cleanup)
//context->eventThread = NULL;
//context->inputThread = NULL;
}
// Log Shutdown
INFO_LOG(Log::sceNet, "EventLoop: End of EventLoop[%i] Thread", matchingId);
// Return Zero to shut up Compiler
return 0;
}
/**
* Matching IO Handler Thread
* @param args sizeof(SceNetAdhocMatchingContext *)
* @param argp SceNetAdhocMatchingContext *
* @return Exit Point is never reached...
*/
int matchingInputThread(int matchingId) { // TODO: The MatchingInput thread is using sceNetAdhocPdpRecv & sceNetAdhocPdpSend functions so it might be better to run this on PSP thread instead of real thread
SetCurrentThreadName("MatchingInput");
auto n = GetI18NCategory(I18NCat::NETWORKING);
// Multithreading Lock
static void __AdhocMatchingTick(u64 userdata, int cyclesLate) {
const int matchingId = (int)userdata;
peerlock.lock();
// Cast Context
SceNetAdhocMatchingContext* context = findMatchingContext(matchingId);
// Multithreading Unlock
SceNetAdhocMatchingContext *context = findMatchingContext(matchingId);
peerlock.unlock();
if (context == NULL || (!context->inputRunning && !context->eventRunning))
return;
if (context->inputRunning)
MatchingInputPass(context, matchingId);
if (context->eventRunning)
MatchingEventPass(context, matchingId);
CoreTiming::ScheduleEvent(usToCycles(MATCHING_TICK_US) - cyclesLate, matchingTickEvent, userdata);
}
// Last Ping
u64_le lastping = 0;
// Last Hello
u64_le lasthello = 0;
u64_le now;
static SceNetEtherAddr sendermac;
static u32_le senderport;
static int rxbuflen;
// Log Startup
INFO_LOG(Log::sceNet, "InputLoop: Begin of InputLoop[%i] Thread", matchingId);
// Run while needed...
if (context != NULL) {
while (contexts != NULL && context->inputRunning) {
// Multithreading Lock
peerlock.lock();
// Cast Context
context = findMatchingContext(matchingId);
// Multithreading Unlock
peerlock.unlock();
while (context != NULL && context->inputRunning && !Core_IsStepping()) {
now = CoreTiming::GetGlobalTimeUsScaled(); //time_now_d()*1000000.0;
// Hello Message Sending Context with unoccupied Slots
if ((context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && (countChildren(context) < (context->maxpeers - 1))) || (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && findP2P(context) == NULL)) {
// Hello Message Broadcast necessary because of Hello Interval
if (context->hello_int > 0)
if (static_cast<s64>(now - lasthello) >= static_cast<s64>(context->hello_int)) {
// Broadcast Hello Message
broadcastHelloMessage(context);
// Update Hello Timer
lasthello = now;
}
}
// Ping Required
if (context->keepalive_int > 0) {
if (static_cast<s64>(now - lastping) >= static_cast<s64>(context->keepalive_int)) {
// Handle Peer Timeouts
handleTimeout(context);
// Broadcast Ping Message
broadcastPingMessage(context);
// Update Ping Timer
lastping = now;
}
}
else {
// FIXME: Should we checks for Timeout too when the game doesn't set the keep alive interval?
handleTimeout(context);
}
// Messages on Stack ready for processing
if (context->input_stack != NULL) {
// Claim Stack
context->inputlock->lock();
// Iterate Message List
ThreadMessage* msg = context->input_stack;
while (msg != NULL) {
// Default Optional Data
void* opt = NULL;
// Grab Optional Data
if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage);
//context->inputlock->unlock(); // Unlock to prevent race condition when locking peerlock
// Send Accept Packet
if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_ACCEPT) sendAcceptPacket(context, &msg->mac, msg->optlen, opt);
// Send Join Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_JOIN) sendJoinPacket(context, &msg->mac, msg->optlen, opt);
// Send Cancel Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_CANCEL) sendCancelPacket(context, &msg->mac, msg->optlen, opt);
// Send Bulk Data Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK) sendBulkDataPacket(context, &msg->mac, msg->optlen, opt);
// Send Birth Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BIRTH) sendBirthPacket(context, &msg->mac);
// Send Death Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_DEATH) sendDeathPacket(context, &msg->mac);
// Cancel Bulk Data Transfer (does nothing as of now as we fire and forget anyway) // Do we need to check DeathPacket and ByePacket here?
//else if(msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) sendAbortBulkDataPacket(context, &msg->mac, msg->optlen, opt);
//context->inputlock->lock(); // Lock again
// Pop input stack from front (this should be queue instead of stack?)
context->input_stack = msg->next;
free(msg);
msg = context->input_stack;
}
// Free Stack
context->inputlock->unlock();
}
// Receive PDP Datagram
// FIXME: When using JPCSP + prx files, the "SceNetAdhocMatchingInput" thread is using blocking PdpRecv with infinite(0) timeout, which can be stopped/aborted using SetSocketAlert, while "SceNetAdhocMatchingEvent" thread is using non-blocking for sending
rxbuflen = context->rxbuflen;
senderport = 0;
// Lock the peer first before locking the socket to avoid race condiion
peerlock.lock();
context->socketlock->lock();
int recvresult = sceNetAdhocPdpRecv(context->socket, &sendermac, &senderport, context->rxbuf, &rxbuflen, 0, ADHOC_F_NONBLOCK);
context->socketlock->unlock();
peerlock.unlock();
// Received Data from a Sender that interests us
// Note: There are cases where the sender port might be re-mapped by router or ISP, so we shouldn't check the source port.
if (recvresult == 0 && rxbuflen > 0) {
// Log Receive Success
if (context->rxbuf[0] > 1) {
INFO_LOG(Log::sceNet, "InputLoop[%d]: Received %d Bytes (Opcode[%d]=%s)", matchingId, rxbuflen, context->rxbuf[0], getMatchingOpcodeStr(context->rxbuf[0]));
}
// Update Peer Timestamp
peerlock.lock();
SceNetAdhocctlPeerInfo* peer = findFriend(&sendermac);
if (peer != NULL) {
now = CoreTiming::GetGlobalTimeUsScaled();
s64 delta = now - peer->last_recv;
DEBUG_LOG(Log::sceNet, "Timestamp LastRecv Delta: %lld (%llu - %llu) from %s", delta, now, peer->last_recv, mac2str(&sendermac).c_str());
if (peer->last_recv != 0) peer->last_recv = std::max(peer->last_recv, now - defaultLastRecvDelta);
}
else {
WARN_LOG(Log::sceNet, "InputLoop[%d]: Unknown Peer[%s:%u] (Recved=%i, Length=%i)", matchingId, mac2str(&sendermac).c_str(), senderport, recvresult, rxbuflen);
}
// Show a warning if other player is having their port being re-mapped, thus that other player may have issue with the communication.
// Note: That other player may need to switch side between host and join, or reboot their router to solve this issue.
if (context->port != senderport && senderport != (*context->peerPort)[sendermac]) {
char name[9] = {};
if (peer != NULL)
truncate_cpy(name, sizeof(name), (const char*)peer->nickname.data);
WARN_LOG(Log::sceNet, "InputLoop[%d]: Unknown Source Port from [%s][%s:%u -> %u] (Recved=%i, Length=%i)", matchingId, name, mac2str(&sendermac).c_str(), senderport, context->port, recvresult, rxbuflen);
g_OSD.Show(OSDType::MESSAGE_WARNING, std::string(n->T("AM: Data from Unknown Port")) + std::string(" [") + std::string(name) + std::string("]:") + std::to_string(senderport) + std::string(" -> ") + std::to_string(context->port) + std::string(" (") + std::to_string(portOffset) + std::string(")"), 0.0f, "unknowndata");
}
// Keep tracks of re-mapped peer's ports for further communication.
// Note: This will only works if this player were able to receives data on normal port from other players (ie. this player's port wasn't remapped)
(*context->peerPort)[sendermac] = senderport;
peerlock.unlock();
// Ping Packet
if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_PING) actOnPingPacket(context, &sendermac);
// Hello Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_HELLO) actOnHelloPacket(context, &sendermac, rxbuflen);
// Join Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_JOIN) actOnJoinPacket(context, &sendermac, rxbuflen);
// Accept Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_ACCEPT) actOnAcceptPacket(context, &sendermac, rxbuflen);
// Cancel Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_CANCEL) actOnCancelPacket(context, &sendermac, rxbuflen);
// Bulk Data Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BULK) actOnBulkDataPacket(context, &sendermac, rxbuflen);
// Abort Bulk Data Packet
//else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) actOnAbortBulkDataPacket(context, &sendermac, rxbuflen);
// Birth Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BIRTH) actOnBirthPacket(context, &sendermac, rxbuflen);
// Death Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_DEATH) actOnDeathPacket(context, &sendermac, rxbuflen);
// Bye Packet
else if (context->rxbuf[0] == PSP_ADHOC_MATCHING_PACKET_BYE) actOnByePacket(context, &sendermac);
// Ignore Incoming Trash Data
}
else
break;
}
// Share CPU Time
sleep_ms(10, "pro-adhoc-4"); //1 //sceKernelDelayThread(10000);
// Don't do anything if it's paused, otherwise the log will be flooded
while (Core_IsStepping() && coreState != CORE_POWERDOWN && contexts != NULL && context->inputRunning)
sleep_ms(10, "pro-adhoc-input-4");
}
if (contexts != NULL) {
// Process Last Messages
if (context->input_stack != NULL) {
// Claim Stack
context->inputlock->lock();
// Iterate Message List
int msg_count = 0;
ThreadMessage* msg = context->input_stack;
while (msg != NULL) {
// Default Optional Data
void* opt = NULL;
// Grab Optional Data
if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage);
// Send Accept Packet
if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_ACCEPT) sendAcceptPacket(context, &msg->mac, msg->optlen, opt);
// Send Join Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_JOIN) sendJoinPacket(context, &msg->mac, msg->optlen, opt);
// Send Cancel Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_CANCEL) sendCancelPacket(context, &msg->mac, msg->optlen, opt);
// Send Bulk Data Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK) sendBulkDataPacket(context, &msg->mac, msg->optlen, opt);
// Send Birth Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BIRTH) sendBirthPacket(context, &msg->mac);
// Send Death Packet
else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_DEATH) sendDeathPacket(context, &msg->mac);
// Cancel Bulk Data Transfer (does nothing as of now as we fire and forget anyway) // Do we need to check DeathPacket and ByePacket here?
//else if(msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) sendAbortBulkDataPacket(context, &msg->mac, msg->optlen, opt);
// Pop input stack from front (this should be queue instead of stack?)
context->input_stack = msg->next;
free(msg);
msg = context->input_stack;
msg_count++;
}
// Free Stack
context->inputlock->unlock();
INFO_LOG(Log::sceNet, "InputLoop[%d]: Finished (%d msg)", matchingId, msg_count);
}
// Clear IO Message Stack
clearStack(context, PSP_ADHOC_MATCHING_INPUT_STACK);
// Send Bye Messages. FIXME: Official prx seems to be sending DEATH instead of BYE packet during MatchingStop? But DEATH packet doesn't works with DBZ Team Tag
sendByePacket(context);
// Free Peer List Buffer
clearPeerList(context); //deleteAllMembers(context);
// Delete Pointer Reference (and notify caller about finished cleanup)
//context->inputThread = NULL;
}
}
// Log Shutdown
INFO_LOG(Log::sceNet, "InputLoop: End of InputLoop[%i] Thread", matchingId);
// Terminate Thread
//sceKernelExitDeleteThread(0);
// Return Zero to shut up Compiler
return 0;
void DoNetAdhocMatchingTick(PointerWrap &p, bool present) {
if (present)
Do(p, matchingTickEvent);
else
matchingTickEvent = -1;
CoreTiming::RestoreRegisterEvent(matchingTickEvent, "__AdhocMatchingTick", __AdhocMatchingTick);
}
@@ -1578,14 +1526,15 @@ int NetAdhocMatching_Stop(int matchingId) {
// This will cause using PdpRecv on this socket to return ERROR_NET_ADHOC_SOCKET_ALERTED (Based on Ys vs. Sora no Kiseki when tested with JPCSP + prx files). Is this used to abort inprogress socket activity?
NetAdhoc_SetSocketAlert(item->socket, ADHOC_F_ALERTRECV);
item->inputRunning = false;
if (item->inputThread.joinable()) {
item->inputThread.join();
// The loops stop here, on the emulator thread, and do what their threads did on the way out.
CoreTiming::UnscheduleEvent(matchingTickEvent, (u64)matchingId);
if (item->inputRunning) {
item->inputRunning = false;
MatchingInputFinish(item, matchingId);
}
item->eventRunning = false;
if (item->eventThread.joinable()) {
item->eventThread.join();
if (item->eventRunning) {
item->eventRunning = false;
MatchingEventFinish(item, matchingId);
}
// Stop fake PSP Thread.
@@ -1898,15 +1847,14 @@ int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int
//item->matchingThread->Start(matchingId, 0);
}
//Create the threads
if (!item->eventRunning) {
item->eventRunning = true;
item->eventThread = std::thread(matchingEventThread, matchingId);
}
if (!item->inputRunning) {
item->inputRunning = true;
item->inputThread = std::thread(matchingInputThread, matchingId);
// Start the loops: one tick of emulated time drives both.
if (!item->eventRunning && !item->inputRunning) {
item->inputLastPing = 0;
item->inputLastHello = 0;
CoreTiming::ScheduleEvent(0, matchingTickEvent, (u64)matchingId);
}
item->eventRunning = true;
item->inputRunning = true;
item->running = 1;
netAdhocMatchingStarted++;
@@ -2687,6 +2635,7 @@ void Register_sceNetAdhocMatching() {
void __NetAdhocMatchingInit() {
netAdhocMatchingInited = false;
matchingTickEvent = CoreTiming::RegisterEvent("__AdhocMatchingTick", __AdhocMatchingTick);
}
void __NetAdhocMatchingShutdown() {
+2
View File
@@ -35,6 +35,8 @@ int NetAdhocMatching_Term();
void DoNetAdhocMatchingInited(PointerWrap &p);
void DoNetAdhocMatchingThreads(PointerWrap &p);
// present: the state has the tick's event id (sceNetAdhoc section 10 and up).
void DoNetAdhocMatchingTick(PointerWrap &p, bool present);
void ZeroNetAdhocMatchingThreads();
void SaveNetAdhocMatchingInited();
void RestoreNetAdhocMatchingInited();
+161 -32
View File
@@ -1,4 +1,5 @@
#include <algorithm>
#include <map>
#include "Common/StringUtils.h"
#include "Common/Net/SocketCompat.h"
#include "Common/Data/Text/Parsers.h"
@@ -8,6 +9,8 @@
#include "Core/HLE/SocketManager.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/CoreTiming.h"
#include "Core/HLE/ErrorCodes.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/sceNet.h"
@@ -222,11 +225,8 @@ static int sceNetInetGetsockname(int socket, u32 namePtr, u32 namelenPtr) {
}
// FIXME: nfds is number of fd(s) as in posix poll, or was it maximum fd value as in posix select? Star Wars Battlefront Renegade seems to set the nfds to 64, while Coded Arms Contagion is using 256
int sceNetInetSelect(int nfds, u32 readfdsPtr, u32 writefdsPtr, u32 exceptfdsPtr, u32 timeoutPtr) {
SceNetInetFdSet *readfds = readfdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(readfdsPtr) : nullptr;
SceNetInetFdSet *writefds = writefdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(writefdsPtr) : nullptr;
SceNetInetFdSet *exceptfds = exceptfdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(exceptfdsPtr) : nullptr;
SceNetInetTimeval *timeout = timeoutPtr ? (SceNetInetTimeval*)Memory::GetPointerWriteOrException(timeoutPtr) : nullptr;
// One look at the sockets, never blocking: reads the requested sets and writes the result over them.
static int InetSelectCheck(int nfds, SceNetInetFdSet *readfds, SceNetInetFdSet *writefds, SceNetInetFdSet *exceptfds, SceUID thread) {
// First, translate the specified fd_sets to host sockets.
@@ -310,15 +310,9 @@ int sceNetInetSelect(int nfds, u32 readfdsPtr, u32 writefdsPtr, u32 exceptfdsPtr
_dbg_assert_(wrcnt < FD_SETSIZE);
_dbg_assert_(excnt < FD_SETSIZE);
timeval tmout = { 5, 543210 }; // Workaround timeout value when timeout = NULL
if (timeout) {
tmout.tv_sec = timeout->tv_sec;
tmout.tv_usec = timeout->tv_usec;
}
DEBUG_LOG(Log::sceNet, "Select(host: %d): Read count: %d, Write count: %d, Except count: %d, TimeVal: %u.%u", maxHostSocket + 1, rdcnt, wrcnt, excnt, (int)tmout.tv_sec, (int)tmout.tv_usec);
// TODO: Simulate blocking behaviour when timeout = NULL to prevent PPSSPP from freezing
// Note: select can overwrite tmout.
int retval = select(maxHostSocket + 1, readfds ? &rdfds : nullptr, writefds ? &wrfds : nullptr, exceptfds ? &exfds : nullptr, /*(timeout == NULL) ? NULL :*/ &tmout);
timeval tmout = { 0, 0 };
VERBOSE_LOG(Log::sceNet, "Select(host: %d): Read count: %d, Write count: %d, Except count: %d", maxHostSocket + 1, rdcnt, wrcnt, excnt);
int retval = select(maxHostSocket + 1, readfds ? &rdfds : nullptr, writefds ? &wrfds : nullptr, exceptfds ? &exfds : nullptr, &tmout);
// Convert the results back to PSP fd_sets.
if (readfds)
@@ -347,14 +341,13 @@ int sceNetInetSelect(int nfds, u32 readfdsPtr, u32 writefdsPtr, u32 exceptfdsPtr
}
if (retval < 0) {
UpdateErrnoFromHost(__KernelGetCurThread(), socket_errno, __FUNCTION__);
return hleDelayResult(hleLogDebug(Log::sceNet, retval), "workaround until blocking-socket", 500); // Using hleDelayResult as a workaround for games that need blocking-socket to be implemented (ie. Coded Arms Contagion)
UpdateErrnoFromHost(thread, socket_errno, __FUNCTION__);
}
return hleDelayResult(hleLogDebug(Log::sceNet, retval), "workaround until blocking-socket", 500); // Using hleDelayResult as a workaround for games that need blocking-socket to be implemented (ie. Coded Arms Contagion)
return retval;
}
int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout) { // timeout in miliseconds just like posix poll? or in microseconds as other PSP timeout?
DEBUG_LOG(Log::sceNet, "UNTESTED sceNetInetPoll(%08x, %d, %i) at %08x", fdsPtr, nfds, timeout, currentMIPS->pc);
// One look at the sockets, never blocking: fills in revents. < 0 with errno set on a bad fd.
static int InetPollCheck(u32 fdsPtr, u32 nfds, SceUID thread) {
int retval = -1;
int maxHostFd = 0;
SceNetInetPollfd *fdarray = (SceNetInetPollfd*)Memory::GetPointerOrException(fdsPtr); // SceNetInetPollfd/pollfd, sceNetInetPoll() have similarity to BSD poll() but pollfd have different size on 64bit
@@ -367,8 +360,9 @@ int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout) { // timeout in milisecond
for (int i = 0; i < (s32)nfds; i++) {
if (fdarray[i].fd < 0) {
// In Unix, this is OK and means it the fd should be ignored, except fdarray[i].revents should be zeroed.
UpdateErrnoFromHost(__KernelGetCurThread(), EINVAL, __FUNCTION__);
return hleLogError(Log::sceNet, -1, "invalid socket id");
UpdateErrnoFromHost(thread, EINVAL, __FUNCTION__);
ERROR_LOG(Log::sceNet, "sceNetInetPoll: invalid socket id");
return -1;
}
SOCKET hostSocket = g_socketManager.GetHostSocketFromInetSocket(fdarray[i].fd);
if (hostSocket > maxHostFd) {
@@ -381,16 +375,11 @@ int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout) { // timeout in milisecond
fdarray[i].revents = 0;
}
timeval tmout = { 5, 543210 }; // Workaround timeout value when timeout = NULL
if (timeout >= 0) {
tmout.tv_sec = timeout / 1000000; // seconds
tmout.tv_usec = (timeout % 1000000); // microseconds
}
// TODO: Simulate blocking behaviour when timeout is non-zero to prevent PPSSPP from freezing
retval = select(maxHostFd + 1, &readfds, &writefds, &exceptfds, /*(timeout<0)? NULL:*/&tmout);
timeval tmout = { 0, 0 };
retval = select(maxHostFd + 1, &readfds, &writefds, &exceptfds, &tmout);
if (retval < 0) {
UpdateErrnoFromHost(__KernelGetCurThread(), EINTR, __FUNCTION__);
return hleDelayResult(hleLogError(Log::sceNet, retval), "workaround until blocking-socket", 500); // Using hleDelayResult as a workaround for games that need blocking-socket to be implemented
UpdateErrnoFromHost(thread, EINTR, __FUNCTION__);
return -2;
}
retval = 0;
@@ -407,8 +396,148 @@ int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout) { // timeout in milisecond
retval++;
VERBOSE_LOG(Log::sceNet, "Poll Socket#%d Fd: %d, events: %04x, revents: %04x, availToRecv: %d", i, fdarray[i].fd, fdarray[i].events, fdarray[i].revents, (int)getAvailToRecv(fdarray[i].fd));
}
//hleEatMicro(1000);
return hleDelayResult(hleLogDebug(Log::sceNet, retval), "workaround until blocking-socket", 1000); // Using hleDelayResult as a workaround for games that need blocking-socket to be implemented
return retval;
}
// A select or poll that found nothing ready and was told to wait: its thread waits in
// emulated time, rechecked every INET_WAIT_POLL_US, until something is ready or the
// timeout passes. No host thread blocks inside the emulator.
static constexpr u64 INET_WAIT_POLL_US = 1000;
// What a wait without a timeout gets, as before: these calls never blocked forever.
static constexpr u64 INET_WAIT_DEFAULT_US = 5543210;
struct InetWait {
bool isPoll;
int nfds;
u32 readfdsPtr, writefdsPtr, exceptfdsPtr;
SceNetInetFdSet readSet, writeSet, exceptSet;
u32 fdsPtr;
u64 deadlineUs;
};
static std::map<SceUID, InetWait> inetWaits;
static int inetWaitEvent = -1;
static SceNetInetFdSet *InetRestoreSet(u32 ptr, const SceNetInetFdSet &set) {
if (!ptr)
return nullptr;
SceNetInetFdSet *dst = (SceNetInetFdSet *)Memory::GetPointerWriteOrException(ptr);
*dst = set;
return dst;
}
static int InetWaitCheck(const InetWait &w, SceUID thread) {
if (w.isPoll)
return InetPollCheck(w.fdsPtr, (u32)w.nfds, thread);
// The last look wrote its result over the sets; ask for the same again.
return InetSelectCheck(w.nfds, InetRestoreSet(w.readfdsPtr, w.readSet), InetRestoreSet(w.writefdsPtr, w.writeSet), InetRestoreSet(w.exceptfdsPtr, w.exceptSet), thread);
}
static void __InetWaitCheck(u64 userdata, int cyclesLate) {
const SceUID thread = (SceUID)userdata;
u32 error;
if (__KernelGetWaitID(thread, WAITTYPE_NET, error) != 1 || error != 0) {
inetWaits.erase(thread);
return;
}
auto it = inetWaits.find(thread);
if (it == inetWaits.end()) {
// Loaded from a state, which doesn't keep these: report a timeout and let the game ask again.
__KernelResumeThreadFromWait(thread, 0);
__KernelReSchedule("inet wait lost");
return;
}
int result = InetWaitCheck(it->second, thread);
if (result == -2)
result = -1;
const u64 now = CoreTiming::GetGlobalTimeUs();
if (result != 0 || now >= it->second.deadlineUs) {
inetWaits.erase(it);
__KernelResumeThreadFromWait(thread, result);
__KernelReSchedule("inet wait done");
return;
}
const u64 left = it->second.deadlineUs - now;
CoreTiming::ScheduleEvent(usToCycles(std::min(left, INET_WAIT_POLL_US)) - cyclesLate, inetWaitEvent, userdata);
}
static int InetWaitBegin(const InetWait &w, const char *reason) {
const SceUID thread = __KernelGetCurThread();
inetWaits[thread] = w;
const u64 left = w.deadlineUs - CoreTiming::GetGlobalTimeUs();
CoreTiming::ScheduleEvent(usToCycles(std::min(left, INET_WAIT_POLL_US)), inetWaitEvent, (u64)thread);
__KernelWaitCurThread(WAITTYPE_NET, 1, 0, 0, false, reason);
return 0;
}
// FIXME: nfds is number of fd(s) as in posix poll, or was it maximum fd value as in posix select? Star Wars Battlefront Renegade seems to set the nfds to 64, while Coded Arms Contagion is using 256
int sceNetInetSelect(int nfds, u32 readfdsPtr, u32 writefdsPtr, u32 exceptfdsPtr, u32 timeoutPtr) {
SceNetInetFdSet *readfds = readfdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(readfdsPtr) : nullptr;
SceNetInetFdSet *writefds = writefdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(writefdsPtr) : nullptr;
SceNetInetFdSet *exceptfds = exceptfdsPtr ? (SceNetInetFdSet*)Memory::GetPointerWriteOrException(exceptfdsPtr) : nullptr;
SceNetInetTimeval *timeout = timeoutPtr ? (SceNetInetTimeval*)Memory::GetPointerWriteOrException(timeoutPtr) : nullptr;
InetWait w{};
w.nfds = nfds;
w.readfdsPtr = readfds ? readfdsPtr : 0;
w.writefdsPtr = writefds ? writefdsPtr : 0;
w.exceptfdsPtr = exceptfds ? exceptfdsPtr : 0;
if (readfds)
w.readSet = *readfds;
if (writefds)
w.writeSet = *writefds;
if (exceptfds)
w.exceptSet = *exceptfds;
const SceUID thread = __KernelGetCurThread();
const int retval = InetSelectCheck(nfds, readfds, writefds, exceptfds, thread);
const u64 waitUs = timeout ? (u64)timeout->tv_sec * 1000000 + (u64)timeout->tv_usec : INET_WAIT_DEFAULT_US;
if (retval != 0 || waitUs == 0) {
// Using hleDelayResult as a workaround for games that need blocking-socket to be implemented (ie. Coded Arms Contagion)
return hleDelayResult(hleLogDebug(Log::sceNet, retval), "workaround until blocking-socket", 500);
}
w.deadlineUs = CoreTiming::GetGlobalTimeUs() + waitUs;
return hleLogDebug(Log::sceNet, InetWaitBegin(w, "inet select"), "waiting");
}
int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout) { // timeout in miliseconds just like posix poll? or in microseconds as other PSP timeout?
DEBUG_LOG(Log::sceNet, "UNTESTED sceNetInetPoll(%08x, %d, %i) at %08x", fdsPtr, nfds, timeout, currentMIPS->pc);
if (nfds > FD_SETSIZE)
nfds = FD_SETSIZE;
const SceUID thread = __KernelGetCurThread();
const int retval = InetPollCheck(fdsPtr, nfds, thread);
if (retval == -1) {
return hleLogError(Log::sceNet, -1, "invalid socket id");
} else if (retval < 0) {
// Using hleDelayResult as a workaround for games that need blocking-socket to be implemented
return hleDelayResult(hleLogError(Log::sceNet, -1), "workaround until blocking-socket", 500);
}
const u64 waitUs = timeout >= 0 ? (u64)timeout : INET_WAIT_DEFAULT_US;
if (retval != 0 || waitUs == 0) {
// Using hleDelayResult as a workaround for games that need blocking-socket to be implemented
return hleDelayResult(hleLogDebug(Log::sceNet, retval), "workaround until blocking-socket", 1000);
}
InetWait w{};
w.isPoll = true;
w.nfds = (int)nfds;
w.fdsPtr = fdsPtr;
w.deadlineUs = CoreTiming::GetGlobalTimeUs() + waitUs;
return hleLogDebug(Log::sceNet, InetWaitBegin(w, "inet poll"), "waiting");
}
void __NetInetInit() {
inetWaits.clear();
inetWaitEvent = CoreTiming::RegisterEvent("__InetWaitCheck", __InetWaitCheck);
}
void __NetInetDoWaitEvent(PointerWrap &p, bool present) {
if (present)
Do(p, inetWaitEvent);
else
inetWaitEvent = -1;
CoreTiming::RestoreRegisterEvent(inetWaitEvent, "__InetWaitCheck", __InetWaitCheck);
if (p.mode == PointerWrap::MODE_READ)
inetWaits.clear();
}
static int sceNetInetRecv(int socket, u32 bufPtr, u32 bufLen, u32 flags) {
+3
View File
@@ -131,4 +131,7 @@ int NetApctl_GetState();
int sceNetApctlConnect(int connIndex);
int sceNetInetPoll(u32 fdsPtr, u32 nfds, int timeout);
void __NetInetInit();
// present: the state has the wait event's id (sceNet section 8 and up).
void __NetInetDoWaitEvent(PointerWrap &p, bool present);
int sceNetApctlDisconnect();
+1 -2
View File
@@ -157,8 +157,7 @@ static int sceNpMatching2ContextStart(int ctxId)
//npMatching2Ctx.started = true;
Url url("http://static-resource.np.community.playstation.net/np/resource/psp-title/" + std::string(npTitleId.data) + "_00/matching/" + std::string(npTitleId.data) + "_00-matching.xml");
http::Client client(&ProcessHostnameWithInfraDNS);
bool cancelled = false;
net::RequestProgress progress(&cancelled);
net::RequestProgress progress(nullptr);
if (!client.Resolve(url.Host().c_str(), url.Port())) {
return hleLogError(Log::sceNet, SCE_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_TITLE, "HTTP failed to resolve %s", url.Resource().c_str());
}
+7
View File
@@ -859,7 +859,14 @@ BootState PSP_Init(const CoreParameter &coreParam, std::string *error_string) {
return BootState::Failed;
}
return PSP_InitWait(error_string);
}
BootState PSP_InitWait(std::string *error_string) {
if (g_bootState == BootState::Off)
return BootState::Off;
// Every path out of the loader thread sets Complete or Failed.
_assert_msg_(g_loadingThread.joinable(), "bootstate: %d", (int)g_bootState.load());
g_loadingThread.join();
return PSP_InitFinish(error_string);
}
+2
View File
@@ -69,6 +69,8 @@ bool PSP_InitStart(const CoreParameter &coreParam);
// Check the return value of this - if Booting, keep calling.
// If Complete or Failed, handle as appropriate, and stop calling.
BootState PSP_InitUpdate(std::string *error_string);
// After PSP_InitStart: blocks until the loader thread is done (it never sleeps), then finishes.
BootState PSP_InitWait(std::string *error_string);
// Blocking wrapper around the two above functions, used for convenience in a couple of places.
// Should be avoided/removed eventually.
+4 -15
View File
@@ -506,8 +506,7 @@ static int upnpService(unsigned int timeout) {
int failCount = 0;
bool wasEnabled = false;
// Absolute time before which we won't try to (re)discover a router. This has to be a deadline
// rather than a sleep length: an incoming request wakes us early, and without it every single
// Absolute time before which we won't try to (re)discover a router: without it every single
// request would trigger another full SSDP discovery while the router is unreachable.
double nextInitTime = 0.0;
@@ -526,10 +525,6 @@ static int upnpService(unsigned int timeout) {
}
}
// 0 means "sleep until someone wakes us" - which is what this thread does for the whole
// session for most users, rather than waking up periodically to find nothing to do.
double wakeAt = 0.0;
if (!g_Config.bEnableUPnP) {
// Callers queue requests without checking the setting (see bind() in sceNetInet), so throw
// them away here. Otherwise the queue grows without bound and, worse, the wait below never
@@ -577,18 +572,12 @@ static int upnpService(unsigned int timeout) {
nextInitTime = time_now_d() + MIN_RETRY_SECONDS;
}
}
if (g_PortManager.GetInitState() != UPNP_INITSTATE_DONE)
wakeAt = nextInitTime;
}
// Sleep until someone wakes us, with no timer: a router that couldn't be reached is tried
// again on the next request (or setting change) once the backoff has passed.
std::unique_lock<std::mutex> lock(g_upnpLock);
auto shouldWake = [seq] { return g_upnpExit || g_upnpWakeSeq != seq; };
if (wakeAt == 0.0) {
g_upnpCond.wait(lock, shouldWake);
} else {
const double delay = std::max(wakeAt - time_now_d(), 0.0);
g_upnpCond.wait_for(lock, std::chrono::duration<double>(delay), shouldWake);
}
g_upnpCond.wait(lock, [seq] { return g_upnpExit || g_upnpWakeSeq != seq; });
}
// Clean up regardless of g_Config.bEnableUPnP, to avoid leaving open ports on the router.
+1 -2
View File
@@ -106,8 +106,7 @@ static ServerStatus RetrieveStatus() {
static bool RegisterServer(int port) {
bool success = false;
http::Client http(nullptr);
bool cancelled = false;
net::RequestProgress progress(&cancelled);
net::RequestProgress progress(nullptr);
Buffer theVoid = Buffer::Void();
http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION));
+19 -13
View File
@@ -47,6 +47,7 @@
#include "Common/TimeUtil.h"
#include "Core/Config.h"
#include "Core/System.h"
#include "Common/Net/Cancel.h"
#include "Core/WebServer.h"
#include "UI/RemoteISOScreen.h"
#include "UI/OnScreenDisplay.h"
@@ -54,7 +55,12 @@
static const char * const REPORT_HOSTNAME = "report.ppsspp.org";
static const int REPORT_PORT = 80;
static bool scanCancelled = false;
// Made before each scan thread starts; a destroyed screen cancels it.
static std::shared_ptr<net::CancelToken> scanCancel;
static bool ScanCancelled() {
return scanCancel && scanCancel->IsCancelled();
}
static bool scanAborted = false;
enum class ServerAllowStatus {
@@ -136,14 +142,14 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort
SetStatus("Connecting to [URL]...", host, port);
// Don't wait as long for a connect - we need a good connection for smooth streaming anyway.
// This way if it's down, we'll find the right one faster.
if (!http.Connect(1, 10.0, &scanCancelled)) {
if (!http.Connect(1, 10.0, scanCancel.get())) {
hadTimeouts = true;
SetStatus("Could not connect to [URL]", host, port);
return false;
}
SetStatus("Loading game list from [URL]...", host, port);
net::RequestProgress progress(&scanCancelled);
net::RequestProgress progress(scanCancel.get());
code = http.GET(http::RequestParams(subdir.c_str()), &result, &progress);
http.Disconnect();
@@ -194,22 +200,22 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort
}
// Don't scan if in manual mode.
if (g_Config.bRemoteISOManual || scanCancelled) {
if (g_Config.bRemoteISOManual || ScanCancelled()) {
return false;
}
// Start by requesting a list of recent local ips for this network.
SetStatus("Looking for peers...", "", 0);
if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) {
if (http.Connect(2, 20.0, &scanCancelled)) {
net::RequestProgress progress(&scanCancelled);
if (http.Connect(2, 20.0, scanCancel.get())) {
net::RequestProgress progress(scanCancel.get());
code = http.GET(http::RequestParams("/match/list"), &result, &progress);
http.Disconnect();
}
}
if (code != 200 || scanCancelled) {
if (!scanCancelled) {
if (code != 200 || ScanCancelled()) {
if (!ScanCancelled()) {
SetStatus("Could not load peers, retrying soon...", "", 0);
}
return false;
@@ -234,7 +240,7 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort
for (const auto pentry : entries) {
JsonGet entry = pentry->value;
if (scanCancelled)
if (ScanCancelled())
return false;
const char *host = entry.getStringOr("ip", "");
@@ -260,8 +266,8 @@ static bool LoadGameList(const Path &url, std::vector<Path> &games) {
browser.SetPath(url);
std::vector<File::FileInfo> files;
browser.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION));
browser.GetListing(files, "iso:cso:chd:pbp:elf:prx:ppdmp:", &scanCancelled);
if (scanCancelled) {
browser.GetListing(files, "iso:cso:chd:pbp:elf:prx:ppdmp:", scanCancel.get());
if (ScanCancelled()) {
return false;
}
for (auto &file : files) {
@@ -456,7 +462,7 @@ void RemoteISOScreen::HandleBrowse(UI::EventParams &e) {
}
RemoteISOConnectScreen::RemoteISOConnectScreen() {
scanCancelled = false;
scanCancel = std::make_shared<net::CancelToken>();
scanAborted = false;
scanThread_ = std::thread([](RemoteISOConnectScreen *thiz) {
@@ -467,7 +473,7 @@ RemoteISOConnectScreen::RemoteISOConnectScreen() {
RemoteISOConnectScreen::~RemoteISOConnectScreen() {
int maxWait = 5000;
scanCancelled = true;
scanCancel->Cancel();
while (GetStatus() == ScanStatus::SCANNING || GetStatus() == ScanStatus::LOADING) {
sleep_ms(1, "remote-iso-scan");
if (--maxWait < 0) {
+8 -7
View File
@@ -48,28 +48,29 @@ add_library(xxhash STATIC
)
set(aemu_postoffice_client_obj
aemu_postoffice/client/postoffice.c
aemu_postoffice/client/postoffice_mem_stdc.c
aemu_postoffice/client/mutex_impl_cpp.cpp
aemu_postoffice/client/delay_impl_cpp.cpp
aemu_postoffice/client/log_impl_ppsspp.cpp
aemu_postoffice_client/postoffice.c
aemu_postoffice_client/postoffice_mem_stdc.c
aemu_postoffice_client/mutex_impl_cpp.cpp
aemu_postoffice_client/log_impl_ppsspp.cpp
)
if(WIN32)
list(APPEND aemu_postoffice_client_obj
aemu_postoffice/client/sock_impl_windows.c
aemu_postoffice_client/sock_impl_windows.c
)
endif()
if(LINUX OR ANDROID OR MACOSX OR IOS OR BSD)
list(APPEND aemu_postoffice_client_obj
aemu_postoffice/client/sock_impl_linux.c
aemu_postoffice_client/sock_impl_linux.c
)
endif()
add_library(aemu_postoffice_client STATIC
${aemu_postoffice_client_obj}
)
# Its close handshake parks on libretro-common's eventcount.
target_link_libraries(aemu_postoffice_client libretro-common)
set(FT_REQUIRE_ZLIB OFF CACHE BOOL "" FORCE)
set(FT_REQUIRE_BZIP2 OFF CACHE BOOL "" FORCE)
+674
View File
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+14
View File
@@ -0,0 +1,14 @@
# aemu_postoffice client
The client half of https://github.com/Kethen/aemu_postoffice (branch `ppsspp`,
commit 9e53363), as the core builds it. GPLv3, see LICENSE.txt.
Changed from upstream:
- Nothing sleeps or yields. Connects wait for the socket once with their
timeout, blocked sends and receives wait for the socket, and a close stops
new operations, wakes blocked ones with `shutdown()`, and parks on a
libretro-common eventcount until those in flight are done.
- `ptp_connect_v4`/`_v6` take a `cancel_fd`: a socket that becomes readable
to give the connect up, so a close doesn't wait one out.
- The PSP, stdc and test builds, and the server, are left out.
@@ -0,0 +1,53 @@
#ifndef __AEMU_POSTOFFICE_PACKETS_H
#define __AEMU_POSTOFFICE_PACKETS_H
#include <stdint.h>
// current limits on send/recv calls
#define AEMU_POSTOFFICE_PDP_BLOCK_MAX (10 * 1024)
#define AEMU_POSTOFFICE_PTP_BLOCK_MAX (50 * 1024)
// structs here are gcc little endian
typedef enum{
AEMU_POSTOFFICE_INIT_PDP = 0,
AEMU_POSTOFFICE_INIT_PTP_LISTEN = 1,
AEMU_POSTOFFICE_INIT_PTP_CONNECT = 2,
AEMU_POSTOFFICE_INIT_PTP_ACCEPT = 3
} aemu_postoffice_init_type;
#pragma pack(push, 1)
// client -> server
typedef struct aemu_postoffice_init{
int32_t init_type;
// 2 bytes on padding on the end, only 6 bytes are used
char src_addr[8];
uint16_t sport;
// 2 bytes on padding on the end
char dst_addr[8];
uint16_t dport;
} aemu_postoffice_init;
// client <-> server
typedef struct aemu_postoffice_pdp{
// 2 bytes on padding on the end
char addr[8];
uint16_t port;
uint32_t size;
} aemu_postoffice_pdp;
// server -> client
typedef struct aemu_postoffice_ptp_connect{
// 2 bytes on padding on the end
char addr[8];
uint16_t port;
} aemu_postoffice_ptp_connect;
typedef struct aemu_postoffice_ptp_data{
uint32_t size;
} aemu_postoffice_ptp_data;
#pragma pack(pop)
#endif
+19
View File
@@ -0,0 +1,19 @@
#ifndef __LOG_IMPL_H
#define __LOG_IMPL_H
#ifdef __cplusplus
extern "C" {
#endif
// on the psp, this points to the printk function provided by aemu
extern int (*log_func)(const char *format, ...);
#define LOG(...){ \
log_func(__VA_ARGS__); \
}
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,27 @@
#include "Common/Log.h"
#include <stdio.h>
#include <stdarg.h>
static int log_impl(const char *format, ...){
va_list args;
char log_buf[2048] = {0};
va_start(args, format);
int len = vsnprintf(log_buf, sizeof(log_buf), format, args);
va_end(args);
if (len > 0){
// chew off the ending '\n' which is a standard in this project, but not in PPSSPP
log_buf[len - 1] = '\0';
ERROR_LOG(Log::sceNet, "%s", log_buf);
}
return 0;
}
extern "C" {
int (*log_func)(const char *format, ...) = log_impl;
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef __MUTEX_IMPL_H
#define __MUTEX_IMPL_H
#ifdef __cplusplus
extern "C" {
#endif
void init_drain_mutex();
void lock_drain_mutex();
void unlock_drain_mutex();
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,15 @@
#include <mutex>
extern "C" {
static std::mutex drain_mutex;
void init_drain_mutex(){
}
void lock_drain_mutex(){
drain_mutex.lock();
}
void unlock_drain_mutex(){
drain_mutex.unlock();
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,81 @@
#ifndef __POSTOFFICE_CLIENT_H
#define __POSTOFFICE_CLIENT_H
#include <stdbool.h>
#include <stdint.h>
// current limits on send/recv calls
#define AEMU_POSTOFFICE_PDP_BLOCK_MAX (10 * 1024)
#define AEMU_POSTOFFICE_PTP_BLOCK_MAX (50 * 1024)
enum aemu_postoffice_client_errors {
AEMU_POSTOFFICE_CLIENT_OK = 0,
AEMU_POSTOFFICE_CLIENT_UNKNOWN = -1,
AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY = -2,
AEMU_POSTOFFICE_CLIENT_SESSION_DEAD = -3,
AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK = -4,
AEMU_POSTOFFICE_CLIENT_SESSION_DATA_TRUNC = -5,
AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK = -6
};
struct aemu_post_office_sock_addr{
uint32_t addr; // network order
uint16_t port; // network order
};
struct aemu_post_office_sock6_addr{
uint8_t addr[16]; // network order
uint16_t port; // network order
};
#ifdef __cplusplus
extern "C" {
#endif
int aemu_post_office_init();
/*
* Thread safety:
* multiple threads can perform create/listen/connect
* only one thread at a time can accept on a created socket
* only one thread at a time can send on a created socket
* only one thread at a time can recv/peek on a created socket
* only one thread at a time can close a created socket
*
* violating the above yields undefined results
*/
void *pdp_create_v6(const struct aemu_post_office_sock6_addr *addr, const char *pdp_mac, int pdp_port, int *state);
void *pdp_create_v4(const struct aemu_post_office_sock_addr *addr, const char *pdp_mac, int pdp_port, int *state);
void pdp_delete(void *pdp_handle);
int pdp_send(void *pdp_handle, const char *pdp_mac, int pdp_port, const char *buf, int len, bool non_block);
int pdp_recv(void *pdp_handle, char *pdp_mac, int *pdp_port, char *buf, int *len, bool non_block);
int pdp_peek_next_size(void *pdp_handle);
int pdp_buffered_data_size(void *pdp_handle);
int pdp_send_buf_not_full(void *pdp_handle);
int pdp_is_dead(void *ptp_handle);
void *ptp_listen_v6(const struct aemu_post_office_sock6_addr *addr, const char *ptp_mac, int ptp_port, int *state);
void *ptp_listen_v4(const struct aemu_post_office_sock_addr *addr, const char *ptp_mac, int ptp_port, int *state);
void *ptp_accept(void *ptp_listen_handle, char *ptp_mac, int *ptp_port, bool nonblock, int *state);
/* cancel_fd: -1, or a socket that becomes readable to give the connect up. */
void *ptp_connect_v6(const struct aemu_post_office_sock6_addr *addr, const char *src_ptp_mac, int ptp_sport, const char *dst_ptp_mac, int ptp_dport, int *state, int cancel_fd);
void *ptp_connect_v4(const struct aemu_post_office_sock_addr *addr, const char *src_ptp_mac, int ptp_sport, const char *dst_ptp_mac, int ptp_dport, int *state, int cancel_fd);
int ptp_send(void *ptp_handle, const char *buf, int len, bool non_block);
int ptp_recv(void *ptp_handle, char *buf, int *len, bool non_block);
void ptp_close(void *ptp_handle);
void ptp_listen_close(void *ptp_listen_handle);
int ptp_peek_next_size(void *ptp_handle);
int ptp_send_buf_not_full(void *pdp_handle);
int ptp_listen_has_request(void *pdp_listen_handler);
int ptp_is_dead(void *ptp_handle);
int pdp_get_native_sock(void *pdp_handle);
int ptp_get_native_sock(void *ptp_handle);
int ptp_listen_get_native_sock(void *ptp_listen_handle);
int ptp_listen_is_dead(void *ptp_listen_handle);
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,70 @@
#ifndef __POSTOFFICE_MEM_H
#define __POSTOFFICE_MEM_H
#include <stdint.h>
#include <retro_atomic.h>
#include "sock_impl.h"
#include "postoffice_client.h"
#include "aemu_postoffice_packets.h"
struct pdp_session{
char *pdp_mac[6];
int16_t pdp_port;
int sock;
bool dead;
/* Set by a close; operations in flight are counted so the close can wait them out. */
retro_atomic_int_t abort;
retro_atomic_int_t inflight;
retro_atomic_int_t claimed;
char recv_ring_buf[AEMU_POSTOFFICE_PDP_BLOCK_MAX + sizeof(aemu_postoffice_pdp)];
int recv_ring_buf_start;
int recv_ring_buf_used;
int buffered_data;
int bytes_till_next_header;
int last_block_size;
};
struct ptp_listen_session{
char *ptp_mac[6];
int16_t ptp_port;
int sock;
bool dead;
retro_atomic_int_t abort;
retro_atomic_int_t inflight;
retro_atomic_int_t claimed;
char addr[sizeof(native_sock6_addr) > sizeof(native_sock_addr) ? sizeof(native_sock6_addr) : sizeof(native_sock_addr)];
int addrlen;
};
struct ptp_session{
int sock;
bool dead;
retro_atomic_int_t abort;
retro_atomic_int_t inflight;
retro_atomic_int_t claimed;
char recv_ring_buf[AEMU_POSTOFFICE_PTP_BLOCK_MAX];
int recv_ring_buf_start;
int recv_ring_buf_used;
int bytes_till_next_header;
};
#ifdef __cplusplus
extern "C" {
#endif
extern int NUM_PDP_SESSIONS;
extern int NUM_PTP_LISTEN_SESSIONS;
extern int NUM_PTP_SESSIONS;
extern struct pdp_session *pdp_sessions;
extern struct ptp_listen_session *ptp_listen_sessions;
extern struct ptp_session *ptp_sessions;
void init_postoffice_mem();
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,18 @@
#include "postoffice_mem.h"
#define SESSIONS_PER_TYPE 32
int NUM_PDP_SESSIONS = SESSIONS_PER_TYPE;
int NUM_PTP_LISTEN_SESSIONS = SESSIONS_PER_TYPE;
int NUM_PTP_SESSIONS = SESSIONS_PER_TYPE;
struct pdp_session _pdp_sessions[SESSIONS_PER_TYPE];
struct ptp_listen_session _ptp_listen_sessions[SESSIONS_PER_TYPE];
struct ptp_session _ptp_sessions[SESSIONS_PER_TYPE];
struct pdp_session *pdp_sessions = _pdp_sessions;
struct ptp_listen_session *ptp_listen_sessions = _ptp_listen_sessions;
struct ptp_session *ptp_sessions = _ptp_sessions;
void init_postoffice_mem(){
}
+48
View File
@@ -0,0 +1,48 @@
#ifndef __SOCK_IMPL_COMMON_H
#define __SOCK_IMPL_COMMON_H
#include <stdbool.h>
#if defined(__unix) || defined(__APPLE__) || defined(__PSP__)
#include <netinet/in.h>
#endif
#ifdef _WIN32
#include <winsock2.h>
#include <ws2ipdef.h>
#include <windows.h>
#endif
#include "postoffice_client.h"
typedef struct sockaddr_in native_sock_addr;
#ifdef __PSP__
typedef int native_sock6_addr;
#else
typedef struct sockaddr_in6 native_sock6_addr;
#endif
#include <retro_atomic.h>
#define NATIVE_SOCK_ABORTED -100
void to_native_sock_addr(native_sock_addr *dst, const struct aemu_post_office_sock_addr *src);
void to_native_sock6_addr(native_sock6_addr *dst, const struct aemu_post_office_sock6_addr *src);
/* cancel_fd: -1, or a socket that becomes readable to give up a wait. */
int native_connect_tcp_sock(void *addr, int addrlen, int cancel_fd);
int native_close_tcp_sock(int sock);
/* Ends any wait blocked on sock, for a close from another thread. */
int native_shutdown_tcp_sock(int sock);
int native_send_till_done(int fd, const char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd);
int native_recv_till_done(int fd, char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd);
/* Blocks until fd is readable (or writable), cancel_fd is readable, or timeout_ms
passes (< 0: no limit). 1 ready, 0 timeout, NATIVE_SOCK_ABORTED cancelled, -1 error. */
int native_wait(int fd, bool for_write, int timeout_ms, int cancel_fd);
int native_recv(int fd, char *buf, int len);
int native_peek(int fd, char *buf, int len);
bool native_send_buf_not_full(int fd);
bool native_hung_up(int fd);
#endif
@@ -0,0 +1,285 @@
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <string.h>
#include <stdio.h>
#include "postoffice_client.h"
#include "sock_impl.h"
#include "log_impl.h"
void to_native_sock_addr(native_sock_addr *dst, const struct aemu_post_office_sock_addr *src){
dst->sin_family = AF_INET;
dst->sin_addr.s_addr = src->addr;
dst->sin_port = src->port;
}
void to_native_sock6_addr(native_sock6_addr *dst, const struct aemu_post_office_sock6_addr *src){
dst->sin6_family = AF_INET6;
dst->sin6_port = src->port;
dst->sin6_flowinfo = 0;
memcpy(dst->sin6_addr.s6_addr, src->addr, 16);
dst->sin6_scope_id = 0;
}
int native_wait(int fd, bool for_write, int timeout_ms, int cancel_fd){
struct pollfd pfd[2];
int count = 1;
pfd[0].fd = fd;
pfd[0].events = for_write ? POLLOUT : POLLIN;
pfd[0].revents = 0;
if (cancel_fd >= 0){
pfd[1].fd = cancel_fd;
pfd[1].events = POLLIN;
pfd[1].revents = 0;
count = 2;
}
int result;
do {
result = poll(pfd, count, timeout_ms);
} while (result < 0 && errno == EINTR);
if (result < 0){
return -1;
}
if (result == 0){
return 0;
}
if (count == 2 && (pfd[1].revents & POLLIN)){
return NATIVE_SOCK_ABORTED;
}
/* POLLERR / POLLHUP count as ready: the next call on the socket reports them. */
return 1;
}
static int connect_with_timeout(int sock, void *addr, int addrlen, int timeout_ms, int *error, int cancel_fd){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
int ret = 0;
if (connect(sock, (struct sockaddr *)addr, addrlen) != 0){
*error = errno;
if (*error != EINPROGRESS && *error != EAGAIN && *error != EALREADY){
return -1;
}
/* One wait for the outcome, a cancel, or the timeout. */
int wait = native_wait(sock, true, timeout_ms, cancel_fd);
if (wait == 0){
*error = ETIMEDOUT;
ret = -1;
}else if (wait == NATIVE_SOCK_ABORTED){
*error = ECANCELED;
ret = -1;
}else if (wait < 0){
*error = errno;
ret = -1;
}else{
int so_error = 0;
socklen_t so_len = sizeof(so_error);
getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_error, &so_len);
*error = so_error;
ret = so_error == 0 ? 0 : -1;
}
}
// just for completness, NBIO is used on the socket after connection anyway
flags = fcntl(sock, F_GETFL, 0);
flags &= ~O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
return ret;
}
int native_connect_tcp_sock(void *addr, int addrlen, int cancel_fd){
native_sock_addr *native_addr = addr;
int sock = socket(native_addr->sin_family, SOCK_STREAM, 0);
if (sock == -1){
LOG("%s: failed creating socket, %s\n", __func__, strerror(errno));
return AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK;
}
// XXX this restricts latency to 500ms, if a server is even further away, connection won't be possible
// Connect
int error = 0;
int connect_status = connect_with_timeout(sock, addr, addrlen, 5000, &error, cancel_fd);
if (connect_status == -1){
LOG("%s: failed connecting, %s\n", __func__, strerror(error));
close(sock);
return AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK;
}
// Set socket options
socklen_t sockopt = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &sockopt, sizeof(sockopt));
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
sockopt = 2626560;
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sockopt, sizeof(sockopt));
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof(sockopt));
#ifndef __linux__
sockopt = 1;
setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &sockopt, sizeof(sockopt));
#endif
// Show some socket options
unsigned int opt_len = sizeof(sockopt);
sockopt = 0;
int get_ret = getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &sockopt, &opt_len);
LOG("%s: TCP_NODELAY is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? errno : 0);
opt_len = sizeof(sockopt);
sockopt = 0;
get_ret = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sockopt, &opt_len);
LOG("%s: SO_SNDBUF is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? errno : 0);
opt_len = sizeof(sockopt);
sockopt = 0;
get_ret = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &sockopt, &opt_len);
LOG("%s: SO_RCVBUF is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? errno : 0);
#ifndef __linux__
opt_len = sizeof(sockopt);
sockopt = 0;
get_ret = getsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &sockopt, &opt_len);
LOG("%s: SO_NOSIGPIPE is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? errno : 0);
#endif
return sock;
}
int native_send_till_done(int fd, const char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd){
int write_offset = 0;
while(write_offset != len){
if (retro_atomic_load_acquire_int(abort)){
return NATIVE_SOCK_ABORTED;
}
#ifdef __linux__
int write_status = send(fd, &buf[write_offset], len - write_offset, MSG_NOSIGNAL);
#else
int write_status = send(fd, &buf[write_offset], len - write_offset, 0);
#endif
if (write_status == -1){
int err = errno;
if (err == EAGAIN || err == EWOULDBLOCK){
if (non_block && write_offset == 0){
return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK;
}
/* Block until it can go on: in block mode, or part of the message is out already. */
if (native_wait(fd, true, -1, cancel_fd) == NATIVE_SOCK_ABORTED){
return NATIVE_SOCK_ABORTED;
}
continue;
}
// Other errors
LOG("%s: failed sending, %s\n", __func__, strerror(errno));
return write_status;
}
write_offset += write_status;
}
return write_offset;
}
int native_recv(int fd, char *buf, int len){
int recv_status = recv(fd, buf, len, 0);
if (recv_status == 0){
return recv_status;
}
if (recv_status < 0){
int err = errno;
if (err == EAGAIN || err == EWOULDBLOCK){
return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK;
}
// Other errors
LOG("%s: failed receving, %s\n", __func__, strerror(errno));
return recv_status;
}
return recv_status;
}
int native_recv_till_done(int fd, char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd){
int read_offset = 0;
while(read_offset != len){
if (retro_atomic_load_acquire_int(abort)){
return NATIVE_SOCK_ABORTED;
}
int recv_status = native_recv(fd, &buf[read_offset], len - read_offset);
if (recv_status == 0){
return recv_status;
}
if (recv_status < 0){
if (recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){
if (non_block && read_offset == 0){
return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK;
}
/* Block until more comes: in block mode, or part of the message is in already. */
if (native_wait(fd, false, -1, cancel_fd) == NATIVE_SOCK_ABORTED){
return NATIVE_SOCK_ABORTED;
}
continue;
}
return recv_status;
}
read_offset += recv_status;
}
return read_offset;
}
int native_close_tcp_sock(int sock){
return close(sock);
}
int native_shutdown_tcp_sock(int sock){
return shutdown(sock, SHUT_RDWR);
}
int native_peek(int fd, char *buf, int len){
int read_result = recv(fd, buf, len, MSG_PEEK);
if (read_result == 0){
return 0;
}
if (read_result == -1){
int err = errno;
if (err == EAGAIN || err == EWOULDBLOCK){
return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK;
}
LOG("%s: failed peeking, %s\n", __func__, strerror(errno));
return -1;
}
return read_result;
}
bool native_send_buf_not_full(int fd){
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLWRNORM;
pfd.revents = 0;
poll(&pfd, 1, 0);
if (pfd.revents & POLLHUP){
return false;
}
if (pfd.revents & POLLWRNORM){
return true;
}
return false;
}
bool native_hung_up(int fd){
uint8_t buf;
int peek_result = native_peek(fd, (char *)&buf, sizeof(buf));
if (peek_result == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK ||
peek_result == sizeof(buf)
){
return false;
}
return true;
}
@@ -0,0 +1,278 @@
#include <winsock2.h>
#include <windows.h>
#include <string.h>
#include <stdio.h>
#include "postoffice_client.h"
#include "sock_impl.h"
#include "log_impl.h"
void to_native_sock_addr(native_sock_addr *dst, const struct aemu_post_office_sock_addr *src){
dst->sin_family = AF_INET;
dst->sin_addr.s_addr = src->addr;
dst->sin_port = src->port;
memset(dst->sin_zero, 0, sizeof(dst->sin_zero));
}
void to_native_sock6_addr(native_sock6_addr *dst, const struct aemu_post_office_sock6_addr *src){
dst->sin6_family = AF_INET6;
dst->sin6_port = src->port;
dst->sin6_flowinfo = 0;
memcpy(dst->sin6_addr.s6_addr, src->addr, 16);
dst->sin6_scope_id = 0;
}
static void init_winsock2(){
static bool initialized = false;
if (!initialized){
initialized = true;
WSADATA data;
int init_result = WSAStartup(MAKEWORD(2,2), &data);
if (init_result != 0){
printf("%s: warning: WSAStartup seems to have failed, %d\n", __func__, init_result);
}
}
}
int native_wait(int fd, bool for_write, int timeout_ms, int cancel_fd){
fd_set rfds, wfds, efds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
FD_SET((SOCKET)fd, for_write ? &wfds : &rfds);
/* A failed non-blocking connect is reported as an exception on Windows. */
FD_SET((SOCKET)fd, &efds);
if (cancel_fd >= 0){
FD_SET((SOCKET)cancel_fd, &rfds);
}
struct timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
int result = select(0, &rfds, &wfds, &efds, timeout_ms < 0 ? NULL : &tv);
if (result == SOCKET_ERROR){
return -1;
}
if (result == 0){
return 0;
}
if (cancel_fd >= 0 && FD_ISSET((SOCKET)cancel_fd, &rfds)){
return NATIVE_SOCK_ABORTED;
}
return 1;
}
static int connect_with_timeout(int sock, native_sock_addr *addr, int addrlen, int timeout_ms, int *error, int cancel_fd){
u_long ioctlopt = 1;
ioctlsocket(sock, FIONBIO, &ioctlopt);
int ret = 0;
if (connect(sock, (struct sockaddr *)addr, addrlen) != 0){
*error = WSAGetLastError();
/* it was found out together with @hrydgard that after using WinHttp, it could also throw WSAEINVAL for WSAEALREADY */
if (*error == WSAEISCONN){
*error = 0;
}else if (*error != WSAEWOULDBLOCK && *error != WSAEALREADY && *error != WSAEINVAL){
ret = -1;
}else{
/* One wait for the outcome, a cancel, or the timeout. */
int wait = native_wait(sock, true, timeout_ms, cancel_fd);
if (wait == 0){
*error = WSAETIMEDOUT;
ret = -1;
}else if (wait == NATIVE_SOCK_ABORTED){
*error = WSAECANCELLED;
ret = -1;
}else if (wait < 0){
*error = WSAGetLastError();
ret = -1;
}else{
int so_error = 0;
int so_len = sizeof(so_error);
getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&so_error, &so_len);
*error = so_error;
ret = so_error == 0 ? 0 : -1;
}
}
}
// just for completness, NBIO is used on the socket after connection anyway
ioctlopt = 0;
ioctlsocket(sock, FIONBIO, &ioctlopt);
return ret;
}
int native_connect_tcp_sock(void *addr, int addrlen, int cancel_fd){
init_winsock2();
native_sock_addr *native_addr = addr;
SOCKET win_sock = socket(native_addr->sin_family, SOCK_STREAM, 0);
if (win_sock == INVALID_SOCKET){
LOG("%s: failed creating socket, %d\n", __func__, WSAGetLastError());
return AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK;
}
int sock = win_sock;
// XXX need to simulate timeout on windows, there's no sockopt for that
// this also restricts latency to 500ms, if a server is even further away, connection won't be possible
// Connect
int error = 0;
int connect_status = connect_with_timeout(sock, addr, addrlen, 5000, &error, cancel_fd);
if (connect_status == -1){
LOG("%s: failed connecting, %d\n", __func__, error);
closesocket(sock);
return AEMU_POSTOFFICE_CLIENT_SESSION_NETWORK;
}
// Set socket options
int sockopt = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&sockopt, sizeof(sockopt));
u_long ioctlopt = 1;
ioctlsocket(sock, FIONBIO, &ioctlopt);
sockopt = 2626560;
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&sockopt, sizeof(sockopt));
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&sockopt, sizeof(sockopt));
// Show some socket options
int opt_len = sizeof(sockopt);
sockopt = 0;
int get_ret = getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&sockopt, &opt_len);
LOG("%s: TCP_NODELAY is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? WSAGetLastError() : 0);
opt_len = sizeof(sockopt);
sockopt = 0;
get_ret = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&sockopt, &opt_len);
LOG("%s: SO_SNDBUF is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? WSAGetLastError() : 0);
opt_len = sizeof(sockopt);
sockopt = 0;
get_ret = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&sockopt, &opt_len);
LOG("%s: SO_RCVBUF is %d (0x%x)\n", __func__, sockopt, get_ret == -1 ? WSAGetLastError() : 0);
return sock;
}
int native_send_till_done(int fd, const char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd){
int write_offset = 0;
while(write_offset != len){
if (retro_atomic_load_acquire_int(abort)){
return NATIVE_SOCK_ABORTED;
}
int write_status = send(fd, &buf[write_offset], len - write_offset, 0);
if (write_status == -1){
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK || err == WSAEINPROGRESS){
if (non_block && write_offset == 0){
return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK;
}
/* Block until it can go on: in block mode, or part of the message is out already. */
if (native_wait(fd, true, -1, cancel_fd) == NATIVE_SOCK_ABORTED){
return NATIVE_SOCK_ABORTED;
}
continue;
}
// Other errors
LOG("%s: failed sending, %d\n", __func__, err);
return write_status;
}
write_offset += write_status;
}
return write_offset;
}
int native_recv(int fd, char *buf, int len){
int recv_status = recv(fd, buf, len, 0);
if (recv_status == 0){
return recv_status;
}
if (recv_status < 0){
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK || err == WSAEINPROGRESS){
return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK;
}
// Other errors
LOG("%s: failed receving, %d\n", __func__, err);
return recv_status;
}
return recv_status;
}
int native_recv_till_done(int fd, char *buf, int len, bool non_block, retro_atomic_int_t *abort, int cancel_fd){
int read_offset = 0;
while(read_offset != len){
if (retro_atomic_load_acquire_int(abort)){
return NATIVE_SOCK_ABORTED;
}
int recv_status = native_recv(fd, &buf[read_offset], len - read_offset);
if (recv_status == 0){
return recv_status;
}
if (recv_status < 0){
if (recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK){
if (non_block && read_offset == 0){
return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK;
}
/* Block until more comes: in block mode, or part of the message is in already. */
if (native_wait(fd, false, -1, cancel_fd) == NATIVE_SOCK_ABORTED){
return NATIVE_SOCK_ABORTED;
}
continue;
}
return recv_status;
}
read_offset += recv_status;
}
return read_offset;
}
int native_close_tcp_sock(int sock){
return closesocket(sock);
}
int native_shutdown_tcp_sock(int sock){
return shutdown(sock, SD_BOTH);
}
int native_peek(int fd, char *buf, int len){
int read_result = recv(fd, buf, len, MSG_PEEK);
if (read_result == 0){
return 0;
}
if (read_result == -1){
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK || err == WSAEINPROGRESS){
return AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK;
}
LOG("%s: failed peeking, %d\n", __func__, WSAGetLastError());
return -1;
}
return read_result;
}
bool native_send_buf_not_full(int fd){
WSAPOLLFD pfd;
pfd.fd = fd;
pfd.events = POLLWRNORM;
pfd.revents = 0;
WSAPoll(&pfd, 1, 0);
if (pfd.revents & POLLHUP){
return false;
}
if (pfd.revents & POLLWRNORM){
return true;
}
return false;
}
bool native_hung_up(int fd){
uint8_t buf;
int peek_result = native_peek(fd, (char *)&buf, sizeof(buf));
if (peek_result == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK ||
peek_result == sizeof(buf)
){
return false;
}
return true;
}
+1 -3
View File
@@ -390,9 +390,7 @@ static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &corePar
}
std::string error_string;
while (PSP_InitUpdate(&error_string) == BootState::Booting) {
sleep_ms(1, "auto-test");
}
PSP_InitWait(&error_string);
if (!PSP_IsInited()) {
GitHubActionsPrint("error", "Test init failed for %s", currentTestName.c_str());
+7 -7
View File
@@ -325,6 +325,7 @@ SOURCES_CXX += \
$(COMMONDIR)/Net/HTTPHeaders.cpp \
$(COMMONDIR)/Net/HTTPRequest.cpp \
$(COMMONDIR)/Net/HTTPNaettRequest.cpp \
$(COMMONDIR)/Net/Cancel.cpp \
$(COMMONDIR)/Net/NetBuffer.cpp \
$(COMMONDIR)/Net/Resolve.cpp \
$(COMMONDIR)/Net/Sinks.cpp \
@@ -503,20 +504,19 @@ SOURCES_CXX += \
endif
SOURCES_C += \
$(EXTDIR)/aemu_postoffice/client/postoffice.c \
$(EXTDIR)/aemu_postoffice/client/postoffice_mem_stdc.c
$(EXTDIR)/aemu_postoffice_client/postoffice.c \
$(EXTDIR)/aemu_postoffice_client/postoffice_mem_stdc.c
SOURCES_CXX += \
$(EXTDIR)/aemu_postoffice/client/mutex_impl_cpp.cpp \
$(EXTDIR)/aemu_postoffice/client/delay_impl_cpp.cpp \
$(EXTDIR)/aemu_postoffice/client/log_impl_ppsspp.cpp \
$(EXTDIR)/aemu_postoffice_client/mutex_impl_cpp.cpp \
$(EXTDIR)/aemu_postoffice_client/log_impl_ppsspp.cpp
ifeq ($(PLATFORM_EXT), win32)
SOURCES_C += \
$(EXTDIR)/aemu_postoffice/client/sock_impl_windows.c
$(EXTDIR)/aemu_postoffice_client/sock_impl_windows.c
else
SOURCES_C += \
$(EXTDIR)/aemu_postoffice/client/sock_impl_linux.c
$(EXTDIR)/aemu_postoffice_client/sock_impl_linux.c
endif
SOURCES_CXX += \
+79
View File
@@ -0,0 +1,79 @@
// Socket waits: a wake or a cancel ends one at once, data ends one, and a timeout still does.
#include "Common/Net/SocketCompat.h"
#include <thread>
#include "Common/Net/Cancel.h"
#include "Common/Net/HTTPClient.h"
#include "Common/Net/Resolve.h"
#include "Common/TimeUtil.h"
#include "UnitTest.h"
static uintptr_t LoopbackUDP(sockaddr_in *addr) {
uintptr_t s = (uintptr_t)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
memset(addr, 0, sizeof(*addr));
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
socklen_t len = sizeof(*addr);
bind(s, (sockaddr *)addr, sizeof(*addr));
getsockname(s, (sockaddr *)addr, &len);
return s;
}
bool TestNetWait() {
net::Init();
// A wake ends a wait that has no socket and no timeout, whether it comes first or second.
{
net::WakeSocket wake;
EXPECT_TRUE(wake.Fd() != -1);
std::thread waker([&] { wake.Wake(); });
EXPECT_EQ_INT((int)net::WaitSocketsOrWake(nullptr, nullptr, 0, false, -1.0, &wake), (int)net::WaitResult::CANCELLED);
waker.join();
wake.Drain();
// Drained, so nothing is pending: a zero timeout times out.
EXPECT_EQ_INT((int)net::WaitSocketsOrWake(nullptr, nullptr, 0, false, 0.0, &wake), (int)net::WaitResult::TIMEOUT);
}
// Data ends a wait; a quiet socket times out.
{
sockaddr_in addrA, addrB;
uintptr_t a = LoopbackUDP(&addrA);
uintptr_t b = LoopbackUDP(&addrB);
EXPECT_EQ_INT((int)net::WaitSocket(a, false, 0.05, nullptr), (int)net::WaitResult::TIMEOUT);
const char x = 'x';
sendto(b, &x, 1, 0, (sockaddr *)&addrA, sizeof(addrA));
EXPECT_EQ_INT((int)net::WaitSocket(a, false, 5.0, nullptr), (int)net::WaitResult::READY);
closesocket(a);
closesocket(b);
}
// A cancelled token ends every wait on it, before or during, and stays cancelled.
{
sockaddr_in addr;
uintptr_t a = LoopbackUDP(&addr);
net::CancelToken cancel;
std::thread canceller([&] { cancel.Cancel(); });
EXPECT_EQ_INT((int)net::WaitSocket(a, false, -1.0, &cancel), (int)net::WaitResult::CANCELLED);
canceller.join();
EXPECT_EQ_INT((int)net::WaitSocket(a, false, -1.0, &cancel), (int)net::WaitResult::CANCELLED);
closesocket(a);
}
// A cancel ends a connect that would otherwise wait out its timeout (nothing answers here).
{
http::Client client(nullptr);
if (client.Resolve("10.255.255.1", 9)) {
net::CancelToken cancel;
const double start = time_now_d();
std::thread canceller([&] { cancel.Cancel(); });
EXPECT_FALSE(client.Connect(1, 30.0, &cancel));
canceller.join();
EXPECT_TRUE(time_now_d() - start < 5.0);
}
}
return true;
}
+2
View File
@@ -2938,6 +2938,7 @@ bool TestShaderGenerators();
bool TestSoftwareGPUJit();
bool TestIRPassSimplify();
bool TestThreadManager();
bool TestNetWait();
bool TestVFS();
bool TestZipSlip();
bool TestLzrc();
@@ -3141,6 +3142,7 @@ TestItem availableTests[] = {
TEST_ITEM(Path),
TEST_ITEM(AndroidContentURI),
TEST_ITEM(ThreadManager),
TEST_ITEM(NetWait),
TEST_ITEM(WrapText),
TEST_ITEM(TinySet),
TEST_ITEM(FastVec),