diff --git a/include/c4f_net.h b/include/c4f_net.h index 6f45701..c6aca4c 100644 --- a/include/c4f_net.h +++ b/include/c4f_net.h @@ -15,6 +15,9 @@ typedef struct C4fNetClient { const unsigned char *body; size_t bodySize, bodySent; void *user; +#ifdef C4F_DIAG + char peer[16]; /* the other end's address, for the diagnostic log */ +#endif } C4fNetClient; enum { C4F_NET_OPEN, C4F_NET_MESSAGE_EVENT, C4F_NET_CLOSE, diff --git a/launcher/diag.c b/launcher/diag.c index 22a7b48..566bde8 100644 --- a/launcher/diag.c +++ b/launcher/diag.c @@ -290,9 +290,11 @@ void c4fDiagStart(int sandboxResult, int sandboxError) version.VersionString[sizeof(version.VersionString) - 1] = 0; snprintf(c4fFirmware, sizeof(c4fFirmware), "\"%s\" = 0x%08x (call returned 0x%08x)", version.VersionString, version.Version, (uint32_t)ret); + /* GoldHEN returns the version as the call's error value (seen on 13.52: + * 256 = SDK 1.00), so only ENOSYS (78) means there is no GoldHEN call. */ long goldHen = c4fGoldHenCommand(0, NULL); - if (goldHen >= 0) snprintf(c4fGoldHen, sizeof(c4fGoldHen), "answers, SDK version 0x%lx", goldHen); - else snprintf(c4fGoldHen, sizeof(c4fGoldHen), "no answer, errno %ld (78 = no GoldHEN SDK call)", -goldHen); + if (goldHen == -78) snprintf(c4fGoldHen, sizeof(c4fGoldHen), "no answer (errno 78: no GoldHEN SDK call)"); + else snprintf(c4fGoldHen, sizeof(c4fGoldHen), "answers, SDK version 0x%lx", goldHen < 0 ? -goldHen : goldHen); if (sandboxResult == 0) snprintf(c4fSandbox, sizeof(c4fSandbox), "left through GoldHEN"); else snprintf(c4fSandbox, sizeof(c4fSandbox), "still inside, errno %d", sandboxError); size_t size = 0; @@ -355,8 +357,9 @@ static char *c4fSummary(const C4fDiagState *s) if (log) c4fLastLine(log, last, sizeof(last)); if (s->running == 1) - c4fAdd(&t, ">> Control4Free is RUNNING (%s). Now try it from your phone. If a controller fails, come back\n" - ">> here and take screenshots of every page again.\n", s->version); + c4fAdd(&t, ">> Control4Free is RUNNING (%s). Now open %s on your phone or PC. Whatever happens, come back\n" + ">> here and take screenshots of pages 1 and 2: page 2 lists every connection it saw.\n", + s->version, s->address[0] ? s->address : "the PS4's address with :4264"); else if (c4fStarted && c4fContains(c4fStartMessage, "PayLoader did not answer")) c4fAdd(&t, ">> GoldHEN's PayLoader (port 9090) did not take the payload. Turn PayLoader on in GoldHEN's\n" ">> settings, then press Cross again.\n"); @@ -541,8 +544,9 @@ void c4fDrawDiag(uint32_t *pixels, const C4fLauncherScreen *s) char line[320]; c4fFillRect(&c, 0, 0, c.w, c.h, C4F_D_BG); - snprintf(line, sizeof(line), "Control4Free DIAGNOSTIC BUILD %s page %d of %d: %s", C4F_LAUNCHER_VERSION, - page + 1, C4F_DIAG_PAGES, c4fPageNames[page]); + /* The build time tells one diagnostic build from the next in a screenshot. */ + snprintf(line, sizeof(line), "Control4Free DIAGNOSTIC BUILD %s (%s %s) page %d of %d: %s", C4F_LAUNCHER_VERSION, + __DATE__, __TIME__, page + 1, C4F_DIAG_PAGES, c4fPageNames[page]); c4fText(&c, C4F_FONT_REGULAR, C4F_D_TITLE, C4F_D_LEFT, 48, C4F_D_ACCENT, line); c4fText(&c, C4F_FONT_REGULAR, C4F_D_SIZE, C4F_D_LEFT, 84, C4F_D_FAINT, "L1 / R1: change page Up / Down: scroll Options: read the kernel log again " diff --git a/src/log.c b/src/log.c index 27bea55..073957f 100644 --- a/src/log.c +++ b/src/log.c @@ -102,5 +102,10 @@ void c4fNotify(const char *fmt, ...) va_start(ap, fmt); (void)vsnprintf(req.message, sizeof(req.message), fmt, ap); va_end(ap); +#ifdef C4F_DIAG + int ret = sceKernelSendNotificationRequest(0, &req, sizeof(req), 0); + c4fLog("notification = 0x%08x: %s\n", (uint32_t)ret, req.message); +#else (void)sceKernelSendNotificationRequest(0, &req, sizeof(req), 0); +#endif } diff --git a/src/main.c b/src/main.c index d685943..2937104 100644 --- a/src/main.c +++ b/src/main.c @@ -118,6 +118,7 @@ static void c4fDiagHost(pid_t pid) C4fProcInfo info; struct sigaction ignore, previous; + c4fLog("diag: built %s %s\n", __DATE__, __TIME__); c4fLog("diag: kernel image base=0x%lx size=0x%lx allproc=0x%lx prison0=0x%lx rootvnode=0x%lx targetid=0x%lx\n", (unsigned long)KERNEL_ADDRESS_IMAGE_BASE, (unsigned long)KERNEL_IMAGE_SIZE, (unsigned long)KERNEL_ADDRESS_ALLPROC, (unsigned long)KERNEL_ADDRESS_PRISON0, diff --git a/src/net.c b/src/net.c index 3b3ee73..8f41076 100644 --- a/src/net.c +++ b/src/net.c @@ -14,6 +14,16 @@ #include #include "c4f_net.h" +#ifdef C4F_DIAG +#include "c4f_log.h" +/* Diagnostic build: each connection from another device, step by step. The + * launcher's status checks over loopback come every 2 seconds and stay out. */ +#define C4F_NET_LOG(c, fmt, ...) do { if (strcmp((c)->peer, "127.0.0.1")) \ + c4fLog("net: %s " fmt "\n", (c)->peer, __VA_ARGS__); } while (0) +#else +#define C4F_NET_LOG(c, fmt, ...) do { } while (0) +#endif + /* Which interface would carry traffic out, without sending anything: a connected * UDP socket picks the route, and getsockname then names the local end. */ void c4fNetLocalAddress(char *out, size_t size) @@ -152,6 +162,7 @@ static void c4fHttpError(C4fNetClient *c, int code, const char *message) { char buf[512]; int n = snprintf(buf, sizeof(buf), "HTTP/1.1 %d Error\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: %zu\r\n\r\n%s", code, strlen(message), message); + C4F_NET_LOG(c, "answered %d %s", code, message); c4fQueue(c, buf, (size_t)n); c->closing = 2; } @@ -165,6 +176,7 @@ static void c4fHttp(C4fNet *net, C4fNetClient *c) if (!end) { if (c->rxUsed == sizeof(c->rx)) c4fHttpError(c, 431, "Headers too large"); return; } used = (size_t)(end-request) + 4; request[used] = 0; + C4F_NET_LOG(c, "asked: %.*s", (int)strcspn(request, "\r\n") > 100 ? 100 : (int)strcspn(request, "\r\n"), request); int statusRequest = !strncmp(request, "GET /api/status HTTP/1.1\r\n", 26); int stopRequest = !strncmp(request, "POST /api/stop HTTP/1.1\r\n", 25); /* The two files a phone needs to keep this page on its home screen. */ @@ -231,17 +243,20 @@ static void c4fHttp(C4fNet *net, C4fNetClient *c) if (!strstr(connection, "upgrade")) { c4fHttpError(c, 400, "Upgrade required"); return; } c4fWebSocketAccept(key, accept); n = snprintf(reply, sizeof(reply), "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n", accept); + C4F_NET_LOG(c, "websocket open%s", ""); c4fQueue(c, reply, (size_t)n); c->websocket = 1; memmove(c->rx, c->rx+used, c->rxUsed-used); c->rxUsed -= used; net->handler(c, C4F_NET_OPEN, NULL, 0, net->context); } else if (asset) { char reply[256]; int n = snprintf(reply, sizeof(reply), "HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %zu\r\nConnection: close\r\nCache-Control: max-age=86400\r\nX-Content-Type-Options: nosniff\r\n\r\n", assetType, assetSize); + C4F_NET_LOG(c, "sending %s, %zu bytes", assetType, assetSize); c4fQueue(c, reply, (size_t)n); c->body = asset; c->bodySize = assetSize; c->rxUsed = 0; } else { char reply[768]; int n = snprintf(reply, sizeof(reply), "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Encoding: gzip\r\nContent-Length: %zu\r\nConnection: close\r\nCache-Control: no-store\r\nX-Content-Type-Options: nosniff\r\nReferrer-Policy: no-referrer\r\nContent-Security-Policy: default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src 'self' data:; media-src data:; connect-src ws:; manifest-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'\r\n\r\n", c4fPageSize); + C4F_NET_LOG(c, "sending the page, %zu bytes", c4fPageSize); c4fQueue(c, reply, (size_t)n); c->body = c4fPage; c->bodySize = c4fPageSize; c->rxUsed = 0; } @@ -343,6 +358,12 @@ int c4fNetPoll(C4fNet *net, int timeoutMs) return -1; for (index = 0; index < C4F_NET_CLIENTS && net->clients[index].fd >= 0; index++) {} if (fd >= 0) { +#ifdef C4F_DIAG + char peerName[16] = "?"; + inet_ntop(AF_INET, &peer.sin_addr, peerName, sizeof(peerName)); + if (index == C4F_NET_CLIENTS || fd >= (int)FD_SETSIZE) + c4fLog("net: %s turned away: fd %d, %s\n", peerName, fd, index == C4F_NET_CLIENTS ? "all slots taken" : "fd too high"); +#endif if (index == C4F_NET_CLIENTS || fd >= (int)FD_SETSIZE || fcntl(fd, F_SETFL, O_NONBLOCK)) close(fd); else { int one = 1; @@ -351,6 +372,10 @@ int c4fNetPoll(C4fNet *net, int timeoutMs) setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); #endif net->clients[index].fd = fd; net->clients[index].openedMs = c4fTimeMs(); +#ifdef C4F_DIAG + memcpy(net->clients[index].peer, peerName, sizeof(peerName)); + C4F_NET_LOG(&net->clients[index], "connected: slot %d, fd %d", index, fd); +#endif } } } @@ -359,8 +384,10 @@ int c4fNetPoll(C4fNet *net, int timeoutMs) if (c->fd < 0) continue; if (FD_ISSET(c->fd, &rd)) { ssize_t n = recv(c->fd, c->rx+c->rxUsed, sizeof(c->rx)-c->rxUsed, 0); - if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) c->closing = 1; - else if (n > 0) { + if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) { + C4F_NET_LOG(c, "recv = %zd, errno %d: it closed its end", n, n < 0 ? errno : 0); + c->closing = 1; + } else if (n > 0) { c->rxUsed += (size_t)n; if (!c->websocket) c4fHttp(net, c); if (c->websocket) c4fWs(net, c); @@ -376,15 +403,23 @@ int c4fNetPoll(C4fNet *net, int timeoutMs) flags = MSG_NOSIGNAL; #endif ssize_t n = send(c->fd, data, left, flags); - if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) c->closing = 1; - else if (n > 0) { + if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { + C4F_NET_LOG(c, "send of %zu bytes failed, errno %d (%s, %zu sent)", left, errno, + body ? "body" : "header", body ? c->bodySent : c->txSent); + c->closing = 1; + } else if (n > 0) { if (body) { c->bodySent += (size_t)n; - if (c->bodySent == c->bodySize) { c->body = NULL; c->closing = 1; } + if (c->bodySent == c->bodySize) { C4F_NET_LOG(c, "sent all %zu bytes", c->bodySize); c->body = NULL; c->closing = 1; } } else { c->txSent += (size_t)n; if (c->txSent == c->txUsed) c->txSent = c->txUsed = 0; } } } - if (!c->websocket && c4fTimeMs()-c->openedMs > 10000) c->closing = 1; + if (!c->websocket && c4fTimeMs()-c->openedMs > 10000) { + /* A first byte of 0x16 is a TLS handshake: the browser tried https. */ + C4F_NET_LOG(c, "timed out after 10 s with %zu bytes unanswered, first byte 0x%02x", c->rxUsed, + c->rxUsed ? c->rx[0] : 0); + c->closing = 1; + } if (c->closing == 1 || (c->closing == 2 && c->txSent == c->txUsed)) c4fDrop(net, c); } return 0;