From eceeb7bf4686f0c7727e59e473ecee8189840a35 Mon Sep 17 00:00:00 2001 From: MoHadiShibli <142665133+MoHadiShibli@users.noreply.github.com> Date: Tue, 6 Oct 2026 02:37:51 +0300 Subject: [PATCH] Diagnostic build: log each connection and each notification On firmware 13.52 the service now starts and answers the launcher, but a browser on the network gets its connection closed, and no notification appears. The diagnostic build now logs, for every connection from another device, what it asked for, what it was sent, and why it was closed (send or recv errors with errno, the 10-second timeout with the first byte received), plus the result of every notification request. The diagnostic screen shows its build time and, while Control4Free runs, asks for the address to be opened and page 2 screenshotted. It also reads GoldHEN's SDK version correctly: GoldHEN returns it through the call's error value. --- include/c4f_net.h | 3 +++ launcher/diag.c | 16 ++++++++++------ src/log.c | 5 +++++ src/main.c | 1 + src/net.c | 47 +++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 60 insertions(+), 12 deletions(-) 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;