Skip to content

Online play through Nextendo for NEX and NPLN games: gRPC sockets, MIT sign-in, signed game patches, service profile - #2589

Open
eyasbzour wants to merge 71 commits into
seedds:mainfrom
eyasbzour:socket-grpc-resolver-msg
Open

eyasbzour wants to merge 71 commits into
seedds:mainfrom
eyasbzour:socket-grpc-resolver-msg

Conversation

@eyasbzour

@eyasbzour eyasbzour commented Nov 13, 2026 •

Copy link
Copy Markdown

This builds on #961 (RobiiAragon's opt-in online backend), which is still open, so until #961 merges the diff shows its roughly 10k lines too. The commits that are new here:

  • b8e25957 Serve gRPC's name lookup and RecvMsg/SendMsg in a network mode
  • aa03bca5 Set the game's own errno on a failed socket call; RecvMsg/SendMsg edge cases
  • 8aace781 Merge seedds/main (465 commits; notes below)
  • 51c46ece Regenerated docs/agents/symptoms.md
  • f1afbb84 EventFd in a network mode is a slot of the transport's socket table
  • 8a36c156, e3fa880a Merge seedds/main again (no conflicts)
  • e6d91b7f The BCAT title IDs come from the build instead of the host source (test_no_title_ids_in_the_host), and three tests define the clock and wait symbols that main's hle_os.c and hle_thread.c now provide
  • ae3a5f02 iOS online: the game is told the address of the adapter the internet route uses, not the first one listed
  • fb1d0b3e, b8bec5a9, cb5bdacd Tests that broke after merging main: t_oe_handoff stubs what hle_oe.c now calls, t_nextendo_account lets the SDK build the AsyncContext, and t_save_sync compares the device with hw.model (a CI runner is a VirtualMac2,1)
  • 9b0ae7b9 Merge seedds/main
  • 67e62a1b Nextendo online: sign-in, signed game patches, service profile and launcher
  • 7f9e8736 Nextendo mode: keep a closed UDP port briefly; ICMP reports on UDP receives are no datagram yet
  • 7ab7b40d services.md: the sign-in is MIT with a build-time client ID
  • fc73a5a4, 72fe137b Review fixes: the UDP rebind and send, one refresh at a time (flock), no second refresh at sign-in, SO_NOSIGPIPE on the loopback listener, a failed refresh keeps the current token until it expires, GetNickname as a wait hook, one /api/me fetch per account, presence requests in order
  • cd11b50f t_pollwait: a 60 s hang guard and unbuffered PASS lines. A slow runner stretched its short sleeps past the 20 s alarm twice in a row on this branch, in both the plain and the ASan job
  • c1f67b53 Service profile: Mario Kart 8 Deluxe's NEX server presents a self-signed certificate, and its routes now accept exactly that certificate
  • 17ad015a Merge seedds/main (t_save_sync takes main's hw.model check)
  • 931bc841 t_mii_sysdata, new on main, includes hle_fs.c, which weak-imports Propose opt-in online service backends and BCAT delivery #961's BCAT provider. It now includes bcat_absent.h, as t_fs_stub.c does, so it links
  • de960fd2 Merge seedds/main (no conflicts)
  • 3798ea9e, 3c7b6af5 A row for DM_EXFAT_DIR, which main's 16af055b read without one, then a merge of seedds/main that keeps main's own row from 210acc66
  • c5188fca The host asks Nextendo why it refuses online play, and the account API uses the OAuth access token (/api/me answered 401 to the nx2 game token)
  • 22bfea82 Merge seedds/main (no conflicts)
  • 407d44c7 The account's play time, Mii and activity on Nextendo
  • 7a51a148 docs/agents/symptoms.md regenerated
  • eb3bdbf9 Merge seedds/main. Main's The network clock reads the host's clock, as the user clock, so saves dated by it are no longer dated 1969 #2359 and Propose opt-in online service backends and BCAT delivery #961 both took over the network clock's GetCurrentTime and IsAccuracySufficient (below)
  • 6f7ff687 The game token's expiry and the id token's iat, nbf and exp are judged and stamped by Nextendo's clock: the offset comes from the Date header of Nextendo's answers, and a device clock a minute or more off is logged
  • 3df926e0 A host thread's native wait before the guest threads exist does nothing. The start-of-session work in 407d44c7 began on a background thread while the modules loaded, before svc_thread_main_init made g_keythr, and pthread_getspecific(0) on Darwin is the thread's own pthread structure, never NULL. The wait took that thread for a guest thread, and Wonder stopped at a different place in each run. Our bug, found on a device build of this branch's code
  • 1dd8f85a Merge seedds/main (no conflicts)

Why

Games whose online code is NPLN (Super Mario Bros. Wonder and other recent titles) talk to their servers over gRPC, which links its own OpenSSL and does TLS itself over the C socket interface. With #961 alone, Wonder's online menu fails with 2321-4992 (gRPC's UNAVAILABLE) at four separate points. Each fix below sits at the SDK level, with no game offsets in the code, so it applies to any game that makes the same calls.

  1. nnsocketGetAddrInfo returned EAI_FAIL whenever an SDK exports both C++ GetAddrInfo overloads, and Wonder's does. The C call takes the plain addrinfo layout. In Wonder 1.2.1's SDK it branches straight to that overload (sdk+0x1481c0), so the C resolver now uses that layout whenever the SDK has it.

  2. gRPC's TCP endpoint reads and writes only through RecvMsg and SendMsg, which were unsupported. They now scatter and gather through one host buffer with Horizon's 0x30-byte msghdr. A message over 16 MiB moves in part on a stream socket, as a real socket may, and fails with EMSGSIZE on a datagram one. A datagram longer than the iovecs is cut and reported with MSG_TRUNC. Ancillary data is refused rather than dropped.

  3. On Horizon, nn::socket's errno is the C library's: detail::SetLastErrno writes *__errno_location(). The host kept only its own copy. gRPC sets EAFNOSUPPORT itself when it falls back from IPv6, then reads errno after a non-blocking connect, so it took that stale value for EINPROGRESS and dropped the address. Every socket hook that fails now also writes the game's errno, and GetLastErrno reads it.

  4. gRPC wakes its pollers with an event descriptor and polls it together with its sockets. In a network mode EventFd failed, and main's offline table numbered its descriptors from 0, the same as the transport's sockets. An event descriptor is now a slot in the transport's socket table, backed by a local socket pair, so Poll and Select see sockets and event descriptors together. Offline nothing changes.

  5. On iOS, net_route has no routing table to read, so the adapter nifm reports was the first IPv4 one getifaddrs lists. With mobile data on, that is usually the cellular one, even while the phone plays over Wi-Fi. Pia gives the address nifm reports to the other players as its local candidate for a direct link. In a Nextendo session the adapter is now the one whose address a UDP socket connected past the local networks gets (connect on UDP sends nothing). If no adapter has that address, any adapter is used, as before. The chosen adapter's name is logged when it changes.

Name lookups and resolver options are logged once, in red when they fail, so the next game that stops before its first socket shows why.

What a player needs to go online

#961 takes the account from a credential provider the app installs and routes only the hosts a service profile names. Neither existed in the repository, so nothing could sign in. With these commits a build made with --nextendo works once the player signs in. NEX games (Mario Kart 8 Deluxe) and NPLN games (Wonder) go through the same code.

  • Sign-in (dm/host/nextendo_login_apple.inc, MIT): OAuth with PKCE and a 127.0.0.1 redirect, the refresh token in a private 0600 file, and a game token from /api/nex-token when a game asks. Refreshes take a file lock, so the Mac tool, the launcher and the game never spend the same refresh token twice. When Nextendo refuses an account, /api/online-status supplies the reason, which becomes the error text. /api/oauth/userinfo must name the same account as the game token. On the Mac, make -C dm/host nextendo_login and then dm/host/nextendo_login sign-in. On the iPhone, the launcher's Online section opens Nextendo's page in an ASWebAuthenticationSession, and a listener in the app takes the loopback redirect. No token is logged.
  • The OAuth client ID is a build setting, DM_NEXTENDO_CLIENT_ID. The Mac reads it from the environment, and build_ipa.sh writes it into the app's Info.plist as DMNextendoClientID. A build without it cannot sign in, and the error says so. This repository ships no client ID. Whoever publishes builds registers a public application at nextendo.network/developers, and Nextendo's sign-in page shows that application's name.
  • Game patches: dm/tools/nextendo_patches.py (after NX Port Studio, MIT) fetches Nextendo's signed patch catalog and checks its Ed25519 signature with nextendo_verify.swift. If the catalog lists the exact main build (all 32 bytes of the build ID) and every guarded byte matches, it patches a copy of main. The host maps that copy only in Nextendo mode (loader.c; on the iPhone it is module nmain), so offline and LAN play keep the original. Wonder 1.2.1 has such a patch. Mario Kart 8 Deluxe 4.0.1 is listed without one.
  • Service profile: dm/tools/nextendo_services.py writes the public profile from dm/net/nextendo-services.json: routes, the provider CA checked by SHA-256, and the manifest. When the player has no firmware certificate store, that CA answers the SSL service's built-in certificate requests. A game whose own TLS takes its roots from the store (Wonder's gRPC) then needs nothing beyond the sign-in.
  • Pinned certificates: Nextendo's NEX server for Mario Kart 8 Deluxe (g2b309e01-lp1.s.n.srv.nintendo.net and g2df33d01-lp1.p.srv.nintendo.net) presents a self-signed certificate, CN=nintendo-private-server, with no SAN. No CA in the profile issues it, so the SSL service's verify_peer failed and the game showed 2306-0502. Those rows are now pinned-exact routes carrying the certificate's SHA-256, and the DER file sits in dm/net/nextendo-pins/. dm_nextendo_x509_verify_pin from Propose opt-in online service backends and BCAT delivery #961 accepts the connection only when the peer sends that exact DER, inside its validity dates, with a valid self-signature and a strong enough key. Every other host still goes through normal verification. Mush8 accepts any certificate from that server; this accepts one.
  • Why Nextendo refuses a game's login: Nextendo's game servers check the account (confirmed e-mail, linked Discord, not online elsewhere, not disabled) before they accept a NEX login, and a refusal reaches the game as a generic authentication error with no reason (Mario Kart 8 Deluxe: 2306-0807). The host now asks /api/online-status with the sign-in's access token, once after the first game credential and again when the game shows a 2306-08xx error, and logs the answer in words. The access token stays in memory and is never logged.
  • Account and social: nickname, country and birthday come from /api/me. That endpoint takes only a session or OAuth access token and answered 401 to the nx2 game token, so the account API now sends the access token from the sign-in's last refresh, and the game token only when there is none. The launcher fills in a country or birthday when the account has none, and the player can type a name of up to 10 characters. The game also gets the online membership and the BaaS id token with the NEX proof. UserPresence::SetDescription runs the SDK's own setter. Presence is posted again every 45 s while a game has published one, because Nextendo stops showing a player whose presence stops arriving. A refused friends or presence request logs its HTTP status.
  • Play time, Mii and activity: Nextendo keeps each game's total seconds per account and keeps the larger of what it has and what it is sent. At the start of a Nextendo session the host reads the account's total for the game once, then sends it plus this run's time every minute. A gap longer than a minute, such as the app being suspended, counts as one minute at most. The account's Mii (a StoreData in /api/profile) joins the console's Miis in the game's Mii database when its data CRC holds and no console Mii has its create ID. Nothing is uploaded. The presence's app_detail is now the game's own SetDescription text, read from the application field (key and value strings in turn, under sys_description, as the SDK's PresenceAccessor lays them out).
  • nifm::GetCurrentProxySetting succeeds with every field zero in a network mode, the same as a console with no proxy. Wonder's NPLN code returns an error when it fails.
  • Launcher: a Network row (Offline, LAN, Local wireless, and Nextendo in a --nextendo build, where it is the default) and an Online section with the sign-in and a birthday picker. The Network row is now shown for every game.
  • Diagnostics: the SSL setup calls log a failure once per call and result, and built-in certificate requests log their IDs (never a certificate). For Wonder 1.2.1, patches.py puts an observation gate on gRPC's grpc_error_create, because Nintendo's gRPC has its logging compiled out.

The sign-in was first written in the fork after Mush8 (GPL-3.0). It has since been rewritten under MIT from Nextendo's public OAuth interface. Mush8, Ryujinx-Nextendo and NX Port Studio were used for facts: which endpoints Nextendo has, the token shapes, and which calls a game makes. No code was taken from Mush8.

UDP in Nextendo mode (untested)

7f9e8736 and fc73a5a4 are separate because no log has shown they are needed yet. When a game closes a bound, unconnected UDP socket, the host socket stays open for 5 s. Without this, datagrams that arrive in between are answered with ICMP port unreachable, where a console would drop them. A bind to that port in that time closes the parked socket and binds the game's new one, so the options the game set on it stay. The host also hands an earlier datagram's ICMP report to the next call on a UDP socket, which Horizon does not do. On a receive it counts as no datagram yet. On a send, which then sent nothing, the datagram is sent again once. All of this applies only in Nextendo mode, for peer-to-peer sessions such as Pia's.

Merging main

Main's offline socket layer (the event descriptor table, Select as Poll over its sets, Star Fox's SDK 21 overloads) now sits under #961's transport, and the SDK 21 overloads go to the transport in a network mode. Main's ssl stubs stay out because #961's hle_ssl.c serves those calls. The SDK 4 GetPeerName is registered once. hle_account.c keeps main's SDK-built AsyncContext: there is no constructor takeover, a context with Nextendo off answers as main's does, and a move constructor copies the vtable. hle_bcat.c drops HLE_FORCE_BIG, since main now runs every takeover on the host stack.

