mirror of
https://github.com/MoHadiShibli/Control4Free.git
synced 2026-10-06 08:00:26 +02:00
Review fixes before publishing
From a last pass over the code and a set of screenshots on phones, an iPad and
a PC:
- The menu button sat on top of R2 on a phone in landscape: R2 starts about 68px
from the right edge, room for one corner button, not two. The menu and full
screen buttons now stack down the edge instead.
- On a narrow phone the connection status was cut off mid-address ("Connected
to 192.16"). The address is now a detail that narrow screens leave out.
- The controller screen said "Controller 1 · Controller 1": the page's own label
and the console's name for it had become the same words. The console's name
is only added now when it says something more.
- Free controllers still offered "a controller on this device", and the app
"controllers connected to the phone or PC"; both now say gamepad, as the rest
of the page does.
- SECURITY.md said no website could drive the console. A page opened from a
saved file connects with Origin: null, and that has to be accepted because the
saved file is how gamepads work in browsers that keep the Gamepad API to secure
pages; a website can send the same Origin from a sandboxed frame. It now says
exactly that, and what such a page still cannot do.
- THIRD_PARTY.md credited the GoldHEN call to autorun.c; it is in sandbox.c.
- Two comments pointed at a tag that only exists locally.
This commit is contained in:
1 parent
a039ce9532
commit
77a1bba68d
6 files changed
+40
-17
No files matched your search
+13
-4
@@ -30,11 +30,12 @@ from the launcher app or the controller page, or turn the console off.
|
||||
Being open to your network is not the same as being open to the internet. These
|
||||
are the limits the code does enforce.
|
||||
|
||||
**A website you visit cannot drive your console.** A page on the internet can send
|
||||
requests to your PS4, so the service refuses the ones that matter:
|
||||
**Websites are kept out, with one deliberate gap.** A page on the internet that you
|
||||
visit can make your browser send requests to your PS4, so the service refuses the
|
||||
ones that matter:
|
||||
|
||||
- A WebSocket handshake carrying an `Origin` that is not the page's own address is
|
||||
refused with 403. A site's script cannot open the control socket.
|
||||
- A WebSocket handshake carrying another site's `Origin` is refused with 403, so an
|
||||
ordinary page's script cannot open the control socket.
|
||||
- The page is only served for a literal IP address or `localhost` in the `Host`
|
||||
header, so a domain name that resolves to your console (DNS rebinding) gets 403.
|
||||
- The launcher's own `GET /api/status` and `POST /api/stop` require the header
|
||||
@@ -44,6 +45,14 @@ requests to your PS4, so the service refuses the ones that matter:
|
||||
- The page is served with a Content-Security-Policy that keeps it to its own
|
||||
resources.
|
||||
|
||||
The gap: a handshake with `Origin: null` is accepted. That is what a browser sends
|
||||
for a controller page opened from a saved file, and the saved file is how gamepads
|
||||
work in browsers that only allow the Gamepad API on secure pages. A website can
|
||||
produce the same `Origin: null` from a sandboxed frame, so a page you have open
|
||||
while on your home network could take a free controller and press buttons, as
|
||||
anyone on your network can. It cannot take a controller someone is using, sign in
|
||||
as a user, or see your screen.
|
||||
|
||||
**One controller has one owner.** A slot is claimed by one WebSocket connection.
|
||||
Another device asking for the same slot is refused (409), and input never claims a
|
||||
slot implicitly. A client that leaves has its buttons released at once.
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
GPL-3.0). The toolchain supplies `sce_sys/about/right.sprx` and its stub
|
||||
`sce_module/libc.prx` and `sce_module/libSceFios2.prx`; LibOrbisPkg produces the
|
||||
package. `docker/Dockerfile.launcher` pins the OpenOrbis image by digest.
|
||||
- `launcher/autorun.c` calls GoldHEN's SDK command (syscall 500, jailbreak and
|
||||
- `launcher/sandbox.c` calls GoldHEN's SDK command (syscall 500, jailbreak and
|
||||
unjailbreak) with the `jailbreak_backup` layout from the GoldHEN Plugins SDK
|
||||
(https://github.com/GoldHEN/GoldHEN_Plugins_SDK), MIT. No SDK code is included.
|
||||
- `vendor/qrcodegen/qrcodegen.c` and `.h` are Project Nayuki's QR generator v1.8.0
|
||||
|
||||
+21
-8
@@ -148,6 +148,11 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#connText, .conn-detail { overflow: hidden; text-overflow: ellipsis; }
|
||||
#connText { flex: none; }
|
||||
.conn-detail { min-width: 0; margin-left: -4px; }
|
||||
@media (max-width: 520px) { .conn-detail { display: none; } }
|
||||
|
||||
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--faint); flex: none; }
|
||||
[data-conn="open"] .dot { background: var(--ok); box-shadow: 0 0 10px var(--ok); }
|
||||
[data-conn="connecting"] .dot { background: var(--warn); animation: pulse 1.2s ease-in-out infinite; }
|
||||
@@ -675,12 +680,15 @@
|
||||
.corner-btn:hover { background: rgba(32, 39, 53, .9); color: #fff; }
|
||||
.corner-btn:active { transform: scale(.92); }
|
||||
.corner-btn.left { left: max(8px, env(safe-area-inset-left)); }
|
||||
/* A column, not a row: in landscape R2 starts about 68px from the right
|
||||
edge, room for one button beside it, not two. */
|
||||
.corner-group {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: max(8px, env(safe-area-inset-top));
|
||||
right: max(8px, env(safe-area-inset-right));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.corner-group .corner-btn { position: static; }
|
||||
@@ -976,7 +984,7 @@
|
||||
<svg viewBox="56 38 888 566" aria-hidden="true"><path fill="#fff" d="M500 78C410 78 330 74 262 62C232 46 188 42 146 62C108 80 86 120 78 170C70 230 58 320 60 400C62 470 80 535 118 568C152 598 196 592 222 560C250 525 272 470 300 430C360 418 430 440 500 440C570 440 640 418 700 430C728 470 750 525 778 560C804 592 848 598 882 568C920 535 938 470 940 400C942 320 930 230 922 170C914 120 892 80 854 62C812 42 768 46 738 62C670 74 590 78 500 78ZM366 98L634 98C648.359 98 660 109.641 660 124L660 226C660 240.359 648.359 252 634 252L366 252C351.641 252 340 240.359 340 226L340 124C340 109.641 351.641 98 366 98ZM173 148L219 148L219 189L260 189L260 235L219 235L219 276L173 276L173 235L132 235L132 189L173 189L173 148ZM804 121C818.912 121 831 133.088 831 148C831 162.912 818.912 175 804 175C789.088 175 777 162.912 777 148C777 133.088 789.088 121 804 121ZM804 249C818.912 249 831 261.088 831 276C831 290.912 818.912 303 804 303C789.088 303 777 290.912 777 276C777 261.088 789.088 249 804 249ZM740 185C754.912 185 767 197.088 767 212C767 226.912 754.912 239 740 239C725.088 239 713 226.912 713 212C713 197.088 725.088 185 740 185ZM868 185C882.912 185 895 197.088 895 212C895 226.912 882.912 239 868 239C853.088 239 841 226.912 841 212C841 197.088 853.088 185 868 185ZM338 276C374.451 276 404 305.549 404 342C404 378.451 374.451 408 338 408C301.549 408 272 378.451 272 342C272 305.549 301.549 276 338 276ZM338 298C313.699 298 294 317.699 294 342C294 366.301 313.699 386 338 386C362.301 386 382 366.301 382 342C382 317.699 362.301 298 338 298ZM662 276C698.451 276 728 305.549 728 342C728 378.451 698.451 408 662 408C625.549 408 596 378.451 596 342C596 305.549 625.549 276 662 276ZM662 298C637.699 298 618 317.699 618 342C618 366.301 637.699 386 662 386C686.301 386 706 366.301 706 342C706 317.699 686.301 298 662 298ZM500 319C509.389 319 517 326.611 517 336C517 345.389 509.389 353 500 353C490.611 353 483 345.389 483 336C483 326.611 490.611 319 500 319Z"/></svg>
|
||||
<span>Control4Free</span>
|
||||
</div>
|
||||
<span class="conn" id="conn" data-conn="connecting" role="status" aria-live="polite"><span class="dot"></span><span id="connText">Connecting</span></span>
|
||||
<span class="conn" id="conn" data-conn="connecting" role="status" aria-live="polite"><span class="dot"></span><span id="connText">Connecting</span><span class="conn-detail" id="connDetail"></span></span>
|
||||
</div>
|
||||
<div class="tb-group">
|
||||
<button class="icon-btn" type="button" id="openSettings" aria-label="Settings">
|
||||
@@ -1244,11 +1252,13 @@
|
||||
return store.get('address', '');
|
||||
}
|
||||
|
||||
function setConnState(state, text) {
|
||||
// `detail` (the address) is dropped on narrow screens, the rest never is.
|
||||
function setConnState(state, text, detail) {
|
||||
conn.state = state;
|
||||
$('#conn').dataset.conn = state;
|
||||
$('#padView').dataset.conn = state;
|
||||
$('#connText').textContent = text;
|
||||
$('#connDetail').textContent = detail || '';
|
||||
renderPadLabel();
|
||||
}
|
||||
|
||||
@@ -1265,7 +1275,8 @@
|
||||
return;
|
||||
}
|
||||
if (!servedByConsole) store.set('address', conn.address);
|
||||
setConnState('connecting', conn.everOpened ? 'Waiting for Control4Free on the PS4' : 'Connecting to ' + conn.address);
|
||||
if (conn.everOpened) setConnState('connecting', 'Waiting for Control4Free on the PS4');
|
||||
else setConnState('connecting', 'Connecting', ' to ' + conn.address);
|
||||
let ws;
|
||||
try {
|
||||
ws = new WebSocket('ws://' + conn.address);
|
||||
@@ -1278,7 +1289,7 @@
|
||||
ws.onopen = () => {
|
||||
conn.retry = 0;
|
||||
conn.everOpened = true;
|
||||
setConnState('open', 'Connected to ' + conn.address);
|
||||
setConnState('open', 'Connected', ' to ' + conn.address);
|
||||
$('#addressForm').hidden = servedByConsole;
|
||||
$('#addressHint').classList.remove('error');
|
||||
call('info', []).then(info => {
|
||||
@@ -1306,7 +1317,7 @@
|
||||
stopAllRumble();
|
||||
const wait = Math.min(5000, 800 * Math.pow(1.6, conn.retry++));
|
||||
if (!conn.everOpened && !servedByConsole) {
|
||||
setConnState('closed', 'Can\'t reach ' + conn.address);
|
||||
setConnState('closed', 'Can\'t reach', ' ' + conn.address);
|
||||
$('#addressForm').hidden = false;
|
||||
$('#addressHint').textContent = 'Can\'t reach the console. Load the Control4Free payload, then check the address.';
|
||||
$('#addressHint').classList.add('error');
|
||||
@@ -2299,7 +2310,7 @@
|
||||
if (s.state === 'ready') return 'Signed in on the PS4';
|
||||
if (s.state === 'paused') return 'Input paused';
|
||||
if (s.state === 'error') return 'Input error — disconnect and reconnect';
|
||||
return 'Touch, keyboard, or a controller on this device';
|
||||
return 'Touch, keyboard, or a gamepad on this device';
|
||||
}
|
||||
|
||||
function stateItem(cls, text) {
|
||||
@@ -2405,9 +2416,11 @@
|
||||
const s = status[primaryPad];
|
||||
document.documentElement.style.setProperty('--accent', css(s.color));
|
||||
const label = $('#padLabel');
|
||||
label.textContent = 'Controller ' + (primaryPad + 1) + (s.known && s.name ? ' · ' + s.name : '');
|
||||
// The console's name for it, when it says more than the number does.
|
||||
const title = 'Controller ' + (primaryPad + 1);
|
||||
label.textContent = title + (s.known && s.name && s.name !== title ? ' · ' + s.name : '');
|
||||
const state = padState(s);
|
||||
const extra = conn.state !== 'open' ? $('#connText').textContent
|
||||
const extra = conn.state !== 'open' ? $('#connText').textContent + $('#connDetail').textContent
|
||||
: state === 'ready' ? 'Signed in on the PS4'
|
||||
: state === 'connecting' ? 'Connecting to the PS4'
|
||||
: state === 'select' ? 'Choose your user on TV with D-pad and Cross'
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
/* Control4Free -- the virtual device (VDA) lifecycle.
|
||||
*
|
||||
* One C4fVirtualPad is one virtual DualShock 4 as the system sees it. The call
|
||||
* order and its reasons came from the spike (tag spike-final, dev notes).
|
||||
* order and its reasons came from a staged diagnostic build, in the git history
|
||||
* before 1.0.0.
|
||||
*
|
||||
* No function here blocks or waits, so the service can keep answering everyone
|
||||
* else while a controller is being created.
|
||||
|
||||
+1
-1
@@ -496,7 +496,7 @@ void c4fDrawLauncher(uint32_t *pixels, const C4fLauncherScreen *s)
|
||||
c4fText(&c, C4F_FONT_LIGHT, 28, fieldX + 28, fieldY + fieldHeight / 2 + 10, C4F_MUTED,
|
||||
"Connect the PS4 to your home network");
|
||||
c4fTextWrapped(&c, C4F_FONT_LIGHT, 24, fieldX, fieldY + fieldHeight + 44, fieldWidth, 36, 2, C4F_MUTED,
|
||||
"Use the same network as the PS4. Touch, a keyboard and controllers connected to the phone "
|
||||
"Use the same network as the PS4. Touch, a keyboard and gamepads connected to the phone "
|
||||
"or PC all work.");
|
||||
|
||||
/* The last result along the bottom of the tile. */
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
* virtual-device API, the one Remote Play goes through. Phones and PCs drive
|
||||
* the controllers from a page the payload serves on port 4264 (web.c).
|
||||
*
|
||||
* The staged spike that established the call order is in git history, tag
|
||||
* spike-final.
|
||||
* The staged diagnostic build that worked out the call order is in the git
|
||||
* history before 1.0.0.
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
Reference in new issue
Block a user