mirror of
https://github.com/holdmysocks/ps5-tailscale.git
synced 2026-10-06 09:00:19 +02:00
The status page can now be protected with a password and edits the settings itself, so the config file no longer has to be changed by hand. - Password for everything on the status page that shows or changes something. The console's own browser is exempt. Connections to the page from the tailnet are served directly so they are not taken for local. - Settings form: name, ports, forwards, proxy, priority, update checks. Most take effect at once; the page says which need a restart. - Game streaming: several Sunshine hosts, each with its own port. - The HTTP proxy is off by default. - The page says when a newer release exists. - Uninstall removes the home screen icon. - Priority setting for streams that stutter under a demanding game. - After the console's network is reconfigured, Tailscale is asked to rebind. Seen working across a short stay in rest mode. - Favicon, and the device list can be collapsed.
191 lines
5.7 KiB
C
191 lines
5.7 KiB
C
/* Home screen icon helper for Tailscale on PS5.
|
|
*
|
|
* A tiny payload that puts a "Tailscale" icon on the home screen: a media app
|
|
* whose only content is a link to the status page, which the console opens in
|
|
* its browser. The daemon's launcher carries this payload and hands it to the
|
|
* ELF loader the first time Tailscale runs. It is a separate payload so that
|
|
* the system libraries it needs are never loaded into the daemon's process.
|
|
*
|
|
* The same payload removes the icon again when its mode byte says so (see
|
|
* icon_mode below); the daemon uses that for Uninstall.
|
|
*
|
|
* Prints "icon: ok" or "icon: removed" on success; the launcher and the
|
|
* daemon look for that.
|
|
*
|
|
* Build with -DASSET_DIR="path/to/appicon" and link, in this order,
|
|
* -lSceIpmi -lSceAppInstUtil -lSceUserService -lSceSystemService (with
|
|
* libSceAppInstUtil alone the payload is never started). */
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <ps5/kernel.h>
|
|
|
|
#ifndef ASSET_DIR
|
|
#error "ASSET_DIR must name the folder with param.json and icon0.png"
|
|
#endif
|
|
|
|
#define TITLE_ID "TSCL00001"
|
|
#define APP_DIR "/user/app/" TITLE_ID
|
|
|
|
#define INCASSET(name, file) \
|
|
__asm__(".section .rodata\n" \
|
|
".balign 16\n" \
|
|
".global " #name "\n" #name ":\n" \
|
|
".incbin \"" file "\"\n" \
|
|
".global " #name "_end\n" #name "_end:\n" \
|
|
".text\n"); \
|
|
extern const uint8_t name[]; \
|
|
extern const uint8_t name##_end[];
|
|
|
|
INCASSET(param_json, ASSET_DIR "/param.json")
|
|
INCASSET(icon_png, ASSET_DIR "/icon0.png")
|
|
|
|
int sceAppInstUtilInitialize(void);
|
|
int sceAppInstUtilTerminate(void);
|
|
int sceAppInstUtilAppInstallAll(void *);
|
|
int sceAppInstUtilAppUnInstall(const char *);
|
|
|
|
/* What to do. A payload sent to the ELF loader gets no arguments, so the
|
|
* mode is a byte in the file itself: the daemon changes the character after
|
|
* the '=' to 'R' before sending the helper when it wants the icon removed
|
|
* (see tsd/homeicon.go). It is volatile so that the compiler reads it at run
|
|
* time instead of baking the install branch in. */
|
|
volatile char icon_mode[] = "TSICON-MODE=I";
|
|
|
|
static int
|
|
remove_icon(void) {
|
|
int err;
|
|
|
|
if ((err = sceAppInstUtilInitialize())) {
|
|
printf("icon: sceAppInstUtilInitialize failed: 0x%08x\n", err);
|
|
return 1;
|
|
}
|
|
err = sceAppInstUtilAppUnInstall(TITLE_ID);
|
|
sceAppInstUtilTerminate();
|
|
/* Whatever the system left behind of the folder goes too. */
|
|
unlink(APP_DIR "/sce_sys/param.json");
|
|
unlink(APP_DIR "/sce_sys/icon0.png");
|
|
rmdir(APP_DIR "/sce_sys");
|
|
rmdir(APP_DIR);
|
|
if (err) {
|
|
printf("icon: removing the app failed: 0x%08x\n", err);
|
|
return 1;
|
|
}
|
|
printf("icon: removed\n");
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
write_file(const char *path, const uint8_t *data, size_t size) {
|
|
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
|
|
if (fd < 0) {
|
|
return -1;
|
|
}
|
|
while (size > 0) {
|
|
ssize_t n = write(fd, data, size);
|
|
if (n < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
data += n;
|
|
size -= n;
|
|
}
|
|
return close(fd);
|
|
}
|
|
|
|
/* Report whether the file at path already has exactly these contents. */
|
|
static int
|
|
file_matches(const char *path, const uint8_t *data, size_t size) {
|
|
struct stat st;
|
|
uint8_t *buf;
|
|
int same = 0;
|
|
FILE *f;
|
|
|
|
if (stat(path, &st) || (size_t)st.st_size != size || !(f = fopen(path, "rb"))) {
|
|
return 0;
|
|
}
|
|
if ((buf = malloc(size))) {
|
|
same = fread(buf, 1, size, f) == size && !memcmp(buf, data, size);
|
|
free(buf);
|
|
}
|
|
fclose(f);
|
|
return same;
|
|
}
|
|
|
|
int
|
|
main(void) {
|
|
int (*install_title_dir)(const char *, const char *, void *) = 0;
|
|
size_t param_size = param_json_end - param_json;
|
|
size_t icon_size = icon_png_end - icon_png;
|
|
pid_t pid = getpid();
|
|
intptr_t rootvnode;
|
|
uint32_t handle;
|
|
int err;
|
|
|
|
setvbuf(stdout, 0, _IONBF, 0);
|
|
|
|
/* /user/app is only writable as root outside the sandbox. */
|
|
if ((rootvnode = kernel_get_root_vnode())) {
|
|
kernel_set_proc_rootdir(pid, rootvnode);
|
|
kernel_set_proc_jaildir(pid, 0);
|
|
}
|
|
kernel_set_ucred_uid(pid, 0);
|
|
kernel_set_ucred_ruid(pid, 0);
|
|
kernel_set_ucred_svuid(pid, 0);
|
|
kernel_set_ucred_rgid(pid, 0);
|
|
kernel_set_ucred_svgid(pid, 0);
|
|
|
|
if (icon_mode[sizeof(icon_mode) - 2] == 'R') {
|
|
return remove_icon();
|
|
}
|
|
|
|
if (file_matches(APP_DIR "/sce_sys/param.json", param_json, param_size) &&
|
|
file_matches(APP_DIR "/sce_sys/icon0.png", icon_png, icon_size)) {
|
|
printf("icon: ok (already installed)\n");
|
|
return 0;
|
|
}
|
|
|
|
if ((err = sceAppInstUtilInitialize())) {
|
|
printf("icon: sceAppInstUtilInitialize failed: 0x%08x\n", err);
|
|
return 1;
|
|
}
|
|
mkdir(APP_DIR, 0755);
|
|
mkdir(APP_DIR "/sce_sys", 0755);
|
|
if (write_file(APP_DIR "/sce_sys/param.json", param_json, param_size) ||
|
|
write_file(APP_DIR "/sce_sys/icon0.png", icon_png, icon_size)) {
|
|
printf("icon: could not write to %s: %s\n", APP_DIR, strerror(errno));
|
|
sceAppInstUtilTerminate();
|
|
return 1;
|
|
}
|
|
|
|
/* Register just this title where the firmware supports it; otherwise ask
|
|
* for a rescan of everything under /user/app. */
|
|
if (!kernel_dynlib_handle(-1, "libSceAppInstUtil.sprx", &handle)) {
|
|
install_title_dir = (void *)kernel_dynlib_resolve(-1, handle, "Wudg3Xe3heE");
|
|
}
|
|
if (install_title_dir) {
|
|
err = install_title_dir(TITLE_ID, "/user/app/", 0);
|
|
} else {
|
|
err = sceAppInstUtilAppInstallAll(0);
|
|
}
|
|
sceAppInstUtilTerminate();
|
|
if (err) {
|
|
printf("icon: registering the app failed: 0x%08x\n", err);
|
|
return 1;
|
|
}
|
|
printf("icon: ok (installed %s)\n", TITLE_ID);
|
|
return 0;
|
|
}
|