The network clock is split between the two. Main's #2359 reads the host clock in GetCurrentTime and now(), so a game that dates its saves by the network clock gets the real date (Divinity: Original Sin 2 dated them 1969). #961 had GetCurrentTime fail unless the kernel's NTP state was synchronized within a second. A Switch reads its network clock unsynchronized too, so main's version stays, and t_network_clock no longer checks the getter. IsAccuracySufficient and the availability task keep #961's kernel NTP check rather than main's fixed "no", since the host can tell whether its clock is synchronized. If you'd rather keep main's "no" there, it's a one-line change. This also changes offline play, not only a network mode: on main, AsyncEnsureNetworkClockAvailabilityTask always finishes with a failure, and with #961 it succeeds whenever the device's clock is synchronized. A game that checks it offline takes the "clock available" path (#961's author found Splatoon 2's calendar warning gone with it).

Tested

  • Super Mario Bros. Wonder 1.2.1 on an iPhone over Nextendo, in a fork build: sign-in, the online menu, creating a room and searching for rooms all work, and Nextendo's site counts the player. That build had the socket fixes, the sign-in, the patch and the profile. It did not have the MIT rewrite of the sign-in, the build-time client ID, the userinfo check or the UDP commit. Two players joining one room (Pia's peer-to-peer link) has not been tried yet.
  • Mario Kart 8 Deluxe 4.0.1 on an iPhone, before the pin: sign-in worked and the game reached Nextendo's NEX server, then stopped at 2306-0502 on the certificate. With the pin, TLS to the NEX server succeeds and the game sends its WebSocket upgrade, then shows 2306-0807 (a NEX authentication refusal) about 4 s later. The same account plays Wonder online. A build that logs Nextendo's reason has not run on a device yet.
  • t_net covers the gathered and scattered round trip, EMSGSIZE, MSG_TRUNC, the resolver layout, the game's errno on a failed call and on an empty event read, mixed Poll and Select over sockets and event descriptors, and the proxy answer. make -C dm/host/tests nextendo-account also compiles the Mac sign-in tool. test_nextendo_patches.py and test_nextendo_services.py cover the catalog checks and the profile. Tests workflow on the fork for this head, every job green: https://github.com/eyasbzour/nxnative/actions/runs/43613758974

