Skip to content

MoonCast 1.0 so far → master (the whole chain #1 → #104 in one merge) - #105

Open
xservzeroman wants to merge 211 commits into
masterfrom
feat/home-shortcuts
Open

xservzeroman wants to merge 211 commits into
masterfrom
feat/home-shortcuts

Conversation

@xservzeroman

Copy link
Copy Markdown

Brings all of MoonCast so far into master in one merge, instead of merging the stacked chain one by one (#1 → #104). After this, the repo's front page, README and default branch are MoonCast instead of the old Moonlight.

What's included

The whole main line of the chain, in order: #1, #4–#16, #23, #24, #30, #31, #33, #34, #36, #45–#55, #61–#64, #66, #67, #69–#73, #75–#91, #93, #94, #96, #97, #100–#104. That is: the rebrand, both apps (TV receiver, handheld sender), PS5 (chiaki-ng, PSN), Xbox Remote Play, the Feel engine, on-screen controllers, foldables, release signing and CI, OpenSSL 3, FSR, the Quick menu items, Touch screen, opt-in usage check-in, On this device, Change picture, Add to home screen, and the UI review rounds.

211 commits, 223 files. master is an ancestor of feat/home-shortcuts, so this merges with no conflicts.

Not included (on purpose)

After merging

I'll close the stacked PRs as merged-through-this-one (with a comment pointing here) and clean up the merged branches (issue #92).

🤖 Generated with Claude Code

claude and others added 30 commits October 15, 2026 19:12
applicationId me.mooncast.tv, app name MoonCast TV, our own id (as the
upstream author asks forks to do). Adds doc/MOONCAST-TV.md: the plan to
use Moonlight's native MediaCodec + Sunshine protocol for low latency,
with the MoonCast PC helper as the strict-NAT fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The native build OOM-restarted the 4 GB container: 3 GB Gradle heap plus
ndk-build compiling every ABI in parallel. Lower the Gradle heap to
1.5 GB, single worker, no parallel; build arm64-v8a only (the stick and
JAL TV are arm64) with -j2 native jobs. BUILD SUCCESSFUL in ~1 min.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace Moonlight's grey Material look with MoonCast's: a night-sky void
background with the copper blood-moon eclipse glow (mc_bg), copper-eclipse
accent/focus, moonlight-silver text, void status/nav bars. Game tiles get
a copper focus ring (mc_tile_focus). Verified on the JAL TV.

Next: Continue-centric home (the live game as the hero tile, one press to
resume on this screen - the phone<->TV handoff), rounded box art, Unbounded/
Figtree fonts, restyled settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eaming

The native TV app now opens to a simple 'Ready to play - waiting for your
phone' splash (like the first WebView MoonCast TV), not Moonlight's host/
game grid. The phone drives everything: it POSTs the game to :8099 /play
{appId,appName} and the receiver fires ShortcutTrampoline to launch the
stream with NATIVE Moonlight (hardware MediaCodec + Sunshine protocol,
low latency) - no remote navigation on the TV. GET /mc-native identifies
it + names the paired PC. MoonCastReceiver is the leanback launcher;
PcView stays for setup/pairing.

Verified on the JAL TV: splash shows, phone /play ES-DE -> native stream,
Sunshine 1440p, MSTAR hardware decode 60 fps, Super Mario Odyssey live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ssion

The native receiver streams direct from Sunshine and owns the gamepad the game
reads. PhoneGamepad connects to the phone's pad server (ws://phone:8099/pad),
parses {pads:[{b,a}]} (W3C order) and feeds pad 0 into this stream's own
NvConnection via sendControllerInput — so no second Sunshine session is needed
for the controller (which gave the game a gamepad it ignored and a second audio
stream that broke the sound). Phone address comes from the /play request.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Graft chiaki-ng's com.metallic.chiaki.lib JNI layer + libchiaki-jni.so/liboboe.so
(arm64) into the fork (AGP 9 built-in Kotlin, @parcelize stripped). New
Ps5StreamActivity runs a chiaki Session to a SurfaceView; Ps5Pad relays the
phone's pad -> ControllerState; MoonCastReceiver.playPs5 launches it when the
phone POSTs /play {ps5host,registKey,morning}. Phone stays the controller.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ps5StreamActivity now derives the wake credential from the registration key
(ASCII up to the null pad, hex->ULong, as chiaki does), sends DiscoveryService
.wakeup(null, host, cred, ps5), then connects with retries while the console
powers up. No manual wake needed.
… keys

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…S5 sessions)

Ps5Pad.start() now stops any previous relay first; stop() interrupts the thread
(with backoff so it can't busy-loop). Ps5StreamActivity always disposes the
chiaki session on quit/destroy and bails the connect loop when closed - so a
failed/ended session no longer leaves a half-open session on the console that
makes the PS5 refuse the next connect from this device.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… relay is authorized (no 403)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ead freeze / deadlock)

Session.dispose() joins native threads; doing it in onDestroy on the UI thread
froze the main looper and stopped the receiver from launching the next stream.
Also stop disposing inside the QuitEvent callback (runs on chiaki's own thread -
would deadlock). Teardown now runs on a dedicated thread.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…x pads lack them

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ress L2 during PS combo

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…et self-heals

The pad relays (Ps5Pad for PS5, PhoneGamepad for Moonlight) opened ws://phone:8099/pad
with no read timeout, so a socket the phone could no longer reach (WiFi power-save / NAT
dropped it with no FIN/RST) left the reader blocked forever - the controller froze on its
last state. Set soTimeout=25s (> the phones 8s heartbeat) so a dead link times out and
run() reconnects; skip empty ping/pong frames; and reset the pad to neutral on ANY exit
(try/finally) so no button/stick sticks while reconnecting.
Bouncing to Moonlight PcView when no PC was paired made the TV look like plain Moonlight
AND tore down the receiver (killing the :8099 pad server so casts could not connect). The
phone drives everything and PS5 needs no PC pairing, so stay on "waiting for your phone";
MENU still opens add-PC for the rare case of pairing a game PC on the TV.
… no port-forward)

