LxBox v2.25.2 发布

已发布 v2.25.2,以下为官方变更日志。

官方变更日志

L×Box v2.25.2

A patch on top of v2.25.1.
The main body of changes is in
v2.25.0 — one parsing
engine shared with
singbox-launcher 2.0.0,
and the Start insurance when the core refuses a server. Read those first;
this patch does not repeat them.

This patch is about subscriptions that used to come back short — and about not
being left in the dark when they do. Links providers actually write are read
instead of silently vanishing; a line the app cannot use says why. A decoy
banner from an expired subscription is no longer offered as a server, and a
transport the core does not speak is refused honestly instead of connecting to
nothing.

Патч поверх v2.25.1.
Основной корпус изменений — в
v2.25.0: один движок
разбора с
лаунчером 2.0.0
и страховка кнопки Start, когда ядро отказывается от сервера. Сначала
читайте их — этот патч их не повторяет.

Этот патч про подписки, которые приезжали короче, чем есть, — и про то, чтобы
не оставлять в тишине, когда так вышло. Ссылки в том написании, в котором их
пишут провайдеры, читаются, а не исчезают молча; строка, которую приложение не
может использовать, называет причину. Баннер-обманка истёкшей подписки больше
не выдаётся за сервер, а транспорт, которого ядро не знает, честно
отбраковывается вместо соединения в никуда.

🇬🇧 English

🩹 Fixes

Subscriptions and links

  • amneziawg:// links no longer disappear. Panels spell the AmneziaWG

scheme out in full, and lines written that way used to vanish whole — every
field in them was already readable, but the list of known schemes lived in the
code as literals and did not include the long spelling.

  • A vpn:// link holding a plain WireGuard config gives a server. Under the

wrapper there may be not only an Amnezia profile but the wg-quick /
AmneziaWG config itself. Such a link used to yield zero servers with a message
about zlib that sent you looking for a fault that was not there.

  • A subscription that is a single Xray configuration — one object rather

than a list — is read as one server instead of yielding nothing.

  • Hysteria2 keeps its bandwidth and its Salamander obfuscation. A speed

written as a string with a unit ("100mbps") was dropped without a word, and
the obfuscation was lost together with its password, so the server arrived and
would not come up. The up / down spelling the official client uses now
arrives too.

  • A provider panel's decoy banner is no longer taken for a server. When a

subscription has expired, panels return not an empty body but a
syntactically valid link to nowhere (0.0.0.0:1, 127.0.0.1:1080) with the
explanation in the remark after # — and when the traffic quota is used up,
that entry can be the only one. What is judged now is the destination: an
entry whose address cannot belong to a server stays out of the list, and the
provider's own text reaches you as the reason.

  • A transport the core does not speak is refused instead of being swapped

out. network: kcp / quic used to reach the core verbatim, the sanitiser
stripped the transport silently, and the server came out as working plain TCP
— a server expecting mKCP will not accept that connection, and you saw no
reason why.

  • TCP header obfuscation (headerType=http) refuses the server. It used to

be carried over into the http transport, which for the core means HTTP/2 —
a different protocol on the wire: a server expecting camouflage received an
h2 handshake and dropped the connection. The server looked healthy and did not
work. Real http transport (spelled out as type=http) is unaffected.

  • A socks password is no longer lost. v2rayN always writes a socks link as

base64("user:pass"), and parsing split the string on a : that is not there:
the name became the whole base64 string and the password vanished silently.

  • wireguard, socks and http elements inside an Xray configuration are no

longer dropped. No section recognised them and the server disappeared
entirely, even though the core has every field they need.

  • ALPN from an Xray configuration reaches the server. It is part of the

handshake: a server with nothing to pick from what was offered drops the
connection, so a node facing an h2-only server simply did not work.

  • A number in alpn or server_ports no longer takes down the whole

configuration. A node from sing-box JSON with "alpn": [443, "h2"] reached
the core as written and the core refused to start at all. A non-string element
is now removed with a warning on that server, and its valid neighbours stay.

  • "No servers found" no longer keeps the reason to itself. A subscription

line whose protocol the app does not know, a link over the allowed length, and
a body that could not be read at all now each name their reason in the
notifications list, with the protocol spelled out instead of an empty list.
An unknown scheme and an unreadable body no longer both report themselves as
"protocol" — each has its own reason now.

  • Service lines that provider panels add are skipped quietly. Routing

commands addressed to neighbouring clients (incy://routing/…,
happ://routing/…) are not servers: a healthy subscription used to show five
refusals alongside working servers.

Workspaces

  • Switching a workspace no longer overwrites the subscriptions in all of

them. If a subscription refresh was in flight at the moment of the switch —
by hand from ⟳, on the hourly timer, on return from the background, or after
the VPN was turned off — the outgoing workspace's screen would write its own
subscriptions into the workspace that had just loaded. Every workspace was
left with a single subscription, the last one refreshed, and the loss was
committed to disk. A refresh is now halted before the switch, and a write from
the outgoing workspace is rejected. Workspaces already overwritten are not
restored by this fix — only a backup can do that.

Start insurance

  • Servers with the same name no longer cut the run short. Two unusable

servers sharing a name were being switched off one per press: after the first
went off its name passed to the second, the state machine read that as "the
same server again", and the VPN never came up. A repeat is now recognised by
the server itself, not by its name.

  • Stop during a server check no longer brings the VPN back. A stop that did

not come from the main screen's button — Debug API, the Quick Settings tile,
the Intent API, Tasker/Locale — left the check running, and a few seconds
later the insurance raised the tunnel by itself. Any Stop now cancels it.

  • The "disabled by insurance" list opens the server you tapped. When several

servers shared a refusal reason, or two servers in a folder shared a name,
tapping a row could open a different server's screen.

Servers screen

  • Deleting a row no longer shifts its neighbours. From a list of

u1, c1, u2, c2 you would delete the chain c1 and get u1, c2, u2: a
record of the same kind slid into the freed slot and jumped over a server.
Slots are now matched by key, not by position.

  • Drag works when the storage holds a record the app cannot read. Any drag

used to roll back silently — the row jumped home. The visible records now
reorder, and the unreadable one keeps its slot.

  • A new row at the end of a long list is no longer hidden under the

SnackBar. A new record is appended at the tail, and the tail only scrolled
as far as the bottom padding, so the "New" row was covered by the
config-rebuild message. The list now keeps room below.

  • The highlight on a new row is dropped when the row is deleted. Deleting

within the seven seconds after adding — from the menu, or on a subscription
refresh — left the highlight bound to a dead record, and the screen kept
accumulating keys of deleted rows until it was closed.

  • LX Backup: importing keeps the source order from the file. A hop chain

sitting between servers in the file moved to the head of the list after
import, because chains and sources were written separately. New records now
take the file's order.

Config editor and DNS

  • Config editor: a selection no longer collapses when the menu appears. A

long tap on the text opened the menu through a modal route, which took focus
away from the editor: the selection collapsed to a caret, and Copy put the
line under the caret into the clipboard instead of the fragment you had
selected. The menu now lives in an overlay bound to the editor — the selection
survives while the menu is on screen, and Cut / Copy / Paste / Select all work
on the real range. Both screens with an editor are covered: the shared config
and the add-server wizard.

  • The dns_shield DNS preset no longer resolves in the clear. The group was

set to mode: fastest — the query goes to every member at once, and plain UDP
with no TLS handshake almost always wins the race against DoH/DoT: the
"shield" regularly answered from an open resolver, and the UDP query was
visible to an observer even when an encrypted member won. google_udp,
cloudflareudp, opendnsudp and yandex_udp are out of the group; the
first three already had an encrypted twin there, and an opendns_doh entry
was added for OpenDNS. The *_udp servers themselves stay in the list —
hints and resolver defaults point at them and you can still pick them.
Existing configurations are not rewritten automatically: the new composition
applies to fresh installs and on the next config build.

Stability

  • "Check all servers" no longer crashes the app on naive servers. The check

gathered every server in the list into one temporary configuration and raised
it in one go. For naive that is disproportionately expensive: the core builds
a full Chromium network stack for each such server, in the same process as the
interface — a list with several naive servers hit the core's memory ceiling
and the process was killed, which from outside looked like the app crashing.
Naive servers are now checked in batches, one per configuration: the session
is shut down after each batch and the engines are freed before the next. Other
protocols are checked as before, in a single configuration; the order and
completeness of the check are unchanged.

🔧 Under the hood

Contract mirror 1.1.49 → 1.1.52: six parsing norms became executable, and the
scheme set is now taken from the contract registry rather than from literals in
the code — so a spelling that arrives with the contract works without a code
change. A manually chosen member of an imported selector group survives the
import → backup → import round trip, and an unread key inside settings /
streamSettings now gets a note instead of vanishing without a trace. The
socks4 link form changed to userid:@host — version 4 has no password by
protocol, but clients always write the separator, and some of them read its
absence as "no name".

🧪 Tests

CI checks (analyze, the full test suite, four l10n checkers, docs parity) is
the release gate. This patch's additions: parsing without a silent loss on any
path, seven regression tests on the workspace switch, the insurance identity
tests, mixed sources[] order on import, and the widget test for the new-row
highlight.

📚 Documentation

The diagnostics and Debug API pages, the guard list and the architecture notes
were brought back in line with the code after the v2.25.1 review. Google Play's
"edge-to-edge display" recommendation was traced to its source: the app makes no
calls to the APIs disabled on Android 15 — the references the static scanner
finds come from the Flutter embedding, where they already sit behind an
SDK_INT < 35 runtime gate. No code changed.

🇷🇺 Русский

🩹 Исправления

Подписки и ссылки

  • Ссылки amneziawg:// больше не теряются. Панели пишут полное имя схемы

AmneziaWG, и такие строки исчезали целиком: все их поля приложение читать
умело, но список схем лежал в коде литералами и полного написания не знал.

  • vpn:// с голым .conf внутри даёт узел. Под обёрткой бывает не только

профиль Amnezia, но и сам конфиг wg-quick / AmneziaWG — прежде такая ссылка
давала ноль узлов с сообщением про zlib, которое уводило искать
несуществующую поломку.

  • Подписка из одного конфига Xray — одного объекта, а н…