Not in this PR: VPN play and the local-wireless room broadcasts, the fork's networking diagnostics ring, and the fork's build workflow (which sets the client ID).

Replace unconditional failure and shared task completion with a precision-checked kernel NTP query and independent SDK event lifetimes. Preserve real failure results and release the guest core across contended service locks.
Translate the Horizon IPv4 socket ABIs, retain generation-safe descriptor lifetimes and bound local discovery. Publish real guest contexts across blocking hooks using the existing per-thread gate stack. Add LDN discovery, membership and lifecycle plus local play-history registration from privately imported resources. Preserve upstream offline account and TLS boundaries.

Synthetic and real loopback fixtures, ASan/UBSan, the full host suite, the production HLE registry, iPhoneOS compilation and synthesized resource-decoder tests pass. Mario Kart 3.0.5 hardware results are owner reports from the originating fork, documented separately from this rebased branch. No frontend, Nextendo integration or game/firmware data is included.
Keep the test thread heap-owned like production, release its handle after joining, and include it in the local-network test target. Stop socket fixtures at failed prerequisites rather than continuing with invalid descriptors.
Add the system permission description required by the native socket transport. Remove redirect-only resolver bookkeeping from the upstream local-only backend; no routing provider or UI is included.
…y copyout

Kernel recvfrom copyout cannot invoke the guest dirty-page signal hook. Prepare the pending span before each receive attempt, including after readiness waits and partial stream reads, without retrying EFAULT or admitting unrelated invalid memory.