- native/mc_jni_patch.py: patches chiaki-ng android chiaki-jni.c (sessionCreate) - when ConnectInfo
  carries psnToken + duid it runs chiaki-ng's PSN steps (session_init, upnp_discover, session_create,
  create_offer, session_start(duid), punch_hole CTRL) and hands the hole-punch session + PSN account id
  to chiaki_session_init; the lib punches the data hole itself. Optional fields: LAN unchanged.
  libchiaki-jni.so (arm64-v8a + armeabi-v7a) rebuilt on xserv with it (jniLibs are not in git).
- ConnectInfo: optional psnToken / duid / psnAccountId.
- Ps5StreamActivity: LAN when a PlayStation answers discovery at the saved address (also in standby),
  otherwise Remote Play via PSN when the phone sent a token; PSN wakes the console.
- MoonCastReceiver: /play accepts psnToken/duid/accountId, reads the whole body, unescapes "\/".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
First live remote attempt failed at get_websocket_fqdn: "SSL peer certificate or SSH remote key was
not OK" - Android's libcurl has no CA store. native/mc_curl_ca_patch.py wraps curl_easy_init in
holepunch.c to set CURLOPT_CAINFO from MC_CA_BUNDLE; Ps5StreamActivity writes AndroidCAStore roots
to filesDir/cacert.pem and exports that path before a remote connect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PSN push websocket (pushcl) sends an incomplete chain; chiaki-ng repairs it via AIA, but both the
repair trust store (aia_openssl.c) and the CAINFO_BLOB merge (holepunch.c) read CURLINFO_CAINFO,
which is curl's built-in default (empty on Android), so the repair failed closed. Fall back to the
app's MC_CA_BUNDLE there (native/mc_curl_ca_patch2.py).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…how status

A console in rest mode only joins the PSN session once awake (~60 s); chiaki-ng waited 30 s for
MEMBER_CREATED, so the first remote attempt always timed out and only the retry connected.
native/mc_wake_timeout_patch.py waits up to 90 s for that one notification. The stream screen now
shows "Waking your PlayStation over the internet..." until the first frame (and a reason if it fails)
instead of a black screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- No UPnP probe in mc_psn_connect: it only ever timed out (7 s per attempt) and the hole-punch
  works without it (chiaki-ng's own holepunch-test never calls it).
- The stream screen follows the app's own logcat (Chiaki tag, no permission needed) and shows the
  real milestone: Reaching PlayStation Network -> Asking your PS5 to start Remote Play (wakes it) ->
  Your PS5 answered -> signing in -> opening the video channel -> Starting video; "Retrying (attempt
  N of 3)" when PSN drops an attempt. Stages only move forward within an attempt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Over PSN the console sends the ctrl session id only after the data channel exists, so chiaki's
SESSION_EXPECT_CTRL_START_MS (10 s) wait before the data punch always ran out (login arrived in
0.25 s, data offer exactly 10 s later). native/mc_ctrl_start_patch.py waits 2 s when a hole-punch
session is used; LAN keeps 10 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s that fail before the picture

- Revert c7d9aae (2 s ctrl-start wait): the console is not ready for the data-channel punch that
  early - it ignored 20 rounds of candidates, sent rudp finish and dropped the session. Back to
  chiaki's 10 s.
- Ps5Pad: every controller update goes through push() under a lock that stop() clears, so nothing
  calls sessionSetControllerState on a session being disposed (native crash after a PS5 quit).
- Remote attempts only succeed on the first picture; a quit before that (e.g. data hole-punch
  failed) is a failed attempt -> clean teardown + retry, instead of closing the stream screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…AT allocation

