Commit Graph
8 Commits
Author SHA1 Message Date
MoHadiShibli a039ce9532 Package: list the app under Applications, not Games
The package declared itself a game (CATEGORY gd), so the PS4 filed it under
Games in the library.

It now uses the values Apollo Save Tool ships with: CATEGORY gde ("Non-Game Mini
App"), ATTRIBUTE 32, APP_TYPE 1. Apollo is built with the same OpenOrbis tools and
lists under Applications. APP_TYPE 4 ("Freemium"), which is reported to move apps
there too, made the PS4 refuse to start ours (CE-39929-2), so it stays 1.

Confirmed on the console (firmware 10.01): the app installs, starts, and lists
under Library > Applications. A test reads the fields back out of the built
package.
2026-10-05 15:30:36 +03:00
MoHadiShibli 5b8433fbba Page: on iPhone, the full-screen button explains the home screen instead
Safari on iPhone lets only video go full screen, so the button did nothing
there. The check for it looked for requestFullscreen, which exists on iPhone even
though every call is refused; it now asks fullscreenEnabled instead.

On iPhone the button now says how to get the same thing: add the page to the Home
Screen and open it from there, where it runs without Safari's bars. When the page
already runs that way, or is installed anywhere else, the button goes away.
Elsewhere it goes full screen as before, prefixed or not.
2026-10-05 14:45:57 +03:00
MoHadiShibli 0c395576eb Launcher: refresh the status every 2 seconds, as it was meant to
The app's controller count, and everything else on its screen, only changed when
the app was closed and opened again.

OpenOrbis's time.h gives CLOCK_MONOTONIC the Linux value, 1. The PS4's kernel is
FreeBSD's, where clock 1 is CLOCK_VIRTUAL: the process's own CPU time. The worker
sleeps between checks and uses almost none, so its clock barely moved and the
check due 2 seconds later never came round. Only the first check, on start-up,
ever ran. The payload is unaffected: its SDK has FreeBSD's value, 4.

The launcher now uses 4 when it is built for the console. Checked the other
constants the app uses against FreeBSD's: everything matches except this one,
MSG_NOSIGNAL and SIGSYS, which were already worked around. A test now fails if
any of the three is used directly in the launcher again.
2026-10-05 14:31:24 +03:00
MoHadiShibli 34f3a3e349 Stop deleting controllers the moment they are created
On the console, every new controller was removed straight away with "That
controller was disconnected after sitting unused".

The idle reaper compared unsigned millisecond stamps directly: now - lastInput.
`now` is read at the top of a main-loop iteration, but since creation became a
state machine the claim finishes inside that iteration and stamps lastInput from
the clock afterwards. Once the two fell on different milliseconds the subtraction
wrapped to about 49 days, and every controller in the claim was deleted. On the
console the gap is InsertData and klog work, so it happened nearly every time;
on a fast host it almost never did, which is why the tests missed it.

Every elapsed-time comparison in web.c now goes through c4fSince, which cannot
wrap, and the clock is read again before the reaper runs. The reaper also logs
what it removes and why, so this would have shown up in the log at once.

The test stub can now spend time inside an iteration the way the console does
(C4F_TEST_SLOW_MS), and the new test fails on the old code in its first round.
2026-10-05 13:10:34 +03:00
MoHadiShibli 243f5efa68 Keep the page on a home screen, say the address out loud, and say what went wrong
Three bits of polish for people who did not write this.

The payload serves /manifest.webmanifest and /icon-192.png, and the page points
at both, so a phone can keep the controller on its home screen instead of typing
an IP address again. The icon is the app's own icon, rendered by the launcher's
drawing code and committed, so the payload build needs no renderer; a test
re-renders it and fails if the two drift apart. One size only: the gradient does
not compress, and a 512 copy would add 150 KB for a splash screen that a plain
HTTP shortcut never shows. write_png now picks the best filter per row, which
also shrinks the package icon.

When the service starts it works out which address the console is actually
reachable on, from the route to the outside world without sending anything, and
puts it in the start-up notification on the TV.

Every error the page can show now says what happened and what to do about it,
rather than "Invalid axis", and the README has a troubleshooting section covering
the ones people will actually hit.
2026-10-05 02:31:50 +03:00
MoHadiShibli 868840e409 Send input the moment it arrives, and leave an idle controller alone
Input was queued and handed to the virtual device on a 16 ms tick, so a press
could wait most of a frame before the console saw it, and the page only sent
state once per animation frame.

The payload now reports a sample as soon as it arrives, unless one went out
within the last 4 ms, and then keeps a 4 ms cadence while the player is doing
something, as a real DualShock 4 does. Half a second after the input stops it
falls back to the 16 ms keepalive, and the main loop's wait follows: 2 ms while
input is moving, 8 ms with a controller sitting idle, 50 ms with no controller at
all, so an unused service costs the console almost nothing.

On the page, a button, touch or key edge is flushed where it happens instead of
waiting for the next frame; stick and trigger movement still rides the frame
loop, where coalescing is what you want. A `ping` method and a round-trip
reading next to the version make a laggy network visible rather than guessed at.
2026-10-05 02:20:01 +03:00
MoHadiShibli 20b6ea2e2a Create controllers without stopping the service, and match only our own device
Connecting a controller took a second or two in the middle of the request
handler: drain the kernel log, prove it delivers, call AddDevice, then read the
log until the device turns up. Everyone else waited. A second player adding a
controller froze the first player's input for as long as it took.

The service now owns one kernel-log reader and reads it a line at a time from
its main loop, and creating a controller is a state machine that loop advances:
drain, verify with a marker, AddDevice, adopt the DeviceId. Nothing blocks, so
the other players keep playing and keep being answered while it runs. Their
slots show "Connecting" until the claim is complete. One controller is created
at a time, because two AddDevice calls at once produce two log lines with no way
to tell which device is which; a second request is refused rather than queued.

Nothing in vda.c waits any more, so the callback that kept pads reporting
through those waits is gone, and the line parsing moved to src/klog_line.c,
which the host tests build as it is.

Only the login manager's own line is accepted as our new device now:
SCE_MBUS_EVENT_DEVICE_ADDED with subType 2. The old code fell back to any
"device added" line it saw, so a real pad being plugged in while a controller
was being created could hand us a DeviceId we do not own, and every input would
go to someone else's pad.

On the page: a refused claim re-reads the console's view and gives up only the
controllers that are in use elsewhere, instead of disconnecting everything this
device was playing with; and when getGamepads() starts throwing, every gamepad
source is blanked, so a held button cannot be flushed for ever with nothing left
to release it.

The test stub is now a fake kernel log: it writes the lines firmware 10.01
writes, our own mirrored output included, so the real reading, marker check and
DeviceId matching are what the tests exercise. SECURITY.md writes down what the
open-LAN design does and does not defend.
2026-10-05 02:12:34 +03:00
MoHadiShibli 4331ec79fd Cleanup: drop the diagnostic spike, one version source, public tests
The staged spike that worked out the VDA call order is no longer part of the
build. It is kept at tag spike-final.

- src/main.c is the service only: instance lock, credentials, MBus and pad
  init, then the web service. src/server.c and include/c4f_server.h are gone
  with stage 8, and so are the stage-only helpers in src/vda.c: the MBus bind
  and holds, the assignment and button-map probes, and the GoldHEN klog stream
  path. The service reads /dev/klog, which is what works.
- VERSION holds the only version number. Both Makefiles pass it in, the
  packager reads it for param.sfo and the package name, and the build and
  deploy scripts read it for the file they look for.
- The payload's log is /data/control4free/control4free.log, not spike.log.
- The page drops the protocol 1 branches: the payload has always answered 2.
  A console slot is a "controller" and a pad plugged into the phone or PC is a
  "gamepad", so the two no longer read as the same word.
- The host tests move to tests/, with tests/run.py to run all four suites. They
  build the real sources with the PS4 calls stubbed, so they need no console.
  klog_test now covers the device path only, and asserts that nothing ever
  connects to GoldHEN's klog server.
2026-10-05 01:53:11 +03:00