The real dirty-tracker fixture passes 43 checks normally and under ASan/UBSan. Negative controls reproduce missing preparation and the wait boundary. The full host suites, iOS compile check and documentation checks also pass.
…e copyout

The kernel getsockopt copyout cannot invoke the dirty-page fault hook. Prepare the validated result span without changing SetSockOpt inputs, invalid-memory failures, result sizes or errno translation.

The expanded real dirty-tracker fixture reproduces 16 failures in 87 checks before this change and passes all 87 normally and under ASan/UBSan afterward. This synthetic reproduction does not establish a game disconnect cause.
Use owned DNSService queries and cancellation identities instead of leaving resolver work and late completions alive. Retire native references on one serial queue, fence finalization, and retain the offline and synchronous resolver paths.
Use owned DNSService queries and cancellation identities instead of leaving resolver work and late completions alive. Retire native references on one serial queue, fence finalization, and retain the offline and synchronous resolver paths.
Keep per-session read-only snapshots and scoped eligibility checks behind a default-off provider hook. Exercise startup, delivery lifetime and sanitizer failures with original offline fixtures; compose the TLS dependency without replacing upstream filesystem behavior.
Replace unconditional failure and shared task completion with a precision-checked kernel NTP query and independent SDK event lifetimes. Preserve real failure results and release the guest core across contended service locks.
Check Foundation fixture construction and bounded DER reads explicitly, initialize the filesystem fixture handle, and remove a fixed filesystem null-argument baseline entry. No new suppression or baseline expansion.
…tely

