mirror of
https://github.com/MoHadiShibli/Control4Free.git
synced 2026-10-06 09:00:29 +02:00
Release scaffolding: changelog, CI, and a research build for rumble
CHANGELOG.md says what 1.0.0 is. The CI workflow builds both toolchain images, the payload and the package, runs every host test, and on a vX.Y.Z tag checks the tag against VERSION before publishing the ELF, the package and their SHA256 sums. `make C4F_PROBE=1` adds a poll of scePadVirtualDeviceGetRemoteSetting for every live controller, logging the return code and only the bytes that change. That is the one call that looks like it could carry a game's rumble and light-bar state back to a virtual pad, and nothing is known about its buffer, so this is how to find out on hardware. It builds to control4free-probe.elf and is never in a release.
This commit is contained in:
1 parent
243f5efa68
commit
de0c0a3eea
5 files changed
+173
-6
No files matched your search
@@ -0,0 +1,59 @@
|
||||
name: build
|
||||
|
||||
# Builds both toolchain images, the payload, the installable package, and runs
|
||||
# every host test. On a vX.Y.Z tag it also publishes the release.
|
||||
on:
|
||||
push:
|
||||
branches: [main, next]
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write # the release step needs it; nothing else writes
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build the toolchain images
|
||||
run: |
|
||||
docker build -t control4free-build -f docker/Dockerfile docker
|
||||
docker build -t control4free-launcher-build -f docker/Dockerfile.launcher docker
|
||||
|
||||
- name: Build the payload and the package
|
||||
run: |
|
||||
docker run --rm --network none -v "$PWD:/src" -w /src \
|
||||
control4free-launcher-build bash -lc 'make && make -C launcher'
|
||||
|
||||
- name: Host tests
|
||||
run: |
|
||||
docker run --rm --network none -v "$PWD:/src" -w /src \
|
||||
control4free-launcher-build python3 -B tests/run.py
|
||||
|
||||
- name: Collect what was built
|
||||
run: |
|
||||
version=$(cat VERSION)
|
||||
mkdir -p artifacts
|
||||
cp build/control4free.elf "build/Control4Free-$version.pkg" artifacts/
|
||||
cd artifacts && sha256sum * > SHA256SUMS && cat SHA256SUMS
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: control4free
|
||||
path: artifacts/
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Check the tag matches VERSION
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
test "v$(cat VERSION)" = "${GITHUB_REF#refs/tags/}" \
|
||||
|| { echo "tag ${GITHUB_REF#refs/tags/} does not match VERSION $(cat VERSION)"; exit 1; }
|
||||
|
||||
- name: Publish the release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: gh release create "${GITHUB_REF#refs/tags/}" artifacts/* --verify-tag --notes-file CHANGELOG.md
|
||||
@@ -0,0 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
Dates are the day the work landed. Versions follow [Semantic Versioning](https://semver.org):
|
||||
PATCH for fixes, MINOR for features that break nothing, MAJOR for anything people
|
||||
rely on that changes.
|
||||
|
||||
## 1.0.0 — unreleased
|
||||
|
||||
First release.
|
||||
|
||||
Control4Free is a GoldHEN payload that turns a phone or PC into a PS4 controller
|
||||
that works everywhere: the home screen, the sign-in screen and games. Up to four
|
||||
of them, through the PS4's own virtual-device API, with the PS4's own
|
||||
"Who's using this controller?" screen deciding who each one is.
|
||||
|
||||
- A controller page the payload serves itself on port 4264. Touch controls shaped
|
||||
like a DualShock 4, keyboard keys, or any gamepad the browser exposes. A layout
|
||||
editor, key remapping and a per-controller picker, saved per device.
|
||||
- An installable app for the PS4's home screen that starts and stops Control4Free,
|
||||
shows its address and a QR code, and sets up GoldHEN's AutoRun so the console
|
||||
starts it by itself after every restart.
|
||||
- The page can be kept on a phone's home screen.
|
||||
- Input is reported the moment it arrives, at a DualShock 4's own 4 ms cadence
|
||||
while a player is doing something, and at a slow keepalive while nobody is.
|
||||
- Connecting a controller never stops the other players being served.
|
||||
- Recovers by itself after rest mode or a network failure: controllers have to be
|
||||
picked again, which is what rest mode does to real ones too.
|
||||
- Open on the local network by design, with no pairing. What that does and does
|
||||
not protect is written down in [SECURITY.md](SECURITY.md).
|
||||
|
||||
Tested on firmware 10.01 with GoldHEN 2.4b18.10. Rumble, light bar and motion are
|
||||
not implemented.
|
||||
@@ -1,7 +1,8 @@
|
||||
# Control4Free -- system-wide virtual controllers for the PS4 (GoldHEN payload).
|
||||
#
|
||||
# Build:
|
||||
# make # build/control4free.elf
|
||||
# make # build/control4free.elf
|
||||
# make C4F_PROBE=1 # plus the rumble/light-bar research probe (dev only)
|
||||
# make clean
|
||||
#
|
||||
# A build needs the ps4-payload-sdk; docker/Dockerfile has it, and
|
||||
@@ -19,11 +20,20 @@ NAME := control4free
|
||||
ELF := build/$(NAME).elf
|
||||
OBJDIR := build/payload
|
||||
SOURCES := src/main.c src/log.c src/vda.c src/klog_line.c src/web.c src/net.c
|
||||
|
||||
# Research build: logs what scePadVirtualDeviceGetRemoteSetting returns. Its ELF
|
||||
# gets its own name, so it can never be mistaken for a release.
|
||||
ifdef C4F_PROBE
|
||||
EXTRA_CFLAGS := -DC4F_PROBE_SETTING
|
||||
ELF := build/$(NAME)-probe.elf
|
||||
OBJDIR := build/probe
|
||||
endif
|
||||
|
||||
OBJECTS := $(SOURCES:src/%.c=$(OBJDIR)/%.o) $(OBJDIR)/client.o $(OBJDIR)/assets.o
|
||||
|
||||
CFLAGS += -std=gnu11 -Wall -Wextra -Wpointer-arith -g -O2
|
||||
CFLAGS += -MMD -MP
|
||||
CFLAGS += -Iinclude -Ivendor/jsmn -DC4F_VERSION='"$(VERSION)"'
|
||||
CFLAGS += -Iinclude -Ivendor/jsmn -DC4F_VERSION='"$(VERSION)"' $(EXTRA_CFLAGS)
|
||||
LDFLAGS += -lScePad -lSceUserService -ldl -lpthread
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
@@ -5,10 +5,13 @@ keyboard, or an Xbox/other controller exposed by the browser's Gamepad API.
|
||||
Virtual controllers use the PS4's native user-selection screen, so they work on
|
||||
the home screen, at sign-in and in games.
|
||||
|
||||
**Status: browser control, native user sign-in, gameplay, the launcher,
|
||||
GoldHEN AutoRun and recovery after rest mode have been confirmed on the
|
||||
development console.** Four virtual slots are exposed; the console's device
|
||||
limits apply.
|
||||
Browser control, native user sign-in, gameplay, the launcher app, GoldHEN
|
||||
AutoRun and recovery after rest mode are all confirmed on the development
|
||||
console. Four controllers at a time; the console's own device limits apply.
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for what is in a release, and
|
||||
[SECURITY.md](SECURITY.md) for what being open on your network does and does not
|
||||
mean.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -173,6 +176,10 @@ readable over GoldHEN's FTP server, with the previous run kept beside it.
|
||||
|
||||
## Build
|
||||
|
||||
Everything builds in Docker, so nothing has to be installed on the machine. CI
|
||||
(`.github/workflows/build.yml`) runs exactly these steps and attaches the payload,
|
||||
the package and their SHA256 sums to each tagged release.
|
||||
|
||||
The [ps4-payload-sdk](https://github.com/ps4-payload-dev/sdk) toolchain is pinned
|
||||
in the Docker image:
|
||||
|
||||
|
||||
@@ -225,6 +225,62 @@ static void c4fEnqueue(C4fWebPad *p, const ScePadData *data)
|
||||
p->queue[(p->head+p->count)%C4F_QUEUE_SIZE] = *data; p->count++;
|
||||
}
|
||||
|
||||
#ifdef C4F_PROBE_SETTING
|
||||
/* Research only, built with `make C4F_PROBE=1`, never in a release.
|
||||
*
|
||||
* A real DualShock 4 is told to rumble and to change its light bar by the game.
|
||||
* For a virtual pad that has to arrive through the same API the Remote Play path
|
||||
* uses, and scePadVirtualDeviceGetRemoteSetting is the only call that looks like
|
||||
* it carries anything back. Nothing is known about its buffer, so this polls it
|
||||
* for every live controller and logs the return code once and then only what
|
||||
* changes. Play something that rumbles, set a light bar, and read the log.
|
||||
*
|
||||
* The page already knows how to act on both ('v' and 'l' messages), so if the
|
||||
* fields turn up here, driving them is a small step. */
|
||||
static void c4fProbeSetting(C4fWeb *app, uint64_t now)
|
||||
{
|
||||
static uint64_t nextAt;
|
||||
static unsigned char previous[C4F_MAX_PADS][256];
|
||||
static int32_t lastRet[C4F_MAX_PADS];
|
||||
static int seen[C4F_MAX_PADS];
|
||||
unsigned char current[256];
|
||||
|
||||
if (now < nextAt) return;
|
||||
nextAt = now + 100;
|
||||
for (int i = 0; i < C4F_MAX_PADS; i++) {
|
||||
C4fWebPad *p = &app->pads[i];
|
||||
int32_t ret;
|
||||
int first, from = -1, to = -1;
|
||||
|
||||
if (!p->active) { seen[i] = 0; continue; }
|
||||
if (!scePadVirtualDeviceGetRemoteSetting) {
|
||||
if (!seen[i]) c4fLog("probe: scePadVirtualDeviceGetRemoteSetting is not exported\n");
|
||||
seen[i] = 1;
|
||||
continue;
|
||||
}
|
||||
memset(current, 0, sizeof(current));
|
||||
ret = scePadVirtualDeviceGetRemoteSetting(p->device.handle, current);
|
||||
first = !seen[i];
|
||||
seen[i] = 1;
|
||||
if (first || ret != lastRet[i]) {
|
||||
c4fLog("probe: controller %d GetRemoteSetting = 0x%08x\n", i + 1, (uint32_t)ret);
|
||||
lastRet[i] = ret;
|
||||
}
|
||||
if (ret != 0) continue;
|
||||
for (int b = 0; b < (int)sizeof(current); b++)
|
||||
if (first || current[b] != previous[i][b]) { if (from < 0) from = b; to = b; }
|
||||
if (from >= 0) {
|
||||
char hex[3 * 256 + 1];
|
||||
int used = 0;
|
||||
for (int b = from; b <= to && used + 3 < (int)sizeof(hex); b++)
|
||||
used += snprintf(hex + used, sizeof(hex) - (size_t)used, "%02x ", current[b]);
|
||||
c4fLog("probe: controller %d bytes %d-%d: %s\n", i + 1, from, to, hex);
|
||||
memcpy(previous[i], current, sizeof(current));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* One kernel-log line. Our own mirrored output is skipped: c4fLog writes to klog
|
||||
* too, so a line that reacted to a line would feed itself for ever. The marker
|
||||
* c4fKlogMark writes carries no [c4f], which is how it gets through. */
|
||||
@@ -594,6 +650,9 @@ int c4fWebRun(int klogFd)
|
||||
}
|
||||
long klogBytes = c4fReadKlog(app);
|
||||
c4fReportPads(app);
|
||||
#ifdef C4F_PROBE_SETTING
|
||||
c4fProbeSetting(app, now);
|
||||
#endif
|
||||
c4fAdvanceAdd(app, now, klogBytes);
|
||||
for (int i = 0; i < C4F_MAX_PADS; i++) {
|
||||
C4fWebPad *p = &app->pads[i];
|
||||
|
||||
Reference in new issue
Block a user