mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-21 16:12:27 -05:00
The WebUI only ran inside RetroArch: a frontend change (LoadExec) ended it with every upload in flight. It is now a payload of its own (daemon/webui_daemon.cpp, webui/ps5-retroarch-webui.elf) serving the title's folder on 6769. eboot.bin, the picker and ES-DE each keep a link to it (src/webui_link.h, 127.0.0.1:6770) and the first to find none starts it through the ELF loader (9021) or websrv (8080 /hbldr). It stops 20 s after the title closes, never mid-transfer; an update's Install closes the title and the daemon installs. With no loader, eboot.bin serves the WebUI while RetroArch runs, as before. The page names the frontend shown. Downloads were handed to MHD as a descriptor (sendfile), which stopped at 33,300 bytes on the console; they are read in 256 KiB blocks. Evidence: evidence/webui-daemon (base PS5 13.40). 200 MiB upload across picker -> RetroArch -> picker and 60 MiB across picker -> ES-DE: one token throughout, 201 Created, every 256 KiB block equal on the console; daemon stopped 20 s after the title closed; a 1,917,648,896-byte download's sha256 equals the FTP copy. Command: tools/run-title.sh --picker-test=600:retroarch --retroarch-frames=1800 --watch 110 with build/tmp/webui-handover.py; tools/verify.sh: all five PASS. Host tests: tests/test_webui_link.py, tests/test_webui_daemon.py.
145 lines
5.5 KiB
Python
145 lines
5.5 KiB
Python
"""The WebUI link (src/webui_link.h) against a loader, a launcher and a daemon played
|
|
here: neither, the daemon sent and linked to, its install request, a stale daemon, and
|
|
the daemon started by the homebrew launcher when there is no loader."""
|
|
from pathlib import Path
|
|
import socket
|
|
import subprocess
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
import unittest
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
ELF = b'\x7fELF' + bytes(range(256)) * 300
|
|
|
|
|
|
def free_port():
|
|
with socket.socket() as s:
|
|
s.bind(('127.0.0.1', 0))
|
|
return s.getsockname()[1]
|
|
|
|
|
|
def listener(port):
|
|
s = socket.socket()
|
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
s.bind(('127.0.0.1', port))
|
|
s.listen(4)
|
|
s.settimeout(10)
|
|
return s
|
|
|
|
|
|
def read_line(conn):
|
|
data = b''
|
|
while not data.endswith(b'\n'):
|
|
chunk = conn.recv(1)
|
|
if not chunk:
|
|
break
|
|
data += chunk
|
|
return data.decode()
|
|
|
|
|
|
class WebuiLink(unittest.TestCase):
|
|
def setUp(self):
|
|
self.dir = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.dir.cleanup)
|
|
app = Path(self.dir.name)
|
|
(app / 'sce_sys').mkdir()
|
|
(app / 'sce_sys/param.json').write_text('{"contentId": "x", "titleId": "PPSA99169"}')
|
|
(app / 'webui').mkdir()
|
|
(app / 'webui/ps5-retroarch-webui.elf').write_bytes(ELF)
|
|
self.link, self.loader, self.launcher = free_port(), free_port(), free_port()
|
|
self.binary = str(app / 'link-test')
|
|
subprocess.run(['c++', '-std=c++17', '-O2', '-Wall', '-Wextra', '-Werror', '-pthread',
|
|
f'-DPS5_WEBUI_APP0="{app}"', f'-DPS5_WEBUI_LINK_PORT={self.link}',
|
|
f'-DPS5_WEBUI_LOADER_PORT={self.loader}', f'-DPS5_WEBUI_LAUNCHER_PORT={self.launcher}',
|
|
'tests/webui_link_test.cpp',
|
|
'-o', self.binary], cwd=ROOT, check=True)
|
|
|
|
def run_link(self):
|
|
return subprocess.Popen([self.binary], stdout=subprocess.PIPE, text=True)
|
|
|
|
def loader_then_daemon(self, replies, received):
|
|
"""The loader takes the ELF whole; then a daemon per reply answers the hello."""
|
|
for reply in replies:
|
|
with listener(self.loader) as loader:
|
|
conn, _ = loader.accept()
|
|
with conn:
|
|
data = b''
|
|
while chunk := conn.recv(65536):
|
|
data += chunk
|
|
received.append(('elf', data == ELF))
|
|
with listener(self.link) as daemon:
|
|
conn, _ = daemon.accept()
|
|
with conn:
|
|
received.append(('hello', read_line(conn)))
|
|
conn.sendall(reply.encode() + b'\n')
|
|
if reply != 'ok':
|
|
continue
|
|
received.append(('frontend', read_line(conn)))
|
|
conn.sendall(b'install\n')
|
|
time.sleep(0.5)
|
|
|
|
def test_no_loader(self):
|
|
start = time.monotonic()
|
|
out, _ = self.run_link().communicate(timeout=20)
|
|
self.assertLess(time.monotonic() - start, 5)
|
|
self.assertIn('wait=0', out)
|
|
self.assertIn(f'no ELF loader on {self.loader}', out)
|
|
self.assertIn(f'no homebrew launcher on {self.launcher}', out)
|
|
self.assertIn('install=0', out)
|
|
|
|
def test_daemon_started_linked_and_install(self):
|
|
received = []
|
|
thread = threading.Thread(target=self.loader_then_daemon, args=(['ok'], received))
|
|
thread.start()
|
|
time.sleep(0.2)
|
|
out, _ = self.run_link().communicate(timeout=30)
|
|
thread.join()
|
|
self.assertEqual(received, [('elf', True), ('hello', 'hello title PPSA99169\n'),
|
|
('frontend', 'frontend retroarch\n')])
|
|
self.assertIn('wait=1', out)
|
|
self.assertIn('install=1 callbacks=1', out)
|
|
|
|
def test_stale_daemon_replaced(self):
|
|
received = []
|
|
thread = threading.Thread(target=self.loader_then_daemon, args=(['stale', 'ok'], received))
|
|
thread.start()
|
|
time.sleep(0.2)
|
|
out, _ = self.run_link().communicate(timeout=30)
|
|
thread.join()
|
|
self.assertEqual([r[0] for r in received], ['elf', 'hello', 'elf', 'hello', 'frontend'])
|
|
self.assertTrue(all(ok for kind, ok in received if kind == 'elf'))
|
|
self.assertIn('another build', out)
|
|
self.assertIn('install=1 callbacks=1', out)
|
|
|
|
def test_launcher_when_no_loader(self):
|
|
received = []
|
|
|
|
def launcher_then_daemon():
|
|
with listener(self.launcher) as launcher:
|
|
conn, _ = launcher.accept()
|
|
with conn:
|
|
received.append(read_line(conn))
|
|
conn.sendall(b'HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n')
|
|
with listener(self.link) as daemon:
|
|
conn, _ = daemon.accept()
|
|
with conn:
|
|
received.append(read_line(conn))
|
|
conn.sendall(b'ok\n')
|
|
received.append(read_line(conn))
|
|
|
|
thread = threading.Thread(target=launcher_then_daemon)
|
|
thread.start()
|
|
time.sleep(0.2)
|
|
out, _ = self.run_link().communicate(timeout=30)
|
|
thread.join()
|
|
self.assertEqual(received, [
|
|
'GET /hbldr?daemon=1&pipe=0&path=/system_ex/app/PPSA99169/webui/ps5-retroarch-webui.elf HTTP/1.0\r\n',
|
|
'hello title PPSA99169\n', 'frontend retroarch\n'])
|
|
self.assertIn(f'no ELF loader on {self.loader}', out)
|
|
self.assertIn('wait=1', out)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|