Ordinary-buffer TLS, policy and resolver tests use the explicit no-tracker fixture hook. The real socket-copyout fixture keeps its registered dirty tracker and links the NSD backend needed by this stack.
I preserved upstream's readback allocation expectation and the existing explicit serial-table release. The LAN/LDN transport and tracked socket copyout corrections remain unchanged. Local network fixtures, 87 normal and sanitized copyout checks, 61 Metal geometry checks, and the documentation tests pass after the merge.
…stream-online-backend-proposal

# Conflicts:
#	dm/host/Makefile
#	dm/host/hle_net_backend.c
#	dm/host/tests/Makefile
#	docs/agents/symptoms.md
#	docs/reference/services.md
…stream-online-backend-proposal

# Conflicts:
#	dm/host/tests/Makefile
…stream-network-clock-main

# Conflicts:
#	docs/agents/symptoms.md
hle_oe.c's dm_set_opmode calls dm_hid_opmode_changed and oe::ExecuteProgram
calls dm_host_shutdown_then; the test stubs them as t_notify_sched.c does.
…e_account.c expects

hle_account.c keeps main's SDK-built AsyncContext, so there is no constructor
hook to call: the test's constructor writes nothing, as the SDK's writes only
guest memory the host never reads. Added checks: a move constructor writes the
vtable and a null IPC pointer and carries the request; with Nextendo off a
context answers as the offline host does.
…c's too

The check wanted cdevice=Mac..., but a virtual Mac (a CI runner) reports
VirtualMac2,1. It now compares with hw.model, as save_sync.c device_name
reads it.
…uncher

The online backend from seedds#961 needed an account and a route profile that the
repository did not provide. This adds them:

- Sign-in (nextendo_login_apple.inc, MIT): OAuth with PKCE and a loopback
  redirect, the refresh token in a private 0600 file, the game token from
  /api/nex-token, /api/online-status reasons on refusal, and an
  /api/oauth/userinfo check that the game token belongs to the signed-in
  account. The Mac uses dm/tools/nextendo_login; the iPhone signs in on the
  launcher's settings screen (NextendoSignIn.m). The OAuth client ID is a
  build setting (DM_NEXTENDO_CLIENT_ID, written into Info.plist as
  DMNextendoClientID); without one nobody can sign in, and the error says so.
- Nextendo's signed patch catalog (dm/tools/nextendo_patches.py, checked
  with nextendo_verify.swift): when it lists the exact main build, a patched
  copy is rewritten into patched/nextendo and mapped only in Nextendo mode
  (loader.c, gen_embed.py module nmain). The public service profile comes
  from dm/net/nextendo-services.json (nextendo_services.py).
- Account: nickname, country and birthday from /api/me with launcher
  fallbacks, online membership, the BaaS id token with the NEX proof.
- Friends and presence: SetDescription runs the SDK's own setter, presence
  is posted again every 45 s, refused API requests log their status.
- SSL: the provider CA answers built-in certificate requests when the
  player has no firmware store; setup failures and built-in requests are
  logged once.
- nifm::GetCurrentProxySetting answers "no proxy" in a network mode.
- Launcher: Network row (Nextendo when built with --nextendo), Online
  section, player name, birthday picker.
- Super Mario Bros. Wonder 1.2.1: grpc_error_create observation gate.
…ceives are no datagram yet

A bound, unconnected UDP socket the game closes stays open for 5 s, and a
bind to its port in that time takes it back, so datagrams that arrive in
between are not answered with port unreachable. A refused or reset error
on a UDP receive, which the host raises for an earlier send's ICMP and
Horizon does not, counts as no datagram yet. Both only in Nextendo mode.
Neither has been seen in a log yet; they follow how peer-to-peer play is
expected to behave on a console.
…nline is the found-on

The account section still said the flow and client ID were Mush8's and the files GPL-3.0, and three found-on lines said Wonder online was only planned.
eyasbzour added a commit to eyasbzour/nxnative that referenced this pull request Nov 13, 2026
…eded

The survey docs still said no PRs go to seedds and that Mush8 code may be ported. The descriptor table, EventFd and NPLN gaps are filled by seedds#2589, the client ID is a build setting, and the Mario Kart menu sites move to a kart branch check.
eyasbzour added a commit to eyasbzour/nxnative that referenced this pull request Nov 13, 2026
…eded

The survey docs still said no PRs go to seedds and that Mush8 code may be ported. The descriptor table, EventFd and NPLN gaps are filled by seedds#2589, the client ID is a build setting, and the Mario Kart menu sites move to a kart branch check.

(cherry picked from commit 500a94b)
…t; a send that met an ICMP report is sent again

Review fixes for the previous commit. Giving the bind the parked host
socket dropped every option the game had set on its new socket and
ignored the bind address, so a bind now closes the parked socket and binds
the new one. On the host, an earlier datagram's ICMP report also comes
back from the next send on a UDP socket, which then sends nothing; in
Nextendo mode that send is made again once.
- One refresh at a time, across threads and processes (flock on a file in
  the sign-in's folder), so a refused old refresh token can no longer
  delete the newer one another caller just saved.
- Sign-in uses the code's own access token for the game token instead of
  refreshing again right away.
- The loopback listener's answers set SO_NOSIGPIPE: a browser preconnect
  that closes early no longer raises SIGPIPE.
- A failed refresh keeps the current game token until its own expiry
  instead of dropping it.
- GetNickname is a wait hook, and callers waiting behind a fetch release
  their guest core.
- /api/me is fetched once per account and shared by the BaaS ID, country
  and birthday readers; a failure is remembered for 60 s.
- Presence requests go out one at a time, so a repeat never lands after a
  newer publish (status 0 stays last).
- services.md: the iPhone carries Nextendo's patched main; hle_net.c's
  proxy comment matches the game notes.
A slow CI runner stretched the test's 2 s of short sleeps past the 20 s alarm: this branch's tests run 43530203751 stopped there twice in both the plain and the ASan job, with nothing in the log to say which case. The guard is a hang check, not a timing check (the cases count back-offs on poll_clock), so it is now 60 s, and stdout is unbuffered so a timeout names the case.
@eyasbzour eyasbzour changed the title Online sockets for gRPC games: RecvMsg/SendMsg, C resolver, game errno, EventFd in a network mode Online play through Nextendo for NEX and NPLN games: gRPC sockets, MIT sign-in, signed game patches, service profile Nov 14, 2026
@github-actions github-actions Bot added rewriter The NSO rewriter and per-build patches game: 010015100B514000 Super Mario Bros. Wonder labels Nov 14, 2026
Mario Kart 8 Deluxe 4.0.1 signed in on an iPhone, then failed its online
menu with 2306-0502: the TLS check to g2b309e01-lp1.s.n.srv.nintendo.net
refused the certificate. Nextendo's second server presents a self-signed
certificate there (CN=nintendo-private-server, no host name, no chain to
the provider CA), the same one for g2df33d01-lp1.p.srv.nintendo.net.

Both routes are now pinned-exact with that certificate
(dm/net/nextendo-pins/<SHA-256>.der, named by certificateSHA256 in
nextendo-services.json), and the server's address gets the paired
pinned-address route. hle_nextendo_x509.c already accepts a pinned leaf
only byte for byte, with its dates, purpose, key and self-signature
checked. nextendo_services.py writes the pinned rows and copies the
certificate into the profile; build_ipa.sh and main.m carry every
certificate file of the profile.
…nt API with the access token

A NEX login refusal (Mario Kart 8 Deluxe 2306-0807) carries no reason. The
host now asks /api/online-status with the sign-in's OAuth access token once
after the first game credential and again when the game shows 2306-08xx,
and logs the answer. /api/me refused the nx2 game token with 401, so the
account API takes the access token first and falls back to nx2.
Every minute of a Nextendo session the host sends the account's total for
the game plus this run's time to /api/history. The account's Mii from
/api/profile joins the game's Mii database when its data CRC holds. The
presence's app_detail is the game's own SetDescription text, read from the
application field.
# Conflicts:
#	dm/host/hle_time.c
#	docs/agents/symptoms.md
#	docs/reference/services.md
The game token's expiry and the id token's iat, nbf and exp were judged
and stamped by the device's clock. The sign-in now keeps Nextendo's clock
less the device's from the Date header of each answer, uses it for both,
and logs an offset of a minute or more.
…t does nothing

Nextendo's start-of-session work (the account's Mii, play time) begins on a
background thread while the modules load, before svc_thread_main_init made
g_keythr. pthread_getspecific(0) on Darwin is the thread's own pthread
structure, never NULL, so the wait took that thread for a guest thread and
changed the host's thread state under its lock: Wonder controls-9 stopped
at a different place in each run (a black screen).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci Continuous integration and repository settings (.github) docs Documentation game: 0100152000022000 Mario Kart 8 Deluxe game: 010015100B514000 Super Mario Bros. Wonder host The Horizon host: SVCs, services, threads, memory (dm/host) ios The iPhone and iPad app (dm/ios) packing Building an IPA or a Mac app: the NSP script, extraction, the shader split, the packers rewriter The NSO rewriter and per-build patches tools Tools and scripts (dm/tools, scripts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants