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.
This commit is contained in:
MoHadiShibli committed 2026-10-06 02:37:51 +03:00
1 parent 056a32f6c9
commit eceeb7bf46
5 files changed
+60 -12

No files matched your search

+3
View File
@@ -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,
+10 -6
View File
@@ -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 "
+5
View File
@@ -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
}
+1
View File
@@ -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,
+41 -6
View File
@@ -14,6 +14,16 @@
#include <unistd.h>
#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;