mirror of
https://github.com/MoHadiShibli/Control4Free.git
synced 2026-10-17 15:01:05 +02:00
Diagnostic build: a package that shows why Control4Free does not start
For consoles it fails on, so a user can send screenshots instead of digging out log files. `make C4F_DIAG=1` and `make -C launcher C4F_DIAG=1` (or tools/build-launcher.ps1 -Diag) build Control4Free-<version>-diag.pkg, listed as "Control4Free (diagnostic)". Its app shows plain pages in place of the normal screen: a summary with a verdict (firmware, GoldHEN, sandbox, auto-start files, the service and the last start), the service log and the previous one, and the kernel log, filtered and in full. The kernel log is read only in short windows, at start-up, around a start and on Options, since Control4Free needs it to sign controllers in. The pages are also saved to /data/control4free/diag-report.txt, and to a USB stick when one is plugged in. The payload says on screen that it started, and logs the kernel addresses the SDK found and the process PayLoader runs it in.
This commit is contained in:
1 parent
94e4a7ecd7
commit
745c16decf
11 files changed
+794
-17
No files matched your search
@@ -3,6 +3,7 @@
|
||||
# Build:
|
||||
# make # build/control4free.elf
|
||||
# make C4F_PROBE=1 # plus the rumble/light-bar research probe (dev only)
|
||||
# make C4F_DIAG=1 # diagnostic build for a console it fails on
|
||||
# make clean
|
||||
#
|
||||
# A build needs the ps4-payload-sdk; docker/Dockerfile has it, and
|
||||
@@ -29,6 +30,15 @@ ifdef C4F_PROBE
|
||||
OBJDIR := build/probe
|
||||
endif
|
||||
|
||||
# Diagnostic build: logs what the SDK found in the kernel and the host process,
|
||||
# and says on screen that it started. The launcher's C4F_DIAG build carries it.
|
||||
ifdef C4F_DIAG
|
||||
EXTRA_CFLAGS += -DC4F_DIAG
|
||||
VERSION := $(VERSION)-diag
|
||||
ELF := build/$(NAME)-diag.elf
|
||||
OBJDIR := build/diag
|
||||
endif
|
||||
|
||||
OBJECTS := $(SOURCES:src/%.c=$(OBJDIR)/%.o) $(OBJDIR)/client.o $(OBJDIR)/assets.o $(OBJDIR)/qrcodegen.o
|
||||
|
||||
CFLAGS += -std=gnu11 -Wall -Wextra -Wpointer-arith -g -O2
|
||||
|
||||
+15
-4
@@ -4,12 +4,23 @@ endif
|
||||
SDK := $(OO_PS4_TOOLCHAIN)
|
||||
VERSION := $(shell cat ../VERSION)
|
||||
OUT := ../build/launcher
|
||||
PAYLOAD := ../build/control4free.elf
|
||||
OBJECTS :=
|
||||
# Diagnostic build: pages of everything that says why Control4Free does not
|
||||
# start, in place of the normal screen, with the diagnostic payload inside.
|
||||
ifdef C4F_DIAG
|
||||
VERSION := $(VERSION)-diag
|
||||
OUT := ../build/launcher-diag
|
||||
PAYLOAD := ../build/control4free-diag.elf
|
||||
DIAG_FLAGS := -DC4F_DIAG
|
||||
OBJECTS += $(OUT)/diag.o
|
||||
endif
|
||||
PKGDIR := $(OUT)/pkg
|
||||
CC := clang-18
|
||||
CXX := clang++-18
|
||||
LD := ld.lld-18
|
||||
CFLAGS := --target=x86_64-pc-freebsd12-elf -fPIC -funwind-tables -O2 -Wall -Wextra -Werror -MMD -MP -D_DEFAULT_SOURCE -isysroot $(SDK) -isystem $(SDK)/include -I../vendor/jsmn -I../vendor/qrcodegen -I../vendor/stb -DC4F_LAUNCHER_VERSION='"$(VERSION)"'
|
||||
OBJECTS := $(OUT)/main.o $(OUT)/screen.o $(OUT)/draw.o $(OUT)/service.o $(OUT)/autorun.o $(OUT)/sandbox.o $(OUT)/logo.o $(OUT)/qrcodegen.o $(OUT)/stb_truetype.o $(OUT)/fonts.o
|
||||
CFLAGS := --target=x86_64-pc-freebsd12-elf -fPIC -funwind-tables -O2 -Wall -Wextra -Werror -MMD -MP -D_DEFAULT_SOURCE -isysroot $(SDK) -isystem $(SDK)/include -I../vendor/jsmn -I../vendor/qrcodegen -I../vendor/stb -DC4F_LAUNCHER_VERSION='"$(VERSION)"' $(DIAG_FLAGS)
|
||||
OBJECTS += $(OUT)/main.o $(OUT)/screen.o $(OUT)/draw.o $(OUT)/service.o $(OUT)/autorun.o $(OUT)/sandbox.o $(OUT)/logo.o $(OUT)/qrcodegen.o $(OUT)/stb_truetype.o $(OUT)/fonts.o
|
||||
LIBS := -lc -lkernel -lSceVideoOut -lScePad -lSceUserService -lSceSystemService -lSceNetCtl
|
||||
FONTS := ../vendor/roboto/Roboto-Light.ttf ../vendor/roboto/Roboto-Regular.ttf
|
||||
.PHONY: all package
|
||||
@@ -34,5 +45,5 @@ $(OUT)/fonts.o: $(OUT)/fonts.c
|
||||
$(CC) $(CFLAGS) -std=gnu11 -c $< -o $@
|
||||
$(OUT)/launcher.elf: $(OBJECTS)
|
||||
$(LD) -m elf_x86_64 -pie --script $(SDK)/link.x --eh-frame-hdr -o $@ $(OBJECTS) -L$(SDK)/lib $(LIBS) $(SDK)/lib/crt1.o
|
||||
package: $(OUT)/launcher.elf ../build/control4free.elf
|
||||
python3 ../tools/package_launcher.py --sdk $(SDK)
|
||||
package: $(OUT)/launcher.elf $(PAYLOAD)
|
||||
python3 ../tools/package_launcher.py --sdk $(SDK) $(if $(C4F_DIAG),--diag)
|
||||
+602
@@ -0,0 +1,602 @@
|
||||
/* Diagnostic build of the launcher: see diag.h.
|
||||
*
|
||||
* Everything is shown on pages a user can screenshot, and also saved to
|
||||
* /data/control4free/diag-report.txt (and a USB stick when one is plugged in).
|
||||
* The kernel log is read only in short windows (start-up, and around a start)
|
||||
* because it has a single reader, and Control4Free needs it to sign a
|
||||
* controller in. */
|
||||
#include "diag.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
#include <orbis/libkernel.h>
|
||||
#include "autorun.h"
|
||||
#include "draw.h"
|
||||
#include "sandbox.h"
|
||||
#include "service.h"
|
||||
|
||||
#define C4F_DIAG_DIR "/user/data/control4free"
|
||||
#define C4F_DIAG_LOG C4F_DIAG_DIR "/control4free.log"
|
||||
#define C4F_DIAG_REPORT C4F_DIAG_DIR "/diag-report.txt"
|
||||
#define C4F_DIAG_KLOG C4F_DIAG_DIR "/diag-klog.txt"
|
||||
#define C4F_DIAG_PAYLOAD "/user/data/payloads/control4free.elf"
|
||||
#define C4F_DIAG_INI "/user/data/GoldHEN/payloads.ini"
|
||||
#define C4F_LOG_TAIL (64 * 1024)
|
||||
#define C4F_KLOG_KEEP 1000
|
||||
#define C4F_KLOG_WIDTH 192
|
||||
#define C4F_KLOG_FILE_MAX (4u << 20)
|
||||
|
||||
#define C4F_D_BG C4F_RGBA(6, 14, 34, 1)
|
||||
#define C4F_D_TEXT C4F_RGBA(235, 240, 255, 1)
|
||||
#define C4F_D_FAINT C4F_RGBA(160, 175, 205, 1)
|
||||
#define C4F_D_ACCENT C4F_RGBA(90, 160, 255, 1)
|
||||
#define C4F_D_WARN C4F_RGBA(255, 208, 77, 1)
|
||||
#define C4F_D_LINE C4F_RGBA(255, 255, 255, .2f)
|
||||
#define C4F_D_SIZE 20.0f
|
||||
#define C4F_D_TITLE 28.0f
|
||||
#define C4F_D_ROW 24
|
||||
#define C4F_D_LEFT 40
|
||||
#define C4F_D_TOP 186
|
||||
#define C4F_D_BOTTOM 1052
|
||||
|
||||
static const char *const c4fPageNames[C4F_DIAG_PAGES] = {
|
||||
"Summary", "Control4Free log", "Previous Control4Free log", "Kernel log (filtered)", "Kernel log (everything)",
|
||||
};
|
||||
|
||||
static pthread_mutex_t c4fLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_t c4fKlogReader;
|
||||
static int c4fReaderStarted, c4fQuit;
|
||||
static uint64_t c4fBootMs, c4fCaptureUntil;
|
||||
|
||||
/* Pages, rebuilt by the launcher's worker and drawn by its main loop. */
|
||||
static char *c4fPages[C4F_DIAG_PAGES];
|
||||
static unsigned c4fGeneration = 1;
|
||||
static int c4fMaxScroll[C4F_DIAG_PAGES];
|
||||
|
||||
/* What start-up found. */
|
||||
static char c4fFirmware[96], c4fGoldHen[96], c4fSandbox[96], c4fBundled[96];
|
||||
|
||||
/* The kernel log: the newest lines, and how reading it went. */
|
||||
static char c4fKlog[C4F_KLOG_KEEP][C4F_KLOG_WIDTH];
|
||||
static unsigned c4fKlogTotal, c4fKlogOpens;
|
||||
static int c4fKlogError;
|
||||
static unsigned long long c4fKlogBytes;
|
||||
|
||||
/* The last start. */
|
||||
static int c4fStarted, c4fStartResult, c4fLogChanged;
|
||||
static uint64_t c4fStartedAt, c4fStartTook;
|
||||
static char c4fStartMessage[192];
|
||||
static char *c4fLogBefore;
|
||||
|
||||
static uint64_t c4fLastReport;
|
||||
static char c4fReportNote[160];
|
||||
|
||||
/* ---- helpers ---- */
|
||||
|
||||
typedef struct { char *data; size_t used, size; } C4fText;
|
||||
|
||||
static void c4fAdd(C4fText *t, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
|
||||
static void c4fAdd(C4fText *t, const char *fmt, ...)
|
||||
{
|
||||
for (;;) {
|
||||
size_t room = t->data ? t->size - t->used : 0;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int n = vsnprintf(t->data ? t->data + t->used : NULL, room, fmt, ap);
|
||||
va_end(ap);
|
||||
if (n < 0) return;
|
||||
if (t->data && (size_t)n < room) { t->used += (size_t)n; return; }
|
||||
size_t grown = t->size ? t->size * 2 : 4096;
|
||||
while (grown < t->used + (size_t)n + 1) grown *= 2;
|
||||
char *next = realloc(t->data, grown);
|
||||
if (!next) return;
|
||||
t->data = next; t->size = grown;
|
||||
}
|
||||
}
|
||||
|
||||
static char *c4fTake(C4fText *t) { return t->data ? t->data : strdup(""); }
|
||||
|
||||
/* The last `limit` bytes of a file, NUL-terminated, NULs shown as spaces;
|
||||
* NULL with errno when it cannot be read. */
|
||||
static char *c4fReadTail(const char *path, size_t limit, long long *total)
|
||||
{
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) return NULL;
|
||||
off_t size = lseek(fd, 0, SEEK_END);
|
||||
off_t start = size > (off_t)limit ? size - (off_t)limit : 0;
|
||||
if (size < 0 || lseek(fd, start, SEEK_SET) < 0) { int saved = errno; close(fd); errno = saved; return NULL; }
|
||||
size_t want = (size_t)(size - start), used = 0;
|
||||
char *data = malloc(want + 1);
|
||||
if (!data) { close(fd); errno = ENOMEM; return NULL; }
|
||||
while (used < want) {
|
||||
ssize_t n = read(fd, data + used, want - used);
|
||||
if (n < 0 && errno == EINTR) continue;
|
||||
if (n <= 0) break;
|
||||
used += (size_t)n;
|
||||
}
|
||||
close(fd);
|
||||
for (size_t i = 0; i < used; i++) if (!data[i]) data[i] = ' ';
|
||||
data[used] = 0;
|
||||
if (total) *total = (long long)size;
|
||||
return data;
|
||||
}
|
||||
|
||||
static uint32_t c4fCrc32Update(uint32_t crc, const unsigned char *data, size_t size)
|
||||
{
|
||||
crc = ~crc;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
crc ^= data[i];
|
||||
for (int bit = 0; bit < 8; bit++) crc = (crc >> 1) ^ (0xedb88320u & (0u - (crc & 1)));
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
static uint32_t c4fCrc32(const unsigned char *data, size_t size) { return c4fCrc32Update(0, data, size); }
|
||||
|
||||
/* A file's size and CRC-32; -1 with errno when it cannot be read. */
|
||||
static int c4fCrcFile(const char *path, size_t *size, uint32_t *crc)
|
||||
{
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
unsigned char buf[16384];
|
||||
*size = 0; *crc = 0;
|
||||
for (;;) {
|
||||
ssize_t n = read(fd, buf, sizeof(buf));
|
||||
if (n < 0 && errno == EINTR) continue;
|
||||
if (n < 0) { int saved = errno; close(fd); errno = saved; return -1; }
|
||||
if (n == 0) break;
|
||||
*crc = c4fCrc32Update(*crc, buf, (size_t)n);
|
||||
*size += (size_t)n;
|
||||
}
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *c4fLastLine(const char *text, char *out, size_t size)
|
||||
{
|
||||
const char *end = text + strlen(text);
|
||||
while (end > text && (end[-1] == '\n' || end[-1] == '\r' || end[-1] == ' ')) end--;
|
||||
const char *start = end;
|
||||
while (start > text && start[-1] != '\n') start--;
|
||||
snprintf(out, size, "%.*s", (int)(end - start), start);
|
||||
return out;
|
||||
}
|
||||
|
||||
static int c4fContains(const char *haystack, const char *needle)
|
||||
{
|
||||
size_t n = strlen(needle);
|
||||
for (; *haystack; haystack++) {
|
||||
size_t i = 0;
|
||||
while (i < n && haystack[i] && (haystack[i] | 0x20) == (needle[i] | 0x20)) i++;
|
||||
if (i == n) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void c4fWriteAll(const char *path, const char *text, int append)
|
||||
{
|
||||
int fd = open(path, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0666);
|
||||
if (fd < 0) return;
|
||||
size_t size = strlen(text);
|
||||
while (size) {
|
||||
ssize_t n = write(fd, text, size);
|
||||
if (n < 0 && errno == EINTR) continue;
|
||||
if (n <= 0) break;
|
||||
text += n; size -= (size_t)n;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* ---- the kernel log ---- */
|
||||
|
||||
/* One line, without terminal colour codes (GoldHEN's lines carry them). */
|
||||
static void c4fKlogAdd(const char *line)
|
||||
{
|
||||
char clean[C4F_KLOG_WIDTH];
|
||||
size_t used = 0;
|
||||
for (const char *p = line; *p && used + 1 < sizeof(clean); p++) {
|
||||
if (*p == '\033') {
|
||||
if (p[1] == '[') { p += 2; while (*p && !(*p >= '@' && *p <= '~')) p++; if (!*p) break; }
|
||||
continue;
|
||||
}
|
||||
if (*p != '\r') clean[used++] = *p;
|
||||
}
|
||||
clean[used] = 0;
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
snprintf(c4fKlog[c4fKlogTotal % C4F_KLOG_KEEP], C4F_KLOG_WIDTH, "%s", clean);
|
||||
c4fKlogTotal++;
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
}
|
||||
|
||||
static void *c4fKlogThread(void *unused)
|
||||
{
|
||||
(void)unused;
|
||||
int fd = -1;
|
||||
size_t used = 0, written = 0;
|
||||
char line[C4F_KLOG_WIDTH * 2];
|
||||
for (;;) {
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
int quit = c4fQuit, want = c4fLauncherTimeMs() < c4fCaptureUntil;
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
if (quit) break;
|
||||
if (want && fd < 0) {
|
||||
fd = open("/dev/klog", O_RDONLY | O_NONBLOCK);
|
||||
int error = fd < 0 ? errno : 0;
|
||||
if (fd >= (int)FD_SETSIZE) { close(fd); fd = -1; error = EMFILE; }
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
c4fKlogError = error;
|
||||
if (fd < 0) c4fCaptureUntil = 0; else c4fKlogOpens++;
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
continue;
|
||||
}
|
||||
if (!want && fd >= 0) {
|
||||
close(fd); fd = -1;
|
||||
if (used) { line[used] = 0; c4fKlogAdd(line); used = 0; }
|
||||
}
|
||||
if (fd < 0) { usleep(100000); continue; }
|
||||
fd_set set; FD_ZERO(&set); FD_SET(fd, &set);
|
||||
struct timeval tv = { 0, 100000 };
|
||||
if (select(fd + 1, &set, NULL, NULL, &tv) <= 0) continue;
|
||||
char buf[4096];
|
||||
ssize_t n = read(fd, buf, sizeof(buf) - 1);
|
||||
if (n < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) continue;
|
||||
if (n <= 0) {
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
c4fKlogError = n < 0 ? errno : EPIPE; c4fCaptureUntil = 0;
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
close(fd); fd = -1;
|
||||
continue;
|
||||
}
|
||||
buf[n] = 0;
|
||||
if (written < C4F_KLOG_FILE_MAX) { c4fWriteAll(C4F_DIAG_KLOG, buf, 1); written += (size_t)n; }
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
c4fKlogBytes += (unsigned long long)n;
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
for (ssize_t i = 0; i < n; i++) {
|
||||
if (buf[i] == '\n' || used + 1 >= sizeof(line)) {
|
||||
line[used] = 0; c4fKlogAdd(line); used = 0;
|
||||
if (buf[i] == '\n') continue;
|
||||
}
|
||||
line[used++] = buf[i];
|
||||
}
|
||||
}
|
||||
if (fd >= 0) close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void c4fDiagCapture(unsigned milliseconds)
|
||||
{
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
uint64_t until = c4fLauncherTimeMs() + milliseconds;
|
||||
if (until > c4fCaptureUntil) c4fCaptureUntil = until;
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
}
|
||||
|
||||
/* ---- start-up and starts ---- */
|
||||
|
||||
void c4fDiagStart(int sandboxResult, int sandboxError)
|
||||
{
|
||||
c4fBootMs = c4fLauncherTimeMs();
|
||||
OrbisKernelSwVersion version;
|
||||
memset(&version, 0, sizeof(version));
|
||||
version.Size = sizeof(version);
|
||||
int ret = sceKernelGetSystemSwVersion(&version);
|
||||
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);
|
||||
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 (sandboxResult == 0) snprintf(c4fSandbox, sizeof(c4fSandbox), "left through GoldHEN");
|
||||
else snprintf(c4fSandbox, sizeof(c4fSandbox), "still inside, errno %d", sandboxError);
|
||||
size_t size = 0;
|
||||
const unsigned char *payload = c4fAutorunBundled(&size);
|
||||
if (payload) snprintf(c4fBundled, sizeof(c4fBundled), "%zu bytes, crc32 %08x", size, c4fCrc32(payload, size));
|
||||
else snprintf(c4fBundled, sizeof(c4fBundled), "could not be read");
|
||||
c4fWriteAll(C4F_DIAG_KLOG, "", 0);
|
||||
c4fReaderStarted = pthread_create(&c4fKlogReader, NULL, c4fKlogThread, NULL) == 0;
|
||||
c4fDiagCapture(4000); /* the backlog: GoldHEN, AutoRun and earlier runs */
|
||||
}
|
||||
|
||||
void c4fDiagStop(void)
|
||||
{
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
c4fQuit = 1;
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
if (c4fReaderStarted) pthread_join(c4fKlogReader, NULL);
|
||||
}
|
||||
|
||||
void c4fDiagBeforeStart(void)
|
||||
{
|
||||
free(c4fLogBefore);
|
||||
c4fLogBefore = c4fReadTail(C4F_DIAG_LOG, C4F_LOG_TAIL, NULL);
|
||||
c4fStartedAt = c4fLauncherTimeMs();
|
||||
c4fDiagCapture(30000);
|
||||
}
|
||||
|
||||
void c4fDiagAfterStart(int result, const char *message)
|
||||
{
|
||||
char *after = c4fReadTail(C4F_DIAG_LOG, C4F_LOG_TAIL, NULL);
|
||||
c4fLogChanged = after && (!c4fLogBefore || strcmp(after, c4fLogBefore));
|
||||
free(after);
|
||||
c4fStarted = 1;
|
||||
c4fStartResult = result;
|
||||
c4fStartTook = c4fLauncherTimeMs() - c4fStartedAt;
|
||||
snprintf(c4fStartMessage, sizeof(c4fStartMessage), "%s", message);
|
||||
c4fLastReport = 0; /* save the report straight away */
|
||||
c4fDiagCapture(3000);
|
||||
}
|
||||
|
||||
/* ---- pages ---- */
|
||||
|
||||
static void c4fFileSummary(C4fText *t, const char *label, const char *path)
|
||||
{
|
||||
long long size = 0;
|
||||
char *text = c4fReadTail(path, C4F_LOG_TAIL, &size);
|
||||
if (!text) { c4fAdd(t, " %s: not there (errno %d)\n", label, errno); return; }
|
||||
char last[200];
|
||||
const char *firstEnd = strchr(text, '\n');
|
||||
c4fAdd(t, " %s: %lld bytes\n first: %.*s\n last: %s\n", label, size,
|
||||
(int)(firstEnd ? firstEnd - text : (long)strlen(text)), text, c4fLastLine(text, last, sizeof(last)));
|
||||
free(text);
|
||||
}
|
||||
|
||||
static char *c4fSummary(const C4fDiagState *s)
|
||||
{
|
||||
C4fText t = {0};
|
||||
char last[200] = "";
|
||||
char *log = c4fReadTail(C4F_DIAG_LOG, C4F_LOG_TAIL, NULL);
|
||||
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);
|
||||
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");
|
||||
else if (c4fStarted && c4fStartResult == -2 && !c4fLogChanged)
|
||||
c4fAdd(&t, ">> The payload was handed to PayLoader, but Control4Free's own code never ran (it wrote no log).\n"
|
||||
">> PayLoader did not run it, or it stopped while starting up on this firmware. The kernel log\n"
|
||||
">> pages (4 and 5) show what the PS4 said meanwhile.\n");
|
||||
else if (c4fStarted && c4fLogChanged)
|
||||
c4fAdd(&t, ">> Control4Free started but does not answer. Its log (page 2) shows how far it got. Last line:\n"
|
||||
">> %s\n", last);
|
||||
else if (c4fStarted)
|
||||
c4fAdd(&t, ">> The start did not work: %s\n", c4fStartMessage);
|
||||
else
|
||||
c4fAdd(&t, ">> Not running. Press Cross to start it, wait for the result (up to 30 seconds), then take a\n"
|
||||
">> screenshot of every page.\n");
|
||||
free(log);
|
||||
|
||||
c4fAdd(&t, "\nConsole\n");
|
||||
c4fAdd(&t, " system software: %s\n", c4fFirmware);
|
||||
c4fAdd(&t, " GoldHEN SDK call: %s\n", c4fGoldHen);
|
||||
c4fAdd(&t, " app sandbox: %s\n", c4fSandbox);
|
||||
c4fAdd(&t, " network address: %s\n", s->address[0] ? s->address : "(none)");
|
||||
|
||||
c4fAdd(&t, "\nThis app\n");
|
||||
c4fAdd(&t, " version %s, bundled payload %s, open for %llu s\n", C4F_LAUNCHER_VERSION, c4fBundled,
|
||||
(unsigned long long)((c4fLauncherTimeMs() - c4fBootMs) / 1000));
|
||||
|
||||
c4fAdd(&t, "\nService (port 4264)\n");
|
||||
if (s->running == 1) c4fAdd(&t, " running, version %s, %d controller(s)\n", s->version, s->controllers);
|
||||
else if (s->running == 0) c4fAdd(&t, " not running (nothing listens)\n");
|
||||
else c4fAdd(&t, " unclear: %s\n", s->problem[0] ? s->problem : "(no detail)");
|
||||
c4fAdd(&t, " screen message: %s%s\n", s->message, s->locked ? " [app error: reopen the app]" : "");
|
||||
|
||||
c4fAdd(&t, "\nLast start from this app\n");
|
||||
if (!c4fStarted) c4fAdd(&t, " none yet\n");
|
||||
else c4fAdd(&t, " result %d after %llu ms; service log %s\n message: %s\n", c4fStartResult,
|
||||
(unsigned long long)c4fStartTook, c4fLogChanged ? "CHANGED (it ran)" : "unchanged (it did not run)",
|
||||
c4fStartMessage);
|
||||
|
||||
c4fAdd(&t, "\nAuto-start\n");
|
||||
static const char *const states[] = { "unknown", "off", "on", "on, but an older copy" };
|
||||
int state = s->autorun + 1;
|
||||
c4fAdd(&t, " state: %s%s%s\n", state >= 0 && state < 4 ? states[state] : "?", s->autorunNote[0] ? "; " : "",
|
||||
s->autorunNote);
|
||||
size_t installedSize = 0;
|
||||
uint32_t installedCrc = 0;
|
||||
if (!c4fCrcFile(C4F_DIAG_PAYLOAD, &installedSize, &installedCrc))
|
||||
c4fAdd(&t, " /data/payloads/control4free.elf: %zu bytes, crc32 %08x\n", installedSize, installedCrc);
|
||||
else c4fAdd(&t, " /data/payloads/control4free.elf: not there (errno %d)\n", errno);
|
||||
char *ini = c4fReadTail(C4F_DIAG_INI, 2048, NULL);
|
||||
if (ini) {
|
||||
c4fAdd(&t, " /data/GoldHEN/payloads.ini:\n");
|
||||
for (char *line = strtok(ini, "\n"); line; line = strtok(NULL, "\n")) c4fAdd(&t, " %s\n", line);
|
||||
free(ini);
|
||||
} else c4fAdd(&t, " /data/GoldHEN/payloads.ini: not there (errno %d)\n", errno);
|
||||
|
||||
c4fAdd(&t, "\nLogs\n");
|
||||
c4fFileSummary(&t, "control4free.log", C4F_DIAG_LOG);
|
||||
c4fFileSummary(&t, "control4free.log.previous", C4F_DIAG_LOG ".previous");
|
||||
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
unsigned total = c4fKlogTotal, opens = c4fKlogOpens;
|
||||
int error = c4fKlogError;
|
||||
unsigned long long bytes = c4fKlogBytes;
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
c4fAdd(&t, " kernel log: read %u time(s), %llu bytes, %u lines", opens, bytes, total);
|
||||
if (error == 16) c4fAdd(&t, "; BUSY: a klog viewer is connected to GoldHEN (port 3232). Close it, press Options.\n");
|
||||
else if (error) c4fAdd(&t, "; last error errno %d\n", error);
|
||||
else c4fAdd(&t, "\n");
|
||||
c4fAdd(&t, " report: %s\n", c4fReportNote[0] ? c4fReportNote : "not saved yet");
|
||||
return c4fTake(&t);
|
||||
}
|
||||
|
||||
static char *c4fLogPage(const char *path)
|
||||
{
|
||||
long long size = 0;
|
||||
char *text = c4fReadTail(path, C4F_LOG_TAIL, &size);
|
||||
if (!text) {
|
||||
C4fText t = {0};
|
||||
c4fAdd(&t, "%s is not there (errno %d).\n", path, errno);
|
||||
return c4fTake(&t);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/* Lines that tend to say why a payload did not run. */
|
||||
static int c4fInteresting(const char *line)
|
||||
{
|
||||
static const char *const words[] = {
|
||||
"c4f", "control4free", "unsupported", "firmware", "kexec", "payload", "elf", "goldhen", "signal",
|
||||
"crash", "fault", "panic", "trap", "segv", "party", "rtld", "dlsym", "unable", "fail", "error",
|
||||
"denied", "9090", "4264",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(words) / sizeof(words[0]); i++) if (c4fContains(line, words[i])) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *c4fKlogPage(int filtered)
|
||||
{
|
||||
C4fText t = {0};
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
unsigned total = c4fKlogTotal, first = total > C4F_KLOG_KEEP ? total - C4F_KLOG_KEEP : 0;
|
||||
if (!total) c4fAdd(&t, "Nothing read from the kernel log yet (see the summary page). Options reads it again.\n");
|
||||
for (unsigned i = first; i < total; i++) {
|
||||
const char *line = c4fKlog[i % C4F_KLOG_KEEP];
|
||||
if (!filtered || c4fInteresting(line)) c4fAdd(&t, "%s\n", line);
|
||||
}
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
return c4fTake(&t);
|
||||
}
|
||||
|
||||
static void c4fSaveReport(void)
|
||||
{
|
||||
C4fText t = {0};
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
for (int i = 0; i < C4F_DIAG_PAGES; i++)
|
||||
c4fAdd(&t, "===== %s =====\n%s\n", c4fPageNames[i], c4fPages[i] ? c4fPages[i] : "");
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
char *text = c4fTake(&t);
|
||||
c4fWriteAll(C4F_DIAG_REPORT, text, 0);
|
||||
snprintf(c4fReportNote, sizeof(c4fReportNote), "/data/control4free/diag-report.txt");
|
||||
static const char *const usb[] = { "/mnt/usb0", "/mnt/usb1" };
|
||||
for (int i = 0; i < 2; i++) {
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "%s/control4free-diag.txt", usb[i]);
|
||||
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
if (fd < 0) continue;
|
||||
close(fd);
|
||||
c4fWriteAll(path, text, 0);
|
||||
size_t used = strlen(c4fReportNote);
|
||||
snprintf(c4fReportNote + used, sizeof(c4fReportNote) - used, " and %s", path);
|
||||
}
|
||||
free(text);
|
||||
}
|
||||
|
||||
unsigned c4fDiagRefresh(const C4fDiagState *state)
|
||||
{
|
||||
char *pages[C4F_DIAG_PAGES] = {
|
||||
c4fSummary(state), c4fLogPage(C4F_DIAG_LOG), c4fLogPage(C4F_DIAG_LOG ".previous"),
|
||||
c4fKlogPage(1), c4fKlogPage(0),
|
||||
};
|
||||
int changed = 0;
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
for (int i = 0; i < C4F_DIAG_PAGES; i++) {
|
||||
if (c4fPages[i] && !strcmp(c4fPages[i], pages[i])) { free(pages[i]); continue; }
|
||||
free(c4fPages[i]);
|
||||
c4fPages[i] = pages[i];
|
||||
changed = 1;
|
||||
}
|
||||
if (changed) c4fGeneration++;
|
||||
unsigned generation = c4fGeneration;
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
uint64_t now = c4fLauncherTimeMs();
|
||||
if (changed && (!c4fLastReport || now - c4fLastReport >= 10000)) { c4fSaveReport(); c4fLastReport = now; }
|
||||
return generation;
|
||||
}
|
||||
|
||||
/* ---- drawing ---- */
|
||||
|
||||
typedef struct { const char *text; int length, highlight; } C4fRow;
|
||||
|
||||
/* How many characters of a line fit in the width. */
|
||||
static int c4fFit(const char *text, int length, float width)
|
||||
{
|
||||
char line[400];
|
||||
int n = length < (int)sizeof(line) - 1 ? length : (int)sizeof(line) - 1;
|
||||
memcpy(line, text, (size_t)n); line[n] = 0;
|
||||
float measured = c4fTextWidth(C4F_FONT_REGULAR, C4F_D_SIZE, line);
|
||||
if (measured <= width) return n;
|
||||
n = (int)(n * width / measured);
|
||||
if (n < 1) n = 1;
|
||||
line[n] = 0;
|
||||
while (n > 1 && c4fTextWidth(C4F_FONT_REGULAR, C4F_D_SIZE, line) > width) line[--n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
void c4fDrawDiag(uint32_t *pixels, const C4fLauncherScreen *s)
|
||||
{
|
||||
C4fCanvas c = { pixels, C4F_SCREEN_WIDTH, C4F_SCREEN_HEIGHT };
|
||||
const float width = C4F_SCREEN_WIDTH - 2 * C4F_D_LEFT;
|
||||
int page = s->diagPage >= 0 && s->diagPage < C4F_DIAG_PAGES ? s->diagPage : 0;
|
||||
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]);
|
||||
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 "
|
||||
"Cross: start Triangle: auto-start on/off Square: stop Circle: close");
|
||||
c4fText(&c, C4F_FONT_REGULAR, C4F_D_SIZE, C4F_D_LEFT, 114, C4F_D_WARN,
|
||||
"Please take a screenshot of EVERY page (hold SHARE, or press SHARE then Triangle) and send them all.");
|
||||
if (s->confirmStop) snprintf(line, sizeof(line), "Press Cross to stop Control4Free, or Circle to cancel.");
|
||||
else snprintf(line, sizeof(line), "%s%s", s->busy ? "WORKING: " : "Now: ", s->message);
|
||||
c4fText(&c, C4F_FONT_REGULAR, C4F_D_SIZE, C4F_D_LEFT, 146, C4F_D_TEXT, line);
|
||||
c4fFillRect(&c, C4F_D_LEFT, 160, (int)width, 1, C4F_D_LINE);
|
||||
|
||||
pthread_mutex_lock(&c4fLock);
|
||||
char *text = strdup(c4fPages[page] ? c4fPages[page] : "(not read yet)");
|
||||
pthread_mutex_unlock(&c4fLock);
|
||||
if (!text) return;
|
||||
|
||||
/* Wrap every line, then show the window the scroll position picks. */
|
||||
int count = 0, capacity = 256;
|
||||
C4fRow *rows = malloc(sizeof(*rows) * (size_t)capacity);
|
||||
for (char *p = text; rows && *p; ) {
|
||||
char *end = strchr(p, '\n');
|
||||
int length = end ? (int)(end - p) : (int)strlen(p);
|
||||
int highlight = !strncmp(p, ">> ", 3);
|
||||
if (highlight) { p += 3; length -= 3; }
|
||||
do {
|
||||
int fit = length ? c4fFit(p, length, width) : 0;
|
||||
if (count == capacity) {
|
||||
C4fRow *grown = realloc(rows, sizeof(*rows) * (size_t)(capacity *= 2));
|
||||
if (!grown) break;
|
||||
rows = grown;
|
||||
}
|
||||
rows[count++] = (C4fRow){ p, fit, highlight };
|
||||
p += fit; length -= fit;
|
||||
} while (length > 0);
|
||||
p = end ? end + 1 : p + strlen(p);
|
||||
}
|
||||
int visible = (C4F_D_BOTTOM - C4F_D_TOP) / C4F_D_ROW;
|
||||
int maxScroll = count > visible ? count - visible : 0;
|
||||
c4fMaxScroll[page] = maxScroll;
|
||||
int first = s->diagScroll[page] >= C4F_DIAG_END ? maxScroll : s->diagScroll[page];
|
||||
if (first > maxScroll) first = maxScroll;
|
||||
if (first < 0) first = 0;
|
||||
for (int i = 0; rows && i < visible && first + i < count; i++) {
|
||||
const C4fRow *row = &rows[first + i];
|
||||
c4fTextN(&c, C4F_FONT_REGULAR, C4F_D_SIZE, C4F_D_LEFT, C4F_D_TOP + i * C4F_D_ROW,
|
||||
row->highlight ? C4F_D_WARN : C4F_D_TEXT, row->text, row->length);
|
||||
}
|
||||
snprintf(line, sizeof(line), "lines %d-%d of %d", count ? first + 1 : 0, first + visible < count ? first + visible : count, count);
|
||||
c4fText(&c, C4F_FONT_REGULAR, C4F_D_SIZE, C4F_D_LEFT, 1072, C4F_D_FAINT, line);
|
||||
free(rows);
|
||||
free(text);
|
||||
}
|
||||
|
||||
int c4fDiagMaxScroll(int page)
|
||||
{
|
||||
return page >= 0 && page < C4F_DIAG_PAGES ? c4fMaxScroll[page] : 0;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef C4F_LAUNCHER_DIAG_H
|
||||
#define C4F_LAUNCHER_DIAG_H
|
||||
/* Diagnostic build (make -C launcher C4F_DIAG=1): plain pages of everything
|
||||
* that says why Control4Free does not start on a console, in place of the
|
||||
* normal screen, so a user can send screenshots instead of log files. */
|
||||
#include <stdint.h>
|
||||
#include "screen.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define C4F_DIAG_PAGES 5
|
||||
/* A page's scroll position meaning "show the end". */
|
||||
#define C4F_DIAG_END 1000000
|
||||
|
||||
typedef struct {
|
||||
int running, controllers, autorun, locked, busy, confirmStop;
|
||||
const char *version, *problem, *message, *address, *autorunNote;
|
||||
} C4fDiagState;
|
||||
|
||||
/* Records what is known at start-up and reads the kernel log's backlog. */
|
||||
void c4fDiagStart(int sandboxResult, int sandboxError);
|
||||
void c4fDiagStop(void);
|
||||
/* Holds the kernel log open for this long, reading it. */
|
||||
void c4fDiagCapture(unsigned milliseconds);
|
||||
/* Around a start: what the service log looked like before, and the result. */
|
||||
void c4fDiagBeforeStart(void);
|
||||
void c4fDiagAfterStart(int result, const char *message);
|
||||
/* Rebuilds the pages; returns their generation, which changes with them. */
|
||||
unsigned c4fDiagRefresh(const C4fDiagState *state);
|
||||
void c4fDrawDiag(uint32_t *pixels, const C4fLauncherScreen *screen);
|
||||
/* The largest useful scroll position of a page, as of its last drawing. */
|
||||
int c4fDiagMaxScroll(int page);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
+44
-1
@@ -16,6 +16,9 @@
|
||||
#include "sandbox.h"
|
||||
#include "screen.h"
|
||||
#include "service.h"
|
||||
#ifdef C4F_DIAG
|
||||
#include "diag.h"
|
||||
#endif
|
||||
|
||||
#define C4F_BUNDLED_PAYLOAD "/app0/assets/control4free.elf"
|
||||
|
||||
@@ -66,6 +69,9 @@ static void *c4fWorker(void *)
|
||||
} else {
|
||||
c4fLauncherSetHost(NULL);
|
||||
}
|
||||
#ifdef C4F_DIAG
|
||||
if (command == C4F_DO_START || command == C4F_DO_SET_UP) c4fDiagBeforeStart();
|
||||
#endif
|
||||
if (command == C4F_DO_START) {
|
||||
size_t payloadSize;
|
||||
const unsigned char *payload = c4fAutorunBundled(&payloadSize);
|
||||
@@ -73,6 +79,9 @@ static void *c4fWorker(void *)
|
||||
}
|
||||
if (command == C4F_DO_STOP) result = c4fLauncherStop(message, sizeof(message));
|
||||
if (command == C4F_DO_SET_UP) result = c4fSetUp(message, sizeof(message));
|
||||
#ifdef C4F_DIAG
|
||||
if (command == C4F_DO_START || command == C4F_DO_SET_UP) c4fDiagAfterStart(result, message);
|
||||
#endif
|
||||
if (command == C4F_DO_AUTORUN_ON) result = c4fAutorunEnable(message, sizeof(message));
|
||||
if (command == C4F_DO_AUTORUN_OFF) result = c4fAutorunDisable(message, sizeof(message));
|
||||
if (first || command >= C4F_DO_SET_UP) autorun = c4fAutorunCheck(autorunNote, sizeof(autorunNote));
|
||||
@@ -98,6 +107,15 @@ static void *c4fWorker(void *)
|
||||
snprintf(c4fScreen.address, sizeof(c4fScreen.address), "%s", address);
|
||||
/* A queued click while a periodic probe was in flight remains busy. */
|
||||
if (!c4fCommand) c4fScreen.busy = 0;
|
||||
#ifdef C4F_DIAG
|
||||
C4fLauncherScreen copy = c4fScreen;
|
||||
pthread_mutex_unlock(&c4fMutex);
|
||||
C4fDiagState diag = { running, status.controllers, autorun, copy.locked, copy.busy, copy.confirmStop,
|
||||
status.version, problem, copy.message, copy.address, copy.autorunNote };
|
||||
unsigned generation = c4fDiagRefresh(&diag);
|
||||
pthread_mutex_lock(&c4fMutex);
|
||||
c4fScreen.diagGeneration = generation;
|
||||
#endif
|
||||
pthread_mutex_unlock(&c4fMutex);
|
||||
first = 0; nextCheck = c4fLauncherTimeMs() + 2000;
|
||||
}
|
||||
@@ -175,7 +193,12 @@ int main(void)
|
||||
if (c4fAutorunLoadBundled(C4F_BUNDLED_PAYLOAD)) printf("[c4f-launcher] bundled payload unreadable\n");
|
||||
/* The sandbox refuses connections to the console itself (EACCES), so the
|
||||
* app could reach neither Control4Free nor PayLoader from inside it. */
|
||||
if (c4fSandboxLeave()) printf("[c4f-launcher] could not leave the sandbox, errno %d\n", errno);
|
||||
int sandbox = c4fSandboxLeave(), sandboxError = errno;
|
||||
if (sandbox) printf("[c4f-launcher] could not leave the sandbox, errno %d\n", sandboxError);
|
||||
#ifdef C4F_DIAG
|
||||
c4fDiagStart(sandbox, sandboxError);
|
||||
for (int i = 0; i < C4F_DIAG_PAGES; i++) c4fScreen.diagScroll[i] = i ? C4F_DIAG_END : 0;
|
||||
#endif
|
||||
pthread_t worker;
|
||||
int workerStarted = pthread_create(&worker, NULL, c4fWorker, NULL) == 0;
|
||||
if (!workerStarted || pad < 0) {
|
||||
@@ -220,11 +243,28 @@ int main(void)
|
||||
off ? "Turning auto-start off..." : "Turning auto-start on...");
|
||||
} else if ((pressed & ORBIS_PAD_BUTTON_SQUARE) && c4fScreen.running == 1) c4fScreen.confirmStop = 1;
|
||||
}
|
||||
#ifdef C4F_DIAG
|
||||
/* Pages and scrolling work while busy too. */
|
||||
int page = c4fScreen.diagPage;
|
||||
if (pressed & (ORBIS_PAD_BUTTON_R1 | ORBIS_PAD_BUTTON_RIGHT)) c4fScreen.diagPage = (page + 1) % C4F_DIAG_PAGES;
|
||||
if (pressed & (ORBIS_PAD_BUTTON_L1 | ORBIS_PAD_BUTTON_LEFT)) c4fScreen.diagPage = (page + C4F_DIAG_PAGES - 1) % C4F_DIAG_PAGES;
|
||||
if (pressed & (ORBIS_PAD_BUTTON_UP | ORBIS_PAD_BUTTON_DOWN)) {
|
||||
int max = c4fDiagMaxScroll(page), at = c4fScreen.diagScroll[page];
|
||||
if (at >= C4F_DIAG_END) at = max;
|
||||
at += pressed & ORBIS_PAD_BUTTON_UP ? -10 : 10;
|
||||
c4fScreen.diagScroll[page] = at >= max ? C4F_DIAG_END : at < 0 ? 0 : at;
|
||||
}
|
||||
if (pressed & ORBIS_PAD_BUTTON_OPTIONS) c4fDiagCapture(4000);
|
||||
#endif
|
||||
C4fLauncherScreen snapshot = c4fScreen;
|
||||
pthread_mutex_unlock(&c4fMutex);
|
||||
/* Redraw only when something changed, then copy it to each buffer once. */
|
||||
if (!drawn || memcmp(&snapshot, &drawnState, sizeof(snapshot))) {
|
||||
#ifdef C4F_DIAG
|
||||
c4fDrawDiag(canvas, &snapshot);
|
||||
#else
|
||||
c4fDrawLauncher(canvas, &snapshot);
|
||||
#endif
|
||||
drawnState = snapshot; drawn = 1; shown[0] = shown[1] = 0;
|
||||
}
|
||||
if (!shown[buffer]) { memcpy(c4fBuffers[buffer], canvas, frameBytes); shown[buffer] = 1; }
|
||||
@@ -243,6 +283,9 @@ int main(void)
|
||||
}
|
||||
pthread_mutex_lock(&c4fMutex); c4fQuit = 1; pthread_mutex_unlock(&c4fMutex);
|
||||
if (workerStarted) pthread_join(worker, NULL);
|
||||
#ifdef C4F_DIAG
|
||||
c4fDiagStop();
|
||||
#endif
|
||||
if (pad >= 0) scePadClose(pad);
|
||||
sceNetCtlTerm();
|
||||
c4fCloseVideo();
|
||||
|
||||
+16
-5
@@ -30,21 +30,32 @@ static long c4fGoldHen(uint64_t command, void *data)
|
||||
return failed ? -ret : ret;
|
||||
}
|
||||
|
||||
long c4fGoldHenCommand(unsigned long command, void *data)
|
||||
{
|
||||
/* Without GoldHEN the call does not exist: get an error, not SIGSYS. The
|
||||
* kernel's SIGSYS is FreeBSD's 12; OpenOrbis's header has Linux's 31. */
|
||||
signal(12, SIG_IGN);
|
||||
return c4fGoldHen(command, data);
|
||||
}
|
||||
|
||||
int c4fSandboxLeave(void)
|
||||
{
|
||||
static C4fJailbreak backup;
|
||||
if (c4fOut) return 0;
|
||||
/* Without GoldHEN the call does not exist: get an error, not SIGSYS. The
|
||||
* kernel's SIGSYS is FreeBSD's 12; OpenOrbis's header has Linux's 31. */
|
||||
signal(12, SIG_IGN);
|
||||
memset(&backup, 0, sizeof(backup));
|
||||
long ret = c4fGoldHen(2, &backup);
|
||||
long ret = c4fGoldHenCommand(2, &backup);
|
||||
if (ret != 0) { errno = ret < 0 ? (int)-ret : EPERM; return -1; }
|
||||
c4fOut = 1;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
/* Host tests: there is no sandbox. */
|
||||
/* Host tests: there is no sandbox, and no GoldHEN. */
|
||||
long c4fGoldHenCommand(unsigned long command, void *data)
|
||||
{
|
||||
(void)command; (void)data;
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
int c4fSandboxLeave(void)
|
||||
{
|
||||
if (getenv("C4F_TEST_NO_GOLDHEN")) { errno = ENOSYS; return -1; }
|
||||
|
||||
@@ -10,6 +10,9 @@ extern "C" {
|
||||
/* 0 when out of the sandbox (again: no-op); -1 with errno otherwise. */
|
||||
int c4fSandboxLeave(void);
|
||||
int c4fSandboxIsOut(void);
|
||||
/* GoldHEN's SDK call (syscall 500): its result, or -errno when the call
|
||||
* failed or does not exist. */
|
||||
long c4fGoldHenCommand(unsigned long command, void *data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ typedef struct {
|
||||
int running, busy, controllers, confirmStop, locked;
|
||||
int autorun; /* C4F_AUTORUN_* from autorun.h */
|
||||
char address[64], message[160], autorunNote[96];
|
||||
#ifdef C4F_DIAG
|
||||
unsigned diagGeneration; /* bumped when a diagnostic page changes */
|
||||
int diagPage, diagScroll[8];
|
||||
#endif
|
||||
} C4fLauncherScreen;
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
+42
@@ -100,6 +100,44 @@ static void c4fRestoreCredentials(void)
|
||||
c4fLogCredentials("restored", g_pid);
|
||||
}
|
||||
|
||||
#ifdef C4F_DIAG
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Diagnostic build: what the SDK's start-up code found in the kernel, and
|
||||
* which process PayLoader runs us in, from GoldHEN's SDK call (syscall 500,
|
||||
* command 4; struct proc_info of the GoldHEN Plugins SDK). */
|
||||
typedef struct {
|
||||
int pid;
|
||||
char name[40], path[64], titleId[16], contentId[64], version[6];
|
||||
uint64_t baseAddress;
|
||||
} __attribute__((packed)) C4fProcInfo;
|
||||
|
||||
static void c4fDiagHost(pid_t pid)
|
||||
{
|
||||
C4fProcInfo info;
|
||||
struct sigaction ignore, previous;
|
||||
|
||||
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,
|
||||
(unsigned long)KERNEL_ADDRESS_ROOTVNODE, (unsigned long)KERNEL_ADDRESS_TARGETID);
|
||||
c4fLog("diag: proc=0x%lx ucred=0x%lx\n", (unsigned long)kernel_get_proc(pid),
|
||||
(unsigned long)kernel_get_proc_ucred(pid));
|
||||
|
||||
/* Without GoldHEN the call does not exist: get an error, not SIGSYS. */
|
||||
memset(&info, 0, sizeof(info));
|
||||
memset(&ignore, 0, sizeof(ignore));
|
||||
ignore.sa_handler = SIG_IGN;
|
||||
sigaction(SIGSYS, &ignore, &previous);
|
||||
int ret = syscall(500, 4, &info);
|
||||
sigaction(SIGSYS, &previous, NULL);
|
||||
info.name[sizeof(info.name) - 1] = info.path[sizeof(info.path) - 1] = info.titleId[sizeof(info.titleId) - 1] = 0;
|
||||
c4fLog("diag: GoldHEN process info = %d: pid=%d name=%s path=%s title=%s\n",
|
||||
ret, info.pid, info.name, info.path, info.titleId);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int c4fFinish(int status)
|
||||
{
|
||||
c4fRestoreCredentials();
|
||||
@@ -127,6 +165,10 @@ int main(void)
|
||||
}
|
||||
c4fLogOpen();
|
||||
c4fLog("Control4Free %s: pid=%d firmware=0x%08x\n", C4F_VERSION, pid, kernel_get_fw_version());
|
||||
#ifdef C4F_DIAG
|
||||
c4fNotify("Control4Free diagnostic build started (firmware %08x)", kernel_get_fw_version());
|
||||
c4fDiagHost(pid);
|
||||
#endif
|
||||
|
||||
c4fLogCredentials("on entry", pid);
|
||||
if (c4fRaiseCredentials(pid) != 0) return c4fFinish(1);
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<# Build the payload and installable PS4 launcher locally. Requires Docker. #>
|
||||
<# Build the payload and installable PS4 launcher locally. Requires Docker.
|
||||
-Diag builds the diagnostic package instead (Control4Free-<version>-diag.pkg). #>
|
||||
param([switch]$Diag)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$projectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
$flag = if ($Diag) { 'C4F_DIAG=1' } else { '' }
|
||||
$suffix = if ($Diag) { '-diag' } else { '' }
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
docker build -t control4free-build -f docker/Dockerfile docker
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Payload toolchain build failed.' }
|
||||
docker build -t control4free-launcher-build -f docker/Dockerfile.launcher docker
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Launcher toolchain build failed.' }
|
||||
docker run --rm --network none -v "${projectRoot}:/src" -w /src control4free-launcher-build bash -lc 'make && make -C launcher'
|
||||
docker run --rm --network none -v "${projectRoot}:/src" -w /src control4free-launcher-build bash -lc "make $flag && make -C launcher $flag"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Launcher package build failed.' }
|
||||
$version = (Get-Content -LiteralPath (Join-Path $projectRoot 'VERSION') -Raw).Trim()
|
||||
Get-Item -LiteralPath (Join-Path $projectRoot "build/Control4Free-$version.pkg") | Select-Object FullName,Length
|
||||
Get-Item -LiteralPath (Join-Path $projectRoot "build/Control4Free-$version$suffix.pkg") | Select-Object FullName,Length
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -95,9 +95,17 @@ def render(args, path, width, height):
|
||||
|
||||
|
||||
def main():
|
||||
global OUT, STAGE, ART, FONTS
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--sdk', required=True, type=Path)
|
||||
sdk = parser.parse_args().sdk
|
||||
# The diagnostic build (make -C launcher C4F_DIAG=1): its own folder, payload,
|
||||
# title and file name, so it can never be mistaken for a release.
|
||||
parser.add_argument('--diag', action='store_true')
|
||||
args = parser.parse_args()
|
||||
sdk = args.sdk
|
||||
suffix = '-diag' if args.diag else ''
|
||||
OUT = ROOT / f'build/launcher{suffix}'
|
||||
STAGE, ART, FONTS = OUT / 'pkg', OUT / 'launcher-art', OUT / 'fonts.c'
|
||||
binary = sdk / 'bin/linux'
|
||||
# Start clean, so nothing left over from an older build gets packaged.
|
||||
shutil.rmtree(STAGE, ignore_errors=True)
|
||||
@@ -106,7 +114,7 @@ def main():
|
||||
run = lambda *args: subprocess.run([str(arg) for arg in args], cwd=STAGE, check=True)
|
||||
run(binary / 'create-fself', '-in=' + str(OUT / 'launcher.elf'), '-out=' + str(OUT / 'launcher.oelf'),
|
||||
'--eboot', 'eboot.bin', '--paid', '0x3800000000000011')
|
||||
shutil.copyfile(ROOT / 'build/control4free.elf', STAGE / 'assets/control4free.elf')
|
||||
shutil.copyfile(ROOT / f'build/control4free{suffix}.elf', STAGE / 'assets/control4free.elf')
|
||||
shutil.copyfile(sdk / 'samples/graphics/sce_sys/about/right.sprx', STAGE / 'sce_sys/about/right.sprx')
|
||||
# OpenOrbis's stub modules. Its CHANGELOG (v0.5) says homebrew packages
|
||||
# should carry them in sce_module/, and every sample does.
|
||||
@@ -136,7 +144,8 @@ def main():
|
||||
'APP_TYPE': ('Integer', 4, '1'), 'APP_VER': ('Utf8', 8, SFO_VERSION),
|
||||
'ATTRIBUTE': ('Integer', 4, '32'), 'CATEGORY': ('Utf8', 4, 'gde'),
|
||||
'CONTENT_ID': ('Utf8', 48, CONTENT_ID), 'DOWNLOAD_DATA_SIZE': ('Integer', 4, '0'),
|
||||
'SYSTEM_VER': ('Integer', 4, '0'), 'TITLE': ('Utf8', 128, 'Control4Free'),
|
||||
'SYSTEM_VER': ('Integer', 4, '0'),
|
||||
'TITLE': ('Utf8', 128, 'Control4Free (diagnostic)' if args.diag else 'Control4Free'),
|
||||
'TITLE_ID': ('Utf8', 12, TITLE_ID), 'VERSION': ('Utf8', 8, SFO_VERSION),
|
||||
}
|
||||
for key, (kind, size, value) in entries.items():
|
||||
@@ -163,7 +172,7 @@ def main():
|
||||
shutil.copyfile(project, OUT / 'launcher.gp4')
|
||||
run(pkgtool, 'pkg_build', project, OUT)
|
||||
generated = OUT / (CONTENT_ID + '.pkg')
|
||||
target = ROOT / f'build/Control4Free-{VERSION}.pkg'
|
||||
target = ROOT / f'build/Control4Free-{VERSION}{suffix}.pkg'
|
||||
shutil.copyfile(generated, target)
|
||||
print(f'Built {target} ({target.stat().st_size:,} bytes)', flush=True)
|
||||
|
||||
|
||||
Reference in new issue
Block a user