Some JAL<->home hole-punches got no candidate response at all (20 rounds, then "Failed to find
reachable candidate"), costing 30-50 s per retry. force_port_guessing makes a port-rewriting NAT
(increment 0, local != STUN port) advertise 75 guessed ports and open 250 sockets; with a
port-preserving NAT it does nothing. Also log the STUN allocation (increment / random).
native/mc_port_guessing_patch.py (applies on top of mc_jni_patch.py).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cast right after the PS5 entered rest mode: the start request arrived mid-transition and was ignored;
the attempt waited its full 90 s for a reply that never came, while a fresh attempt woke it in 40 s.
Waiting longer on one request never helps, a new request does: 60 s per attempt (a normal wake took
~46 s) and 4 attempts for remote.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fails

The Android binding left packet_loss_max and enable_idr_on_fec_failure zeroed: chiaki always told the
PS5 0% loss ("measured=12.1% reported_max=0.0%"), so the console never lowered its bitrate on a lossy
link, and corrupt frames lingered until the next keyframe (111 corrupt-frame reports in one remote
session). native/mc_quality_patch.py sets chiaki-ng desktop's values: 0.05 and true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ps5Pad listens on UDP :8100 for {"seq","k","pads"} from the phone: accepted only from the phone IP with
our pad key and a newer seq (reset accepted after a phone restart / long gap). While UDP flows (<1 s
old) the WebSocket pad frames are ignored; if it stops, the WebSocket path takes over again. Avoids
TCP resend stalls on jittery Wi-Fi.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ndheld (Odin)

- Ps5StreamActivity reads this device's own gamepad (buttons, sticks, analog triggers, d-pad / hat)
  and maps it to a DualSense state with the relay's combos (L2+Start = PS, Select+Start = touchpad);
  sent via Ps5Pad.pushLocal under the relay's lock (never into a disposing session). System Back
  (not the pad's B) leaves the stream.
- Immersive full screen (no status/nav bars) when focused.
- Exported, so the MoonCast app can open it on the same device.
- Ps5Pad.start attaches the session even without a phone relay, so local input works on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s busy

- onStop finishes the player: on the Odin, switching back to MoonCast left the PS5 session streaming in
  the background, and the PS5 (one Remote Play session at a time) then refused the TV with
  0x80108b10 "Remote is already in use".
- QUIT_REASON_SESSION_REQUEST_RP_IN_USE before the picture: show "Your PS5 is already streaming to
  another device. Close it there, then try again." for 6 s (no pointless retries) instead of closing silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One SRCH with a 1.5 s wait: on the home Mi stick (2.4 GHz, some loss) a single lost packet sent a home
session over PSN (~40-80 s, "Holepunch session was deleted" at teardown) instead of the LAN (~9 s).
Now up to 3 SRCH, 600 ms each, any reply = LAN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(pad): self-heal a dead controller socket + stay on the waiting screen (PS5 TV)
claude added 30 commits October 21, 2026 17:19
…ard) and Decode time in the Quick menu (test232)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…test233)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…illustration

compare.html draws a made-up 720p frame, enlarges it 3.4x, and runs FSR 1.0's RCAS on the right half.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ening (test234)

FsrRenderer: the decoder renders into a SurfaceTexture; copy, EASU and RCAS passes (ports of
ffx_fsr1.h, MIT) draw to the screen surface with the decoder's presentation time. Only when the
stream is clearly smaller than the screen; a fold moves only FSR's output. The mockup comparison
is now rendered by these shaders in WebGL 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t235)

PC: MediaCodecDecoderRenderer.setSurfaceOverride, no setFixedSize while FSR is planned, off with HDR.
Xbox: FsrGlDrawer as SurfaceViewRenderer's GlDrawer, hardware scaler off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…atency keys stepwise (test236)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed (test237)

BatteryCap reads hh_battery_res and the plugged state; Game caps prefConfig keeping the shape and
compares fold sizes with the uncapped size; Ps5StreamActivity takes the lower preset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…PC (test238)

Streaming status flips the stats corner on PC, PS5 and Xbox (new StatusCorner for
PS5 and Xbox) and saves it as Stats while playing. PC only: Task Manager sends
Ctrl+Shift+Esc; Hibernate PC asks first (Cancel picked), then sends Win+X, U, H, and
the stream ending within a minute after that is not reported as an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…endent (test239)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y (test240)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…video (test242)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Off at every start (test241)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n default in Settings, menu scrolls to the selection (test243)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…start, battery-cap Auto bitrate (test244)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (test245)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… only), Obtainium links and Privacy in README (test246)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… PS5 and Xbox (test247)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rid (test249)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…apps starts on Done and says what each tap did (test251)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ings (test252)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…read picker, breath pause, a11y labels (test253)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uick menu, All games scroll, TV re-fit and shared covers (test254)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hint bar (test255)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…et Add apps tile, top pill ellipsis, group corners (test256)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants