Compare commits

..

2 commits

Author SHA1 Message Date
938198bf11 Updated to 4.0.0 2025-11-20 21:24:53 +01:00
b7554a5383 Repo created 2025-11-20 21:18:19 +01:00
124 changed files with 2896 additions and 5507 deletions

674
LICENSE
View file

@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View file

@ -1,7 +0,0 @@
# Reporting Security Issues
The KernelSU team and community take security bugs in KernelSU seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/tiann/KernelSU/security/advisories/new) tab, or you can mailto [weishu](mailto:twsxtd@gmail.com) directly.
The KernelSU team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.

View file

@ -1,6 +0,0 @@
project_id_env: CROWDIN_PROJECT_ID
api_token_env: CROWDIN_API_TOKEN
preserve_hierarchy: 1
files:
- source: /manager/app/src/main/res/values/strings.xml
translation: /manager/app/src/main/res/values-%two_letters_code%/strings.xml

View file

@ -1,14 +0,0 @@
alias bk := build_ksud
alias bm := build_manager
build_ksud:
cross build --target aarch64-linux-android --release --manifest-path ./userspace/ksud/Cargo.toml
build_manager: build_ksud
cp userspace/ksud/target/aarch64-linux-android/release/ksud manager/app/src/main/jniLibs/arm64-v8a/libksud.so
cd manager && ./gradlew aDebug
clippy:
cargo fmt --manifest-path ./userspace/ksud/Cargo.toml
cross clippy --target x86_64-pc-windows-gnu --release --manifest-path ./userspace/ksud/Cargo.toml
cross clippy --target aarch64-linux-android --release --manifest-path ./userspace/ksud/Cargo.toml

View file

@ -47,7 +47,7 @@ static void remove_uid_from_arr(uid_t uid)
if (allow_list_pointer == 0)
return;
temp_arr = kzalloc(sizeof(allow_list_arr), GFP_KERNEL);
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL);
if (temp_arr == NULL) {
pr_err("%s: unable to allocate memory\n", __func__);
return;
@ -200,7 +200,7 @@ bool ksu_set_app_profile(struct app_profile *profile, bool persist)
}
// not found, alloc a new node!
p = (struct perm_data *)kzalloc(sizeof(struct perm_data), GFP_KERNEL);
p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL);
if (!p) {
pr_err("ksu_set_app_profile alloc failed\n");
return false;

View file

@ -37,7 +37,7 @@ static struct sdesc *init_sdesc(struct crypto_shash *alg)
int size;
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
sdesc = kzalloc(size, GFP_KERNEL);
sdesc = kmalloc(size, GFP_KERNEL);
if (!sdesc)
return ERR_PTR(-ENOMEM);
sdesc->shash.tfm = alg;

View file

@ -14,7 +14,6 @@
#include "selinux/selinux.h"
#include "syscall_hook_manager.h"
#include "sucompat.h"
#include "sulog.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 7, 0)

View file

@ -7,7 +7,6 @@ enum ksu_feature_id {
KSU_FEATURE_SU_COMPAT = 0,
KSU_FEATURE_KERNEL_UMOUNT = 1,
KSU_FEATURE_ENHANCED_SECURITY = 2,
KSU_FEATURE_SULOG = 3,
KSU_FEATURE_MAX
};

View file

@ -43,6 +43,24 @@ static const struct ksu_feature_handler kernel_umount_handler = {
.set_handler = kernel_umount_feature_set,
};
static bool should_umount(struct path *path)
{
if (!path) {
return false;
}
if (current->nsproxy->mnt_ns == init_nsproxy.mnt_ns) {
pr_info("ignore global mnt namespace process: %d\n", current_uid().val);
return false;
}
if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) {
const char *fstype = path->mnt->mnt_sb->s_type->name;
return strcmp(fstype, "overlay") == 0;
}
return false;
}
extern int path_umount(struct path *path, int flags);
static void ksu_umount_mnt(struct path *path, int flags)
@ -53,7 +71,7 @@ static void ksu_umount_mnt(struct path *path, int flags)
}
}
void try_umount(const char *mnt, int flags)
void try_umount(const char *mnt, bool check_mnt, int flags)
{
struct path path;
int err = kern_path(mnt, 0, &path);
@ -67,6 +85,12 @@ void try_umount(const char *mnt, int flags)
return;
}
// we are only interest in some specific mounts
if (check_mnt && !should_umount(&path)) {
path_put(&path);
return;
}
ksu_umount_mnt(&path, flags);
}
@ -83,14 +107,8 @@ static void umount_tw_func(struct callback_head *cb)
saved = override_creds(tw->old_cred);
}
struct mount_entry *entry;
down_read(&mount_list_lock);
list_for_each_entry(entry, &mount_list, list) {
pr_info("%s: unmounting: %s flags 0x%x\n", __func__, entry->umountable, entry->flags);
try_umount(entry->umountable, entry->flags);
}
up_read(&mount_list_lock);
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and
// filter the mountpoint whose target is `/data/adb`
ksu_umount_manager_execute_all(tw->old_cred);
if (saved)
@ -138,7 +156,7 @@ int ksu_handle_umount(uid_t old_uid, uid_t new_uid)
// umount the target mnt
pr_info("handle umount for uid: %d, pid: %d\n", new_uid, current->pid);
tw = kzalloc(sizeof(*tw), GFP_ATOMIC);
tw = kmalloc(sizeof(*tw), GFP_ATOMIC);
if (!tw)
return 0;

View file

@ -2,24 +2,13 @@
#define __KSU_H_KERNEL_UMOUNT
#include <linux/types.h>
#include <linux/list.h>
#include <linux/rwsem.h>
void ksu_kernel_umount_init(void);
void ksu_kernel_umount_exit(void);
void try_umount(const char *mnt, int flags);
void try_umount(const char *mnt, bool check_mnt, int flags);
// Handler function to be called from setresuid hook
int ksu_handle_umount(uid_t old_uid, uid_t new_uid);
// for the umount list
struct mount_entry {
char *umountable;
unsigned int flags;
struct list_head list;
};
extern struct list_head mount_list;
extern struct rw_semaphore mount_list_lock;
#endif
#endif

View file

@ -92,14 +92,14 @@ void on_post_fs_data(void)
}
extern void ext4_unregister_sysfs(struct super_block *sb);
int nuke_ext4_sysfs(const char* mnt)
static void nuke_ext4_sysfs(void)
{
#ifdef CONFIG_EXT4_FS
struct path path;
int err = kern_path(mnt, 0, &path);
int err = kern_path("/data/adb/modules", 0, &path);
if (err) {
pr_err("nuke path err: %d\n", err);
return err;
return;
}
struct super_block *sb = path.dentry->d_inode->i_sb;
@ -107,19 +107,18 @@ int nuke_ext4_sysfs(const char* mnt)
if (strcmp(name, "ext4") != 0) {
pr_info("nuke but module aren't mounted\n");
path_put(&path);
return -EINVAL;
return;
}
ext4_unregister_sysfs(sb);
path_put(&path);
return 0;
#endif
}
void on_module_mounted(void){
pr_info("on_module_mounted!\n");
ksu_module_mounted = true;
nuke_ext4_sysfs();
}
void on_boot_completed(void){

View file

@ -12,8 +12,6 @@ void on_boot_completed(void);
bool ksu_is_safe_mode(void);
int nuke_ext4_sysfs(const char* mnt);
extern u32 ksu_file_sid;
extern bool ksu_module_mounted;
extern bool ksu_boot_completed;

View file

@ -8,7 +8,6 @@
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/binfmts.h>
#include "manual_su.h"
#include "ksu.h"
#include "allowlist.h"
@ -50,7 +49,7 @@ static char* get_token_from_envp(void)
return NULL;
}
env_copy = kzalloc(env_len + 1, GFP_KERNEL);
env_copy = kmalloc(env_len + 1, GFP_KERNEL);
if (!env_copy) {
up_read(&mm->mmap_lock);
return NULL;
@ -73,7 +72,7 @@ static char* get_token_from_envp(void)
char *token_end = strchr(token_start, '\0');
if (token_end && (token_end - token_start) == KSU_TOKEN_LENGTH) {
token = kzalloc(KSU_TOKEN_LENGTH + 1, GFP_KERNEL);
token = kmalloc(KSU_TOKEN_LENGTH + 1, GFP_KERNEL);
if (token) {
memcpy(token, token_start, KSU_TOKEN_LENGTH);
token[KSU_TOKEN_LENGTH] = '\0';

View file

@ -354,7 +354,7 @@ static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src,
if (datum->u.xperms == NULL) {
datum->u.xperms =
(struct avtab_extended_perms *)(kzalloc(
(struct avtab_extended_perms *)(kmalloc(
sizeof(xperms), GFP_KERNEL));
if (!datum->u.xperms) {
pr_err("alloc xperms failed\n");
@ -548,7 +548,7 @@ static bool add_filename_trans(struct policydb *db, const char *s,
trans = (struct filename_trans_datum *)kcalloc(1 ,sizeof(*trans),
GFP_ATOMIC);
struct filename_trans_key *new_key =
(struct filename_trans_key *)kzalloc(sizeof(*new_key),
(struct filename_trans_key *)kmalloc(sizeof(*new_key),
GFP_ATOMIC);
*new_key = key;
new_key->name = kstrdup(key.name, GFP_ATOMIC);

View file

@ -17,6 +17,7 @@
#include "app_profile.h"
#include "syscall_hook_manager.h"
#include "sulog.h"
#define SU_PATH "/system/bin/su"

View file

@ -14,10 +14,8 @@
#include <linux/spinlock.h>
#include "klog.h"
#include "sulog.h"
#include "ksu.h"
#include "feature.h"
#if __SULOG_GATE
@ -26,28 +24,7 @@ static DEFINE_SPINLOCK(dedup_lock);
static LIST_HEAD(sulog_queue);
static struct workqueue_struct *sulog_workqueue;
static struct work_struct sulog_work;
static bool sulog_enabled __read_mostly = true;
static int sulog_feature_get(u64 *value)
{
*value = sulog_enabled ? 1 : 0;
return 0;
}
static int sulog_feature_set(u64 value)
{
bool enable = value != 0;
sulog_enabled = enable;
pr_info("sulog: set to %d\n", enable);
return 0;
}
static const struct ksu_feature_handler sulog_handler = {
.feature_id = KSU_FEATURE_SULOG,
.name = "sulog",
.get_handler = sulog_feature_get,
.set_handler = sulog_feature_set,
};
static bool sulog_enabled = true;
static void get_timestamp(char *buf, size_t len)
{
@ -203,7 +180,7 @@ static void sulog_add_entry(char *log_buf, size_t len, uid_t uid, u8 dedup_type)
if (!dedup_should_print(uid, dedup_type, log_buf, len))
return;
entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
if (!entry)
return;
@ -326,10 +303,6 @@ void ksu_sulog_report_syscall(uid_t uid, const char *comm, const char *syscall,
int ksu_sulog_init(void)
{
if (ksu_register_feature_handler(&sulog_handler)) {
pr_err("Failed to register sulog feature handler\n");
}
sulog_workqueue = alloc_workqueue("ksu_sulog", WQ_UNBOUND | WQ_HIGHPRI, 1);
if (!sulog_workqueue) {
pr_err("sulog: failed to create workqueue\n");
@ -346,8 +319,6 @@ void ksu_sulog_exit(void)
struct sulog_entry *entry, *tmp;
unsigned long flags;
ksu_unregister_feature_handler(KSU_FEATURE_SULOG);
sulog_enabled = false;
if (sulog_workqueue) {

View file

@ -12,7 +12,7 @@
extern struct timezone sys_tz;
#define SULOG_PATH "/data/adb/ksu/log/sulog.log"
#define SULOG_MAX_SIZE (32 * 1024 * 1024) // 128MB
#define SULOG_MAX_SIZE (128 * 1024 * 1024) // 128MB
#define SULOG_ENTRY_MAX_LEN 512
#define SULOG_COMM_LEN 256
#define DEDUP_SECS 10

View file

@ -1,3 +1,5 @@
#include "supercalls.h"
#include <linux/anon_inodes.h>
#include <linux/capability.h>
#include <linux/cred.h>
@ -12,14 +14,13 @@
#include <linux/uaccess.h>
#include <linux/version.h>
#include "supercalls.h"
#include "arch.h"
#include "allowlist.h"
#include "feature.h"
#include "klog.h" // IWYU pragma: keep
#include "ksud.h"
#include "kernel_umount.h"
#include "manager.h"
#include "sulog.h"
#include "selinux/selinux.h"
#include "objsec.h"
#include "file_wrapper.h"
@ -28,7 +29,6 @@
#include "dynamic_manager.h"
#include "umount_manager.h"
#include "sulog.h"
#ifdef CONFIG_KSU_MANUAL_SU
#include "manual_su.h"
#endif
@ -481,141 +481,6 @@ static int do_manage_mark(void __user *arg)
return 0;
}
static int do_nuke_ext4_sysfs(void __user *arg)
{
struct ksu_nuke_ext4_sysfs_cmd cmd;
char mnt[256];
long ret;
if (copy_from_user(&cmd, arg, sizeof(cmd)))
return -EFAULT;
if (!cmd.arg)
return -EINVAL;
memset(mnt, 0, sizeof(mnt));
ret = strncpy_from_user(mnt, cmd.arg, sizeof(mnt));
if (ret < 0) {
pr_err("nuke ext4 copy mnt failed: %ld\\n", ret);
return -EFAULT; // 或者 return ret;
}
if (ret == sizeof(mnt)) {
pr_err("nuke ext4 mnt path too long\\n");
return -ENAMETOOLONG;
}
pr_info("do_nuke_ext4_sysfs: %s\n", mnt);
return nuke_ext4_sysfs(mnt);
}
struct list_head mount_list = LIST_HEAD_INIT(mount_list);
DECLARE_RWSEM(mount_list_lock);
static int add_try_umount(void __user *arg)
{
struct mount_entry *new_entry, *entry, *tmp;
struct ksu_add_try_umount_cmd cmd;
char buf[256] = {0};
if (copy_from_user(&cmd, arg, sizeof cmd))
return -EFAULT;
switch (cmd.mode) {
case KSU_UMOUNT_WIPE: {
struct mount_entry *entry, *tmp;
down_write(&mount_list_lock);
list_for_each_entry_safe(entry, tmp, &mount_list, list) {
pr_info("wipe_umount_list: removing entry: %s\n", entry->umountable);
list_del(&entry->list);
kfree(entry->umountable);
kfree(entry);
}
up_write(&mount_list_lock);
return 0;
}
case KSU_UMOUNT_ADD: {
long len = strncpy_from_user(buf, (const char __user *)cmd.arg, 256);
if (len <= 0)
return -EFAULT;
buf[sizeof(buf) - 1] = '\0';
new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL);
if (!new_entry)
return -ENOMEM;
new_entry->umountable = kstrdup(buf, GFP_KERNEL);
if (!new_entry->umountable) {
kfree(new_entry);
return -1;
}
down_write(&mount_list_lock);
// disallow dupes
// if this gets too many, we can consider moving this whole task to a kthread
list_for_each_entry(entry, &mount_list, list) {
if (!strcmp(entry->umountable, buf)) {
pr_info("cmd_add_try_umount: %s is already here!\n", buf);
up_write(&mount_list_lock);
kfree(new_entry->umountable);
kfree(new_entry);
return -1;
}
}
// now check flags and add
// this also serves as a null check
if (cmd.flags)
new_entry->flags = cmd.flags;
else
new_entry->flags = 0;
// debug
list_add(&new_entry->list, &mount_list);
up_write(&mount_list_lock);
pr_info("cmd_add_try_umount: %s added!\n", buf);
return 0;
}
// this is just strcmp'd wipe anyway
case KSU_UMOUNT_DEL: {
long len = strncpy_from_user(buf, (const char __user *)cmd.arg, sizeof(buf) - 1);
if (len <= 0)
return -EFAULT;
buf[sizeof(buf) - 1] = '\0';
down_write(&mount_list_lock);
list_for_each_entry_safe(entry, tmp, &mount_list, list) {
if (!strcmp(entry->umountable, buf)) {
pr_info("cmd_add_try_umount: entry removed: %s\n", entry->umountable);
list_del(&entry->list);
kfree(entry->umountable);
kfree(entry);
}
}
up_write(&mount_list_lock);
return 0;
}
default: {
pr_err("cmd_add_try_umount: invalid operation %u\n", cmd.mode);
return -EINVAL;
}
} // switch(cmd.mode)
return 0;
}
// 100. GET_FULL_VERSION - Get full version string
static int do_get_full_version(void __user *arg)
{
@ -827,7 +692,7 @@ static int do_umount_manager(void __user *arg)
switch (cmd.operation) {
case UMOUNT_OP_ADD: {
return ksu_umount_manager_add(cmd.path, cmd.flags, false);
return ksu_umount_manager_add(cmd.path, cmd.check_mnt, cmd.flags, false);
}
case UMOUNT_OP_REMOVE: {
return ksu_umount_manager_remove(cmd.path);
@ -863,8 +728,6 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = {
{ .cmd = KSU_IOCTL_SET_FEATURE, .name = "SET_FEATURE", .handler = do_set_feature, .perm_check = manager_or_root },
{ .cmd = KSU_IOCTL_GET_WRAPPER_FD, .name = "GET_WRAPPER_FD", .handler = do_get_wrapper_fd, .perm_check = manager_or_root },
{ .cmd = KSU_IOCTL_MANAGE_MARK, .name = "MANAGE_MARK", .handler = do_manage_mark, .perm_check = manager_or_root },
{ .cmd = KSU_IOCTL_NUKE_EXT4_SYSFS, .name = "NUKE_EXT4_SYSFS", .handler = do_nuke_ext4_sysfs, .perm_check = manager_or_root },
{ .cmd = KSU_IOCTL_ADD_TRY_UMOUNT, .name = "ADD_TRY_UMOUNT", .handler = add_try_umount, .perm_check = manager_or_root },
{ .cmd = KSU_IOCTL_GET_FULL_VERSION,.name = "GET_FULL_VERSION", .handler = do_get_full_version, .perm_check = always_allow},
{ .cmd = KSU_IOCTL_HOOK_TYPE,.name = "GET_HOOK_TYPE", .handler = do_get_hook_type, .perm_check = manager_or_root},
{ .cmd = KSU_IOCTL_ENABLE_KPM, .name = "GET_ENABLE_KPM", .handler = do_enable_kpm, .perm_check = manager_or_root},

View file

@ -94,21 +94,6 @@ struct ksu_manage_mark_cmd {
#define KSU_MARK_UNMARK 3
#define KSU_MARK_REFRESH 4
struct ksu_nuke_ext4_sysfs_cmd {
__aligned_u64 arg; // Input: mnt pointer
};
struct ksu_add_try_umount_cmd {
__aligned_u64 arg; // char ptr, this is the mountpoint
__u32 flags; // this is the flag we use for it
__u8 mode; // denotes what to do with it 0:wipe_list 1:add_to_list 2:delete_entry
};
#define KSU_UMOUNT_WIPE 0 // ignore everything and wipe list
#define KSU_UMOUNT_ADD 1 // add entry (path + flags)
#define KSU_UMOUNT_DEL 2 // delete entry, strcmp
// Other command structures
struct ksu_get_full_version_cmd {
char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string
@ -162,8 +147,6 @@ struct ksu_manual_su_cmd {
#define KSU_IOCTL_SET_FEATURE _IOC(_IOC_WRITE, 'K', 14, 0)
#define KSU_IOCTL_GET_WRAPPER_FD _IOC(_IOC_WRITE, 'K', 15, 0)
#define KSU_IOCTL_MANAGE_MARK _IOC(_IOC_READ|_IOC_WRITE, 'K', 16, 0)
#define KSU_IOCTL_NUKE_EXT4_SYSFS _IOC(_IOC_WRITE, 'K', 17, 0)
#define KSU_IOCTL_ADD_TRY_UMOUNT _IOC(_IOC_WRITE, 'K', 18, 0)
// Other IOCTL command definitions
#define KSU_IOCTL_GET_FULL_VERSION _IOC(_IOC_READ, 'K', 100, 0)
#define KSU_IOCTL_HOOK_TYPE _IOC(_IOC_READ, 'K', 101, 0)

View file

@ -268,7 +268,7 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
if (d_type == DT_DIR && my_ctx->depth > 0 &&
(my_ctx->stop && !*my_ctx->stop)) {
struct data_path *data = kzalloc(sizeof(struct data_path), GFP_ATOMIC);
struct data_path *data = kmalloc(sizeof(struct data_path), GFP_ATOMIC);
if (!data) {
pr_err("Failed to allocate memory for %s\n", dirpath);
@ -303,24 +303,29 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
// Check for dynamic sign or multi-manager signatures
if (is_multi_manager && (signature_index == DYNAMIC_SIGN_INDEX || signature_index >= 2)) {
crown_manager(dirpath, my_ctx->private_data, signature_index);
struct apk_path_hash *apk_data = kmalloc(sizeof(struct apk_path_hash), GFP_ATOMIC);
if (apk_data) {
apk_data->hash = hash;
apk_data->exists = true;
list_add_tail(&apk_data->list, &apk_path_hash_list);
}
} else if (is_manager_apk(dirpath)) {
crown_manager(dirpath, my_ctx->private_data, 0);
*my_ctx->stop = 1;
}
struct apk_path_hash *apk_data = kzalloc(sizeof(*apk_data), GFP_ATOMIC);
if (apk_data) {
apk_data->hash = hash;
apk_data->exists = true;
list_add_tail(&apk_data->list, &apk_path_hash_list);
}
if (is_manager_apk(dirpath)) {
// Manager found, clear APK cache list
list_for_each_entry_safe(pos, n, &apk_path_hash_list, list) {
list_for_each_entry_safe (pos, n, &apk_path_hash_list, list) {
list_del(&pos->list);
kfree(pos);
}
} else {
struct apk_path_hash *apk_data = kmalloc(sizeof(struct apk_path_hash), GFP_ATOMIC);
if (apk_data) {
apk_data->hash = hash;
apk_data->exists = true;
list_add_tail(&apk_data->list, &apk_path_hash_list);
}
}
}
}

View file

@ -17,7 +17,7 @@ static struct umount_manager g_umount_mgr = {
static void try_umount_path(struct umount_entry *entry)
{
try_umount(entry->path, entry->flags);
try_umount(entry->path, entry->check_mnt, entry->flags);
}
static struct umount_entry *find_entry_locked(const char *path)
@ -33,12 +33,46 @@ static struct umount_entry *find_entry_locked(const char *path)
return NULL;
}
static int init_default_entries(void)
{
int ret;
const struct {
const char *path;
bool check_mnt;
int flags;
} defaults[] = {
{ "/odm", true, 0 },
{ "/system", true, 0 },
{ "/vendor", true, 0 },
{ "/product", true, 0 },
{ "/system_ext", true, 0 },
{ "/data/adb/modules", false, MNT_DETACH },
{ "/debug_ramdisk", false, MNT_DETACH },
};
for (int i = 0; i < ARRAY_SIZE(defaults); i++) {
ret = ksu_umount_manager_add(defaults[i].path,
defaults[i].check_mnt,
defaults[i].flags,
true); // is_default = true
if (ret) {
pr_err("Failed to add default entry: %s, ret=%d\n",
defaults[i].path, ret);
return ret;
}
}
pr_info("Initialized %zu default umount entries\n", ARRAY_SIZE(defaults));
return 0;
}
int ksu_umount_manager_init(void)
{
INIT_LIST_HEAD(&g_umount_mgr.entry_list);
spin_lock_init(&g_umount_mgr.lock);
return 0;
return init_default_entries();
}
void ksu_umount_manager_exit(void)
@ -59,7 +93,7 @@ void ksu_umount_manager_exit(void)
pr_info("Umount manager cleaned up\n");
}
int ksu_umount_manager_add(const char *path, int flags, bool is_default)
int ksu_umount_manager_add(const char *path, bool check_mnt, int flags, bool is_default)
{
struct umount_entry *entry;
unsigned long irqflags;
@ -93,6 +127,7 @@ int ksu_umount_manager_add(const char *path, int flags, bool is_default)
}
strncpy(entry->path, path, sizeof(entry->path) - 1);
entry->check_mnt = check_mnt;
entry->flags = flags;
entry->state = UMOUNT_STATE_IDLE;
entry->is_default = is_default;
@ -199,6 +234,7 @@ int ksu_umount_manager_get_entries(struct ksu_umount_entry_info __user *entries,
memset(&info, 0, sizeof(info));
strncpy(info.path, entry->path, sizeof(info.path) - 1);
info.check_mnt = entry->check_mnt;
info.flags = entry->flags;
info.is_default = entry->is_default;
info.state = entry->state;

View file

@ -16,6 +16,7 @@ enum umount_entry_state {
struct umount_entry {
struct list_head list;
char path[256];
bool check_mnt;
int flags;
enum umount_entry_state state;
bool is_default;
@ -39,6 +40,7 @@ enum umount_manager_op {
struct ksu_umount_manager_cmd {
__u32 operation;
char path[256];
__u8 check_mnt;
__s32 flags;
__u32 count;
__aligned_u64 entries_ptr;
@ -46,6 +48,7 @@ struct ksu_umount_manager_cmd {
struct ksu_umount_entry_info {
char path[256];
__u8 check_mnt;
__s32 flags;
__u8 is_default;
__u32 state;
@ -54,7 +57,7 @@ struct ksu_umount_entry_info {
int ksu_umount_manager_init(void);
void ksu_umount_manager_exit(void);
int ksu_umount_manager_add(const char *path, int flags, bool is_default);
int ksu_umount_manager_add(const char *path, bool check_mnt, int flags, bool is_default);
int ksu_umount_manager_remove(const char *path);
void ksu_umount_manager_execute_all(const struct cred *cred);
int ksu_umount_manager_get_entries(struct ksu_umount_entry_info __user *entries, u32 *count);

View file

@ -319,14 +319,6 @@ NativeBridge(setEnhancedSecurityEnabled, jboolean, jboolean enabled) {
return set_enhanced_security_enabled(enabled);
}
NativeBridgeNP(isSuLogEnabled, jboolean) {
return is_sulog_enabled();
}
NativeBridge(setSuLogEnabled, jboolean, jboolean enabled) {
return set_sulog_enabled(enabled);
}
NativeBridge(getUserName, jstring, jint uid) {
struct passwd *pw = getpwuid((uid_t) uid);
if (pw && pw->pw_name && pw->pw_name[0] != '\0') {

View file

@ -231,22 +231,6 @@ bool is_enhanced_security_enabled() {
return value != 0;
}
bool set_sulog_enabled(bool enabled) {
return set_feature(KSU_FEATURE_SULOG, enabled ? 1 : 0);
}
bool is_sulog_enabled() {
uint64_t value = 0;
bool supported = false;
if (!get_feature(KSU_FEATURE_SULOG, &value, &supported)) {
return false;
}
if (!supported) {
return false;
}
return value != 0;
}
void get_full_version(char* buff) {
struct ksu_get_full_version_cmd cmd = {0};
if (ksuctl(KSU_IOCTL_GET_FULL_VERSION, &cmd) == 0) {

View file

@ -132,7 +132,6 @@ enum ksu_feature_id {
KSU_FEATURE_SU_COMPAT = 0,
KSU_FEATURE_KERNEL_UMOUNT = 1,
KSU_FEATURE_ENHANCED_SECURITY = 2,
KSU_FEATURE_SULOG = 3,
};
// Generic feature API
@ -212,11 +211,8 @@ bool is_kernel_umount_enabled();
// Enhanced security
bool set_enhanced_security_enabled(bool enabled);
bool is_enhanced_security_enabled();
// Su log
bool set_sulog_enabled(bool enabled);
bool is_sulog_enabled();
bool is_enhanced_security_enabled();
// Other command structures
struct ksu_get_full_version_cmd {

View file

@ -118,15 +118,6 @@ object Natives {
external fun isEnhancedSecurityEnabled(): Boolean
external fun setEnhancedSecurityEnabled(enabled: Boolean): Boolean
/**
* Su Log can be enabled/disabled.
* 0: disabled
* 1: enabled
* negative : error
*/
external fun isSuLogEnabled(): Boolean
external fun setSuLogEnabled(enabled: Boolean): Boolean
external fun isKPMEnabled(): Boolean
external fun getHookType(): String

View file

@ -353,40 +353,38 @@ fun InstallScreen(
.fillMaxWidth()
.padding(16.dp)
) {
if (isGKI) {
// 使用本地的LKM文件
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
elevation = getCardElevation(),
// 使用本地的LKM文件
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
elevation = getCardElevation(),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp),
) {
ListItem(
headlineContent = {
Text(stringResource(id = R.string.install_upload_lkm_file))
},
supportingContent = {
(lkmSelection as? LkmSelection.LkmUri)?.let {
Text(
stringResource(
id = R.string.selected_lkm,
it.uri.lastPathSegment ?: "(file)"
)
)
}
},
leadingContent = {
Icon(
Icons.AutoMirrored.Filled.Input,
contentDescription = null
)
},
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp),
) {
ListItem(
headlineContent = {
Text(stringResource(id = R.string.install_upload_lkm_file))
},
supportingContent = {
(lkmSelection as? LkmSelection.LkmUri)?.let {
Text(
stringResource(
id = R.string.selected_lkm,
it.uri.lastPathSegment ?: "(file)"
)
)
}
},
leadingContent = {
Icon(
Icons.AutoMirrored.Filled.Input,
contentDescription = null
)
},
modifier = Modifier
.fillMaxWidth()
.clickable { onLkmUpload() }
)
}
.clickable { onLkmUpload() }
)
}
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->

View file

@ -865,7 +865,7 @@ private fun ModuleList(
ModuleOperationUtils.handleModuleUninstall(module.dirId)
uninstallModule(module.dirId)
} else {
undoUninstallModule(module.dirId)
restoreModule(module.dirId)
}
}
}

View file

@ -11,10 +11,12 @@ import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material.icons.automirrored.filled.Undo
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.rounded.EnhancedEncryption
@ -54,6 +56,7 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
import com.sukisu.ultra.ui.theme.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation
import com.sukisu.ultra.ui.util.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -76,7 +79,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
val snackBarHost = LocalSnackbarHost.current
val context = LocalContext.current
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
var isSuLogEnabled by remember { mutableStateOf(Natives.isSuLogEnabled()) }
var selectedEngine by rememberSaveable {
mutableStateOf(
prefs.getString("webui_engine", "default") ?: "default"
@ -148,28 +150,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
)
}
val enhancedStatus by produceState(initialValue = "") {
value = getFeatureStatus("enhanced_security")
}
val enhancedSummary = when (enhancedStatus) {
"unsupported" -> stringResource(id = R.string.feature_status_unsupported_summary)
"managed" -> stringResource(id = R.string.feature_status_managed_summary)
else -> stringResource(id = R.string.settings_enable_enhanced_security_summary)
}
SuperDropdown(
icon = Icons.Rounded.EnhancedEncryption,
title = stringResource(id = R.string.settings_enable_enhanced_security),
summary = enhancedSummary,
summary = stringResource(id = R.string.settings_enable_enhanced_security_summary),
items = modeItems,
leftAction = {
Icon(
Icons.Rounded.EnhancedEncryption,
modifier = Modifier.padding(end = 16.dp),
contentDescription = stringResource(id = R.string.settings_enable_enhanced_security),
tint = MaterialTheme.colorScheme.onBackground
)
},
enabled = enhancedStatus == "supported",
selectedIndex = enhancedSecurityMode,
onSelectedIndexChange = { index ->
when (index) {
@ -208,28 +193,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
)
}
val suStatus by produceState(initialValue = "") {
value = getFeatureStatus("su_compat")
}
val suSummary = when (suStatus) {
"unsupported" -> stringResource(id = R.string.feature_status_unsupported_summary)
"managed" -> stringResource(id = R.string.feature_status_managed_summary)
else -> stringResource(id = R.string.settings_disable_su_summary)
}
SuperDropdown(
icon = Icons.Rounded.RemoveModerator,
title = stringResource(id = R.string.settings_disable_su),
summary = suSummary,
summary = stringResource(id = R.string.settings_disable_su_summary),
items = modeItems,
leftAction = {
Icon(
Icons.Rounded.RemoveModerator,
modifier = Modifier.padding(end = 16.dp),
contentDescription = stringResource(id = R.string.settings_disable_su),
tint = MaterialTheme.colorScheme.onBackground
)
},
enabled = suStatus == "supported",
selectedIndex = suCompatMode,
onSelectedIndexChange = { index ->
when (index) {
@ -268,28 +236,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
)
}
val umountStatus by produceState(initialValue = "") {
value = getFeatureStatus("kernel_umount")
}
val umountSummary = when (umountStatus) {
"unsupported" -> stringResource(id = R.string.feature_status_unsupported_summary)
"managed" -> stringResource(id = R.string.feature_status_managed_summary)
else -> stringResource(id = R.string.settings_disable_kernel_umount_summary)
}
SuperDropdown(
icon = Icons.Rounded.RemoveCircle,
title = stringResource(id = R.string.settings_disable_kernel_umount),
summary = umountSummary,
summary = stringResource(id = R.string.settings_disable_kernel_umount_summary),
items = modeItems,
leftAction = {
Icon(
Icons.Rounded.RemoveCircle,
modifier = Modifier.padding(end = 16.dp),
contentDescription = stringResource(id = R.string.settings_disable_kernel_umount),
tint = MaterialTheme.colorScheme.onBackground
)
},
enabled = umountStatus == "supported",
selectedIndex = kernelUmountMode,
onSelectedIndexChange = { index ->
when (index) {
@ -319,68 +270,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
)
var suLogMode by rememberSaveable {
mutableIntStateOf(
run {
val currentEnabled = Natives.isSuLogEnabled()
val savedPersist = prefs.getInt("sulog_mode", 0)
if (savedPersist == 2) 2 else if (!currentEnabled) 1 else 0
}
)
}
val suLogStatus by produceState(initialValue = "") {
value = getFeatureStatus("sulog")
}
val suLogSummary = when (suLogStatus) {
"unsupported" -> stringResource(id = R.string.feature_status_unsupported_summary)
"managed" -> stringResource(id = R.string.feature_status_managed_summary)
else -> stringResource(id = R.string.settings_disable_sulog_summary)
}
SuperDropdown(
title = stringResource(id = R.string.settings_disable_sulog),
summary = suLogSummary,
items = modeItems,
leftAction = {
Icon(
Icons.Rounded.RemoveCircle,
modifier = Modifier.padding(end = 16.dp),
contentDescription = stringResource(id = R.string.settings_disable_sulog),
tint = MaterialTheme.colorScheme.onBackground
)
},
enabled = suLogStatus == "supported",
selectedIndex = suLogMode,
onSelectedIndexChange = { index ->
when (index) {
// Default: enable and save to persist
0 -> if (Natives.setSuLogEnabled(true)) {
execKsud("feature save", true)
prefs.edit { putInt("sulog_mode", 0) }
suLogMode = 0
isSuLogEnabled = true
}
// Temporarily disable: save enabled state first, then disable
1 -> if (Natives.setSuLogEnabled(true)) {
execKsud("feature save", true)
if (Natives.setSuLogEnabled(false)) {
prefs.edit { putInt("sulog_mode", 0) }
suLogMode = 1
isSuLogEnabled = false
}
}
// Permanently disable: disable and save
2 -> if (Natives.setSuLogEnabled(false)) {
execKsud("feature save", true)
prefs.edit { putInt("sulog_mode", 2) }
suLogMode = 2
isSuLogEnabled = false
}
}
}
)
// 卸载模块开关
var umountChecked by rememberSaveable { mutableStateOf(Natives.isDefaultUmountModules()) }
SwitchItem(
@ -394,6 +283,26 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
}
)
// 强制签名验证开关
var forceSignatureVerification by rememberSaveable {
mutableStateOf(prefs.getBoolean("force_signature_verification", false))
}
SwitchItem(
icon = Icons.Filled.Security,
title = stringResource(R.string.module_signature_verification),
summary = stringResource(R.string.module_signature_verification_summary),
checked = forceSignatureVerification,
onCheckedChange = { enabled ->
prefs.edit { putBoolean("force_signature_verification", enabled) }
forceSignatureVerification = enabled
}
)
// UID 扫描开关
if (Natives.version >= Natives.MINIMAL_SUPPORTED_UID_SCANNER && Natives.version >= Natives.MINIMAL_NEW_IOCTL_KERNEL) {
UidScannerSection(prefs, snackBarHost, scope, context)
}
}
)
}
@ -494,16 +403,14 @@ fun SettingScreen(navigator: DestinationsNavigator) {
// 查看使用日志
KsuIsValid {
if (isSuLogEnabled) {
SettingItem(
icon = Icons.Filled.Visibility,
title = stringResource(R.string.log_viewer_view_logs),
summary = stringResource(R.string.log_viewer_view_logs_summary),
onClick = {
navigator.navigate(LogViewerScreenDestination)
}
)
}
SettingItem(
icon = Icons.Filled.Visibility,
title = stringResource(R.string.log_viewer_view_logs),
summary = stringResource(R.string.log_viewer_view_logs_summary),
onClick = {
navigator.navigate(LogViewerScreenDestination)
}
)
}
val lkmMode = Natives.isLkmMode
KsuIsValid {
@ -1049,3 +956,124 @@ private fun TopBar(
scrollBehavior = scrollBehavior
)
}
@Composable
private fun UidScannerSection(
prefs: SharedPreferences,
snackBarHost: SnackbarHostState,
scope: CoroutineScope,
context: Context
) {
if (Natives.version < Natives.MINIMAL_SUPPORTED_UID_SCANNER) return
val realAuto = Natives.isUidScannerEnabled()
val realMulti = getUidMultiUserScan()
var autoOn by remember { mutableStateOf(realAuto) }
var multiOn by remember { mutableStateOf(realMulti) }
LaunchedEffect(Unit) {
autoOn = realAuto
multiOn = realMulti
prefs.edit {
putBoolean("uid_auto_scan", autoOn)
putBoolean("uid_multi_user_scan", multiOn)
}
}
SwitchItem(
icon = Icons.Filled.Scanner,
title = stringResource(R.string.uid_auto_scan_title),
summary = stringResource(R.string.uid_auto_scan_summary),
checked = autoOn,
onCheckedChange = { target ->
autoOn = target
if (!target) multiOn = false
scope.launch(Dispatchers.IO) {
setUidAutoScan(target)
val actual = Natives.isUidScannerEnabled() || readUidScannerFile()
withContext(Dispatchers.Main) {
autoOn = actual
if (!actual) multiOn = false
prefs.edit {
putBoolean("uid_auto_scan", actual)
putBoolean("uid_multi_user_scan", multiOn)
}
if (actual != target) {
snackBarHost.showSnackbar(
context.getString(R.string.uid_scanner_setting_failed)
)
}
}
}
}
)
AnimatedVisibility(
visible = autoOn,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
SwitchItem(
icon = Icons.Filled.Groups,
title = stringResource(R.string.uid_multi_user_scan_title),
summary = stringResource(R.string.uid_multi_user_scan_summary),
checked = multiOn,
onCheckedChange = { target ->
scope.launch(Dispatchers.IO) {
val ok = setUidMultiUserScan(target)
withContext(Dispatchers.Main) {
if (ok) {
multiOn = target
prefs.edit { putBoolean("uid_multi_user_scan", target) }
} else {
snackBarHost.showSnackbar(
context.getString(R.string.uid_scanner_setting_failed)
)
}
}
}
}
)
}
AnimatedVisibility(
visible = autoOn,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
val confirmDialog = rememberConfirmDialog()
SettingItem(
icon = Icons.Filled.CleaningServices,
title = stringResource(R.string.clean_runtime_environment),
summary = stringResource(R.string.clean_runtime_environment_summary),
onClick = {
scope.launch {
if (confirmDialog.awaitConfirm(
title = context.getString(R.string.clean_runtime_environment),
content = context.getString(R.string.clean_runtime_environment_confirm)
) == ConfirmResult.Confirmed
) {
if (cleanRuntimeEnvironment()) {
autoOn = false
multiOn = false
prefs.edit {
putBoolean("uid_auto_scan", false)
putBoolean("uid_multi_user_scan", false)
}
Natives.setUidScannerEnabled(false)
snackBarHost.showSnackbar(
context.getString(R.string.clean_runtime_environment_success)
)
} else {
snackBarHost.showSnackbar(
context.getString(R.string.clean_runtime_environment_failed)
)
}
}
}
}
)
}
}

View file

@ -6,7 +6,6 @@ import androidx.compose.animation.core.*
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.expandVertically
import androidx.compose.animation.*
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
@ -32,7 +31,6 @@ import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -40,15 +38,12 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@ -56,7 +51,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest
import com.dergoogler.mmrl.ui.component.LabelItem
import com.dergoogler.mmrl.ui.component.LabelItemDefaults
@ -320,9 +314,6 @@ private fun SuperUserContent(
scope: CoroutineScope
) {
val expandedGroups = remember { mutableStateOf(setOf<Int>()) }
val density = LocalDensity.current
val targetSizePx = remember(density) { with(density) { 36.dp.roundToPx() } }
val context = LocalContext.current
PullToRefreshBox(
modifier = Modifier.padding(innerPadding),
@ -338,7 +329,6 @@ private fun SuperUserContent(
filteredAndSortedAppGroups.forEachIndexed { _, appGroup ->
item(key = "${appGroup.uid}-${appGroup.mainApp.packageName}") {
AppGroupItem(
expandedGroups = expandedGroups,
appGroup = appGroup,
isSelected = appGroup.packageNames.any { viewModel.selectedApps.contains(it) },
onToggleSelection = {
@ -367,46 +357,32 @@ private fun SuperUserContent(
)
}
if (appGroup.apps.size <= 1) return@forEachIndexed
items(appGroup.apps, key = { "${it.packageName}-${it.uid}" }) { app ->
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(context)
.data(app.packageInfo)
.size(targetSizePx)
.crossfade(true)
.build()
)
val listItemContent = remember(app.packageName, appGroup.uid) {
@Composable {
ListItem(
modifier = Modifier
.clickable { navigator.navigate(AppProfileScreenDestination(app)) }
.fillMaxWidth()
.padding(start = 10.dp),
headlineContent = { Text(app.label, style = MaterialTheme.typography.bodyMedium) },
supportingContent = { Text(app.packageName, style = MaterialTheme.typography.bodySmall) },
leadingContent = {
Image(
painter = painter,
contentDescription = app.label,
modifier = Modifier
.padding(4.dp)
.size(36.dp),
contentScale = ContentScale.Crop
)
}
)
}
}
AnimatedVisibility(
visible = expandedGroups.value.contains(appGroup.uid),
visible = expandedGroups.value.contains(appGroup.uid) && appGroup.apps.size > 1,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
listItemContent()
ListItem(
modifier = Modifier
.fillMaxWidth()
.padding(start = 10.dp)
.clickable {
navigator.navigate(AppProfileScreenDestination(app))
},
headlineContent = { Text(app.label, style = MaterialTheme.typography.bodyMedium) },
supportingContent = { Text(app.packageName, style = MaterialTheme.typography.bodySmall) },
leadingContent = {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(app.packageInfo)
.crossfade(true)
.build(),
contentDescription = app.label,
modifier = Modifier.padding(4.dp).width(36.dp).height(36.dp)
)
}
)
}
}
}
@ -821,8 +797,7 @@ private fun AppGroupItem(
onToggleSelection: () -> Unit,
onClick: () -> Unit,
onLongClick: () -> Unit,
viewModel: SuperUserViewModel,
expandedGroups: MutableState<Set<Int>>
viewModel: SuperUserViewModel
) {
val mainApp = appGroup.mainApp
@ -843,27 +818,9 @@ private fun AppGroupItem(
} else {
mainApp.packageName
}
Text(summaryText)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(summaryText)
if (appGroup.apps.size > 1) {
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = null,
modifier = Modifier.rotate(
animateFloatAsState(
targetValue = if (expandedGroups.value.contains(appGroup.uid)) 180f else 0f,
animationSpec = tween(200, easing = LinearOutSlowInEasing),
label = ""
).value
)
)
}
}
Spacer(modifier = Modifier.height(4.dp))
FlowRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
if (appGroup.allowSu) {
@ -896,7 +853,7 @@ private fun AppGroupItem(
)
}
if (appGroup.apps.size > 1) {
appGroup.userName?.let {
Natives.getUserName(appGroup.uid)?.let {
LabelItem(
text = it,
style = LabelItemDefaults.style.copy(

View file

@ -1,6 +1,5 @@
package com.sukisu.ultra.ui.screen
import android.content.Context
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@ -38,7 +37,9 @@ private val SPACING_LARGE = 16.dp
data class UmountPathEntry(
val path: String,
val checkMnt: Boolean,
val flags: Int,
val isDefault: Boolean
)
@OptIn(ExperimentalMaterial3Api::class)
@ -242,11 +243,11 @@ fun UmountManagerScreen(navigator: DestinationsNavigator) {
if (showAddDialog) {
AddUmountPathDialog(
onDismiss = { showAddDialog = false },
onConfirm = { path, flags ->
onConfirm = { path, checkMnt, flags ->
showAddDialog = false
scope.launch(Dispatchers.IO) {
val success = addUmountPath(path, flags)
val success = addUmountPath(path, checkMnt, flags)
withContext(Dispatchers.Main) {
if (success) {
saveUmountConfig()
@ -290,7 +291,10 @@ fun UmountPathCard(
Icon(
imageVector = Icons.Filled.Folder,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
tint = if (entry.isDefault)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.secondary,
modifier = Modifier.size(24.dp)
)
@ -304,31 +308,42 @@ fun UmountPathCard(
Spacer(modifier = Modifier.height(SPACING_SMALL))
Text(
text = buildString {
append(context.getString(R.string.check_mount_type))
append(": ")
append(if (entry.checkMnt) context.getString(R.string.yes) else context.getString(R.string.no))
append(" | ")
append(context.getString(R.string.flags))
append(": ")
append(entry.flags.toUmountFlagName(context))
if (entry.isDefault) {
append(" | ")
append(context.getString(R.string.default_entry))
}
},
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
IconButton(
onClick = {
scope.launch {
if (confirmDialog.awaitConfirm(
title = context.getString(R.string.confirm_delete),
content = context.getString(R.string.confirm_delete_umount_path, entry.path)
) == ConfirmResult.Confirmed) {
onDelete()
if (!entry.isDefault) {
IconButton(
onClick = {
scope.launch {
if (confirmDialog.awaitConfirm(
title = context.getString(R.string.confirm_delete),
content = context.getString(R.string.confirm_delete_umount_path, entry.path)
) == ConfirmResult.Confirmed) {
onDelete()
}
}
}
) {
Icon(
imageVector = Icons.Filled.Delete,
contentDescription = null,
tint = MaterialTheme.colorScheme.error
)
}
) {
Icon(
imageVector = Icons.Filled.Delete,
contentDescription = null,
tint = MaterialTheme.colorScheme.error
)
}
}
}
@ -337,9 +352,10 @@ fun UmountPathCard(
@Composable
fun AddUmountPathDialog(
onDismiss: () -> Unit,
onConfirm: (String, Int) -> Unit
onConfirm: (String, Boolean, Int) -> Unit
) {
var path by rememberSaveable { mutableStateOf("") }
var checkMnt by rememberSaveable { mutableStateOf(false) }
var flags by rememberSaveable { mutableStateOf("-1") }
AlertDialog(
@ -357,6 +373,20 @@ fun AddUmountPathDialog(
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = checkMnt,
onCheckedChange = { checkMnt = it }
)
Spacer(modifier = Modifier.width(SPACING_SMALL))
Text(stringResource(R.string.check_mount_type_overlay))
}
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
OutlinedTextField(
value = flags,
onValueChange = { flags = it },
@ -372,7 +402,7 @@ fun AddUmountPathDialog(
TextButton(
onClick = {
val flagsInt = flags.toIntOrNull() ?: -1
onConfirm(path, flagsInt)
onConfirm(path, checkMnt, flagsInt)
},
enabled = path.isNotBlank()
) {
@ -393,16 +423,18 @@ private fun parseUmountPaths(output: String): List<UmountPathEntry> {
return lines.drop(2).mapNotNull { line ->
val parts = line.trim().split(Regex("\\s+"))
if (parts.size >= 2) {
if (parts.size >= 4) {
UmountPathEntry(
path = parts[0],
flags = parts[1].toIntOrNull() ?: -1
checkMnt = parts[1].equals("true", ignoreCase = true),
flags = parts[2].toIntOrNull() ?: -1,
isDefault = parts[3].equals("Yes", ignoreCase = true)
)
} else null
}
}
private fun Int.toUmountFlagName(context: Context): String {
private fun Int.toUmountFlagName(context: android.content.Context): String {
return when (this) {
-1 -> context.getString(R.string.mnt_detach)
else -> this.toString()

View file

@ -866,9 +866,12 @@ object SuSFSManager {
"CONFIG_KSU_SUSFS_SPOOF_CMDLINE_OR_BOOTCONFIG" to context.getString(R.string.spoof_cmdline_feature_label),
"CONFIG_KSU_SUSFS_OPEN_REDIRECT" to context.getString(R.string.open_redirect_feature_label),
"CONFIG_KSU_SUSFS_ENABLE_LOG" to context.getString(R.string.enable_log_feature_label),
"CONFIG_KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT" to context.getString(R.string.auto_default_mount_feature_label),
"CONFIG_KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT" to context.getString(R.string.auto_bind_mount_feature_label),
"CONFIG_KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT" to context.getString(R.string.auto_try_umount_bind_feature_label),
"CONFIG_KSU_SUSFS_HIDE_KSU_SUSFS_SYMBOLS" to context.getString(R.string.hide_symbols_feature_label),
"CONFIG_KSU_SUSFS_SUS_KSTAT" to context.getString(R.string.sus_kstat_feature_label),
"CONFIG_KSU_SUSFS_SUS_SU" to context.getString(R.string.sus_su_feature_label)
)
@ -896,9 +899,12 @@ object SuSFSManager {
"spoof_cmdline_feature_label" to context.getString(R.string.spoof_cmdline_feature_label),
"open_redirect_feature_label" to context.getString(R.string.open_redirect_feature_label),
"enable_log_feature_label" to context.getString(R.string.enable_log_feature_label),
"auto_default_mount_feature_label" to context.getString(R.string.auto_default_mount_feature_label),
"auto_bind_mount_feature_label" to context.getString(R.string.auto_bind_mount_feature_label),
"auto_try_umount_bind_feature_label" to context.getString(R.string.auto_try_umount_bind_feature_label),
"hide_symbols_feature_label" to context.getString(R.string.hide_symbols_feature_label),
"sus_kstat_feature_label" to context.getString(R.string.sus_kstat_feature_label),
"sus_su_feature_label" to context.getString(R.string.sus_su_feature_label)
)
return defaultFeatures.map { (_, displayName) ->

View file

@ -0,0 +1,572 @@
package com.sukisu.ultra.ui.util;
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.text.TextUtils;
import android.util.Log;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Locale;
/**
* An object to convert Chinese character to its corresponding pinyin string. For characters with
* multiple possible pinyin string, only one is selected according to collator. Polyphone is not
* supported in this implementation. This class is implemented to achieve the best runtime
* performance and minimum runtime resources with tolerable sacrifice of accuracy. This
* implementation highly depends on zh_CN ICU collation data and must be always synchronized with
* ICU.
* <p>
* Currently this file is aligned to zh.txt in ICU 4.6
*/
@SuppressWarnings("SizeReplaceableByIsEmpty")
public record HanziToPinyin(boolean mHasChinaCollator) {
private static final String TAG = "HanziToPinyin";
// Turn on this flag when we want to check internal data structure.
private static final boolean DEBUG = false;
/**
* Unihans array.
* <p>
* Each unihans is the first one within same pinyin when collator is zh_CN.
*/
public static final char[] UNIHANS = {
'阿', '哎', '安', '肮', '凹', '八',
'挀', '扳', '邦', '勹', '陂', '奔',
'伻', '屄', '边', '灬', '憋', '汃',
'冫', '癶', '峬', '嚓', '偲', '参',
'仓', '撡', '冊', '嵾', '曽', '曾',
'層', '叉', '芆', '辿', '伥', '抄',
'车', '抻', '沈', '沉', '阷', '吃',
'充', '抽', '出', '欻', '揣', '巛',
'刅', '吹', '旾', '逴', '呲', '匆',
'凑', '粗', '汆', '崔', '邨', '搓',
'咑', '呆', '丹', '当', '刀', '嘚',
'扥', '灯', '氐', '嗲', '甸', '刁',
'爹', '丁', '丟', '东', '吺', '厾',
'耑', '襨', '吨', '多', '妸', '诶',
'奀', '鞥', '儿', '发', '帆', '匚',
'飞', '分', '丰', '覅', '仏', '紑',
'伕', '旮', '侅', '甘', '冈', '皋',
'戈', '给', '根', '刯', '工', '勾',
'估', '瓜', '乖', '关', '光', '归',
'丨', '呙', '哈', '咍', '佄', '夯',
'茠', '诃', '黒', '拫', '亨', '噷',
'叿', '齁', '乯', '花', '怀', '犿',
'巟', '灰', '昏', '吙', '丌', '加',
'戋', '江', '艽', '阶', '巾', '坕',
'冂', '丩', '凥', '姢', '噘', '军',
'咔', '开', '刊', '忼', '尻', '匼',
'肎', '劥', '空', '抠', '扝', '夸',
'蒯', '宽', '匡', '亏', '坤', '扩',
'垃', '来', '兰', '啷', '捞', '肋',
'勒', '崚', '刕', '俩', '奁', '良',
'撩', '列', '拎', '刢', '溜', '囖',
'龙', '瞜', '噜', '娈', '畧', '抡',
'罗', '呣', '妈', '埋', '嫚', '牤',
'猫', '么', '呅', '门', '甿', '咪',
'宀', '喵', '乜', '民', '名', '谬',
'摸', '哞', '毪', '嗯', '拏', '腉',
'囡', '囔', '孬', '疒', '娞', '恁',
'能', '妮', '拈', '嬢', '鸟', '捏',
'囜', '宁', '妞', '农', '羺', '奴',
'奻', '疟', '黁', '郍', '喔', '讴',
'妑', '拍', '眅', '乓', '抛', '呸',
'喷', '匉', '丕', '囨', '剽', '氕',
'姘', '乒', '钋', '剖', '仆', '七',
'掐', '千', '呛', '悄', '癿', '亲',
'狅', '芎', '丘', '区', '峑', '缺',
'夋', '呥', '穣', '娆', '惹', '人',
'扔', '日', '茸', '厹', '邚', '挼',
'堧', '婑', '瞤', '捼', '仨', '毢',
'三', '桒', '掻', '閪', '森', '僧',
'杀', '筛', '山', '伤', '弰', '奢',
'申', '莘', '敒', '升', '尸', '収',
'书', '刷', '衰', '闩', '双', '谁',
'吮', '说', '厶', '忪', '捜', '苏',
'狻', '夊', '孙', '唆', '他', '囼',
'坍', '汤', '夲', '忑', '熥', '剔',
'天', '旫', '帖', '厅', '囲', '偷',
'凸', '湍', '推', '吞', '乇', '穵',
'歪', '弯', '尣', '危', '昷', '翁',
'挝', '乌', '夕', '虲', '仚', '乡',
'灱', '些', '心', '星', '凶', '休',
'吁', '吅', '削', '坃', '丫', '恹',
'央', '幺', '倻', '一', '囙', '应',
'哟', '佣', '优', '扜', '囦', '曰',
'晕', '筠', '筼', '帀', '災', '兂',
'匨', '傮', '则', '贼', '怎', '増',
'扎', '捚', '沾', '张', '长', '長',
'佋', '蜇', '贞', '争', '之', '峙',
'庢', '中', '州', '朱', '抓', '拽',
'专', '妆', '隹', '宒', '卓', '乲',
'宗', '邹', '租', '钻', '厜', '尊',
'昨', '兙', '鿃', '鿄'};
/**
* Pinyin array.
* <p>
* Each pinyin is corresponding to unihans of same
* offset in the unihans array.
*/
public static final byte[][] PINYINS = {
{65, 0, 0, 0, 0, 0}, {65, 73, 0, 0, 0, 0},
{65, 78, 0, 0, 0, 0}, {65, 78, 71, 0, 0, 0},
{65, 79, 0, 0, 0, 0}, {66, 65, 0, 0, 0, 0},
{66, 65, 73, 0, 0, 0}, {66, 65, 78, 0, 0, 0},
{66, 65, 78, 71, 0, 0}, {66, 65, 79, 0, 0, 0},
{66, 69, 73, 0, 0, 0}, {66, 69, 78, 0, 0, 0},
{66, 69, 78, 71, 0, 0}, {66, 73, 0, 0, 0, 0},
{66, 73, 65, 78, 0, 0}, {66, 73, 65, 79, 0, 0},
{66, 73, 69, 0, 0, 0}, {66, 73, 78, 0, 0, 0},
{66, 73, 78, 71, 0, 0}, {66, 79, 0, 0, 0, 0},
{66, 85, 0, 0, 0, 0}, {67, 65, 0, 0, 0, 0},
{67, 65, 73, 0, 0, 0}, {67, 65, 78, 0, 0, 0},
{67, 65, 78, 71, 0, 0}, {67, 65, 79, 0, 0, 0},
{67, 69, 0, 0, 0, 0}, {67, 69, 78, 0, 0, 0},
{67, 69, 78, 71, 0, 0}, {90, 69, 78, 71, 0, 0},
{67, 69, 78, 71, 0, 0}, {67, 72, 65, 0, 0, 0},
{67, 72, 65, 73, 0, 0}, {67, 72, 65, 78, 0, 0},
{67, 72, 65, 78, 71, 0}, {67, 72, 65, 79, 0, 0},
{67, 72, 69, 0, 0, 0}, {67, 72, 69, 78, 0, 0},
{83, 72, 69, 78, 0, 0}, {67, 72, 69, 78, 0, 0},
{67, 72, 69, 78, 71, 0}, {67, 72, 73, 0, 0, 0},
{67, 72, 79, 78, 71, 0}, {67, 72, 79, 85, 0, 0},
{67, 72, 85, 0, 0, 0}, {67, 72, 85, 65, 0, 0},
{67, 72, 85, 65, 73, 0}, {67, 72, 85, 65, 78, 0},
{67, 72, 85, 65, 78, 71}, {67, 72, 85, 73, 0, 0},
{67, 72, 85, 78, 0, 0}, {67, 72, 85, 79, 0, 0},
{67, 73, 0, 0, 0, 0}, {67, 79, 78, 71, 0, 0},
{67, 79, 85, 0, 0, 0}, {67, 85, 0, 0, 0, 0},
{67, 85, 65, 78, 0, 0}, {67, 85, 73, 0, 0, 0},
{67, 85, 78, 0, 0, 0}, {67, 85, 79, 0, 0, 0},
{68, 65, 0, 0, 0, 0}, {68, 65, 73, 0, 0, 0},
{68, 65, 78, 0, 0, 0}, {68, 65, 78, 71, 0, 0},
{68, 65, 79, 0, 0, 0}, {68, 69, 0, 0, 0, 0},
{68, 69, 78, 0, 0, 0}, {68, 69, 78, 71, 0, 0},
{68, 73, 0, 0, 0, 0}, {68, 73, 65, 0, 0, 0},
{68, 73, 65, 78, 0, 0}, {68, 73, 65, 79, 0, 0},
{68, 73, 69, 0, 0, 0}, {68, 73, 78, 71, 0, 0},
{68, 73, 85, 0, 0, 0}, {68, 79, 78, 71, 0, 0},
{68, 79, 85, 0, 0, 0}, {68, 85, 0, 0, 0, 0},
{68, 85, 65, 78, 0, 0}, {68, 85, 73, 0, 0, 0},
{68, 85, 78, 0, 0, 0}, {68, 85, 79, 0, 0, 0},
{69, 0, 0, 0, 0, 0}, {69, 73, 0, 0, 0, 0},
{69, 78, 0, 0, 0, 0}, {69, 78, 71, 0, 0, 0},
{69, 82, 0, 0, 0, 0}, {70, 65, 0, 0, 0, 0},
{70, 65, 78, 0, 0, 0}, {70, 65, 78, 71, 0, 0},
{70, 69, 73, 0, 0, 0}, {70, 69, 78, 0, 0, 0},
{70, 69, 78, 71, 0, 0}, {70, 73, 65, 79, 0, 0},
{70, 79, 0, 0, 0, 0}, {70, 79, 85, 0, 0, 0},
{70, 85, 0, 0, 0, 0}, {71, 65, 0, 0, 0, 0},
{71, 65, 73, 0, 0, 0}, {71, 65, 78, 0, 0, 0},
{71, 65, 78, 71, 0, 0}, {71, 65, 79, 0, 0, 0},
{71, 69, 0, 0, 0, 0}, {71, 69, 73, 0, 0, 0},
{71, 69, 78, 0, 0, 0}, {71, 69, 78, 71, 0, 0},
{71, 79, 78, 71, 0, 0}, {71, 79, 85, 0, 0, 0},
{71, 85, 0, 0, 0, 0}, {71, 85, 65, 0, 0, 0},
{71, 85, 65, 73, 0, 0}, {71, 85, 65, 78, 0, 0},
{71, 85, 65, 78, 71, 0}, {71, 85, 73, 0, 0, 0},
{71, 85, 78, 0, 0, 0}, {71, 85, 79, 0, 0, 0},
{72, 65, 0, 0, 0, 0}, {72, 65, 73, 0, 0, 0},
{72, 65, 78, 0, 0, 0}, {72, 65, 78, 71, 0, 0},
{72, 65, 79, 0, 0, 0}, {72, 69, 0, 0, 0, 0},
{72, 69, 73, 0, 0, 0}, {72, 69, 78, 0, 0, 0},
{72, 69, 78, 71, 0, 0}, {72, 77, 0, 0, 0, 0},
{72, 79, 78, 71, 0, 0}, {72, 79, 85, 0, 0, 0},
{72, 85, 0, 0, 0, 0}, {72, 85, 65, 0, 0, 0},
{72, 85, 65, 73, 0, 0}, {72, 85, 65, 78, 0, 0},
{72, 85, 65, 78, 71, 0}, {72, 85, 73, 0, 0, 0},
{72, 85, 78, 0, 0, 0}, {72, 85, 79, 0, 0, 0},
{74, 73, 0, 0, 0, 0}, {74, 73, 65, 0, 0, 0},
{74, 73, 65, 78, 0, 0}, {74, 73, 65, 78, 71, 0},
{74, 73, 65, 79, 0, 0}, {74, 73, 69, 0, 0, 0},
{74, 73, 78, 0, 0, 0}, {74, 73, 78, 71, 0, 0},
{74, 73, 79, 78, 71, 0}, {74, 73, 85, 0, 0, 0},
{74, 85, 0, 0, 0, 0}, {74, 85, 65, 78, 0, 0},
{74, 85, 69, 0, 0, 0}, {74, 85, 78, 0, 0, 0},
{75, 65, 0, 0, 0, 0}, {75, 65, 73, 0, 0, 0},
{75, 65, 78, 0, 0, 0}, {75, 65, 78, 71, 0, 0},
{75, 65, 79, 0, 0, 0}, {75, 69, 0, 0, 0, 0},
{75, 69, 78, 0, 0, 0}, {75, 69, 78, 71, 0, 0},
{75, 79, 78, 71, 0, 0}, {75, 79, 85, 0, 0, 0},
{75, 85, 0, 0, 0, 0}, {75, 85, 65, 0, 0, 0},
{75, 85, 65, 73, 0, 0}, {75, 85, 65, 78, 0, 0},
{75, 85, 65, 78, 71, 0}, {75, 85, 73, 0, 0, 0},
{75, 85, 78, 0, 0, 0}, {75, 85, 79, 0, 0, 0},
{76, 65, 0, 0, 0, 0}, {76, 65, 73, 0, 0, 0},
{76, 65, 78, 0, 0, 0}, {76, 65, 78, 71, 0, 0},
{76, 65, 79, 0, 0, 0}, {76, 69, 0, 0, 0, 0},
{76, 69, 73, 0, 0, 0}, {76, 69, 78, 71, 0, 0},
{76, 73, 0, 0, 0, 0}, {76, 73, 65, 0, 0, 0},
{76, 73, 65, 78, 0, 0}, {76, 73, 65, 78, 71, 0},
{76, 73, 65, 79, 0, 0}, {76, 73, 69, 0, 0, 0},
{76, 73, 78, 0, 0, 0}, {76, 73, 78, 71, 0, 0},
{76, 73, 85, 0, 0, 0}, {76, 79, 0, 0, 0, 0},
{76, 79, 78, 71, 0, 0}, {76, 79, 85, 0, 0, 0},
{76, 85, 0, 0, 0, 0}, {76, 85, 65, 78, 0, 0},
{76, 85, 69, 0, 0, 0}, {76, 85, 78, 0, 0, 0},
{76, 85, 79, 0, 0, 0}, {77, 0, 0, 0, 0, 0},
{77, 65, 0, 0, 0, 0}, {77, 65, 73, 0, 0, 0},
{77, 65, 78, 0, 0, 0}, {77, 65, 78, 71, 0, 0},
{77, 65, 79, 0, 0, 0}, {77, 69, 0, 0, 0, 0},
{77, 69, 73, 0, 0, 0}, {77, 69, 78, 0, 0, 0},
{77, 69, 78, 71, 0, 0}, {77, 73, 0, 0, 0, 0},
{77, 73, 65, 78, 0, 0}, {77, 73, 65, 79, 0, 0},
{77, 73, 69, 0, 0, 0}, {77, 73, 78, 0, 0, 0},
{77, 73, 78, 71, 0, 0}, {77, 73, 85, 0, 0, 0},
{77, 79, 0, 0, 0, 0}, {77, 79, 85, 0, 0, 0},
{77, 85, 0, 0, 0, 0}, {78, 0, 0, 0, 0, 0},
{78, 65, 0, 0, 0, 0}, {78, 65, 73, 0, 0, 0},
{78, 65, 78, 0, 0, 0}, {78, 65, 78, 71, 0, 0},
{78, 65, 79, 0, 0, 0}, {78, 69, 0, 0, 0, 0},
{78, 69, 73, 0, 0, 0}, {78, 69, 78, 0, 0, 0},
{78, 69, 78, 71, 0, 0}, {78, 73, 0, 0, 0, 0},
{78, 73, 65, 78, 0, 0}, {78, 73, 65, 78, 71, 0},
{78, 73, 65, 79, 0, 0}, {78, 73, 69, 0, 0, 0},
{78, 73, 78, 0, 0, 0}, {78, 73, 78, 71, 0, 0},
{78, 73, 85, 0, 0, 0}, {78, 79, 78, 71, 0, 0},
{78, 79, 85, 0, 0, 0}, {78, 85, 0, 0, 0, 0},
{78, 85, 65, 78, 0, 0}, {78, 85, 69, 0, 0, 0},
{78, 85, 78, 0, 0, 0}, {78, 85, 79, 0, 0, 0},
{79, 0, 0, 0, 0, 0}, {79, 85, 0, 0, 0, 0},
{80, 65, 0, 0, 0, 0}, {80, 65, 73, 0, 0, 0},
{80, 65, 78, 0, 0, 0}, {80, 65, 78, 71, 0, 0},
{80, 65, 79, 0, 0, 0}, {80, 69, 73, 0, 0, 0},
{80, 69, 78, 0, 0, 0}, {80, 69, 78, 71, 0, 0},
{80, 73, 0, 0, 0, 0}, {80, 73, 65, 78, 0, 0},
{80, 73, 65, 79, 0, 0}, {80, 73, 69, 0, 0, 0},
{80, 73, 78, 0, 0, 0}, {80, 73, 78, 71, 0, 0},
{80, 79, 0, 0, 0, 0}, {80, 79, 85, 0, 0, 0},
{80, 85, 0, 0, 0, 0}, {81, 73, 0, 0, 0, 0},
{81, 73, 65, 0, 0, 0}, {81, 73, 65, 78, 0, 0},
{81, 73, 65, 78, 71, 0}, {81, 73, 65, 79, 0, 0},
{81, 73, 69, 0, 0, 0}, {81, 73, 78, 0, 0, 0},
{81, 73, 78, 71, 0, 0}, {81, 73, 79, 78, 71, 0},
{81, 73, 85, 0, 0, 0}, {81, 85, 0, 0, 0, 0},
{81, 85, 65, 78, 0, 0}, {81, 85, 69, 0, 0, 0},
{81, 85, 78, 0, 0, 0}, {82, 65, 78, 0, 0, 0},
{82, 65, 78, 71, 0, 0}, {82, 65, 79, 0, 0, 0},
{82, 69, 0, 0, 0, 0}, {82, 69, 78, 0, 0, 0},
{82, 69, 78, 71, 0, 0}, {82, 73, 0, 0, 0, 0},
{82, 79, 78, 71, 0, 0}, {82, 79, 85, 0, 0, 0},
{82, 85, 0, 0, 0, 0}, {82, 85, 65, 0, 0, 0},
{82, 85, 65, 78, 0, 0}, {82, 85, 73, 0, 0, 0},
{82, 85, 78, 0, 0, 0}, {82, 85, 79, 0, 0, 0},
{83, 65, 0, 0, 0, 0}, {83, 65, 73, 0, 0, 0},
{83, 65, 78, 0, 0, 0}, {83, 65, 78, 71, 0, 0},
{83, 65, 79, 0, 0, 0}, {83, 69, 0, 0, 0, 0},
{83, 69, 78, 0, 0, 0}, {83, 69, 78, 71, 0, 0},
{83, 72, 65, 0, 0, 0}, {83, 72, 65, 73, 0, 0},
{83, 72, 65, 78, 0, 0}, {83, 72, 65, 78, 71, 0},
{83, 72, 65, 79, 0, 0}, {83, 72, 69, 0, 0, 0},
{83, 72, 69, 78, 0, 0}, {88, 73, 78, 0, 0, 0},
{83, 72, 69, 78, 0, 0}, {83, 72, 69, 78, 71, 0},
{83, 72, 73, 0, 0, 0}, {83, 72, 79, 85, 0, 0},
{83, 72, 85, 0, 0, 0}, {83, 72, 85, 65, 0, 0},
{83, 72, 85, 65, 73, 0}, {83, 72, 85, 65, 78, 0},
{83, 72, 85, 65, 78, 71}, {83, 72, 85, 73, 0, 0},
{83, 72, 85, 78, 0, 0}, {83, 72, 85, 79, 0, 0},
{83, 73, 0, 0, 0, 0}, {83, 79, 78, 71, 0, 0},
{83, 79, 85, 0, 0, 0}, {83, 85, 0, 0, 0, 0},
{83, 85, 65, 78, 0, 0}, {83, 85, 73, 0, 0, 0},
{83, 85, 78, 0, 0, 0}, {83, 85, 79, 0, 0, 0},
{84, 65, 0, 0, 0, 0}, {84, 65, 73, 0, 0, 0},
{84, 65, 78, 0, 0, 0}, {84, 65, 78, 71, 0, 0},
{84, 65, 79, 0, 0, 0}, {84, 69, 0, 0, 0, 0},
{84, 69, 78, 71, 0, 0}, {84, 73, 0, 0, 0, 0},
{84, 73, 65, 78, 0, 0}, {84, 73, 65, 79, 0, 0},
{84, 73, 69, 0, 0, 0}, {84, 73, 78, 71, 0, 0},
{84, 79, 78, 71, 0, 0}, {84, 79, 85, 0, 0, 0},
{84, 85, 0, 0, 0, 0}, {84, 85, 65, 78, 0, 0},
{84, 85, 73, 0, 0, 0}, {84, 85, 78, 0, 0, 0},
{84, 85, 79, 0, 0, 0}, {87, 65, 0, 0, 0, 0},
{87, 65, 73, 0, 0, 0}, {87, 65, 78, 0, 0, 0},
{87, 65, 78, 71, 0, 0}, {87, 69, 73, 0, 0, 0},
{87, 69, 78, 0, 0, 0}, {87, 69, 78, 71, 0, 0},
{87, 79, 0, 0, 0, 0}, {87, 85, 0, 0, 0, 0},
{88, 73, 0, 0, 0, 0}, {88, 73, 65, 0, 0, 0},
{88, 73, 65, 78, 0, 0}, {88, 73, 65, 78, 71, 0},
{88, 73, 65, 79, 0, 0}, {88, 73, 69, 0, 0, 0},
{88, 73, 78, 0, 0, 0}, {88, 73, 78, 71, 0, 0},
{88, 73, 79, 78, 71, 0}, {88, 73, 85, 0, 0, 0},
{88, 85, 0, 0, 0, 0}, {88, 85, 65, 78, 0, 0},
{88, 85, 69, 0, 0, 0}, {88, 85, 78, 0, 0, 0},
{89, 65, 0, 0, 0, 0}, {89, 65, 78, 0, 0, 0},
{89, 65, 78, 71, 0, 0}, {89, 65, 79, 0, 0, 0},
{89, 69, 0, 0, 0, 0}, {89, 73, 0, 0, 0, 0},
{89, 73, 78, 0, 0, 0}, {89, 73, 78, 71, 0, 0},
{89, 79, 0, 0, 0, 0}, {89, 79, 78, 71, 0, 0},
{89, 79, 85, 0, 0, 0}, {89, 85, 0, 0, 0, 0},
{89, 85, 65, 78, 0, 0}, {89, 85, 69, 0, 0, 0},
{89, 85, 78, 0, 0, 0}, {74, 85, 78, 0, 0, 0},
{89, 85, 78, 0, 0, 0}, {90, 65, 0, 0, 0, 0},
{90, 65, 73, 0, 0, 0}, {90, 65, 78, 0, 0, 0},
{90, 65, 78, 71, 0, 0}, {90, 65, 79, 0, 0, 0},
{90, 69, 0, 0, 0, 0}, {90, 69, 73, 0, 0, 0},
{90, 69, 78, 0, 0, 0}, {90, 69, 78, 71, 0, 0},
{90, 72, 65, 0, 0, 0}, {90, 72, 65, 73, 0, 0},
{90, 72, 65, 78, 0, 0}, {90, 72, 65, 78, 71, 0},
{67, 72, 65, 78, 71, 0}, {90, 72, 65, 78, 71, 0},
{90, 72, 65, 79, 0, 0}, {90, 72, 69, 0, 0, 0},
{90, 72, 69, 78, 0, 0}, {90, 72, 69, 78, 71, 0},
{90, 72, 73, 0, 0, 0}, {83, 72, 73, 0, 0, 0},
{90, 72, 73, 0, 0, 0}, {90, 72, 79, 78, 71, 0},
{90, 72, 79, 85, 0, 0}, {90, 72, 85, 0, 0, 0},
{90, 72, 85, 65, 0, 0}, {90, 72, 85, 65, 73, 0},
{90, 72, 85, 65, 78, 0}, {90, 72, 85, 65, 78, 71},
{90, 72, 85, 73, 0, 0}, {90, 72, 85, 78, 0, 0},
{90, 72, 85, 79, 0, 0}, {90, 73, 0, 0, 0, 0},
{90, 79, 78, 71, 0, 0}, {90, 79, 85, 0, 0, 0},
{90, 85, 0, 0, 0, 0}, {90, 85, 65, 78, 0, 0},
{90, 85, 73, 0, 0, 0}, {90, 85, 78, 0, 0, 0},
{90, 85, 79, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
{83, 72, 65, 78, 0, 0}, {0, 0, 0, 0, 0, 0}};
/**
* First and last Chinese character with known Pinyin according to zh collation
*/
private static final String FIRST_PINYIN_UNIHAN = "";
private static final String LAST_PINYIN_UNIHAN = "鿿";
private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA);
private static HanziToPinyin sInstance;
public static class Token {
/**
* Separator between target string for each source char
*/
public static final String SEPARATOR = " ";
public static final int LATIN = 1;
public static final int PINYIN = 2;
public static final int UNKNOWN = 3;
public Token() {
}
public Token(int type, String source, String target) {
this.type = type;
this.source = source;
this.target = target;
}
/**
* Type of this token, ASCII, PINYIN or UNKNOWN.
*/
public int type;
/**
* Original string before translation.
*/
public String source;
/**
* Translated string of source. For Han, target is corresponding Pinyin. Otherwise target is
* original string in source.
*/
public String target;
}
public static HanziToPinyin getInstance() {
synchronized (HanziToPinyin.class) {
if (sInstance != null) {
return sInstance;
}
// Check if zh_CN collation data is available
final Locale[] locale = Collator.getAvailableLocales();
for (Locale value : locale) {
if (value.equals(Locale.CHINA) || value.getLanguage().contains("zh")) {
// Do self validation just once.
if (DEBUG) {
Log.d(TAG, "Self validation. Result: " + doSelfValidation());
}
sInstance = new HanziToPinyin(true);
return sInstance;
}
}
if (sInstance == null) {//这个判断是用于处理国产ROM的兼容性问题
if (Locale.CHINA.equals(Locale.getDefault())) {
sInstance = new HanziToPinyin(true);
return sInstance;
}
}
Log.w(TAG, "There is no Chinese collator, HanziToPinyin is disabled");
sInstance = new HanziToPinyin(false);
return sInstance;
}
}
/**
* Validate if our internal table has some wrong value.
*
* @return true when the table looks correct.
*/
private static boolean doSelfValidation() {
char lastChar = UNIHANS[0];
String lastString = Character.toString(lastChar);
for (char c : UNIHANS) {
if (lastChar == c) {
continue;
}
final String curString = Character.toString(c);
int cmp = COLLATOR.compare(lastString, curString);
if (cmp >= 0) {
Log.e(TAG, "Internal error in Unihan table. " + "The last string \"" + lastString
+ "\" is greater than current string \"" + curString + "\".");
return false;
}
lastString = curString;
}
return true;
}
private Token getToken(char character) {
Token token = new Token();
final String letter = Character.toString(character);
token.source = letter;
int offset = -1;
int cmp;
if (character < 256) {
token.type = Token.LATIN;
token.target = letter;
return token;
} else {
cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN);
if (cmp < 0) {
token.type = Token.UNKNOWN;
token.target = letter;
return token;
} else if (cmp == 0) {
token.type = Token.PINYIN;
offset = 0;
} else {
cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN);
if (cmp > 0) {
token.type = Token.UNKNOWN;
token.target = letter;
return token;
} else if (cmp == 0) {
token.type = Token.PINYIN;
offset = UNIHANS.length - 1;
}
}
}
token.type = Token.PINYIN;
if (offset < 0) {
int begin = 0;
int end = UNIHANS.length - 1;
while (begin <= end) {
offset = (begin + end) / 2;
final String unihan = Character.toString(UNIHANS[offset]);
cmp = COLLATOR.compare(letter, unihan);
if (cmp == 0) {
break;
} else if (cmp > 0) {
begin = offset + 1;
} else {
end = offset - 1;
}
}
}
if (cmp < 0) {
offset--;
}
StringBuilder pinyin = new StringBuilder();
for (int j = 0; j < PINYINS[offset].length && PINYINS[offset][j] != 0; j++) {
pinyin.append((char) PINYINS[offset][j]);
}
token.target = pinyin.toString();
if (TextUtils.isEmpty(token.target)) {
token.type = Token.UNKNOWN;
token.target = token.source;
}
return token;
}
/**
* Convert the input to a array of tokens. The sequence of ASCII or Unknown characters without
* space will be put into a Token, One Hanzi character which has pinyin will be treated as a
* Token. If these is no China collator, the empty token array is returned.
*/
public ArrayList<Token> get(final String input) {
ArrayList<Token> tokens = new ArrayList<>();
if (!mHasChinaCollator || TextUtils.isEmpty(input)) {
// return empty tokens.
return tokens;
}
final int inputLength = input.length();
final StringBuilder sb = new StringBuilder();
int tokenType = Token.LATIN;
// Go through the input, create a new token when
// a. Token type changed
// b. Get the Pinyin of current charater.
// c. current character is space.
for (int i = 0; i < inputLength; i++) {
final char character = input.charAt(i);
if (character == ' ') {
if (sb.length() > 0) {
addToken(sb, tokens, tokenType);
}
} else if (character < 256) {
if (tokenType != Token.LATIN && sb.length() > 0) {
addToken(sb, tokens, tokenType);
}
tokenType = Token.LATIN;
sb.append(character);
} else {
Token t = getToken(character);
if (t.type == Token.PINYIN) {
if (sb.length() > 0) {
addToken(sb, tokens, tokenType);
}
tokens.add(t);
tokenType = Token.PINYIN;
} else {
if (tokenType != t.type && sb.length() > 0) {
addToken(sb, tokens, tokenType);
}
tokenType = t.type;
sb.append(character);
}
}
}
if (sb.length() > 0) {
addToken(sb, tokens, tokenType);
}
return tokens;
}
private void addToken(
final StringBuilder sb, final ArrayList<Token> tokens, final int tokenType) {
String str = sb.toString();
tokens.add(new Token(tokenType, str, str));
sb.setLength(0);
}
public String toPinyinString(String string) {
if (string == null) {
return null;
}
StringBuilder sb = new StringBuilder();
ArrayList<Token> tokens = get(string);
for (Token token : tokens) {
sb.append(token.target);
}
return sb.toString().toLowerCase();
}
}

View file

@ -1,522 +0,0 @@
package com.sukisu.ultra.ui.util
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.text.TextUtils
import android.util.Log
import java.text.Collator
import java.util.Locale
class HanziToPinyin private constructor(val hasChinaCollator: Boolean) {
class Token(
var type: Int = 0,
var source: String = "",
var target: String = ""
) {
companion object {
const val LATIN = 1
const val PINYIN = 2
const val UNKNOWN = 3
}
}
private fun getToken(character: Char): Token {
val token = Token()
val letter = character.toString()
token.source = letter
var offset = -1
var cmp: Int
if (character < 256.toChar()) {
token.type = Token.LATIN
token.target = letter
return token
} else {
cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN)
if (cmp < 0) {
token.type = Token.UNKNOWN
token.target = letter
return token
} else if (cmp == 0) {
token.type = Token.PINYIN
offset = 0
} else {
cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN)
if (cmp > 0) {
token.type = Token.UNKNOWN
token.target = letter
return token
} else if (cmp == 0) {
token.type = Token.PINYIN
offset = UNIHANS.size - 1
}
}
}
token.type = Token.PINYIN
if (offset < 0) {
var begin = 0
var end = UNIHANS.size - 1
while (begin <= end) {
offset = (begin + end) / 2
val unihan = UNIHANS[offset].toString()
cmp = COLLATOR.compare(letter, unihan)
when {
cmp == 0 -> break
cmp > 0 -> begin = offset + 1
else -> end = offset - 1
}
}
}
if (cmp < 0) {
offset--
}
val pinyin = StringBuilder()
for (j in PINYINS[offset].indices) {
if (PINYINS[offset][j] == 0.toByte()) break
pinyin.append(PINYINS[offset][j].toInt().toChar())
}
token.target = pinyin.toString()
if (TextUtils.isEmpty(token.target)) {
token.type = Token.UNKNOWN
token.target = token.source
}
return token
}
fun get(input: String?): ArrayList<Token> {
val tokens = ArrayList<Token>()
if (!hasChinaCollator || TextUtils.isEmpty(input)) {
return tokens
}
val inputLength = input!!.length
val sb = StringBuilder()
var tokenType = Token.LATIN
for (i in 0 until inputLength) {
val character = input[i]
when {
character == ' ' -> {
if (sb.isNotEmpty()) {
addToken(sb, tokens, tokenType)
}
}
character < 256.toChar() -> {
if (tokenType != Token.LATIN && sb.isNotEmpty()) {
addToken(sb, tokens, tokenType)
}
tokenType = Token.LATIN
sb.append(character)
}
else -> {
val t = getToken(character)
if (t.type == Token.PINYIN) {
if (sb.isNotEmpty()) {
addToken(sb, tokens, tokenType)
}
tokens.add(t)
tokenType = Token.PINYIN
} else {
if (tokenType != t.type && sb.isNotEmpty()) {
addToken(sb, tokens, tokenType)
}
tokenType = t.type
sb.append(character)
}
}
}
}
if (sb.isNotEmpty()) {
addToken(sb, tokens, tokenType)
}
return tokens
}
private fun addToken(sb: StringBuilder, tokens: ArrayList<Token>, tokenType: Int) {
val str = sb.toString()
tokens.add(Token(tokenType, str, str))
sb.setLength(0)
}
fun toPinyinString(string: String?): String? {
if (string == null) {
return null
}
val sb = StringBuilder()
val tokens = get(string)
for (token in tokens) {
sb.append(token.target)
}
return sb.toString().lowercase()
}
companion object {
private const val TAG = "HanziToPinyin"
private const val DEBUG = false
val UNIHANS = charArrayOf(
'阿', '哎', '安', '肮', '凹', '八',
'挀', '扳', '邦', '勹', '陂', '奔',
'伻', '屄', '边', '灬', '憋', '汃',
'冫', '癶', '峬', '嚓', '偲', '参',
'仓', '撡', '冊', '嵾', '曽', '曾',
'層', '叉', '芆', '辿', '伥', '抄',
'车', '抻', '沈', '沉', '阷', '吃',
'充', '抽', '出', '欻', '揣', '巛',
'刅', '吹', '旾', '逴', '呲', '匆',
'凑', '粗', '汆', '崔', '邨', '搓',
'咑', '呆', '丹', '当', '刀', '嘚',
'扥', '灯', '氐', '嗲', '甸', '刁',
'爹', '丁', '丟', '东', '吺', '厾',
'耑', '襨', '吨', '多', '妸', '诶',
'奀', '鞥', '儿', '发', '帆', '匚',
'飞', '分', '丰', '覅', '仏', '紑',
'伕', '旮', '侅', '甘', '冈', '皋',
'戈', '给', '根', '刯', '工', '勾',
'估', '瓜', '乖', '关', '光', '归',
'丨', '呙', '哈', '咍', '佄', '夯',
'茠', '诃', '黒', '拫', '亨', '噷',
'叿', '齁', '乯', '花', '怀', '犿',
'巟', '灰', '昏', '吙', '丌', '加',
'戋', '江', '艽', '阶', '巾', '坕',
'冂', '丩', '凥', '姢', '噘', '军',
'咔', '开', '刊', '忼', '尻', '匼',
'肎', '劥', '空', '抠', '扝', '夸',
'蒯', '宽', '匡', '亏', '坤', '扩',
'垃', '来', '兰', '啷', '捞', '肋',
'勒', '崚', '刕', '俩', '奁', '良',
'撩', '列', '拎', '刢', '溜', '囖',
'龙', '瞜', '噜', '娈', '畧', '抡',
'罗', '呣', '妈', '埋', '嫚', '牤',
'猫', '么', '呅', '门', '甿', '咪',
'宀', '喵', '乜', '民', '名', '谬',
'摸', '哞', '毪', '嗯', '拏', '腉',
'囡', '囔', '孬', '疒', '娞', '恁',
'能', '妮', '拈', '嬢', '鸟', '捏',
'囜', '宁', '妞', '农', '羺', '奴',
'奻', '疟', '黁', '郍', '喔', '讴',
'妑', '拍', '眅', '乓', '抛', '呸',
'喷', '匉', '丕', '囨', '剽', '氕',
'姘', '乒', '钋', '剖', '仆', '七',
'掐', '千', '呛', '悄', '癿', '亲',
'狅', '芎', '丘', '区', '峑', '缺',
'夋', '呥', '穣', '娆', '惹', '人',
'扔', '日', '茸', '厹', '邚', '挼',
'堧', '婑', '瞤', '捼', '仨', '毢',
'三', '桒', '掻', '閪', '森', '僧',
'杀', '筛', '山', '伤', '弰', '奢',
'申', '莘', '敒', '升', '尸', '収',
'书', '刷', '衰', '闩', '双', '谁',
'吮', '说', '厶', '忪', '捜', '苏',
'狻', '夊', '孙', '唆', '他', '囼',
'坍', '汤', '夲', '忑', '熥', '剔',
'天', '旫', '帖', '厅', '囲', '偷',
'凸', '湍', '推', '吞', '乇', '穵',
'歪', '弯', '尣', '危', '昷', '翁',
'挝', '乌', '夕', '虲', '仚', '乡',
'灱', '些', '心', '星', '凶', '休',
'吁', '吅', '削', '坃', '丫', '恹',
'央', '幺', '倻', '一', '囙', '应',
'哟', '佣', '优', '扜', '囦', '曰',
'晕', '筠', '筼', '帀', '災', '兂',
'匨', '傮', '则', '贼', '怎', '増',
'扎', '捚', '沾', '张', '长', '長',
'佋', '蜇', '贞', '争', '之', '峙',
'庢', '中', '州', '朱', '抓', '拽',
'专', '妆', '隹', '宒', '卓', '乲',
'宗', '邹', '租', '钻', '厜', '尊',
'昨', '兙', '鿃', '鿄'
)
val PINYINS = arrayOf(
byteArrayOf(65, 0, 0, 0, 0, 0), byteArrayOf(65, 73, 0, 0, 0, 0),
byteArrayOf(65, 78, 0, 0, 0, 0), byteArrayOf(65, 78, 71, 0, 0, 0),
byteArrayOf(65, 79, 0, 0, 0, 0), byteArrayOf(66, 65, 0, 0, 0, 0),
byteArrayOf(66, 65, 73, 0, 0, 0), byteArrayOf(66, 65, 78, 0, 0, 0),
byteArrayOf(66, 65, 78, 71, 0, 0), byteArrayOf(66, 65, 79, 0, 0, 0),
byteArrayOf(66, 69, 73, 0, 0, 0), byteArrayOf(66, 69, 78, 0, 0, 0),
byteArrayOf(66, 69, 78, 71, 0, 0), byteArrayOf(66, 73, 0, 0, 0, 0),
byteArrayOf(66, 73, 65, 78, 0, 0), byteArrayOf(66, 73, 65, 79, 0, 0),
byteArrayOf(66, 73, 69, 0, 0, 0), byteArrayOf(66, 73, 78, 0, 0, 0),
byteArrayOf(66, 73, 78, 71, 0, 0), byteArrayOf(66, 79, 0, 0, 0, 0),
byteArrayOf(66, 85, 0, 0, 0, 0), byteArrayOf(67, 65, 0, 0, 0, 0),
byteArrayOf(67, 65, 73, 0, 0, 0), byteArrayOf(67, 65, 78, 0, 0, 0),
byteArrayOf(67, 65, 78, 71, 0, 0), byteArrayOf(67, 65, 79, 0, 0, 0),
byteArrayOf(67, 69, 0, 0, 0, 0), byteArrayOf(67, 69, 78, 0, 0, 0),
byteArrayOf(67, 69, 78, 71, 0, 0), byteArrayOf(90, 69, 78, 71, 0, 0),
byteArrayOf(67, 69, 78, 71, 0, 0), byteArrayOf(67, 72, 65, 0, 0, 0),
byteArrayOf(67, 72, 65, 73, 0, 0), byteArrayOf(67, 72, 65, 78, 0, 0),
byteArrayOf(67, 72, 65, 78, 71, 0), byteArrayOf(67, 72, 65, 79, 0, 0),
byteArrayOf(67, 72, 69, 0, 0, 0), byteArrayOf(67, 72, 69, 78, 0, 0),
byteArrayOf(83, 72, 69, 78, 0, 0), byteArrayOf(67, 72, 69, 78, 0, 0),
byteArrayOf(67, 72, 69, 78, 71, 0), byteArrayOf(67, 72, 73, 0, 0, 0),
byteArrayOf(67, 72, 79, 78, 71, 0), byteArrayOf(67, 72, 79, 85, 0, 0),
byteArrayOf(67, 72, 85, 0, 0, 0), byteArrayOf(67, 72, 85, 65, 0, 0),
byteArrayOf(67, 72, 85, 65, 73, 0), byteArrayOf(67, 72, 85, 65, 78, 0),
byteArrayOf(67, 72, 85, 65, 78, 71), byteArrayOf(67, 72, 85, 73, 0, 0),
byteArrayOf(67, 72, 85, 78, 0, 0), byteArrayOf(67, 72, 85, 79, 0, 0),
byteArrayOf(67, 73, 0, 0, 0, 0), byteArrayOf(67, 79, 78, 71, 0, 0),
byteArrayOf(67, 79, 85, 0, 0, 0), byteArrayOf(67, 85, 0, 0, 0, 0),
byteArrayOf(67, 85, 65, 78, 0, 0), byteArrayOf(67, 85, 73, 0, 0, 0),
byteArrayOf(67, 85, 78, 0, 0, 0), byteArrayOf(67, 85, 79, 0, 0, 0),
byteArrayOf(68, 65, 0, 0, 0, 0), byteArrayOf(68, 65, 73, 0, 0, 0),
byteArrayOf(68, 65, 78, 0, 0, 0), byteArrayOf(68, 65, 78, 71, 0, 0),
byteArrayOf(68, 65, 79, 0, 0, 0), byteArrayOf(68, 69, 0, 0, 0, 0),
byteArrayOf(68, 69, 78, 0, 0, 0), byteArrayOf(68, 69, 78, 71, 0, 0),
byteArrayOf(68, 73, 0, 0, 0, 0), byteArrayOf(68, 73, 65, 0, 0, 0),
byteArrayOf(68, 73, 65, 78, 0, 0), byteArrayOf(68, 73, 65, 79, 0, 0),
byteArrayOf(68, 73, 69, 0, 0, 0), byteArrayOf(68, 73, 78, 71, 0, 0),
byteArrayOf(68, 73, 85, 0, 0, 0), byteArrayOf(68, 79, 78, 71, 0, 0),
byteArrayOf(68, 79, 85, 0, 0, 0), byteArrayOf(68, 85, 0, 0, 0, 0),
byteArrayOf(68, 85, 65, 78, 0, 0), byteArrayOf(68, 85, 73, 0, 0, 0),
byteArrayOf(68, 85, 78, 0, 0, 0), byteArrayOf(68, 85, 79, 0, 0, 0),
byteArrayOf(69, 0, 0, 0, 0, 0), byteArrayOf(69, 73, 0, 0, 0, 0),
byteArrayOf(69, 78, 0, 0, 0, 0), byteArrayOf(69, 78, 71, 0, 0, 0),
byteArrayOf(69, 82, 0, 0, 0, 0), byteArrayOf(70, 65, 0, 0, 0, 0),
byteArrayOf(70, 65, 78, 0, 0, 0), byteArrayOf(70, 65, 78, 71, 0, 0),
byteArrayOf(70, 69, 73, 0, 0, 0), byteArrayOf(70, 69, 78, 0, 0, 0),
byteArrayOf(70, 69, 78, 71, 0, 0), byteArrayOf(70, 73, 65, 79, 0, 0),
byteArrayOf(70, 79, 0, 0, 0, 0), byteArrayOf(70, 79, 85, 0, 0, 0),
byteArrayOf(70, 85, 0, 0, 0, 0), byteArrayOf(71, 65, 0, 0, 0, 0),
byteArrayOf(71, 65, 73, 0, 0, 0), byteArrayOf(71, 65, 78, 0, 0, 0),
byteArrayOf(71, 65, 78, 71, 0, 0), byteArrayOf(71, 65, 79, 0, 0, 0),
byteArrayOf(71, 69, 0, 0, 0, 0), byteArrayOf(71, 69, 73, 0, 0, 0),
byteArrayOf(71, 69, 78, 0, 0, 0), byteArrayOf(71, 69, 78, 71, 0, 0),
byteArrayOf(71, 79, 78, 71, 0, 0), byteArrayOf(71, 79, 85, 0, 0, 0),
byteArrayOf(71, 85, 0, 0, 0, 0), byteArrayOf(71, 85, 65, 0, 0, 0),
byteArrayOf(71, 85, 65, 73, 0, 0), byteArrayOf(71, 85, 65, 78, 0, 0),
byteArrayOf(71, 85, 65, 78, 71, 0), byteArrayOf(71, 85, 73, 0, 0, 0),
byteArrayOf(71, 85, 78, 0, 0, 0), byteArrayOf(71, 85, 79, 0, 0, 0),
byteArrayOf(72, 65, 0, 0, 0, 0), byteArrayOf(72, 65, 73, 0, 0, 0),
byteArrayOf(72, 65, 78, 0, 0, 0), byteArrayOf(72, 65, 78, 71, 0, 0),
byteArrayOf(72, 65, 79, 0, 0, 0), byteArrayOf(72, 69, 0, 0, 0, 0),
byteArrayOf(72, 69, 73, 0, 0, 0), byteArrayOf(72, 69, 78, 0, 0, 0),
byteArrayOf(72, 69, 78, 71, 0, 0), byteArrayOf(72, 77, 0, 0, 0, 0),
byteArrayOf(72, 79, 78, 71, 0, 0), byteArrayOf(72, 79, 85, 0, 0, 0),
byteArrayOf(72, 85, 0, 0, 0, 0), byteArrayOf(72, 85, 65, 0, 0, 0),
byteArrayOf(72, 85, 65, 73, 0, 0), byteArrayOf(72, 85, 65, 78, 0, 0),
byteArrayOf(72, 85, 65, 78, 71, 0), byteArrayOf(72, 85, 73, 0, 0, 0),
byteArrayOf(72, 85, 78, 0, 0, 0), byteArrayOf(72, 85, 79, 0, 0, 0),
byteArrayOf(74, 73, 0, 0, 0, 0), byteArrayOf(74, 73, 65, 0, 0, 0),
byteArrayOf(74, 73, 65, 78, 0, 0), byteArrayOf(74, 73, 65, 78, 71, 0),
byteArrayOf(74, 73, 65, 79, 0, 0), byteArrayOf(74, 73, 69, 0, 0, 0),
byteArrayOf(74, 73, 78, 0, 0, 0), byteArrayOf(74, 73, 78, 71, 0, 0),
byteArrayOf(74, 73, 79, 78, 71, 0), byteArrayOf(74, 73, 85, 0, 0, 0),
byteArrayOf(74, 85, 0, 0, 0, 0), byteArrayOf(74, 85, 65, 78, 0, 0),
byteArrayOf(74, 85, 69, 0, 0, 0), byteArrayOf(74, 85, 78, 0, 0, 0),
byteArrayOf(75, 65, 0, 0, 0, 0), byteArrayOf(75, 65, 73, 0, 0, 0),
byteArrayOf(75, 65, 78, 0, 0, 0), byteArrayOf(75, 65, 78, 71, 0, 0),
byteArrayOf(75, 65, 79, 0, 0, 0), byteArrayOf(75, 69, 0, 0, 0, 0),
byteArrayOf(75, 69, 78, 0, 0, 0), byteArrayOf(75, 69, 78, 71, 0, 0),
byteArrayOf(75, 79, 78, 71, 0, 0), byteArrayOf(75, 79, 85, 0, 0, 0),
byteArrayOf(75, 85, 0, 0, 0, 0), byteArrayOf(75, 85, 65, 0, 0, 0),
byteArrayOf(75, 85, 65, 73, 0, 0), byteArrayOf(75, 85, 65, 78, 0, 0),
byteArrayOf(75, 85, 65, 78, 71, 0), byteArrayOf(75, 85, 73, 0, 0, 0),
byteArrayOf(75, 85, 78, 0, 0, 0), byteArrayOf(75, 85, 79, 0, 0, 0),
byteArrayOf(76, 65, 0, 0, 0, 0), byteArrayOf(76, 65, 73, 0, 0, 0),
byteArrayOf(76, 65, 78, 0, 0, 0), byteArrayOf(76, 65, 78, 71, 0, 0),
byteArrayOf(76, 65, 79, 0, 0, 0), byteArrayOf(76, 69, 0, 0, 0, 0),
byteArrayOf(76, 69, 73, 0, 0, 0), byteArrayOf(76, 69, 78, 71, 0, 0),
byteArrayOf(76, 73, 0, 0, 0, 0), byteArrayOf(76, 73, 65, 0, 0, 0),
byteArrayOf(76, 73, 65, 78, 0, 0), byteArrayOf(76, 73, 65, 78, 71, 0),
byteArrayOf(76, 73, 65, 79, 0, 0), byteArrayOf(76, 73, 69, 0, 0, 0),
byteArrayOf(76, 73, 78, 0, 0, 0), byteArrayOf(76, 73, 78, 71, 0, 0),
byteArrayOf(76, 73, 85, 0, 0, 0), byteArrayOf(76, 79, 0, 0, 0, 0),
byteArrayOf(76, 79, 78, 71, 0, 0), byteArrayOf(76, 79, 85, 0, 0, 0),
byteArrayOf(76, 85, 0, 0, 0, 0), byteArrayOf(76, 85, 65, 78, 0, 0),
byteArrayOf(76, 85, 69, 0, 0, 0), byteArrayOf(76, 85, 78, 0, 0, 0),
byteArrayOf(76, 85, 79, 0, 0, 0), byteArrayOf(77, 0, 0, 0, 0, 0),
byteArrayOf(77, 65, 0, 0, 0, 0), byteArrayOf(77, 65, 73, 0, 0, 0),
byteArrayOf(77, 65, 78, 0, 0, 0), byteArrayOf(77, 65, 78, 71, 0, 0),
byteArrayOf(77, 65, 79, 0, 0, 0), byteArrayOf(77, 69, 0, 0, 0, 0),
byteArrayOf(77, 69, 73, 0, 0, 0), byteArrayOf(77, 69, 78, 0, 0, 0),
byteArrayOf(77, 69, 78, 71, 0, 0), byteArrayOf(77, 73, 0, 0, 0, 0),
byteArrayOf(77, 73, 65, 78, 0, 0), byteArrayOf(77, 73, 65, 79, 0, 0),
byteArrayOf(77, 73, 69, 0, 0, 0), byteArrayOf(77, 73, 78, 0, 0, 0),
byteArrayOf(77, 73, 78, 71, 0, 0), byteArrayOf(77, 73, 85, 0, 0, 0),
byteArrayOf(77, 79, 0, 0, 0, 0), byteArrayOf(77, 79, 85, 0, 0, 0),
byteArrayOf(77, 85, 0, 0, 0, 0), byteArrayOf(78, 0, 0, 0, 0, 0),
byteArrayOf(78, 65, 0, 0, 0, 0), byteArrayOf(78, 65, 73, 0, 0, 0),
byteArrayOf(78, 65, 78, 0, 0, 0), byteArrayOf(78, 65, 78, 71, 0, 0),
byteArrayOf(78, 65, 79, 0, 0, 0), byteArrayOf(78, 69, 0, 0, 0, 0),
byteArrayOf(78, 69, 73, 0, 0, 0), byteArrayOf(78, 69, 78, 0, 0, 0),
byteArrayOf(78, 69, 78, 71, 0, 0), byteArrayOf(78, 73, 0, 0, 0, 0),
byteArrayOf(78, 73, 65, 78, 0, 0), byteArrayOf(78, 73, 65, 78, 71, 0),
byteArrayOf(78, 73, 65, 79, 0, 0), byteArrayOf(78, 73, 69, 0, 0, 0),
byteArrayOf(78, 73, 78, 0, 0, 0), byteArrayOf(78, 73, 78, 71, 0, 0),
byteArrayOf(78, 73, 85, 0, 0, 0), byteArrayOf(78, 79, 78, 71, 0, 0),
byteArrayOf(78, 79, 85, 0, 0, 0), byteArrayOf(78, 85, 0, 0, 0, 0),
byteArrayOf(78, 85, 65, 78, 0, 0), byteArrayOf(78, 85, 69, 0, 0, 0),
byteArrayOf(78, 85, 78, 0, 0, 0), byteArrayOf(78, 85, 79, 0, 0, 0),
byteArrayOf(79, 0, 0, 0, 0, 0), byteArrayOf(79, 85, 0, 0, 0, 0),
byteArrayOf(80, 65, 0, 0, 0, 0), byteArrayOf(80, 65, 73, 0, 0, 0),
byteArrayOf(80, 65, 78, 0, 0, 0), byteArrayOf(80, 65, 78, 71, 0, 0),
byteArrayOf(80, 65, 79, 0, 0, 0), byteArrayOf(80, 69, 73, 0, 0, 0),
byteArrayOf(80, 69, 78, 0, 0, 0), byteArrayOf(80, 69, 78, 71, 0, 0),
byteArrayOf(80, 73, 0, 0, 0, 0), byteArrayOf(80, 73, 65, 78, 0, 0),
byteArrayOf(80, 73, 65, 79, 0, 0), byteArrayOf(80, 73, 69, 0, 0, 0),
byteArrayOf(80, 73, 78, 0, 0, 0), byteArrayOf(80, 73, 78, 71, 0, 0),
byteArrayOf(80, 79, 0, 0, 0, 0), byteArrayOf(80, 79, 85, 0, 0, 0),
byteArrayOf(80, 85, 0, 0, 0, 0), byteArrayOf(81, 73, 0, 0, 0, 0),
byteArrayOf(81, 73, 65, 0, 0, 0), byteArrayOf(81, 73, 65, 78, 0, 0),
byteArrayOf(81, 73, 65, 78, 71, 0), byteArrayOf(81, 73, 65, 79, 0, 0),
byteArrayOf(81, 73, 69, 0, 0, 0), byteArrayOf(81, 73, 78, 0, 0, 0),
byteArrayOf(81, 73, 78, 71, 0, 0), byteArrayOf(81, 73, 79, 78, 71, 0),
byteArrayOf(81, 73, 85, 0, 0, 0), byteArrayOf(81, 85, 0, 0, 0, 0),
byteArrayOf(81, 85, 65, 78, 0, 0), byteArrayOf(81, 85, 69, 0, 0, 0),
byteArrayOf(81, 85, 78, 0, 0, 0), byteArrayOf(82, 65, 78, 0, 0, 0),
byteArrayOf(82, 65, 78, 71, 0, 0), byteArrayOf(82, 65, 79, 0, 0, 0),
byteArrayOf(82, 69, 0, 0, 0, 0), byteArrayOf(82, 69, 78, 0, 0, 0),
byteArrayOf(82, 69, 78, 71, 0, 0), byteArrayOf(82, 73, 0, 0, 0, 0),
byteArrayOf(82, 79, 78, 71, 0, 0), byteArrayOf(82, 79, 85, 0, 0, 0),
byteArrayOf(82, 85, 0, 0, 0, 0), byteArrayOf(82, 85, 65, 0, 0, 0),
byteArrayOf(82, 85, 65, 78, 0, 0), byteArrayOf(82, 85, 73, 0, 0, 0),
byteArrayOf(82, 85, 78, 0, 0, 0), byteArrayOf(82, 85, 79, 0, 0, 0),
byteArrayOf(83, 65, 0, 0, 0, 0), byteArrayOf(83, 65, 73, 0, 0, 0),
byteArrayOf(83, 65, 78, 0, 0, 0), byteArrayOf(83, 65, 78, 71, 0, 0),
byteArrayOf(83, 65, 79, 0, 0, 0), byteArrayOf(83, 69, 0, 0, 0, 0),
byteArrayOf(83, 69, 78, 0, 0, 0), byteArrayOf(83, 69, 78, 71, 0, 0),
byteArrayOf(83, 72, 65, 0, 0, 0), byteArrayOf(83, 72, 65, 73, 0, 0),
byteArrayOf(83, 72, 65, 78, 0, 0), byteArrayOf(83, 72, 65, 78, 71, 0),
byteArrayOf(83, 72, 65, 79, 0, 0), byteArrayOf(83, 72, 69, 0, 0, 0),
byteArrayOf(83, 72, 69, 78, 0, 0), byteArrayOf(88, 73, 78, 0, 0, 0),
byteArrayOf(83, 72, 69, 78, 0, 0), byteArrayOf(83, 72, 69, 78, 71, 0),
byteArrayOf(83, 72, 73, 0, 0, 0), byteArrayOf(83, 72, 79, 85, 0, 0),
byteArrayOf(83, 72, 85, 0, 0, 0), byteArrayOf(83, 72, 85, 65, 0, 0),
byteArrayOf(83, 72, 85, 65, 73, 0), byteArrayOf(83, 72, 85, 65, 78, 0),
byteArrayOf(83, 72, 85, 65, 78, 71), byteArrayOf(83, 72, 85, 73, 0, 0),
byteArrayOf(83, 72, 85, 78, 0, 0), byteArrayOf(83, 72, 85, 79, 0, 0),
byteArrayOf(83, 73, 0, 0, 0, 0), byteArrayOf(83, 79, 78, 71, 0, 0),
byteArrayOf(83, 79, 85, 0, 0, 0), byteArrayOf(83, 85, 0, 0, 0, 0),
byteArrayOf(83, 85, 65, 78, 0, 0), byteArrayOf(83, 85, 73, 0, 0, 0),
byteArrayOf(83, 85, 78, 0, 0, 0), byteArrayOf(83, 85, 79, 0, 0, 0),
byteArrayOf(84, 65, 0, 0, 0, 0), byteArrayOf(84, 65, 73, 0, 0, 0),
byteArrayOf(84, 65, 78, 0, 0, 0), byteArrayOf(84, 65, 78, 71, 0, 0),
byteArrayOf(84, 65, 79, 0, 0, 0), byteArrayOf(84, 69, 0, 0, 0, 0),
byteArrayOf(84, 69, 78, 71, 0, 0), byteArrayOf(84, 73, 0, 0, 0, 0),
byteArrayOf(84, 73, 65, 78, 0, 0), byteArrayOf(84, 73, 65, 79, 0, 0),
byteArrayOf(84, 73, 69, 0, 0, 0), byteArrayOf(84, 73, 78, 71, 0, 0),
byteArrayOf(84, 79, 78, 71, 0, 0), byteArrayOf(84, 79, 85, 0, 0, 0),
byteArrayOf(84, 85, 0, 0, 0, 0), byteArrayOf(84, 85, 65, 78, 0, 0),
byteArrayOf(84, 85, 73, 0, 0, 0), byteArrayOf(84, 85, 78, 0, 0, 0),
byteArrayOf(84, 85, 79, 0, 0, 0), byteArrayOf(87, 65, 0, 0, 0, 0),
byteArrayOf(87, 65, 73, 0, 0, 0), byteArrayOf(87, 65, 78, 0, 0, 0),
byteArrayOf(87, 65, 78, 71, 0, 0), byteArrayOf(87, 69, 73, 0, 0, 0),
byteArrayOf(87, 69, 78, 0, 0, 0), byteArrayOf(87, 69, 78, 71, 0, 0),
byteArrayOf(87, 79, 0, 0, 0, 0), byteArrayOf(87, 85, 0, 0, 0, 0),
byteArrayOf(88, 73, 0, 0, 0, 0), byteArrayOf(88, 73, 65, 0, 0, 0),
byteArrayOf(88, 73, 65, 78, 0, 0), byteArrayOf(88, 73, 65, 78, 71, 0),
byteArrayOf(88, 73, 65, 79, 0, 0), byteArrayOf(88, 73, 69, 0, 0, 0),
byteArrayOf(88, 73, 78, 0, 0, 0), byteArrayOf(88, 73, 78, 71, 0, 0),
byteArrayOf(88, 73, 79, 78, 71, 0), byteArrayOf(88, 73, 85, 0, 0, 0),
byteArrayOf(88, 85, 0, 0, 0, 0), byteArrayOf(88, 85, 65, 78, 0, 0),
byteArrayOf(88, 85, 69, 0, 0, 0), byteArrayOf(88, 85, 78, 0, 0, 0),
byteArrayOf(89, 65, 0, 0, 0, 0), byteArrayOf(89, 65, 78, 0, 0, 0),
byteArrayOf(89, 65, 78, 71, 0, 0), byteArrayOf(89, 65, 79, 0, 0, 0),
byteArrayOf(89, 69, 0, 0, 0, 0), byteArrayOf(89, 73, 0, 0, 0, 0),
byteArrayOf(89, 73, 78, 0, 0, 0), byteArrayOf(89, 73, 78, 71, 0, 0),
byteArrayOf(89, 79, 0, 0, 0, 0), byteArrayOf(89, 79, 78, 71, 0, 0),
byteArrayOf(89, 79, 85, 0, 0, 0), byteArrayOf(89, 85, 0, 0, 0, 0),
byteArrayOf(89, 85, 65, 78, 0, 0), byteArrayOf(89, 85, 69, 0, 0, 0),
byteArrayOf(89, 85, 78, 0, 0, 0), byteArrayOf(74, 85, 78, 0, 0, 0),
byteArrayOf(89, 85, 78, 0, 0, 0), byteArrayOf(90, 65, 0, 0, 0, 0),
byteArrayOf(90, 65, 73, 0, 0, 0), byteArrayOf(90, 65, 78, 0, 0, 0),
byteArrayOf(90, 65, 78, 71, 0, 0), byteArrayOf(90, 65, 79, 0, 0, 0),
byteArrayOf(90, 69, 0, 0, 0, 0), byteArrayOf(90, 69, 73, 0, 0, 0),
byteArrayOf(90, 69, 78, 0, 0, 0), byteArrayOf(90, 69, 78, 71, 0, 0),
byteArrayOf(90, 72, 65, 0, 0, 0), byteArrayOf(90, 72, 65, 73, 0, 0),
byteArrayOf(90, 72, 65, 78, 0, 0), byteArrayOf(90, 72, 65, 78, 71, 0),
byteArrayOf(67, 72, 65, 78, 71, 0), byteArrayOf(90, 72, 65, 78, 71, 0),
byteArrayOf(90, 72, 65, 79, 0, 0), byteArrayOf(90, 72, 69, 0, 0, 0),
byteArrayOf(90, 72, 69, 78, 0, 0), byteArrayOf(90, 72, 69, 78, 71, 0),
byteArrayOf(90, 72, 73, 0, 0, 0), byteArrayOf(83, 72, 73, 0, 0, 0),
byteArrayOf(90, 72, 73, 0, 0, 0), byteArrayOf(90, 72, 79, 78, 71, 0),
byteArrayOf(90, 72, 79, 85, 0, 0), byteArrayOf(90, 72, 85, 0, 0, 0),
byteArrayOf(90, 72, 85, 65, 0, 0), byteArrayOf(90, 72, 85, 65, 73, 0),
byteArrayOf(90, 72, 85, 65, 78, 0), byteArrayOf(90, 72, 85, 65, 78, 71),
byteArrayOf(90, 72, 85, 73, 0, 0), byteArrayOf(90, 72, 85, 78, 0, 0),
byteArrayOf(90, 72, 85, 79, 0, 0), byteArrayOf(90, 73, 0, 0, 0, 0),
byteArrayOf(90, 79, 78, 71, 0, 0), byteArrayOf(90, 79, 85, 0, 0, 0),
byteArrayOf(90, 85, 0, 0, 0, 0), byteArrayOf(90, 85, 65, 78, 0, 0),
byteArrayOf(90, 85, 73, 0, 0, 0), byteArrayOf(90, 85, 78, 0, 0, 0),
byteArrayOf(90, 85, 79, 0, 0, 0), byteArrayOf(0, 0, 0, 0, 0, 0),
byteArrayOf(83, 72, 65, 78, 0, 0), byteArrayOf(0, 0, 0, 0, 0, 0)
)
private const val FIRST_PINYIN_UNIHAN = ""
private const val LAST_PINYIN_UNIHAN = "鿿"
private val COLLATOR: Collator = Collator.getInstance(Locale.CHINA)
private var sInstance: HanziToPinyin? = null
fun getInstance(): HanziToPinyin {
synchronized(HanziToPinyin::class.java) {
if (sInstance != null) {
return sInstance!!
}
val locale = Collator.getAvailableLocales()
for (value in locale) {
if (value == Locale.CHINA || value.language.contains("zh")) {
if (DEBUG) {
Log.d(TAG, "Self validation. Result: ${doSelfValidation()}")
}
sInstance = HanziToPinyin(true)
return sInstance!!
}
}
if (sInstance == null) {
if (Locale.CHINA == Locale.getDefault()) {
sInstance = HanziToPinyin(true)
return sInstance!!
}
}
Log.w(TAG, "There is no Chinese collator, HanziToPinyin is disabled")
sInstance = HanziToPinyin(false)
return sInstance!!
}
}
private fun doSelfValidation(): Boolean {
val lastChar = UNIHANS[0]
var lastString = lastChar.toString()
for (c in UNIHANS) {
if (lastChar == c) {
continue
}
val curString = c.toString()
val cmp = COLLATOR.compare(lastString, curString)
if (cmp >= 0) {
Log.e(
TAG,
"Internal error in Unihan table. The last string \"$lastString\" " +
"is greater than current string \"$curString\"."
)
return false
}
lastString = curString
}
return true
}
}
}

View file

@ -13,6 +13,7 @@ import android.util.Log
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.io.SuFile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
@ -21,6 +22,7 @@ import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
import org.json.JSONArray
import java.io.File
import java.util.concurrent.CountDownLatch
/**
@ -97,13 +99,6 @@ fun execKsud(args: String, newShell: Boolean = false): Boolean {
}
}
suspend fun getFeatureStatus(feature: String): String = withContext(Dispatchers.IO) {
val shell = getRootShell()
val out = shell.newJob()
.add("${getKsuDaemonPath()} feature check $feature").to(ArrayList<String>(), null).exec().out
out.firstOrNull()?.trim().orEmpty()
}
fun install() {
val start = SystemClock.elapsedRealtime()
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so").absolutePath
@ -114,8 +109,8 @@ fun install() {
fun listModules(): String {
val shell = getRootShell()
val out = shell.newJob()
.add("${getKsuDaemonPath()} module list").to(ArrayList(), null).exec().out
val out =
shell.newJob().add("${getKsuDaemonPath()} module list").to(ArrayList(), null).exec().out
return out.joinToString("\n").ifBlank { "[]" }
}
@ -156,13 +151,6 @@ fun restoreModule(id: String): Boolean {
return result
}
fun undoUninstallModule(id: String): Boolean {
val cmd = "module undo-uninstall $id"
val result = execKsud(cmd, true)
Log.i(TAG, "undo uninstall module $id result: $result")
return result
}
private fun flashWithIO(
cmd: String,
onStdout: (String) -> Unit,
@ -681,14 +669,15 @@ fun readUidScannerFile(): Boolean {
return try {
ShellUtils.fastCmd(shell, "cat /data/adb/ksu/.uid_scanner").trim() == "1"
} catch (_: Exception) {
false
false
}
}
fun addUmountPath(path: String, flags: Int): Boolean {
fun addUmountPath(path: String, checkMnt: Boolean, flags: Int): Boolean {
val shell = getRootShell()
val checkMntFlag = if (checkMnt) "--check-mnt" else ""
val flagsArg = if (flags >= 0) "--flags $flags" else ""
val cmd = "${getKsuDaemonPath()} umount add $path $flagsArg"
val cmd = "${getKsuDaemonPath()} umount add $path $checkMntFlag $flagsArg"
val result = ShellUtils.fastCmdResult(shell, cmd)
Log.i(TAG, "add umount path $path result: $result")
return result

View file

@ -105,7 +105,7 @@ class ModuleViewModel : ViewModel() {
).thenBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id)
modules.filter {
it.id.contains(search, true) || it.name.contains(search, true) || HanziToPinyin.getInstance()
.toPinyinString(it.name)?.contains(search, true) == true
.toPinyinString(it.name).contains(search, true)
}.sortedWith(comparator).also {
isRefreshing = false
}
@ -143,15 +143,15 @@ class ModuleViewModel : ViewModel() {
obj.optString("name"),
obj.optString("author", "Unknown"),
obj.optString("version", "Unknown"),
obj.getIntCompat("versionCode", 0),
obj.optInt("versionCode", 0),
obj.optString("description"),
obj.getBooleanCompat("enabled"),
obj.getBooleanCompat("update"),
obj.getBooleanCompat("remove"),
obj.getBoolean("enabled"),
obj.getBoolean("update"),
obj.getBoolean("remove"),
obj.optString("updateJson"),
obj.getBooleanCompat("web"),
obj.getBooleanCompat("action"),
obj.optString("dir_id", obj.getString("id"))
obj.optBoolean("web"),
obj.optBoolean("action"),
obj.getString("dir_id")
)
}.toList()
@ -469,26 +469,6 @@ class ModuleSizeCache(context: Context) {
}
}
private fun JSONObject.getBooleanCompat(key: String, default: Boolean = false): Boolean {
if (!has(key)) return default
return when (val value = opt(key)) {
is Boolean -> value
is String -> value.equals("true", ignoreCase = true) || value == "1"
is Number -> value.toInt() != 0
else -> default
}
}
private fun JSONObject.getIntCompat(key: String, default: Int = 0): Int {
if (!has(key)) return default
return when (val value = opt(key)) {
is Int -> value
is Number -> value.toInt()
is String -> value.toIntOrNull() ?: default
else -> default
}
}
/**
* 格式化文件大小的工具函数
*/

View file

@ -18,6 +18,7 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.parcelize.Parcelize
import java.text.Collator
import java.util.*
import java.util.concurrent.LinkedBlockingQueue
@ -26,10 +27,6 @@ import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import com.sukisu.zako.IKsuInterface
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
enum class AppCategory(val displayNameRes: Int, val persistKey: String) {
ALL(com.sukisu.ultra.R.string.category_all_apps, "ALL"),
@ -61,8 +58,7 @@ class SuperUserViewModel : ViewModel() {
private const val TAG = "SuperUserViewModel"
private val appsLock = Any()
var apps by mutableStateOf<List<AppInfo>>(emptyList())
private val _isAppListLoaded = MutableStateFlow(false)
val isAppListLoaded = _isAppListLoaded.asStateFlow()
var appGroups by mutableStateOf<List<AppGroup>>(emptyList())
@JvmStatic
fun getAppIconDrawable(context: Context, packageName: String): Drawable? {
@ -71,8 +67,6 @@ class SuperUserViewModel : ViewModel() {
?.packageInfo?.applicationInfo?.loadIcon(context.packageManager)
}
var appGroups by mutableStateOf<List<AppGroup>>(emptyList())
private const val PREFS_NAME = "settings"
private const val KEY_SHOW_SYSTEM_APPS = "show_system_apps"
private const val KEY_SELECTED_CATEGORY = "selected_category"
@ -83,36 +77,31 @@ class SuperUserViewModel : ViewModel() {
private const val BATCH_SIZE = 20
}
@Immutable
@Parcelize
data class AppInfo(
val label: String,
val packageInfo: PackageInfo,
val profile: Natives.Profile?,
) : Parcelable {
@IgnoredOnParcel
val packageName: String = packageInfo.packageName
@IgnoredOnParcel
val uid: Int = packageInfo.applicationInfo!!.uid
val packageName: String get() = packageInfo.packageName
val uid: Int get() = packageInfo.applicationInfo!!.uid
}
@Immutable
@Parcelize
data class AppGroup(
val uid: Int,
val apps: List<AppInfo>,
val profile: Natives.Profile?
) : Parcelable {
@IgnoredOnParcel
val mainApp: AppInfo = apps.first()
@IgnoredOnParcel
val packageNames: List<String> = apps.map { it.packageName }
@IgnoredOnParcel
val allowSu: Boolean = profile?.allowSu == true
@IgnoredOnParcel
val userName: String? = Natives.getUserName(uid)
@IgnoredOnParcel
val hasCustomProfile : Boolean = profile?.let { if (it.allowSu) !it.rootUseDefault else !it.nonRootUseDefault } ?: false
val mainApp: AppInfo get() = apps.first()
val packageNames: List<String> get() = apps.map { it.packageName }
val allowSu: Boolean get() = profile?.allowSu == true
val userName: String? get() = Natives.getUserName(uid)
val hasCustomProfile: Boolean
get() = profile?.let {
if (it.allowSu) !it.rootUseDefault else !it.nonRootUseDefault
} ?: false
}
private val appProcessingThreadPool = ThreadPoolExecutor(
@ -342,10 +331,6 @@ class SuperUserViewModel : ViewModel() {
stopKsuService()
synchronized(appsLock) {
_isAppListLoaded.value = true
}
appListMutex.withLock {
val filteredApps = result.filter { it.packageName != ksuApp.packageName }
apps = filteredApps
@ -361,7 +346,7 @@ class SuperUserViewModel : ViewModel() {
group.apps.any { app ->
app.label.contains(search, true) ||
app.packageName.contains(search, true) ||
HanziToPinyin.getInstance().toPinyinString(app.label)?.contains(search, true) == true
HanziToPinyin.getInstance().toPinyinString(app.label).contains(search, true)
}
}.filter { group ->
group.uid == 2000 || showSystemApps ||

View file

@ -0,0 +1,47 @@
package com.sukisu.ultra.ui.webui;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.LruCache;
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel;
public class AppIconUtil {
// Limit cache size to 200 icons
private static final int CACHE_SIZE = 200;
private static final LruCache<String, Bitmap> iconCache = new LruCache<>(CACHE_SIZE);
public static synchronized Bitmap loadAppIconSync(Context context, String packageName, int sizePx) {
Bitmap cached = iconCache.get(packageName);
if (cached != null) return cached;
try {
Drawable drawable = SuperUserViewModel.getAppIconDrawable(context, packageName);
if (drawable == null) {
return null;
}
Bitmap raw = drawableToBitmap(drawable, sizePx);
Bitmap icon = Bitmap.createScaledBitmap(raw, sizePx, sizePx, true);
if (raw != icon) raw.recycle();
iconCache.put(packageName, icon);
return icon;
} catch (Exception e) {
return null;
}
}
private static Bitmap drawableToBitmap(Drawable drawable, int size) {
if (drawable instanceof BitmapDrawable) return ((BitmapDrawable) drawable).getBitmap();
int width = drawable.getIntrinsicWidth() > 0 ? drawable.getIntrinsicWidth() : size;
int height = drawable.getIntrinsicHeight() > 0 ? drawable.getIntrinsicHeight() : size;
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bmp;
}
}

View file

@ -1,46 +0,0 @@
package com.sukisu.ultra.ui.webui
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.util.LruCache
import androidx.core.graphics.createBitmap
import androidx.core.graphics.scale
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel.Companion.getAppIconDrawable
object AppIconUtil {
// Limit cache size to 200 icons
private const val CACHE_SIZE = 200
private val iconCache = LruCache<String?, Bitmap?>(CACHE_SIZE)
@Synchronized
fun loadAppIconSync(context: Context, packageName: String, sizePx: Int): Bitmap? {
val cached = iconCache.get(packageName)
if (cached != null) return cached
try {
val drawable = getAppIconDrawable(context, packageName) ?: return null
val raw = drawableToBitmap(drawable, sizePx)
val icon = raw.scale(sizePx, sizePx)
iconCache.put(packageName, icon)
return icon
} catch (_: Exception) {
return null
}
}
private fun drawableToBitmap(drawable: Drawable, size: Int): Bitmap {
if (drawable is BitmapDrawable) return drawable.bitmap
val width = if (drawable.intrinsicWidth > 0) drawable.intrinsicWidth else size
val height = if (drawable.intrinsicHeight > 0) drawable.intrinsicHeight else size
val bmp = createBitmap(width, height)
val canvas = Canvas(bmp)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bmp
}
}

View file

@ -1,40 +0,0 @@
package com.sukisu.ultra.ui.webui
/**
* Insets data class from GitHub@MMRLApp/WebUI-X-Portable
*
* Data class representing insets (top, bottom, left, right) for a view.
*
* This class provides methods to generate CSS code that can be injected into a WebView
* to apply these insets as CSS variables. This is useful for adapting web content
* to the safe areas of a device screen, considering notches, status bars, and navigation bars.
*
* @property top The top inset value in pixels.
* @property bottom The bottom inset value in pixels.
* @property left The left inset value in pixels.
* @property right The right inset value in pixels.
*/
data class Insets(
val top: Int,
val bottom: Int,
val left: Int,
val right: Int,
) {
val css
get() = buildString {
appendLine(":root {")
appendLine("\t--safe-area-inset-top: ${top}px;")
appendLine("\t--safe-area-inset-right: ${right}px;")
appendLine("\t--safe-area-inset-bottom: ${bottom}px;")
appendLine("\t--safe-area-inset-left: ${left}px;")
appendLine("\t--window-inset-top: var(--safe-area-inset-top, 0px);")
appendLine("\t--window-inset-bottom: var(--safe-area-inset-bottom, 0px);")
appendLine("\t--window-inset-left: var(--safe-area-inset-left, 0px);")
appendLine("\t--window-inset-right: var(--safe-area-inset-right, 0px);")
appendLine("\t--f7-safe-area-top: var(--window-inset-top, 0px) !important;")
appendLine("\t--f7-safe-area-bottom: var(--window-inset-bottom, 0px) !important;")
appendLine("\t--f7-safe-area-left: var(--window-inset-left, 0px) !important;")
appendLine("\t--f7-safe-area-right: var(--window-inset-right, 0px) !important;")
append("}")
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sukisu.ultra.ui.webui;
import java.net.URLConnection;
class MimeUtil {
public static String getMimeFromFileName(String fileName) {
if (fileName == null) {
return null;
}
// Copying the logic and mapping that Chromium follows.
// First we check against the OS (this is a limited list by default)
// but app developers can extend this.
// We then check against a list of hardcoded mime types above if the
// OS didn't provide a result.
String mimeType = URLConnection.guessContentTypeFromName(fileName);
if (mimeType != null) {
return mimeType;
}
return guessHardcodedMime(fileName);
}
// We should keep this map in sync with the lists under
// //net/base/mime_util.cc in Chromium.
// A bunch of the mime types don't really apply to Android land
// like word docs so feel free to filter out where necessary.
private static String guessHardcodedMime(String fileName) {
int finalFullStop = fileName.lastIndexOf('.');
if (finalFullStop == -1) {
return null;
}
final String extension = fileName.substring(finalFullStop + 1).toLowerCase();
return switch (extension) {
case "webm" -> "video/webm";
case "mpeg", "mpg" -> "video/mpeg";
case "mp3" -> "audio/mpeg";
case "wasm" -> "application/wasm";
case "xhtml", "xht", "xhtm" -> "application/xhtml+xml";
case "flac" -> "audio/flac";
case "ogg", "oga", "opus" -> "audio/ogg";
case "wav" -> "audio/wav";
case "m4a" -> "audio/x-m4a";
case "gif" -> "image/gif";
case "jpeg", "jpg", "jfif", "pjpeg", "pjp" -> "image/jpeg";
case "png" -> "image/png";
case "apng" -> "image/apng";
case "svg", "svgz" -> "image/svg+xml";
case "webp" -> "image/webp";
case "mht", "mhtml" -> "multipart/related";
case "css" -> "text/css";
case "html", "htm", "shtml", "shtm", "ehtml" -> "text/html";
case "js", "mjs" -> "application/javascript";
case "xml" -> "text/xml";
case "mp4", "m4v" -> "video/mp4";
case "ogv", "ogm" -> "video/ogg";
case "ico" -> "image/x-icon";
case "woff" -> "application/font-woff";
case "gz", "tgz" -> "application/gzip";
case "json" -> "application/json";
case "pdf" -> "application/pdf";
case "zip" -> "application/zip";
case "bmp" -> "image/bmp";
case "tiff", "tif" -> "image/tiff";
default -> null;
};
}
}

View file

@ -1,77 +0,0 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sukisu.ultra.ui.webui
import java.net.URLConnection
internal object MimeUtil {
fun getMimeFromFileName(fileName: String?): String? {
if (fileName == null) {
return null
}
val mimeType = URLConnection.guessContentTypeFromName(fileName)
if (mimeType != null) {
return mimeType
}
return guessHardcodedMime(fileName)
}
private fun guessHardcodedMime(fileName: String): String? {
val finalFullStop = fileName.lastIndexOf('.')
if (finalFullStop == -1) {
return null
}
val extension = fileName.substring(finalFullStop + 1).lowercase()
return when (extension) {
"webm" -> "video/webm"
"mpeg", "mpg" -> "video/mpeg"
"mp3" -> "audio/mpeg"
"wasm" -> "application/wasm"
"xhtml", "xht", "xhtm" -> "application/xhtml+xml"
"flac" -> "audio/flac"
"ogg", "oga", "opus" -> "audio/ogg"
"wav" -> "audio/wav"
"m4a" -> "audio/x-m4a"
"gif" -> "image/gif"
"jpeg", "jpg", "jfif", "pjpeg", "pjp" -> "image/jpeg"
"png" -> "image/png"
"apng" -> "image/apng"
"svg", "svgz" -> "image/svg+xml"
"webp" -> "image/webp"
"mht", "mhtml" -> "multipart/related"
"css" -> "text/css"
"html", "htm", "shtml", "shtm", "ehtml" -> "text/html"
"js", "mjs" -> "application/javascript"
"xml" -> "text/xml"
"mp4", "m4v" -> "video/mp4"
"ogv", "ogm" -> "video/ogg"
"ico" -> "image/x-icon"
"woff" -> "application/font-woff"
"gz", "tgz" -> "application/gzip"
"json" -> "application/json"
"pdf" -> "application/pdf"
"zip" -> "application/zip"
"bmp" -> "image/bmp"
"tiff", "tif" -> "image/tiff"
else -> null
}
}
}

View file

@ -0,0 +1,188 @@
package com.sukisu.ultra.ui.webui;
import android.content.Context;
import android.util.Log;
import android.webkit.WebResourceResponse;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import androidx.webkit.WebViewAssetLoader;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
/**
* Handler class to open files from file system by root access
* For more information about android storage please refer to
* <a href="https://developer.android.com/guide/topics/data/data-storage">Android Developers
* Docs: Data and file storage overview</a>.
* <p class="note">
* To avoid leaking user or app data to the web, make sure to choose {@code directory}
* carefully, and assume any file under this directory could be accessed by any web page subject
* to same-origin rules.
* <p>
* A typical usage would be like:
* <pre class="prettyprint">
* File publicDir = new File(context.getFilesDir(), "public");
* // Host "files/public/" in app's data directory under:
* // http://appassets.androidplatform.net/public/...
* WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
* .addPathHandler("/public/", new InternalStoragePathHandler(context, publicDir))
* .build();
* </pre>
*/
public final class SuFilePathHandler implements WebViewAssetLoader.PathHandler {
private static final String TAG = "SuFilePathHandler";
/**
* Default value to be used as MIME type if guessing MIME type failed.
*/
public static final String DEFAULT_MIME_TYPE = "text/plain";
/**
* Forbidden subdirectories of {@link Context#getDataDir} that cannot be exposed by this
* handler. They are forbidden as they often contain sensitive information.
* <p class="note">
* Note: Any future addition to this list will be considered breaking changes to the API.
*/
private static final String[] FORBIDDEN_DATA_DIRS =
new String[] {"/data/data", "/data/system"};
@NonNull
private final File mDirectory;
private final Shell mShell;
/**
* Creates PathHandler for app's internal storage.
* The directory to be exposed must be inside either the application's internal data
* directory {@link Context#getDataDir} or cache directory {@link Context#getCacheDir}.
* External storage is not supported for security reasons, as other apps with
* {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} may be able to modify the
* files.
* <p>
* Exposing the entire data or cache directory is not permitted, to avoid accidentally
* exposing sensitive application files to the web. Certain existing subdirectories of
* {@link Context#getDataDir} are also not permitted as they are often sensitive.
* These files are ({@code "app_webview/"}, {@code "databases/"}, {@code "lib/"},
* {@code "shared_prefs/"} and {@code "code_cache/"}).
* <p>
* The application should typically use a dedicated subdirectory for the files it intends to
* expose and keep them separate from other files.
*
* @param directory the absolute path of the exposed app internal storage directory from
* which files can be loaded.
* @throws IllegalArgumentException if the directory is not allowed.
*/
public SuFilePathHandler(@NonNull File directory, Shell rootShell) {
try {
mDirectory = new File(getCanonicalDirPath(directory));
if (!isAllowedInternalStorageDir()) {
throw new IllegalArgumentException("The given directory \"" + directory
+ "\" doesn't exist under an allowed app internal storage directory");
}
mShell = rootShell;
} catch (IOException e) {
throw new IllegalArgumentException(
"Failed to resolve the canonical path for the given directory: "
+ directory.getPath(), e);
}
}
private boolean isAllowedInternalStorageDir() throws IOException {
String dir = getCanonicalDirPath(mDirectory);
for (String forbiddenPath : FORBIDDEN_DATA_DIRS) {
if (dir.startsWith(forbiddenPath)) {
return false;
}
}
return true;
}
/**
* Opens the requested file from the exposed data directory.
* <p>
* The matched prefix path used shouldn't be a prefix of a real web path. Thus, if the
* requested file cannot be found or is outside the mounted directory a
* {@link WebResourceResponse} object with a {@code null} {@link InputStream} will be
* returned instead of {@code null}. This saves the time of falling back to network and
* trying to resolve a path that doesn't exist. A {@link WebResourceResponse} with
* {@code null} {@link InputStream} will be received as an HTTP response with status code
* {@code 404} and no body.
* <p class="note">
* The MIME type for the file will be determined from the file's extension using
* {@link java.net.URLConnection#guessContentTypeFromName}. Developers should ensure that
* files are named using standard file extensions. If the file does not have a
* recognised extension, {@code "text/plain"} will be used by default.
*
* @param path the suffix path to be handled.
* @return {@link WebResourceResponse} for the requested file.
*/
@Override
@WorkerThread
@NonNull
public WebResourceResponse handle(@NonNull String path) {
try {
File file = getCanonicalFileIfChild(mDirectory, path);
if (file != null) {
InputStream is = openFile(file, mShell);
String mimeType = guessMimeType(path);
return new WebResourceResponse(mimeType, null, is);
} else {
Log.e(TAG, String.format(
"The requested file: %s is outside the mounted directory: %s", path,
mDirectory));
}
} catch (IOException e) {
Log.e(TAG, "Error opening the requested path: " + path, e);
}
return new WebResourceResponse(null, null, null);
}
public static String getCanonicalDirPath(@NonNull File file) throws IOException {
String canonicalPath = file.getCanonicalPath();
if (!canonicalPath.endsWith("/")) canonicalPath += "/";
return canonicalPath;
}
public static File getCanonicalFileIfChild(@NonNull File parent, @NonNull String child)
throws IOException {
String parentCanonicalPath = getCanonicalDirPath(parent);
String childCanonicalPath = new File(parent, child).getCanonicalPath();
if (childCanonicalPath.startsWith(parentCanonicalPath)) {
return new File(childCanonicalPath);
}
return null;
}
@NonNull
private static InputStream handleSvgzStream(@NonNull String path,
@NonNull InputStream stream) throws IOException {
return path.endsWith(".svgz") ? new GZIPInputStream(stream) : stream;
}
public static InputStream openFile(@NonNull File file, @NonNull Shell shell) throws IOException {
SuFile suFile = new SuFile(file.getAbsolutePath());
suFile.setShell(shell);
InputStream fis = SuFileInputStream.open(suFile);
return handleSvgzStream(file.getPath(), fis);
}
/**
* Use {@link MimeUtil#getMimeFromFileName} to guess MIME type or return the
* {@link #DEFAULT_MIME_TYPE} if it can't guess.
*
* @param filePath path of the file to guess its MIME type.
* @return MIME type guessed from file extension or {@link #DEFAULT_MIME_TYPE}.
*/
@NonNull
public static String guessMimeType(@NonNull String filePath) {
String mimeType = MimeUtil.getMimeFromFileName(filePath);
return mimeType == null ? DEFAULT_MIME_TYPE : mimeType;
}
}

View file

@ -1,192 +0,0 @@
package com.sukisu.ultra.ui.webui
import android.content.Context
import android.util.Log
import android.webkit.WebResourceResponse
import androidx.annotation.WorkerThread
import androidx.webkit.WebViewAssetLoader
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream
import java.io.ByteArrayInputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.nio.charset.StandardCharsets
import java.util.zip.GZIPInputStream
/**
* Handler class to open files from file system by root access
* For more information about android storage please refer to
* [Android Developers Docs: Data and file storage overview](https://developer.android.com/guide/topics/data/data-storage).
*
* To avoid leaking user or app data to the web, make sure to choose [directory]
* carefully, and assume any file under this directory could be accessed by any web page subject
* to same-origin rules.
*
* A typical usage would be like:
* ```
* val publicDir = File(context.filesDir, "public")
* // Host "files/public/" in app's data directory under:
* // http://appassets.androidplatform.net/public/...
* val assetLoader = WebViewAssetLoader.Builder()
* .addPathHandler("/public/", SuFilePathHandler(context, publicDir, shell, insetsSupplier))
* .build()
* ```
*/
class SuFilePathHandler(
directory: File,
private val shell: Shell,
private val insetsSupplier: InsetsSupplier
) : WebViewAssetLoader.PathHandler {
private val directory: File
init {
try {
this.directory = File(getCanonicalDirPath(directory))
if (!isAllowedInternalStorageDir()) {
throw IllegalArgumentException(
"The given directory \"$directory\" doesn't exist under an allowed app internal storage directory"
)
}
} catch (e: IOException) {
throw IllegalArgumentException(
"Failed to resolve the canonical path for the given directory: ${directory.path}",
e
)
}
}
fun interface InsetsSupplier {
fun get(): Insets
}
private fun isAllowedInternalStorageDir(): Boolean {
return try {
val dir = getCanonicalDirPath(directory)
FORBIDDEN_DATA_DIRS.none { dir.startsWith(it) }
} catch (_: IOException) {
false
}
}
/**
* Opens the requested file from the exposed data directory.
*
* The matched prefix path used shouldn't be a prefix of a real web path. Thus, if the
* requested file cannot be found or is outside the mounted directory a
* [WebResourceResponse] object with a `null` [InputStream] will be
* returned instead of `null`. This saves the time of falling back to network and
* trying to resolve a path that doesn't exist. A [WebResourceResponse] with
* `null` [InputStream] will be received as an HTTP response with status code
* `404` and no body.
*
* The MIME type for the file will be determined from the file's extension using
* [java.net.URLConnection.guessContentTypeFromName]. Developers should ensure that
* files are named using standard file extensions. If the file does not have a
* recognised extension, `"text/plain"` will be used by default.
*
* @param path the suffix path to be handled.
* @return [WebResourceResponse] for the requested file.
*/
@WorkerThread
override fun handle(path: String): WebResourceResponse {
if (path == "internal/insets.css") {
val css = insetsSupplier.get().css
return WebResourceResponse(
"text/css",
"utf-8",
ByteArrayInputStream(css.toByteArray(StandardCharsets.UTF_8))
)
}
try {
val file = getCanonicalFileIfChild(directory, path)
if (file != null) {
val inputStream = openFile(file, shell)
val mimeType = guessMimeType(path)
return WebResourceResponse(mimeType, null, inputStream)
} else {
Log.e(
TAG,
"The requested file: $path is outside the mounted directory: $directory"
)
}
} catch (e: IOException) {
Log.e(TAG, "Error opening the requested path: $path", e)
}
return WebResourceResponse(null, null, null)
}
companion object {
private const val TAG = "SuFilePathHandler"
/**
* Default value to be used as MIME type if guessing MIME type failed.
*/
const val DEFAULT_MIME_TYPE = "text/plain"
/**
* Forbidden subdirectories of [Context.getDataDir] that cannot be exposed by this
* handler. They are forbidden as they often contain sensitive information.
*
* Note: Any future addition to this list will be considered breaking changes to the API.
*/
private val FORBIDDEN_DATA_DIRS = arrayOf("/data/data", "/data/system")
@JvmStatic
@Throws(IOException::class)
fun getCanonicalDirPath(file: File): String {
var canonicalPath = file.canonicalPath
if (!canonicalPath.endsWith("/")) {
canonicalPath += "/"
}
return canonicalPath
}
@JvmStatic
@Throws(IOException::class)
fun getCanonicalFileIfChild(parent: File, child: String): File? {
val parentCanonicalPath = getCanonicalDirPath(parent)
val childCanonicalPath = File(parent, child).canonicalPath
return if (childCanonicalPath.startsWith(parentCanonicalPath)) {
File(childCanonicalPath)
} else {
null
}
}
@Throws(IOException::class)
private fun handleSvgzStream(path: String, stream: InputStream): InputStream {
return if (path.endsWith(".svgz")) {
GZIPInputStream(stream)
} else {
stream
}
}
@JvmStatic
@Throws(IOException::class)
fun openFile(file: File, shell: Shell): InputStream {
val suFile = SuFile(file.absolutePath).apply {
setShell(shell)
}
val fis = SuFileInputStream.open(suFile)
return handleSvgzStream(file.path, fis)
}
/**
* Use [MimeUtil.getMimeFromFileName] to guess MIME type or return the
* [DEFAULT_MIME_TYPE] if it can't guess.
*
* @param filePath path of the file to guess its MIME type.
* @return MIME type guessed from file extension or [DEFAULT_MIME_TYPE].
*/
@JvmStatic
fun guessMimeType(filePath: String): String {
return MimeUtil.getMimeFromFileName(filePath) ?: DEFAULT_MIME_TYPE
}
}
}

View file

@ -2,38 +2,32 @@ package com.sukisu.ultra.ui.webui
import android.annotation.SuppressLint
import android.app.ActivityManager
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.ViewGroup.MarginLayoutParams
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.activity.viewModels
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.webkit.WebViewAssetLoader
import com.dergoogler.mmrl.platform.model.ModId
import com.dergoogler.mmrl.webui.interfaces.WXOptions
import com.sukisu.ultra.ui.util.createRootShell
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.io.File
@SuppressLint("SetJavaScriptEnabled")
class WebUIActivity : ComponentActivity() {
private val rootShell by lazy { createRootShell(true) }
private lateinit var insets: Insets
private val superUserViewModel: SuperUserViewModel by viewModels()
private var webView = null as WebView?
override fun onCreate(savedInstanceState: Bundle?) {
@ -46,21 +40,10 @@ class WebUIActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
lifecycleScope.launch {
superUserViewModel.fetchAppList()
}
lifecycleScope.launch {
SuperUserViewModel.isAppListLoaded.first { it }
setupWebView()
}
}
private fun setupWebView() {
val moduleId = intent.getStringExtra("id") ?: finishAndRemoveTask().let { return }
val name = intent.getStringExtra("name") ?: finishAndRemoveTask().let { return }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
@ -77,12 +60,11 @@ class WebUIActivity : ComponentActivity() {
val moduleDir = "/data/adb/modules/${moduleId}"
val webRoot = File("${moduleDir}/webroot")
insets = Insets(0, 0, 0, 0)
val webViewAssetLoader = WebViewAssetLoader.Builder()
.setDomain("mui.kernelsu.org")
.addPathHandler(
"/",
SuFilePathHandler(webRoot, rootShell) { insets }
SuFilePathHandler(webRoot, rootShell)
)
.build()
@ -112,18 +94,15 @@ class WebUIActivity : ComponentActivity() {
val webView = WebView(this).apply {
webView = this
setBackgroundColor(Color.TRANSPARENT)
val density = resources.displayMetrics.density
ViewCompat.setOnApplyWindowInsetsListener(this) { _, windowInsets ->
val inset = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout())
insets = Insets(
top = (inset.top / density).toInt(),
bottom = (inset.bottom / density).toInt(),
left = (inset.left / density).toInt(),
right = (inset.right / density).toInt()
)
WindowInsetsCompat.CONSUMED
ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->
val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updateLayoutParams<MarginLayoutParams> {
leftMargin = inset.left
rightMargin = inset.right
topMargin = inset.top
bottomMargin = inset.bottom
}
return@setOnApplyWindowInsetsListener insets
}
settings.javaScriptEnabled = true
settings.domStorageEnabled = true

View file

@ -17,9 +17,6 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.getValue
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -28,7 +25,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.core.content.edit
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@ -37,8 +33,8 @@ import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.theme.component.ImageEditorDialog
import com.sukisu.ultra.ui.component.KsuIsValid
import com.sukisu.ultra.ui.screen.SwitchItem
import com.sukisu.ultra.ui.theme.*
import com.sukisu.ultra.ui.util.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -49,7 +45,6 @@ import zako.zako.zako.zakoui.screen.moreSettings.component.SettingItem
import zako.zako.zako.zakoui.screen.moreSettings.component.SettingsCard
import zako.zako.zako.zakoui.screen.moreSettings.component.SettingsDivider
import zako.zako.zako.zakoui.screen.moreSettings.component.SwitchSettingItem
import zako.zako.zako.zakoui.screen.moreSettings.component.UidScannerSection
import zako.zako.zako.zakoui.screen.moreSettings.state.MoreSettingsState
import kotlin.math.roundToInt
@ -346,11 +341,6 @@ private fun AdvancedSettings(
state: MoreSettingsState,
handlers: MoreSettingsHandlers
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val snackBarHost = remember { SnackbarHostState() }
val prefs = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) }
SettingsCard(title = stringResource(R.string.advanced_settings)) {
// SELinux 开关
SwitchSettingItem(
@ -363,27 +353,6 @@ private fun AdvancedSettings(
onChange = handlers::handleSelinuxChange
)
var forceSignatureVerification by rememberSaveable {
mutableStateOf(prefs.getBoolean("force_signature_verification", false))
}
// 强制签名验证开关
SwitchItem(
icon = Icons.Filled.Security,
title = stringResource(R.string.module_signature_verification),
summary = stringResource(R.string.module_signature_verification_summary),
checked = forceSignatureVerification,
onCheckedChange = { enabled ->
prefs.edit { putBoolean("force_signature_verification", enabled) }
forceSignatureVerification = enabled
}
)
// UID 扫描开关
if (Natives.version >= Natives.MINIMAL_SUPPORTED_UID_SCANNER && Natives.version >= Natives.MINIMAL_NEW_IOCTL_KERNEL) {
UidScannerSection(prefs, snackBarHost, scope, context)
}
// 动态管理器设置
if (Natives.version >= Natives.MINIMAL_SUPPORTED_DYNAMIC_MANAGER && Natives.version >= Natives.MINIMAL_NEW_IOCTL_KERNEL) {
SettingItem(

View file

@ -6,38 +6,15 @@ import android.content.SharedPreferences
import android.content.res.Configuration
import android.net.Uri
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CleaningServices
import androidx.compose.material.icons.filled.Groups
import androidx.compose.material.icons.filled.Scanner
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.content.edit
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.component.ConfirmResult
import com.sukisu.ultra.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.screen.SettingItem
import com.sukisu.ultra.ui.screen.SwitchItem
import com.sukisu.ultra.ui.theme.*
import com.sukisu.ultra.ui.util.*
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import zako.zako.zako.zakoui.screen.moreSettings.state.MoreSettingsState
import zako.zako.zako.zakoui.screen.moreSettings.util.toggleLauncherIcon

View file

@ -1,23 +1,13 @@
package zako.zako.zako.zakoui.screen.moreSettings.component
import android.content.Context
import android.content.SharedPreferences
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.CleaningServices
import androidx.compose.material.icons.filled.Groups
import androidx.compose.material.icons.filled.Scanner
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -30,22 +20,9 @@ import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import com.sukisu.ultra.Natives
import zako.zako.zako.zakoui.screen.moreSettings.util.LocaleHelper
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.component.ConfirmResult
import com.sukisu.ultra.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.screen.SwitchItem
import com.sukisu.ultra.ui.theme.*
import com.sukisu.ultra.ui.util.cleanRuntimeEnvironment
import com.sukisu.ultra.ui.util.getUidMultiUserScan
import com.sukisu.ultra.ui.util.readUidScannerFile
import com.sukisu.ultra.ui.util.setUidAutoScan
import com.sukisu.ultra.ui.util.setUidMultiUserScan
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import zako.zako.zako.zakoui.screen.moreSettings.MoreSettingsHandlers
import zako.zako.zako.zakoui.screen.moreSettings.state.MoreSettingsState
@ -496,125 +473,4 @@ fun DynamicManagerDialog(
}
}
)
}
@Composable
fun UidScannerSection(
prefs: SharedPreferences,
snackBarHost: SnackbarHostState,
scope: CoroutineScope,
context: Context
) {
if (Natives.version < Natives.MINIMAL_SUPPORTED_UID_SCANNER) return
val realAuto = Natives.isUidScannerEnabled()
val realMulti = getUidMultiUserScan()
var autoOn by remember { mutableStateOf(realAuto) }
var multiOn by remember { mutableStateOf(realMulti) }
LaunchedEffect(Unit) {
autoOn = realAuto
multiOn = realMulti
prefs.edit {
putBoolean("uid_auto_scan", autoOn)
putBoolean("uid_multi_user_scan", multiOn)
}
}
SwitchItem(
icon = Icons.Filled.Scanner,
title = stringResource(R.string.uid_auto_scan_title),
summary = stringResource(R.string.uid_auto_scan_summary),
checked = autoOn,
onCheckedChange = { target ->
autoOn = target
if (!target) multiOn = false
scope.launch(Dispatchers.IO) {
setUidAutoScan(target)
val actual = Natives.isUidScannerEnabled() || readUidScannerFile()
withContext(Dispatchers.Main) {
autoOn = actual
if (!actual) multiOn = false
prefs.edit {
putBoolean("uid_auto_scan", actual)
putBoolean("uid_multi_user_scan", multiOn)
}
if (actual != target) {
snackBarHost.showSnackbar(
context.getString(R.string.uid_scanner_setting_failed)
)
}
}
}
}
)
AnimatedVisibility(
visible = autoOn,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
SwitchItem(
icon = Icons.Filled.Groups,
title = stringResource(R.string.uid_multi_user_scan_title),
summary = stringResource(R.string.uid_multi_user_scan_summary),
checked = multiOn,
onCheckedChange = { target ->
scope.launch(Dispatchers.IO) {
val ok = setUidMultiUserScan(target)
withContext(Dispatchers.Main) {
if (ok) {
multiOn = target
prefs.edit { putBoolean("uid_multi_user_scan", target) }
} else {
snackBarHost.showSnackbar(
context.getString(R.string.uid_scanner_setting_failed)
)
}
}
}
}
)
}
AnimatedVisibility(
visible = autoOn,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
val confirmDialog = rememberConfirmDialog()
com.sukisu.ultra.ui.screen.SettingItem(
icon = Icons.Filled.CleaningServices,
title = stringResource(R.string.clean_runtime_environment),
summary = stringResource(R.string.clean_runtime_environment_summary),
onClick = {
scope.launch {
if (confirmDialog.awaitConfirm(
title = context.getString(R.string.clean_runtime_environment),
content = context.getString(R.string.clean_runtime_environment_confirm)
) == ConfirmResult.Confirmed
) {
if (cleanRuntimeEnvironment()) {
autoOn = false
multiOn = false
prefs.edit {
putBoolean("uid_auto_scan", false)
putBoolean("uid_multi_user_scan", false)
}
Natives.setUidScannerEnabled(false)
snackBarHost.showSnackbar(
context.getString(R.string.clean_runtime_environment_success)
)
} else {
snackBarHost.showSnackbar(
context.getString(R.string.clean_runtime_environment_failed)
)
}
}
}
}
)
}
}

View file

@ -70,6 +70,9 @@ class MoreSettingsState(
// SELinux状态
var selinuxEnabled by mutableStateOf(false)
// SuSFS 状态
var isSusFSEnabled by mutableStateOf(true)
// 卡片配置状态
var cardAlpha by mutableFloatStateOf(CardConfig.cardAlpha)
var cardDim by mutableFloatStateOf(CardConfig.cardDim)

Binary file not shown.

View file

@ -126,6 +126,7 @@
<string name="selected_lkm">LKM المحددة: %s</string>
<string name="save_log">حفظ السجلات</string>
<string name="log_saved">السجلات محفوظة</string>
<string name="sus_su_mode">وضع SuS SU</string>
<!-- Module related -->
<string name="module_install_confirm">تأكيد وحدة التثبيت %1$s؟</string>
<string name="unknown_module">وحدة غير معروفة</string>
@ -275,6 +276,8 @@
<string name="advanced_settings">إعدادات متقدمة</string>
<string name="appearance_settings">تخصيص شريط الأدوات</string>
<string name="back">عد مرة أخرى</string>
<string name="susfs_enabled">تم تمكين SuSFS</string>
<string name="susfs_disabled">تم تعطيل SuSFS</string>
<string name="background_set_success">تم تعيين الخلفية بنجاح</string>
<string name="background_removed">إزالة خلفيات مخصصة</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">Girişləri Saxla</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">Sačuvaj Dnevnike</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">Gem Logfiler</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -126,6 +126,7 @@
<string name="selected_lkm">Wähle LKM: %s</string>
<string name="save_log">Protokolle Speichern</string>
<string name="log_saved">Protokolle gespeichert</string>
<string name="sus_su_mode">SuS SU-Modus:</string>
<!-- Module related -->
<string name="module_install_confirm">das Installationsmodul %1$s bestätigen ?</string>
<string name="unknown_module">unbekannter Modul</string>
@ -275,6 +276,8 @@
<string name="advanced_settings">Erweiterte Einstellungen</string>
<string name="appearance_settings">Passt die Symbolleiste an.</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS aktiviert</string>
<string name="susfs_disabled">SuSFS deaktiviert</string>
<string name="background_set_success">Hintergrund erfolgreich gesetzt</string>
<string name="background_removed">Eigene Hintergründe entfernt</string>
<string name="icon_switch_title">Alternatives Symbol</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">LKM seleccionado: %s</string>
<string name="save_log">Guardar registros</string>
<string name="log_saved">Registro guardado</string>
<string name="sus_su_mode">Modo SuS SU:</string>
<!-- Module related -->
<string name="module_install_confirm">¿confirmar la instalación del módulo %1$s?</string>
<string name="unknown_module">módulo desconocido</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Configuraciones avanzadas</string>
<string name="appearance_settings">Personalizar la barra de herramientas.</string>
<string name="back">Retorno</string>
<string name="susfs_enabled">SuSFS activado</string>
<string name="susfs_disabled">SuSFS desactivado</string>
<string name="background_set_success">Fondo establecido correctamente</string>
<string name="background_removed">Eliminar fondo personalizado</string>
<string name="icon_switch_title">Icono alternativo</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Valitud LKM: %s</string>
<string name="save_log">Salvesta Logid</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">ذخیره گزارش‌ها</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">I-save ang mga Log</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -126,6 +126,7 @@
<string name="selected_lkm">LKM sélectionné: %s</string>
<string name="save_log">Enregistrer les journaux</string>
<string name="log_saved">Journaux enregistrés</string>
<string name="sus_su_mode">Mode Sus</string>
<!-- Module related -->
<string name="module_install_confirm">confirmer l\'installation du module %1$s?</string>
<string name="unknown_module">module inconnu</string>
@ -275,6 +276,8 @@
<string name="advanced_settings">Paramètres avancés</string>
<string name="appearance_settings">Choisir les boutons à afficher</string>
<string name="back">Reviens</string>
<string name="susfs_enabled">SuSFS activé</string>
<string name="susfs_disabled">SuSFS désactivé</string>
<string name="background_set_success">Fond d\'écran défini avec succès</string>
<string name="background_removed">Fond d\'écran personnalisé supprimé</string>
<string name="icon_switch_title">Icône alternative</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">लॉग सहेजें</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">Spremi Zapise</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Kiválasztott LKM: %s</string>
<string name="save_log">Naplók mentése</string>
<string name="log_saved">Mentett naplók</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -113,6 +113,7 @@ Gunakan opsi ini hanya setelah OTA selesai.
Lanjutkan?</string>
<string name="install_next">Lanjut</string>
<string name="select_file_tip">Disarankan gambar partisi %1$s</string>
<string name="select_file_tip_vendor">(tidak stabil)</string>
<string name="select_kmi">Pilih KMI</string>
<string name="settings_uninstall">Copot Pemasangan</string>
<string name="settings_uninstall_temporary">Copot Pemasangan Sementara</string>
@ -127,6 +128,7 @@ Lanjutkan?</string>
<string name="selected_lkm">LKM Terpilih: %s</string>
<string name="save_log">Simpan Log</string>
<string name="log_saved">Log Disimpan</string>
<string name="sus_su_mode">Mode SuS SU:</string>
<!-- Module related -->
<string name="module_install_confirm">Konfirmasi pemasangan modul %1$s?</string>
<string name="unknown_module">modul tidak dikenal</string>
@ -281,6 +283,8 @@ Tanamkan: Secara permanen memasang ke sistem</string>
<string name="advanced_settings">Pengaturan Lanjutan</string>
<string name="appearance_settings">Sesuaikan Bilah Alat</string>
<string name="back">Kembali</string>
<string name="susfs_enabled">SuSFS diaktifkan</string>
<string name="susfs_disabled">SuSFS dinonaktifkan</string>
<string name="background_set_success">Latar belakang berhasil diatur</string>
<string name="background_removed">Latar belakang khusus dihapus</string>
<string name="icon_switch_title">Ikon Alternatif</string>

View file

@ -114,6 +114,7 @@
<string name="install_upload_lkm_file">Gunakan berkas LKM lokal</string>
<string name="install_only_support_ko_file">Hanya berkas .ko yang didukung</string>
<string name="select_file_tip">%1$s image partisi terekomendasi</string>
<string name="select_file_tip_vendor">(tidak stabil)</string>
<string name="select_kmi">Pilih KMI</string>
<string name="settings_uninstall">Hapus</string>
<string name="settings_uninstall_temporary">Hapus sementara</string>
@ -128,6 +129,7 @@
<string name="selected_lkm">LKM dipilih: %s</string>
<string name="save_log">Simpan Log</string>
<string name="log_saved">Log disimpan</string>
<string name="sus_su_mode">Mode SuS SU:</string>
<!-- Module related -->
<string name="module_install_confirm">konfirmasi pemasangan modul %1$s?</string>
<string name="unknown_module">module tidak dikenal</string>
@ -288,6 +290,8 @@
<string name="advanced_settings">Pengaturan Lanjutan</string>
<string name="appearance_settings">Kustomisasi toolbar</string>
<string name="back">Kembali</string>
<string name="susfs_enabled">SuSFS dinyalakan</string>
<string name="susfs_disabled">SuSFS dimatikan</string>
<string name="background_set_success">Set latar belakang berhasil</string>
<string name="background_removed">Latar belakang khusus yang dihapus</string>
<string name="icon_switch_title">Ubah ikon</string>
@ -604,6 +608,7 @@
<string name="loop_paths_section">Jalur Loop</string>
<string name="add_loop_path">Tambahkan Jalur Loop</string>
<!-- 循环路径功能描述 -->
<string name="sus_loop_path_feature_label">Jalur Loop SUS</string>
<string name="sus_loop_paths_description_title">Konfigurasi Jalur Loop</string>
<string name="sus_loop_paths_description_text">Jalur loop ditandai ulang sebagai SUS_PATH pada setiap startup aplikasi pengguna non-root atau layanan terisolasi. Ini membantu mengatasi masalah di mana jalur yang ditambahkan mungkin memiliki status inode direset atau inode dibuat ulang di kernel.</string>
<string name="avc_log_spoofing">Palsukan log AVC</string>
@ -657,6 +662,7 @@ Diaktifkan: Aktifkan pemalsuan sus tcontext dari \'su\' dengan \'kernel\' yang d
<string name="uid_multi_user_scan_title">Pemindaian Aplikasi Multi-Pengguna</string>
<string name="uid_multi_user_scan_summary">Ketika diaktifkan, fitur ini akan memindai aplikasi untuk semua pengguna, termasuk profil kerja</string>
<string name="uid_scanner_setting_failed">Gagal mengatur, silakan periksa perizinan</string>
<string name="uid_scanner_setting_error">Gagal mengatur: %s</string>
<string name="clean_runtime_environment">Bersihkan Lingkungan Runtime</string>
<string name="clean_runtime_environment_summary">Bersihkan berkas runtime dan hentikan layanan pemindai</string>
<string name="clean_runtime_environment_confirm">Apakah Anda yakin ingin membersihkan lingkungan runtime? Tindakan ini akan menghentikan layanan pemindai dan menghapus berkas yang terkait.</string>

View file

@ -126,6 +126,7 @@
<string name="selected_lkm">LKM selezionato: %s</string>
<string name="save_log">Salva Registri</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -275,6 +276,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -112,6 +112,7 @@
\n続行しますか</string>
<string name="install_next">次へ</string>
<string name="select_file_tip">%1$s のパーティションイメージを推奨します。</string>
<string name="select_file_tip_vendor">(不安定)</string>
<string name="select_kmi">KMI を選択してください</string>
<string name="settings_uninstall">アンインストール</string>
<string name="settings_uninstall_temporary">一時的にアンインストールする</string>
@ -126,6 +127,7 @@
<string name="selected_lkm">選択された LKM: %s</string>
<string name="save_log">ログを保存</string>
<string name="log_saved">保存されたログ</string>
<string name="sus_su_mode">SuS SU モード:</string>
<!-- Module related -->
<string name="module_install_confirm">%1$s モジュールをインストールしますか?</string>
<string name="unknown_module">不明なモジュール</string>
@ -279,6 +281,8 @@
<string name="advanced_settings">高度な設定</string>
<string name="appearance_settings">ツールバーをカスタマイズ</string>
<string name="back">戻る</string>
<string name="susfs_enabled">SuSFS 有効</string>
<string name="susfs_disabled">SuSFS 無効</string>
<string name="background_set_success">背景の設定が成功しました</string>
<string name="background_removed">カスタム背景を削除しました</string>
<string name="icon_switch_title">代替アイコン</string>
@ -593,6 +597,7 @@
<string name="loop_paths_section">ループパス</string>
<string name="add_loop_path">ループパスを追加</string>
<!-- 循环路径功能描述 -->
<string name="sus_loop_path_feature_label">SUS ループパス</string>
<string name="sus_loop_paths_description_title">ループパスの構成</string>
<string name="sus_loop_paths_description_text">ループパスは、非 root ユーザーアプリまたは独立したサービスの起動ごとに SUS_PATH として再設定されます。これにより、追加されたパスの inode ステータスがリセットされたり、カーネル内で inode が再生成される問題に対処できます。</string>
<string name="avc_log_spoofing">AVC ログの偽装</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">ಲಾಗ್ಗಳನ್ನು ಉಳಿಸಿ</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">선택된 LKM: %s</string>
<string name="save_log">로그 저장</string>
<string name="log_saved">로그 저장됨</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">Saglabāt Žurnālus</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -126,6 +126,7 @@
<string name="selected_lkm">Izvēlētais lkm: %s</string>
<string name="save_log">Išsaugoti Žurnalus</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -275,6 +276,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">लॉग जतन करा</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">Simpan Log</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -126,6 +126,7 @@
<string name="selected_lkm">Geselecteerde LKM: %s</string>
<string name="save_log">Logboeken Opslaan</string>
<string name="log_saved">Logs opgeslagen</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -275,6 +276,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -126,6 +126,7 @@
<string name="selected_lkm">Wybrano LKM: %s</string>
<string name="save_log">Zapisz dzienniki</string>
<string name="log_saved">Dzienniki zapisane</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -275,6 +276,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">LKM selecionado: %s</string>
<string name="save_log">Salvar Registros</string>
<string name="log_saved">Registros salvos</string>
<string name="sus_su_mode">Modo SU SuSU:</string>
<!-- Module related -->
<string name="module_install_confirm">¿confirmar la instalación del módulo %1$s?</string>
<string name="unknown_module">módulo desconocido</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Configurações Avançadas</string>
<string name="appearance_settings">Personaliza a barra de ferramentas.</string>
<string name="back">Retorno</string>
<string name="susfs_enabled">SuSFS habilitado</string>
<string name="susfs_disabled">SuSFS desativado</string>
<string name="background_set_success">Fundo definido com sucesso</string>
<string name="background_removed">Remover</string>
<string name="icon_switch_title">Ícone alternativo</string>

View file

@ -126,6 +126,7 @@
<string name="selected_lkm">Lkm selectat: %s</string>
<string name="save_log">Salvează Jurnale</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -275,6 +276,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -115,6 +115,7 @@
<string name="install_upload_lkm_file">Использовать локальный файл LKM</string>
<string name="install_only_support_ko_file">Поддерживаются только файлы .ko</string>
<string name="select_file_tip">Образ раздела %1$s рекомендуется</string>
<string name="select_file_tip_vendor">(нестабильный)</string>
<string name="select_kmi">Выбрать KMI</string>
<string name="settings_uninstall">Удалить</string>
<string name="settings_uninstall_temporary">Удалить на время</string>
@ -129,6 +130,7 @@
<string name="selected_lkm">Выбран LKM: %s</string>
<string name="save_log">Сохранить логи</string>
<string name="log_saved">Логи сохранены</string>
<string name="sus_su_mode">SuS SU режим:</string>
<!-- Module related -->
<string name="module_install_confirm">подтвердите установку модуля %1$s?</string>
<string name="unknown_module">неизвестный модуль</string>
@ -289,6 +291,8 @@
<string name="advanced_settings">Расширенные</string>
<string name="appearance_settings">Внешний вид</string>
<string name="back">Возвращение</string>
<string name="susfs_enabled">SuSFS включен</string>
<string name="susfs_disabled">SuSFS выключен</string>
<string name="background_set_success">Фон успешно установлен</string>
<string name="background_removed">Пользовательский фон удалён</string>
<string name="icon_switch_title">Альт. иконка</string>
@ -605,6 +609,7 @@
<string name="loop_paths_section">Циклические пути</string>
<string name="add_loop_path">Добавить циклический путь</string>
<!-- 循环路径功能描述 -->
<string name="sus_loop_path_feature_label">Циклический путь SUS</string>
<string name="sus_loop_paths_description_title">Конфигурация пути цикла</string>
<string name="sus_loop_paths_description_text">Пути цикла повторно отмечены как SUS_PATH в каждом пользовательском приложении, не являющемся root, или изолированном запуске службы. Это помогает решить проблемы, в которых добавленные пути могут иметь сброс статуса inode или повторно созданные inode в ядре.</string>
<string name="avc_log_spoofing">Спуф AVC лога</string>
@ -657,6 +662,7 @@
<string name="uid_multi_user_scan_title">Поиск многопользовательских приложений</string>
<string name="uid_multi_user_scan_summary">Когда включено, сканирует приложения для всех пользователей, включая рабочие профили</string>
<string name="uid_scanner_setting_failed">Не удалось установить, проверьте права доступа</string>
<string name="uid_scanner_setting_error">Не удалось установить: %s</string>
<string name="clean_runtime_environment">Очистить среду Runtime</string>
<string name="clean_runtime_environment_summary">Очистить среду Runtime и остановить службу сканирования</string>
<string name="clean_runtime_environment_confirm">Вы уверены, что хотите очистить среду Runtime? Это остановит службу сканирования и удалит связанные с ней файлы.</string>
@ -699,6 +705,9 @@
<string name="log_viewer_clear_logs">Очистить логи</string>
<string name="log_viewer_clear_logs_confirm">Вы уверены, что хотите удалить выбранный файл журнала? Это действие нельзя отменить.</string>
<string name="log_viewer_logs_cleared">Логи успешно очищены</string>
<string name="log_viewer_select_file">Выберите файл журнала</string>
<string name="log_viewer_current_log">Текущий лог</string>
<string name="log_viewer_old_log">Старый лог</string>
<string name="log_viewer_filter_type">Фильтрация по типу</string>
<string name="log_viewer_all_types">Все типы</string>
<string name="log_viewer_showing_entries">Показаны записи %1$d из %2$d</string>
@ -726,6 +735,8 @@
<string name="umount_path_restart_notice">Для применения изменений необходима перезагрузка. Система применит новый конфиг при следующем запуске.</string>
<string name="add_umount_path">Добавить путь размонтирования</string>
<string name="mount_path">Смонтировать путь</string>
<string name="check_mount_type">Проверить тип монтирования</string>
<string name="check_mount_type_overlay">Проверить, является ли оверлеем</string>
<string name="umount_flags">Флаги размонтирования</string>
<string name="umount_flags_hint">0=Обычное размонтирование, 8=MNT_DETACH, -1=Автоматически</string>
<string name="flags">Флаги</string>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">Shrani Dnevnike</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -273,6 +274,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -126,6 +126,7 @@
<string name="selected_lkm">เลือก LKM: %s</string>
<string name="save_log">บันทึกบันทึก</string>
<string name="log_saved">บันทึก Log แล้ว</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
@ -275,6 +276,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>

View file

@ -112,6 +112,7 @@
<string name="install_upload_lkm_file">Yerel LKM dosyası kullan</string>
<string name="install_only_support_ko_file">Yalnızca .ko dosyaları desteklenir</string>
<string name="select_file_tip">%1$s bölüm görüntüsü önerilir</string>
<string name="select_file_tip_vendor">(kararsız)</string>
<string name="select_kmi">KMI seçin</string>
<string name="settings_uninstall">Kaldır</string>
<string name="settings_uninstall_temporary">Geçici olarak kaldır</string>
@ -126,6 +127,7 @@
<string name="selected_lkm">Seçilen LKM: %s</string>
<string name="save_log">Günlükleri kaydet</string>
<string name="log_saved">Günlükler kaydedildi</string>
<string name="sus_su_mode">SuS SU modu:</string>
<!-- Module related -->
<string name="module_install_confirm">%1$s modülünü yüklemek istediğinizden emin misiniz?</string>
<string name="unknown_module">Bilinmeyen modül</string>
@ -286,6 +288,8 @@
<string name="advanced_settings">Gelişmiş Ayarlar</string>
<string name="appearance_settings">Araç çubuğunu özelleştir</string>
<string name="back">Geri</string>
<string name="susfs_enabled">SuSFS etkin</string>
<string name="susfs_disabled">SuSFS devre dışı</string>
<string name="background_set_success">Arka plan başarıyla ayarlandı</string>
<string name="background_removed">Özel arka planlar kaldırıldı</string>
<string name="icon_switch_title">Alternatif simge</string>
@ -602,6 +606,7 @@
<string name="loop_paths_section">Döngü Yolları</string>
<string name="add_loop_path">Döngü Yolu Ekle</string>
<!-- 循环路径功能描述 -->
<string name="sus_loop_path_feature_label">SUS Döngü Yolu</string>
<string name="sus_loop_paths_description_title">Döngü Yolu Yapılandırması</string>
<string name="sus_loop_paths_description_text">Döngü yolları, her kök olmayan (non-root) kullanıcı uygulaması veya yalıtılmış hizmet başlangıcında SUS_PATH olarak yeniden işaretlenir. Bu, eklenen yolların inode durumunun sıfırlanması veya çekirdekte yeniden oluşturulması gibi sorunları gidermeye yardımcı olur.</string>
<string name="avc_log_spoofing">AVC Günlük Kaydı Taklidi</string>
@ -655,6 +660,7 @@ etkin: Çekirdekteki AVC günlük kaydında, \'su\' komutuna ait tcontext\'i \'k
<string name="uid_multi_user_scan_title">Çok Kullanıcılı Uygulama Taraması</string>
<string name="uid_multi_user_scan_summary">Etkinleştirildiğinde, iş profilleri de dahil olmak üzere tüm kullanıcıların uygulamalarını tarar.</string>
<string name="uid_scanner_setting_failed">Ayar başarısız oldu, lütfen izinleri kontrol edin</string>
<string name="uid_scanner_setting_error">Ayar başarısız oldu: %s</string>
<string name="clean_runtime_environment">Çalışma Zamanı Ortamını Temizle</string>
<string name="clean_runtime_environment_summary">Çalışma zamanı dosyalarını temizleyin ve tarayıcı hizmetini durdurun</string>
<string name="clean_runtime_environment_confirm">Çalışma zamanı ortamını temizlemek istediğinizden emin misiniz? Bu işlem tarayıcı hizmetini durduracak ve ilgili dosyaları kaldıracaktır.</string>
@ -697,6 +703,9 @@ etkin: Çekirdekteki AVC günlük kaydında, \'su\' komutuna ait tcontext\'i \'k
<string name="log_viewer_clear_logs">Günlükleri Temizle</string>
<string name="log_viewer_clear_logs_confirm">Seçili günlük dosyasını temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.</string>
<string name="log_viewer_logs_cleared">Günlükler başarıyla temizlendi</string>
<string name="log_viewer_select_file">Günlük Dosyası Seç</string>
<string name="log_viewer_current_log">Mevcut Günlük</string>
<string name="log_viewer_old_log">Eski Günlük</string>
<string name="log_viewer_filter_type">Türe Göre Filtrele</string>
<string name="log_viewer_all_types">Tüm Türler</string>
<string name="log_viewer_showing_entries">%2$d girişten %1$d tanesi gösteriliyor</string>

View file

@ -111,6 +111,7 @@
<string name="install_inactive_slot_warning">Ваш пристрій буде **ПРИМУСОВО** завантажено в поточний неактивний слот після перезавантаження!\nВикористовуйте цю опцію лише після завершення OTA.\nПродовжити?</string>
<string name="install_next">Далі</string>
<string name="select_file_tip">Рекомендується образ розділу %1$s</string>
<string name="select_file_tip_vendor">(нестабільно)</string>
<string name="select_kmi">Вибрати KMI</string>
<string name="settings_uninstall">Видалити</string>
<string name="settings_uninstall_temporary">Тимчасово видалити</string>
@ -125,6 +126,7 @@
<string name="selected_lkm">Обраний LKM: %s</string>
<string name="save_log">Зберегти логи</string>
<string name="log_saved">Логи збережено</string>
<string name="sus_su_mode">Режим SuS SU:</string>
<!-- Module related -->
<string name="module_install_confirm">Підтвердити встановлення модуля %1$s?</string>
<string name="unknown_module">невідомий модуль</string>
@ -276,6 +278,8 @@
<string name="advanced_settings">Розширені налаштування</string>
<string name="appearance_settings">Налаштувати панель інструментів</string>
<string name="back">Повернутися</string>
<string name="susfs_enabled">SuSFS увімкнено</string>
<string name="susfs_disabled">SuSFS вимкнено</string>
<string name="background_set_success">Фон успішно встановлено</string>
<string name="background_removed">Видалено власні фони</string>
<string name="icon_switch_title">Альтернативна іконка</string>

View file

@ -110,6 +110,7 @@
<string name="install_inactive_slot_warning">Thiết bị của bạn sẽ **BUỘC** phải khởi động vào phân vùng chưa được sử dụng!\nChỉ sử dụng tùy chọn này sau khi cập nhật OTA hoàn tất.\nTiếp tục?</string>
<string name="install_next">Kế tiếp</string>
<string name="select_file_tip">Phân vùng %1$s được khuyến nghị</string>
<string name="select_file_tip_vendor">(Thử nghiệm)</string>
<string name="select_kmi">Chọn KMI</string>
<string name="settings_uninstall">Gỡ cài đặt</string>
<string name="settings_uninstall_temporary">Gỡ cài đặt tạm thời</string>
@ -124,6 +125,7 @@
<string name="selected_lkm">Đã chọn LKM: %s</string>
<string name="save_log">Lưu nhật ký</string>
<string name="log_saved">Nhật ký đã được lưu</string>
<string name="sus_su_mode">Chế độ SuS SU:</string>
<!-- Module related -->
<string name="module_install_confirm">Xác nhận cài đặt module %1$s</string>
<string name="unknown_module">Module không xác định</string>
@ -277,6 +279,8 @@
<string name="advanced_settings">Cài đặt nâng cao</string>
<string name="appearance_settings">Cài đặt giao diện</string>
<string name="back">Trở lại</string>
<string name="susfs_enabled">SuSFS đã bật</string>
<string name="susfs_disabled">SuSFS đã tắt</string>
<string name="background_set_success">Đã cài đặt hình nền thành công</string>
<string name="background_removed">Đã xóa hình nền tùy chỉnh</string>
<string name="icon_switch_title">Thay thế icon</string>
@ -593,6 +597,7 @@
<string name="loop_paths_section">Đường dẫn Vòng lặp</string>
<string name="add_loop_path">Thêm Đường dẫn Vòng lặp</string>
<!-- 循环路径功能描述 -->
<string name="sus_loop_path_feature_label">Đường dẫn Vòng lặp SuS</string>
<string name="sus_loop_paths_description_title">Cấu hình Đường dẫn Vòng lặp</string>
<string name="sus_loop_paths_description_text">Đường dẫn Vòng lặp được đổi tên thành SUS_PATH mỗi khi một ứng dụng không phải root hoặc dịch vụ cô lập được khởi động. Điều này giúp giải quyết vấn đề đường dẫn đã thêm có thể trở nên không hợp lệ do trạng thái inode được đặt lại hoặc inode được tạo lại trong Kernel</string>
<string name="avc_log_spoofing">Giả mạo nhật ký AVC</string>
@ -646,6 +651,7 @@ Bật: Kích hoạt tính năng giả mạo sus tcontext của \'su\' thành \'k
<string name="uid_multi_user_scan_title">Quét ứng dụng nhiều người dùng</string>
<string name="uid_multi_user_scan_summary">Khi được bật, tất cả ứng dụng của người dùng sẽ được quét, bao gồm cả dữ liệu công việc, v.v</string>
<string name="uid_scanner_setting_failed">Thiết lập thất bại, vui lòng kiểm tra quyền</string>
<string name="uid_scanner_setting_error">Thiết lập thất bại: %s</string>
<string name="clean_runtime_environment">Dọn dẹp môi trường hoạt động</string>
<string name="clean_runtime_environment_summary">Dọn dẹp các file hoạt động và dừng quét các dịch vụ</string>
<string name="clean_runtime_environment_confirm">Bạn có chắc chắn muốn dọn dẹp môi trường hoạt động không? Thao tác này sẽ dừng dịch vụ quét và xóa các file liên quan</string>
@ -688,6 +694,9 @@ Bật: Kích hoạt tính năng giả mạo sus tcontext của \'su\' thành \'k
<string name="log_viewer_clear_logs">Xoá nhật ký</string>
<string name="log_viewer_clear_logs_confirm">Bạn có chắc chắn muốn xóa tệp nhật ký đã chọn không? Thao tác này không thể hoàn tác</string>
<string name="log_viewer_logs_cleared">Đã xoá nhật ký thành công</string>
<string name="log_viewer_select_file">Chọn tệp nhật ký</string>
<string name="log_viewer_current_log">Nhật ký hiện tại</string>
<string name="log_viewer_old_log">Nhật ký cũ</string>
<string name="log_viewer_filter_type">Lọc theo loại</string>
<string name="log_viewer_all_types">Tất cả các loại</string>
<string name="log_viewer_showing_entries">Hiển thị %1$d trong tổng %2$d nhật ký</string>

View file

@ -113,6 +113,7 @@
<string name="install_upload_lkm_file">使用本地 LKM 文件</string>
<string name="install_only_support_ko_file">仅支持选择 .ko 文件</string>
<string name="select_file_tip">建议选择 %1$s 分区镜像</string>
<string name="select_file_tip_vendor">(实验性的)</string>
<string name="select_kmi">选择 KMI</string>
<string name="settings_uninstall">卸载</string>
<string name="settings_uninstall_temporary">临时卸载</string>
@ -127,6 +128,7 @@
<string name="selected_lkm">已选择的 LKM%s</string>
<string name="save_log">保存日志</string>
<string name="log_saved">日志已保存</string>
<string name="sus_su_mode">SuS SU 模式:</string>
<!-- Module related -->
<string name="module_install_confirm">确认安装模块 %1$s</string>
<string name="unknown_module">未知模块</string>
@ -168,8 +170,6 @@
<string name="settings_disable_kernel_umount_summary">关闭 KernelSU 控制的内核级 umount 行为。</string>
<string name="settings_enable_enhanced_security">增强安全性</string>
<string name="settings_enable_enhanced_security_summary">使用更严格的安全策略。</string>
<string name="feature_status_unsupported_summary">内核不支持此功能。</string>
<string name="feature_status_managed_summary">此功能由模块管理。</string>
<string name="settings_mode_default">默认</string>
<string name="settings_mode_temp_enable">临时启用</string>
<string name="settings_mode_always_enable">始终启用</string>
@ -289,6 +289,8 @@
<string name="advanced_settings">高级设置</string>
<string name="appearance_settings">外观设置</string>
<string name="back">返回</string>
<string name="susfs_enabled">SuSFS 已启用</string>
<string name="susfs_disabled">SuSFS 已禁用</string>
<string name="background_set_success">背景设置成功</string>
<string name="background_removed">已移除自定义背景</string>
<string name="icon_switch_title">备选图标</string>
@ -605,6 +607,7 @@
<string name="loop_paths_section">循环路径</string>
<string name="add_loop_path">添加循环路径</string>
<!-- 循环路径功能描述 -->
<string name="sus_loop_path_feature_label">SuS 循环路径</string>
<string name="sus_loop_paths_description_title">循环路径配置</string>
<string name="sus_loop_paths_description_text">循环路径会在每次非 root 用户应用或隔离服务启动时重新标记为 SUS_PATH。这有助于解决添加的路径可能因 inode 状态重置或内核中 inode 重新创建而失效的问题</string>
<string name="avc_log_spoofing">AVC 日志欺骗</string>
@ -658,6 +661,7 @@
<string name="uid_multi_user_scan_title">多用户应用扫描</string>
<string name="uid_multi_user_scan_summary">开启后将扫描所有用户的应用,包括工作资料等</string>
<string name="uid_scanner_setting_failed">设置失败,请检查权限</string>
<string name="uid_scanner_setting_error">设置失败: %s</string>
<string name="clean_runtime_environment">清理运行环境</string>
<string name="clean_runtime_environment_summary">清理运行时文件并停止扫描服务</string>
<string name="clean_runtime_environment_confirm">您确定要清理运行环境吗?这将停止扫描服务并删除相关文件</string>
@ -700,6 +704,9 @@
<string name="log_viewer_clear_logs">清空日志</string>
<string name="log_viewer_clear_logs_confirm">确定要清空选中的日志文件吗?此操作无法撤销。</string>
<string name="log_viewer_logs_cleared">日志清空成功</string>
<string name="log_viewer_select_file">选择日志文件</string>
<string name="log_viewer_current_log">当前日志</string>
<string name="log_viewer_old_log">旧日志</string>
<string name="log_viewer_filter_type">按类型筛选</string>
<string name="log_viewer_all_types">所有类型</string>
<string name="log_viewer_showing_entries">显示 %1$d / %2$d 条记录</string>
@ -726,6 +733,8 @@
<string name="umount_path_restart_notice">添加或删除路径后需要重启设备才能生效。系统会在下次启动时应用新的配置。</string>
<string name="add_umount_path">添加 Umount 路径</string>
<string name="mount_path">挂载路径</string>
<string name="check_mount_type">检查挂载类型</string>
<string name="check_mount_type_overlay">检查是否为 overlay 类型</string>
<string name="umount_flags">卸载标志</string>
<string name="umount_flags_hint">0=正常卸载, 8=MNT_DETACH, -1=自动</string>
<string name="flags">标志</string>
@ -742,6 +751,4 @@
<string name="apply_config">应用配置</string>
<string name="config_applied">配置已应用到内核</string>
<string name="group_contains_apps">包含 %1$d 个应用</string>
<string name="settings_disable_sulog"> 禁用超级用户日志 </string>
<string name="settings_disable_sulog_summary">禁用 KernelSU 超级用户访问记录</string>
</resources>

View file

@ -124,6 +124,7 @@
<string name="selected_lkm">選擇嘅 LKM%s</string>
<string name="save_log">存儲日誌</string>
<string name="log_saved">日誌已存儲</string>
<string name="sus_su_mode">SuS SU 模式:</string>
<!-- Module related -->
<string name="module_install_confirm">確認安裝模組 %1$s</string>
<string name="unknown_module">未知模組</string>
@ -275,6 +276,8 @@
<string name="advanced_settings">高級配置</string>
<string name="appearance_settings">外觀配置</string>
<string name="back">返回</string>
<string name="susfs_enabled">SuSFS 已啟用</string>
<string name="susfs_disabled">SuSFS 已禁用</string>
<string name="background_set_success">背景設定成功</string>
<string name="background_removed">已移除自定義背景</string>
<string name="icon_switch_title">備選圖標</string>
@ -573,6 +576,7 @@
<string name="home_zygisk_implement">Zygisk 實現</string>
<!-- 循环路径相关 -->
<!-- 循环路径功能描述 -->
<string name="sus_loop_path_feature_label">SuS 循環路徑</string>
<string name="sus_loop_paths_description_title">循環路徑配置</string>
<string name="sus_loop_paths_description_text">循環路徑會喺每次非 root 用戶應用程式或者隔離服務啟動時,重新標記做 SUS_PATH。咁樣可以解決因為 inode 狀態重設或者核心重新建立 inode 而令到添加嘅路徑失效嘅問題。</string>
<string name="avc_log_spoofing">AVC 日誌欺騙</string>

View file

@ -110,6 +110,7 @@
<string name="install_inactive_slot_warning">將在重新啟動後強制切換至另一槽位!\n注意僅能在 OTA 更新完成後重新啟動前使用。\n確定繼續</string>
<string name="install_next">下一步</string>
<string name="select_file_tip">建議選擇 %1$s 分區映像檔</string>
<string name="select_file_tip_vendor">(實驗性的)</string>
<string name="select_kmi">選擇 KMI</string>
<string name="settings_uninstall">解除安裝</string>
<string name="settings_uninstall_temporary">臨時解除安裝</string>
@ -124,6 +125,7 @@
<string name="selected_lkm">已選擇的 LKM%s</string>
<string name="save_log">儲存日誌</string>
<string name="log_saved">日誌已儲存</string>
<string name="sus_su_mode">SuS SU 模式:</string>
<!-- Module related -->
<string name="module_install_confirm">確定安裝模組 %1$s</string>
<string name="unknown_module">未知模組</string>
@ -277,6 +279,8 @@
<string name="advanced_settings">進階設定</string>
<string name="appearance_settings">外觀設定</string>
<string name="back">返回</string>
<string name="susfs_enabled">SuSFS 已啟用</string>
<string name="susfs_disabled">SuSFS 已禁用</string>
<string name="background_set_success">背景設定成功</string>
<string name="background_removed">已移除自訂背景</string>
<string name="icon_switch_title">備用圖示</string>
@ -589,6 +593,7 @@
<string name="loop_paths_section">循環路徑</string>
<string name="add_loop_path">新增循環路徑</string>
<!-- 循环路径功能描述 -->
<string name="sus_loop_path_feature_label">SuS 循環路徑</string>
<string name="sus_loop_paths_description_title">循環路徑設定</string>
<string name="sus_loop_paths_description_text">循環路徑會在每次非 root 使用者應用程式或隔離服務啟動時重新標記為 SUS_PATH。這有助於解決新增的路徑可能因 inode 狀態重設或內核中 inode 重新建立而失效的問題</string>
<string name="avc_log_spoofing">AVC 日誌偽裝</string>
@ -642,6 +647,7 @@
<string name="uid_multi_user_scan_title">多使用者應用掃描</string>
<string name="uid_multi_user_scan_summary">開啟後將掃描所有使用者的應用,包括工作資料等</string>
<string name="uid_scanner_setting_failed">設定失敗,請檢查許可權</string>
<string name="uid_scanner_setting_error">設定失敗: %s</string>
<string name="clean_runtime_environment">清理執行環境</string>
<string name="clean_runtime_environment_summary">清理執行時檔案並停止掃描服務</string>
<string name="clean_runtime_environment_confirm">您確定要清理執行環境嗎?這將停止掃描服務並刪除相關檔案</string>

View file

@ -115,6 +115,7 @@
<string name="install_upload_lkm_file">Use local LKM file</string>
<string name="install_only_support_ko_file">Only .ko files are supported</string>
<string name="select_file_tip">%1$s partition image is recommended</string>
<string name="select_file_tip_vendor">(Unstable)</string>
<string name="select_kmi">Select KMI</string>
<string name="settings_uninstall">Uninstall</string>
<string name="settings_uninstall_temporary">Uninstall temporarily</string>
@ -129,6 +130,7 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">Save logs</string>
<string name="log_saved">Logs saved</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">Confirm install module %1$s</string>
<string name="unknown_module">Unknown module</string>
@ -170,8 +172,6 @@
<string name="settings_disable_kernel_umount_summary">Disable kernel-level umount behavior controlled by KernelSU.</string>
<string name="settings_enable_enhanced_security">Enable enhanced security</string>
<string name="settings_enable_enhanced_security_summary">Enable stricter security policies.</string>
<string name="feature_status_unsupported_summary">Kernel does not support this feature.</string>
<string name="feature_status_managed_summary">This feature is managed by a module.</string>
<string name="settings_mode_default">Default</string>
<string name="settings_mode_temp_enable">Temporarily enable</string>
<string name="settings_mode_always_enable">Permanently enable</string>
@ -292,6 +292,8 @@
<string name="advanced_settings">Advanced Settings</string>
<string name="appearance_settings">Customize the toolbar</string>
<string name="back">Comeback</string>
<string name="susfs_enabled">SuSFS enabled</string>
<string name="susfs_disabled">SuSFS disabled</string>
<string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string>
<string name="icon_switch_title">Alternate icon</string>
@ -608,6 +610,7 @@
<string name="loop_paths_section">Loop Paths</string>
<string name="add_loop_path">Add Loop Path</string>
<!-- 循环路径功能描述 -->
<string name="sus_loop_path_feature_label">SUS Loop Path</string>
<string name="sus_loop_paths_description_title">Loop Path Configuration</string>
<string name="sus_loop_paths_description_text">Loop paths are re-flagged as SUS_PATH on each non-root user app or isolated service startup. This helps address issues where added paths may have their inode status reset or inode re-created in the kernel</string>
<string name="avc_log_spoofing">AVC Log Spoofing</string>
@ -665,6 +668,7 @@ Important Note:\n
<string name="uid_multi_user_scan_title">Multi-User Application Scanning</string>
<string name="uid_multi_user_scan_summary">When enabled, scans applications for all users, including work profiles</string>
<string name="uid_scanner_setting_failed">Setting failed, please check permissions</string>
<string name="uid_scanner_setting_error">Setting failed: %s</string>
<string name="clean_runtime_environment">Clean Runtime Environment</string>
<string name="clean_runtime_environment_summary">Clean up runtime files and stop the scanner service</string>
<string name="clean_runtime_environment_confirm">Are you sure you want to clean the runtime environment? This will stop the scanner service and remove related files.</string>
@ -707,6 +711,9 @@ Important Note:\n
<string name="log_viewer_clear_logs">Clear Logs</string>
<string name="log_viewer_clear_logs_confirm">Are you sure you want to clear the selected log file? This action cannot be undone.</string>
<string name="log_viewer_logs_cleared">Logs cleared successfully</string>
<string name="log_viewer_select_file">Select Log File</string>
<string name="log_viewer_current_log">Current Log</string>
<string name="log_viewer_old_log">Old Log</string>
<string name="log_viewer_filter_type">Filter by Type</string>
<string name="log_viewer_all_types">All Types</string>
<string name="log_viewer_showing_entries">Showing %1$d of %2$d entries</string>
@ -735,6 +742,8 @@ Important Note:\n
<string name="umount_path_restart_notice">A reboot is required for changes to take effect. The system will apply the new configuration on the next boot.</string>
<string name="add_umount_path">Add Umount Path</string>
<string name="mount_path">Mount Path</string>
<string name="check_mount_type">Check Mount Type</string>
<string name="check_mount_type_overlay">Check if it is an overlay type</string>
<string name="umount_flags">Unmount Flags</string>
<string name="umount_flags_hint">0=Normal unmount, 8=MNT_DETACH, -1=Auto</string>
<string name="flags">Flags</string>
@ -752,6 +761,4 @@ Important Note:\n
<string name="config_applied">Configuration applied to kernel</string>
<string name="mnt_detach">MNT_DETACH</string>
<string name="group_contains_apps">Contains %d apps</string>
<string name="settings_disable_sulog">Disable superuser logging</string>
<string name="settings_disable_sulog_summary">Disable KernelSU superuser access logging</string>
</resources>

File diff suppressed because it is too large Load diff

View file

@ -6,11 +6,11 @@ edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
notify = "8.2"
notify = "6.1"
anyhow = "1"
clap = { version = "4", features = ["derive"] }
const_format = "0.2"
zip = { version = "6", features = [
zip = { version = "3", features = [
"deflate",
"deflate64",
"time",
@ -38,7 +38,7 @@ rust-embed = { version = "8", features = [
"debug-embed",
"compression", # must clean build after updating binaries
] }
which = "8"
which = "7"
getopts = "0.2"
sha256 = "1"
sha1 = "0.10"
@ -48,14 +48,14 @@ regex-lite = "0.1"
serde = { version = "1.0", features = ["derive"] }
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies]
rustix = { git = "https://github.com/Kernel-SU/rustix.git", rev = "4a53fbc7cb7a07cabe87125cc21dbc27db316259", features = [
rustix = { git = "https://github.com/Kernel-SU/rustix.git", branch = "main", features = [
"all-apis",
] }
# some android specific dependencies which compiles under unix are also listed here for convenience of coding
android-properties = { version = "0.2", features = ["bionic-deprecated"] }
[target.'cfg(target_os = "android")'.dependencies]
android_logger = { version = "0.15", default-features = false }
android_logger = { version = "0.14", default-features = false }
[profile.release]
overflow-checks = false

Binary file not shown.

Binary file not shown.

View file

@ -1,13 +1,15 @@
use anyhow::{Ok, Result};
use clap::Parser;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
#[cfg(target_os = "android")]
use android_logger::Config;
#[cfg(target_os = "android")]
use log::LevelFilter;
use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils};
use crate::{
apk_sign, assets, debug, defs, defs::KSUD_VERBOSE_LOG_FILE, init_event, ksucalls, module, utils,
};
/// KernelSU userspace cli
#[derive(Parser, Debug)]
@ -15,6 +17,9 @@ use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils};
struct Args {
#[command(subcommand)]
command: Commands,
#[arg(short, long, default_value_t = cfg!(debug_assertions))]
verbose: bool,
}
#[derive(clap::Subcommand, Debug)]
@ -147,11 +152,6 @@ enum Commands {
#[command(subcommand)]
command: Debug,
},
/// Kernel interface
Kernel {
#[command(subcommand)]
command: Kernel,
},
}
#[derive(clap::Subcommand, Debug)]
@ -184,7 +184,7 @@ enum Debug {
/// Set the manager app, kernel CONFIG_KSU_DEBUG should be enabled.
SetManager {
/// manager package name
#[arg(default_value_t = String::from("com.sukisu.ultra"))]
#[arg(default_value_t = String::from("me.weishu.kernelsu"))]
apk: String,
},
@ -204,6 +204,8 @@ enum Debug {
/// Get kernel version
Version,
Mount,
/// For testing
Test,
@ -270,14 +272,14 @@ enum Module {
zip: String,
},
/// Undo module uninstall mark <id>
UndoUninstall {
/// Uninstall module <id>
Uninstall {
/// module id
id: String,
},
/// Uninstall module <id>
Uninstall {
/// Restore module <id>
Restore {
/// module id
id: String,
},
@ -302,51 +304,6 @@ enum Module {
/// list all modules
List,
/// manage module configuration
Config {
#[command(subcommand)]
command: ModuleConfigCmd,
},
}
#[derive(clap::Subcommand, Debug)]
enum ModuleConfigCmd {
/// Get a config value
Get {
/// config key
key: String,
},
/// Set a config value
Set {
/// config key
key: String,
/// config value
value: String,
/// use temporary config (cleared on reboot)
#[arg(short, long)]
temp: bool,
},
/// List all config entries
List,
/// Delete a config entry
Delete {
/// config key
key: String,
/// delete from temporary config
#[arg(short, long)]
temp: bool,
},
/// Clear all config entries
Clear {
/// clear temporary config
#[arg(short, long)]
temp: bool,
},
}
#[derive(clap::Subcommand, Debug)]
@ -421,41 +378,6 @@ enum Feature {
Save,
}
#[derive(clap::Subcommand, Debug)]
enum Kernel {
/// Nuke ext4 sysfs
NukeExt4Sysfs {
/// mount point
mnt: String,
},
/// Manage umount list
Umount {
#[command(subcommand)]
command: UmountOp,
},
/// Notify that module is mounted
NotifyModuleMounted,
}
#[derive(clap::Subcommand, Debug)]
enum UmountOp {
/// Add mount point to umount list
Add {
/// mount point path
mnt: String,
/// umount flags (default: 0, MNT_DETACH: 2)
#[arg(short, long, default_value = "0")]
flags: u32,
},
/// Delete mount point from umount list
Del {
/// mount point path
mnt: String,
},
/// Wipe all entries from umount list
Wipe,
}
#[cfg(target_arch = "aarch64")]
mod kpm_cmd {
use clap::Subcommand;
@ -489,6 +411,7 @@ enum Umount {
/// Check mount type (overlay)
#[arg(long, default_value = "false")]
check_mnt: bool,
/// Umount flags (0 or 8 for MNT_DETACH)
#[arg(long, default_value = "-1")]
@ -504,7 +427,7 @@ enum Umount {
/// List all umount paths
List,
/// Clear all recorded umount paths
/// Clear all custom paths (keep defaults)
ClearCustom,
/// Save configuration to file
@ -536,6 +459,10 @@ pub fn run() -> Result<()> {
let cli = Args::parse();
if !cli.verbose && !Path::new(KSUD_VERBOSE_LOG_FILE).exists() {
log::set_max_level(LevelFilter::Info);
}
log::info!("command: {:?}", cli.command);
let result = match cli.command {
@ -549,72 +476,12 @@ pub fn run() -> Result<()> {
}
match command {
Module::Install { zip } => module::install_module(&zip),
Module::UndoUninstall { id } => module::undo_uninstall_module(&id),
Module::Uninstall { id } => module::uninstall_module(&id),
Module::Restore { id } => module::restore_uninstall_module(&id),
Module::Enable { id } => module::enable_module(&id),
Module::Disable { id } => module::disable_module(&id),
Module::Action { id } => module::run_action(&id),
Module::List => module::list_modules(),
Module::Config { command } => {
// Get module ID from environment variable
let module_id = std::env::var("KSU_MODULE").map_err(|_| {
anyhow::anyhow!("This command must be run in the context of a module")
})?;
use crate::module_config;
match command {
ModuleConfigCmd::Get { key } => {
// Use merge_configs to respect priority (temp overrides persist)
let config = module_config::merge_configs(&module_id)?;
match config.get(&key) {
Some(value) => {
println!("{}", value);
Ok(())
}
None => anyhow::bail!("Key '{}' not found", key),
}
}
ModuleConfigCmd::Set { key, value, temp } => {
// Validate input at CLI layer for better user experience
module_config::validate_config_key(&key)?;
module_config::validate_config_value(&value)?;
let config_type = if temp {
module_config::ConfigType::Temp
} else {
module_config::ConfigType::Persist
};
module_config::set_config_value(&module_id, &key, &value, config_type)
}
ModuleConfigCmd::List => {
let config = module_config::merge_configs(&module_id)?;
if config.is_empty() {
println!("No config entries found");
} else {
for (key, value) in config {
println!("{}={}", key, value);
}
}
Ok(())
}
ModuleConfigCmd::Delete { key, temp } => {
let config_type = if temp {
module_config::ConfigType::Temp
} else {
module_config::ConfigType::Persist
};
module_config::delete_config_value(&module_id, &key, config_type)
}
ModuleConfigCmd::Clear { temp } => {
let config_type = if temp {
module_config::ConfigType::Temp
} else {
module_config::ConfigType::Persist
};
module_config::clear_config(&module_id, config_type)
}
}
}
}
}
Commands::Install { magiskboot } => utils::install(magiskboot),
@ -657,6 +524,7 @@ pub fn run() -> Result<()> {
Ok(())
}
Debug::Su { global_mnt } => crate::su::grant_root(global_mnt),
Debug::Mount => init_event::mount_modules_systemlessly(),
Debug::Test => assets::ensure_binaries(false),
Debug::Mark { command } => match command {
MarkCommand::Get { pid } => debug::mark_get(pid),
@ -722,18 +590,6 @@ pub fn run() -> Result<()> {
magiskboot,
flash,
} => crate::boot_patch::restore(boot, magiskboot, flash),
Commands::Kernel { command } => match command {
Kernel::NukeExt4Sysfs { mnt } => ksucalls::nuke_ext4_sysfs(&mnt),
Kernel::Umount { command } => match command {
UmountOp::Add { mnt, flags } => ksucalls::umount_list_add(&mnt, flags),
UmountOp::Del { mnt } => ksucalls::umount_list_del(&mnt),
UmountOp::Wipe => ksucalls::umount_list_wipe().map_err(Into::into),
},
Kernel::NotifyModuleMounted => {
ksucalls::report_module_mounted();
Ok(())
}
},
#[cfg(target_arch = "aarch64")]
Commands::Kpm { command } => {
use crate::cli::kpm_cmd::Kpm;
@ -754,7 +610,11 @@ pub fn run() -> Result<()> {
}
}
Commands::Umount { command } => match command {
Umount::Add { path, flags } => crate::umount_manager::add_umount_path(&path, flags),
Umount::Add {
path,
check_mnt,
flags,
} => crate::umount_manager::add_umount_path(&path, check_mnt, flags),
Umount::Remove { path } => crate::umount_manager::remove_umount_path(&path),
Umount::List => crate::umount_manager::list_umount_paths(),
Umount::ClearCustom => crate::umount_manager::clear_custom_paths(),
@ -765,7 +625,11 @@ pub fn run() -> Result<()> {
};
if let Err(e) = &result {
log::error!("Error: {e:?}");
for c in e.chain() {
log::error!("{c:#?}");
}
log::error!("{:#?}", e.backtrace());
}
result
}

View file

@ -10,6 +10,7 @@ pub const PROFILE_SELINUX_DIR: &str = concatcp!(PROFILE_DIR, "selinux/");
pub const PROFILE_TEMPLATE_DIR: &str = concatcp!(PROFILE_DIR, "templates/");
pub const KSURC_PATH: &str = concatcp!(WORKING_DIR, ".ksurc");
pub const KSU_MOUNT_SOURCE: &str = "KSU";
pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "ksud");
pub const MAGISKBOOT_PATH: &str = concatcp!(BINARY_DIR, "magiskboot");
@ -17,24 +18,18 @@ pub const MAGISKBOOT_PATH: &str = concatcp!(BINARY_DIR, "magiskboot");
pub const DAEMON_LINK_PATH: &str = concatcp!(BINARY_DIR, "ksud");
pub const MODULE_DIR: &str = concatcp!(ADB_DIR, "modules/");
// warning: this directory should not change, or you need to change the code in module_installer.sh!!!
pub const MODULE_UPDATE_DIR: &str = concatcp!(ADB_DIR, "modules_update/");
pub const METAMODULE_DIR: &str = concatcp!(ADB_DIR, "metamodule/");
pub const KSUD_VERBOSE_LOG_FILE: &str = concatcp!(ADB_DIR, "verbose");
pub const MODULE_WEB_DIR: &str = "webroot";
pub const MODULE_ACTION_SH: &str = "action.sh";
pub const DISABLE_FILE_NAME: &str = "disable";
pub const UPDATE_FILE_NAME: &str = "update";
pub const REMOVE_FILE_NAME: &str = "remove";
// Module config system
pub const MODULE_CONFIG_DIR: &str = concatcp!(WORKING_DIR, "module_configs/");
pub const PERSIST_CONFIG_NAME: &str = "persist.config";
pub const TEMP_CONFIG_NAME: &str = "tmp.config";
// Metamodule support
pub const METAMODULE_MOUNT_SCRIPT: &str = "metamount.sh";
pub const METAMODULE_METAINSTALL_SCRIPT: &str = "metainstall.sh";
pub const METAMODULE_METAUNINSTALL_SCRIPT: &str = "metauninstall.sh";
pub const SKIP_MOUNT_FILE_NAME: &str = "skip_mount";
pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_CODE"));
pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_NAME"));
@ -42,3 +37,6 @@ pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_N
pub const KSU_BACKUP_DIR: &str = WORKING_DIR;
pub const KSU_BACKUP_FILE_PREFIX: &str = "ksu_backup_";
pub const BACKUP_FILENAME: &str = "stock_image.sha1";
pub const NO_TMPFS_PATH: &str = concatcp!(WORKING_DIR, ".notmpfs");
pub const NO_MOUNT_PATH: &str = concatcp!(WORKING_DIR, ".nomount");

View file

@ -17,7 +17,6 @@ pub enum FeatureId {
SuCompat = 0,
KernelUmount = 1,
EnhancedSecurity = 2,
SuLog = 3,
}
impl FeatureId {
@ -26,7 +25,6 @@ impl FeatureId {
0 => Some(FeatureId::SuCompat),
1 => Some(FeatureId::KernelUmount),
2 => Some(FeatureId::EnhancedSecurity),
3 => Some(FeatureId::SuLog),
_ => None,
}
}
@ -36,7 +34,6 @@ impl FeatureId {
FeatureId::SuCompat => "su_compat",
FeatureId::KernelUmount => "kernel_umount",
FeatureId::EnhancedSecurity => "enhanced_security",
FeatureId::SuLog => "sulog",
}
}
@ -51,9 +48,6 @@ impl FeatureId {
FeatureId::EnhancedSecurity => {
"Enhanced Security - disable nonKSU root elevation and unauthorized UID downgrades"
}
FeatureId::SuLog => {
"SU Log - enables logging of SU command usage to kernel log for auditing purposes"
}
}
}
}
@ -63,7 +57,6 @@ fn parse_feature_id(name: &str) -> Result<FeatureId> {
"su_compat" | "0" => Ok(FeatureId::SuCompat),
"kernel_umount" | "1" => Ok(FeatureId::KernelUmount),
"enhanced_security" | "2" => Ok(FeatureId::EnhancedSecurity),
"sulog" | "3" => Ok(FeatureId::SuLog),
_ => bail!("Unknown feature: {}", name),
}
}
@ -206,39 +199,6 @@ pub fn get_feature(id: String) -> Result<()> {
pub fn set_feature(id: String, value: u64) -> Result<()> {
let feature_id = parse_feature_id(&id)?;
// Check if this feature is managed by any module
if let Ok(managed_features_map) = crate::module::get_managed_features() {
// Find which modules manage this feature
let managing_modules: Vec<&String> = managed_features_map
.iter()
.filter(|(_, features)| features.iter().any(|f| f == feature_id.name()))
.map(|(module_id, _)| module_id)
.collect();
if !managing_modules.is_empty() {
// Feature is managed, check if caller is an authorized module
let caller_module = std::env::var("KSU_MODULE").unwrap_or_default();
if caller_module.is_empty() || !managing_modules.contains(&&caller_module) {
bail!(
"Feature '{}' is managed by module(s): {}. Direct modification is not allowed.",
feature_id.name(),
managing_modules
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ")
);
}
log::info!(
"Module '{}' is setting managed feature '{}'",
caller_module,
feature_id.name()
);
}
}
crate::ksucalls::set_feature(feature_id as u32, value)
.with_context(|| format!("Failed to set feature {} to {}", id, value))?;
@ -274,7 +234,6 @@ pub fn list_features() -> Result<()> {
FeatureId::SuCompat,
FeatureId::KernelUmount,
FeatureId::EnhancedSecurity,
FeatureId::SuLog,
];
for feature_id in all_features.iter() {
@ -338,7 +297,6 @@ pub fn save_config() -> Result<()> {
FeatureId::SuCompat,
FeatureId::KernelUmount,
FeatureId::EnhancedSecurity,
FeatureId::SuLog,
];
for feature_id in all_features.iter() {
@ -391,7 +349,7 @@ pub fn init_features() -> Result<()> {
let mut features = load_binary_config()?;
// Get managed features from active modules and skip them during init
// Get managed features from active modules
if let Ok(managed_features_map) = crate::module::get_managed_features() {
if !managed_features_map.is_empty() {
log::info!(
@ -399,7 +357,7 @@ pub fn init_features() -> Result<()> {
managed_features_map.len()
);
// Build a set of all managed feature IDs to skip
// Force override managed features to 0
for (module_id, feature_list) in managed_features_map.iter() {
log::info!(
"Module '{}' manages {} feature(s)",
@ -409,20 +367,12 @@ pub fn init_features() -> Result<()> {
for feature_name in feature_list {
if let Ok(feature_id) = parse_feature_id(feature_name) {
let feature_id_u32 = feature_id as u32;
// Remove managed features from config, let modules control them
if features.remove(&feature_id_u32).is_some() {
log::info!(
" - Skipping managed feature '{}' (controlled by module: {})",
feature_name,
module_id
);
} else {
log::info!(
" - Feature '{}' is managed by module '{}', skipping",
feature_name,
module_id
);
}
log::info!(
" - Force overriding managed feature '{}' to 0 (by module: {})",
feature_name,
module_id
);
features.insert(feature_id_u32, 0);
} else {
log::warn!(
" - Unknown managed feature '{}' from module '{}', ignoring",
@ -446,9 +396,9 @@ pub fn init_features() -> Result<()> {
apply_config(&features)?;
// Save the configuration (excluding managed features)
// Save the final configuration (including managed features forced to 0)
save_binary_config(&features)?;
log::info!("Saved feature configuration to file");
log::info!("Saved final feature configuration to file");
Ok(())
}

View file

@ -1,25 +1,34 @@
#[cfg(target_arch = "aarch64")]
use crate::kpm;
use crate::module::{handle_updated_modules, prune_modules};
use crate::utils::is_safe_mode;
use crate::{
assets, defs, ksucalls, metamodule, restorecon,
utils::{self},
assets, defs,
defs::{KSU_MOUNT_SOURCE, NO_MOUNT_PATH, NO_TMPFS_PATH},
ksucalls,
module::{handle_updated_modules, prune_modules},
restorecon, uid_scanner, utils,
utils::find_tmp_path,
};
use anyhow::{Context, Result};
use log::{info, warn};
use rustix::fs::{MountFlags, mount};
use std::path::Path;
#[cfg(target_os = "android")]
pub fn mount_modules_systemlessly() -> Result<()> {
crate::magic_mount::magic_mount(&find_tmp_path())
}
#[cfg(not(target_os = "android"))]
pub fn mount_modules_systemlessly() -> Result<()> {
Ok(())
}
pub fn on_post_data_fs() -> Result<()> {
ksucalls::report_post_fs_data();
utils::umask(0);
// Clear all temporary module configs early
if let Err(e) = crate::module_config::clear_all_temp_configs() {
warn!("clear temp configs failed: {e}");
}
#[cfg(unix)]
let _ = catch_bootlog("logcat", vec!["logcat"]);
#[cfg(unix)]
@ -30,11 +39,9 @@ pub fn on_post_data_fs() -> Result<()> {
return Ok(());
}
let safe_mode = crate::utils::is_safe_mode();
let safe_mode = utils::is_safe_mode();
if safe_mode {
// we should still ensure module directory exists in safe mode
// because we may need to operate the module dir in safe mode
warn!("safe mode, skip common post-fs-data.d scripts");
} else {
// Then exec common post-fs-data scripts
@ -43,18 +50,18 @@ pub fn on_post_data_fs() -> Result<()> {
}
}
let module_dir = defs::MODULE_DIR;
assets::ensure_binaries(true).with_context(|| "Failed to extract bin assets")?;
// Start UID scanner daemon with highest priority
crate::uid_scanner::start_uid_scanner_daemon()?;
uid_scanner::start_uid_scanner_daemon()?;
if is_safe_mode() {
warn!("safe mode, skip load feature config");
} else if let Err(e) = crate::umount_manager::load_and_apply_config() {
warn!("Failed to load umount config: {e}");
}
// tell kernel that we've mount the module, so that it can do some optimization
ksucalls::report_module_mounted();
// if we are in safe mode, we should disable all modules
if safe_mode {
@ -65,14 +72,14 @@ pub fn on_post_data_fs() -> Result<()> {
return Ok(());
}
if let Err(e) = handle_updated_modules() {
warn!("handle updated modules failed: {e}");
}
if let Err(e) = prune_modules() {
warn!("prune modules failed: {e}");
}
if let Err(e) = handle_updated_modules() {
warn!("handle updated modules failed: {e}");
}
if let Err(e) = restorecon::restorecon() {
warn!("restorecon failed: {e}");
}
@ -103,9 +110,23 @@ pub fn on_post_data_fs() -> Result<()> {
warn!("KPM: Failed to load KPM modules: {e}");
}
// execute metamodule post-fs-data script first (priority)
if let Err(e) = metamodule::exec_stage_script("post-fs-data", true) {
warn!("exec metamodule post-fs-data script failed: {e}");
let tmpfs_path = find_tmp_path();
// for compatibility
let no_mount = Path::new(NO_TMPFS_PATH).exists() || Path::new(NO_MOUNT_PATH).exists();
// mount temp dir
if !no_mount {
if let Err(e) = mount(
KSU_MOUNT_SOURCE,
&tmpfs_path,
"tmpfs",
MountFlags::empty(),
"",
) {
warn!("do temp dir mount failed: {e}");
}
} else {
info!("no tmpfs requested");
}
// exec modules post-fs-data scripts
@ -119,15 +140,18 @@ pub fn on_post_data_fs() -> Result<()> {
warn!("load system.prop failed: {e}");
}
// execute metamodule mount script
if let Err(e) = metamodule::exec_mount_script(module_dir) {
warn!("execute metamodule mount failed: {e}");
// mount module systemlessly by magic mount
#[cfg(target_os = "android")]
if !no_mount {
if let Err(e) = crate::magic_mount::magic_mount(&tmpfs_path) {
warn!("do systemless mount failed: {e}");
}
} else {
info!("no mount requested");
}
run_stage("post-mount", true);
std::env::set_current_dir("/").with_context(|| "failed to chdir to /")?;
Ok(())
}
@ -147,13 +171,6 @@ fn run_stage(stage: &str, block: bool) {
if let Err(e) = crate::module::exec_common_scripts(&format!("{stage}.d"), block) {
warn!("Failed to exec common {stage} scripts: {e}");
}
// execute metamodule stage script first (priority)
if let Err(e) = metamodule::exec_stage_script(stage, block) {
warn!("Failed to exec metamodule {stage} script: {e}");
}
// execute regular modules stage scripts
if let Err(e) = crate::module::exec_stage_script(stage, block) {
warn!("Failed to exec {stage} scripts: {e}");
}

View file

@ -85,7 +85,7 @@ setup_flashable() {
$BOOTMODE && return
if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then
# We will have to manually find out OUTFD
for FD in `ls /proc/$$/fd`; do
for FD in /proc/$$/fd/*; do
if readlink /proc/$$/fd/$FD | grep -q pipe; then
if ps | grep -v grep | grep -qE " 3 $FD |status_fd=$FD"; then
OUTFD=$FD
@ -313,6 +313,14 @@ mark_remove() {
chmod 644 $1
}
mark_replace() {
# REPLACE must be directory!!!
# https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories
mkdir -p $1 2>/dev/null
setfattr -n trusted.overlay.opaque -v y $1
chmod 644 $1
}
request_size_check() {
reqSizeM=`du -ms "$1" | cut -f1`
}
@ -330,19 +338,16 @@ is_legacy_script() {
}
handle_partition() {
# if /system/vendor is a symlink, we need to move it out of $MODPATH/system
# if /system/vendor is a normal directory, no special handling is needed.
if [ ! -e $MODPATH/system/$1 ]; then
PARTITION="$1"
REQUIRE_SYMLINK="$2"
if [ ! -e "$MODPATH/system/$PARTITION" ]; then
# no partition found
return;
fi
# we move the folder to / only if it is a native folder that is not a symlink
if [ -d "/$1" ] && [ ! -L "/$1" ]; then
ui_print "- Handle partition /$1"
# we create a symlink if module want to access $MODPATH/system/$1
# but it doesn't always work(ie. write it in post-fs-data.sh would fail because it is readonly)
mv -f $MODPATH/system/$1 $MODPATH/$1 && ln -sf ../$1 $MODPATH/system/$1
if [ "$REQUIRE_SYMLINK" = "false" ] || [ -L "/system/$PARTITION" ] && [ "$(readlink -f "/system/$PARTITION")" = "/$PARTITION" ]; then
ui_print "- Handle partition /$PARTITION"
ln -sf "./system/$PARTITION" "$MODPATH/$PARTITION"
fi
}
@ -423,23 +428,23 @@ install_module() {
[ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh
fi
handle_partition vendor true
handle_partition system_ext true
handle_partition product true
handle_partition odm false
# Handle replace folders
for TARGET in $REPLACE; do
ui_print "- Replace target: $TARGET"
mark_replace $MODPATH$TARGET
mark_replace "$MODPATH$TARGET"
done
# Handle remove files
for TARGET in $REMOVE; do
ui_print "- Remove target: $TARGET"
mark_remove $MODPATH$TARGET
mark_remove "$MODPATH$TARGET"
done
handle_partition vendor
handle_partition system_ext
handle_partition product
handle_partition odm
if $BOOTMODE; then
mktouch $NVBASE/modules/$MODID/update
rm -rf $NVBASE/modules/$MODID/remove 2>/dev/null

View file

@ -17,8 +17,6 @@ const KSU_IOCTL_GET_FEATURE: u32 = 0xc0004b0d; // _IOC(_IOC_READ|_IOC_WRITE, 'K'
const KSU_IOCTL_SET_FEATURE: u32 = 0x40004b0e; // _IOC(_IOC_WRITE, 'K', 14, 0)
const KSU_IOCTL_GET_WRAPPER_FD: u32 = 0x40004b0f; // _IOC(_IOC_WRITE, 'K', 15, 0)
const KSU_IOCTL_MANAGE_MARK: u32 = 0xc0004b10; // _IOC(_IOC_READ|_IOC_WRITE, 'K', 16, 0)
const KSU_IOCTL_NUKE_EXT4_SYSFS: u32 = 0x40004b11; // _IOC(_IOC_WRITE, 'K', 17, 0)
const KSU_IOCTL_ADD_TRY_UMOUNT: u32 = 0x40004b12; // _IOC(_IOC_WRITE, 'K', 18, 0)
#[repr(C)]
#[derive(Clone, Copy, Default)]
@ -75,31 +73,12 @@ struct ManageMarkCmd {
result: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct NukeExt4SysfsCmd {
pub arg: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct AddTryUmountCmd {
arg: u64, // char ptr, this is the mountpoint
flags: u32, // this is the flag we use for it
mode: u8, // denotes what to do with it 0:wipe_list 1:add_to_list 2:delete_entry
}
// Mark operation constants
const KSU_MARK_GET: u32 = 1;
const KSU_MARK_MARK: u32 = 2;
const KSU_MARK_UNMARK: u32 = 3;
const KSU_MARK_REFRESH: u32 = 4;
// Umount operation constants
const KSU_UMOUNT_WIPE: u8 = 0;
const KSU_UMOUNT_ADD: u8 = 1;
const KSU_UMOUNT_DEL: u8 = 2;
// Global driver fd cache
#[cfg(any(target_os = "linux", target_os = "android"))]
static DRIVER_FD: OnceLock<RawFd> = OnceLock::new();
@ -315,47 +294,3 @@ pub fn mark_refresh() -> std::io::Result<()> {
ksuctl(KSU_IOCTL_MANAGE_MARK, &mut cmd as *mut _)?;
Ok(())
}
pub fn nuke_ext4_sysfs(mnt: &str) -> anyhow::Result<()> {
let c_mnt = std::ffi::CString::new(mnt)?;
let mut ioctl_cmd = NukeExt4SysfsCmd {
arg: c_mnt.as_ptr() as u64,
};
ksuctl(KSU_IOCTL_NUKE_EXT4_SYSFS, &mut ioctl_cmd as *mut _)?;
Ok(())
}
/// Wipe all entries from umount list
pub fn umount_list_wipe() -> std::io::Result<()> {
let mut cmd = AddTryUmountCmd {
arg: 0,
flags: 0,
mode: KSU_UMOUNT_WIPE,
};
ksuctl(KSU_IOCTL_ADD_TRY_UMOUNT, &mut cmd as *mut _)?;
Ok(())
}
/// Add mount point to umount list
pub fn umount_list_add(path: &str, flags: u32) -> anyhow::Result<()> {
let c_path = std::ffi::CString::new(path)?;
let mut cmd = AddTryUmountCmd {
arg: c_path.as_ptr() as u64,
flags,
mode: KSU_UMOUNT_ADD,
};
ksuctl(KSU_IOCTL_ADD_TRY_UMOUNT, &mut cmd as *mut _)?;
Ok(())
}
/// Delete mount point from umount list
pub fn umount_list_del(path: &str) -> anyhow::Result<()> {
let c_path = std::ffi::CString::new(path)?;
let mut cmd = AddTryUmountCmd {
arg: c_path.as_ptr() as u64,
flags: 0,
mode: KSU_UMOUNT_DEL,
};
ksuctl(KSU_IOCTL_ADD_TRY_UMOUNT, &mut cmd as *mut _)?;
Ok(())
}

Some files were not shown because too many files have changed in this diff Show more