diff --git a/Android.mk b/Android.mk index e69de29bb2d1..ad206a90b5b5 100644 --- a/Android.mk +++ b/Android.mk @@ -0,0 +1 @@ +TARGET_KERNEL_ADDITIONAL_FLAGS += LOCALVERSION= diff --git a/KernelSU/.gitattributes b/KernelSU/.gitattributes new file mode 100644 index 000000000000..95ff27aa3a42 --- /dev/null +++ b/KernelSU/.gitattributes @@ -0,0 +1 @@ +*.bat eol=crlf \ No newline at end of file diff --git a/KernelSU/.gitignore b/KernelSU/.gitignore new file mode 100644 index 000000000000..706fd07fada6 --- /dev/null +++ b/KernelSU/.gitignore @@ -0,0 +1,2 @@ +.idea +.vscode diff --git a/KernelSU/LICENSE b/KernelSU/LICENSE new file mode 100644 index 000000000000..f288702d2fa1 --- /dev/null +++ b/KernelSU/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/KernelSU/SECURITY.md b/KernelSU/SECURITY.md new file mode 100644 index 000000000000..83040d98981f --- /dev/null +++ b/KernelSU/SECURITY.md @@ -0,0 +1,7 @@ +# 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. diff --git a/KernelSU/docs/README.md b/KernelSU/docs/README.md new file mode 100644 index 000000000000..0128cd3d221e --- /dev/null +++ b/KernelSU/docs/README.md @@ -0,0 +1,57 @@ +**English** | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +A Kernel-based root solution for Android devices. + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## Features + +1. Kernel-based `su` and root access management. +2. Module system based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS). +3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage. + +## Compatibility State + +KernelSU officially supports Android GKI 2.0 devices (kernel 5.10+). Older kernels (4.14+) are also compatible, but the kernel will have to be built manually. + +With this, WSA, ChromeOS, and container-based Android are all supported. + +Currently, only `arm64-v8a` and `x86_64` are supported. + +## Usage + +- [Installation Instruction](https://kernelsu.org/guide/installation.html) +- [How to build?](https://kernelsu.org/guide/how-to-build.html) +- [Official Website](https://kernelsu.org/) + +## Translation + +To help translate KernelSU or improve existing translations, please use [Weblate](https://hosted.weblate.org/engage/kernelsu/). PR of Manager's translation is no longer accepted, because it will conflict with Weblate. + +## Discussion + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## Security + +For information on reporting security vulnerabilities in KernelSU, see [SECURITY.md](/SECURITY.md). + +## License + +- Files under the `kernel` directory are [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). +- All other parts except the `kernel` directory are [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html). + +## Credits + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): the KernelSU idea. +- [Magisk](https://github.com/topjohnwu/Magisk): the powerful root tool. +- [genuine](https://github.com/brevent/genuine/): apk v2 signature validation. +- [Diamorphine](https://github.com/m0nad/Diamorphine): some rootkit skills. diff --git a/KernelSU/docs/README_CN.md b/KernelSU/docs/README_CN.md new file mode 100644 index 000000000000..dc34b86ab139 --- /dev/null +++ b/KernelSU/docs/README_CN.md @@ -0,0 +1,57 @@ +[English](README.md) | [Español](README_ES.md) | **简体中文** | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +一个 Android 上基于内核的 root 方案。 + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## 特性 + +- 基于内核的 `su` 和权限管理。 +- 基于 [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) 的模块系统。 +- [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html): 把 Root 权限关进笼子里。 + +## 兼容状态 + +KernelSU 官方支持 GKI 2.0 的设备(内核版本5.10以上);旧内核也是兼容的(最低4.14+),不过需要自己编译内核。 + +WSA, ChromeOS 和运行在容器上的 Android 也可以与 KernelSU 一起工作。 + +目前支持架构 : `arm64-v8a` 和 `x86_64`。 + +## 使用方法 + +- [安装教程](https://kernelsu.org/zh_CN/guide/installation.html) +- [如何构建?](https://kernelsu.org/zh_CN/guide/how-to-build.html) +- [官方网站](https://kernelsu.org/zh_CN/) + +## 参与翻译 + +要将 KernelSU 翻译成您的语言,或完善现有的翻译,请使用 [Weblate](https://hosted.weblate.org/engage/kernelsu/)。现已不再接受有关管理器翻译的PR,因为这会与Weblate冲突。 + +## 讨论 + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## 安全性 + +有关报告 KernelSU 安全漏洞的信息,请参阅 [SECURITY.md](/SECURITY.md)。 + +## 许可证 + +- 目录 `kernel` 下所有文件为 [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)。 +- 除 `kernel` 目录的其他部分均为 [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)。 + +## 鸣谢 + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的灵感。 +- [Magisk](https://github.com/topjohnwu/Magisk):强大的 root 工具箱。 +- [genuine](https://github.com/brevent/genuine/):apk v2 签名验证。 +- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技巧。 diff --git a/KernelSU/docs/README_ES.md b/KernelSU/docs/README_ES.md new file mode 100644 index 000000000000..ed4f699b791d --- /dev/null +++ b/KernelSU/docs/README_ES.md @@ -0,0 +1,56 @@ +[English](README.md) | **Español** | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +Una solución root basada en el kernel para dispositivos Android. + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localización-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Seguir-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/Licencia-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## Características + +1. Binario `su` basado en el kernel y gestión de acceso root. +2. Sistema de módulos basado en [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS). + +## Estado de compatibilidad + +**KernelSU** soporta de forma oficial dispositivos Android con **GKI 2.0** (a partir de la versión **5.10** del kernel). Los kernels antiguos (a partir de la versión **4.14**) también son compatibles, pero necesitas compilarlos por tu cuenta. + +Con esto, WSA, ChromeOS y Android basado en contenedores están todos compatibles. + +Actualmente, solo se admiten las arquitecturas `arm64-v8a` y `x86_64`. + +## Uso + +- [¿Cómo instalarlo?](https://kernelsu.org/guide/installation.html) +- [¿Cómo compilarlo?](https://kernelsu.org/guide/how-to-build.html) +- [Site oficial](https://kernelsu.org/) + +## Traducción + +Para ayudar a traducir KernelSU o mejorar las traducciones existentes, utilice [Weblate](https://hosted.weblate.org/engage/kernelsu/). Ya no se aceptan PR de la traducción de Manager porque entrará en conflicto con Weblate. + +## Discusión + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## Seguridad + +Para obtener información sobre cómo informar vulnerabilidades de seguridad en KernelSU, consulte [SECURITY.md](/SECURITY.md). + +## Licencia + +- Los archivos bajo el directorio `kernel` están licenciados bajo [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). +- Todas las demás partes, a excepción del directorio `kernel`, están licenciados bajo [GPL-3-or-later](https://www.gnu.org/licenses/gpl-3.0.html). + +## Créditos + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): la idea de KernelSU. +- [Magisk](https://github.com/topjohnwu/Magisk): la poderosa herramienta root. +- [genuine](https://github.com/brevent/genuine/): validación de firma apk v2. +- [Diamorphine](https://github.com/m0nad/Diamorphine): algunas habilidades de rootkit. diff --git a/KernelSU/docs/README_ID.md b/KernelSU/docs/README_ID.md new file mode 100644 index 000000000000..511683a7503d --- /dev/null +++ b/KernelSU/docs/README_ID.md @@ -0,0 +1,53 @@ +[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | **Indonesia** | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +Solusi root berbasis Kernel untuk perangkat Android. + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## Fitur + +1. Manajemen akses root dan `su` berbasis kernel. +2. Sistem modul berdasarkan [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS). +3. [Profil Aplikasi](https://kernelsu.org/guide/app-profile.html): Kunci daya root di dalam sangkar. + +## Status Kompatibilitas + +KernelSU secara resmi mendukung perangkat Android GKI 2.0 (dengan kernel 5.10+), kernel lama (4.14+) juga kompatibel, tetapi Anda perlu membuat kernel sendiri. + +WSA, ChromeOS, dan Android berbasis wadah juga dapat bekerja dengan KernelSU terintegrasi. + +Dan ABI yang didukung saat ini adalah: `arm64-v8a` dan `x86_64` + +## Penggunaan + +- [Petunjuk Instalasi](https://kernelsu.org/id_ID/guide/installation.html) +- [Bagaimana cara membuat?](https://kernelsu.org/id_ID/guide/how-to-build.html) +- [Situs Web Resmi](https://kernelsu.org/id_ID/) + +## Terjemahan + +Untuk menerjemahkan KernelSU ke dalam bahasa Anda atau menyempurnakan terjemahan yang sudah ada, harap gunakan [Weblat](https://hosted.weblate.org/engage/kernelsu/). + +## Diskusi + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## Lisensi + +- File di bawah direktori `kernel` adalah [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). +- Semua bagian lain kecuali direktori `kernel` adalah [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html). + +## Kredit + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): ide KernelSU. +- [Magisk](https://github.com/topjohnwu/Magisk): alat root yang ampuh. +- [genuine](https://github.com/brevent/genuine/): validasi tanda tangan apk v2. +- [Diamorphine](https://github.com/m0nad/Diamorphine): beberapa keterampilan rootkit. diff --git a/KernelSU/docs/README_IN.md b/KernelSU/docs/README_IN.md new file mode 100644 index 000000000000..7396ab5c2b10 --- /dev/null +++ b/KernelSU/docs/README_IN.md @@ -0,0 +1,53 @@ +[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | **हिंदी** + +# KernelSU + +logo + +Android उपकरणों के लिए कर्नेल-आधारित रूट समाधान। + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## विशेषताएँ + +1. कर्नेल-आधारित `su` और रूट एक्सेस प्रबंधन। +2. [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) पर आधारित मॉड्यूल प्रणाली। +3. [App Profile](https://kernelsu.org/guide/app-profile.html): Root शक्ति को पिंजरे में बंद कर दो। + +## अनुकूलता अवस्था + +KernelSU आधिकारिक तौर पर Android GKI 2.0 डिवाइस (कर्नेल 5.10+) का समर्थन करता है। पुराने कर्नेल (4.14+) भी संगत हैं, लेकिन कर्नेल को मैन्युअल रूप से बनाना होगा। + +इसके साथ, WSA, ChromeOS और कंटेनर-आधारित Android सभी समर्थित हैं। + +वर्तमान में, केवल `arm64-v8a` और `x86_64` समर्थित हैं। + +## प्रयोग + +- [स्थापना निर्देश](https://kernelsu.org/guide/installation.html) +- [कैसे बनाना है ?](https://kernelsu.org/guide/how-to-build.html) +- [आधिकारिक वेबसाइट](https://kernelsu.org/) + +## अनुवाद करना + +KernelSU का अनुवाद करने या मौजूदा अनुवादों को बेहतर बनाने में सहायता के लिए, कृपया इसका उपयोग करें [Weblate](https://hosted.weblate.org/engage/kernelsu/). + +## बहस + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## लाइसेंस + +- `Kernel` निर्देशिका के अंतर्गत फ़ाइलें हैं [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +- `Kernel` निर्देशिका को छोड़कर अन्य सभी भाग हैं [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html) + +## आभार सूची + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU विचार। +- [Magisk](https://github.com/topjohnwu/Magisk): शक्तिशाली root उपकरण। +- [genuine](https://github.com/brevent/genuine/): apk v2 हस्ताक्षर सत्यापन। +- [Diamorphine](https://github.com/m0nad/Diamorphine): कुछ रूटकिट कौशल। diff --git a/KernelSU/docs/README_IW.md b/KernelSU/docs/README_IW.md new file mode 100644 index 000000000000..5997487e2a08 --- /dev/null +++ b/KernelSU/docs/README_IW.md @@ -0,0 +1,53 @@ +[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | **עברית** | [हिंदी](README_IN.md) + +# KernelSU + +logo + +פתרון לניהול root מבוסס על Kernel עבור מכשירי Android. + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## תכונות + +1. ניהול root ו־`su` מבוססים על Kernel. +2. מערכת מודולים מבוססת [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS). +3. [פרופיל אפליקציה](https://kernelsu.org/guide/app-profile.html): נעילת גישת root בכלוב. + +## מצב תאימות + +KernelSU תומך במכשירי Android GKI 2.0 (kernel 5.10+) באופן רשמי. לליבות ישנות (4.14+) יש גם תאימות, אך יידרש לבנות את הליבה באופן ידני. + +באמצעות זה, תמיכה זמינה גם ל-WSA, ChromeOS ומכשירי Android המבוססים על מיכלים. + +כרגע, רק `arm64-v8a` ו־`x86_64` נתמכים. + +## שימוש + +- [הוראות התקנה](https://kernelsu.org/guide/installation.html) +- [איך לבנות?](https://kernelsu.org/guide/how-to-build.html) +- [האתר רשמי](https://kernelsu.org/) + +## תרגום + +כדי לעזור בתרגום של KernelSU או לשפר תרגומים קיימים, יש להשתמש ב-[Weblate](https://hosted.weblate.org/engage/kernelsu/). + +## דיון + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## רשיון + +- קבצים תחת הספרייה `kernel` מוגנים על פי [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). +- כל החלקים האחרים, למעט הספרייה `kernel`, מוגנים על פי [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html). + +## קרדיטים + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): הרעיון של KernelSU. +- [Magisk](https://github.com/topjohnwu/Magisk): הכלי הסופר חזק לניהול root. +- [genuine](https://github.com/brevent/genuine/): אימות חתימת apk v2. +- [Diamorphine](https://github.com/m0nad/Diamorphine): כמה יכולות רוט. diff --git a/KernelSU/docs/README_JP.md b/KernelSU/docs/README_JP.md new file mode 100644 index 000000000000..093618753775 --- /dev/null +++ b/KernelSU/docs/README_JP.md @@ -0,0 +1,53 @@ +[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | **日本語** | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +Android におけるカーネルベースの root ソリューションです。 + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## 特徴 + +1. カーネルベースの `su` と権限管理。 +2. [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) に基づくモジュールシステム。 +3. [アプリのプロファイル](https://kernelsu.org/guide/app-profile.html): root の権限をケージ内に閉じ込めます。 + +## 対応状況 + +KernelSU は GKI 2.0 デバイス(カーネルバージョン 5.10 以上)を公式にサポートしています。古いカーネル(4.14以上)とも互換性がありますが、自分でカーネルをビルドする必要があります。 + +WSA 、ChromeOS とコンテナ上で動作する Android でも KernelSU を統合して動かせます。 + +現在サポートしているアーキテクチャは `arm64-v8a` および `x86_64` です。 + +## 使用方法 + +- [インストール方法はこちら](https://kernelsu.org/ja_JP/guide/installation.html) +- [ビルド方法はこちら](https://kernelsu.org/guide/how-to-build.html) +- [公式サイト](https://kernelsu.org/ja_JP/) + +## 翻訳 + +KernelSU をあなたの言語に翻訳するか、既存の翻訳を改善するには、[Weblate](https://hosted.weblate.org/engage/kernelsu/) を使用してください。Manager翻訳した PR は、Weblate と競合するため受け入れられなくなりました。 + +## ディスカッション + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## ライセンス + +- `kernel` ディレクトリの下にあるすべてのファイル: [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)。 +- `kernel` ディレクトリ以外のすべてのファイル: [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)。 + +## クレジット + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU のアイデア元。 +- [Magisk](https://github.com/topjohnwu/Magisk):強力な root ツール。 +- [genuine](https://github.com/brevent/genuine/):apk v2 の署名検証。 +- [Diamorphine](https://github.com/m0nad/Diamorphine): rootkit のスキル。 diff --git a/KernelSU/docs/README_KR.md b/KernelSU/docs/README_KR.md new file mode 100644 index 000000000000..3226008920a1 --- /dev/null +++ b/KernelSU/docs/README_KR.md @@ -0,0 +1,57 @@ +[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | **한국어** | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +안드로이드 기기에서 사용되는 커널 기반 루팅 솔루션입니다. + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## 기능들 + +1. 커널 기반 `su` 및 루트 액세스 관리. +2. [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) 기반 모듈 시스템. +3. [App Profile](https://kernelsu.org/guide/app-profile.html): 루트 권한을 케이지에 가둡니다. + +## 호환 상태 + +KernelSU는 공식적으로 안드로이드 GKI 2.0 디바이스(커널 5.10 이상)를 지원합니다. 오래된 커널(4.14 이상)도 사용할 수 있지만, 커널을 수동으로 빌드해야 합니다. + +KernelSU는 WSA, ChromeOS, 컨테이너 기반 안드로이드 모두를 지원합니다. + +현재는 `arm64-v8a`와 `x86_64`만 지원됩니다. + +## 사용 방법 + +- [설치 방법](https://kernelsu.org/guide/installation.html) +- [어떻게 빌드하나요?](https://kernelsu.org/guide/how-to-build.html) +- [공식 웹사이트](https://kernelsu.org/) + +## 번역 + +KernelSU 번역을 돕거나 기존 번역을 개선하려면 [Weblate](https://hosted.weblate.org/engage/kernelsu/)를 이용해 주세요. 매니저의 번역은 Weblate와 충돌할 수 있으므로 더 이상 허용되지 않습니다. + +## 토론 + +- 텔레그램: [@KernelSU](https://t.me/KernelSU) + +## 보안 + +KernelSU의 보안 취약점 보고에 대한 자세한 내용은 [SECURITY.md](/SECURITY.md)를 참조하세요. + +## 저작권 + +- `kernel` 디렉터리 아래의 파일은 [GPL-2.0 전용](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)입니다. +- `kernel` 디렉토리를 제외한 다른 모든 부분은 [GPL-3.0-이상](https://www.gnu.org/licenses/gpl-3.0.html)입니다. + +## 크래딧 + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU의 아이디어. +- [Magisk](https://github.com/topjohnwu/Magisk): 강력한 루팅 도구. +- [genuine](https://github.com/brevent/genuine/): apk v2 서명 유효성 검사. +- [Diamorphine](https://github.com/m0nad/Diamorphine): 일부 rootkit 스킬. diff --git a/KernelSU/docs/README_PL.md b/KernelSU/docs/README_PL.md new file mode 100644 index 000000000000..ab9066296dd9 --- /dev/null +++ b/KernelSU/docs/README_PL.md @@ -0,0 +1,55 @@ +[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | **Polski** | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +Rozwiązanie root oparte na jądrze dla urządzeń z systemem Android. + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## Cechy + +1. Oparte na jądrze `su` i zarządzanie dostępem roota. +2. System modułów oparty na [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS). + +## Kompatybilność + +KernelSU oficjalnie obsługuje urządzenia z Androidem GKI 2.0 (z jądrem 5.10+), starsze jądra (4.14+) są również kompatybilne, ale musisz sam skompilować jądro. + +WSA i Android oparty na kontenerach również powinny działać ze zintegrowanym KernelSU. + +Aktualnie obsługiwane ABI to : `arm64-v8a` i `x86_64`. + +## Użycie + +- [Instalacja](https://kernelsu.org/guide/installation.html) +- [Jak skompilować?](https://kernelsu.org/guide/how-to-build.html) + +## Tłumaczenie + +Aby pomóc w tłumaczeniu KernelSU lub ulepszyć istniejące tłumaczenia, użyj [Weblate](https://hosted.weblate.org/engage/kernelsu/). PR tłumaczenia Managera nie jest już akceptowany, ponieważ będzie kolidował z Weblate. + +## Dyskusja + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## Bezpieczeństwo + +Informacje na temat zgłaszania luk w zabezpieczeniach w KernelSU można znaleźć w pliku [SECURITY.md](/SECURITY.md). + +## Licencja + +- Pliki w katalogu `kernel` są na licencji [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). +- Wszystkie inne części poza katalogiem `kernel` są na licencji [GPL-3-or-later](https://www.gnu.org/licenses/gpl-3.0.html). + +## Podziękowania + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): pomysłodawca KernelSU. +- [Magisk](https://github.com/topjohnwu/Magisk): implementacja sepolicy. +- [genuine](https://github.com/brevent/genuine/): walidacja podpisu apk v2. +- [Diamorphine](https://github.com/m0nad/Diamorphine): cenna znajomość rootkitów. diff --git a/KernelSU/docs/README_PT-BR.md b/KernelSU/docs/README_PT-BR.md new file mode 100644 index 000000000000..b5421e7f9be0 --- /dev/null +++ b/KernelSU/docs/README_PT-BR.md @@ -0,0 +1,57 @@ +[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | **Português (Brasil)** | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +Uma solução root baseada em kernel para dispositivos Android. + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localização-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Seguir-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/Licença-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## Características + +1. `su` e gerenciamento de acesso root baseado em kernel. +2. Sistema modular baseado em [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS). +3. [Perfil do Aplicativo](https://kernelsu.org/pt_BR/guide/app-profile.html): Tranque o poder root em uma gaiola. + +## Estado de compatibilidade + +O KernelSU oferece suporte oficial a dispositivos Android GKI 2.0 (kernel 5.10+). Kernels mais antigos (4.14+) também são compatíveis, mas o kernel terá que ser construído manualmente. + +Com isso, WSA, ChromeOS e Android baseado em contêiner são todos suportados. + +Atualmente, apenas `arm64-v8a` e `x86_64` são suportados. + +## Uso + + - [Instalação](https://kernelsu.org/pt_BR/guide/installation.html) + - [Como compilar o KernelSU?](https://kernelsu.org/pt_BR/guide/how-to-build.html) + - [Site oficial](https://kernelsu.org/pt_BR/) + +## Tradução + +Para contribuir com a tradução do KernelSU ou aprimorar traduções existentes, por favor, utilize o [Weblate](https://hosted.weblate.org/engage/kernelsu/). PR para a tradução do Gerenciador não são mais aceitas, pois podem entrar em conflito com o Weblate. + +## Discussão + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## Segurança + +Para obter informações sobre como relatar vulnerabilidades de segurança do KernelSU, consulte [SECURITY.md](/SECURITY.md). + +## Licença + +- Os arquivos no diretório `kernel` são [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). +- Todas as outras partes, exceto o diretório `kernel` são [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html). + +## Créditos + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): a ideia do KernelSU. +- [Magisk](https://github.com/topjohnwu/Magisk): a poderosa ferramenta root. +- [genuine](https://github.com/brevent/genuine/): validação de assinatura apk v2. +- [Diamorphine](https://github.com/m0nad/Diamorphine): algumas habilidades de rootkit. diff --git a/KernelSU/docs/README_RU.md b/KernelSU/docs/README_RU.md new file mode 100644 index 000000000000..7e50f47adfe5 --- /dev/null +++ b/KernelSU/docs/README_RU.md @@ -0,0 +1,49 @@ +[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | **Русский** | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +Решение на основе ядра root для Android-устройств. + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## Особенности + +1. Управление `su` и root-доступом на основе ядра. +2. Система модулей на основе [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS). +3. [Профиль приложений](https://kernelsu.org/ru_RU/guide/app-profile.html): Запри корневую силу в клетке. + +## Совместимость + +KernelSU официально поддерживает устройства на базе Android GKI 2.0 (с ядром 5.10+), старые ядра (4.14+) также совместимы, но для этого необходимо собрать ядро самостоятельно. + +WSA и Android на основе контейнеров также должны работать с интегрированным KernelSU. + +В настоящее время поддерживаются следующие ABI: `arm64-v8a` и `x86_64`. + +## Использование + +- [Установка](https://kernelsu.org/ru_RU/guide/installation.html) +- [Как собрать?](https://kernelsu.org/ru_RU/guide/how-to-build.html) +- [официальный сайт](https://kernelsu.org/ru_RU/) + +## Обсуждение + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## Лицензия + +- Файлы в директории `kernel` [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). +- Все остальные части, кроме директории `kernel` [GPL-3-or-later](https://www.gnu.org/licenses/gpl-3.0.html). + +## Благодарности + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): идея KernelSU. +- [Magisk](https://github.com/topjohnwu/Magisk): реализация sepolicy. +- [genuine](https://github.com/brevent/genuine/): проверка подписи apk v2. +- [Diamorphine](https://github.com/m0nad/Diamorphine): некоторые навыки руткита. diff --git a/KernelSU/docs/README_TR.md b/KernelSU/docs/README_TR.md new file mode 100644 index 000000000000..bc3363e8b490 --- /dev/null +++ b/KernelSU/docs/README_TR.md @@ -0,0 +1,57 @@ +[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | **Türkçe** | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +Android cihazlar için kernel tabanlı root çözümü. + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## Özellikler + +1. Kernel-tabanlı `su` ve root erişimi yönetimi. +2. [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)'ye dayalı modül sistemi. +3. [Uygulama profili](https://kernelsu.org/guide/app-profile.html): Root gücünü bir kafese kapatın. + +## Uyumluluk Durumu + +KernelSU resmi olarak Android GKI 2.0 cihazlarını (5.10+ kernelli) destekler, eski kernellerle de (4.14+) uyumludur, ancak kerneli kendinizin derlemeniz gerekir. + +Bununla birlikte; WSA, ChromeOS ve konteyner tabanlı Android'in tamamı desteklenmektedir. + +Şimdilik sadece `arm64-v8a` ve `x86_64` desteklenmektedir. + +## Kullanım + +- [Yükleme yönergeleri](https://kernelsu.org/guide/installation.html) +- [Nasıl derlenir?](https://kernelsu.org/guide/how-to-build.html) +- [Resmi WEB sitesi](https://kernelsu.org/) + +## Çeviri + +KernelSU'nun başka dillere çevrilmesine veya mevcut çevirilerin iyileştirilmesine yardımcı olmak için lütfen [Weblate](https://hosted.weblate.org/engage/kernelsu/) kullanın. Yönetici uygulamasının PR ile çevirisi, Weblate ile çakışacağından artık kabul edilmeyecektir. + +## Tartışma + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## Güvenlik + +KernelSU'daki güvenlik açıklarını bildirme hakkında bilgi için, bkz [SECURITY.md](/SECURITY.md). + +## Lisans + +- `kernel` klasöründeki dosyalar [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) lisansı altındadır. +- `kernel` klasörü dışındaki bütün diğer bölümler [GPL-3-veya-sonraki](https://www.gnu.org/licenses/gpl-3.0.html) lisansı altındadır. + +## Krediler + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU fikri. +- [Magisk](https://github.com/topjohnwu/Magisk): güçlü root aracı. +- [genuine](https://github.com/brevent/genuine/): apk v2 imza doğrulaması. +- [Diamorphine](https://github.com/m0nad/Diamorphine): bazı rootkit becerileri. diff --git a/KernelSU/docs/README_TW.md b/KernelSU/docs/README_TW.md new file mode 100644 index 000000000000..d025e1742a67 --- /dev/null +++ b/KernelSU/docs/README_TW.md @@ -0,0 +1,56 @@ +[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | **繁體中文** | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +一個基於核心的 Android 裝置 Root 解決方案 + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## 功能 + +- 基於核心的 `su` 和 Root 存取權管理。 +- 基於 [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) 的模組系統。 +- [App Profile](https://kernelsu.org/zh_TW/guide/app-profile.html): 將 Root 的權限鎖在牢籠中. + +## 相容性狀態 + +KernelSU 官方支援 Android GKI 2.0 的裝置 (核心版本 5.10+ );舊版核心同樣相容 (最低 4.14+ ),但需要自行編譯核心。 + +WSA和ChromeOS和執行在容器中的 Android 也可以與 KernelSU 一同運作。 + +目前支援架構:`arm64-v8a` 和 `x86_64`。 + +## 使用方法 + +- [安裝教學](https://kernelsu.org/zh_TW/guide/installation.html) +- [如何建置?](https://kernelsu.org/zh_TW/guide/how-to-build.html) +- [官方網站](https://kernelsu.org/zh_TW/) + +## 翻譯 + +若要協助翻譯 KernelSU 或改進現有翻譯,請使用 [Weblate](https://hosted.weblate.org/engage/kernelsu/)。 翻譯管理器的PR不再被接受,因為它會與Weblate衝突。 + +### 討論 + +- Telegram:[@KernelSU](https://t.me/KernelSU) + +## 安全 +有關報告 KernelSU 中的安全漏洞的資訊,請參閱 [SECURITY.md](/SECURITY.md)。 + +## 授權 + +- 目錄 `kernel` 下所有檔案為 [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)。 +- 除 `kernel` 目錄的其他部分均為 [GPL-3-or-later](https://www.gnu.org/licenses/gpl-3.0.html)。 + +## 致謝 + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的靈感。 +- [Magisk](https://github.com/topjohnwu/Magisk):sepolicy 實作。 +- [genuine](https://github.com/brevent/genuine/):apk v2 簽章驗證。 +- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技巧。 diff --git a/KernelSU/docs/README_VI.md b/KernelSU/docs/README_VI.md new file mode 100644 index 000000000000..7dd3b4227aec --- /dev/null +++ b/KernelSU/docs/README_VI.md @@ -0,0 +1,53 @@ +[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | **Tiếng Việt** | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) + +# KernelSU + +logo + +Giải pháp root thông qua thay đổi trên Kernel hệ điều hành cho các thiết bị Android. + +[![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) +[![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) +[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/KernelSU) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![GitHub License](https://img.shields.io/github/license/tiann/KernelSU?logo=gnu)](/LICENSE) + +## Tính năng + +1. Hỗ trợ gói thực thi `su` và quản lý quyền root. +2. Hệ thống mô-đun thông qua [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS). +3. [App Profile](https://kernelsu.org/guide/app-profile.html): Hạn chế quyền root của ứng dụng. + +## Tình trạng tương thích + +KernelSU chính thức hỗ trợ các thiết bị Android với kernel GKI 2.0 (phiên bản kernel 5.10+), các phiên bản kernel cũ hơn (4.14+) cũng tương thích, nhưng bạn cần phải tự biên dịch. + +WSA, ChromeOS và Android dựa trên container(container-based) cũng được hỗ trợ bởi KernelSU. + +Hiên tại Giao diện nhị phân của ứng dụng (ABI) được hỗ trợ bao gồm `arm64-v8a` và `x86_64`. + +## Sử dụng + +- [Hướng dẫn cài đặt](https://kernelsu.org/vi_VN/guide/installation.html) +- [Cách để build?](https://kernelsu.org/vi_VN/guide/how-to-build.html) +- [Website Chính Thức](https://kernelsu.org/vi_VN/) + +## Hỗ trợ dịch + +Nếu bạn muốn hỗ trợ dịch KernelSU sang một ngôn ngữ khác hoặc cải thiện các bản dịch trước, vui lòng sử dụng [Weblate](https://hosted.weblate.org/engage/kernelsu/). + +## Thảo luận + +- Telegram: [@KernelSU](https://t.me/KernelSU) + +## Giấy phép + +- Tất cả các file trong thư mục `kernel` dùng giấy phép [GPL-2-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). +- Tất cả các thành phần khác ngoại trừ thư mục `kernel` dùng giấy phép [GPL-3-or-later](https://www.gnu.org/licenses/gpl-3.0.html). + +## Lời cảm ơn + +- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): ý tưởng cho KernelSU. +- [Magisk](https://github.com/topjohnwu/Magisk): công cụ root mạnh mẽ. +- [genuine](https://github.com/brevent/genuine/): phương pháp xác thực apk v2. +- [Diamorphine](https://github.com/m0nad/Diamorphine): các phương pháp ẩn của rootkit. diff --git a/KernelSU/js/README.md b/KernelSU/js/README.md new file mode 100644 index 000000000000..4af3a7185863 --- /dev/null +++ b/KernelSU/js/README.md @@ -0,0 +1,111 @@ +# Library for KernelSU's module WebUI + +## Install + +```sh +yarn add kernelsu +``` + +## API + +### exec + +Spawns a **root** shell and runs a command within that shell, passing the `stdout` and `stderr` to a Promise when complete. + +- `command` `` The command to run, with space-separated arguments. +- `options` `` + - `cwd` - Current working directory of the child process + - `env` - Environment key-value pairs + +```javascript +import { exec } from 'kernelsu'; + +const { errno, stdout, stderr } = await exec('ls -l', { cwd: '/tmp' }); +if (errno === 0) { + // success + console.log(stdout); +} +``` + +### spawn + +Spawns a new process using the given `command` in **root** shell, with command-line arguments in `args`. If omitted, `args` defaults to an empty array. + +Returns a `ChildProcess`, Instances of the ChildProcess represent spawned child processes. + +- `command` `` The command to run. +- `args` `` List of string arguments. +- `options` ``: + - `cwd` `` - Current working directory of the child process + - `env` `` - Environment key-value pairs + +Example of running `ls -lh /data`, capturing `stdout`, `stderr`, and the exit code: + +```javascript +import { spawn } from 'kernelsu'; + +const ls = spawn('ls', ['-lh', '/data']); + +ls.stdout.on('data', (data) => { + console.log(`stdout: ${data}`); +}); + +ls.stderr.on('data', (data) => { + console.log(`stderr: ${data}`); +}); + +ls.on('exit', (code) => { + console.log(`child process exited with code ${code}`); +}); +``` + +#### ChildProcess + +##### Event 'exit' + +- `code` `` The exit code if the child exited on its own. + +The `'exit'` event is emitted after the child process ends. If the process exited, `code` is the final exit code of the process, otherwise null + +##### Event 'error' + +- `err` `` The error. + +The `'error'` event is emitted whenever: + +- The process could not be spawned. +- The process could not be killed. + +##### `stdout` + +A `Readable Stream` that represents the child process's `stdout`. + +```javascript +const subprocess = spawn('ls'); + +subprocess.stdout.on('data', (data) => { + console.log(`Received chunk ${data}`); +}); +``` + +#### `stderr` + +A `Readable Stream` that represents the child process's `stderr`. + +### fullScreen + +Request the WebView enter/exit full screen. + +```javascript +import { fullScreen } from 'kernelsu'; +fullScreen(true); +``` + +### toast + +Show a toast message. + +```javascript +import { toast } from 'kernelsu'; +toast('Hello, world!'); +``` diff --git a/KernelSU/js/index.d.ts b/KernelSU/js/index.d.ts new file mode 100644 index 000000000000..b65248e378f7 --- /dev/null +++ b/KernelSU/js/index.d.ts @@ -0,0 +1,45 @@ +interface ExecOptions { + cwd?: string, + env?: { [key: string]: string } +} + +interface ExecResults { + errno: number, + stdout: string, + stderr: string +} + +declare function exec(command: string): Promise; +declare function exec(command: string, options: ExecOptions): Promise; + +interface SpawnOptions { + cwd?: string, + env?: { [key: string]: string } +} + +interface Stdio { + on(event: 'data', callback: (data: string) => void) +} + +interface ChildProcess { + stdout: Stdio, + stderr: Stdio, + on(event: 'exit', callback: (code: number) => void) + on(event: 'error', callback: (err: any) => void) +} + +declare function spawn(command: string): ChildProcess; +declare function spawn(command: string, args: string[]): ChildProcess; +declare function spawn(command: string, options: SpawnOptions): ChildProcess; +declare function spawn(command: string, args: string[], options: SpawnOptions): ChildProcess; + +declare function fullScreen(isFullScreen: boolean); + +declare function toast(message: string); + +export { + exec, + spawn, + fullScreen, + toast +} diff --git a/KernelSU/js/index.js b/KernelSU/js/index.js new file mode 100644 index 000000000000..4b64e9c4c455 --- /dev/null +++ b/KernelSU/js/index.js @@ -0,0 +1,115 @@ +let callbackCounter = 0; +function getUniqueCallbackName(prefix) { + return `${prefix}_callback_${Date.now()}_${callbackCounter++}`; +} + +export function exec(command, options) { + if (typeof options === "undefined") { + options = {}; + } + + return new Promise((resolve, reject) => { + // Generate a unique callback function name + const callbackFuncName = getUniqueCallbackName("exec"); + + // Define the success callback function + window[callbackFuncName] = (errno, stdout, stderr) => { + resolve({ errno, stdout, stderr }); + cleanup(callbackFuncName); + }; + + function cleanup(successName) { + delete window[successName]; + } + + try { + ksu.exec(command, JSON.stringify(options), callbackFuncName); + } catch (error) { + reject(error); + cleanup(callbackFuncName); + } + }); +} + +function Stdio() { + this.listeners = {}; + } + + Stdio.prototype.on = function (event, listener) { + if (!this.listeners[event]) { + this.listeners[event] = []; + } + this.listeners[event].push(listener); + }; + + Stdio.prototype.emit = function (event, ...args) { + if (this.listeners[event]) { + this.listeners[event].forEach((listener) => listener(...args)); + } + }; + + function ChildProcess() { + this.listeners = {}; + this.stdin = new Stdio(); + this.stdout = new Stdio(); + this.stderr = new Stdio(); + } + + ChildProcess.prototype.on = function (event, listener) { + if (!this.listeners[event]) { + this.listeners[event] = []; + } + this.listeners[event].push(listener); + }; + + ChildProcess.prototype.emit = function (event, ...args) { + if (this.listeners[event]) { + this.listeners[event].forEach((listener) => listener(...args)); + } + }; + + export function spawn(command, args, options) { + if (typeof args === "undefined") { + args = []; + } else if (!(args instanceof Array)) { + // allow for (command, options) signature + options = args; + } + + if (typeof options === "undefined") { + options = {}; + } + + const child = new ChildProcess(); + const childCallbackName = getUniqueCallbackName("spawn"); + window[childCallbackName] = child; + + function cleanup(name) { + delete window[name]; + } + + child.on("exit", code => { + cleanup(childCallbackName); + }); + + try { + ksu.spawn( + command, + JSON.stringify(args), + JSON.stringify(options), + childCallbackName + ); + } catch (error) { + child.emit("error", error); + cleanup(childCallbackName); + } + return child; + } + +export function fullScreen(isFullScreen) { + ksu.fullScreen(isFullScreen); +} + +export function toast(message) { + ksu.toast(message); +} diff --git a/KernelSU/js/package.json b/KernelSU/js/package.json new file mode 100644 index 000000000000..02b350f33c79 --- /dev/null +++ b/KernelSU/js/package.json @@ -0,0 +1,26 @@ +{ + "name": "kernelsu", + "version": "1.0.6", + "description": "Library for KernelSU's module WebUI", + "main": "index.js", + "types": "index.d.ts", + "scripts": { + "test": "npm run test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/tiann/KernelSU.git" + }, + "keywords": [ + "su", + "kernelsu", + "module", + "webui" + ], + "author": "weishu", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/tiann/KernelSU/issues" + }, + "homepage": "https://github.com/tiann/KernelSU#readme" +} diff --git a/KernelSU/justfile b/KernelSU/justfile new file mode 100644 index 000000000000..51bef767d4a9 --- /dev/null +++ b/KernelSU/justfile @@ -0,0 +1,14 @@ +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 diff --git a/KernelSU/kernel/.clang-format b/KernelSU/kernel/.clang-format new file mode 100644 index 000000000000..10dc5a9a61b3 --- /dev/null +++ b/KernelSU/kernel/.clang-format @@ -0,0 +1,548 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 4. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +#AlignEscapedNewlines: Left # Unknown to clang-format-4.0 +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + #AfterExternBlock: false # Unknown to clang-format-5.0 + BeforeCatch: false + BeforeElse: false + IndentBraces: false + #SplitEmptyFunction: true # Unknown to clang-format-4.0 + #SplitEmptyRecord: true # Unknown to clang-format-4.0 + #SplitEmptyNamespace: true # Unknown to clang-format-4.0 +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0 +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0 +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +#CompactNamespaces: false # Unknown to clang-format-4.0 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +#FixNamespaceComments: false # Unknown to clang-format-4.0 + +# Taken from: +# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \ +# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ +# | sort | uniq +ForEachMacros: + - 'apei_estatus_for_each_section' + - 'ata_for_each_dev' + - 'ata_for_each_link' + - '__ata_qc_for_each' + - 'ata_qc_for_each' + - 'ata_qc_for_each_raw' + - 'ata_qc_for_each_with_internal' + - 'ax25_for_each' + - 'ax25_uid_for_each' + - '__bio_for_each_bvec' + - 'bio_for_each_bvec' + - 'bio_for_each_bvec_all' + - 'bio_for_each_integrity_vec' + - '__bio_for_each_segment' + - 'bio_for_each_segment' + - 'bio_for_each_segment_all' + - 'bio_list_for_each' + - 'bip_for_each_vec' + - 'bitmap_for_each_clear_region' + - 'bitmap_for_each_set_region' + - 'blkg_for_each_descendant_post' + - 'blkg_for_each_descendant_pre' + - 'blk_queue_for_each_rl' + - 'bond_for_each_slave' + - 'bond_for_each_slave_rcu' + - 'bpf_for_each_spilled_reg' + - 'btree_for_each_safe128' + - 'btree_for_each_safe32' + - 'btree_for_each_safe64' + - 'btree_for_each_safel' + - 'card_for_each_dev' + - 'cgroup_taskset_for_each' + - 'cgroup_taskset_for_each_leader' + - 'cpufreq_for_each_entry' + - 'cpufreq_for_each_entry_idx' + - 'cpufreq_for_each_valid_entry' + - 'cpufreq_for_each_valid_entry_idx' + - 'css_for_each_child' + - 'css_for_each_descendant_post' + - 'css_for_each_descendant_pre' + - 'device_for_each_child_node' + - 'dma_fence_chain_for_each' + - 'do_for_each_ftrace_op' + - 'drm_atomic_crtc_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane_state' + - 'drm_atomic_for_each_plane_damage' + - 'drm_client_for_each_connector_iter' + - 'drm_client_for_each_modeset' + - 'drm_connector_for_each_possible_encoder' + - 'drm_for_each_bridge_in_chain' + - 'drm_for_each_connector_iter' + - 'drm_for_each_crtc' + - 'drm_for_each_encoder' + - 'drm_for_each_encoder_mask' + - 'drm_for_each_fb' + - 'drm_for_each_legacy_plane' + - 'drm_for_each_plane' + - 'drm_for_each_plane_mask' + - 'drm_for_each_privobj' + - 'drm_mm_for_each_hole' + - 'drm_mm_for_each_node' + - 'drm_mm_for_each_node_in_range' + - 'drm_mm_for_each_node_safe' + - 'flow_action_for_each' + - 'for_each_active_dev_scope' + - 'for_each_active_drhd_unit' + - 'for_each_active_iommu' + - 'for_each_aggr_pgid' + - 'for_each_available_child_of_node' + - 'for_each_bio' + - 'for_each_board_func_rsrc' + - 'for_each_bvec' + - 'for_each_card_auxs' + - 'for_each_card_auxs_safe' + - 'for_each_card_components' + - 'for_each_card_dapms' + - 'for_each_card_pre_auxs' + - 'for_each_card_prelinks' + - 'for_each_card_rtds' + - 'for_each_card_rtds_safe' + - 'for_each_card_widgets' + - 'for_each_card_widgets_safe' + - 'for_each_cgroup_storage_type' + - 'for_each_child_of_node' + - 'for_each_clear_bit' + - 'for_each_clear_bit_from' + - 'for_each_cmsghdr' + - 'for_each_compatible_node' + - 'for_each_component_dais' + - 'for_each_component_dais_safe' + - 'for_each_comp_order' + - 'for_each_console' + - 'for_each_cpu' + - 'for_each_cpu_and' + - 'for_each_cpu_not' + - 'for_each_cpu_wrap' + - 'for_each_dapm_widgets' + - 'for_each_dev_addr' + - 'for_each_dev_scope' + - 'for_each_displayid_db' + - 'for_each_dma_cap_mask' + - 'for_each_dpcm_be' + - 'for_each_dpcm_be_rollback' + - 'for_each_dpcm_be_safe' + - 'for_each_dpcm_fe' + - 'for_each_drhd_unit' + - 'for_each_dss_dev' + - 'for_each_efi_memory_desc' + - 'for_each_efi_memory_desc_in_map' + - 'for_each_element' + - 'for_each_element_extid' + - 'for_each_element_id' + - 'for_each_endpoint_of_node' + - 'for_each_evictable_lru' + - 'for_each_fib6_node_rt_rcu' + - 'for_each_fib6_walker_rt' + - 'for_each_free_mem_pfn_range_in_zone' + - 'for_each_free_mem_pfn_range_in_zone_from' + - 'for_each_free_mem_range' + - 'for_each_free_mem_range_reverse' + - 'for_each_func_rsrc' + - 'for_each_hstate' + - 'for_each_if' + - 'for_each_iommu' + - 'for_each_ip_tunnel_rcu' + - 'for_each_irq_nr' + - 'for_each_link_codecs' + - 'for_each_link_cpus' + - 'for_each_link_platforms' + - 'for_each_lru' + - 'for_each_matching_node' + - 'for_each_matching_node_and_match' + - 'for_each_member' + - 'for_each_mem_region' + - 'for_each_memblock_type' + - 'for_each_memcg_cache_index' + - 'for_each_mem_pfn_range' + - '__for_each_mem_range' + - 'for_each_mem_range' + - '__for_each_mem_range_rev' + - 'for_each_mem_range_rev' + - 'for_each_migratetype_order' + - 'for_each_msi_entry' + - 'for_each_msi_entry_safe' + - 'for_each_net' + - 'for_each_net_continue_reverse' + - 'for_each_netdev' + - 'for_each_netdev_continue' + - 'for_each_netdev_continue_rcu' + - 'for_each_netdev_continue_reverse' + - 'for_each_netdev_feature' + - 'for_each_netdev_in_bond_rcu' + - 'for_each_netdev_rcu' + - 'for_each_netdev_reverse' + - 'for_each_netdev_safe' + - 'for_each_net_rcu' + - 'for_each_new_connector_in_state' + - 'for_each_new_crtc_in_state' + - 'for_each_new_mst_mgr_in_state' + - 'for_each_new_plane_in_state' + - 'for_each_new_private_obj_in_state' + - 'for_each_node' + - 'for_each_node_by_name' + - 'for_each_node_by_type' + - 'for_each_node_mask' + - 'for_each_node_state' + - 'for_each_node_with_cpus' + - 'for_each_node_with_property' + - 'for_each_nonreserved_multicast_dest_pgid' + - 'for_each_of_allnodes' + - 'for_each_of_allnodes_from' + - 'for_each_of_cpu_node' + - 'for_each_of_pci_range' + - 'for_each_old_connector_in_state' + - 'for_each_old_crtc_in_state' + - 'for_each_old_mst_mgr_in_state' + - 'for_each_oldnew_connector_in_state' + - 'for_each_oldnew_crtc_in_state' + - 'for_each_oldnew_mst_mgr_in_state' + - 'for_each_oldnew_plane_in_state' + - 'for_each_oldnew_plane_in_state_reverse' + - 'for_each_oldnew_private_obj_in_state' + - 'for_each_old_plane_in_state' + - 'for_each_old_private_obj_in_state' + - 'for_each_online_cpu' + - 'for_each_online_node' + - 'for_each_online_pgdat' + - 'for_each_pci_bridge' + - 'for_each_pci_dev' + - 'for_each_pci_msi_entry' + - 'for_each_pcm_streams' + - 'for_each_physmem_range' + - 'for_each_populated_zone' + - 'for_each_possible_cpu' + - 'for_each_present_cpu' + - 'for_each_prime_number' + - 'for_each_prime_number_from' + - 'for_each_process' + - 'for_each_process_thread' + - 'for_each_property_of_node' + - 'for_each_registered_fb' + - 'for_each_requested_gpio' + - 'for_each_requested_gpio_in_range' + - 'for_each_reserved_mem_range' + - 'for_each_reserved_mem_region' + - 'for_each_rtd_codec_dais' + - 'for_each_rtd_codec_dais_rollback' + - 'for_each_rtd_components' + - 'for_each_rtd_cpu_dais' + - 'for_each_rtd_cpu_dais_rollback' + - 'for_each_rtd_dais' + - 'for_each_set_bit' + - 'for_each_set_bit_from' + - 'for_each_set_clump8' + - 'for_each_sg' + - 'for_each_sg_dma_page' + - 'for_each_sg_page' + - 'for_each_sgtable_dma_page' + - 'for_each_sgtable_dma_sg' + - 'for_each_sgtable_page' + - 'for_each_sgtable_sg' + - 'for_each_sibling_event' + - 'for_each_subelement' + - 'for_each_subelement_extid' + - 'for_each_subelement_id' + - '__for_each_thread' + - 'for_each_thread' + - 'for_each_unicast_dest_pgid' + - 'for_each_wakeup_source' + - 'for_each_zone' + - 'for_each_zone_zonelist' + - 'for_each_zone_zonelist_nodemask' + - 'fwnode_for_each_available_child_node' + - 'fwnode_for_each_child_node' + - 'fwnode_graph_for_each_endpoint' + - 'gadget_for_each_ep' + - 'genradix_for_each' + - 'genradix_for_each_from' + - 'hash_for_each' + - 'hash_for_each_possible' + - 'hash_for_each_possible_rcu' + - 'hash_for_each_possible_rcu_notrace' + - 'hash_for_each_possible_safe' + - 'hash_for_each_rcu' + - 'hash_for_each_safe' + - 'hctx_for_each_ctx' + - 'hlist_bl_for_each_entry' + - 'hlist_bl_for_each_entry_rcu' + - 'hlist_bl_for_each_entry_safe' + - 'hlist_for_each' + - 'hlist_for_each_entry' + - 'hlist_for_each_entry_continue' + - 'hlist_for_each_entry_continue_rcu' + - 'hlist_for_each_entry_continue_rcu_bh' + - 'hlist_for_each_entry_from' + - 'hlist_for_each_entry_from_rcu' + - 'hlist_for_each_entry_rcu' + - 'hlist_for_each_entry_rcu_bh' + - 'hlist_for_each_entry_rcu_notrace' + - 'hlist_for_each_entry_safe' + - '__hlist_for_each_rcu' + - 'hlist_for_each_safe' + - 'hlist_nulls_for_each_entry' + - 'hlist_nulls_for_each_entry_from' + - 'hlist_nulls_for_each_entry_rcu' + - 'hlist_nulls_for_each_entry_safe' + - 'i3c_bus_for_each_i2cdev' + - 'i3c_bus_for_each_i3cdev' + - 'ide_host_for_each_port' + - 'ide_port_for_each_dev' + - 'ide_port_for_each_present_dev' + - 'idr_for_each_entry' + - 'idr_for_each_entry_continue' + - 'idr_for_each_entry_continue_ul' + - 'idr_for_each_entry_ul' + - 'in_dev_for_each_ifa_rcu' + - 'in_dev_for_each_ifa_rtnl' + - 'inet_bind_bucket_for_each' + - 'inet_lhash2_for_each_icsk_rcu' + - 'key_for_each' + - 'key_for_each_safe' + - 'klp_for_each_func' + - 'klp_for_each_func_safe' + - 'klp_for_each_func_static' + - 'klp_for_each_object' + - 'klp_for_each_object_safe' + - 'klp_for_each_object_static' + - 'kunit_suite_for_each_test_case' + - 'kvm_for_each_memslot' + - 'kvm_for_each_vcpu' + - 'list_for_each' + - 'list_for_each_codec' + - 'list_for_each_codec_safe' + - 'list_for_each_continue' + - 'list_for_each_entry' + - 'list_for_each_entry_continue' + - 'list_for_each_entry_continue_rcu' + - 'list_for_each_entry_continue_reverse' + - 'list_for_each_entry_from' + - 'list_for_each_entry_from_rcu' + - 'list_for_each_entry_from_reverse' + - 'list_for_each_entry_lockless' + - 'list_for_each_entry_rcu' + - 'list_for_each_entry_reverse' + - 'list_for_each_entry_safe' + - 'list_for_each_entry_safe_continue' + - 'list_for_each_entry_safe_from' + - 'list_for_each_entry_safe_reverse' + - 'list_for_each_prev' + - 'list_for_each_prev_safe' + - 'list_for_each_safe' + - 'llist_for_each' + - 'llist_for_each_entry' + - 'llist_for_each_entry_safe' + - 'llist_for_each_safe' + - 'mci_for_each_dimm' + - 'media_device_for_each_entity' + - 'media_device_for_each_intf' + - 'media_device_for_each_link' + - 'media_device_for_each_pad' + - 'nanddev_io_for_each_page' + - 'netdev_for_each_lower_dev' + - 'netdev_for_each_lower_private' + - 'netdev_for_each_lower_private_rcu' + - 'netdev_for_each_mc_addr' + - 'netdev_for_each_uc_addr' + - 'netdev_for_each_upper_dev_rcu' + - 'netdev_hw_addr_list_for_each' + - 'nft_rule_for_each_expr' + - 'nla_for_each_attr' + - 'nla_for_each_nested' + - 'nlmsg_for_each_attr' + - 'nlmsg_for_each_msg' + - 'nr_neigh_for_each' + - 'nr_neigh_for_each_safe' + - 'nr_node_for_each' + - 'nr_node_for_each_safe' + - 'of_for_each_phandle' + - 'of_property_for_each_string' + - 'of_property_for_each_u32' + - 'pci_bus_for_each_resource' + - 'pcm_for_each_format' + - 'ping_portaddr_for_each_entry' + - 'plist_for_each' + - 'plist_for_each_continue' + - 'plist_for_each_entry' + - 'plist_for_each_entry_continue' + - 'plist_for_each_entry_safe' + - 'plist_for_each_safe' + - 'pnp_for_each_card' + - 'pnp_for_each_dev' + - 'protocol_for_each_card' + - 'protocol_for_each_dev' + - 'queue_for_each_hw_ctx' + - 'radix_tree_for_each_slot' + - 'radix_tree_for_each_tagged' + - 'rbtree_postorder_for_each_entry_safe' + - 'rdma_for_each_block' + - 'rdma_for_each_port' + - 'rdma_umem_for_each_dma_block' + - 'resource_list_for_each_entry' + - 'resource_list_for_each_entry_safe' + - 'rhl_for_each_entry_rcu' + - 'rhl_for_each_rcu' + - 'rht_for_each' + - 'rht_for_each_entry' + - 'rht_for_each_entry_from' + - 'rht_for_each_entry_rcu' + - 'rht_for_each_entry_rcu_from' + - 'rht_for_each_entry_safe' + - 'rht_for_each_from' + - 'rht_for_each_rcu' + - 'rht_for_each_rcu_from' + - '__rq_for_each_bio' + - 'rq_for_each_bvec' + - 'rq_for_each_segment' + - 'scsi_for_each_prot_sg' + - 'scsi_for_each_sg' + - 'sctp_for_each_hentry' + - 'sctp_skb_for_each' + - 'shdma_for_each_chan' + - '__shost_for_each_device' + - 'shost_for_each_device' + - 'sk_for_each' + - 'sk_for_each_bound' + - 'sk_for_each_entry_offset_rcu' + - 'sk_for_each_from' + - 'sk_for_each_rcu' + - 'sk_for_each_safe' + - 'sk_nulls_for_each' + - 'sk_nulls_for_each_from' + - 'sk_nulls_for_each_rcu' + - 'snd_array_for_each' + - 'snd_pcm_group_for_each_entry' + - 'snd_soc_dapm_widget_for_each_path' + - 'snd_soc_dapm_widget_for_each_path_safe' + - 'snd_soc_dapm_widget_for_each_sink_path' + - 'snd_soc_dapm_widget_for_each_source_path' + - 'tb_property_for_each' + - 'tcf_exts_for_each_action' + - 'udp_portaddr_for_each_entry' + - 'udp_portaddr_for_each_entry_rcu' + - 'usb_hub_for_each_child' + - 'v4l2_device_for_each_subdev' + - 'v4l2_m2m_for_each_dst_buf' + - 'v4l2_m2m_for_each_dst_buf_safe' + - 'v4l2_m2m_for_each_src_buf' + - 'v4l2_m2m_for_each_src_buf_safe' + - 'virtio_device_for_each_vq' + - 'while_for_each_ftrace_op' + - 'xa_for_each' + - 'xa_for_each_marked' + - 'xa_for_each_range' + - 'xa_for_each_start' + - 'xas_for_each' + - 'xas_for_each_conflict' + - 'xas_for_each_marked' + - 'xbc_array_for_each_value' + - 'xbc_for_each_key_value' + - 'xbc_node_for_each_array_value' + - 'xbc_node_for_each_child' + - 'xbc_node_for_each_key_value' + - 'zorro_for_each_dev' + +#IncludeBlocks: Preserve # Unknown to clang-format-5.0 +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +#IndentPPDirectives: None # Unknown to clang-format-5.0 +IndentWidth: 8 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0 +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +#SortUsingDeclarations: false # Unknown to clang-format-4.0 +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0 +#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0 +SpaceBeforeParens: ControlStatements +#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0 +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Always +... diff --git a/KernelSU/kernel/Kconfig b/KernelSU/kernel/Kconfig new file mode 100644 index 000000000000..67f177f4708a --- /dev/null +++ b/KernelSU/kernel/Kconfig @@ -0,0 +1,19 @@ +menu "KernelSU" + +config KSU + tristate "KernelSU function support" + depends on OVERLAY_FS + default y + help + Enable kernel-level root privileges on Android System. + To compile as a module, choose M here: the + module will be called kernelsu. + +config KSU_DEBUG + bool "KernelSU debug mode" + depends on KSU + default n + help + Enable KernelSU debug mode. + +endmenu diff --git a/KernelSU/kernel/LICENSE b/KernelSU/kernel/LICENSE new file mode 100644 index 000000000000..d159169d1050 --- /dev/null +++ b/KernelSU/kernel/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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. diff --git a/KernelSU/kernel/Makefile b/KernelSU/kernel/Makefile new file mode 100644 index 000000000000..82618aa8514e --- /dev/null +++ b/KernelSU/kernel/Makefile @@ -0,0 +1,70 @@ +kernelsu-objs := ksu.o +kernelsu-objs += allowlist.o +kernelsu-objs += apk_sign.o +kernelsu-objs += sucompat.o +kernelsu-objs += throne_tracker.o +kernelsu-objs += core_hook.o +kernelsu-objs += ksud.o +kernelsu-objs += embed_ksud.o +kernelsu-objs += kernel_compat.o + +kernelsu-objs += selinux/selinux.o +kernelsu-objs += selinux/sepolicy.o +kernelsu-objs += selinux/rules.o +ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include +ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h + +obj-$(CONFIG_KSU) += kernelsu.o + +# .git is a text file while the module is imported by 'git submodule add'. +ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0) +$(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin [ -f ../.git/shallow ] && git fetch --unshallow) +KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count HEAD) +# ksu_version: major * 10000 + git version + 200 for historical reasons +$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 200)) +$(info -- KernelSU version: $(KSU_VERSION)) +ccflags-y += -DKSU_VERSION=$(KSU_VERSION) +else # Preserve the pinned v0.9.5 version when KernelSU is vendored. +KSU_VERSION := 11872 +$(info -- KernelSU version: $(KSU_VERSION) (vendored v0.9.5)) +ccflags-y += -DKSU_VERSION=$(KSU_VERSION) +endif + +ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0) +ccflags-y += -DKSU_COMPAT_HAS_CURRENT_SID +endif + +ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/include/security.h; echo $$?),0) +ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE +endif + +ifndef KSU_EXPECTED_SIZE +KSU_EXPECTED_SIZE := 0x033b +endif + +ifndef KSU_EXPECTED_HASH +KSU_EXPECTED_HASH := c371061b19d8c7d7d6133c6a9bafe198fa944e50c1b31c9d8daa8d7f1fc2d2d6 +endif + +ifdef KSU_MANAGER_PACKAGE +ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\" +$(info -- KernelSU Manager package name: $(KSU_MANAGER_PACKAGE)) +endif + +$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE)) +$(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH)) + +ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE) +ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\" + +ifeq ($(shell grep -q "int path_umount" $(srctree)/fs/namespace.c; echo $$?),0) +ccflags-y += -DKSU_UMOUNT +else +$(info -- Did you know you can backport path_umount to fs/namespace.c from 5.9?) +$(info -- Read: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#how-to-backport-path-umount) +endif + +ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat +ccflags-y += -Wno-declaration-after-statement -Wno-unused-function + +# Keep a new line here!! Because someone may append config diff --git a/KernelSU/kernel/allowlist.c b/KernelSU/kernel/allowlist.c new file mode 100644 index 000000000000..4fbba9355949 --- /dev/null +++ b/KernelSU/kernel/allowlist.c @@ -0,0 +1,529 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) +#include +#endif + +#include "ksu.h" +#include "klog.h" // IWYU pragma: keep +#include "selinux/selinux.h" +#include "kernel_compat.h" +#include "allowlist.h" +#include "manager.h" + +#define FILE_MAGIC 0x7f4b5355 // ' KSU', u32 +#define FILE_FORMAT_VERSION 3 // u32 + +#define KSU_APP_PROFILE_PRESERVE_UID 9999 // NOBODY_UID +#define KSU_DEFAULT_SELINUX_DOMAIN "u:r:su:s0" + +static DEFINE_MUTEX(allowlist_mutex); + +// default profiles, these may be used frequently, so we cache it +static struct root_profile default_root_profile; +static struct non_root_profile default_non_root_profile; + +static int allow_list_arr[PAGE_SIZE / sizeof(int)] __read_mostly __aligned(PAGE_SIZE); +static int allow_list_pointer __read_mostly = 0; + +static void remove_uid_from_arr(uid_t uid) +{ + int *temp_arr; + int i, j; + + if (allow_list_pointer == 0) + return; + + temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL); + if (temp_arr == NULL) { + pr_err("%s: unable to allocate memory\n", __func__); + return; + } + + for (i = j = 0; i < allow_list_pointer; i++) { + if (allow_list_arr[i] == uid) + continue; + temp_arr[j++] = allow_list_arr[i]; + } + + allow_list_pointer = j; + + for (; j < ARRAY_SIZE(allow_list_arr); j++) + temp_arr[j] = -1; + + memcpy(&allow_list_arr, temp_arr, PAGE_SIZE); + kfree(temp_arr); +} + +static void init_default_profiles() +{ + default_root_profile.uid = 0; + default_root_profile.gid = 0; + default_root_profile.groups_count = 1; + default_root_profile.groups[0] = 0; + memset(&default_root_profile.capabilities, 0xff, + sizeof(default_root_profile.capabilities)); + default_root_profile.namespaces = 0; + strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN); + + // This means that we will umount modules by default! + default_non_root_profile.umount_modules = true; +} + +struct perm_data { + struct list_head list; + struct app_profile profile; +}; + +static struct list_head allow_list; + +static uint8_t allow_list_bitmap[PAGE_SIZE] __read_mostly __aligned(PAGE_SIZE); +#define BITMAP_UID_MAX ((sizeof(allow_list_bitmap) * BITS_PER_BYTE) - 1) + +#define KERNEL_SU_ALLOWLIST "/data/adb/ksu/.allowlist" + +static struct work_struct ksu_save_work; +static struct work_struct ksu_load_work; + +bool persistent_allow_list(void); + +void ksu_show_allow_list(void) +{ + struct perm_data *p = NULL; + struct list_head *pos = NULL; + pr_info("ksu_show_allow_list\n"); + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + pr_info("uid :%d, allow: %d\n", p->profile.current_uid, + p->profile.allow_su); + } +} + +#ifdef CONFIG_KSU_DEBUG +static void ksu_grant_root_to_shell() +{ + struct app_profile profile = { + .allow_su = true, + .current_uid = 2000, + }; + strcpy(profile.key, "com.android.shell"); + strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN); + ksu_set_app_profile(&profile, false); +} +#endif + +bool ksu_get_app_profile(struct app_profile *profile) +{ + struct perm_data *p = NULL; + struct list_head *pos = NULL; + bool found = false; + + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + bool uid_match = profile->current_uid == p->profile.current_uid; + if (uid_match) { + // found it, override it with ours + memcpy(profile, &p->profile, sizeof(*profile)); + found = true; + goto exit; + } + } + +exit: + return found; +} + +static inline bool forbid_system_uid(uid_t uid) { + #define SHELL_UID 2000 + #define SYSTEM_UID 1000 + return uid < SHELL_UID && uid != SYSTEM_UID; +} + +static bool profile_valid(struct app_profile *profile) +{ + if (!profile) { + return false; + } + + if (forbid_system_uid(profile->current_uid)) { + pr_err("uid lower than 2000 is unsupported: %d\n", profile->current_uid); + return false; + } + + if (profile->version < KSU_APP_PROFILE_VER) { + pr_info("Unsupported profile version: %d\n", profile->version); + return false; + } + + if (profile->allow_su) { + if (profile->rp_config.profile.groups_count > KSU_MAX_GROUPS) { + return false; + } + + if (strlen(profile->rp_config.profile.selinux_domain) == 0) { + return false; + } + } + + return true; +} + +bool ksu_set_app_profile(struct app_profile *profile, bool persist) +{ + struct perm_data *p = NULL; + struct list_head *pos = NULL; + bool result = false; + + if (!profile_valid(profile)) { + pr_err("Failed to set app profile: invalid profile!\n"); + return false; + } + + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + // both uid and package must match, otherwise it will break multiple package with different user id + if (profile->current_uid == p->profile.current_uid && + !strcmp(profile->key, p->profile.key)) { + // found it, just override it all! + memcpy(&p->profile, profile, sizeof(*profile)); + result = true; + goto out; + } + } + + // not found, alloc a new node! + p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL); + if (!p) { + pr_err("ksu_set_app_profile alloc failed\n"); + return false; + } + + memcpy(&p->profile, profile, sizeof(*profile)); + if (profile->allow_su) { + pr_info("set root profile, key: %s, uid: %d, gid: %d, context: %s\n", + profile->key, profile->current_uid, + profile->rp_config.profile.gid, + profile->rp_config.profile.selinux_domain); + } else { + pr_info("set app profile, key: %s, uid: %d, umount modules: %d\n", + profile->key, profile->current_uid, + profile->nrp_config.profile.umount_modules); + } + list_add_tail(&p->list, &allow_list); + +out: + if (profile->current_uid <= BITMAP_UID_MAX) { + if (profile->allow_su) + allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |= 1 << (profile->current_uid % BITS_PER_BYTE); + else + allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE)); + } else { + if (profile->allow_su) { + /* + * 1024 apps with uid higher than BITMAP_UID_MAX + * registered to request superuser? + */ + if (allow_list_pointer >= ARRAY_SIZE(allow_list_arr)) { + pr_err("too many apps registered\n"); + WARN_ON(1); + return false; + } + allow_list_arr[allow_list_pointer++] = profile->current_uid; + } else { + remove_uid_from_arr(profile->current_uid); + } + } + result = true; + + // check if the default profiles is changed, cache it to a single struct to accelerate access. + if (unlikely(!strcmp(profile->key, "$"))) { + // set default non root profile + memcpy(&default_non_root_profile, &profile->nrp_config.profile, + sizeof(default_non_root_profile)); + } + + if (unlikely(!strcmp(profile->key, "#"))) { + // set default root profile + memcpy(&default_root_profile, &profile->rp_config.profile, + sizeof(default_root_profile)); + } + + if (persist) + persistent_allow_list(); + + return result; +} + +bool __ksu_is_allow_uid(uid_t uid) +{ + int i; + + if (unlikely(uid == 0)) { + // already root, but only allow our domain. + return is_ksu_domain(); + } + + if (forbid_system_uid(uid)) { + // do not bother going through the list if it's system + return false; + } + + if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) { + // manager is always allowed! + return true; + } + + if (likely(uid <= BITMAP_UID_MAX)) { + return !!(allow_list_bitmap[uid / BITS_PER_BYTE] & (1 << (uid % BITS_PER_BYTE))); + } else { + for (i = 0; i < allow_list_pointer; i++) { + if (allow_list_arr[i] == uid) + return true; + } + } + + return false; +} + +bool ksu_uid_should_umount(uid_t uid) +{ + struct app_profile profile = { .current_uid = uid }; + if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) { + // we should not umount on manager! + return false; + } + bool found = ksu_get_app_profile(&profile); + if (!found) { + // no app profile found, it must be non root app + return default_non_root_profile.umount_modules; + } + if (profile.allow_su) { + // if found and it is granted to su, we shouldn't umount for it + return false; + } else { + // found an app profile + if (profile.nrp_config.use_default) { + return default_non_root_profile.umount_modules; + } else { + return profile.nrp_config.profile.umount_modules; + } + } +} + +struct root_profile *ksu_get_root_profile(uid_t uid) +{ + struct perm_data *p = NULL; + struct list_head *pos = NULL; + + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + if (uid == p->profile.current_uid && p->profile.allow_su) { + if (!p->profile.rp_config.use_default) { + return &p->profile.rp_config.profile; + } + } + } + + // use default profile + return &default_root_profile; +} + +bool ksu_get_allow_list(int *array, int *length, bool allow) +{ + struct perm_data *p = NULL; + struct list_head *pos = NULL; + int i = 0; + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + // pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow); + if (p->profile.allow_su == allow) { + array[i++] = p->profile.current_uid; + } + } + *length = i; + + return true; +} + +void do_save_allow_list(struct work_struct *work) +{ + u32 magic = FILE_MAGIC; + u32 version = FILE_FORMAT_VERSION; + struct perm_data *p = NULL; + struct list_head *pos = NULL; + loff_t off = 0; + + struct file *fp = + ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (IS_ERR(fp)) { + pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp)); + return; + } + + // store magic and version + if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != + sizeof(magic)) { + pr_err("save_allow_list write magic failed.\n"); + goto exit; + } + + if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != + sizeof(version)) { + pr_err("save_allow_list write version failed.\n"); + goto exit; + } + + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + pr_info("save allow list, name: %s uid :%d, allow: %d\n", + p->profile.key, p->profile.current_uid, + p->profile.allow_su); + + ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile), + &off); + } + +exit: + filp_close(fp, 0); +} + +void do_load_allow_list(struct work_struct *work) +{ + loff_t off = 0; + ssize_t ret = 0; + struct file *fp = NULL; + u32 magic; + u32 version; + +#ifdef CONFIG_KSU_DEBUG + // always allow adb shell by default + ksu_grant_root_to_shell(); +#endif + + // load allowlist now! + fp = ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_RDONLY, 0); + if (IS_ERR(fp)) { + pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp)); + return; + } + + // verify magic + if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != + sizeof(magic) || + magic != FILE_MAGIC) { + pr_err("allowlist file invalid: %d!\n", magic); + goto exit; + } + + if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != + sizeof(version)) { + pr_err("allowlist read version: %d failed\n", version); + goto exit; + } + + pr_info("allowlist version: %d\n", version); + + while (true) { + struct app_profile profile; + + ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile), + &off); + + if (ret <= 0) { + pr_info("load_allow_list read err: %zd\n", ret); + break; + } + + pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n", + profile.key, profile.current_uid, profile.allow_su); + ksu_set_app_profile(&profile, false); + } + +exit: + ksu_show_allow_list(); + filp_close(fp, 0); +} + +void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data) +{ + struct perm_data *np = NULL; + struct perm_data *n = NULL; + + bool modified = false; + // TODO: use RCU! + mutex_lock(&allowlist_mutex); + list_for_each_entry_safe (np, n, &allow_list, list) { + uid_t uid = np->profile.current_uid; + char *package = np->profile.key; + // we use this uid for special cases, don't prune it! + bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID; + if (!is_preserved_uid && !is_uid_valid(uid, package, data)) { + modified = true; + pr_info("prune uid: %d, package: %s\n", uid, package); + list_del(&np->list); + if (likely(uid <= BITMAP_UID_MAX)) { + allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE)); + } + remove_uid_from_arr(uid); + smp_mb(); + kfree(np); + } + } + mutex_unlock(&allowlist_mutex); + + if (modified) { + persistent_allow_list(); + } +} + +// make sure allow list works cross boot +bool persistent_allow_list(void) +{ + return ksu_queue_work(&ksu_save_work); +} + +bool ksu_load_allow_list(void) +{ + return ksu_queue_work(&ksu_load_work); +} + +void ksu_allowlist_init(void) +{ + int i; + + BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE); + BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE); + + for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++) + allow_list_arr[i] = -1; + + INIT_LIST_HEAD(&allow_list); + + INIT_WORK(&ksu_save_work, do_save_allow_list); + INIT_WORK(&ksu_load_work, do_load_allow_list); + + init_default_profiles(); +} + +void ksu_allowlist_exit(void) +{ + struct perm_data *np = NULL; + struct perm_data *n = NULL; + + do_save_allow_list(NULL); + + // free allowlist + mutex_lock(&allowlist_mutex); + list_for_each_entry_safe (np, n, &allow_list, list) { + list_del(&np->list); + kfree(np); + } + mutex_unlock(&allowlist_mutex); +} diff --git a/KernelSU/kernel/allowlist.h b/KernelSU/kernel/allowlist.h new file mode 100644 index 000000000000..e89bf71fa10c --- /dev/null +++ b/KernelSU/kernel/allowlist.h @@ -0,0 +1,27 @@ +#ifndef __KSU_H_ALLOWLIST +#define __KSU_H_ALLOWLIST + +#include +#include "ksu.h" + +void ksu_allowlist_init(void); + +void ksu_allowlist_exit(void); + +bool ksu_load_allow_list(void); + +void ksu_show_allow_list(void); + +bool __ksu_is_allow_uid(uid_t uid); +#define ksu_is_allow_uid(uid) unlikely(__ksu_is_allow_uid(uid)) + +bool ksu_get_allow_list(int *array, int *length, bool allow); + +void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, char *, void *), void *data); + +bool ksu_get_app_profile(struct app_profile *); +bool ksu_set_app_profile(struct app_profile *, bool persist); + +bool ksu_uid_should_umount(uid_t uid); +struct root_profile *ksu_get_root_profile(uid_t uid); +#endif diff --git a/KernelSU/kernel/apk_sign.c b/KernelSU/kernel/apk_sign.c new file mode 100644 index 000000000000..ba8b73f2eb2b --- /dev/null +++ b/KernelSU/kernel/apk_sign.c @@ -0,0 +1,320 @@ +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_KSU_DEBUG +#include +#endif +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) +#include +#else +#include +#endif + +#include "apk_sign.h" +#include "klog.h" // IWYU pragma: keep +#include "kernel_compat.h" + + +struct sdesc { + struct shash_desc shash; + char ctx[]; +}; + +static struct sdesc *init_sdesc(struct crypto_shash *alg) +{ + struct sdesc *sdesc; + int size; + + size = sizeof(struct shash_desc) + crypto_shash_descsize(alg); + sdesc = kmalloc(size, GFP_KERNEL); + if (!sdesc) + return ERR_PTR(-ENOMEM); + sdesc->shash.tfm = alg; + return sdesc; +} + +static int calc_hash(struct crypto_shash *alg, const unsigned char *data, + unsigned int datalen, unsigned char *digest) +{ + struct sdesc *sdesc; + int ret; + + sdesc = init_sdesc(alg); + if (IS_ERR(sdesc)) { + pr_info("can't alloc sdesc\n"); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest); + kfree(sdesc); + return ret; +} + +static int ksu_sha256(const unsigned char *data, unsigned int datalen, + unsigned char *digest) +{ + struct crypto_shash *alg; + char *hash_alg_name = "sha256"; + int ret; + + alg = crypto_alloc_shash(hash_alg_name, 0, 0); + if (IS_ERR(alg)) { + pr_info("can't alloc alg %s\n", hash_alg_name); + return PTR_ERR(alg); + } + ret = calc_hash(alg, data, datalen, digest); + crypto_free_shash(alg); + return ret; +} + +static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, + unsigned expected_size, const char *expected_sha256) +{ + ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer-sequence length + ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer length + ksu_kernel_read_compat(fp, size4, 0x4, pos); // signed data length + + *offset += 0x4 * 3; + + ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length + + *pos += *size4; + *offset += 0x4 + *size4; + + ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length + ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length + *offset += 0x4 * 2; + + if (*size4 == expected_size) { + *offset += *size4; + +#define CERT_MAX_LENGTH 1024 + char cert[CERT_MAX_LENGTH]; + if (*size4 > CERT_MAX_LENGTH) { + pr_info("cert length overlimit\n"); + return false; + } + ksu_kernel_read_compat(fp, cert, *size4, pos); + unsigned char digest[SHA256_DIGEST_SIZE]; + if (IS_ERR(ksu_sha256(cert, *size4, digest))) { + pr_info("sha256 error\n"); + return false; + } + + char hash_str[SHA256_DIGEST_SIZE * 2 + 1]; + hash_str[SHA256_DIGEST_SIZE * 2] = '\0'; + + bin2hex(hash_str, digest, SHA256_DIGEST_SIZE); + pr_info("sha256: %s, expected: %s\n", hash_str, + expected_sha256); + if (strcmp(expected_sha256, hash_str) == 0) { + return true; + } + } + return false; +} + +struct zip_entry_header { + uint32_t signature; + uint16_t version; + uint16_t flags; + uint16_t compression; + uint16_t mod_time; + uint16_t mod_date; + uint32_t crc32; + uint32_t compressed_size; + uint32_t uncompressed_size; + uint16_t file_name_length; + uint16_t extra_field_length; +} __attribute__((packed)); + +// This is a necessary but not sufficient condition, but it is enough for us +static bool has_v1_signature_file(struct file *fp) +{ + struct zip_entry_header header; + const char MANIFEST[] = "META-INF/MANIFEST.MF"; + + loff_t pos = 0; + + while (ksu_kernel_read_compat(fp, &header, + sizeof(struct zip_entry_header), &pos) == + sizeof(struct zip_entry_header)) { + if (header.signature != 0x04034b50) { + // ZIP magic: 'PK' + return false; + } + // Read the entry file name + if (header.file_name_length == sizeof(MANIFEST) - 1) { + char fileName[sizeof(MANIFEST)]; + ksu_kernel_read_compat(fp, fileName, + header.file_name_length, &pos); + fileName[header.file_name_length] = '\0'; + + // Check if the entry matches META-INF/MANIFEST.MF + if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) == + 0) { + return true; + } + } else { + // Skip the entry file name + pos += header.file_name_length; + } + + // Skip to the next entry + pos += header.extra_field_length + header.compressed_size; + } + + return false; +} + +static __always_inline bool check_v2_signature(char *path, + unsigned expected_size, + const char *expected_sha256) +{ + unsigned char buffer[0x11] = { 0 }; + u32 size4; + u64 size8, size_of_block; + + loff_t pos; + + bool v2_signing_valid = false; + int v2_signing_blocks = 0; + bool v3_signing_exist = false; + bool v3_1_signing_exist = false; + + int i; + struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0); + if (IS_ERR(fp)) { + pr_err("open %s error.\n", path); + return false; + } + + // disable inotify for this file + fp->f_mode |= FMODE_NONOTIFY; + + // https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD) + for (i = 0;; ++i) { + unsigned short n; + pos = generic_file_llseek(fp, -i - 2, SEEK_END); + ksu_kernel_read_compat(fp, &n, 2, &pos); + if (n == i) { + pos -= 22; + ksu_kernel_read_compat(fp, &size4, 4, &pos); + if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) { + break; + } + } + if (i == 0xffff) { + pr_info("error: cannot find eocd\n"); + goto clean; + } + } + + pos += 12; + // offset + ksu_kernel_read_compat(fp, &size4, 0x4, &pos); + pos = size4 - 0x18; + + ksu_kernel_read_compat(fp, &size8, 0x8, &pos); + ksu_kernel_read_compat(fp, buffer, 0x10, &pos); + if (strcmp((char *)buffer, "APK Sig Block 42")) { + goto clean; + } + + pos = size4 - (size8 + 0x8); + ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos); + if (size_of_block != size8) { + goto clean; + } + + int loop_count = 0; + while (loop_count++ < 10) { + uint32_t id; + uint32_t offset; + ksu_kernel_read_compat(fp, &size8, 0x8, + &pos); // sequence length + if (size8 == size_of_block) { + break; + } + ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id + offset = 4; + if (id == 0x7109871au) { + v2_signing_blocks++; + v2_signing_valid = + check_block(fp, &size4, &pos, &offset, + expected_size, expected_sha256); + } else if (id == 0xf05368c0u) { + // http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#73 + v3_signing_exist = true; + } else if (id == 0x1b93ad61u) { + // http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#74 + v3_1_signing_exist = true; + } else { +#ifdef CONFIG_KSU_DEBUG + pr_info("Unknown id: 0x%08x\n", id); +#endif + } + pos += (size8 - offset); + } + + if (v2_signing_blocks != 1) { +#ifdef CONFIG_KSU_DEBUG + pr_err("Unexpected v2 signature count: %d\n", + v2_signing_blocks); +#endif + v2_signing_valid = false; + } + + if (v2_signing_valid) { + int has_v1_signing = has_v1_signature_file(fp); + if (has_v1_signing) { + pr_err("Unexpected v1 signature scheme found!\n"); + filp_close(fp, 0); + return false; + } + } +clean: + filp_close(fp, 0); + + if (v3_signing_exist || v3_1_signing_exist) { +#ifdef CONFIG_KSU_DEBUG + pr_err("Unexpected v3 signature scheme found!\n"); +#endif + return false; + } + + return v2_signing_valid; +} + +#ifdef CONFIG_KSU_DEBUG + +int ksu_debug_manager_uid = -1; + +#include "manager.h" + +static int set_expected_size(const char *val, const struct kernel_param *kp) +{ + int rv = param_set_uint(val, kp); + ksu_set_manager_uid(ksu_debug_manager_uid); + pr_info("ksu_manager_uid set to %d\n", ksu_debug_manager_uid); + return rv; +} + +static struct kernel_param_ops expected_size_ops = { + .set = set_expected_size, + .get = param_get_uint, +}; + +module_param_cb(ksu_debug_manager_uid, &expected_size_ops, + &ksu_debug_manager_uid, S_IRUSR | S_IWUSR); + +#endif + +bool is_manager_apk(char *path) +{ + return check_v2_signature(path, EXPECTED_SIZE, EXPECTED_HASH); +} \ No newline at end of file diff --git a/KernelSU/kernel/apk_sign.h b/KernelSU/kernel/apk_sign.h new file mode 100644 index 000000000000..bed501c49264 --- /dev/null +++ b/KernelSU/kernel/apk_sign.h @@ -0,0 +1,8 @@ +#ifndef __KSU_H_APK_V2_SIGN +#define __KSU_H_APK_V2_SIGN + +#include + +bool is_manager_apk(char *path); + +#endif diff --git a/KernelSU/kernel/arch.h b/KernelSU/kernel/arch.h new file mode 100644 index 000000000000..f36ec5f509fa --- /dev/null +++ b/KernelSU/kernel/arch.h @@ -0,0 +1,92 @@ +#ifndef __KSU_H_ARCH +#define __KSU_H_ARCH + +#include + +#if defined(__aarch64__) + +#define __PT_PARM1_REG regs[0] +#define __PT_PARM2_REG regs[1] +#define __PT_PARM3_REG regs[2] +#define __PT_SYSCALL_PARM4_REG regs[3] +#define __PT_CCALL_PARM4_REG regs[3] +#define __PT_PARM5_REG regs[4] +#define __PT_PARM6_REG regs[5] +#define __PT_RET_REG regs[30] +#define __PT_FP_REG regs[29] /* Works only with CONFIG_FRAME_POINTER */ +#define __PT_RC_REG regs[0] +#define __PT_SP_REG sp +#define __PT_IP_REG pc + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) +#define PRCTL_SYMBOL "__arm64_sys_prctl" +#define SYS_READ_SYMBOL "__arm64_sys_read" +#define SYS_NEWFSTATAT_SYMBOL "__arm64_sys_newfstatat" +#define SYS_FACCESSAT_SYMBOL "__arm64_sys_faccessat" +#define SYS_EXECVE_SYMBOL "__arm64_sys_execve" +#else +#define PRCTL_SYMBOL "sys_prctl" +#define SYS_READ_SYMBOL "sys_read" +#define SYS_NEWFSTATAT_SYMBOL "sys_newfstatat" +#define SYS_FACCESSAT_SYMBOL "sys_faccessat" +#define SYS_EXECVE_SYMBOL "sys_execve" +#endif + +#elif defined(__x86_64__) + +#define __PT_PARM1_REG di +#define __PT_PARM2_REG si +#define __PT_PARM3_REG dx +/* syscall uses r10 for PARM4 */ +#define __PT_SYSCALL_PARM4_REG r10 +#define __PT_CCALL_PARM4_REG cx +#define __PT_PARM5_REG r8 +#define __PT_PARM6_REG r9 +#define __PT_RET_REG sp +#define __PT_FP_REG bp +#define __PT_RC_REG ax +#define __PT_SP_REG sp +#define __PT_IP_REG ip +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) +#define PRCTL_SYMBOL "__x64_sys_prctl" +#define SYS_READ_SYMBOL "__x64_sys_read" +#define SYS_NEWFSTATAT_SYMBOL "__x64_sys_newfstatat" +#define SYS_FACCESSAT_SYMBOL "__x64_sys_faccessat" +#define SYS_EXECVE_SYMBOL "__x64_sys_execve" +#else +#define PRCTL_SYMBOL "sys_prctl" +#define SYS_READ_SYMBOL "sys_read" +#define SYS_NEWFSTATAT_SYMBOL "sys_newfstatat" +#define SYS_FACCESSAT_SYMBOL "sys_faccessat" +#define SYS_EXECVE_SYMBOL "sys_execve" +#endif + +#else +#error "Unsupported arch" +#endif + +/* allow some architecutres to override `struct pt_regs` */ +#ifndef __PT_REGS_CAST +#define __PT_REGS_CAST(x) (x) +#endif + +#define PT_REGS_PARM1(x) (__PT_REGS_CAST(x)->__PT_PARM1_REG) +#define PT_REGS_PARM2(x) (__PT_REGS_CAST(x)->__PT_PARM2_REG) +#define PT_REGS_PARM3(x) (__PT_REGS_CAST(x)->__PT_PARM3_REG) +#define PT_REGS_SYSCALL_PARM4(x) (__PT_REGS_CAST(x)->__PT_SYSCALL_PARM4_REG) +#define PT_REGS_CCALL_PARM4(x) (__PT_REGS_CAST(x)->__PT_CCALL_PARM4_REG) +#define PT_REGS_PARM5(x) (__PT_REGS_CAST(x)->__PT_PARM5_REG) +#define PT_REGS_PARM6(x) (__PT_REGS_CAST(x)->__PT_PARM6_REG) +#define PT_REGS_RET(x) (__PT_REGS_CAST(x)->__PT_RET_REG) +#define PT_REGS_FP(x) (__PT_REGS_CAST(x)->__PT_FP_REG) +#define PT_REGS_RC(x) (__PT_REGS_CAST(x)->__PT_RC_REG) +#define PT_REGS_SP(x) (__PT_REGS_CAST(x)->__PT_SP_REG) +#define PT_REGS_IP(x) (__PT_REGS_CAST(x)->__PT_IP_REG) + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) +#define PT_REAL_REGS(regs) ((struct pt_regs *)PT_REGS_PARM1(regs)) +#else +#define PT_REAL_REGS(regs) ((regs)) +#endif + +#endif diff --git a/KernelSU/kernel/core_hook.c b/KernelSU/kernel/core_hook.c new file mode 100644 index 000000000000..429ba3306ea3 --- /dev/null +++ b/KernelSU/kernel/core_hook.c @@ -0,0 +1,856 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef MODULE +#include +#include +#include +#include +#include +#endif + +#include "allowlist.h" +#include "arch.h" +#include "core_hook.h" +#include "klog.h" // IWYU pragma: keep +#include "ksu.h" +#include "ksud.h" +#include "manager.h" +#include "selinux/selinux.h" +#include "throne_tracker.h" +#include "throne_tracker.h" +#include "kernel_compat.h" + +static bool ksu_module_mounted = false; + +extern int handle_sepolicy(unsigned long arg3, void __user *arg4); + +static inline bool is_allow_su() +{ + if (is_manager()) { + // we are manager, allow! + return true; + } + return ksu_is_allow_uid(current_uid().val); +} + +static inline bool is_unsupported_uid(uid_t uid) +{ +#define LAST_APPLICATION_UID 19999 + uid_t appid = uid % 100000; + return appid > LAST_APPLICATION_UID; +} + +static struct group_info root_groups = { .usage = ATOMIC_INIT(2) }; + +static void setup_groups(struct root_profile *profile, struct cred *cred) +{ + if (profile->groups_count > KSU_MAX_GROUPS) { + pr_warn("Failed to setgroups, too large group: %d!\n", + profile->uid); + return; + } + + if (profile->groups_count == 1 && profile->groups[0] == 0) { + // setgroup to root and return early. + if (cred->group_info) + put_group_info(cred->group_info); + cred->group_info = get_group_info(&root_groups); + return; + } + + u32 ngroups = profile->groups_count; + struct group_info *group_info = groups_alloc(ngroups); + if (!group_info) { + pr_warn("Failed to setgroups, ENOMEM for: %d\n", profile->uid); + return; + } + + int i; + for (i = 0; i < ngroups; i++) { + gid_t gid = profile->groups[i]; + kgid_t kgid = make_kgid(current_user_ns(), gid); + if (!gid_valid(kgid)) { + pr_warn("Failed to setgroups, invalid gid: %d\n", gid); + put_group_info(group_info); + return; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) + group_info->gid[i] = kgid; +#else + GROUP_AT(group_info, i) = kgid; +#endif + } + + groups_sort(group_info); + set_groups(cred, group_info); +} + +void escape_to_root(void) +{ + struct cred *cred; + + cred = (struct cred *)__task_cred(current); + + if (cred->euid.val == 0) { + pr_warn("Already root, don't escape!\n"); + return; + } + struct root_profile *profile = ksu_get_root_profile(cred->uid.val); + + cred->uid.val = profile->uid; + cred->suid.val = profile->uid; + cred->euid.val = profile->uid; + cred->fsuid.val = profile->uid; + + cred->gid.val = profile->gid; + cred->fsgid.val = profile->gid; + cred->sgid.val = profile->gid; + cred->egid.val = profile->gid; + + BUILD_BUG_ON(sizeof(profile->capabilities.effective) != + sizeof(kernel_cap_t)); + + // setup capabilities + // we need CAP_DAC_READ_SEARCH becuase `/data/adb/ksud` is not accessible for non root process + // we add it here but don't add it to cap_inhertiable, it would be dropped automaticly after exec! + u64 cap_for_ksud = + profile->capabilities.effective | CAP_DAC_READ_SEARCH; + memcpy(&cred->cap_effective, &cap_for_ksud, + sizeof(cred->cap_effective)); + memcpy(&cred->cap_inheritable, &profile->capabilities.effective, + sizeof(cred->cap_inheritable)); + memcpy(&cred->cap_permitted, &profile->capabilities.effective, + sizeof(cred->cap_permitted)); + memcpy(&cred->cap_bset, &profile->capabilities.effective, + sizeof(cred->cap_bset)); + memcpy(&cred->cap_ambient, &profile->capabilities.effective, + sizeof(cred->cap_ambient)); + + // disable seccomp +#if defined(CONFIG_GENERIC_ENTRY) && \ + LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) + current_thread_info()->syscall_work &= ~SYSCALL_WORK_SECCOMP; +#else + current_thread_info()->flags &= ~(TIF_SECCOMP | _TIF_SECCOMP); +#endif + +#ifdef CONFIG_SECCOMP + current->seccomp.mode = 0; + current->seccomp.filter = NULL; +#else +#endif + + setup_groups(profile, cred); + + setup_selinux(profile->selinux_domain); +} + +int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry) +{ + if (!current->mm) { + // skip kernel threads + return 0; + } + + if (current_uid().val != 1000) { + // skip non system uid + return 0; + } + + if (!old_dentry || !new_dentry) { + return 0; + } + + // /data/system/packages.list.tmp -> /data/system/packages.list + if (strcmp(new_dentry->d_iname, "packages.list")) { + return 0; + } + + char path[128]; + char *buf = dentry_path_raw(new_dentry, path, sizeof(path)); + if (IS_ERR(buf)) { + pr_err("dentry_path_raw failed.\n"); + return 0; + } + + if (strcmp(buf, "/system/packages.list")) { + return 0; + } + pr_info("renameat: %s -> %s, new path: %s\n", old_dentry->d_iname, + new_dentry->d_iname, buf); + + track_throne(); + + return 0; +} + +int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + // if success, we modify the arg5 as result! + u32 *result = (u32 *)arg5; + u32 reply_ok = KERNEL_SU_OPTION; + + if (KERNEL_SU_OPTION != option) { + return 0; + } + + // TODO: find it in throne tracker! + uid_t current_uid_val = current_uid().val; + uid_t manager_uid = ksu_get_manager_uid(); + if (current_uid_val != manager_uid && + current_uid_val % 100000 == manager_uid) { + ksu_set_manager_uid(current_uid_val); + } + + bool from_root = 0 == current_uid().val; + bool from_manager = is_manager(); + + if (!from_root && !from_manager) { + // only root or manager can access this interface + return 0; + } + +#ifdef CONFIG_KSU_DEBUG + pr_info("option: 0x%x, cmd: %ld\n", option, arg2); +#endif + + if (arg2 == CMD_BECOME_MANAGER) { + if (from_manager) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("become_manager: prctl reply error\n"); + } + return 0; + } + return 0; + } + + if (arg2 == CMD_GRANT_ROOT) { + if (is_allow_su()) { + pr_info("allow root for: %d\n", current_uid().val); + escape_to_root(); + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("grant_root: prctl reply error\n"); + } + } + return 0; + } + + // Both root manager and root processes should be allowed to get version + if (arg2 == CMD_GET_VERSION) { + u32 version = KERNEL_SU_VERSION; + if (copy_to_user(arg3, &version, sizeof(version))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } +#ifdef MODULE + u32 is_lkm = 0x1; +#else + u32 is_lkm = 0x0; +#endif + if (arg4 && copy_to_user(arg4, &is_lkm, sizeof(is_lkm))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + return 0; + } + + if (arg2 == CMD_REPORT_EVENT) { + if (!from_root) { + return 0; + } + switch (arg3) { + case EVENT_POST_FS_DATA: { + static bool post_fs_data_lock = false; + if (!post_fs_data_lock) { + post_fs_data_lock = true; + pr_info("post-fs-data triggered\n"); + on_post_fs_data(); + } + break; + } + case EVENT_BOOT_COMPLETED: { + static bool boot_complete_lock = false; + if (!boot_complete_lock) { + boot_complete_lock = true; + pr_info("boot_complete triggered\n"); + } + break; + } + case EVENT_MODULE_MOUNTED: { + ksu_module_mounted = true; + pr_info("module mounted!\n"); + break; + } + default: + break; + } + return 0; + } + + if (arg2 == CMD_SET_SEPOLICY) { + if (!from_root) { + return 0; + } + if (!handle_sepolicy(arg3, arg4)) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("sepolicy: prctl reply error\n"); + } + } + + return 0; + } + + if (arg2 == CMD_CHECK_SAFEMODE) { + if (ksu_is_safe_mode()) { + pr_warn("safemode enabled!\n"); + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("safemode: prctl reply error\n"); + } + } + return 0; + } + + if (arg2 == CMD_GET_ALLOW_LIST || arg2 == CMD_GET_DENY_LIST) { + u32 array[128]; + u32 array_length; + bool success = ksu_get_allow_list(array, &array_length, + arg2 == CMD_GET_ALLOW_LIST); + if (success) { + if (!copy_to_user(arg4, &array_length, + sizeof(array_length)) && + !copy_to_user(arg3, array, + sizeof(u32) * array_length)) { + if (copy_to_user(result, &reply_ok, + sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %lu\n", + arg2); + } + } else { + pr_err("prctl copy allowlist error\n"); + } + } + return 0; + } + + if (arg2 == CMD_UID_GRANTED_ROOT || arg2 == CMD_UID_SHOULD_UMOUNT) { + uid_t target_uid = (uid_t)arg3; + bool allow = false; + if (arg2 == CMD_UID_GRANTED_ROOT) { + allow = ksu_is_allow_uid(target_uid); + } else if (arg2 == CMD_UID_SHOULD_UMOUNT) { + allow = ksu_uid_should_umount(target_uid); + } else { + pr_err("unknown cmd: %lu\n", arg2); + } + if (!copy_to_user(arg4, &allow, sizeof(allow))) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + } else { + pr_err("prctl copy err, cmd: %lu\n", arg2); + } + return 0; + } + + // all other cmds are for 'root manager' + if (!from_manager) { + return 0; + } + + // we are already manager + if (arg2 == CMD_GET_APP_PROFILE) { + struct app_profile profile; + if (copy_from_user(&profile, arg3, sizeof(profile))) { + pr_err("copy profile failed\n"); + return 0; + } + + bool success = ksu_get_app_profile(&profile); + if (success) { + if (copy_to_user(arg3, &profile, sizeof(profile))) { + pr_err("copy profile failed\n"); + return 0; + } + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + } + return 0; + } + + if (arg2 == CMD_SET_APP_PROFILE) { + struct app_profile profile; + if (copy_from_user(&profile, arg3, sizeof(profile))) { + pr_err("copy profile failed\n"); + return 0; + } + + // todo: validate the params + if (ksu_set_app_profile(&profile, true)) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + } + return 0; + } + + return 0; +} + +static bool is_appuid(kuid_t uid) +{ +#define PER_USER_RANGE 100000 +#define FIRST_APPLICATION_UID 10000 +#define LAST_APPLICATION_UID 19999 + + uid_t appid = uid.val % PER_USER_RANGE; + return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID; +} + +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; +} + +static int ksu_umount_mnt(struct path *path, int flags) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) || defined(KSU_UMOUNT) + return path_umount(path, flags); +#else + // TODO: umount for non GKI kernel + return -ENOSYS; +#endif +} + +static void try_umount(const char *mnt, bool check_mnt, int flags) +{ + struct path path; + int err = kern_path(mnt, 0, &path); + if (err) { + return; + } + + if (path.dentry != path.mnt->mnt_root) { + // it is not root mountpoint, maybe umounted by others already. + return; + } + + // we are only interest in some specific mounts + if (check_mnt && !should_umount(&path)) { + return; + } + + err = ksu_umount_mnt(&path, flags); + if (err) { + pr_warn("umount %s failed: %d\n", mnt, err); + } +} + +int ksu_handle_setuid(struct cred *new, const struct cred *old) +{ + // this hook is used for umounting overlayfs for some uid, if there isn't any module mounted, just ignore it! + if (!ksu_module_mounted) { + return 0; + } + + if (!new || !old) { + return 0; + } + + kuid_t new_uid = new->uid; + kuid_t old_uid = old->uid; + + if (0 != old_uid.val) { + // old process is not root, ignore it. + return 0; + } + + if (!is_appuid(new_uid) || is_unsupported_uid(new_uid.val)) { + // pr_info("handle setuid ignore non application or isolated uid: %d\n", new_uid.val); + return 0; + } + + if (ksu_is_allow_uid(new_uid.val)) { + // pr_info("handle setuid ignore allowed application: %d\n", new_uid.val); + return 0; + } + + if (!ksu_uid_should_umount(new_uid.val)) { + return 0; + } else { +#ifdef CONFIG_KSU_DEBUG + pr_info("uid: %d should not umount!\n", current_uid().val); +#endif + } + + // check old process's selinux context, if it is not zygote, ignore it! + // because some su apps may setuid to untrusted_app but they are in global mount namespace + // when we umount for such process, that is a disaster! + bool is_zygote_child = is_zygote(old->security); + if (!is_zygote_child) { + pr_info("handle umount ignore non zygote child: %d\n", + current->pid); + return 0; + } +#ifdef CONFIG_KSU_DEBUG + // umount the target mnt + pr_info("handle umount for uid: %d, pid: %d\n", new_uid.val, + current->pid); +#endif + + // fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and + // filter the mountpoint whose target is `/data/adb` + try_umount("/system", true, 0); + try_umount("/vendor", true, 0); + try_umount("/product", true, 0); + try_umount("/data/adb/modules", false, MNT_DETACH); + + // try umount ksu temp path + try_umount("/debug_ramdisk", false, MNT_DETACH); + try_umount("/sbin", false, MNT_DETACH); + + return 0; +} + +// Init functons + +static int handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + int option = (int)PT_REGS_PARM1(real_regs); + unsigned long arg2 = (unsigned long)PT_REGS_PARM2(real_regs); + unsigned long arg3 = (unsigned long)PT_REGS_PARM3(real_regs); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) + // PRCTL_SYMBOL is the arch-specificed one, which receive raw pt_regs from syscall + unsigned long arg4 = (unsigned long)PT_REGS_SYSCALL_PARM4(real_regs); +#else + // PRCTL_SYMBOL is the common one, called by C convention in do_syscall_64 + // https://elixir.bootlin.com/linux/v4.15.18/source/arch/x86/entry/common.c#L287 + unsigned long arg4 = (unsigned long)PT_REGS_CCALL_PARM4(real_regs); +#endif + unsigned long arg5 = (unsigned long)PT_REGS_PARM5(real_regs); + + return ksu_handle_prctl(option, arg2, arg3, arg4, arg5); +} + +static struct kprobe prctl_kp = { + .symbol_name = PRCTL_SYMBOL, + .pre_handler = handler_pre, +}; + +static int renameat_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + // https://elixir.bootlin.com/linux/v5.12-rc1/source/include/linux/fs.h + struct renamedata *rd = PT_REGS_PARM1(regs); + struct dentry *old_entry = rd->old_dentry; + struct dentry *new_entry = rd->new_dentry; +#else + struct dentry *old_entry = (struct dentry *)PT_REGS_PARM2(regs); + struct dentry *new_entry = (struct dentry *)PT_REGS_CCALL_PARM4(regs); +#endif + + return ksu_handle_rename(old_entry, new_entry); +} + +static struct kprobe renameat_kp = { + .symbol_name = "vfs_rename", + .pre_handler = renameat_handler_pre, +}; + +__maybe_unused int ksu_kprobe_init(void) +{ + int rc = 0; + rc = register_kprobe(&prctl_kp); + + if (rc) { + pr_info("prctl kprobe failed: %d.\n", rc); + return rc; + } + + rc = register_kprobe(&renameat_kp); + pr_info("renameat kp: %d\n", rc); + + return rc; +} + +__maybe_unused int ksu_kprobe_exit(void) +{ + unregister_kprobe(&prctl_kp); + unregister_kprobe(&renameat_kp); + return 0; +} + +static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + ksu_handle_prctl(option, arg2, arg3, arg4, arg5); + return -ENOSYS; +} +// kernel 4.4 and 4.9 +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) +static int ksu_key_permission(key_ref_t key_ref, const struct cred *cred, + unsigned perm) +{ + if (init_session_keyring != NULL) { + return 0; + } + if (strcmp(current->comm, "init")) { + // we are only interested in `init` process + return 0; + } + init_session_keyring = cred->session_keyring; + pr_info("kernel_compat: got init_session_keyring\n"); + return 0; +} +#endif +static int ksu_inode_rename(struct inode *old_inode, struct dentry *old_dentry, + struct inode *new_inode, struct dentry *new_dentry) +{ + return ksu_handle_rename(old_dentry, new_dentry); +} + +static int ksu_task_fix_setuid(struct cred *new, const struct cred *old, + int flags) +{ + return ksu_handle_setuid(new, old); +} + +#ifndef MODULE +static struct security_hook_list ksu_hooks[] = { + LSM_HOOK_INIT(task_prctl, ksu_task_prctl), + LSM_HOOK_INIT(inode_rename, ksu_inode_rename), + LSM_HOOK_INIT(task_fix_setuid, ksu_task_fix_setuid), +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) + LSM_HOOK_INIT(key_permission, ksu_key_permission) +#endif +}; + +void __init ksu_lsm_hook_init(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + security_add_hooks(ksu_hooks, ARRAY_SIZE(ksu_hooks), "ksu"); +#else + // https://elixir.bootlin.com/linux/v4.10.17/source/include/linux/lsm_hooks.h#L1892 + security_add_hooks(ksu_hooks, ARRAY_SIZE(ksu_hooks)); +#endif +} + +#else +static int override_security_head(void *head, const void *new_head, size_t len) +{ + unsigned long base = (unsigned long)head & PAGE_MASK; + unsigned long offset = offset_in_page(head); + + // this is impossible for our case because the page alignment + // but be careful for other cases! + BUG_ON(offset + len > PAGE_SIZE); + struct page *page = phys_to_page(__pa(base)); + if (!page) { + return -EFAULT; + } + + void *addr = vmap(&page, 1, VM_MAP, PAGE_KERNEL); + if (!addr) { + return -ENOMEM; + } + local_irq_disable(); + memcpy(addr + offset, new_head, len); + local_irq_enable(); + vunmap(addr); + return 0; +} + +static void free_security_hook_list(struct hlist_head *head) +{ + struct hlist_node *temp; + struct security_hook_list *entry; + + if (!head) + return; + + hlist_for_each_entry_safe (entry, temp, head, list) { + hlist_del(&entry->list); + kfree(entry); + } + + kfree(head); +} + +struct hlist_head *copy_security_hlist(struct hlist_head *orig) +{ + struct hlist_head *new_head = kmalloc(sizeof(*new_head), GFP_KERNEL); + if (!new_head) + return NULL; + + INIT_HLIST_HEAD(new_head); + + struct security_hook_list *entry; + struct security_hook_list *new_entry; + + hlist_for_each_entry (entry, orig, list) { + new_entry = kmalloc(sizeof(*new_entry), GFP_KERNEL); + if (!new_entry) { + free_security_hook_list(new_head); + return NULL; + } + + *new_entry = *entry; + + hlist_add_tail_rcu(&new_entry->list, new_head); + } + + return new_head; +} + +#define LSM_SEARCH_MAX 180 // This should be enough to iterate +static void *find_head_addr(void *security_ptr, int *index) +{ + if (!security_ptr) { + return NULL; + } + struct hlist_head *head_start = + (struct hlist_head *)&security_hook_heads; + + for (int i = 0; i < LSM_SEARCH_MAX; i++) { + struct hlist_head *head = head_start + i; + struct security_hook_list *pos; + hlist_for_each_entry (pos, head, list) { + if (pos->hook.capget == security_ptr) { + if (index) { + *index = i; + } + return head; + } + } + } + + return NULL; +} + +#define GET_SYMBOL_ADDR(sym) \ + ({ \ + void *addr = kallsyms_lookup_name(#sym ".cfi_jt"); \ + if (!addr) { \ + addr = kallsyms_lookup_name(#sym); \ + } \ + addr; \ + }) + +#define KSU_LSM_HOOK_HACK_INIT(head_ptr, name, func) \ + do { \ + static struct security_hook_list hook = { \ + .hook = { .name = func } \ + }; \ + hook.head = head_ptr; \ + hook.lsm = "ksu"; \ + struct hlist_head *new_head = copy_security_hlist(hook.head); \ + if (!new_head) { \ + pr_err("Failed to copy security list: %s\n", #name); \ + break; \ + } \ + hlist_add_tail_rcu(&hook.list, new_head); \ + if (override_security_head(hook.head, new_head, \ + sizeof(*new_head))) { \ + free_security_hook_list(new_head); \ + pr_err("Failed to hack lsm for: %s\n", #name); \ + } \ + } while (0) + +void __init ksu_lsm_hook_init(void) +{ + void *cap_prctl = GET_SYMBOL_ADDR(cap_task_prctl); + void *prctl_head = find_head_addr(cap_prctl, NULL); + if (prctl_head) { + if (prctl_head != &security_hook_heads.task_prctl) { + pr_warn("prctl's address has shifted!\n"); + } + KSU_LSM_HOOK_HACK_INIT(prctl_head, task_prctl, ksu_task_prctl); + } else { + pr_warn("Failed to find task_prctl!\n"); + } + + int inode_killpriv_index = -1; + void *cap_killpriv = GET_SYMBOL_ADDR(cap_inode_killpriv); + find_head_addr(cap_killpriv, &inode_killpriv_index); + if (inode_killpriv_index < 0) { + pr_warn("Failed to find inode_rename, use kprobe instead!\n"); + register_kprobe(&renameat_kp); + } else { + int inode_rename_index = inode_killpriv_index + + &security_hook_heads.inode_rename - + &security_hook_heads.inode_killpriv; + struct hlist_head *head_start = + (struct hlist_head *)&security_hook_heads; + void *inode_rename_head = head_start + inode_rename_index; + if (inode_rename_head != &security_hook_heads.inode_rename) { + pr_warn("inode_rename's address has shifted!\n"); + } + KSU_LSM_HOOK_HACK_INIT(inode_rename_head, inode_rename, + ksu_inode_rename); + } + void *cap_setuid = GET_SYMBOL_ADDR(cap_task_fix_setuid); + void *setuid_head = find_head_addr(cap_setuid, NULL); + if (setuid_head) { + if (setuid_head != &security_hook_heads.task_fix_setuid) { + pr_warn("setuid's address has shifted!\n"); + } + KSU_LSM_HOOK_HACK_INIT(setuid_head, task_fix_setuid, + ksu_task_fix_setuid); + } else { + pr_warn("Failed to find task_fix_setuid!\n"); + } + smp_mb(); +} +#endif + +void __init ksu_core_init(void) +{ + ksu_lsm_hook_init(); +} + +void ksu_core_exit(void) +{ +#ifdef CONFIG_KPROBES + pr_info("ksu_core_kprobe_exit\n"); + // we dont use this now + // ksu_kprobe_exit(); +#endif +} diff --git a/KernelSU/kernel/core_hook.h b/KernelSU/kernel/core_hook.h new file mode 100644 index 000000000000..616951e8db35 --- /dev/null +++ b/KernelSU/kernel/core_hook.h @@ -0,0 +1,9 @@ +#ifndef __KSU_H_KSU_CORE +#define __KSU_H_KSU_CORE + +#include + +void __init ksu_core_init(void); +void ksu_core_exit(void); + +#endif diff --git a/KernelSU/kernel/embed_ksud.c b/KernelSU/kernel/embed_ksud.c new file mode 100644 index 000000000000..24c401219c15 --- /dev/null +++ b/KernelSU/kernel/embed_ksud.c @@ -0,0 +1,5 @@ +// WARNING: THIS IS A STUB FILE +// This file will be regenerated by CI + +unsigned int ksud_size = 0; +const char ksud[0] = {}; diff --git a/KernelSU/kernel/export_symbol.txt b/KernelSU/kernel/export_symbol.txt new file mode 100644 index 000000000000..1abd805e8e6a --- /dev/null +++ b/KernelSU/kernel/export_symbol.txt @@ -0,0 +1,2 @@ +register_kprobe +unregister_kprobe diff --git a/KernelSU/kernel/include/ksu_hook.h b/KernelSU/kernel/include/ksu_hook.h new file mode 100644 index 000000000000..ea0b04d3e538 --- /dev/null +++ b/KernelSU/kernel/include/ksu_hook.h @@ -0,0 +1,28 @@ +#ifndef __KSU_H_KSHOOK +#define __KSU_H_KSHOOK + +#include +#include + +// For sucompat + +int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, + int *flags); + +int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags); + +// For ksud + +int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, + size_t *count_ptr, loff_t **pos); + +// For ksud and sucompat + +int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, + void *envp, int *flags); + +// For volume button +int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, + int *value); + +#endif diff --git a/KernelSU/kernel/kernel_compat.c b/KernelSU/kernel/kernel_compat.c new file mode 100644 index 000000000000..a250cc2fa5c8 --- /dev/null +++ b/KernelSU/kernel/kernel_compat.c @@ -0,0 +1,176 @@ +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#include +#else +#include +#endif +#include +#include "klog.h" // IWYU pragma: keep +#include "kernel_compat.h" // Add check Huawei Device + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) +#include +#include +#include +struct key *init_session_keyring = NULL; + +static inline int install_session_keyring(struct key *keyring) +{ + struct cred *new; + int ret; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + ret = install_session_keyring_to_cred(new, keyring); + if (ret < 0) { + abort_creds(new); + return ret; + } + + return commit_creds(new); +} +#endif + +extern struct task_struct init_task; + +// mnt_ns context switch for environment that android_init->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns, such as WSA +struct ksu_ns_fs_saved { + struct nsproxy *ns; + struct fs_struct *fs; +}; + +static void ksu_save_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved) +{ + ns_fs_saved->ns = current->nsproxy; + ns_fs_saved->fs = current->fs; +} + +static void ksu_load_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved) +{ + current->nsproxy = ns_fs_saved->ns; + current->fs = ns_fs_saved->fs; +} + +static bool android_context_saved_checked = false; +static bool android_context_saved_enabled = false; +static struct ksu_ns_fs_saved android_context_saved; + +void ksu_android_ns_fs_check() +{ + if (android_context_saved_checked) + return; + android_context_saved_checked = true; + task_lock(current); + if (current->nsproxy && current->fs && + current->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns) { + android_context_saved_enabled = true; + pr_info("android context saved enabled due to init mnt_ns(%p) != android mnt_ns(%p)\n", + current->nsproxy->mnt_ns, init_task.nsproxy->mnt_ns); + ksu_save_ns_fs(&android_context_saved); + } else { + pr_info("android context saved disabled\n"); + } + task_unlock(current); +} + +struct file *ksu_filp_open_compat(const char *filename, int flags, umode_t mode) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) + if (init_session_keyring != NULL && !current_cred()->session_keyring && + (current->flags & PF_WQ_WORKER)) { + pr_info("installing init session keyring for older kernel\n"); + install_session_keyring(init_session_keyring); + } +#endif + // switch mnt_ns even if current is not wq_worker, to ensure what we open is the correct file in android mnt_ns, rather than user created mnt_ns + struct ksu_ns_fs_saved saved; + if (android_context_saved_enabled) { + pr_info("start switch current nsproxy and fs to android context\n"); + task_lock(current); + ksu_save_ns_fs(&saved); + ksu_load_ns_fs(&android_context_saved); + task_unlock(current); + } + struct file *fp = filp_open(filename, flags, mode); + if (android_context_saved_enabled) { + task_lock(current); + ksu_load_ns_fs(&saved); + task_unlock(current); + pr_info("switch current nsproxy and fs back to saved successfully\n"); + } + return fp; +} + +ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, + loff_t *pos) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + return kernel_read(p, buf, count, pos); +#else + loff_t offset = pos ? *pos : 0; + ssize_t result = kernel_read(p, offset, (char *)buf, count); + if (pos && result > 0) { + *pos = offset + result; + } + return result; +#endif +} + +ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count, + loff_t *pos) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + return kernel_write(p, buf, count, pos); +#else + loff_t offset = pos ? *pos : 0; + ssize_t result = kernel_write(p, buf, count, offset); + if (pos && result > 0) { + *pos = offset + result; + } + return result; +#endif +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) +long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr, + long count) +{ + return strncpy_from_user_nofault(dst, unsafe_addr, count); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0) +long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr, + long count) +{ + return strncpy_from_user_nofault(dst, unsafe_addr, count); +} +#else +// Copied from: https://elixir.bootlin.com/linux/v4.9.337/source/mm/maccess.c#L201 +long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr, + long count) +{ + mm_segment_t old_fs = get_fs(); + long ret; + + if (unlikely(count <= 0)) + return 0; + + set_fs(USER_DS); + pagefault_disable(); + ret = strncpy_from_user(dst, unsafe_addr, count); + pagefault_enable(); + set_fs(old_fs); + + if (ret >= count) { + ret = count; + dst[ret - 1] = '\0'; + } else if (ret > 0) { + ret++; + } + + return ret; +} +#endif diff --git a/KernelSU/kernel/kernel_compat.h b/KernelSU/kernel/kernel_compat.h new file mode 100644 index 000000000000..ba9981857fd1 --- /dev/null +++ b/KernelSU/kernel/kernel_compat.h @@ -0,0 +1,39 @@ +#ifndef __KSU_H_KERNEL_COMPAT +#define __KSU_H_KERNEL_COMPAT + +#include +#include +#include "ss/policydb.h" +#include "linux/key.h" + +/* + * Adapt to Huawei HISI kernel without affecting other kernels , + * Huawei Hisi Kernel EBITMAP Enable or Disable Flag , + * From ss/ebitmap.h + */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) && \ + (LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) || \ + (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)) && \ + (LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)) +#ifdef HISI_SELINUX_EBITMAP_RO +#define CONFIG_IS_HW_HISI +#endif +#endif + +extern long ksu_strncpy_from_user_nofault(char *dst, + const void __user *unsafe_addr, + long count); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) +extern struct key *init_session_keyring; +#endif + +extern void ksu_android_ns_fs_check(); +extern struct file *ksu_filp_open_compat(const char *filename, int flags, + umode_t mode); +extern ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, + loff_t *pos); +extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, + size_t count, loff_t *pos); + +#endif diff --git a/KernelSU/kernel/klog.h b/KernelSU/kernel/klog.h new file mode 100644 index 000000000000..a934027fbeeb --- /dev/null +++ b/KernelSU/kernel/klog.h @@ -0,0 +1,11 @@ +#ifndef __KSU_H_KLOG +#define __KSU_H_KLOG + +#include + +#ifdef pr_fmt +#undef pr_fmt +#define pr_fmt(fmt) "KernelSU: " fmt +#endif + +#endif diff --git a/KernelSU/kernel/ksu.c b/KernelSU/kernel/ksu.c new file mode 100644 index 000000000000..3639edc21503 --- /dev/null +++ b/KernelSU/kernel/ksu.c @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include + +#include "allowlist.h" +#include "arch.h" +#include "core_hook.h" +#include "klog.h" // IWYU pragma: keep +#include "ksu.h" +#include "throne_tracker.h" + +static struct workqueue_struct *ksu_workqueue; + +bool ksu_queue_work(struct work_struct *work) +{ + return queue_work(ksu_workqueue, work); +} + +extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, + void *argv, void *envp, int *flags); + +extern int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr, + void *argv, void *envp, int *flags); + +int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, + void *envp, int *flags) +{ + ksu_handle_execveat_ksud(fd, filename_ptr, argv, envp, flags); + return ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp, + flags); +} + +extern void ksu_sucompat_init(); +extern void ksu_sucompat_exit(); +extern void ksu_ksud_init(); +extern void ksu_ksud_exit(); + +int __init kernelsu_init(void) +{ +#ifdef CONFIG_KSU_DEBUG + pr_alert("*************************************************************"); + pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); + pr_alert("** **"); + pr_alert("** You are running KernelSU in DEBUG mode **"); + pr_alert("** **"); + pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); + pr_alert("*************************************************************"); +#endif + + ksu_core_init(); + + ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0); + + ksu_allowlist_init(); + + ksu_throne_tracker_init(); + +#ifdef CONFIG_KPROBES + ksu_sucompat_init(); + ksu_ksud_init(); +#else + pr_alert("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html"); +#endif + +#ifdef MODULE +#ifndef CONFIG_KSU_DEBUG + kobject_del(&THIS_MODULE->mkobj.kobj); +#endif +#endif + return 0; +} + +void kernelsu_exit(void) +{ + ksu_allowlist_exit(); + + ksu_throne_tracker_exit(); + + destroy_workqueue(ksu_workqueue); + +#ifdef CONFIG_KPROBES + ksu_ksud_exit(); + ksu_sucompat_exit(); +#endif + + ksu_core_exit(); +} + +module_init(kernelsu_init); +module_exit(kernelsu_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("weishu"); +MODULE_DESCRIPTION("Android KernelSU"); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) +MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); +#endif diff --git a/KernelSU/kernel/ksu.h b/KernelSU/kernel/ksu.h new file mode 100644 index 000000000000..35d1b14dbb7c --- /dev/null +++ b/KernelSU/kernel/ksu.h @@ -0,0 +1,99 @@ +#ifndef __KSU_H_KSU +#define __KSU_H_KSU + +#include +#include + +#define KERNEL_SU_VERSION KSU_VERSION +#define KERNEL_SU_OPTION 0xDEADBEEF + +#define CMD_GRANT_ROOT 0 +#define CMD_BECOME_MANAGER 1 +#define CMD_GET_VERSION 2 +#define CMD_ALLOW_SU 3 +#define CMD_DENY_SU 4 +#define CMD_GET_ALLOW_LIST 5 +#define CMD_GET_DENY_LIST 6 +#define CMD_REPORT_EVENT 7 +#define CMD_SET_SEPOLICY 8 +#define CMD_CHECK_SAFEMODE 9 +#define CMD_GET_APP_PROFILE 10 +#define CMD_SET_APP_PROFILE 11 +#define CMD_UID_GRANTED_ROOT 12 +#define CMD_UID_SHOULD_UMOUNT 13 + +#define EVENT_POST_FS_DATA 1 +#define EVENT_BOOT_COMPLETED 2 +#define EVENT_MODULE_MOUNTED 3 + +#define KSU_APP_PROFILE_VER 2 +#define KSU_MAX_PACKAGE_NAME 256 +// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups. +#define KSU_MAX_GROUPS 32 +#define KSU_SELINUX_DOMAIN 64 + +struct root_profile { + int32_t uid; + int32_t gid; + + int32_t groups_count; + int32_t groups[KSU_MAX_GROUPS]; + + // kernel_cap_t is u32[2] for capabilities v3 + struct { + u64 effective; + u64 permitted; + u64 inheritable; + } capabilities; + + char selinux_domain[KSU_SELINUX_DOMAIN]; + + int32_t namespaces; +}; + +struct non_root_profile { + bool umount_modules; +}; + +struct app_profile { + // It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this. + u32 version; + + // this is usually the package of the app, but can be other value for special apps + char key[KSU_MAX_PACKAGE_NAME]; + int32_t current_uid; + bool allow_su; + + union { + struct { + bool use_default; + char template_name[KSU_MAX_PACKAGE_NAME]; + + struct root_profile profile; + } rp_config; + + struct { + bool use_default; + + struct non_root_profile profile; + } nrp_config; + }; +}; + +bool ksu_queue_work(struct work_struct *work); + +static inline int startswith(char *s, char *prefix) +{ + return strncmp(s, prefix, strlen(prefix)); +} + +static inline int endswith(const char *s, const char *t) +{ + size_t slen = strlen(s); + size_t tlen = strlen(t); + if (tlen > slen) + return 1; + return strcmp(s + slen - tlen, t); +} + +#endif diff --git a/KernelSU/kernel/ksud.c b/KernelSU/kernel/ksud.c new file mode 100644 index 000000000000..68e473524284 --- /dev/null +++ b/KernelSU/kernel/ksud.c @@ -0,0 +1,666 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) +#include +#else +#include +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) +#include +#endif +#include +#include +#include +#include +#include + +#include "allowlist.h" +#include "arch.h" +#include "klog.h" // IWYU pragma: keep +#include "ksud.h" +#include "kernel_compat.h" +#include "selinux/selinux.h" + +static const char KERNEL_SU_RC[] = + "\n" + + "on post-fs-data\n" + " start logd\n" + // We should wait for the post-fs-data finish + " exec u:r:su:s0 root -- " KSUD_PATH " post-fs-data\n" + "\n" + + "on nonencrypted\n" + " exec u:r:su:s0 root -- " KSUD_PATH " services\n" + "\n" + + "on property:vold.decrypt=trigger_restart_framework\n" + " exec u:r:su:s0 root -- " KSUD_PATH " services\n" + "\n" + + "on property:sys.boot_completed=1\n" + " exec u:r:su:s0 root -- " KSUD_PATH " boot-completed\n" + "\n" + + "\n"; + +static void stop_vfs_read_hook(); +static void stop_execve_hook(); +static void stop_input_hook(); + +#ifdef CONFIG_KPROBES +static struct work_struct stop_vfs_read_work; +static struct work_struct stop_execve_hook_work; +static struct work_struct stop_input_hook_work; +#else +bool ksu_vfs_read_hook __read_mostly = true; +bool ksu_execveat_hook __read_mostly = true; +bool ksu_input_hook __read_mostly = true; +#endif + +u32 ksu_devpts_sid; + +void on_post_fs_data(void) +{ + static bool done = false; + if (done) { + pr_info("on_post_fs_data already done\n"); + return; + } + done = true; + pr_info("on_post_fs_data!\n"); + ksu_load_allow_list(); + // sanity check, this may influence the performance + stop_input_hook(); + + ksu_devpts_sid = ksu_get_devpts_sid(); + pr_info("devpts sid: %d\n", ksu_devpts_sid); +} + +#define MAX_ARG_STRINGS 0x7FFFFFFF +struct user_arg_ptr { +#ifdef CONFIG_COMPAT + bool is_compat; +#endif + union { + const char __user *const __user *native; +#ifdef CONFIG_COMPAT + const compat_uptr_t __user *compat; +#endif + } ptr; +}; + +static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr) +{ + const char __user *native; + +#ifdef CONFIG_COMPAT + if (unlikely(argv.is_compat)) { + compat_uptr_t compat; + + if (get_user(compat, argv.ptr.compat + nr)) + return ERR_PTR(-EFAULT); + + return compat_ptr(compat); + } +#endif + + if (get_user(native, argv.ptr.native + nr)) + return ERR_PTR(-EFAULT); + + return native; +} + +/* + * count() counts the number of strings in array ARGV. + */ + +/* + * Make sure old GCC compiler can use __maybe_unused, + * Test passed in 4.4.x ~ 4.9.x when use GCC. + */ + +static int __maybe_unused count(struct user_arg_ptr argv, int max) +{ + int i = 0; + + if (argv.ptr.native != NULL) { + for (;;) { + const char __user *p = get_user_arg_ptr(argv, i); + + if (!p) + break; + + if (IS_ERR(p)) + return -EFAULT; + + if (i >= max) + return -E2BIG; + ++i; + + if (fatal_signal_pending(current)) + return -ERESTARTNOHAND; + cond_resched(); + } + } + return i; +} + +// IMPORTANT NOTE: the call from execve_handler_pre WON'T provided correct value for envp and flags in GKI version +int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr, + struct user_arg_ptr *argv, + struct user_arg_ptr *envp, int *flags) +{ +#ifndef CONFIG_KPROBES + if (!ksu_execveat_hook) { + return 0; + } +#endif + struct filename *filename; + + static const char app_process[] = "/system/bin/app_process"; + static bool first_app_process = true; + + /* This applies to versions Android 10+ */ + static const char system_bin_init[] = "/system/bin/init"; + /* This applies to versions between Android 6 ~ 9 */ + static const char old_system_init[] = "/init"; + static bool init_second_stage_executed = false; + + if (!filename_ptr) + return 0; + + filename = *filename_ptr; + if (IS_ERR(filename)) { + return 0; + } + + if (unlikely(!memcmp(filename->name, system_bin_init, + sizeof(system_bin_init) - 1) && + argv)) { + // /system/bin/init executed + int argc = count(*argv, MAX_ARG_STRINGS); + pr_info("/system/bin/init argc: %d\n", argc); + if (argc > 1 && !init_second_stage_executed) { + const char __user *p = get_user_arg_ptr(*argv, 1); + if (p && !IS_ERR(p)) { + char first_arg[16]; + ksu_strncpy_from_user_nofault( + first_arg, p, sizeof(first_arg)); + pr_info("/system/bin/init first arg: %s\n", + first_arg); + if (!strcmp(first_arg, "second_stage")) { + pr_info("/system/bin/init second_stage executed\n"); + apply_kernelsu_rules(); + init_second_stage_executed = true; + ksu_android_ns_fs_check(); + } + } else { + pr_err("/system/bin/init parse args err!\n"); + } + } + } else if (unlikely(!memcmp(filename->name, old_system_init, + sizeof(old_system_init) - 1) && + argv)) { + // /init executed + int argc = count(*argv, MAX_ARG_STRINGS); + pr_info("/init argc: %d\n", argc); + if (argc > 1 && !init_second_stage_executed) { + /* This applies to versions between Android 6 ~ 7 */ + const char __user *p = get_user_arg_ptr(*argv, 1); + if (p && !IS_ERR(p)) { + char first_arg[16]; + ksu_strncpy_from_user_nofault( + first_arg, p, sizeof(first_arg)); + pr_info("/init first arg: %s\n", first_arg); + if (!strcmp(first_arg, "--second-stage")) { + pr_info("/init second_stage executed\n"); + apply_kernelsu_rules(); + init_second_stage_executed = true; + ksu_android_ns_fs_check(); + } + } else { + pr_err("/init parse args err!\n"); + } + } else if (argc == 1 && !init_second_stage_executed && envp) { + /* This applies to versions between Android 8 ~ 9 */ + int envc = count(*envp, MAX_ARG_STRINGS); + if (envc > 0) { + int n; + for (n = 1; n <= envc; n++) { + const char __user *p = + get_user_arg_ptr(*envp, n); + if (!p || IS_ERR(p)) { + continue; + } + char env[256]; + // Reading environment variable strings from user space + if (ksu_strncpy_from_user_nofault( + env, p, sizeof(env)) < 0) + continue; + // Parsing environment variable names and values + char *env_name = env; + char *env_value = strchr(env, '='); + if (env_value == NULL) + continue; + // Replace equal sign with string terminator + *env_value = '\0'; + env_value++; + // Check if the environment variable name and value are matching + if (!strcmp(env_name, + "INIT_SECOND_STAGE") && + (!strcmp(env_value, "1") || + !strcmp(env_value, "true"))) { + pr_info("/init second_stage executed\n"); + apply_kernelsu_rules(); + init_second_stage_executed = + true; + ksu_android_ns_fs_check(); + } + } + } + } + } + + if (unlikely(first_app_process && !memcmp(filename->name, app_process, + sizeof(app_process) - 1))) { + first_app_process = false; + pr_info("exec app_process, /data prepared, second_stage: %d\n", + init_second_stage_executed); + on_post_fs_data(); // we keep this for old ksud + stop_execve_hook(); + } + + return 0; +} + +static ssize_t (*orig_read)(struct file *, char __user *, size_t, loff_t *); +static ssize_t (*orig_read_iter)(struct kiocb *, struct iov_iter *); +static struct file_operations fops_proxy; +static ssize_t read_count_append = 0; + +static ssize_t read_proxy(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + bool first_read = file->f_pos == 0; + ssize_t ret = orig_read(file, buf, count, pos); + if (first_read) { + pr_info("read_proxy append %ld + %ld\n", ret, + read_count_append); + ret += read_count_append; + } + return ret; +} + +static ssize_t read_iter_proxy(struct kiocb *iocb, struct iov_iter *to) +{ + bool first_read = iocb->ki_pos == 0; + ssize_t ret = orig_read_iter(iocb, to); + if (first_read) { + pr_info("read_iter_proxy append %ld + %ld\n", ret, + read_count_append); + ret += read_count_append; + } + return ret; +} + +int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, + size_t *count_ptr, loff_t **pos) +{ +#ifndef CONFIG_KPROBES + if (!ksu_vfs_read_hook) { + return 0; + } +#endif + struct file *file; + char __user *buf; + size_t count; + + if (strcmp(current->comm, "init")) { + // we are only interest in `init` process + return 0; + } + + file = *file_ptr; + if (IS_ERR(file)) { + return 0; + } + + if (!d_is_reg(file->f_path.dentry)) { + return 0; + } + + const char *short_name = file->f_path.dentry->d_name.name; + if (strcmp(short_name, "atrace.rc")) { + // we are only interest `atrace.rc` file name file + return 0; + } + char path[256]; + char *dpath = d_path(&file->f_path, path, sizeof(path)); + + if (IS_ERR(dpath)) { + return 0; + } + + if (strcmp(dpath, "/system/etc/init/atrace.rc")) { + return 0; + } + + // we only process the first read + static bool rc_inserted = false; + if (rc_inserted) { + // we don't need this kprobe, unregister it! + stop_vfs_read_hook(); + return 0; + } + rc_inserted = true; + + // now we can sure that the init process is reading + // `/system/etc/init/atrace.rc` + buf = *buf_ptr; + count = *count_ptr; + + size_t rc_count = strlen(KERNEL_SU_RC); + + pr_info("vfs_read: %s, comm: %s, count: %zu, rc_count: %zu\n", dpath, + current->comm, count, rc_count); + + if (count < rc_count) { + pr_err("count: %zu < rc_count: %zu\n", count, rc_count); + return 0; + } + + size_t ret = copy_to_user(buf, KERNEL_SU_RC, rc_count); + if (ret) { + pr_err("copy ksud.rc failed: %zu\n", ret); + return 0; + } + + // we've succeed to insert ksud.rc, now we need to proxy the read and modify the result! + // But, we can not modify the file_operations directly, because it's in read-only memory. + // We just replace the whole file_operations with a proxy one. + memcpy(&fops_proxy, file->f_op, sizeof(struct file_operations)); + orig_read = file->f_op->read; + if (orig_read) { + fops_proxy.read = read_proxy; + } + orig_read_iter = file->f_op->read_iter; + if (orig_read_iter) { + fops_proxy.read_iter = read_iter_proxy; + } + // replace the file_operations + file->f_op = &fops_proxy; + read_count_append = rc_count; + + *buf_ptr = buf + rc_count; + *count_ptr = count - rc_count; + + return 0; +} + +int ksu_handle_sys_read(unsigned int fd, char __user **buf_ptr, + size_t *count_ptr) +{ + struct file *file = fget(fd); + if (!file) { + return 0; + } + int result = ksu_handle_vfs_read(&file, buf_ptr, count_ptr, NULL); + fput(file); + return result; +} + +static unsigned int volumedown_pressed_count = 0; + +static bool is_volumedown_enough(unsigned int count) +{ + return count >= 3; +} + +int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, + int *value) +{ +#ifndef CONFIG_KPROBES + if (!ksu_input_hook) { + return 0; + } +#endif + if (*type == EV_KEY && *code == KEY_VOLUMEDOWN) { + int val = *value; + pr_info("KEY_VOLUMEDOWN val: %d\n", val); + if (val) { + // key pressed, count it + volumedown_pressed_count += 1; + if (is_volumedown_enough(volumedown_pressed_count)) { + stop_input_hook(); + } + } + } + + return 0; +} + +bool ksu_is_safe_mode() +{ + static bool safe_mode = false; + if (safe_mode) { + // don't need to check again, userspace may call multiple times + return true; + } + + // stop hook first! + stop_input_hook(); + + pr_info("volumedown_pressed_count: %d\n", volumedown_pressed_count); + if (is_volumedown_enough(volumedown_pressed_count)) { + // pressed over 3 times + pr_info("KEY_VOLUMEDOWN pressed max times, safe mode detected!\n"); + safe_mode = true; + return true; + } + + return false; +} + +#ifdef CONFIG_KPROBES + +// https://elixir.bootlin.com/linux/v5.10.158/source/fs/exec.c#L1864 +static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + int *fd = (int *)&PT_REGS_PARM1(regs); + struct filename **filename_ptr = + (struct filename **)&PT_REGS_PARM2(regs); + struct user_arg_ptr argv; +#ifdef CONFIG_COMPAT + argv.is_compat = PT_REGS_PARM3(regs); + if (unlikely(argv.is_compat)) { + argv.ptr.compat = PT_REGS_CCALL_PARM4(regs); + } else { + argv.ptr.native = PT_REGS_CCALL_PARM4(regs); + } +#else + argv.ptr.native = PT_REGS_PARM3(regs); +#endif + + return ksu_handle_execveat_ksud(fd, filename_ptr, &argv, NULL, NULL); +} + +static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + const char __user **filename_user = + (const char **)&PT_REGS_PARM1(real_regs); + const char __user *const __user *__argv = + (const char __user *const __user *)PT_REGS_PARM2(real_regs); + struct user_arg_ptr argv = { .ptr.native = __argv }; + struct filename filename_in, *filename_p; + char path[32]; + + if (!filename_user) + return 0; + + memset(path, 0, sizeof(path)); + ksu_strncpy_from_user_nofault(path, *filename_user, 32); + filename_in.name = path; + + filename_p = &filename_in; + return ksu_handle_execveat_ksud(AT_FDCWD, &filename_p, &argv, NULL, + NULL); +} + +// remove this later! +__maybe_unused static int vfs_read_handler_pre(struct kprobe *p, + struct pt_regs *regs) +{ + struct file **file_ptr = (struct file **)&PT_REGS_PARM1(regs); + char __user **buf_ptr = (char **)&PT_REGS_PARM2(regs); + size_t *count_ptr = (size_t *)&PT_REGS_PARM3(regs); + loff_t **pos_ptr = (loff_t **)&PT_REGS_CCALL_PARM4(regs); + + return ksu_handle_vfs_read(file_ptr, buf_ptr, count_ptr, pos_ptr); +} + +static int sys_read_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + unsigned int fd = PT_REGS_PARM1(real_regs); + char __user **buf_ptr = (char __user **)&PT_REGS_PARM2(real_regs); + size_t count_ptr = (size_t *)&PT_REGS_PARM3(real_regs); + + return ksu_handle_sys_read(fd, buf_ptr, count_ptr); +} + +static int input_handle_event_handler_pre(struct kprobe *p, + struct pt_regs *regs) +{ + unsigned int *type = (unsigned int *)&PT_REGS_PARM2(regs); + unsigned int *code = (unsigned int *)&PT_REGS_PARM3(regs); + int *value = (int *)&PT_REGS_CCALL_PARM4(regs); + return ksu_handle_input_handle_event(type, code, value); +} + +#if 1 +static struct kprobe execve_kp = { + .symbol_name = SYS_EXECVE_SYMBOL, + .pre_handler = sys_execve_handler_pre, +}; +#else +static struct kprobe execve_kp = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + .symbol_name = "do_execveat_common", +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + .symbol_name = "__do_execve_file", +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) + .symbol_name = "do_execveat_common", +#endif + .pre_handler = execve_handler_pre, +}; +#endif + +#if 1 +static struct kprobe vfs_read_kp = { + .symbol_name = SYS_READ_SYMBOL, + .pre_handler = sys_read_handler_pre, +}; +#else +static struct kprobe vfs_read_kp = { + .symbol_name = "vfs_read", + .pre_handler = vfs_read_handler_pre, +}; +#endif + +static struct kprobe input_event_kp = { + .symbol_name = "input_event", + .pre_handler = input_handle_event_handler_pre, +}; + +static void do_stop_vfs_read_hook(struct work_struct *work) +{ + unregister_kprobe(&vfs_read_kp); +} + +static void do_stop_execve_hook(struct work_struct *work) +{ + unregister_kprobe(&execve_kp); +} + +static void do_stop_input_hook(struct work_struct *work) +{ + unregister_kprobe(&input_event_kp); +} +#endif + +static void stop_vfs_read_hook() +{ +#ifdef CONFIG_KPROBES + bool ret = schedule_work(&stop_vfs_read_work); + pr_info("unregister vfs_read kprobe: %d!\n", ret); +#else + ksu_vfs_read_hook = false; + pr_info("stop vfs_read_hook\n"); +#endif +} + +static void stop_execve_hook() +{ +#ifdef CONFIG_KPROBES + bool ret = schedule_work(&stop_execve_hook_work); + pr_info("unregister execve kprobe: %d!\n", ret); +#else + ksu_execveat_hook = false; + pr_info("stop execve_hook\n"); +#endif +} + +static void stop_input_hook() +{ + static bool input_hook_stopped = false; + if (input_hook_stopped) { + return; + } + input_hook_stopped = true; +#ifdef CONFIG_KPROBES + bool ret = schedule_work(&stop_input_hook_work); + pr_info("unregister input kprobe: %d!\n", ret); +#else + ksu_input_hook = false; + pr_info("stop input_hook\n"); +#endif +} + +// ksud: module support +void ksu_ksud_init() +{ +#ifdef CONFIG_KPROBES + int ret; + + ret = register_kprobe(&execve_kp); + pr_info("ksud: execve_kp: %d\n", ret); + + ret = register_kprobe(&vfs_read_kp); + pr_info("ksud: vfs_read_kp: %d\n", ret); + + ret = register_kprobe(&input_event_kp); + pr_info("ksud: input_event_kp: %d\n", ret); + + INIT_WORK(&stop_vfs_read_work, do_stop_vfs_read_hook); + INIT_WORK(&stop_execve_hook_work, do_stop_execve_hook); + INIT_WORK(&stop_input_hook_work, do_stop_input_hook); +#endif +} + +void ksu_ksud_exit() +{ +#ifdef CONFIG_KPROBES + unregister_kprobe(&execve_kp); + // this should be done before unregister vfs_read_kp + // unregister_kprobe(&vfs_read_kp); + unregister_kprobe(&input_event_kp); +#endif +} \ No newline at end of file diff --git a/KernelSU/kernel/ksud.h b/KernelSU/kernel/ksud.h new file mode 100644 index 000000000000..cc2df243a8f0 --- /dev/null +++ b/KernelSU/kernel/ksud.h @@ -0,0 +1,14 @@ +#ifndef __KSU_H_KSUD +#define __KSU_H_KSUD + +#include + +#define KSUD_PATH "/data/adb/ksud" + +void on_post_fs_data(void); + +bool ksu_is_safe_mode(void); + +extern u32 ksu_devpts_sid; + +#endif diff --git a/KernelSU/kernel/manager.h b/KernelSU/kernel/manager.h new file mode 100644 index 000000000000..be5bbced6f73 --- /dev/null +++ b/KernelSU/kernel/manager.h @@ -0,0 +1,36 @@ +#ifndef __KSU_H_KSU_MANAGER +#define __KSU_H_KSU_MANAGER + +#include +#include + +#define KSU_INVALID_UID -1 + +extern uid_t ksu_manager_uid; // DO NOT DIRECT USE + +static inline bool ksu_is_manager_uid_valid() +{ + return ksu_manager_uid != KSU_INVALID_UID; +} + +static inline bool is_manager() +{ + return unlikely(ksu_manager_uid == current_uid().val); +} + +static inline uid_t ksu_get_manager_uid() +{ + return ksu_manager_uid; +} + +static inline void ksu_set_manager_uid(uid_t uid) +{ + ksu_manager_uid = uid; +} + +static inline void ksu_invalidate_manager_uid() +{ + ksu_manager_uid = KSU_INVALID_UID; +} + +#endif diff --git a/KernelSU/kernel/selinux/Makefile b/KernelSU/kernel/selinux/Makefile new file mode 100644 index 000000000000..870750be2ed6 --- /dev/null +++ b/KernelSU/kernel/selinux/Makefile @@ -0,0 +1,16 @@ +obj-y += selinux.o +obj-y += sepolicy.o +obj-y += rules.o + +ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0) +ccflags-y += -DKSU_COMPAT_HAS_CURRENT_SID +endif + +ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/include/security.h; echo $$?),0) +ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE +endif + +ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion +ccflags-y += -Wno-declaration-after-statement -Wno-unused-function +ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include +ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h diff --git a/KernelSU/kernel/selinux/rules.c b/KernelSU/kernel/selinux/rules.c new file mode 100644 index 000000000000..1ba6d853f2a9 --- /dev/null +++ b/KernelSU/kernel/selinux/rules.c @@ -0,0 +1,476 @@ +#include +#include +#include + +#include "../klog.h" // IWYU pragma: keep +#include "selinux.h" +#include "sepolicy.h" +#include "ss/services.h" +#include "linux/lsm_audit.h" +#include "xfrm.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) +#define SELINUX_POLICY_INSTEAD_SELINUX_SS +#endif + +#define KERNEL_SU_DOMAIN "su" +#define KERNEL_SU_FILE "ksu_file" +#define KERNEL_EXEC_TYPE "ksu_exec" +#define ALL NULL + +static struct policydb *get_policydb(void) +{ + struct policydb *db; +// selinux_state does not exists before 4.19 +#ifdef KSU_COMPAT_USE_SELINUX_STATE +#ifdef SELINUX_POLICY_INSTEAD_SELINUX_SS + struct selinux_policy *policy = rcu_dereference(selinux_state.policy); + db = &policy->policydb; +#else + struct selinux_ss *ss = rcu_dereference(selinux_state.ss); + db = &ss->policydb; +#endif +#else + db = &policydb; +#endif + return db; +} + +void apply_kernelsu_rules() +{ + if (!getenforce()) { + pr_info("SELinux permissive or disabled, apply rules!\n"); + } + + rcu_read_lock(); + struct policydb *db = get_policydb(); + + ksu_permissive(db, KERNEL_SU_DOMAIN); + ksu_typeattribute(db, KERNEL_SU_DOMAIN, "mlstrustedsubject"); + ksu_typeattribute(db, KERNEL_SU_DOMAIN, "netdomain"); + ksu_typeattribute(db, KERNEL_SU_DOMAIN, "bluetoothdomain"); + + // Create unconstrained file type + ksu_type(db, KERNEL_SU_FILE, "file_type"); + ksu_typeattribute(db, KERNEL_SU_FILE, "mlstrustedobject"); + ksu_allow(db, ALL, KERNEL_SU_FILE, ALL, ALL); + + // allow all! + ksu_allow(db, KERNEL_SU_DOMAIN, ALL, ALL, ALL); + + // allow us do any ioctl + if (db->policyvers >= POLICYDB_VERSION_XPERMS_IOCTL) { + ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "blk_file", ALL); + ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "fifo_file", ALL); + ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "chr_file", ALL); + ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "file", ALL); + } + + // we need to save allowlist in /data/adb/ksu + ksu_allow(db, "kernel", "adb_data_file", "dir", ALL); + ksu_allow(db, "kernel", "adb_data_file", "file", ALL); + // we need to search /data/app + ksu_allow(db, "kernel", "apk_data_file", "file", "open"); + ksu_allow(db, "kernel", "apk_data_file", "dir", "open"); + ksu_allow(db, "kernel", "apk_data_file", "dir", "read"); + ksu_allow(db, "kernel", "apk_data_file", "dir", "search"); + // we may need to do mount on shell + ksu_allow(db, "kernel", "shell_data_file", "file", ALL); + // we need to read /data/system/packages.list + ksu_allow(db, "kernel", "kernel", "capability", "dac_override"); + // Android 10+: + // http://aospxref.com/android-12.0.0_r3/xref/system/sepolicy/private/file_contexts#512 + ksu_allow(db, "kernel", "packages_list_file", "file", ALL); + // Kernel 4.4 + ksu_allow(db, "kernel", "packages_list_file", "dir", ALL); + // Android 9-: + // http://aospxref.com/android-9.0.0_r61/xref/system/sepolicy/private/file_contexts#360 + ksu_allow(db, "kernel", "system_data_file", "file", ALL); + ksu_allow(db, "kernel", "system_data_file", "dir", ALL); + // our ksud triggered by init + ksu_allow(db, "init", "adb_data_file", "file", ALL); + ksu_allow(db, "init", "adb_data_file", "dir", ALL); // #1289 + ksu_allow(db, "init", KERNEL_SU_DOMAIN, ALL, ALL); + // we need to umount modules in zygote + ksu_allow(db, "zygote", "adb_data_file", "dir", "search"); + + // copied from Magisk rules + // suRights + ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "dir", "search"); + ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "dir", "read"); + ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "file", "open"); + ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "file", "read"); + ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "process", "getattr"); + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "process", "sigchld"); + + // allowLog + ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "dir", "search"); + ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "read"); + ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "open"); + ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "getattr"); + + // dumpsys + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fd", "use"); + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "write"); + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "read"); + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "open"); + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "getattr"); + + // bootctl + ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "dir", "search"); + ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "file", "read"); + ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "file", "open"); + ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "process", + "getattr"); + + // For mounting loop devices, mirrors, tmpfs + ksu_allow(db, "kernel", ALL, "file", "read"); + ksu_allow(db, "kernel", ALL, "file", "write"); + + // Allow all binder transactions + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL); + + // Allow system server kill su process + ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "getpgid"); + ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "sigkill"); + + rcu_read_unlock(); +} + +#define MAX_SEPOL_LEN 128 + +#define CMD_NORMAL_PERM 1 +#define CMD_XPERM 2 +#define CMD_TYPE_STATE 3 +#define CMD_TYPE 4 +#define CMD_TYPE_ATTR 5 +#define CMD_ATTR 6 +#define CMD_TYPE_TRANSITION 7 +#define CMD_TYPE_CHANGE 8 +#define CMD_GENFSCON 9 + +struct sepol_data { + u32 cmd; + u32 subcmd; + char __user *sepol1; + char __user *sepol2; + char __user *sepol3; + char __user *sepol4; + char __user *sepol5; + char __user *sepol6; + char __user *sepol7; +}; + +static int get_object(char *buf, char __user *user_object, size_t buf_sz, + char **object) +{ + if (!user_object) { + *object = ALL; + return 0; + } + + if (strncpy_from_user(buf, user_object, buf_sz) < 0) { + return -1; + } + + *object = buf; + + return 0; +} + +// reset avc cache table, otherwise the new rules will not take effect if already denied +static void reset_avc_cache() +{ +#if ((!defined(KSU_COMPAT_USE_SELINUX_STATE)) || \ + LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)) + avc_ss_reset(0); + selnl_notify_policyload(0); + selinux_status_update_policyload(0); +#else + struct selinux_avc *avc = selinux_state.avc; + avc_ss_reset(avc, 0); + selnl_notify_policyload(0); + selinux_status_update_policyload(&selinux_state, 0); +#endif + selinux_xfrm_notify_policyload(); +} + +int handle_sepolicy(unsigned long arg3, void __user *arg4) +{ + if (!arg4) { + return -1; + } + + if (!getenforce()) { + pr_info("SELinux permissive or disabled when handle policy!\n"); + } + + struct sepol_data data; + if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) { + pr_err("sepol: copy sepol_data failed.\n"); + return -1; + } + + u32 cmd = data.cmd; + u32 subcmd = data.subcmd; + + rcu_read_lock(); + + struct policydb *db = get_policydb(); + + int ret = -1; + if (cmd == CMD_NORMAL_PERM) { + char src_buf[MAX_SEPOL_LEN]; + char tgt_buf[MAX_SEPOL_LEN]; + char cls_buf[MAX_SEPOL_LEN]; + char perm_buf[MAX_SEPOL_LEN]; + + char *s, *t, *c, *p; + if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) { + pr_err("sepol: copy src failed.\n"); + goto exit; + } + + if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) { + pr_err("sepol: copy tgt failed.\n"); + goto exit; + } + + if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) { + pr_err("sepol: copy cls failed.\n"); + goto exit; + } + + if (get_object(perm_buf, data.sepol4, sizeof(perm_buf), &p) < + 0) { + pr_err("sepol: copy perm failed.\n"); + goto exit; + } + + bool success = false; + if (subcmd == 1) { + success = ksu_allow(db, s, t, c, p); + } else if (subcmd == 2) { + success = ksu_deny(db, s, t, c, p); + } else if (subcmd == 3) { + success = ksu_auditallow(db, s, t, c, p); + } else if (subcmd == 4) { + success = ksu_dontaudit(db, s, t, c, p); + } else { + pr_err("sepol: unknown subcmd: %d\n", subcmd); + } + ret = success ? 0 : -1; + + } else if (cmd == CMD_XPERM) { + char src_buf[MAX_SEPOL_LEN]; + char tgt_buf[MAX_SEPOL_LEN]; + char cls_buf[MAX_SEPOL_LEN]; + + char __maybe_unused + operation[MAX_SEPOL_LEN]; // it is always ioctl now! + char perm_set[MAX_SEPOL_LEN]; + + char *s, *t, *c; + if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) { + pr_err("sepol: copy src failed.\n"); + goto exit; + } + if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) { + pr_err("sepol: copy tgt failed.\n"); + goto exit; + } + if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) { + pr_err("sepol: copy cls failed.\n"); + goto exit; + } + if (strncpy_from_user(operation, data.sepol4, + sizeof(operation)) < 0) { + pr_err("sepol: copy operation failed.\n"); + goto exit; + } + if (strncpy_from_user(perm_set, data.sepol5, sizeof(perm_set)) < + 0) { + pr_err("sepol: copy perm_set failed.\n"); + goto exit; + } + + bool success = false; + if (subcmd == 1) { + success = ksu_allowxperm(db, s, t, c, perm_set); + } else if (subcmd == 2) { + success = ksu_auditallowxperm(db, s, t, c, perm_set); + } else if (subcmd == 3) { + success = ksu_dontauditxperm(db, s, t, c, perm_set); + } else { + pr_err("sepol: unknown subcmd: %d\n", subcmd); + } + ret = success ? 0 : -1; + } else if (cmd == CMD_TYPE_STATE) { + char src[MAX_SEPOL_LEN]; + + if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) { + pr_err("sepol: copy src failed.\n"); + goto exit; + } + + bool success = false; + if (subcmd == 1) { + success = ksu_permissive(db, src); + } else if (subcmd == 2) { + success = ksu_enforce(db, src); + } else { + pr_err("sepol: unknown subcmd: %d\n", subcmd); + } + if (success) + ret = 0; + + } else if (cmd == CMD_TYPE || cmd == CMD_TYPE_ATTR) { + char type[MAX_SEPOL_LEN]; + char attr[MAX_SEPOL_LEN]; + + if (strncpy_from_user(type, data.sepol1, sizeof(type)) < 0) { + pr_err("sepol: copy type failed.\n"); + goto exit; + } + if (strncpy_from_user(attr, data.sepol2, sizeof(attr)) < 0) { + pr_err("sepol: copy attr failed.\n"); + goto exit; + } + + bool success = false; + if (cmd == CMD_TYPE) { + success = ksu_type(db, type, attr); + } else { + success = ksu_typeattribute(db, type, attr); + } + if (!success) { + pr_err("sepol: %d failed.\n", cmd); + goto exit; + } + ret = 0; + + } else if (cmd == CMD_ATTR) { + char attr[MAX_SEPOL_LEN]; + + if (strncpy_from_user(attr, data.sepol1, sizeof(attr)) < 0) { + pr_err("sepol: copy attr failed.\n"); + goto exit; + } + if (!ksu_attribute(db, attr)) { + pr_err("sepol: %d failed.\n", cmd); + goto exit; + } + ret = 0; + + } else if (cmd == CMD_TYPE_TRANSITION) { + char src[MAX_SEPOL_LEN]; + char tgt[MAX_SEPOL_LEN]; + char cls[MAX_SEPOL_LEN]; + char default_type[MAX_SEPOL_LEN]; + char object[MAX_SEPOL_LEN]; + + if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) { + pr_err("sepol: copy src failed.\n"); + goto exit; + } + if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) { + pr_err("sepol: copy tgt failed.\n"); + goto exit; + } + if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) { + pr_err("sepol: copy cls failed.\n"); + goto exit; + } + if (strncpy_from_user(default_type, data.sepol4, + sizeof(default_type)) < 0) { + pr_err("sepol: copy default_type failed.\n"); + goto exit; + } + char *real_object; + if (data.sepol5 == NULL) { + real_object = NULL; + } else { + if (strncpy_from_user(object, data.sepol5, + sizeof(object)) < 0) { + pr_err("sepol: copy object failed.\n"); + goto exit; + } + real_object = object; + } + + bool success = ksu_type_transition(db, src, tgt, cls, + default_type, real_object); + if (success) + ret = 0; + + } else if (cmd == CMD_TYPE_CHANGE) { + char src[MAX_SEPOL_LEN]; + char tgt[MAX_SEPOL_LEN]; + char cls[MAX_SEPOL_LEN]; + char default_type[MAX_SEPOL_LEN]; + + if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) { + pr_err("sepol: copy src failed.\n"); + goto exit; + } + if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) { + pr_err("sepol: copy tgt failed.\n"); + goto exit; + } + if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) { + pr_err("sepol: copy cls failed.\n"); + goto exit; + } + if (strncpy_from_user(default_type, data.sepol4, + sizeof(default_type)) < 0) { + pr_err("sepol: copy default_type failed.\n"); + goto exit; + } + bool success = false; + if (subcmd == 1) { + success = ksu_type_change(db, src, tgt, cls, + default_type); + } else if (subcmd == 2) { + success = ksu_type_member(db, src, tgt, cls, + default_type); + } else { + pr_err("sepol: unknown subcmd: %d\n", subcmd); + } + if (success) + ret = 0; + } else if (cmd == CMD_GENFSCON) { + char name[MAX_SEPOL_LEN]; + char path[MAX_SEPOL_LEN]; + char context[MAX_SEPOL_LEN]; + if (strncpy_from_user(name, data.sepol1, sizeof(name)) < 0) { + pr_err("sepol: copy name failed.\n"); + goto exit; + } + if (strncpy_from_user(path, data.sepol2, sizeof(path)) < 0) { + pr_err("sepol: copy path failed.\n"); + goto exit; + } + if (strncpy_from_user(context, data.sepol3, sizeof(context)) < + 0) { + pr_err("sepol: copy context failed.\n"); + goto exit; + } + + if (!ksu_genfscon(db, name, path, context)) { + pr_err("sepol: %d failed.\n", cmd); + goto exit; + } + ret = 0; + } else { + pr_err("sepol: unknown cmd: %d\n", cmd); + } + +exit: + rcu_read_unlock(); + + // only allow and xallow needs to reset avc cache, but we cannot do that because + // we are in atomic context. so we just reset it every time. + reset_avc_cache(); + + return ret; +} diff --git a/KernelSU/kernel/selinux/selinux.c b/KernelSU/kernel/selinux/selinux.c new file mode 100644 index 000000000000..c333e8a800e6 --- /dev/null +++ b/KernelSU/kernel/selinux/selinux.c @@ -0,0 +1,145 @@ +#include "selinux.h" +#include "objsec.h" +#include "linux/version.h" +#include "../klog.h" // IWYU pragma: keep +#ifndef KSU_COMPAT_USE_SELINUX_STATE +#include "avc.h" +#endif + +#define KERNEL_SU_DOMAIN "u:r:su:s0" + +static int transive_to_domain(const char *domain) +{ + struct cred *cred; + struct task_security_struct *tsec; + u32 sid; + int error; + + cred = (struct cred *)__task_cred(current); + + tsec = cred->security; + if (!tsec) { + pr_err("tsec == NULL!\n"); + return -1; + } + + error = security_secctx_to_secid(domain, strlen(domain), &sid); + if (error) { + pr_info("security_secctx_to_secid %s -> sid: %d, error: %d\n", + domain, sid, error); + } + if (!error) { + tsec->sid = sid; + tsec->create_sid = 0; + tsec->keycreate_sid = 0; + tsec->sockcreate_sid = 0; + } + return error; +} + +void setup_selinux(const char *domain) +{ + if (transive_to_domain(domain)) { + pr_err("transive domain failed.\n"); + return; + } + + /* we didn't need this now, we have change selinux rules when boot! +if (!is_domain_permissive) { + if (set_domain_permissive() == 0) { + is_domain_permissive = true; + } +}*/ +} + +void setenforce(bool enforce) +{ +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +#ifdef KSU_COMPAT_USE_SELINUX_STATE + selinux_state.enforcing = enforce; +#else + selinux_enforcing = enforce; +#endif +#endif +} + +bool getenforce() +{ +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +#ifdef KSU_COMPAT_USE_SELINUX_STATE + if (selinux_state.disabled) { +#else + if (selinux_disabled) { +#endif + return false; + } +#endif + +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +#ifdef KSU_COMPAT_USE_SELINUX_STATE + return selinux_state.enforcing; +#else + return selinux_enforcing; +#endif +#else + return true; +#endif +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)) && \ + !defined(KSU_COMPAT_HAS_CURRENT_SID) +/* + * get the subjective security ID of the current task + */ +static inline u32 current_sid(void) +{ + const struct task_security_struct *tsec = current_security(); + + return tsec->sid; +} +#endif + +bool is_ksu_domain() +{ + char *domain; + u32 seclen; + bool result; + int err = security_secid_to_secctx(current_sid(), &domain, &seclen); + if (err) { + return false; + } + result = strncmp(KERNEL_SU_DOMAIN, domain, seclen) == 0; + security_release_secctx(domain, seclen); + return result; +} + +bool is_zygote(void *sec) +{ + struct task_security_struct *tsec = (struct task_security_struct *)sec; + if (!tsec) { + return false; + } + char *domain; + u32 seclen; + bool result; + int err = security_secid_to_secctx(tsec->sid, &domain, &seclen); + if (err) { + return false; + } + result = strncmp("u:r:zygote:s0", domain, seclen) == 0; + security_release_secctx(domain, seclen); + return result; +} + +#define DEVPTS_DOMAIN "u:object_r:devpts:s0" + +u32 ksu_get_devpts_sid() +{ + u32 devpts_sid = 0; + int err = security_secctx_to_secid(DEVPTS_DOMAIN, strlen(DEVPTS_DOMAIN), + &devpts_sid); + if (err) { + pr_info("get devpts sid err %d\n", err); + } + return devpts_sid; +} \ No newline at end of file diff --git a/KernelSU/kernel/selinux/selinux.h b/KernelSU/kernel/selinux/selinux.h new file mode 100644 index 000000000000..07120c253268 --- /dev/null +++ b/KernelSU/kernel/selinux/selinux.h @@ -0,0 +1,25 @@ +#ifndef __KSU_H_SELINUX +#define __KSU_H_SELINUX + +#include "linux/types.h" +#include "linux/version.h" + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) || defined(KSU_COMPAT_HAS_SELINUX_STATE) +#define KSU_COMPAT_USE_SELINUX_STATE +#endif + +void setup_selinux(const char *); + +void setenforce(bool); + +bool getenforce(); + +bool is_ksu_domain(); + +bool is_zygote(void *cred); + +void apply_kernelsu_rules(); + +u32 ksu_get_devpts_sid(); + +#endif diff --git a/KernelSU/kernel/selinux/sepolicy.c b/KernelSU/kernel/selinux/sepolicy.c new file mode 100644 index 000000000000..acdc45ad81f7 --- /dev/null +++ b/KernelSU/kernel/selinux/sepolicy.c @@ -0,0 +1,1070 @@ +#include +#include +#include +#include + +#include "sepolicy.h" +#include "../klog.h" // IWYU pragma: keep +#include "ss/symtab.h" +#include "../kernel_compat.h" // Add check Huawei Device + +#define KSU_SUPPORT_ADD_TYPE + +////////////////////////////////////////////////////// +// Declaration +////////////////////////////////////////////////////// + +static struct avtab_node *get_avtab_node(struct policydb *db, + struct avtab_key *key, + struct avtab_extended_perms *xperms); + +static bool add_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *p, int effect, bool invert); + +static void add_rule_raw(struct policydb *db, struct type_datum *src, + struct type_datum *tgt, struct class_datum *cls, + struct perm_datum *perm, int effect, bool invert); + +static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src, + struct type_datum *tgt, struct class_datum *cls, + uint16_t low, uint16_t high, int effect, + bool invert); +static bool add_xperm_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *range, int effect, + bool invert); + +static bool add_type_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *d, int effect); + +static bool add_filename_trans(struct policydb *db, const char *s, + const char *t, const char *c, const char *d, + const char *o); + +static bool add_genfscon(struct policydb *db, const char *fs_name, + const char *path, const char *context); + +static bool add_type(struct policydb *db, const char *type_name, bool attr); + +static bool set_type_state(struct policydb *db, const char *type_name, + bool permissive); + +static void add_typeattribute_raw(struct policydb *db, struct type_datum *type, + struct type_datum *attr); + +static bool add_typeattribute(struct policydb *db, const char *type, + const char *attr); + +////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////// + +// Invert is adding rules for auditdeny; in other cases, invert is removing +// rules +#define strip_av(effect, invert) ((effect == AVTAB_AUDITDENY) == !invert) + +#define ksu_hash_for_each(node_ptr, n_slot, cur) \ + int i; \ + for (i = 0; i < n_slot; ++i) \ + for (cur = node_ptr[i]; cur; cur = cur->next) + +// htable is a struct instead of pointer above 5.8.0: +// https://elixir.bootlin.com/linux/v5.8-rc1/source/security/selinux/ss/symtab.h +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) +#define ksu_hashtab_for_each(htab, cur) \ + ksu_hash_for_each(htab.htable, htab.size, cur) +#else +#define ksu_hashtab_for_each(htab, cur) \ + ksu_hash_for_each(htab->htable, htab->size, cur) +#endif + +// symtab_search is introduced on 5.9.0: +// https://elixir.bootlin.com/linux/v5.9-rc1/source/security/selinux/ss/symtab.h +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) +#define symtab_search(s, name) hashtab_search((s)->table, name) +#define symtab_insert(s, name, datum) hashtab_insert((s)->table, name, datum) +#endif + +#define avtab_for_each(avtab, cur) \ + ksu_hash_for_each(avtab.htable, avtab.nslot, cur); + +static struct avtab_node *get_avtab_node(struct policydb *db, + struct avtab_key *key, + struct avtab_extended_perms *xperms) +{ + struct avtab_node *node; + + /* AVTAB_XPERMS entries are not necessarily unique */ + if (key->specified & AVTAB_XPERMS) { + bool match = false; + node = avtab_search_node(&db->te_avtab, key); + while (node) { + if ((node->datum.u.xperms->specified == + xperms->specified) && + (node->datum.u.xperms->driver == xperms->driver)) { + match = true; + break; + } + node = avtab_search_node_next(node, key->specified); + } + if (!match) + node = NULL; + } else { + node = avtab_search_node(&db->te_avtab, key); + } + + if (!node) { + struct avtab_datum avdatum = {}; + /* + * AUDITDENY, aka DONTAUDIT, are &= assigned, versus |= for + * others. Initialize the data accordingly. + */ + if (key->specified & AVTAB_XPERMS) { + avdatum.u.xperms = xperms; + } else { + avdatum.u.data = + key->specified == AVTAB_AUDITDENY ? ~0U : 0U; + } + /* this is used to get the node - insertion is actually unique */ + node = avtab_insert_nonunique(&db->te_avtab, key, &avdatum); + + int grow_size = sizeof(struct avtab_key); + grow_size += sizeof(struct avtab_datum); + if (key->specified & AVTAB_XPERMS) { + grow_size += sizeof(u8); + grow_size += sizeof(u8); + grow_size += sizeof(u32) * + ARRAY_SIZE(avdatum.u.xperms->perms.p); + } + db->len += grow_size; + } + + return node; +} + +static bool add_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *p, int effect, bool invert) +{ + struct type_datum *src = NULL, *tgt = NULL; + struct class_datum *cls = NULL; + struct perm_datum *perm = NULL; + + if (s) { + src = symtab_search(&db->p_types, s); + if (src == NULL) { + pr_info("source type %s does not exist\n", s); + return false; + } + } + + if (t) { + tgt = symtab_search(&db->p_types, t); + if (tgt == NULL) { + pr_info("target type %s does not exist\n", t); + return false; + } + } + + if (c) { + cls = symtab_search(&db->p_classes, c); + if (cls == NULL) { + pr_info("class %s does not exist\n", c); + return false; + } + } + + if (p) { + if (c == NULL) { + pr_info("No class is specified, cannot add perm [%s] \n", + p); + return false; + } + + perm = symtab_search(&cls->permissions, p); + if (perm == NULL && cls->comdatum != NULL) { + perm = symtab_search(&cls->comdatum->permissions, p); + } + if (perm == NULL) { + pr_info("perm %s does not exist in class %s\n", p, c); + return false; + } + } + add_rule_raw(db, src, tgt, cls, perm, effect, invert); + return true; +} + +static void add_rule_raw(struct policydb *db, struct type_datum *src, + struct type_datum *tgt, struct class_datum *cls, + struct perm_datum *perm, int effect, bool invert) +{ + if (src == NULL) { + struct hashtab_node *node; + if (strip_av(effect, invert)) { + ksu_hashtab_for_each(db->p_types.table, node) + { + add_rule_raw(db, + (struct type_datum *)node->datum, + tgt, cls, perm, effect, invert); + }; + } else { + ksu_hashtab_for_each(db->p_types.table, node) + { + struct type_datum *type = + (struct type_datum *)(node->datum); + if (type->attribute) { + add_rule_raw(db, type, tgt, cls, perm, + effect, invert); + } + }; + } + } else if (tgt == NULL) { + struct hashtab_node *node; + if (strip_av(effect, invert)) { + ksu_hashtab_for_each(db->p_types.table, node) + { + add_rule_raw(db, src, + (struct type_datum *)node->datum, + cls, perm, effect, invert); + }; + } else { + ksu_hashtab_for_each(db->p_types.table, node) + { + struct type_datum *type = + (struct type_datum *)(node->datum); + if (type->attribute) { + add_rule_raw(db, src, type, cls, perm, + effect, invert); + } + }; + } + } else if (cls == NULL) { + struct hashtab_node *node; + ksu_hashtab_for_each(db->p_classes.table, node) + { + add_rule_raw(db, src, tgt, + (struct class_datum *)node->datum, perm, + effect, invert); + } + } else { + struct avtab_key key; + key.source_type = src->value; + key.target_type = tgt->value; + key.target_class = cls->value; + key.specified = effect; + + struct avtab_node *node = get_avtab_node(db, &key, NULL); + if (invert) { + if (perm) + node->datum.u.data &= + ~(1U << (perm->value - 1)); + else + node->datum.u.data = 0U; + } else { + if (perm) + node->datum.u.data |= 1U << (perm->value - 1); + else + node->datum.u.data = ~0U; + } + } +} + +#define ioctl_driver(x) (x >> 8 & 0xFF) +#define ioctl_func(x) (x & 0xFF) + +#define xperm_test(x, p) (1 & (p[x >> 5] >> (x & 0x1f))) +#define xperm_set(x, p) (p[x >> 5] |= (1 << (x & 0x1f))) +#define xperm_clear(x, p) (p[x >> 5] &= ~(1 << (x & 0x1f))) + +static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src, + struct type_datum *tgt, struct class_datum *cls, + uint16_t low, uint16_t high, int effect, + bool invert) +{ + if (src == NULL) { + struct hashtab_node *node; + ksu_hashtab_for_each(db->p_types.table, node) + { + struct type_datum *type = + (struct type_datum *)(node->datum); + if (type->attribute) { + add_xperm_rule_raw(db, type, tgt, cls, low, + high, effect, invert); + } + }; + } else if (tgt == NULL) { + struct hashtab_node *node; + ksu_hashtab_for_each(db->p_types.table, node) + { + struct type_datum *type = + (struct type_datum *)(node->datum); + if (type->attribute) { + add_xperm_rule_raw(db, src, type, cls, low, + high, effect, invert); + } + }; + } else if (cls == NULL) { + struct hashtab_node *node; + ksu_hashtab_for_each(db->p_classes.table, node) + { + add_xperm_rule_raw(db, src, tgt, + (struct class_datum *)(node->datum), + low, high, effect, invert); + }; + } else { + struct avtab_key key; + key.source_type = src->value; + key.target_type = tgt->value; + key.target_class = cls->value; + key.specified = effect; + + struct avtab_datum *datum; + struct avtab_node *node; + struct avtab_extended_perms xperms; + + memset(&xperms, 0, sizeof(xperms)); + if (ioctl_driver(low) != ioctl_driver(high)) { + xperms.specified = AVTAB_XPERMS_IOCTLDRIVER; + xperms.driver = 0; + } else { + xperms.specified = AVTAB_XPERMS_IOCTLFUNCTION; + xperms.driver = ioctl_driver(low); + } + int i; + if (xperms.specified == AVTAB_XPERMS_IOCTLDRIVER) { + for (i = ioctl_driver(low); i <= ioctl_driver(high); + ++i) { + if (invert) + xperm_clear(i, xperms.perms.p); + else + xperm_set(i, xperms.perms.p); + } + } else { + for (i = ioctl_func(low); i <= ioctl_func(high); ++i) { + if (invert) + xperm_clear(i, xperms.perms.p); + else + xperm_set(i, xperms.perms.p); + } + } + + node = get_avtab_node(db, &key, &xperms); + if (!node) { + pr_warn("add_xperm_rule_raw cannot found node!\n"); + return; + } + datum = &node->datum; + + if (datum->u.xperms == NULL) { + datum->u.xperms = + (struct avtab_extended_perms *)(kmalloc( + sizeof(xperms), GFP_KERNEL)); + if (!datum->u.xperms) { + pr_err("alloc xperms failed\n"); + return; + } + memcpy(datum->u.xperms, &xperms, sizeof(xperms)); + } + } +} + +static bool add_xperm_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *range, int effect, + bool invert) +{ + struct type_datum *src = NULL, *tgt = NULL; + struct class_datum *cls = NULL; + + if (s) { + src = symtab_search(&db->p_types, s); + if (src == NULL) { + pr_info("source type %s does not exist\n", s); + return false; + } + } + + if (t) { + tgt = symtab_search(&db->p_types, t); + if (tgt == NULL) { + pr_info("target type %s does not exist\n", t); + return false; + } + } + + if (c) { + cls = symtab_search(&db->p_classes, c); + if (cls == NULL) { + pr_info("class %s does not exist\n", c); + return false; + } + } + + u16 low, high; + + if (range) { + if (strchr(range, '-')) { + sscanf(range, "%hx-%hx", &low, &high); + } else { + sscanf(range, "%hx", &low); + high = low; + } + } else { + low = 0; + high = 0xFFFF; + } + + add_xperm_rule_raw(db, src, tgt, cls, low, high, effect, invert); + return true; +} + +static bool add_type_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *d, int effect) +{ + struct type_datum *src, *tgt, *def; + struct class_datum *cls; + + src = symtab_search(&db->p_types, s); + if (src == NULL) { + pr_info("source type %s does not exist\n", s); + return false; + } + tgt = symtab_search(&db->p_types, t); + if (tgt == NULL) { + pr_info("target type %s does not exist\n", t); + return false; + } + cls = symtab_search(&db->p_classes, c); + if (cls == NULL) { + pr_info("class %s does not exist\n", c); + return false; + } + def = symtab_search(&db->p_types, d); + if (def == NULL) { + pr_info("default type %s does not exist\n", d); + return false; + } + + struct avtab_key key; + key.source_type = src->value; + key.target_type = tgt->value; + key.target_class = cls->value; + key.specified = effect; + + struct avtab_node *node = get_avtab_node(db, &key, NULL); + node->datum.u.data = def->value; + + return true; +} + +// 5.9.0 : static inline int hashtab_insert(struct hashtab *h, void *key, void +// *datum, struct hashtab_key_params key_params) 5.8.0: int +// hashtab_insert(struct hashtab *h, void *k, void *d); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) +static u32 filenametr_hash(const void *k) +{ + const struct filename_trans_key *ft = k; + unsigned long hash; + unsigned int byte_num; + unsigned char focus; + + hash = ft->ttype ^ ft->tclass; + + byte_num = 0; + while ((focus = ft->name[byte_num++])) + hash = partial_name_hash(focus, hash); + return hash; +} + +static int filenametr_cmp(const void *k1, const void *k2) +{ + const struct filename_trans_key *ft1 = k1; + const struct filename_trans_key *ft2 = k2; + int v; + + v = ft1->ttype - ft2->ttype; + if (v) + return v; + + v = ft1->tclass - ft2->tclass; + if (v) + return v; + + return strcmp(ft1->name, ft2->name); +} + +static const struct hashtab_key_params filenametr_key_params = { + .hash = filenametr_hash, + .cmp = filenametr_cmp, +}; +#endif + +static bool add_filename_trans(struct policydb *db, const char *s, + const char *t, const char *c, const char *d, + const char *o) +{ + struct type_datum *src, *tgt, *def; + struct class_datum *cls; + + src = symtab_search(&db->p_types, s); + if (src == NULL) { + pr_warn("source type %s does not exist\n", s); + return false; + } + tgt = symtab_search(&db->p_types, t); + if (tgt == NULL) { + pr_warn("target type %s does not exist\n", t); + return false; + } + cls = symtab_search(&db->p_classes, c); + if (cls == NULL) { + pr_warn("class %s does not exist\n", c); + return false; + } + def = symtab_search(&db->p_types, d); + if (def == NULL) { + pr_warn("default type %s does not exist\n", d); + return false; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0) + struct filename_trans_key key; + key.ttype = tgt->value; + key.tclass = cls->value; + key.name = (char *)o; + + struct filename_trans_datum *last = NULL; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + struct filename_trans_datum *trans = + policydb_filenametr_search(db, &key); +#else + struct filename_trans_datum *trans = + hashtab_search(&db->filename_trans, &key); +#endif + while (trans) { + if (ebitmap_get_bit(&trans->stypes, src->value - 1)) { + // Duplicate, overwrite existing data and return + trans->otype = def->value; + return true; + } + if (trans->otype == def->value) + break; + last = trans; + trans = trans->next; + } + + if (trans == NULL) { + trans = (struct filename_trans_datum *)kcalloc(sizeof(*trans), + 1, GFP_ATOMIC); + struct filename_trans_key *new_key = + (struct filename_trans_key *)kmalloc(sizeof(*new_key), + GFP_ATOMIC); + *new_key = key; + new_key->name = kstrdup(key.name, GFP_ATOMIC); + trans->next = last; + trans->otype = def->value; + hashtab_insert(&db->filename_trans, new_key, trans, + filenametr_key_params); + } + + db->compat_filename_trans_count++; + return ebitmap_set_bit(&trans->stypes, src->value - 1, 1) == 0; +#else // < 5.7.0, has no filename_trans_key, but struct filename_trans + + struct filename_trans key; + key.ttype = tgt->value; + key.tclass = cls->value; + key.name = (char *)o; + + struct filename_trans_datum *trans = + hashtab_search(db->filename_trans, &key); + + if (trans == NULL) { + trans = (struct filename_trans_datum *)kcalloc(sizeof(*trans), + 1, GFP_ATOMIC); + if (!trans) { + pr_err("add_filename_trans: Failed to alloc datum\n"); + return false; + } + struct filename_trans *new_key = + (struct filename_trans *)kmalloc(sizeof(*new_key), + GFP_ATOMIC); + if (!new_key) { + pr_err("add_filename_trans: Failed to alloc new_key\n"); + return false; + } + *new_key = key; + new_key->name = kstrdup(key.name, GFP_ATOMIC); + trans->otype = def->value; + hashtab_insert(db->filename_trans, new_key, trans); + } + + return ebitmap_set_bit(&db->filename_trans_ttypes, src->value - 1, 1) == + 0; +#endif +} + +static bool add_genfscon(struct policydb *db, const char *fs_name, + const char *path, const char *context) +{ + return false; +} + +static void *ksu_realloc(void *old, size_t new_size, size_t old_size) +{ + // we can't use krealloc, because it may be read-only + void *new = kzalloc(new_size, GFP_ATOMIC); + if (!new) { + return NULL; + } + if (old_size) { + memcpy(new, old, old_size); + } + // we can't use kfree, because it may be read-only + // there maybe some leaks, maybe we can check ptr_write, but it's not a big deal + // kfree(old); + return new; +} + +static bool add_type(struct policydb *db, const char *type_name, bool attr) +{ +#ifdef KSU_SUPPORT_ADD_TYPE + struct type_datum *type = symtab_search(&db->p_types, type_name); + if (type) { + pr_warn("Type %s already exists\n", type_name); + return true; + } + + u32 value = ++db->p_types.nprim; + type = (struct type_datum *)kzalloc(sizeof(struct type_datum), + GFP_ATOMIC); + if (!type) { + pr_err("add_type: alloc type_datum failed.\n"); + return false; + } + + type->primary = 1; + type->value = value; + type->attribute = attr; + + char *key = kstrdup(type_name, GFP_ATOMIC); + if (!key) { + pr_err("add_type: alloc key failed.\n"); + return false; + } + + if (symtab_insert(&db->p_types, key, type)) { + pr_err("add_type: insert symtab failed.\n"); + return false; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) + struct ebitmap *new_type_attr_map_array = + ksu_realloc(db->type_attr_map_array, + value * sizeof(struct ebitmap), + (value - 1) * sizeof(struct ebitmap)); + + if (!new_type_attr_map_array) { + pr_err("add_type: alloc type_attr_map_array failed\n"); + return false; + } + + struct type_datum **new_type_val_to_struct = + ksu_realloc(db->type_val_to_struct, + sizeof(*db->type_val_to_struct) * value, + sizeof(*db->type_val_to_struct) * (value - 1)); + + if (!new_type_val_to_struct) { + pr_err("add_type: alloc type_val_to_struct failed\n"); + return false; + } + + char **new_val_to_name_types = + ksu_realloc(db->sym_val_to_name[SYM_TYPES], + sizeof(char *) * value, + sizeof(char *) * (value - 1)); + if (!new_val_to_name_types) { + pr_err("add_type: alloc val_to_name failed\n"); + return false; + } + + db->type_attr_map_array = new_type_attr_map_array; + ebitmap_init(&db->type_attr_map_array[value - 1]); + ebitmap_set_bit(&db->type_attr_map_array[value - 1], value - 1, 1); + + db->type_val_to_struct = new_type_val_to_struct; + db->type_val_to_struct[value - 1] = type; + + db->sym_val_to_name[SYM_TYPES] = new_val_to_name_types; + db->sym_val_to_name[SYM_TYPES][value - 1] = key; + + int i; + for (i = 0; i < db->p_roles.nprim; ++i) { + ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1, + 1); + } + + return true; +#elif defined(CONFIG_IS_HW_HISI) + /* + * Huawei use type_attr_map and type_val_to_struct. + * And use ebitmap not flex_array. + */ + size_t new_size = sizeof(struct ebitmap) * db->p_types.nprim; + struct ebitmap *new_type_attr_map = + (krealloc(db->type_attr_map, new_size, GFP_ATOMIC)); + + struct type_datum **new_type_val_to_struct = + krealloc(db->type_val_to_struct, + sizeof(*db->type_val_to_struct) * db->p_types.nprim, + GFP_ATOMIC); + + if (!new_type_attr_map) { + pr_err("add_type: alloc type_attr_map failed\n"); + return false; + } + + if (!new_type_val_to_struct) { + pr_err("add_type: alloc type_val_to_struct failed\n"); + return false; + } + + char **new_val_to_name_types = + krealloc(db->sym_val_to_name[SYM_TYPES], + sizeof(char *) * db->symtab[SYM_TYPES].nprim, + GFP_KERNEL); + if (!new_val_to_name_types) { + pr_err("add_type: alloc val_to_name failed\n"); + return false; + } + + db->type_attr_map = new_type_attr_map; + ebitmap_init(&db->type_attr_map[value - 1], HISI_SELINUX_EBITMAP_RO); + ebitmap_set_bit(&db->type_attr_map[value - 1], value - 1, 1); + + db->type_val_to_struct = new_type_val_to_struct; + db->type_val_to_struct[value - 1] = type; + + db->sym_val_to_name[SYM_TYPES] = new_val_to_name_types; + db->sym_val_to_name[SYM_TYPES][value - 1] = key; + + int i; + for (i = 0; i < db->p_roles.nprim; ++i) { + ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1, + 1); + } + + return true; +#else + // flex_array is not extensible, we need to create a new bigger one instead + struct flex_array *new_type_attr_map_array = + flex_array_alloc(sizeof(struct ebitmap), db->p_types.nprim, + GFP_ATOMIC | __GFP_ZERO); + + struct flex_array *new_type_val_to_struct = + flex_array_alloc(sizeof(struct type_datum *), db->p_types.nprim, + GFP_ATOMIC | __GFP_ZERO); + + struct flex_array *new_val_to_name_types = + flex_array_alloc(sizeof(char *), db->symtab[SYM_TYPES].nprim, + GFP_ATOMIC | __GFP_ZERO); + + if (!new_type_attr_map_array) { + pr_err("add_type: alloc type_attr_map_array failed\n"); + return false; + } + + if (!new_type_val_to_struct) { + pr_err("add_type: alloc type_val_to_struct failed\n"); + return false; + } + + if (!new_val_to_name_types) { + pr_err("add_type: alloc val_to_name failed\n"); + return false; + } + + // preallocate so we don't have to worry about the put ever failing + if (flex_array_prealloc(new_type_attr_map_array, 0, db->p_types.nprim, + GFP_ATOMIC | __GFP_ZERO)) { + pr_err("add_type: prealloc type_attr_map_array failed\n"); + return false; + } + + if (flex_array_prealloc(new_type_val_to_struct, 0, db->p_types.nprim, + GFP_ATOMIC | __GFP_ZERO)) { + pr_err("add_type: prealloc type_val_to_struct_array failed\n"); + return false; + } + + if (flex_array_prealloc(new_val_to_name_types, 0, + db->symtab[SYM_TYPES].nprim, + GFP_ATOMIC | __GFP_ZERO)) { + pr_err("add_type: prealloc val_to_name_types failed\n"); + return false; + } + + int j; + void *old_elem; + // copy the old data or pointers to new flex arrays + for (j = 0; j < db->type_attr_map_array->total_nr_elements; j++) { + old_elem = flex_array_get(db->type_attr_map_array, j); + if (old_elem) + flex_array_put(new_type_attr_map_array, j, old_elem, + GFP_ATOMIC | __GFP_ZERO); + } + + for (j = 0; j < db->type_val_to_struct_array->total_nr_elements; j++) { + old_elem = flex_array_get_ptr(db->type_val_to_struct_array, j); + if (old_elem) + flex_array_put_ptr(new_type_val_to_struct, j, old_elem, + GFP_ATOMIC | __GFP_ZERO); + } + + for (j = 0; j < db->symtab[SYM_TYPES].nprim; j++) { + old_elem = + flex_array_get_ptr(db->sym_val_to_name[SYM_TYPES], j); + if (old_elem) + flex_array_put_ptr(new_val_to_name_types, j, old_elem, + GFP_ATOMIC | __GFP_ZERO); + } + + // store the pointer of old flex arrays first, when assigning new ones we + // should free it + struct flex_array *old_fa; + + old_fa = db->type_attr_map_array; + db->type_attr_map_array = new_type_attr_map_array; + if (old_fa) { + flex_array_free(old_fa); + } + + ebitmap_init(flex_array_get(db->type_attr_map_array, value - 1)); + ebitmap_set_bit(flex_array_get(db->type_attr_map_array, value - 1), + value - 1, 1); + + old_fa = db->type_val_to_struct_array; + db->type_val_to_struct_array = new_type_val_to_struct; + if (old_fa) { + flex_array_free(old_fa); + } + flex_array_put_ptr(db->type_val_to_struct_array, value - 1, type, + GFP_ATOMIC | __GFP_ZERO); + + old_fa = db->sym_val_to_name[SYM_TYPES]; + db->sym_val_to_name[SYM_TYPES] = new_val_to_name_types; + if (old_fa) { + flex_array_free(old_fa); + } + flex_array_put_ptr(db->sym_val_to_name[SYM_TYPES], value - 1, key, + GFP_ATOMIC | __GFP_ZERO); + + int i; + for (i = 0; i < db->p_roles.nprim; ++i) { + ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1, + 1); + } + return true; +#endif + +#else + return false; +#endif +} + +static bool set_type_state(struct policydb *db, const char *type_name, + bool permissive) +{ + struct type_datum *type; + if (type_name == NULL) { + struct hashtab_node *node; + ksu_hashtab_for_each(db->p_types.table, node) + { + type = (struct type_datum *)(node->datum); + if (ebitmap_set_bit(&db->permissive_map, type->value, + permissive)) + pr_info("Could not set bit in permissive map\n"); + }; + } else { + type = (struct type_datum *)symtab_search(&db->p_types, + type_name); + if (type == NULL) { + pr_info("type %s does not exist\n", type_name); + return false; + } + if (ebitmap_set_bit(&db->permissive_map, type->value, + permissive)) { + pr_info("Could not set bit in permissive map\n"); + return false; + } + } + return true; +} + +static void add_typeattribute_raw(struct policydb *db, struct type_datum *type, + struct type_datum *attr) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) + struct ebitmap *sattr = &db->type_attr_map_array[type->value - 1]; +#elif defined(CONFIG_IS_HW_HISI) + /* + * HISI_SELINUX_EBITMAP_RO is Huawei's unique features. + */ + struct ebitmap *sattr = &db->type_attr_map[type->value - 1], + HISI_SELINUX_EBITMAP_RO; +#else + struct ebitmap *sattr = + flex_array_get(db->type_attr_map_array, type->value - 1); +#endif + ebitmap_set_bit(sattr, attr->value - 1, 1); + + struct hashtab_node *node; + struct constraint_node *n; + struct constraint_expr *e; + ksu_hashtab_for_each(db->p_classes.table, node) + { + struct class_datum *cls = (struct class_datum *)(node->datum); + for (n = cls->constraints; n; n = n->next) { + for (e = n->expr; e; e = e->next) { + if (e->expr_type == CEXPR_NAMES && + ebitmap_get_bit(&e->type_names->types, + attr->value - 1)) { + ebitmap_set_bit(&e->names, + type->value - 1, 1); + } + } + } + }; +} + +static bool add_typeattribute(struct policydb *db, const char *type, + const char *attr) +{ + struct type_datum *type_d = symtab_search(&db->p_types, type); + if (type_d == NULL) { + pr_info("type %s does not exist\n", type); + return false; + } else if (type_d->attribute) { + pr_info("type %s is an attribute\n", attr); + return false; + } + + struct type_datum *attr_d = symtab_search(&db->p_types, attr); + if (attr_d == NULL) { + pr_info("attribute %s does not exist\n", type); + return false; + } else if (!attr_d->attribute) { + pr_info("type %s is not an attribute \n", attr); + return false; + } + + add_typeattribute_raw(db, type_d, attr_d); + return true; +} + +////////////////////////////////////////////////////////////////////////// + +// Operation on types +bool ksu_type(struct policydb *db, const char *name, const char *attr) +{ + return add_type(db, name, false) && add_typeattribute(db, name, attr); +} + +bool ksu_attribute(struct policydb *db, const char *name) +{ + return add_type(db, name, true); +} + +bool ksu_permissive(struct policydb *db, const char *type) +{ + return set_type_state(db, type, true); +} + +bool ksu_enforce(struct policydb *db, const char *type) +{ + return set_type_state(db, type, false); +} + +bool ksu_typeattribute(struct policydb *db, const char *type, const char *attr) +{ + return add_typeattribute(db, type, attr); +} + +bool ksu_exists(struct policydb *db, const char *type) +{ + return symtab_search(&db->p_types, type) != NULL; +} + +// Access vector rules +bool ksu_allow(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm) +{ + return add_rule(db, src, tgt, cls, perm, AVTAB_ALLOWED, false); +} + +bool ksu_deny(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm) +{ + return add_rule(db, src, tgt, cls, perm, AVTAB_ALLOWED, true); +} + +bool ksu_auditallow(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm) +{ + return add_rule(db, src, tgt, cls, perm, AVTAB_AUDITALLOW, false); +} +bool ksu_dontaudit(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm) +{ + return add_rule(db, src, tgt, cls, perm, AVTAB_AUDITDENY, true); +} + +// Extended permissions access vector rules +bool ksu_allowxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range) +{ + return add_xperm_rule(db, src, tgt, cls, range, AVTAB_XPERMS_ALLOWED, + false); +} + +bool ksu_auditallowxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range) +{ + return add_xperm_rule(db, src, tgt, cls, range, AVTAB_XPERMS_AUDITALLOW, + false); +} + +bool ksu_dontauditxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range) +{ + return add_xperm_rule(db, src, tgt, cls, range, AVTAB_XPERMS_DONTAUDIT, + false); +} + +// Type rules +bool ksu_type_transition(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def, const char *obj) +{ + if (obj) { + return add_filename_trans(db, src, tgt, cls, def, obj); + } else { + return add_type_rule(db, src, tgt, cls, def, AVTAB_TRANSITION); + } +} + +bool ksu_type_change(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def) +{ + return add_type_rule(db, src, tgt, cls, def, AVTAB_CHANGE); +} + +bool ksu_type_member(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def) +{ + return add_type_rule(db, src, tgt, cls, def, AVTAB_MEMBER); +} + +// File system labeling +bool ksu_genfscon(struct policydb *db, const char *fs_name, const char *path, + const char *ctx) +{ + return add_genfscon(db, fs_name, path, ctx); +} diff --git a/KernelSU/kernel/selinux/sepolicy.h b/KernelSU/kernel/selinux/sepolicy.h new file mode 100644 index 000000000000..675d1499e46d --- /dev/null +++ b/KernelSU/kernel/selinux/sepolicy.h @@ -0,0 +1,46 @@ +#ifndef __KSU_H_SEPOLICY +#define __KSU_H_SEPOLICY + +#include + +#include "ss/policydb.h" + +// Operation on types +bool ksu_type(struct policydb *db, const char *name, const char *attr); +bool ksu_attribute(struct policydb *db, const char *name); +bool ksu_permissive(struct policydb *db, const char *type); +bool ksu_enforce(struct policydb *db, const char *type); +bool ksu_typeattribute(struct policydb *db, const char *type, const char *attr); +bool ksu_exists(struct policydb *db, const char *type); + +// Access vector rules +bool ksu_allow(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); +bool ksu_deny(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); +bool ksu_auditallow(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); +bool ksu_dontaudit(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); + +// Extended permissions access vector rules +bool ksu_allowxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range); +bool ksu_auditallowxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range); +bool ksu_dontauditxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range); + +// Type rules +bool ksu_type_transition(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def, const char *obj); +bool ksu_type_change(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def); +bool ksu_type_member(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def); + +// File system labeling +bool ksu_genfscon(struct policydb *db, const char *fs_name, const char *path, + const char *ctx); + +#endif diff --git a/KernelSU/kernel/setup.sh b/KernelSU/kernel/setup.sh new file mode 100755 index 000000000000..e688dbaf3ae5 --- /dev/null +++ b/KernelSU/kernel/setup.sh @@ -0,0 +1,75 @@ +#!/bin/sh +set -eu + +GKI_ROOT=$(pwd) + +display_usage() { + echo "Usage: $0 [--cleanup | ]" + echo " --cleanup: Cleans up previous modifications made by the script." + echo " : Sets up or updates the KernelSU to specified tag or commit." + echo " -h, --help: Displays this usage information." + echo " (no args): Sets up or updates the KernelSU environment to the latest tagged version." +} + +initialize_variables() { + if test -d "$GKI_ROOT/common/drivers"; then + DRIVER_DIR="$GKI_ROOT/common/drivers" + elif test -d "$GKI_ROOT/drivers"; then + DRIVER_DIR="$GKI_ROOT/drivers" + else + echo '[ERROR] "drivers/" directory not found.' + exit 127 + fi + + DRIVER_MAKEFILE=$DRIVER_DIR/Makefile + DRIVER_KCONFIG=$DRIVER_DIR/Kconfig +} + +# Reverts modifications made by this script +perform_cleanup() { + echo "[+] Cleaning up..." + [ -L "$DRIVER_DIR/kernelsu" ] && rm "$DRIVER_DIR/kernelsu" && echo "[-] Symlink removed." + grep -q "kernelsu" "$DRIVER_MAKEFILE" && sed -i '/kernelsu/d' "$DRIVER_MAKEFILE" && echo "[-] Makefile reverted." + grep -q "drivers/kernelsu/Kconfig" "$DRIVER_KCONFIG" && sed -i '/drivers\/kernelsu\/Kconfig/d' "$DRIVER_KCONFIG" && echo "[-] Kconfig reverted." + if [ -d "$GKI_ROOT/KernelSU" ]; then + rm -rf "$GKI_ROOT/KernelSU" && echo "[-] KernelSU directory deleted." + fi +} + +# Sets up or update KernelSU environment +setup_kernelsu() { + echo "[+] Setting up KernelSU..." + test -d "$GKI_ROOT/KernelSU" || git clone https://github.com/tiann/KernelSU && echo "[+] Repository cloned." + cd "$GKI_ROOT/KernelSU" + git stash && echo "[-] Stashed current changes." + if [ "$(git status | grep -Po 'v\d+(\.\d+)*' | head -n1)" ]; then + git checkout main && echo "[-] Switched to main branch." + fi + git pull && echo "[+] Repository updated." + if [ -z "${1-}" ]; then + git checkout "$(git describe --abbrev=0 --tags)" && echo "[-] Checked out latest tag." + else + git checkout "$1" && echo "[-] Checked out $1." || echo "[-] Checkout default branch" + fi + cd "$DRIVER_DIR" + ln -sf "$(realpath --relative-to="$DRIVER_DIR" "$GKI_ROOT/KernelSU/kernel")" "kernelsu" && echo "[+] Symlink created." + + # Add entries in Makefile and Kconfig if not already existing + grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE" && echo "[+] Modified Makefile." + grep -q "source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG" && echo "[+] Modified Kconfig." + echo '[+] Done.' +} + +# Process command-line arguments +if [ "$#" -eq 0 ]; then + initialize_variables + setup_kernelsu +elif [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + display_usage +elif [ "$1" = "--cleanup" ]; then + initialize_variables + perform_cleanup +else + initialize_variables + setup_kernelsu "$@" +fi diff --git a/KernelSU/kernel/sucompat.c b/KernelSU/kernel/sucompat.c new file mode 100644 index 000000000000..9b45cd0d9490 --- /dev/null +++ b/KernelSU/kernel/sucompat.c @@ -0,0 +1,367 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +#include +#else +#include +#endif + +#include "objsec.h" +#include "allowlist.h" +#include "arch.h" +#include "klog.h" // IWYU pragma: keep +#include "ksud.h" +#include "kernel_compat.h" + +#define SU_PATH "/system/bin/su" +#define SH_PATH "/system/bin/sh" + +extern void escape_to_root(); + +static void __user *userspace_stack_buffer(const void *d, size_t len) +{ + /* To avoid having to mmap a page in userspace, just write below the stack + * pointer. */ + char __user *p = (void __user *)current_user_stack_pointer() - len; + + return copy_to_user(p, d, len) ? NULL : p; +} + +static char __user *sh_user_path(void) +{ + static const char sh_path[] = "/system/bin/sh"; + + return userspace_stack_buffer(sh_path, sizeof(sh_path)); +} + +static char __user *ksud_user_path(void) +{ + static const char ksud_path[] = KSUD_PATH; + + return userspace_stack_buffer(ksud_path, sizeof(ksud_path)); +} + +int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, + int *__unused_flags) +{ + const char su[] = SU_PATH; + + if (!ksu_is_allow_uid(current_uid().val)) { + return 0; + } + + char path[sizeof(su) + 1]; + memset(path, 0, sizeof(path)); + ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); + + if (unlikely(!memcmp(path, su, sizeof(su)))) { + pr_info("faccessat su->sh!\n"); + *filename_user = sh_user_path(); + } + + return 0; +} + +int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags) +{ + // const char sh[] = SH_PATH; + const char su[] = SU_PATH; + + if (!ksu_is_allow_uid(current_uid().val)) { + return 0; + } + + if (unlikely(!filename_user)) { + return 0; + } + + char path[sizeof(su) + 1]; + memset(path, 0, sizeof(path)); +// Remove this later!! we use syscall hook, so this will never happen!!!!! +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) && 0 + // it becomes a `struct filename *` after 5.18 + // https://elixir.bootlin.com/linux/v5.18/source/fs/stat.c#L216 + const char sh[] = SH_PATH; + struct filename *filename = *((struct filename **)filename_user); + if (IS_ERR(filename)) { + return 0; + } + if (likely(memcmp(filename->name, su, sizeof(su)))) + return 0; + pr_info("vfs_statx su->sh!\n"); + memcpy((void *)filename->name, sh, sizeof(sh)); +#else + ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); + + if (unlikely(!memcmp(path, su, sizeof(su)))) { + pr_info("newfstatat su->sh!\n"); + *filename_user = sh_user_path(); + } +#endif + + return 0; +} + +// the call from execve_handler_pre won't provided correct value for __never_use_argument, use them after fix execve_handler_pre, keeping them for consistence for manually patched code +int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, + void *__never_use_argv, void *__never_use_envp, + int *__never_use_flags) +{ + struct filename *filename; + const char sh[] = KSUD_PATH; + const char su[] = SU_PATH; + + if (unlikely(!filename_ptr)) + return 0; + + filename = *filename_ptr; + if (IS_ERR(filename)) { + return 0; + } + + if (likely(memcmp(filename->name, su, sizeof(su)))) + return 0; + + if (!ksu_is_allow_uid(current_uid().val)) + return 0; + + pr_info("do_execveat_common su found\n"); + memcpy((void *)filename->name, sh, sizeof(sh)); + + escape_to_root(); + + return 0; +} + +int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user, + void *__never_use_argv, void *__never_use_envp, + int *__never_use_flags) +{ + const char su[] = SU_PATH; + char path[sizeof(su) + 1]; + + if (unlikely(!filename_user)) + return 0; + + memset(path, 0, sizeof(path)); + ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); + + if (likely(memcmp(path, su, sizeof(su)))) + return 0; + + if (!ksu_is_allow_uid(current_uid().val)) + return 0; + + pr_info("sys_execve su found\n"); + *filename_user = ksud_user_path(); + + escape_to_root(); + + return 0; +} + +int ksu_handle_devpts(struct inode *inode) +{ + if (!current->mm) { + return 0; + } + + uid_t uid = current_uid().val; + if (uid % 100000 < 10000) { + // not untrusted_app, ignore it + return 0; + } + + if (!ksu_is_allow_uid(uid)) + return 0; + + if (ksu_devpts_sid) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) + struct inode_security_struct *sec = selinux_inode(inode); +#else + struct inode_security_struct *sec = + (struct inode_security_struct *)inode->i_security; +#endif + if (sec) { + sec->sid = ksu_devpts_sid; + } + } + + return 0; +} + +#ifdef CONFIG_KPROBES + +__maybe_unused static int faccessat_handler_pre(struct kprobe *p, + struct pt_regs *regs) +{ + int *dfd = (int *)&PT_REGS_PARM1(regs); + const char __user **filename_user = (const char **)&PT_REGS_PARM2(regs); + int *mode = (int *)&PT_REGS_PARM3(regs); + // Both sys_ and do_ is C function + int *flags = (int *)&PT_REGS_CCALL_PARM4(regs); + + return ksu_handle_faccessat(dfd, filename_user, mode, flags); +} + +static int sys_faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + int *dfd = (int *)&PT_REGS_PARM1(real_regs); + const char __user **filename_user = + (const char **)&PT_REGS_PARM2(real_regs); + int *mode = (int *)&PT_REGS_PARM3(real_regs); + + return ksu_handle_faccessat(dfd, filename_user, mode, NULL); +} + +__maybe_unused static int newfstatat_handler_pre(struct kprobe *p, + struct pt_regs *regs) +{ + int *dfd = (int *)&PT_REGS_PARM1(regs); + const char __user **filename_user = (const char **)&PT_REGS_PARM2(regs); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + // static int vfs_statx(int dfd, const char __user *filename, int flags, struct kstat *stat, u32 request_mask) + int *flags = (int *)&PT_REGS_PARM3(regs); +#else + // int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,int flag) + int *flags = (int *)&PT_REGS_CCALL_PARM4(regs); +#endif + + return ksu_handle_stat(dfd, filename_user, flags); +} + +static int sys_newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + int *dfd = (int *)&PT_REGS_PARM1(real_regs); + const char __user **filename_user = + (const char **)&PT_REGS_PARM2(real_regs); + int *flags = (int *)&PT_REGS_SYSCALL_PARM4(real_regs); + + return ksu_handle_stat(dfd, filename_user, flags); +} + +// https://elixir.bootlin.com/linux/v5.10.158/source/fs/exec.c#L1864 +static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + int *fd = (int *)&PT_REGS_PARM1(regs); + struct filename **filename_ptr = + (struct filename **)&PT_REGS_PARM2(regs); + + return ksu_handle_execveat_sucompat(fd, filename_ptr, NULL, NULL, NULL); +} + +static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + const char __user **filename_user = + (const char **)&PT_REGS_PARM1(real_regs); + + return ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL, NULL, + NULL); +} + +#if 1 +static struct kprobe faccessat_kp = { + .symbol_name = SYS_FACCESSAT_SYMBOL, + .pre_handler = sys_faccessat_handler_pre, +}; +#else +static struct kprobe faccessat_kp = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) + .symbol_name = "do_faccessat", +#else + .symbol_name = "sys_faccessat", +#endif + .pre_handler = faccessat_handler_pre, +}; +#endif + +#if 1 +static struct kprobe newfstatat_kp = { + .symbol_name = SYS_NEWFSTATAT_SYMBOL, + .pre_handler = sys_newfstatat_handler_pre, +}; +#else +static struct kprobe newfstatat_kp = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + .symbol_name = "vfs_statx", +#else + .symbol_name = "vfs_fstatat", +#endif + .pre_handler = newfstatat_handler_pre, +}; +#endif + +#if 1 +static struct kprobe execve_kp = { + .symbol_name = SYS_EXECVE_SYMBOL, + .pre_handler = sys_execve_handler_pre, +}; +#else +static struct kprobe execve_kp = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + .symbol_name = "do_execveat_common", +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + .symbol_name = "__do_execve_file", +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) + .symbol_name = "do_execveat_common", +#endif + .pre_handler = execve_handler_pre, +}; +#endif + +static int pts_unix98_lookup_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct inode *inode; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0) + struct file *file = (struct file *)PT_REGS_PARM2(regs); + inode = file->f_path.dentry->d_inode; +#else + inode = (struct inode *)PT_REGS_PARM2(regs); +#endif + + return ksu_handle_devpts(inode); +} + +static struct kprobe pts_unix98_lookup_kp = { .symbol_name = + "pts_unix98_lookup", + .pre_handler = + pts_unix98_lookup_pre }; + +#endif + +// sucompat: permited process can execute 'su' to gain root access. +void ksu_sucompat_init() +{ +#ifdef CONFIG_KPROBES + int ret; + ret = register_kprobe(&execve_kp); + pr_info("sucompat: execve_kp: %d\n", ret); + ret = register_kprobe(&newfstatat_kp); + pr_info("sucompat: newfstatat_kp: %d\n", ret); + ret = register_kprobe(&faccessat_kp); + pr_info("sucompat: faccessat_kp: %d\n", ret); + ret = register_kprobe(&pts_unix98_lookup_kp); + pr_info("sucompat: devpts_kp: %d\n", ret); +#endif +} + +void ksu_sucompat_exit() +{ +#ifdef CONFIG_KPROBES + unregister_kprobe(&execve_kp); + unregister_kprobe(&newfstatat_kp); + unregister_kprobe(&faccessat_kp); + unregister_kprobe(&pts_unix98_lookup_kp); +#endif +} diff --git a/KernelSU/kernel/throne_tracker.c b/KernelSU/kernel/throne_tracker.c new file mode 100644 index 000000000000..c709a6921a23 --- /dev/null +++ b/KernelSU/kernel/throne_tracker.c @@ -0,0 +1,382 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "allowlist.h" +#include "klog.h" // IWYU pragma: keep +#include "ksu.h" +#include "manager.h" +#include "throne_tracker.h" +#include "kernel_compat.h" + +uid_t ksu_manager_uid = KSU_INVALID_UID; + +#define SYSTEM_PACKAGES_LIST_PATH "/data/system/packages.list.tmp" + +struct uid_data { + struct list_head list; + u32 uid; + char package[KSU_MAX_PACKAGE_NAME]; +}; + +static int get_pkg_from_apk_path(char *pkg, const char *path) +{ + int len = strlen(path); + if (len >= KSU_MAX_PACKAGE_NAME || len < 1) + return -1; + + const char *last_slash = NULL; + const char *second_last_slash = NULL; + + int i; + for (i = len - 1; i >= 0; i--) { + if (path[i] == '/') { + if (!last_slash) { + last_slash = &path[i]; + } else { + second_last_slash = &path[i]; + break; + } + } + } + + if (!last_slash || !second_last_slash) + return -1; + + const char *last_hyphen = strchr(second_last_slash, '-'); + if (!last_hyphen || last_hyphen > last_slash) + return -1; + + int pkg_len = last_hyphen - second_last_slash - 1; + if (pkg_len >= KSU_MAX_PACKAGE_NAME || pkg_len <= 0) + return -1; + + // Copying the package name + strncpy(pkg, second_last_slash + 1, pkg_len); + pkg[pkg_len] = '\0'; + + return 0; +} + +static void crown_manager(const char *apk, struct list_head *uid_data) +{ + char pkg[KSU_MAX_PACKAGE_NAME]; + if (get_pkg_from_apk_path(pkg, apk) < 0) { + pr_err("Failed to get package name from apk path: %s\n", apk); + return; + } + + pr_info("manager pkg: %s\n", pkg); + +#ifdef KSU_MANAGER_PACKAGE + // pkg is `/` + if (strncmp(pkg, KSU_MANAGER_PACKAGE, sizeof(KSU_MANAGER_PACKAGE))) { + pr_info("manager package is inconsistent with kernel build: %s\n", + KSU_MANAGER_PACKAGE); + return; + } +#endif + struct list_head *list = (struct list_head *)uid_data; + struct uid_data *np; + + list_for_each_entry (np, list, list) { + if (strncmp(np->package, pkg, KSU_MAX_PACKAGE_NAME) == 0) { + pr_info("Crowning manager: %s(uid=%d)\n", pkg, np->uid); + ksu_set_manager_uid(np->uid); + break; + } + } +} + +#define DATA_PATH_LEN 384 // 384 is enough for /data/app//base.apk + +struct data_path { + char dirpath[DATA_PATH_LEN]; + int depth; + struct list_head list; +}; + +struct apk_path_hash { + unsigned int hash; + bool exists; + struct list_head list; +}; + +static struct list_head apk_path_hash_list = LIST_HEAD_INIT(apk_path_hash_list); + +struct my_dir_context { + struct dir_context ctx; + struct list_head *data_path_list; + char *parent_dir; + void *private_data; + int depth; + int *stop; +}; +// https://docs.kernel.org/filesystems/porting.html +// filldir_t (readdir callbacks) calling conventions have changed. Instead of returning 0 or -E... it returns bool now. false means "no more" (as -E... used to) and true - "keep going" (as 0 in old calling conventions). Rationale: callers never looked at specific -E... values anyway. -> iterate_shared() instances require no changes at all, all filldir_t ones in the tree converted. +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +#define FILLDIR_RETURN_TYPE bool +#define FILLDIR_ACTOR_CONTINUE true +#define FILLDIR_ACTOR_STOP false +#else +#define FILLDIR_RETURN_TYPE int +#define FILLDIR_ACTOR_CONTINUE 0 +#define FILLDIR_ACTOR_STOP -EINVAL +#endif + +FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name, + int namelen, loff_t off, u64 ino, + unsigned int d_type) +{ + struct my_dir_context *my_ctx = + container_of(ctx, struct my_dir_context, ctx); + char dirpath[DATA_PATH_LEN]; + + if (!my_ctx) { + pr_err("Invalid context\n"); + return FILLDIR_ACTOR_STOP; + } + if (my_ctx->stop && *my_ctx->stop) { + pr_info("Stop searching\n"); + return FILLDIR_ACTOR_STOP; + } + + if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen)) + return FILLDIR_ACTOR_CONTINUE; // Skip "." and ".." + + if (snprintf(dirpath, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir, + namelen, name) >= DATA_PATH_LEN) { + pr_err("Path too long: %s/%.*s\n", my_ctx->parent_dir, namelen, + name); + return FILLDIR_ACTOR_CONTINUE; + } + + if (d_type == DT_DIR && my_ctx->depth > 0 && + (my_ctx->stop && !*my_ctx->stop)) { + struct data_path *data = kmalloc(sizeof(struct data_path), GFP_ATOMIC); + + if (!data) { + pr_err("Failed to allocate memory for %s\n", dirpath); + return FILLDIR_ACTOR_CONTINUE; + } + + strscpy(data->dirpath, dirpath, DATA_PATH_LEN); + data->depth = my_ctx->depth - 1; + list_add_tail(&data->list, my_ctx->data_path_list); + } else { + if ((namelen == 8) && (strncmp(name, "base.apk", namelen) == 0)) { + struct apk_path_hash *pos, *n; +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) + unsigned int hash = full_name_hash(dirpath, strlen(dirpath)); +#else + unsigned int hash = full_name_hash(NULL, dirpath, strlen(dirpath)); +#endif + list_for_each_entry(pos, &apk_path_hash_list, list) { + if (hash == pos->hash) { + pos->exists = true; + return FILLDIR_ACTOR_CONTINUE; + } + } + + bool is_manager = is_manager_apk(dirpath); + pr_info("Found new base.apk at path: %s, is_manager: %d\n", + dirpath, is_manager); + if (is_manager) { + crown_manager(dirpath, my_ctx->private_data); + *my_ctx->stop = 1; + + // Manager found, clear APK cache 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); + apk_data->hash = hash; + apk_data->exists = true; + list_add_tail(&apk_data->list, &apk_path_hash_list); + } + } + } + + return FILLDIR_ACTOR_CONTINUE; +} + +void search_manager(const char *path, int depth, struct list_head *uid_data) +{ + int i, stop = 0; + struct list_head data_path_list; + INIT_LIST_HEAD(&data_path_list); + + // Initialize APK cache list + struct apk_path_hash *pos, *n; + list_for_each_entry(pos, &apk_path_hash_list, list) { + pos->exists = false; + } + + // First depth + struct data_path data; + strscpy(data.dirpath, path, DATA_PATH_LEN); + data.depth = depth; + list_add_tail(&data.list, &data_path_list); + + for (i = depth; i > 0; i--) { + struct data_path *pos, *n; + + list_for_each_entry_safe(pos, n, &data_path_list, list) { + struct my_dir_context ctx = { .ctx.actor = my_actor, + .data_path_list = &data_path_list, + .parent_dir = pos->dirpath, + .private_data = uid_data, + .depth = pos->depth, + .stop = &stop }; + struct file *file; + + if (!stop) { + file = ksu_filp_open_compat(pos->dirpath, O_RDONLY | O_NOFOLLOW, 0); + if (IS_ERR(file)) { + pr_err("Failed to open directory: %s, err: %ld\n", pos->dirpath, PTR_ERR(file)); + return; + } + + iterate_dir(file, &ctx.ctx); + filp_close(file, NULL); + } + + list_del(&pos->list); + if (pos != &data) + kfree(pos); + } + } + + // Remove stale cached APK entries + list_for_each_entry_safe(pos, n, &apk_path_hash_list, list) { + if (!pos->exists) { + list_del(&pos->list); + kfree(pos); + } + } +} + +static bool is_uid_exist(uid_t uid, char *package, void *data) +{ + struct list_head *list = (struct list_head *)data; + struct uid_data *np; + + bool exist = false; + list_for_each_entry (np, list, list) { + if (np->uid == uid % 100000 && + strncmp(np->package, package, KSU_MAX_PACKAGE_NAME) == 0) { + exist = true; + break; + } + } + return exist; +} + +void track_throne() +{ + struct file *fp = + ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0); + if (IS_ERR(fp)) { + pr_err("%s: open " SYSTEM_PACKAGES_LIST_PATH " failed: %ld\n", + __func__, PTR_ERR(fp)); + return; + } + + struct list_head uid_list; + INIT_LIST_HEAD(&uid_list); + + char chr = 0; + loff_t pos = 0; + loff_t line_start = 0; + char buf[KSU_MAX_PACKAGE_NAME]; + for (;;) { + ssize_t count = + ksu_kernel_read_compat(fp, &chr, sizeof(chr), &pos); + if (count != sizeof(chr)) + break; + if (chr != '\n') + continue; + + count = ksu_kernel_read_compat(fp, buf, sizeof(buf), + &line_start); + + struct uid_data *data = + kzalloc(sizeof(struct uid_data), GFP_ATOMIC); + if (!data) { + filp_close(fp, 0); + goto out; + } + + char *tmp = buf; + const char *delim = " "; + char *package = strsep(&tmp, delim); + char *uid = strsep(&tmp, delim); + if (!uid || !package) { + pr_err("update_uid: package or uid is NULL!\n"); + break; + } + + u32 res; + if (kstrtou32(uid, 10, &res)) { + pr_err("update_uid: uid parse err\n"); + break; + } + data->uid = res; + strncpy(data->package, package, KSU_MAX_PACKAGE_NAME); + list_add_tail(&data->list, &uid_list); + // reset line start + line_start = pos; + } + filp_close(fp, 0); + + // now update uid list + struct uid_data *np; + struct uid_data *n; + + // first, check if manager_uid exist! + bool manager_exist = false; + list_for_each_entry (np, &uid_list, list) { + // if manager is installed in work profile, the uid in packages.list is still equals main profile + // don't delete it in this case! + int manager_uid = ksu_get_manager_uid() % 100000; + if (np->uid == manager_uid) { + manager_exist = true; + break; + } + } + + if (!manager_exist) { + if (ksu_is_manager_uid_valid()) { + pr_info("manager is uninstalled, invalidate it!\n"); + ksu_invalidate_manager_uid(); + } + pr_info("Searching manager...\n"); + search_manager("/data/app", 2, &uid_list); + pr_info("Search manager finished\n"); + } + + // then prune the allowlist + ksu_prune_allowlist(is_uid_exist, &uid_list); +out: + // free uid_list + list_for_each_entry_safe (np, n, &uid_list, list) { + list_del(&np->list); + kfree(np); + } +} + +void ksu_throne_tracker_init() +{ + // nothing to do +} + +void ksu_throne_tracker_exit() +{ + // nothing to do +} diff --git a/KernelSU/kernel/throne_tracker.h b/KernelSU/kernel/throne_tracker.h new file mode 100644 index 000000000000..5d7f477003ac --- /dev/null +++ b/KernelSU/kernel/throne_tracker.h @@ -0,0 +1,10 @@ +#ifndef __KSU_H_UID_OBSERVER +#define __KSU_H_UID_OBSERVER + +void ksu_throne_tracker_init(); + +void ksu_throne_tracker_exit(); + +void track_throne(); + +#endif diff --git a/KernelSU/manager/.gitignore b/KernelSU/manager/.gitignore new file mode 100644 index 000000000000..dd42da43f547 --- /dev/null +++ b/KernelSU/manager/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +local.properties +.idea +.DS_Store +build +captures +.cxx +key.jks diff --git a/KernelSU/manager/app/.gitignore b/KernelSU/manager/app/.gitignore new file mode 100644 index 000000000000..dc5ca963c57a --- /dev/null +++ b/KernelSU/manager/app/.gitignore @@ -0,0 +1,2 @@ +/build +/release/ diff --git a/KernelSU/manager/app/build.gradle.kts b/KernelSU/manager/app/build.gradle.kts new file mode 100644 index 000000000000..1c85d8af7e8a --- /dev/null +++ b/KernelSU/manager/app/build.gradle.kts @@ -0,0 +1,116 @@ +import com.android.build.gradle.internal.api.BaseVariantOutputImpl + +plugins { + alias(libs.plugins.agp.app) + alias(libs.plugins.kotlin) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.ksp) + alias(libs.plugins.lsplugin.apksign) + id("kotlin-parcelize") +} + +val managerVersionCode: Int by rootProject.extra +val managerVersionName: String by rootProject.extra + +apksign { + storeFileProperty = "KEYSTORE_FILE" + storePasswordProperty = "KEYSTORE_PASSWORD" + keyAliasProperty = "KEY_ALIAS" + keyPasswordProperty = "KEY_PASSWORD" +} + +android { + namespace = "me.weishu.kernelsu" + + buildTypes { + release { + isMinifyEnabled = true + isShrinkResources = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + buildFeatures { + aidl = true + buildConfig = true + compose = true + } + + kotlinOptions { + jvmTarget = "21" + } + + packaging { + jniLibs { + useLegacyPackaging = true + } + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + + externalNativeBuild { + cmake { + path("src/main/cpp/CMakeLists.txt") + } + } + + applicationVariants.all { + outputs.forEach { + val output = it as BaseVariantOutputImpl + output.outputFileName = "KernelSU_${managerVersionName}_${managerVersionCode}-$name.apk" + } + + kotlin.sourceSets { + getByName(name) { + kotlin.srcDir("build/generated/ksp/$name/kotlin") + } + } + } +} + +dependencies { + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.navigation.compose) + + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.material.icons.extended) + implementation(libs.androidx.compose.material) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.ui.tooling.preview) + + debugImplementation(libs.androidx.compose.ui.test.manifest) + debugImplementation(libs.androidx.compose.ui.tooling) + + implementation(libs.androidx.lifecycle.runtime.compose) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.viewmodel.compose) + + implementation(libs.com.google.accompanist.drawablepainter) + implementation(libs.com.google.accompanist.navigation.animation) + implementation(libs.com.google.accompanist.systemuicontroller) + implementation(libs.com.google.accompanist.webview) + + implementation(libs.compose.destinations.animations.core) + ksp(libs.compose.destinations.ksp) + + implementation(libs.com.github.topjohnwu.libsu.core) + implementation(libs.com.github.topjohnwu.libsu.service) + implementation(libs.com.github.topjohnwu.libsu.io) + + implementation(libs.dev.rikka.rikkax.parcelablelist) + + implementation(libs.io.coil.kt.coil.compose) + + implementation(libs.kotlinx.coroutines.core) + + implementation(libs.me.zhanghai.android.appiconloader.coil) + + implementation(libs.sheet.compose.dialogs.core) + implementation(libs.sheet.compose.dialogs.list) + implementation(libs.sheet.compose.dialogs.input) + + implementation(libs.markdown) + implementation(libs.androidx.webkit) +} \ No newline at end of file diff --git a/KernelSU/manager/app/proguard-rules.pro b/KernelSU/manager/app/proguard-rules.pro new file mode 100644 index 000000000000..dcaf39ce70fe --- /dev/null +++ b/KernelSU/manager/app/proguard-rules.pro @@ -0,0 +1,9 @@ +-dontwarn org.bouncycastle.jsse.BCSSLParameters +-dontwarn org.bouncycastle.jsse.BCSSLSocket +-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider +-dontwarn org.conscrypt.Conscrypt$Version +-dontwarn org.conscrypt.Conscrypt +-dontwarn org.conscrypt.ConscryptHostnameVerifier +-dontwarn org.openjsse.javax.net.ssl.SSLParameters +-dontwarn org.openjsse.javax.net.ssl.SSLSocket +-dontwarn org.openjsse.net.ssl.OpenJSSE diff --git a/KernelSU/manager/app/src/main/AndroidManifest.xml b/KernelSU/manager/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..32c920ab4c3b --- /dev/null +++ b/KernelSU/manager/app/src/main/AndroidManifest.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/aidl/me/weishu/kernelsu/IKsuInterface.aidl b/KernelSU/manager/app/src/main/aidl/me/weishu/kernelsu/IKsuInterface.aidl new file mode 100644 index 000000000000..a6545566bb4f --- /dev/null +++ b/KernelSU/manager/app/src/main/aidl/me/weishu/kernelsu/IKsuInterface.aidl @@ -0,0 +1,9 @@ +// IKsuInterface.aidl +package me.weishu.kernelsu; + +import android.content.pm.PackageInfo; +import rikka.parcelablelist.ParcelableListSlice; + +interface IKsuInterface { + ParcelableListSlice getPackages(int flags); +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/cpp/CMakeLists.txt b/KernelSU/manager/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000000..cca26d5e4aef --- /dev/null +++ b/KernelSU/manager/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,18 @@ + +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. +cmake_minimum_required(VERSION 3.18.1) + +project("kernelsu") + +add_library(kernelsu + SHARED + jni.cc + ksu.cc + ) + +find_library(log-lib log) + +target_link_libraries(kernelsu ${log-lib}) \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/cpp/jni.cc b/KernelSU/manager/app/src/main/cpp/jni.cc new file mode 100644 index 000000000000..b0d28698742b --- /dev/null +++ b/KernelSU/manager/app/src/main/cpp/jni.cc @@ -0,0 +1,298 @@ +#include + +#include + +#include +#include + +#include "ksu.h" + +#define LOG_TAG "KernelSU" +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) + +extern "C" +JNIEXPORT jboolean JNICALL +Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) { + auto cpkg = env->GetStringUTFChars(pkg, nullptr); + auto result = become_manager(cpkg); + env->ReleaseStringUTFChars(pkg, cpkg); + return result; +} + +extern "C" +JNIEXPORT jint JNICALL +Java_me_weishu_kernelsu_Natives_getVersion(JNIEnv *env, jobject) { + return get_version(); +} + +extern "C" +JNIEXPORT jintArray JNICALL +Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jobject) { + int uids[1024]; + int size = 0; + bool result = get_allow_list(uids, &size); + LOGD("getAllowList: %d, size: %d", result, size); + if (result) { + auto array = env->NewIntArray(size); + env->SetIntArrayRegion(array, 0, size, uids); + return array; + } + return env->NewIntArray(0); +} + +extern "C" +JNIEXPORT jboolean JNICALL +Java_me_weishu_kernelsu_Natives_isSafeMode(JNIEnv *env, jclass clazz) { + return is_safe_mode(); +} + +extern "C" +JNIEXPORT jboolean JNICALL +Java_me_weishu_kernelsu_Natives_isLkmMode(JNIEnv *env, jclass clazz) { + return is_lkm_mode(); +} + +static void fillIntArray(JNIEnv *env, jobject list, int *data, int count) { + auto cls = env->GetObjectClass(list); + auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z"); + auto integerCls = env->FindClass("java/lang/Integer"); + auto constructor = env->GetMethodID(integerCls, "", "(I)V"); + for (int i = 0; i < count; ++i) { + auto integer = env->NewObject(integerCls, constructor, data[i]); + env->CallBooleanMethod(list, add, integer); + } +} + +static void addIntToList(JNIEnv *env, jobject list, int ele) { + auto cls = env->GetObjectClass(list); + auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z"); + auto integerCls = env->FindClass("java/lang/Integer"); + auto constructor = env->GetMethodID(integerCls, "", "(I)V"); + auto integer = env->NewObject(integerCls, constructor, ele); + env->CallBooleanMethod(list, add, integer); +} + +static uint64_t capListToBits(JNIEnv *env, jobject list) { + auto cls = env->GetObjectClass(list); + auto get = env->GetMethodID(cls, "get", "(I)Ljava/lang/Object;"); + auto size = env->GetMethodID(cls, "size", "()I"); + auto listSize = env->CallIntMethod(list, size); + auto integerCls = env->FindClass("java/lang/Integer"); + auto intValue = env->GetMethodID(integerCls, "intValue", "()I"); + uint64_t result = 0; + for (int i = 0; i < listSize; ++i) { + auto integer = env->CallObjectMethod(list, get, i); + int data = env->CallIntMethod(integer, intValue); + + if (cap_valid(data)) { + result |= (1ULL << data); + } + } + + return result; +} + +static int getListSize(JNIEnv *env, jobject list) { + auto cls = env->GetObjectClass(list); + auto size = env->GetMethodID(cls, "size", "()I"); + return env->CallIntMethod(list, size); +} + +static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) { + auto cls = env->GetObjectClass(list); + auto get = env->GetMethodID(cls, "get", "(I)Ljava/lang/Object;"); + auto integerCls = env->FindClass("java/lang/Integer"); + auto intValue = env->GetMethodID(integerCls, "intValue", "()I"); + for (int i = 0; i < count; ++i) { + auto integer = env->CallObjectMethod(list, get, i); + data[i] = env->CallIntMethod(integer, intValue); + } +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) { + if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) { + return nullptr; + } + + p_key_t key = {}; + auto cpkg = env->GetStringUTFChars(pkg, nullptr); + strcpy(key, cpkg); + env->ReleaseStringUTFChars(pkg, cpkg); + + app_profile profile = {}; + profile.version = KSU_APP_PROFILE_VER; + + strcpy(profile.key, key); + profile.current_uid = uid; + + bool useDefaultProfile = !get_app_profile(key, &profile); + + auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile"); + auto constructor = env->GetMethodID(cls, "", "()V"); + auto obj = env->NewObject(cls, constructor); + auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;"); + auto currentUidField = env->GetFieldID(cls, "currentUid", "I"); + auto allowSuField = env->GetFieldID(cls, "allowSu", "Z"); + + auto rootUseDefaultField = env->GetFieldID(cls, "rootUseDefault", "Z"); + auto rootTemplateField = env->GetFieldID(cls, "rootTemplate", "Ljava/lang/String;"); + + auto uidField = env->GetFieldID(cls, "uid", "I"); + auto gidField = env->GetFieldID(cls, "gid", "I"); + auto groupsField = env->GetFieldID(cls, "groups", "Ljava/util/List;"); + auto capabilitiesField = env->GetFieldID(cls, "capabilities", "Ljava/util/List;"); + auto domainField = env->GetFieldID(cls, "context", "Ljava/lang/String;"); + auto namespacesField = env->GetFieldID(cls, "namespace", "I"); + + auto nonRootUseDefaultField = env->GetFieldID(cls, "nonRootUseDefault", "Z"); + auto umountModulesField = env->GetFieldID(cls, "umountModules", "Z"); + + env->SetObjectField(obj, keyField, env->NewStringUTF(profile.key)); + env->SetIntField(obj, currentUidField, profile.current_uid); + + if (useDefaultProfile) { + // no profile found, so just use default profile: + // don't allow root and use default profile! + LOGD("use default profile for: %s, %d", key, uid); + + // allow_su = false + // non root use default = true + env->SetBooleanField(obj, allowSuField, false); + env->SetBooleanField(obj, nonRootUseDefaultField, true); + + return obj; + } + + auto allowSu = profile.allow_su; + + if (allowSu) { + env->SetBooleanField(obj, rootUseDefaultField, (jboolean) profile.rp_config.use_default); + if (strlen(profile.rp_config.template_name) > 0) { + env->SetObjectField(obj, rootTemplateField, + env->NewStringUTF(profile.rp_config.template_name)); + } + + env->SetIntField(obj, uidField, profile.rp_config.profile.uid); + env->SetIntField(obj, gidField, profile.rp_config.profile.gid); + + jobject groupList = env->GetObjectField(obj, groupsField); + int groupCount = profile.rp_config.profile.groups_count; + if (groupCount > KSU_MAX_GROUPS) { + LOGD("kernel group count too large: %d???", groupCount); + groupCount = KSU_MAX_GROUPS; + } + fillIntArray(env, groupList, profile.rp_config.profile.groups, groupCount); + + jobject capList = env->GetObjectField(obj, capabilitiesField); + for (int i = 0; i <= CAP_LAST_CAP; i++) { + if (profile.rp_config.profile.capabilities.effective & (1ULL << i)) { + addIntToList(env, capList, i); + } + } + + env->SetObjectField(obj, domainField, + env->NewStringUTF(profile.rp_config.profile.selinux_domain)); + env->SetIntField(obj, namespacesField, profile.rp_config.profile.namespaces); + env->SetBooleanField(obj, allowSuField, profile.allow_su); + } else { + env->SetBooleanField(obj, nonRootUseDefaultField, + (jboolean) profile.nrp_config.use_default); + env->SetBooleanField(obj, umountModulesField, profile.nrp_config.profile.umount_modules); + } + + return obj; +} + +extern "C" +JNIEXPORT jboolean JNICALL +Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) { + auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile"); + + auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;"); + auto currentUidField = env->GetFieldID(cls, "currentUid", "I"); + auto allowSuField = env->GetFieldID(cls, "allowSu", "Z"); + + auto rootUseDefaultField = env->GetFieldID(cls, "rootUseDefault", "Z"); + auto rootTemplateField = env->GetFieldID(cls, "rootTemplate", "Ljava/lang/String;"); + + auto uidField = env->GetFieldID(cls, "uid", "I"); + auto gidField = env->GetFieldID(cls, "gid", "I"); + auto groupsField = env->GetFieldID(cls, "groups", "Ljava/util/List;"); + auto capabilitiesField = env->GetFieldID(cls, "capabilities", "Ljava/util/List;"); + auto domainField = env->GetFieldID(cls, "context", "Ljava/lang/String;"); + auto namespacesField = env->GetFieldID(cls, "namespace", "I"); + + auto nonRootUseDefaultField = env->GetFieldID(cls, "nonRootUseDefault", "Z"); + auto umountModulesField = env->GetFieldID(cls, "umountModules", "Z"); + + auto key = env->GetObjectField(profile, keyField); + if (!key) { + return false; + } + if (env->GetStringLength((jstring) key) > KSU_MAX_PACKAGE_NAME) { + return false; + } + + auto cpkg = env->GetStringUTFChars((jstring) key, nullptr); + p_key_t p_key = {}; + strcpy(p_key, cpkg); + env->ReleaseStringUTFChars((jstring) key, cpkg); + + auto currentUid = env->GetIntField(profile, currentUidField); + + auto uid = env->GetIntField(profile, uidField); + auto gid = env->GetIntField(profile, gidField); + auto groups = env->GetObjectField(profile, groupsField); + auto capabilities = env->GetObjectField(profile, capabilitiesField); + auto domain = env->GetObjectField(profile, domainField); + auto allowSu = env->GetBooleanField(profile, allowSuField); + auto umountModules = env->GetBooleanField(profile, umountModulesField); + + app_profile p = {}; + p.version = KSU_APP_PROFILE_VER; + + strcpy(p.key, p_key); + p.allow_su = allowSu; + p.current_uid = currentUid; + + if (allowSu) { + p.rp_config.use_default = env->GetBooleanField(profile, rootUseDefaultField); + auto templateName = env->GetObjectField(profile, rootTemplateField); + if (templateName) { + auto ctemplateName = env->GetStringUTFChars((jstring) templateName, nullptr); + strcpy(p.rp_config.template_name, ctemplateName); + env->ReleaseStringUTFChars((jstring) templateName, ctemplateName); + } + + p.rp_config.profile.uid = uid; + p.rp_config.profile.gid = gid; + + int groups_count = getListSize(env, groups); + if (groups_count > KSU_MAX_GROUPS) { + LOGD("groups count too large: %d", groups_count); + return false; + } + p.rp_config.profile.groups_count = groups_count; + fillArrayWithList(env, groups, p.rp_config.profile.groups, groups_count); + + p.rp_config.profile.capabilities.effective = capListToBits(env, capabilities); + + auto cdomain = env->GetStringUTFChars((jstring) domain, nullptr); + strcpy(p.rp_config.profile.selinux_domain, cdomain); + env->ReleaseStringUTFChars((jstring) domain, cdomain); + + p.rp_config.profile.namespaces = env->GetIntField(profile, namespacesField); + } else { + p.nrp_config.use_default = env->GetBooleanField(profile, nonRootUseDefaultField); + p.nrp_config.profile.umount_modules = umountModules; + } + + return set_app_profile(&p); +} +extern "C" +JNIEXPORT jboolean JNICALL +Java_me_weishu_kernelsu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) { + return uid_should_umount(uid); +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/cpp/ksu.cc b/KernelSU/manager/app/src/main/cpp/ksu.cc new file mode 100644 index 000000000000..1e7981892f00 --- /dev/null +++ b/KernelSU/manager/app/src/main/cpp/ksu.cc @@ -0,0 +1,86 @@ +// +// Created by weishu on 2022/12/9. +// + +#include +#include +#include +#include +#include + +#include "ksu.h" + +#define KERNEL_SU_OPTION 0xDEADBEEF + +#define CMD_GRANT_ROOT 0 + +#define CMD_BECOME_MANAGER 1 +#define CMD_GET_VERSION 2 +#define CMD_ALLOW_SU 3 +#define CMD_DENY_SU 4 +#define CMD_GET_SU_LIST 5 +#define CMD_GET_DENY_LIST 6 +#define CMD_CHECK_SAFEMODE 9 + +#define CMD_GET_APP_PROFILE 10 +#define CMD_SET_APP_PROFILE 11 + +#define CMD_IS_UID_GRANTED_ROOT 12 +#define CMD_IS_UID_SHOULD_UMOUNT 13 + +static bool ksuctl(int cmd, void* arg1, void* arg2) { + int32_t result = 0; + prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result); + return result == KERNEL_SU_OPTION; +} + +bool become_manager(const char* pkg) { + char param[128]; + uid_t uid = getuid(); + uint32_t userId = uid / 100000; + if (userId == 0) { + sprintf(param, "/data/data/%s", pkg); + } else { + snprintf(param, sizeof(param), "/data/user/%d/%s", userId, pkg); + } + + return ksuctl(CMD_BECOME_MANAGER, param, nullptr); +} + +// cache the result to avoid unnecessary syscall +static bool is_lkm; +int get_version() { + int32_t version = -1; + int32_t lkm = 0; + ksuctl(CMD_GET_VERSION, &version, &lkm); + if (!is_lkm && lkm != 0) { + is_lkm = true; + } + return version; +} + +bool get_allow_list(int *uids, int *size) { + return ksuctl(CMD_GET_SU_LIST, uids, size); +} + +bool is_safe_mode() { + return ksuctl(CMD_CHECK_SAFEMODE, nullptr, nullptr); +} + +bool is_lkm_mode() { + // you should call get_version first! + return is_lkm; +} + +bool uid_should_umount(int uid) { + bool should; + return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, reinterpret_cast(uid), &should) && should; +} + +bool set_app_profile(const app_profile *profile) { + return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, nullptr); +} + +bool get_app_profile(p_key_t key, app_profile *profile) { + return ksuctl(CMD_GET_APP_PROFILE, (void*) profile, nullptr); +} diff --git a/KernelSU/manager/app/src/main/cpp/ksu.h b/KernelSU/manager/app/src/main/cpp/ksu.h new file mode 100644 index 000000000000..160a9d6faa0b --- /dev/null +++ b/KernelSU/manager/app/src/main/cpp/ksu.h @@ -0,0 +1,82 @@ +// +// Created by weishu on 2022/12/9. +// + +#ifndef KERNELSU_KSU_H +#define KERNELSU_KSU_H + +#include + +bool become_manager(const char *); + +int get_version(); + +bool get_allow_list(int *uids, int *size); + +bool uid_should_umount(int uid); + +bool is_safe_mode(); + +bool is_lkm_mode(); + +#define KSU_APP_PROFILE_VER 2 +#define KSU_MAX_PACKAGE_NAME 256 +// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups. +#define KSU_MAX_GROUPS 32 +#define KSU_SELINUX_DOMAIN 64 + +using p_key_t = char[KSU_MAX_PACKAGE_NAME]; + +struct root_profile { + int32_t uid; + int32_t gid; + + int32_t groups_count; + int32_t groups[KSU_MAX_GROUPS]; + + // kernel_cap_t is u32[2] for capabilities v3 + struct { + uint64_t effective; + uint64_t permitted; + uint64_t inheritable; + } capabilities; + + char selinux_domain[KSU_SELINUX_DOMAIN]; + + int32_t namespaces; +}; + +struct non_root_profile { + bool umount_modules; +}; + +struct app_profile { + // It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this. + uint32_t version; + + // this is usually the package of the app, but can be other value for special apps + char key[KSU_MAX_PACKAGE_NAME]; + int32_t current_uid; + bool allow_su; + + union { + struct { + bool use_default; + char template_name[KSU_MAX_PACKAGE_NAME]; + + struct root_profile profile; + } rp_config; + + struct { + bool use_default; + + struct non_root_profile profile; + } nrp_config; + }; +}; + +bool set_app_profile(const app_profile *profile); + +bool get_app_profile(p_key_t key, app_profile *profile); + +#endif //KERNELSU_KSU_H diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt new file mode 100644 index 000000000000..0503b42f0f43 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt @@ -0,0 +1,36 @@ +package me.weishu.kernelsu + +import android.app.Application +import coil.Coil +import coil.ImageLoader +import me.zhanghai.android.appiconloader.coil.AppIconFetcher +import me.zhanghai.android.appiconloader.coil.AppIconKeyer +import java.io.File + +lateinit var ksuApp: KernelSUApplication + +class KernelSUApplication : Application() { + + override fun onCreate() { + super.onCreate() + ksuApp = this + + val context = this + val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size) + Coil.setImageLoader( + ImageLoader.Builder(context) + .components { + add(AppIconKeyer()) + add(AppIconFetcher.Factory(iconSize, false, context)) + } + .build() + ) + + val webroot = File(dataDir, "webroot") + if (!webroot.exists()) { + webroot.mkdir() + } + } + + +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/Kernels.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/Kernels.kt new file mode 100644 index 000000000000..860e46fcb149 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/Kernels.kt @@ -0,0 +1,44 @@ +package me.weishu.kernelsu + +import android.system.Os + +/** + * @author weishu + * @date 2022/12/10. + */ + +data class KernelVersion(val major: Int, val patchLevel: Int, val subLevel: Int) { + override fun toString(): String { + return "$major.$patchLevel.$subLevel" + } + + fun isGKI(): Boolean { + + // kernel 6.x + if (major > 5) { + return true + } + + // kernel 5.10.x + if (major == 5) { + return patchLevel >= 10 + } + + return false + } +} + +fun parseKernelVersion(version: String): KernelVersion { + val find = "(\\d+)\\.(\\d+)\\.(\\d+)".toRegex().find(version) + return if (find != null) { + KernelVersion(find.groupValues[1].toInt(), find.groupValues[2].toInt(), find.groupValues[3].toInt()) + } else { + KernelVersion(-1, -1, -1) + } +} + +fun getKernelVersion(): KernelVersion { + Os.uname().release.let { + return parseKernelVersion(it) + } +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt new file mode 100644 index 000000000000..bb788293aa31 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt @@ -0,0 +1,117 @@ +package me.weishu.kernelsu + +import android.os.Parcelable +import androidx.annotation.Keep +import androidx.compose.runtime.Immutable +import kotlinx.parcelize.Parcelize + +/** + * @author weishu + * @date 2022/12/8. + */ +object Natives { + // minimal supported kernel version + // 10915: allowlist breaking change, add app profile + // 10931: app profile struct add 'version' field + // 10946: add capabilities + // 10977: change groups_count and groups to avoid overflow write + // 11071: Fix the issue of failing to set a custom SELinux type. + const val MINIMAL_SUPPORTED_KERNEL = 11071 + + // 11640: Support query working mode, LKM or GKI + // when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant. + const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648 + const val KERNEL_SU_DOMAIN = "u:r:su:s0" + + const val ROOT_UID = 0 + const val ROOT_GID = 0 + + init { + System.loadLibrary("kernelsu") + } + + // become root manager, return true if success. + external fun becomeManager(pkg: String?): Boolean + val version: Int + external get + + // get the uid list of allowed su processes. + val allowList: IntArray + external get + + val isSafeMode: Boolean + external get + + val isLkmMode: Boolean + external get + + external fun uidShouldUmount(uid: Int): Boolean + + /** + * Get the profile of the given package. + * @param key usually the package name + * @return return null if failed. + */ + external fun getAppProfile(key: String?, uid: Int): Profile + external fun setAppProfile(profile: Profile?): Boolean + + private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$" + private const val NOBODY_UID = 9999 + + fun setDefaultUmountModules(umountModules: Boolean): Boolean { + Profile( + NON_ROOT_DEFAULT_PROFILE_KEY, + NOBODY_UID, + false, + umountModules = umountModules + ).let { + return setAppProfile(it) + } + } + + fun isDefaultUmountModules(): Boolean { + getAppProfile(NON_ROOT_DEFAULT_PROFILE_KEY, NOBODY_UID).let { + return it.umountModules + } + } + + fun requireNewKernel(): Boolean { + return version < MINIMAL_SUPPORTED_KERNEL + } + + @Immutable + @Parcelize + @Keep + data class Profile( + // and there is a default profile for root and non-root + val name: String, + // current uid for the package, this is convivent for kernel to check + // if the package name doesn't match uid, then it should be invalidated. + val currentUid: Int = 0, + + // if this is true, kernel will grant root permission to this package + val allowSu: Boolean = false, + + // these are used for root profile + val rootUseDefault: Boolean = true, + val rootTemplate: String? = null, + val uid: Int = ROOT_UID, + val gid: Int = ROOT_GID, + val groups: List = mutableListOf(), + val capabilities: List = mutableListOf(), + val context: String = KERNEL_SU_DOMAIN, + val namespace: Int = Namespace.INHERITED.ordinal, + + val nonRootUseDefault: Boolean = true, + val umountModules: Boolean = true, + var rules: String = "", // this field is save in ksud!! + ) : Parcelable { + enum class Namespace { + INHERITED, + GLOBAL, + INDIVIDUAL, + } + + constructor() : this("") + } +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/profile/Capabilities.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/profile/Capabilities.kt new file mode 100644 index 000000000000..d52518c2b4e0 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/profile/Capabilities.kt @@ -0,0 +1,49 @@ +package me.weishu.kernelsu.profile + +/** + * @author weishu + * @date 2023/6/3. + */ +enum class Capabilities(val cap: Int, val display: String, val desc: String) { + CAP_CHOWN(0, "CHOWN", "Make arbitrary changes to file UIDs and GIDs (see chown(2))"), + CAP_DAC_OVERRIDE(1, "DAC_OVERRIDE", "Bypass file read, write, and execute permission checks"), + CAP_DAC_READ_SEARCH(2, "DAC_READ_SEARCH", "Bypass file read permission checks and directory read and execute permission checks"), + CAP_FOWNER(3, "FOWNER", "Bypass permission checks on operations that normally require the filesystem UID of the process to match the UID of the file (e.g., chmod(2), utime(2)), excluding those operations covered by CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH"), + CAP_FSETID(4, "FSETID", "Don’t clear set-user-ID and set-group-ID permission bits when a file is modified; set the set-group-ID bit for a file whose GID does not match the filesystem or any of the supplementary GIDs of the calling process"), + CAP_KILL(5, "KILL", "Bypass permission checks for sending signals (see kill(2))."), + CAP_SETGID(6, "SETGID", "Make arbitrary manipulations of process GIDs and supplementary GID list; allow setgid(2) manipulation of the caller’s effective and real group IDs"), + CAP_SETUID(7, "SETUID", "Make arbitrary manipulations of process UIDs (setuid(2), setreuid(2), setresuid(2), setfsuid(2)); allow changing the current process user IDs; allow changing of the current process group ID to any value in the system’s range of legal group IDs"), + CAP_SETPCAP(8, "SETPCAP", "If file capabilities are supported: grant or remove any capability in the caller’s permitted capability set to or from any other process. (This property supersedes the obsolete notion of giving a process all capabilities by granting all capabilities in its permitted set, and of removing all capabilities from a process by granting no capabilities in its permitted set. It does not permit any actions that were not permitted before.)"), + CAP_LINUX_IMMUTABLE(9, "LINUX_IMMUTABLE", "Set the FS_APPEND_FL and FS_IMMUTABLE_FL inode flags (see chattr(1))."), + CAP_NET_BIND_SERVICE(10, "NET_BIND_SERVICE", "Bind a socket to Internet domain"), + CAP_NET_BROADCAST(11, "NET_BROADCAST", "Make socket broadcasts, and listen to multicasts"), + CAP_NET_ADMIN(12, "NET_ADMIN", "Perform various network-related operations: interface configuration, administration of IP firewall, masquerading, and accounting, modify routing tables, bind to any address for transparent proxying, set type-of-service (TOS), clear driver statistics, set promiscuous mode, enabling multicasting, use setsockopt(2) to set the following socket options: SO_DEBUG, SO_MARK, SO_PRIORITY (for a priority outside the range 0 to 6), SO_RCVBUFFORCE, and SO_SNDBUFFORCE"), + CAP_NET_RAW(13, "NET_RAW", "Use RAW and PACKET sockets"), + CAP_IPC_LOCK(14, "IPC_LOCK", "Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2))"), + CAP_IPC_OWNER(15, "IPC_OWNER", "Bypass permission checks for operations on System V IPC objects"), + CAP_SYS_MODULE(16, "SYS_MODULE", "Load and unload kernel modules (see init_module(2) and delete_module(2)); in kernels before 2.6.25, this also granted rights for various other operations related to kernel modules"), + CAP_SYS_RAWIO(17, "SYS_RAWIO", "Perform I/O port operations (iopl(2) and ioperm(2)); access /proc/kcore"), + CAP_SYS_CHROOT(18, "SYS_CHROOT", "Use chroot(2)"), + CAP_SYS_PTRACE(19, "SYS_PTRACE", "Trace arbitrary processes using ptrace(2)"), + CAP_SYS_PACCT(20, "SYS_PACCT", "Use acct(2)"), + CAP_SYS_ADMIN(21, "SYS_ADMIN", "Perform a range of system administration operations including: quotactl(2), mount(2), umount(2), swapon(2), swapoff(2), sethostname(2), and setdomainname(2); set and modify process resource limits (setrlimit(2)); perform various network-related operations (e.g., setting privileged socket options, enabling multicasting, interface configuration); perform various IPC operations (e.g., SysV semaphores, POSIX message queues, System V shared memory); allow reboot and kexec_load(2); override /proc/sys kernel tunables; perform ptrace(2) PTRACE_SECCOMP_GET_FILTER operation; perform some tracing and debugging operations (see ptrace(2)); administer the lifetime of kernel tracepoints (tracefs(5)); perform the KEYCTL_CHOWN and KEYCTL_SETPERM keyctl(2) operations; perform the following keyctl(2) operations: KEYCTL_CAPABILITIES, KEYCTL_CAPSQUASH, and KEYCTL_PKEY_ OPERATIONS; set state for the Extensible Authentication Protocol (EAP) kernel module; and override the RLIMIT_NPROC resource limit; allow ioperm/iopl access to I/O ports"), + CAP_SYS_BOOT(22, "SYS_BOOT", "Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution"), + CAP_SYS_NICE(23, "SYS_NICE", "Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes; set real-time scheduling policies for calling process, and set scheduling policies and priorities for arbitrary processes (sched_setscheduler(2), sched_setparam(2)"), + CAP_SYS_RESOURCE(24, "SYS_RESOURCE", "Override resource Limits. Set resource limits (setrlimit(2), prlimit(2)), override quota limits (quota(2), quotactl(2)), override reserved space on ext2 filesystem (ext2_ioctl(2)), override size restrictions on IPC message queues (msg(2)) and system V shared memory segments (shmget(2)), and override the /proc/sys/fs/pipe-size-max limit"), + CAP_SYS_TIME(25, "SYS_TIME", "Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock"), + CAP_SYS_TTY_CONFIG(26, "SYS_TTY_CONFIG", "Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals"), + CAP_MKNOD(27, "MKNOD", "Create special files using mknod(2)"), + CAP_LEASE(28, "LEASE", "Establish leases on arbitrary files (see fcntl(2))"), + CAP_AUDIT_WRITE(29, "AUDIT_WRITE", "Write records to kernel auditing log"), + CAP_AUDIT_CONTROL(30, "AUDIT_CONTROL", "Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules"), + CAP_SETFCAP(31, "SETFCAP", "If file capabilities are supported: grant or remove any capability in any capability set to any file"), + CAP_MAC_OVERRIDE(32, "MAC_OVERRIDE", "Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM)"), + CAP_MAC_ADMIN(33, "MAC_ADMIN", "Allow MAC configuration or state changes. Implemented for the Smack LSM"), + CAP_SYSLOG(34, "SYSLOG", "Perform privileged syslog(2) operations. See syslog(2) for information on which operations require privilege"), + CAP_WAKE_ALARM(35, "WAKE_ALARM", "Trigger something that will wake up the system"), + CAP_BLOCK_SUSPEND(36, "BLOCK_SUSPEND", "Employ features that can block system suspend"), + CAP_AUDIT_READ(37, "AUDIT_READ", "Allow reading the audit log via a multicast netlink socket"), + CAP_PERFMON(38, "PERFMON", "Allow performance monitoring via perf_event_open(2)"), + CAP_BPF(39, "BPF", "Allow BPF operations via bpf(2)"), + CAP_CHECKPOINT_RESTORE(40, "CHECKPOINT_RESTORE", "Allow processes to be checkpointed via checkpoint/restore in user namespace(2)"), +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/profile/Groups.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/profile/Groups.kt new file mode 100644 index 000000000000..2ddb94dc91a1 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/profile/Groups.kt @@ -0,0 +1,130 @@ +package me.weishu.kernelsu.profile + +/** + * https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h + * @author weishu + * @date 2023/6/3. + */ +enum class Groups(val gid: Int, val display: String, val desc: String) { + ROOT(0, "root", "traditional unix root user"), + DAEMON(1, "daemon", "Traditional unix daemon owner."), + BIN(2, "bin", "Traditional unix binaries owner."), + SYS(3, "sys", "A group with the same gid on Linux/macOS/Android."), + SYSTEM(1000, "system", "system server"), + RADIO(1001, "radio", "telephony subsystem, RIL"), + BLUETOOTH(1002, "bluetooth", "bluetooth subsystem"), + GRAPHICS(1003, "graphics", "graphics devices"), + INPUT(1004, "input", "input devices"), + AUDIO(1005, "audio", "audio devices"), + CAMERA(1006, "camera", "camera devices"), + LOG(1007, "log", "log devices"), + COMPASS(1008, "compass", "compass device"), + MOUNT(1009, "mount", "mountd socket"), + WIFI(1010, "wifi", "wifi subsystem"), + ADB(1011, "adb", "android debug bridge (adbd)"), + INSTALL(1012, "install", "group for installing packages"), + MEDIA(1013, "media", "mediaserver process"), + DHCP(1014, "dhcp", "dhcp client"), + SDCARD_RW(1015, "sdcard_rw", "external storage write access"), + VPN(1016, "vpn", "vpn system"), + KEYSTORE(1017, "keystore", "keystore subsystem"), + USB(1018, "usb", "USB devices"), + DRM(1019, "drm", "DRM server"), + MDNSR(1020, "mdnsr", "MulticastDNSResponder (service discovery)"), + GPS(1021, "gps", "GPS daemon"), + UNUSED1(1022, "unused1", "deprecated, DO NOT USE"), + MEDIA_RW(1023, "media_rw", "internal media storage write access"), + MTP(1024, "mtp", "MTP USB driver access"), + UNUSED2(1025, "unused2", "deprecated, DO NOT USE"), + DRMRPC(1026, "drmrpc", "group for drm rpc"), + NFC(1027, "nfc", "nfc subsystem"), + SDCARD_R(1028, "sdcard_r", "external storage read access"), + CLAT(1029, "clat", "clat part of nat464"), + LOOP_RADIO(1030, "loop_radio", "loop radio devices"), + MEDIA_DRM(1031, "media_drm", "MediaDrm plugins"), + PACKAGE_INFO(1032, "package_info", "access to installed package details"), + SDCARD_PICS(1033, "sdcard_pics", "external storage photos access"), + SDCARD_AV(1034, "sdcard_av", "external storage audio/video access"), + SDCARD_ALL(1035, "sdcard_all", "access all users external storage"), + LOGD(1036, "logd", "log daemon"), + SHARED_RELRO(1037, "shared_relro", "creator of shared GNU RELRO files"), + DBUS(1038, "dbus", "dbus-daemon IPC broker process"), + TLSDATE(1039, "tlsdate", "tlsdate unprivileged user"), + MEDIA_EX(1040, "media_ex", "mediaextractor process"), + AUDIOSERVER(1041, "audioserver", "audioserver process"), + METRICS_COLL(1042, "metrics_coll", "metrics_collector process"), + METRICSD(1043, "metricsd", "metricsd process"), + WEBSERV(1044, "webserv", "webservd process"), + DEBUGGERD(1045, "debuggerd", "debuggerd unprivileged user"), + MEDIA_CODEC(1046, "media_codec", "media_codec process"), + CAMERASERVER(1047, "cameraserver", "cameraserver process"), + FIREWALL(1048, "firewall", "firewall process"), + TRUNKS(1049, "trunks", "trunksd process"), + NVRAM(1050, "nvram", "nvram daemon"), + DNS(1051, "dns", "DNS resolution daemon (system: netd)"), + DNS_TETHER(1052, "dns_tether", "DNS resolution daemon (tether: dnsmasq)"), + WEBVIEW_ZYGOTE(1053, "webview_zygote", "WebView zygote process"), + VEHICLE_NETWORK(1054, "vehicle_network", "Vehicle network service"), + MEDIA_AUDIO(1055, "media_audio", "GID for audio files on internal media storage"), + MEDIA_VIDEO(1056, "media_video", "GID for video files on internal media storage"), + MEDIA_IMAGE(1057, "media_image", "GID for image files on internal media storage"), + TOMBSTONED(1058, "tombstoned", "tombstoned user"), + MEDIA_OBB(1059, "media_obb", "GID for OBB files on internal media storage"), + ESE(1060, "ese", "embedded secure element (eSE) subsystem"), + OTA_UPDATE(1061, "ota_update", "resource tracking UID for OTA updates"), + AUTOMOTIVE_EVS(1062, "automotive_evs", "Automotive rear and surround view system"), + LOWPAN(1063, "lowpan", "LoWPAN subsystem"), + HSM(1064, "lowpan", "hardware security module subsystem"), + RESERVED_DISK(1065, "reserved_disk", "GID that has access to reserved disk space"), + STATSD(1066, "statsd", "statsd daemon"), + INCIDENTD(1067, "incidentd", "incidentd daemon"), + SECURE_ELEMENT(1068, "secure_element", "secure element subsystem"), + LMKD(1069, "lmkd", "low memory killer daemon"), + LLKD(1070, "llkd", "live lock daemon"), + IORAPD(1071, "iorapd", "input/output readahead and pin daemon"), + GPU_SERVICE(1072, "gpu_service", "GPU service daemon"), + NETWORK_STACK(1073, "network_stack", "network stack service"), + GSID(1074, "GSID", "GSI service daemon"), + FSVERITY_CERT(1075, "fsverity_cert", "fs-verity key ownership in keystore"), + CREDSTORE(1076, "credstore", "identity credential manager service"), + EXTERNAL_STORAGE(1077, "external_storage", "Full external storage access including USB OTG volumes"), + EXT_DATA_RW(1078, "ext_data_rw", "GID for app-private data directories on external storage"), + EXT_OBB_RW(1079, "ext_obb_rw", "GID for OBB directories on external storage"), + CONTEXT_HUB(1080, "context_hub", "GID for access to the Context Hub"), + VIRTUALIZATIONSERVICE(1081, "virtualizationservice", "VirtualizationService daemon"), + ARTD(1082, "artd", "ART Service daemon"), + UWB(1083, "uwb", "UWB subsystem"), + THREAD_NETWORK(1084, "thread_network", "Thread Network subsystem"), + DICED(1085, "diced", "Android's DICE daemon"), + DMESGD(1086, "dmesgd", "dmesg parsing daemon for kernel report collection"), + JC_WEAVER(1087, "jc_weaver", "Javacard Weaver HAL - to manage omapi ARA rules"), + JC_STRONGBOX(1088, "jc_strongbox", "Javacard Strongbox HAL - to manage omapi ARA rules"), + JC_IDENTITYCRED(1089, "jc_identitycred", "Javacard Identity Cred HAL - to manage omapi ARA rules"), + SDK_SANDBOX(1090, "sdk_sandbox", "SDK sandbox virtual UID"), + SECURITY_LOG_WRITER(1091, "security_log_writer", "write to security log"), + PRNG_SEEDER(1092, "prng_seeder", "PRNG seeder daemon"), + + SHELL(2000, "shell", "adb and debug shell user"), + CACHE(2001, "cache", "cache access"), + DIAG(2002, "diag", "access to diagnostic resources"), + + /* The 3000 series are intended for use as supplemental group id's only. + * They indicate special Android capabilities that the kernel is aware of. */ + NET_BT_ADMIN(3001, "net_bt_admin", "bluetooth: create any socket"), + NET_BT(3002, "net_bt", "bluetooth: create sco, rfcomm or l2cap sockets"), + INET(3003, "inet", "can create AF_INET and AF_INET6 sockets"), + NET_RAW(3004, "net_raw", "can create raw INET sockets"), + NET_ADMIN(3005, "net_admin", "can configure interfaces and routing tables."), + NET_BW_STATS(3006, "net_bw_stats", "read bandwidth statistics"), + NET_BW_ACCT(3007, "net_bw_acct", "change bandwidth statistics accounting"), + NET_BT_STACK(3008, "net_bt_stack", "access to various bluetooth management functions"), + READPROC(3009, "readproc", "Allow /proc read access"), + WAKELOCK(3010, "wakelock", "Allow system wakelock read/write access"), + UHID(3011, "uhid", "Allow read/write to /dev/uhid node"), + READTRACEFS(3012, "readtracefs", "Allow tracefs read"), + + EVERYBODY(9997, "everybody", "Shared external storage read/write"), + MISC(9998, "misc", "Access to misc storage"), + NOBODY(9999, "nobody", "Reserved"), + APP(10000, "app", "Access to app data"), +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/KsuService.java b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/KsuService.java new file mode 100644 index 000000000000..2ebcc786391d --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/KsuService.java @@ -0,0 +1,77 @@ +package me.weishu.kernelsu.ui; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.IBinder; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.topjohnwu.superuser.ipc.RootService; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import me.weishu.kernelsu.IKsuInterface; +import rikka.parcelablelist.ParcelableListSlice; + +/** + * @author weishu + * @date 2023/4/18. + */ + +public class KsuService extends RootService { + + private static final String TAG = "KsuService"; + + class Stub extends IKsuInterface.Stub { + @Override + public ParcelableListSlice getPackages(int flags) { + List list = getInstalledPackagesAll(flags); + Log.i(TAG, "getPackages: " + list.size()); + return new ParcelableListSlice<>(list); + } + } + + @Override + public IBinder onBind(@NonNull Intent intent) { + return new Stub(); + } + + List getUserIds() { + List result = new ArrayList<>(); + UserManager um = (UserManager) getSystemService(Context.USER_SERVICE); + List userProfiles = um.getUserProfiles(); + for (UserHandle userProfile : userProfiles) { + int userId = userProfile.hashCode(); + result.add(userProfile.hashCode()); + } + return result; + } + + ArrayList getInstalledPackagesAll(int flags) { + ArrayList packages = new ArrayList<>(); + for (Integer userId : getUserIds()) { + Log.i(TAG, "getInstalledPackagesAll: " + userId); + packages.addAll(getInstalledPackagesAsUser(flags, userId)); + } + return packages; + } + + List getInstalledPackagesAsUser(int flags, int userId) { + try { + PackageManager pm = getPackageManager(); + Method getInstalledPackagesAsUser = pm.getClass().getDeclaredMethod("getInstalledPackagesAsUser", int.class, int.class); + return (List) getInstalledPackagesAsUser.invoke(pm, flags, userId); + } catch (Throwable e) { + Log.e(TAG, "err", e); + } + + return new ArrayList<>(); + } +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/MainActivity.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/MainActivity.kt new file mode 100644 index 000000000000..042155fbee62 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/MainActivity.kt @@ -0,0 +1,97 @@ +package me.weishu.kernelsu.ui + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import com.ramcosta.composedestinations.DestinationsNavHost +import com.ramcosta.composedestinations.navigation.popBackStack +import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.ksuApp +import me.weishu.kernelsu.ui.screen.BottomBarDestination +import me.weishu.kernelsu.ui.screen.NavGraphs +import me.weishu.kernelsu.ui.theme.KernelSUTheme +import me.weishu.kernelsu.ui.util.LocalSnackbarHost +import me.weishu.kernelsu.ui.util.rootAvailable + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + KernelSUTheme { + val navController = rememberNavController() + val snackbarHostState = remember { SnackbarHostState() } + Scaffold( + bottomBar = { BottomBar(navController) }, + snackbarHost = { SnackbarHost(snackbarHostState) } + ) { innerPadding -> + CompositionLocalProvider( + LocalSnackbarHost provides snackbarHostState, + ) { + DestinationsNavHost( + modifier = Modifier.padding(innerPadding), + navGraph = NavGraphs.root, + navController = navController + ) + } + } + } + } + } +} + +@Composable +private fun BottomBar(navController: NavHostController) { + val isManager = Natives.becomeManager(ksuApp.packageName) + val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable() + NavigationBar(tonalElevation = 8.dp) { + BottomBarDestination.entries.forEach { destination -> + if (!fullFeatured && destination.rootRequired) return@forEach + val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction) + NavigationBarItem( + selected = isCurrentDestOnBackStack, + onClick = { + if (isCurrentDestOnBackStack) { + navController.popBackStack(destination.direction, false) + } + + navController.navigate(destination.direction.route) { + popUpTo(NavGraphs.root.route) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + }, + icon = { + if (isCurrentDestOnBackStack) { + Icon(destination.iconSelected, stringResource(destination.label)) + } else { + Icon(destination.iconNotSelected, stringResource(destination.label)) + } + }, + label = { Text(stringResource(destination.label)) }, + alwaysShowLabel = false + ) + } + } +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AboutCard.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AboutCard.kt new file mode 100644 index 000000000000..0807d0521dfe --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AboutCard.kt @@ -0,0 +1,123 @@ +package me.weishu.kernelsu.ui.component + +import android.text.method.LinkMovementMethod +import android.widget.TextView +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.window.Dialog +import androidx.core.content.res.ResourcesCompat +import androidx.core.text.HtmlCompat +import com.google.accompanist.drawablepainter.rememberDrawablePainter +import me.weishu.kernelsu.BuildConfig +import me.weishu.kernelsu.R + +@Preview +@Composable +fun AboutCard() { + ElevatedCard( + modifier = Modifier + .fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp) + ) { + AboutCardContent() + } + } +} + +@Composable +fun AboutDialog(dismiss: () -> Unit) { + Dialog(onDismissRequest = { dismiss() }) { + AboutCard() + } +} + +@Composable +private fun AboutCardContent() { + Column( + modifier = Modifier + .fillMaxWidth() + ) { + val drawable = ResourcesCompat.getDrawable( + LocalContext.current.resources, + R.mipmap.ic_launcher, + LocalContext.current.theme + ) + + Row { + Image( + painter = rememberDrawablePainter(drawable), + contentDescription = "icon", + modifier = Modifier.size(40.dp) + ) + + Spacer(modifier = Modifier.width(12.dp)) + + Column { + + Text( + stringResource(id = R.string.app_name), + style = MaterialTheme.typography.titleSmall, + fontSize = 18.sp + ) + Text( + BuildConfig.VERSION_NAME, + style = MaterialTheme.typography.bodySmall, + fontSize = 14.sp + ) + + Spacer(modifier = Modifier.height(8.dp)) + + HtmlText( + html = stringResource( + id = R.string.about_source_code, + "GitHub", + "Telegram" + ) + ) + } + } + } +} + +@Composable +fun HtmlText(html: String, modifier: Modifier = Modifier) { + val contentColor = LocalContentColor.current + AndroidView( + modifier = modifier, + factory = { context -> + TextView(context).also { + it.movementMethod = LinkMovementMethod.getInstance() + } + }, + update = { + it.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT) + it.setTextColor(contentColor.toArgb()) + } + ) +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/Dialog.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/Dialog.kt new file mode 100644 index 000000000000..e2c3fa453e0d --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/Dialog.kt @@ -0,0 +1,450 @@ +package me.weishu.kernelsu.ui.component + +import android.graphics.text.LineBreaker +import android.os.Parcelable +import android.text.Layout +import android.text.method.LinkMovementMethod +import android.util.Log +import android.view.ViewGroup +import android.widget.TextView +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import io.noties.markwon.Markwon +import io.noties.markwon.utils.NoCopySpannableFactory +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.onEach +import kotlinx.parcelize.Parcelize +import kotlin.coroutines.resume + +private const val TAG = "DialogComponent" + +interface ConfirmDialogVisuals : Parcelable { + val title: String + val content: String + val isMarkdown: Boolean + val confirm: String? + val dismiss: String? +} + +@Parcelize +private data class ConfirmDialogVisualsImpl( + override val title: String, + override val content: String, + override val isMarkdown: Boolean, + override val confirm: String?, + override val dismiss: String?, +) : ConfirmDialogVisuals { + companion object { + val Empty: ConfirmDialogVisuals = ConfirmDialogVisualsImpl("", "", false, null, null) + } +} + +interface DialogHandle { + val isShown: Boolean + val dialogType: String + fun show() + fun hide() +} + +interface LoadingDialogHandle : DialogHandle { + suspend fun withLoading(block: suspend () -> R): R + fun showLoading() +} + +sealed interface ConfirmResult { + object Confirmed : ConfirmResult + object Canceled : ConfirmResult +} + +interface ConfirmDialogHandle : DialogHandle { + val visuals: ConfirmDialogVisuals + + fun showConfirm( + title: String, + content: String, + markdown: Boolean = false, + confirm: String? = null, + dismiss: String? = null + ) + + suspend fun awaitConfirm( + title: String, + content: String, + markdown: Boolean = false, + confirm: String? = null, + dismiss: String? = null + ): ConfirmResult +} + +private abstract class DialogHandleBase( + protected val visible: MutableState, + protected val coroutineScope: CoroutineScope +) : DialogHandle { + override val isShown: Boolean + get() = visible.value + + override fun show() { + coroutineScope.launch { + visible.value = true + } + } + + final override fun hide() { + coroutineScope.launch { + visible.value = false + } + } + + override fun toString(): String { + return dialogType + } +} + +private class LoadingDialogHandleImpl( + visible: MutableState, + coroutineScope: CoroutineScope +) : LoadingDialogHandle, DialogHandleBase(visible, coroutineScope) { + override suspend fun withLoading(block: suspend () -> R): R { + return coroutineScope.async { + try { + visible.value = true + block() + } finally { + visible.value = false + } + }.await() + } + + override fun showLoading() { + show() + } + + override val dialogType: String get() = "LoadingDialog" +} + +typealias NullableCallback = (() -> Unit)? + +interface ConfirmCallback { + + val onConfirm: NullableCallback + + val onDismiss: NullableCallback + + val isEmpty: Boolean get() = onConfirm == null && onDismiss == null + + companion object { + operator fun invoke(onConfirmProvider: () -> NullableCallback, onDismissProvider: () -> NullableCallback): ConfirmCallback { + return object : ConfirmCallback { + override val onConfirm: NullableCallback + get() = onConfirmProvider() + override val onDismiss: NullableCallback + get() = onDismissProvider() + } + } + } +} + +private class ConfirmDialogHandleImpl( + visible: MutableState, + coroutineScope: CoroutineScope, + callback: ConfirmCallback, + override var visuals: ConfirmDialogVisuals = ConfirmDialogVisualsImpl.Empty, + private val resultFlow: ReceiveChannel +) : ConfirmDialogHandle, DialogHandleBase(visible, coroutineScope) { + private class ResultCollector( + private val callback: ConfirmCallback + ) : FlowCollector { + fun handleResult(result: ConfirmResult) { + Log.d(TAG, "handleResult: ${result.javaClass.simpleName}") + when (result) { + ConfirmResult.Confirmed -> onConfirm() + ConfirmResult.Canceled -> onDismiss() + } + } + + fun onConfirm() { + callback.onConfirm?.invoke() + } + + fun onDismiss() { + callback.onDismiss?.invoke() + } + + override suspend fun emit(value: ConfirmResult) { + handleResult(value) + } + } + + private val resultCollector = ResultCollector(callback) + + private var awaitContinuation: CancellableContinuation? = null + + private val isCallbackEmpty = callback.isEmpty + + init { + coroutineScope.launch { + resultFlow + .consumeAsFlow() + .onEach { result -> + awaitContinuation?.let { + awaitContinuation = null + if (it.isActive) { + it.resume(result) + } + } + } + .onEach { hide() } + .collect(resultCollector) + } + } + + private suspend fun awaitResult(): ConfirmResult { + return suspendCancellableCoroutine { + awaitContinuation = it.apply { + if (isCallbackEmpty) { + invokeOnCancellation { + visible.value = false + } + } + } + } + } + + fun updateVisuals(visuals: ConfirmDialogVisuals) { + this.visuals = visuals + } + + override fun show() { + if (visuals !== ConfirmDialogVisualsImpl.Empty) { + super.show() + } else { + throw UnsupportedOperationException("can't show confirm dialog with the Empty visuals") + } + } + + override fun showConfirm( + title: String, + content: String, + markdown: Boolean, + confirm: String?, + dismiss: String? + ) { + coroutineScope.launch { + updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss)) + show() + } + } + + override suspend fun awaitConfirm( + title: String, + content: String, + markdown: Boolean, + confirm: String?, + dismiss: String? + ): ConfirmResult { + coroutineScope.launch { + updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss)) + show() + } + return awaitResult() + } + + override val dialogType: String get() = "ConfirmDialog" + + override fun toString(): String { + return "${super.toString()}(visuals: $visuals)" + } + + companion object { + fun Saver( + visible: MutableState, + coroutineScope: CoroutineScope, + callback: ConfirmCallback, + resultChannel: ReceiveChannel + ) = Saver( + save = { + it.visuals + }, + restore = { + Log.d(TAG, "ConfirmDialog restore, visuals: $it") + ConfirmDialogHandleImpl(visible, coroutineScope, callback, it, resultChannel) + } + ) + } +} + +private class CustomDialogHandleImpl( + visible: MutableState, + coroutineScope: CoroutineScope +) : DialogHandleBase(visible, coroutineScope) { + override val dialogType: String get() = "CustomDialog" +} + +@Composable +fun rememberLoadingDialog(): LoadingDialogHandle { + val visible = remember { + mutableStateOf(false) + } + val coroutineScope = rememberCoroutineScope() + + if (visible.value) { + LoadingDialog() + } + + return remember { + LoadingDialogHandleImpl(visible, coroutineScope) + } +} + +@Composable +private fun rememberConfirmDialog(visuals: ConfirmDialogVisuals, callback: ConfirmCallback): ConfirmDialogHandle { + val visible = rememberSaveable { + mutableStateOf(false) + } + val coroutineScope = rememberCoroutineScope() + val resultChannel = remember { + Channel() + } + + val handle = rememberSaveable( + saver = ConfirmDialogHandleImpl.Saver(visible, coroutineScope, callback, resultChannel), + init = { + ConfirmDialogHandleImpl(visible, coroutineScope, callback, visuals, resultChannel) + } + ) + + if (visible.value) { + ConfirmDialog( + handle.visuals, + confirm = { coroutineScope.launch { resultChannel.send(ConfirmResult.Confirmed) } }, + dismiss = { coroutineScope.launch { resultChannel.send(ConfirmResult.Canceled) } } + ) + } + + return handle +} + +@Composable +fun rememberConfirmCallback(onConfirm: NullableCallback, onDismiss: NullableCallback): ConfirmCallback { + val currentOnConfirm by rememberUpdatedState(newValue = onConfirm) + val currentOnDismiss by rememberUpdatedState(newValue = onDismiss) + return remember { + ConfirmCallback({ currentOnConfirm }, { currentOnDismiss }) + } +} + +@Composable +fun rememberConfirmDialog(onConfirm: NullableCallback = null, onDismiss: NullableCallback = null): ConfirmDialogHandle { + return rememberConfirmDialog(rememberConfirmCallback(onConfirm, onDismiss)) +} + +@Composable +fun rememberConfirmDialog(callback: ConfirmCallback): ConfirmDialogHandle { + return rememberConfirmDialog(ConfirmDialogVisualsImpl.Empty, callback) +} + +@Composable +fun rememberCustomDialog(composable: @Composable (dismiss: () -> Unit) -> Unit): DialogHandle { + val visible = rememberSaveable { + mutableStateOf(false) + } + val coroutineScope = rememberCoroutineScope() + if (visible.value) { + composable { visible.value = false } + } + return remember { + CustomDialogHandleImpl(visible, coroutineScope) + } +} + +@Composable +private fun LoadingDialog() { + Dialog( + onDismissRequest = {}, + properties = DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false) + ) { + Surface( + modifier = Modifier.size(100.dp), shape = RoundedCornerShape(8.dp) + ) { + Box( + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator() + } + } + } +} + +@Composable +private fun ConfirmDialog(visuals: ConfirmDialogVisuals, confirm: () -> Unit, dismiss: () -> Unit) { + AlertDialog( + onDismissRequest = { + dismiss() + }, + title = { + Text(text = visuals.title) + }, + text = { + if (visuals.isMarkdown) { + MarkdownContent(content = visuals.content) + } else { + Text(text = visuals.content) + } + }, + confirmButton = { + TextButton(onClick = confirm) { + Text(text = visuals.confirm ?: stringResource(id = android.R.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = dismiss) { + Text(text = visuals.dismiss ?: stringResource(id = android.R.string.cancel)) + } + }, + ) +} + +@Composable +private fun MarkdownContent(content: String) { + val contentColor = LocalContentColor.current + + AndroidView( + factory = { context -> + TextView(context).apply { + movementMethod = LinkMovementMethod.getInstance() + setSpannableFactory(NoCopySpannableFactory.getInstance()) + breakStrategy = LineBreaker.BREAK_STRATEGY_SIMPLE + hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + }, + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + update = { + Markwon.create(it.context).setMarkdown(it, content) + it.setTextColor(contentColor.toArgb()) + } + ) +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/KeyEventBlocker.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/KeyEventBlocker.kt new file mode 100644 index 000000000000..b3268131268a --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/KeyEventBlocker.kt @@ -0,0 +1,28 @@ +package me.weishu.kernelsu.ui.component + +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.onKeyEvent + +@Composable +fun KeyEventBlocker(predicate: (KeyEvent) -> Boolean) { + val requester = remember { FocusRequester() } + Box( + Modifier + .onKeyEvent { + predicate(it) + } + .focusRequester(requester) + .focusable() + ) + LaunchedEffect(Unit) { + requester.requestFocus() + } +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/SearchBar.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/SearchBar.kt new file mode 100644 index 000000000000..8195cdddd40b --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/SearchBar.kt @@ -0,0 +1,149 @@ +package me.weishu.kernelsu.ui.component + +import android.util.Log +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +private const val TAG = "SearchBar" + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SearchAppBar( + title: @Composable () -> Unit, + searchText: String, + onSearchTextChange: (String) -> Unit, + onClearClick: () -> Unit, + onBackClick: (() -> Unit)? = null, + onConfirm: (() -> Unit)? = null, + dropdownContent: @Composable (() -> Unit)? = null, +) { + val keyboardController = LocalSoftwareKeyboardController.current + val focusRequester = remember { FocusRequester() } + var onSearch by remember { mutableStateOf(false) } + + if (onSearch) { + LaunchedEffect(Unit) { focusRequester.requestFocus() } + } + DisposableEffect(Unit) { + onDispose { + keyboardController?.hide() + } + } + + TopAppBar( + title = { + Box { + AnimatedVisibility( + modifier = Modifier.align(Alignment.CenterStart), + visible = !onSearch, + enter = fadeIn(), + exit = fadeOut(), + content = { title() } + ) + + AnimatedVisibility( + visible = onSearch, + enter = fadeIn(), + exit = fadeOut() + ) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 2.dp, bottom = 2.dp, end = if (onBackClick != null) 0.dp else 14.dp) + .focusRequester(focusRequester) + .onFocusChanged { focusState -> + if (focusState.isFocused) onSearch = true + Log.d(TAG, "onFocusChanged: $focusState") + }, + value = searchText, + onValueChange = onSearchTextChange, + trailingIcon = { + IconButton( + onClick = { + onSearch = false + keyboardController?.hide() + onClearClick() + }, + content = { Icon(Icons.Filled.Close, null) } + ) + }, + maxLines = 1, + singleLine = true, + keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { + keyboardController?.hide() + onConfirm?.invoke() + }) + ) + } + } + }, + navigationIcon = { + if (onBackClick != null) { + IconButton( + onClick = onBackClick, + content = { Icon(Icons.AutoMirrored.Outlined.ArrowBack, null) } + ) + } + }, + actions = { + AnimatedVisibility( + visible = !onSearch + ) { + IconButton( + onClick = { onSearch = true }, + content = { Icon(Icons.Filled.Search, null) } + ) + } + + if (dropdownContent != null) { + dropdownContent() + } + + } + ) +} + +@Preview +@Composable +private fun SearchAppBarPreview() { + var searchText by remember { mutableStateOf("") } + SearchAppBar( + title = { Text("Search text") }, + searchText = searchText, + onSearchTextChange = { searchText = it }, + onClearClick = { searchText = "" } + ) +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/SettingsItem.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/SettingsItem.kt new file mode 100644 index 000000000000..6db20b163cde --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/SettingsItem.kt @@ -0,0 +1,57 @@ +package me.weishu.kernelsu.ui.component + +import androidx.compose.foundation.clickable +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector + +@Composable +fun SwitchItem( + icon: ImageVector? = null, + title: String, + summary: String? = null, + checked: Boolean, + enabled: Boolean = true, + onCheckedChange: (Boolean) -> Unit +) { + ListItem( + modifier = Modifier.clickable { + onCheckedChange.invoke(!checked) + }, + headlineContent = { + Text(title) + }, + leadingContent = icon?.let { + { Icon(icon, title) } + }, + trailingContent = { + Switch(checked = checked, enabled = enabled, onCheckedChange = onCheckedChange) + }, + supportingContent = { + if (summary != null) { + Text(summary) + } + } + ) +} + +@Composable +fun RadioItem( + title: String, + selected: Boolean, + onClick: () -> Unit, +) { + ListItem( + headlineContent = { + Text(title) + }, + leadingContent = { + RadioButton(selected = selected, onClick = onClick) + }, + ) +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/AppProfileConfig.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/AppProfileConfig.kt new file mode 100644 index 000000000000..065ff6d0cf02 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/AppProfileConfig.kt @@ -0,0 +1,63 @@ +package me.weishu.kernelsu.ui.component.profile + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.component.SwitchItem + +@Composable +fun AppProfileConfig( + modifier: Modifier = Modifier, + fixedName: Boolean, + enabled: Boolean, + profile: Natives.Profile, + onProfileChange: (Natives.Profile) -> Unit, +) { + Column(modifier = modifier) { + if (!fixedName) { + OutlinedTextField( + label = { Text(stringResource(R.string.profile_name)) }, + value = profile.name, + onValueChange = { onProfileChange(profile.copy(name = it)) } + ) + } + + SwitchItem( + title = stringResource(R.string.profile_umount_modules), + summary = stringResource(R.string.profile_umount_modules_summary), + checked = if (enabled) { + profile.umountModules + } else { + Natives.isDefaultUmountModules() + }, + enabled = enabled, + onCheckedChange = { + onProfileChange( + profile.copy( + umountModules = it, + nonRootUseDefault = false + ) + ) + } + ) + } +} + +@Preview +@Composable +private fun AppProfileConfigPreview() { + var profile by remember { mutableStateOf(Natives.Profile("")) } + AppProfileConfig(fixedName = true, enabled = false, profile = profile) { + profile = it + } +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/RootProfileConfig.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/RootProfileConfig.kt new file mode 100644 index 000000000000..d2cb3f342e51 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/RootProfileConfig.kt @@ -0,0 +1,471 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package me.weishu.kernelsu.ui.component.profile + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.filled.ArrowDropUp +import androidx.compose.material3.AssistChip +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.text.isDigitsOnly +import com.maxkeppeker.sheets.core.models.base.Header +import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState +import com.maxkeppeler.sheets.input.InputDialog +import com.maxkeppeler.sheets.input.models.InputHeader +import com.maxkeppeler.sheets.input.models.InputSelection +import com.maxkeppeler.sheets.input.models.InputTextField +import com.maxkeppeler.sheets.input.models.InputTextFieldType +import com.maxkeppeler.sheets.input.models.ValidationResult +import com.maxkeppeler.sheets.list.ListDialog +import com.maxkeppeler.sheets.list.models.ListOption +import com.maxkeppeler.sheets.list.models.ListSelection +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.R +import me.weishu.kernelsu.profile.Capabilities +import me.weishu.kernelsu.profile.Groups +import me.weishu.kernelsu.ui.component.rememberCustomDialog +import me.weishu.kernelsu.ui.util.isSepolicyValid + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun RootProfileConfig( + modifier: Modifier = Modifier, + fixedName: Boolean, + profile: Natives.Profile, + onProfileChange: (Natives.Profile) -> Unit, +) { + Column(modifier = modifier) { + if (!fixedName) { + OutlinedTextField( + label = { Text(stringResource(R.string.profile_name)) }, + value = profile.name, + onValueChange = { onProfileChange(profile.copy(name = it)) } + ) + } + + var expanded by remember { mutableStateOf(false) } + val currentNamespace = when (profile.namespace) { + Natives.Profile.Namespace.INHERITED.ordinal -> stringResource(R.string.profile_namespace_inherited) + Natives.Profile.Namespace.GLOBAL.ordinal -> stringResource(R.string.profile_namespace_global) + Natives.Profile.Namespace.INDIVIDUAL.ordinal -> stringResource(R.string.profile_namespace_individual) + else -> stringResource(R.string.profile_namespace_inherited) + } + ListItem(headlineContent = { + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = !expanded } + ) { + OutlinedTextField( + modifier = Modifier + .menuAnchor() + .fillMaxWidth(), + readOnly = true, + label = { Text(stringResource(R.string.profile_namespace)) }, + value = currentNamespace, + onValueChange = {}, + trailingIcon = { + if (expanded) Icon(Icons.Filled.ArrowDropUp, null) + else Icon(Icons.Filled.ArrowDropDown, null) + }, + ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.profile_namespace_inherited)) }, + onClick = { + onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.INHERITED.ordinal)) + expanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.profile_namespace_global)) }, + onClick = { + onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.GLOBAL.ordinal)) + expanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.profile_namespace_individual)) }, + onClick = { + onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.INDIVIDUAL.ordinal)) + expanded = false + }, + ) + } + } + }) + + UidPanel(uid = profile.uid, label = "uid", onUidChange = { + onProfileChange( + profile.copy( + uid = it, + rootUseDefault = false + ) + ) + }) + + UidPanel(uid = profile.gid, label = "gid", onUidChange = { + onProfileChange( + profile.copy( + gid = it, + rootUseDefault = false + ) + ) + }) + + val selectedGroups = profile.groups.ifEmpty { listOf(0) }.let { e -> + e.mapNotNull { g -> + Groups.entries.find { it.gid == g } + } + } + GroupsPanel(selectedGroups) { + onProfileChange( + profile.copy( + groups = it.map { group -> group.gid }.ifEmpty { listOf(0) }, + rootUseDefault = false + ) + ) + } + + val selectedCaps = profile.capabilities.mapNotNull { e -> + Capabilities.entries.find { it.cap == e } + } + + CapsPanel(selectedCaps) { + onProfileChange( + profile.copy( + capabilities = it.map { cap -> cap.cap }, + rootUseDefault = false + ) + ) + } + + SELinuxPanel(profile = profile, onSELinuxChange = { domain, rules -> + onProfileChange( + profile.copy( + context = domain, + rules = rules, + rootUseDefault = false + ) + ) + }) + + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun GroupsPanel(selected: List, closeSelection: (selection: Set) -> Unit) { + val selectGroupsDialog = rememberCustomDialog { dismiss: () -> Unit -> + val groups = Groups.entries.toTypedArray().sortedWith( + compareBy { if (selected.contains(it)) 0 else 1 } + .then(compareBy { + when (it) { + Groups.ROOT -> 0 + Groups.SYSTEM -> 1 + Groups.SHELL -> 2 + else -> Int.MAX_VALUE + } + }) + .then(compareBy { it.name }) + + ) + val options = groups.map { value -> + ListOption( + titleText = value.display, + subtitleText = value.desc, + selected = selected.contains(value), + ) + } + + val selection = HashSet(selected) + ListDialog( + state = rememberUseCaseState(visible = true, onFinishedRequest = { + closeSelection(selection) + }, onCloseRequest = { + dismiss() + }), + header = Header.Default( + title = stringResource(R.string.profile_groups), + ), + selection = ListSelection.Multiple( + showCheckBoxes = true, + options = options, + maxChoices = 32, // Kernel only supports 32 groups at most + ) { indecies, _ -> + // Handle selection + selection.clear() + indecies.forEach { index -> + val group = groups[index] + selection.add(group) + } + } + ) + } + + OutlinedCard(modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + .clickable { + selectGroupsDialog.show() + }) { + + Column(modifier = Modifier.padding(16.dp)) { + Text(stringResource(R.string.profile_groups)) + FlowRow { + selected.forEach { group -> + AssistChip( + modifier = Modifier.padding(3.dp), + onClick = { /*TODO*/ }, + label = { Text(group.display) }) + } + } + } + + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun CapsPanel( + selected: Collection, + closeSelection: (selection: Set) -> Unit +) { + val selectCapabilitiesDialog = rememberCustomDialog { dismiss -> + val caps = Capabilities.entries.toTypedArray().sortedWith( + compareBy { if (selected.contains(it)) 0 else 1 } + .then(compareBy { it.name }) + ) + val options = caps.map { value -> + ListOption( + titleText = value.display, + subtitleText = value.desc, + selected = selected.contains(value), + ) + } + + val selection = HashSet(selected) + ListDialog( + state = rememberUseCaseState(visible = true, onFinishedRequest = { + closeSelection(selection) + }, onCloseRequest = { + dismiss() + }), + header = Header.Default( + title = stringResource(R.string.profile_capabilities), + ), + selection = ListSelection.Multiple( + showCheckBoxes = true, + options = options + ) { indecies, _ -> + // Handle selection + selection.clear() + indecies.forEach { index -> + val group = caps[index] + selection.add(group) + } + } + ) + } + + OutlinedCard(modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + .clickable { + selectCapabilitiesDialog.show() + }) { + + Column(modifier = Modifier.padding(16.dp)) { + Text(stringResource(R.string.profile_capabilities)) + FlowRow { + selected.forEach { group -> + AssistChip( + modifier = Modifier.padding(3.dp), + onClick = { /*TODO*/ }, + label = { Text(group.display) }) + } + } + } + + } +} + +@Composable +private fun UidPanel(uid: Int, label: String, onUidChange: (Int) -> Unit) { + + ListItem(headlineContent = { + var isError by remember { + mutableStateOf(false) + } + var lastValidUid by remember { + mutableStateOf(uid) + } + + val keyboardController = LocalSoftwareKeyboardController.current + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + label = { Text(label) }, + value = uid.toString(), + isError = isError, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { + keyboardController?.hide() + }), + onValueChange = { + if (it.isEmpty()) { + onUidChange(0) + return@OutlinedTextField + } + val valid = isTextValidUid(it) + + val targetUid = if (valid) it.toInt() else lastValidUid + if (valid) { + lastValidUid = it.toInt() + } + + onUidChange(targetUid) + + isError = !valid + } + ) + }) +} + +@Composable +private fun SELinuxPanel( + profile: Natives.Profile, + onSELinuxChange: (domain: String, rules: String) -> Unit +) { + val editSELinuxDialog = rememberCustomDialog { dismiss -> + var domain by remember { mutableStateOf(profile.context) } + var rules by remember { mutableStateOf(profile.rules) } + + val inputOptions = listOf( + InputTextField( + text = domain, + header = InputHeader( + title = stringResource(id = R.string.profile_selinux_domain), + ), + type = InputTextFieldType.OUTLINED, + required = true, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Ascii, + imeAction = ImeAction.Next + ), + resultListener = { + domain = it ?: "" + }, + validationListener = { value -> + // value can be a-zA-Z0-9_ + val regex = Regex("^[a-z_]+:[a-z0-9_]+:[a-z0-9_]+(:[a-z0-9_]+)?$") + if (value?.matches(regex) == true) ValidationResult.Valid + else ValidationResult.Invalid("Domain must be in the format of \"user:role:type:level\"") + } + ), + InputTextField( + text = rules, + header = InputHeader( + title = stringResource(id = R.string.profile_selinux_rules), + ), + type = InputTextFieldType.OUTLINED, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Ascii, + ), + singleLine = false, + resultListener = { + rules = it ?: "" + }, + validationListener = { value -> + if (isSepolicyValid(value)) ValidationResult.Valid + else ValidationResult.Invalid("SELinux rules is invalid!") + } + ) + ) + + InputDialog( + state = rememberUseCaseState(visible = true, + onFinishedRequest = { + onSELinuxChange(domain, rules) + }, + onCloseRequest = { + dismiss() + }), + header = Header.Default( + title = stringResource(R.string.profile_selinux_context), + ), + selection = InputSelection( + input = inputOptions, + onPositiveClick = { result -> + // Handle selection + }, + ) + ) + } + + ListItem(headlineContent = { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .clickable { + editSELinuxDialog.show() + }, + enabled = false, + colors = OutlinedTextFieldDefaults.colors( + disabledTextColor = MaterialTheme.colorScheme.onSurface, + disabledBorderColor = MaterialTheme.colorScheme.outline, + disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant + ), + label = { Text(text = stringResource(R.string.profile_selinux_context)) }, + value = profile.context, + onValueChange = { }, + ) + }) +} + +@Preview +@Composable +private fun RootProfileConfigPreview() { + var profile by remember { mutableStateOf(Natives.Profile("")) } + RootProfileConfig(fixedName = true, profile = profile) { + profile = it + } +} + +private fun isTextValidUid(text: String): Boolean { + return text.isNotEmpty() && text.isDigitsOnly() && text.toInt() >= 0 && text.toInt() <= Int.MAX_VALUE +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/TemplateConfig.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/TemplateConfig.kt new file mode 100644 index 000000000000..d09a23434f85 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/TemplateConfig.kt @@ -0,0 +1,116 @@ +package me.weishu.kernelsu.ui.component.profile + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ReadMore +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.filled.ArrowDropUp +import androidx.compose.material.icons.filled.Create +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.util.listAppProfileTemplates +import me.weishu.kernelsu.ui.util.setSepolicy +import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById + +/** + * @author weishu + * @date 2023/10/21. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TemplateConfig( + profile: Natives.Profile, + onViewTemplate: (id: String) -> Unit = {}, + onManageTemplate: () -> Unit = {}, + onProfileChange: (Natives.Profile) -> Unit +) { + var expanded by remember { mutableStateOf(false) } + var template by rememberSaveable { + mutableStateOf(profile.rootTemplate ?: "") + } + val profileTemplates = listAppProfileTemplates() + val noTemplates = profileTemplates.isEmpty() + + ListItem(headlineContent = { + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = it }, + ) { + OutlinedTextField( + modifier = Modifier + .menuAnchor() + .fillMaxWidth(), + readOnly = true, + label = { Text(stringResource(R.string.profile_template)) }, + value = template.ifEmpty { "None" }, + onValueChange = {}, + trailingIcon = { + if (noTemplates) { + IconButton( + onClick = onManageTemplate + ) { + Icon(Icons.Filled.Create, null) + } + } else if (expanded) Icon(Icons.Filled.ArrowDropUp, null) + else Icon(Icons.Filled.ArrowDropDown, null) + }, + ) + if (profileTemplates.isEmpty()) { + return@ExposedDropdownMenuBox + } + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + profileTemplates.forEach { tid -> + val templateInfo = + getTemplateInfoById(tid) ?: return@forEach + DropdownMenuItem( + text = { Text(tid) }, + onClick = { + template = tid + if (setSepolicy(tid, templateInfo.rules.joinToString("\n"))) { + onProfileChange( + profile.copy( + rootTemplate = tid, + rootUseDefault = false, + uid = templateInfo.uid, + gid = templateInfo.gid, + groups = templateInfo.groups, + capabilities = templateInfo.capabilities, + context = templateInfo.context, + namespace = templateInfo.namespace, + ) + ) + } + expanded = false + }, + trailingIcon = { + IconButton(onClick = { + onViewTemplate(tid) + }) { + Icon(Icons.AutoMirrored.Filled.ReadMore, null) + } + } + ) + } + } + } + }) +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/AppProfile.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/AppProfile.kt new file mode 100644 index 000000000000..ca53388f8ea4 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/AppProfile.kt @@ -0,0 +1,362 @@ +package me.weishu.kernelsu.ui.screen + +import androidx.annotation.StringRes +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.AccountCircle +import androidx.compose.material.icons.filled.Android +import androidx.compose.material.icons.filled.Security +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import kotlinx.coroutines.launch +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.component.SwitchItem +import me.weishu.kernelsu.ui.component.profile.AppProfileConfig +import me.weishu.kernelsu.ui.component.profile.RootProfileConfig +import me.weishu.kernelsu.ui.component.profile.TemplateConfig +import me.weishu.kernelsu.ui.screen.destinations.AppProfileTemplateScreenDestination +import me.weishu.kernelsu.ui.screen.destinations.TemplateEditorScreenDestination +import me.weishu.kernelsu.ui.util.LocalSnackbarHost +import me.weishu.kernelsu.ui.util.forceStopApp +import me.weishu.kernelsu.ui.util.getSepolicy +import me.weishu.kernelsu.ui.util.launchApp +import me.weishu.kernelsu.ui.util.restartApp +import me.weishu.kernelsu.ui.util.setSepolicy +import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel +import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById + +/** + * @author weishu + * @date 2023/5/16. + */ +@Destination +@Composable +fun AppProfileScreen( + navigator: DestinationsNavigator, + appInfo: SuperUserViewModel.AppInfo, +) { + val context = LocalContext.current + val snackbarHost = LocalSnackbarHost.current + val scope = rememberCoroutineScope() + val failToUpdateAppProfile = + stringResource(R.string.failed_to_update_app_profile).format(appInfo.label) + val failToUpdateSepolicy = + stringResource(R.string.failed_to_update_sepolicy).format(appInfo.label) + + val packageName = appInfo.packageName + val initialProfile = Natives.getAppProfile(packageName, appInfo.uid) + if (initialProfile.allowSu) { + initialProfile.rules = getSepolicy(packageName) + } + var profile by rememberSaveable { + mutableStateOf(initialProfile) + } + + Scaffold( + topBar = { TopBar { navigator.popBackStack() } }, + ) { paddingValues -> + AppProfileInner( + modifier = Modifier + .padding(paddingValues) + .verticalScroll(rememberScrollState()), + packageName = appInfo.packageName, + appLabel = appInfo.label, + appIcon = { + AsyncImage( + model = ImageRequest.Builder(context).data(appInfo.packageInfo).crossfade(true) + .build(), + contentDescription = appInfo.label, + modifier = Modifier + .padding(4.dp) + .width(48.dp) + .height(48.dp) + ) + }, + profile = profile, + onViewTemplate = { + getTemplateInfoById(it)?.let { info -> + navigator.navigate(TemplateEditorScreenDestination(info)) + } + }, + onManageTemplate = { + navigator.navigate(AppProfileTemplateScreenDestination()) + }, + onProfileChange = { + scope.launch { + if (it.allowSu && !it.rootUseDefault && it.rules.isNotEmpty()) { + if (!setSepolicy(profile.name, it.rules)) { + snackbarHost.showSnackbar(failToUpdateSepolicy) + return@launch + } + } + if (!Natives.setAppProfile(it)) { + snackbarHost.showSnackbar(failToUpdateAppProfile.format(appInfo.uid)) + } else { + profile = it + } + } + }, + ) + } +} + +@Composable +private fun AppProfileInner( + modifier: Modifier = Modifier, + packageName: String, + appLabel: String, + appIcon: @Composable () -> Unit, + profile: Natives.Profile, + onViewTemplate: (id: String) -> Unit = {}, + onManageTemplate: () -> Unit = {}, + onProfileChange: (Natives.Profile) -> Unit, +) { + val isRootGranted = profile.allowSu + + Column(modifier = modifier) { + AppMenuBox(packageName) { + ListItem( + headlineContent = { Text(appLabel) }, + supportingContent = { Text(packageName) }, + leadingContent = appIcon, + ) + } + + SwitchItem( + icon = Icons.Filled.Security, + title = stringResource(id = R.string.superuser), + checked = isRootGranted, + onCheckedChange = { onProfileChange(profile.copy(allowSu = it)) }, + ) + + Crossfade(targetState = isRootGranted, label = "") { current -> + Column { + if (current) { + val initialMode = if (profile.rootUseDefault) { + Mode.Default + } else if (profile.rootTemplate != null) { + Mode.Template + } else { + Mode.Custom + } + var mode by rememberSaveable { + mutableStateOf(initialMode) + } + ProfileBox(mode, true) { + // template mode shouldn't change profile here! + if (it == Mode.Default || it == Mode.Custom) { + onProfileChange(profile.copy(rootUseDefault = it == Mode.Default)) + } + mode = it + } + Crossfade(targetState = mode, label = "") { currentMode -> + if (currentMode == Mode.Template) { + TemplateConfig( + profile = profile, + onViewTemplate = onViewTemplate, + onManageTemplate = onManageTemplate, + onProfileChange = onProfileChange + ) + } else if (mode == Mode.Custom) { + RootProfileConfig( + fixedName = true, + profile = profile, + onProfileChange = onProfileChange + ) + } + } + } else { + val mode = if (profile.nonRootUseDefault) Mode.Default else Mode.Custom + ProfileBox(mode, false) { + onProfileChange(profile.copy(nonRootUseDefault = (it == Mode.Default))) + } + Crossfade(targetState = mode, label = "") { currentMode -> + val modifyEnabled = currentMode == Mode.Custom + AppProfileConfig( + fixedName = true, + profile = profile, + enabled = modifyEnabled, + onProfileChange = onProfileChange + ) + } + } + } + } + } +} + +private enum class Mode(@StringRes private val res: Int) { + Default(R.string.profile_default), Template(R.string.profile_template), Custom(R.string.profile_custom); + + val text: String + @Composable get() = stringResource(res) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar(onBack: () -> Unit) { + TopAppBar( + title = { + Text(stringResource(R.string.profile)) + }, + navigationIcon = { + IconButton( + onClick = onBack + ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } + }, + ) +} + +@Composable +private fun ProfileBox( + mode: Mode, + hasTemplate: Boolean, + onModeChange: (Mode) -> Unit, +) { + ListItem( + headlineContent = { Text(stringResource(R.string.profile)) }, + supportingContent = { Text(mode.text) }, + leadingContent = { Icon(Icons.Filled.AccountCircle, null) }, + ) + HorizontalDivider(thickness = Dp.Hairline) + ListItem(headlineContent = { + Row( + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly + ) { + FilterChip( + selected = mode == Mode.Default, + label = { Text(stringResource(R.string.profile_default)) }, + onClick = { onModeChange(Mode.Default) }, + ) + if (hasTemplate) { + FilterChip( + selected = mode == Mode.Template, + label = { Text(stringResource(R.string.profile_template)) }, + onClick = { onModeChange(Mode.Template) }, + ) + } + FilterChip( + selected = mode == Mode.Custom, + label = { Text(stringResource(R.string.profile_custom)) }, + onClick = { onModeChange(Mode.Custom) }, + ) + } + }) +} + +@Composable +private fun AppMenuBox(packageName: String, content: @Composable () -> Unit) { + + var expanded by remember { mutableStateOf(false) } + var touchPoint: Offset by remember { mutableStateOf(Offset.Zero) } + val density = LocalDensity.current + + BoxWithConstraints( + Modifier + .fillMaxSize() + .pointerInput(Unit) { + detectTapGestures { + touchPoint = it + expanded = true + } + }) { + + content() + + val (offsetX, offsetY) = with(density) { + (touchPoint.x.toDp()) to (touchPoint.y.toDp()) + } + + DropdownMenu( + expanded = expanded, + offset = DpOffset(offsetX, -offsetY), + onDismissRequest = { + expanded = false + }, + ) { + DropdownMenuItem( + text = { Text(stringResource(id = R.string.launch_app)) }, + onClick = { + expanded = false + launchApp(packageName) + }, + ) + DropdownMenuItem( + text = { Text(stringResource(id = R.string.force_stop_app)) }, + onClick = { + expanded = false + forceStopApp(packageName) + }, + ) + DropdownMenuItem( + text = { Text(stringResource(id = R.string.restart_app)) }, + onClick = { + expanded = false + restartApp(packageName) + }, + ) + } + } + + +} + +@Preview +@Composable +private fun AppProfilePreview() { + var profile by remember { mutableStateOf(Natives.Profile("")) } + AppProfileInner( + packageName = "icu.nullptr.test", + appLabel = "Test", + appIcon = { Icon(Icons.Filled.Android, null) }, + profile = profile, + onProfileChange = { + profile = it + }, + ) +} + diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/BottomBarDestination.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/BottomBarDestination.kt new file mode 100644 index 000000000000..9345ced52ff0 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/BottomBarDestination.kt @@ -0,0 +1,24 @@ +package me.weishu.kernelsu.ui.screen + +import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.outlined.* +import androidx.compose.ui.graphics.vector.ImageVector +import com.ramcosta.composedestinations.spec.DirectionDestinationSpec +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.screen.destinations.HomeScreenDestination +import me.weishu.kernelsu.ui.screen.destinations.SuperUserScreenDestination +import me.weishu.kernelsu.ui.screen.destinations.ModuleScreenDestination + +enum class BottomBarDestination( + val direction: DirectionDestinationSpec, + @StringRes val label: Int, + val iconSelected: ImageVector, + val iconNotSelected: ImageVector, + val rootRequired: Boolean, +) { + Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false), + SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security, true), + Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true) +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Flash.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Flash.kt new file mode 100644 index 000000000000..1e3d48fdae05 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Flash.kt @@ -0,0 +1,238 @@ +package me.weishu.kernelsu.ui.screen + +import android.net.Uri +import android.os.Environment +import android.os.Parcelable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.filled.Save +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.key +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.parcelize.Parcelize +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.component.KeyEventBlocker +import me.weishu.kernelsu.ui.util.LkmSelection +import me.weishu.kernelsu.ui.util.LocalSnackbarHost +import me.weishu.kernelsu.ui.util.flashModule +import me.weishu.kernelsu.ui.util.installBoot +import me.weishu.kernelsu.ui.util.reboot +import me.weishu.kernelsu.ui.util.restoreBoot +import me.weishu.kernelsu.ui.util.uninstallPermanently +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +enum class FlashingStatus { + FLASHING, + SUCCESS, + FAILED +} + +/** + * @author weishu + * @date 2023/1/1. + */ +@Composable +@Destination +fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) { + + var text by rememberSaveable { mutableStateOf("") } + val logContent = rememberSaveable { StringBuilder() } + var showFloatAction by rememberSaveable { mutableStateOf(false) } + + val snackBarHost = LocalSnackbarHost.current + val scope = rememberCoroutineScope() + val scrollState = rememberScrollState() + var flashing by rememberSaveable { + mutableStateOf(FlashingStatus.FLASHING) + } + + LaunchedEffect(Unit) { + if (text.isNotEmpty()) { + return@LaunchedEffect + } + withContext(Dispatchers.IO) { + flashIt(flashIt, onFinish = { showReboot, code -> + if (code != 0) { + text += "Error: exit code = $code.\nPlease save and check the log.\n" + } + if (showReboot) { + text += "\n\n\n" + showFloatAction = true + } + flashing = if (code == 0) FlashingStatus.SUCCESS else FlashingStatus.FAILED + }, onStdout = { + text += "$it\n" + logContent.append(it).append("\n") + }, onStderr = { + logContent.append(it).append("\n") + }) + } + } + + Scaffold( + topBar = { + TopBar( + flashing, + onBack = { + navigator.popBackStack() + }, + onSave = { + scope.launch { + val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()) + val date = format.format(Date()) + val file = File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), + "KernelSU_install_log_${date}.log" + ) + file.writeText(logContent.toString()) + snackBarHost.showSnackbar("Log saved to ${file.absolutePath}") + } + } + ) + }, + floatingActionButton = { + if (showFloatAction) { + val reboot = stringResource(id = R.string.reboot) + ExtendedFloatingActionButton( + onClick = { + scope.launch { + withContext(Dispatchers.IO) { + reboot() + } + } + }, + icon = { Icon(Icons.Filled.Refresh, reboot) }, + text = { Text(text = reboot) }, + ) + } + + } + ) { innerPadding -> + KeyEventBlocker { + it.key == Key.VolumeDown || it.key == Key.VolumeUp + } + Column( + modifier = Modifier + .fillMaxSize(1f) + .padding(innerPadding) + .verticalScroll(scrollState), + ) { + LaunchedEffect(text) { + scrollState.animateScrollTo(scrollState.maxValue) + } + Text( + modifier = Modifier.padding(8.dp), + text = text, + fontSize = MaterialTheme.typography.bodySmall.fontSize, + fontFamily = FontFamily.Monospace, + lineHeight = MaterialTheme.typography.bodySmall.lineHeight, + ) + } + } +} + +@Parcelize +sealed class FlashIt : Parcelable { + data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : + FlashIt() + + data class FlashModule(val uri: Uri) : FlashIt() + + data object FlashRestore : FlashIt() + + data object FlashUninstall : FlashIt() +} + +fun flashIt( + flashIt: FlashIt, onFinish: (Boolean, Int) -> Unit, + onStdout: (String) -> Unit, + onStderr: (String) -> Unit +) { + when (flashIt) { + is FlashIt.FlashBoot -> installBoot( + flashIt.boot, + flashIt.lkm, + flashIt.ota, + onFinish, + onStdout, + onStderr + ) + + is FlashIt.FlashModule -> flashModule(flashIt.uri, onFinish, onStdout, onStderr) + + FlashIt.FlashRestore -> restoreBoot(onFinish, onStdout, onStderr) + + FlashIt.FlashUninstall -> uninstallPermanently(onFinish, onStdout, onStderr) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar(status: FlashingStatus, onBack: () -> Unit = {}, onSave: () -> Unit = {}) { + TopAppBar( + title = { + Text( + stringResource( + when (status) { + FlashingStatus.FLASHING -> R.string.flashing + FlashingStatus.SUCCESS -> R.string.flash_success + FlashingStatus.FAILED -> R.string.flash_failed + } + ) + ) + }, + navigationIcon = { + IconButton( + onClick = onBack + ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } + }, + actions = { + IconButton(onClick = onSave) { + Icon( + imageVector = Icons.Filled.Save, + contentDescription = "Localized description" + ) + } + } + ) +} + +@Preview +@Composable +fun InstallPreview() { + InstallScreen(EmptyDestinationsNavigator) +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Home.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Home.kt new file mode 100644 index 000000000000..7f1d483f14bf --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Home.kt @@ -0,0 +1,442 @@ +package me.weishu.kernelsu.ui.screen + +import android.content.Context +import android.os.Build +import android.os.PowerManager +import android.system.Os +import androidx.annotation.StringRes +import androidx.compose.animation.* +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Archive +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.outlined.Block +import androidx.compose.material.icons.outlined.CheckCircle +import androidx.compose.material.icons.outlined.Warning +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootNavGraph +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import me.weishu.kernelsu.* +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.component.rememberConfirmDialog +import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination +import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination +import me.weishu.kernelsu.ui.util.* +import me.weishu.kernelsu.ui.util.module.LatestVersionInfo + +@RootNavGraph(start = true) +@Destination +@Composable +fun HomeScreen(navigator: DestinationsNavigator) { + val kernelVersion = getKernelVersion() + + Scaffold(topBar = { + TopBar(kernelVersion, onSettingsClick = { + navigator.navigate(SettingScreenDestination) + }, onInstallClick = { + navigator.navigate(InstallScreenDestination) + }) + }) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + val isManager = Natives.becomeManager(ksuApp.packageName) + SideEffect { + if (isManager) install() + } + val ksuVersion = if (isManager) Natives.version else null + val lkmMode = ksuVersion?.let { + if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) Natives.isLkmMode else null + } + + StatusCard(kernelVersion, ksuVersion, lkmMode) { + navigator.navigate(InstallScreenDestination) + } + if (isManager && Natives.requireNewKernel()) { + WarningCard( + stringResource(id = R.string.require_kernel_version).format( + ksuVersion, Natives.MINIMAL_SUPPORTED_KERNEL + ) + ) + } + if (ksuVersion != null && !rootAvailable()) { + WarningCard( + stringResource(id = R.string.grant_root_failed) + ) + } + val checkUpdate = + LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) + .getBoolean("check_update", true) + if (checkUpdate) { + UpdateCard() + } + InfoCard() + DonateCard() + LearnMoreCard() + Spacer(Modifier) + } + } +} + +@Composable +fun UpdateCard() { + val context = LocalContext.current + val latestVersionInfo = LatestVersionInfo() + val newVersion by produceState(initialValue = latestVersionInfo) { + value = withContext(Dispatchers.IO){ + checkNewVersion() + } + } + + + val currentVersionCode = getManagerVersion(context).second + val newVersionCode = newVersion.versionCode + val newVersionUrl = newVersion.downloadUrl + val changelog = newVersion.changelog + + val uriHandler = LocalUriHandler.current + val title = stringResource(id = R.string.module_changelog) + val updateText = stringResource(id = R.string.module_update) + + AnimatedVisibility( + visible = newVersionCode > currentVersionCode, + enter = fadeIn() + expandVertically(), + exit = shrinkVertically() + fadeOut() + ) { + val updateDialog = rememberConfirmDialog(onConfirm = { uriHandler.openUri(newVersionUrl) }) + WarningCard( + message = stringResource(id = R.string.new_version_available).format(newVersionCode), + MaterialTheme.colorScheme.outlineVariant + ) { + if (changelog.isNotEmpty()) { + updateDialog.showConfirm( + title = title, + content = changelog, + markdown = true, + confirm = updateText + ) + } + } + } +} + +@Composable +fun RebootDropdownItem(@StringRes id: Int, reason: String = "") { + DropdownMenuItem(text = { + Text(stringResource(id)) + }, onClick = { + reboot(reason) + }) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar( + kernelVersion: KernelVersion, + onInstallClick: () -> Unit, + onSettingsClick: () -> Unit +) { + TopAppBar(title = { Text(stringResource(R.string.app_name)) }, actions = { + if (kernelVersion.isGKI()) { + IconButton(onClick = onInstallClick) { + Icon( + imageVector = Icons.Filled.Archive, + contentDescription = stringResource(id = R.string.install) + ) + } + } + + var showDropdown by remember { mutableStateOf(false) } + IconButton(onClick = { + showDropdown = true + }) { + Icon( + imageVector = Icons.Filled.Refresh, + contentDescription = stringResource(id = R.string.reboot) + ) + + DropdownMenu(expanded = showDropdown, onDismissRequest = { + showDropdown = false + }) { + + RebootDropdownItem(id = R.string.reboot) + + val pm = + LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager? + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) { + RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace") + } + RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery") + RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader") + RebootDropdownItem(id = R.string.reboot_download, reason = "download") + RebootDropdownItem(id = R.string.reboot_edl, reason = "edl") + } + } + + IconButton(onClick = onSettingsClick) { + Icon( + imageVector = Icons.Filled.Settings, + contentDescription = stringResource(id = R.string.settings) + ) + } + }) +} + +@Composable +private fun StatusCard( + kernelVersion: KernelVersion, + ksuVersion: Int?, + lkmMode: Boolean?, + onClickInstall: () -> Unit = {} +) { + ElevatedCard( + colors = CardDefaults.elevatedCardColors(containerColor = run { + if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer + else MaterialTheme.colorScheme.errorContainer + }) + ) { + Row(modifier = Modifier + .fillMaxWidth() + .clickable { + if (kernelVersion.isGKI()) { + onClickInstall() + } + } + .padding(24.dp), verticalAlignment = Alignment.CenterVertically) { + when { + ksuVersion != null -> { + val safeMode = when { + Natives.isSafeMode -> " [${stringResource(id = R.string.safe_mode)}]" + else -> "" + } + + val workingMode = when (lkmMode) { + null -> "" + true -> " " + else -> " " + } + + val workingText = + "${stringResource(id = R.string.home_working)}$workingMode$safeMode" + + Icon(Icons.Outlined.CheckCircle, stringResource(R.string.home_working)) + Column(Modifier.padding(start = 20.dp)) { + Text( + text = workingText, + style = MaterialTheme.typography.titleMedium + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.home_working_version, ksuVersion), + style = MaterialTheme.typography.bodyMedium + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource( + R.string.home_superuser_count, getSuperuserCount() + ), style = MaterialTheme.typography.bodyMedium + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.home_module_count, getModuleCount()), + style = MaterialTheme.typography.bodyMedium + ) + } + } + + kernelVersion.isGKI() -> { + Icon(Icons.Outlined.Warning, stringResource(R.string.home_not_installed)) + Column(Modifier.padding(start = 20.dp)) { + Text( + text = stringResource(R.string.home_not_installed), + style = MaterialTheme.typography.titleMedium + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.home_click_to_install), + style = MaterialTheme.typography.bodyMedium + ) + } + } + + else -> { + Icon(Icons.Outlined.Block, stringResource(R.string.home_unsupported)) + Column(Modifier.padding(start = 20.dp)) { + Text( + text = stringResource(R.string.home_unsupported), + style = MaterialTheme.typography.titleMedium + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.home_unsupported_reason), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } + } + } +} + +@Composable +fun WarningCard( + message: String, color: Color = MaterialTheme.colorScheme.error, onClick: (() -> Unit)? = null +) { + ElevatedCard( + colors = CardDefaults.elevatedCardColors( + containerColor = color + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .then(onClick?.let { Modifier.clickable { it() } } ?: Modifier) + .padding(24.dp) + ) { + Text( + text = message, style = MaterialTheme.typography.bodyMedium + ) + } + } +} + +@Composable +fun LearnMoreCard() { + val uriHandler = LocalUriHandler.current + val url = stringResource(R.string.home_learn_kernelsu_url) + + ElevatedCard { + + Row(modifier = Modifier + .fillMaxWidth() + .clickable { + uriHandler.openUri(url) + } + .padding(24.dp), verticalAlignment = Alignment.CenterVertically) { + Column { + Text( + text = stringResource(R.string.home_learn_kernelsu), + style = MaterialTheme.typography.titleSmall + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.home_click_to_learn_kernelsu), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } +} + +@Composable +fun DonateCard() { + val uriHandler = LocalUriHandler.current + + ElevatedCard { + + Row(modifier = Modifier + .fillMaxWidth() + .clickable { + uriHandler.openUri("https://patreon.com/weishu") + } + .padding(24.dp), verticalAlignment = Alignment.CenterVertically) { + Column { + Text( + text = stringResource(R.string.home_support_title), + style = MaterialTheme.typography.titleSmall + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.home_support_content), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } +} + +@Composable +private fun InfoCard() { + val context = LocalContext.current + + ElevatedCard { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp) + ) { + val contents = StringBuilder() + val uname = Os.uname() + + @Composable + fun InfoCardItem(label: String, content: String) { + contents.appendLine(label).appendLine(content).appendLine() + Text(text = label, style = MaterialTheme.typography.bodyLarge) + Text(text = content, style = MaterialTheme.typography.bodyMedium) + } + + InfoCardItem(stringResource(R.string.home_kernel), uname.release) + + Spacer(Modifier.height(16.dp)) + val managerVersion = getManagerVersion(context) + InfoCardItem( + stringResource(R.string.home_manager_version), + "${managerVersion.first} (${managerVersion.second})" + ) + + Spacer(Modifier.height(16.dp)) + InfoCardItem(stringResource(R.string.home_fingerprint), Build.FINGERPRINT) + + Spacer(Modifier.height(16.dp)) + InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus()) + } + } +} + +fun getManagerVersion(context: Context): Pair { + val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) + return Pair(packageInfo.versionName, packageInfo.versionCode) +} + +@Preview +@Composable +private fun StatusCardPreview() { + Column { + StatusCard(KernelVersion(5, 10, 101), 1, null) + StatusCard(KernelVersion(5, 10, 101), 20000, true) + StatusCard(KernelVersion(5, 10, 101), null, true) + StatusCard(KernelVersion(4, 10, 101), null, false) + } +} + +@Preview +@Composable +private fun WarningCardPreview() { + Column { + WarningCard(message = "Warning message") + WarningCard( + message = "Warning message ", + MaterialTheme.colorScheme.outlineVariant, + onClick = {}) + } +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Install.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Install.kt new file mode 100644 index 000000000000..71b5c9756909 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Install.kt @@ -0,0 +1,311 @@ +package me.weishu.kernelsu.ui.screen + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.StringRes +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.FileUpload +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.maxkeppeker.sheets.core.models.base.Header +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.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.component.DialogHandle +import me.weishu.kernelsu.ui.component.rememberConfirmDialog +import me.weishu.kernelsu.ui.component.rememberCustomDialog +import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination +import me.weishu.kernelsu.ui.util.LkmSelection +import me.weishu.kernelsu.ui.util.getCurrentKmi +import me.weishu.kernelsu.ui.util.getSupportedKmis +import me.weishu.kernelsu.ui.util.isAbDevice +import me.weishu.kernelsu.ui.util.isInitBoot +import me.weishu.kernelsu.ui.util.rootAvailable + +/** + * @author weishu + * @date 2024/3/12. + */ +@Destination +@Composable +fun InstallScreen(navigator: DestinationsNavigator) { + var installMethod by remember { + mutableStateOf(null) + } + + var lkmSelection by remember { + mutableStateOf(LkmSelection.KmiNone) + } + + val onInstall = { + installMethod?.let { method -> + val flashIt = FlashIt.FlashBoot( + boot = if (method is InstallMethod.SelectFile) method.uri else null, + lkm = lkmSelection, + ota = method is InstallMethod.DirectInstallToInactiveSlot + ) + navigator.navigate(FlashScreenDestination(flashIt)) + } + } + + val currentKmi by produceState(initialValue = "") { value = getCurrentKmi() } + + val selectKmiDialog = rememberSelectKmiDialog { kmi -> + kmi?.let { + lkmSelection = LkmSelection.KmiString(it) + onInstall() + } + } + + val onClickNext = { + if (lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) { + // no lkm file selected and cannot get current kmi + selectKmiDialog.show() + } else { + onInstall() + } + } + + val selectLkmLauncher = + rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + it.data?.data?.let { uri -> + lkmSelection = LkmSelection.LkmUri(uri) + } + } + } + + val onLkmUpload = { + selectLkmLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply { + type = "application/octet-stream" + }) + } + + Scaffold(topBar = { + TopBar( + onBack = { navigator.popBackStack() }, onLkmUpload = onLkmUpload + ) + }) { + Column(modifier = Modifier.padding(it)) { + SelectInstallMethod { method -> + installMethod = method + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + (lkmSelection as? LkmSelection.LkmUri)?.let { + Text( + stringResource( + id = R.string.selected_lkm, + it.uri.lastPathSegment ?: "(file)" + ) + ) + } + Button(modifier = Modifier.fillMaxWidth(), + enabled = installMethod != null, + onClick = { + onClickNext() + }) { + Text( + stringResource(id = R.string.install_next), + fontSize = MaterialTheme.typography.bodyMedium.fontSize + ) + } + } + } + } +} + +sealed class InstallMethod { + data class SelectFile( + val uri: Uri? = null, + @StringRes override val label: Int = R.string.select_file, + override val summary: String? + ) : InstallMethod() + + data object DirectInstall : InstallMethod() { + override val label: Int + get() = R.string.direct_install + } + + data object DirectInstallToInactiveSlot : InstallMethod() { + override val label: Int + get() = R.string.install_inactive_slot + } + + abstract val label: Int + open val summary: String? = null +} + +@Composable +private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) { + val rootAvailable = rootAvailable() + val isAbDevice = isAbDevice() + val selectFileTip = stringResource( + id = R.string.select_file_tip, if (isInitBoot()) "init_boot" else "boot" + ) + val radioOptions = + mutableListOf(InstallMethod.SelectFile(summary = selectFileTip)) + if (rootAvailable) { + radioOptions.add(InstallMethod.DirectInstall) + + if (isAbDevice) { + radioOptions.add(InstallMethod.DirectInstallToInactiveSlot) + } + } + + var selectedOption by remember { mutableStateOf(null) } + val selectImageLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { + if (it.resultCode == Activity.RESULT_OK) { + it.data?.data?.let { uri -> + val option = InstallMethod.SelectFile(uri, summary = selectFileTip) + selectedOption = option + onSelected(option) + } + } + } + + val confirmDialog = rememberConfirmDialog(onConfirm = { + selectedOption = InstallMethod.DirectInstallToInactiveSlot + onSelected(InstallMethod.DirectInstallToInactiveSlot) + }, onDismiss = null) + val dialogTitle = stringResource(id = android.R.string.dialog_alert_title) + val dialogContent = stringResource(id = R.string.install_inactive_slot_warning) + + val onClick = { option: InstallMethod -> + + when (option) { + is InstallMethod.SelectFile -> { + selectImageLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply { + type = "application/octet-stream" + }) + } + + is InstallMethod.DirectInstall -> { + selectedOption = option + onSelected(option) + } + + is InstallMethod.DirectInstallToInactiveSlot -> { + confirmDialog.showConfirm(dialogTitle, dialogContent) + } + } + } + + Column { + radioOptions.forEach { option -> + Row(verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { + onClick(option) + }) { + RadioButton(selected = option.javaClass == selectedOption?.javaClass, onClick = { + onClick(option) + }) + Column { + Text( + text = stringResource(id = option.label), + fontSize = MaterialTheme.typography.titleMedium.fontSize, + fontFamily = MaterialTheme.typography.titleMedium.fontFamily, + fontStyle = MaterialTheme.typography.titleMedium.fontStyle + ) + option.summary?.let { + Text( + text = it, + fontSize = MaterialTheme.typography.bodySmall.fontSize, + fontFamily = MaterialTheme.typography.bodySmall.fontFamily, + fontStyle = MaterialTheme.typography.bodySmall.fontStyle + ) + } + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle { + return rememberCustomDialog { dismiss -> + val supportedKmi by produceState(initialValue = emptyList()) { + value = getSupportedKmis() + } + val options = supportedKmi.map { value -> + ListOption( + titleText = value + ) + } + + var selection by remember { mutableStateOf(null) } + ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = { + onSelected(selection) + }, onCloseRequest = { + dismiss() + }), header = Header.Default( + title = stringResource(R.string.select_kmi), + ), selection = ListSelection.Single( + showRadioButtons = true, + options = options, + ) { _, option -> + selection = option.titleText + }) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar(onBack: () -> Unit = {}, onLkmUpload: () -> Unit = {}) { + TopAppBar(title = { Text(stringResource(R.string.install)) }, navigationIcon = { + IconButton( + onClick = onBack + ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } + }, actions = { + IconButton(onClick = onLkmUpload) { + Icon(Icons.Filled.FileUpload, contentDescription = null) + } + }) +} + +@Composable +@Preview +fun SelectInstall_Preview() { + InstallScreen(EmptyDestinationsNavigator) +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt new file mode 100644 index 000000000000..9812e87ebba6 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt @@ -0,0 +1,576 @@ +package me.weishu.kernelsu.ui.screen + +import android.app.Activity.RESULT_OK +import android.content.Intent +import android.net.Uri +import android.util.Log +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.Button +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarResult +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +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.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.component.ConfirmResult +import me.weishu.kernelsu.ui.component.rememberConfirmDialog +import me.weishu.kernelsu.ui.component.rememberLoadingDialog +import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination +import me.weishu.kernelsu.ui.util.DownloadListener +import me.weishu.kernelsu.ui.util.LocalSnackbarHost +import me.weishu.kernelsu.ui.util.download +import me.weishu.kernelsu.ui.util.hasMagisk +import me.weishu.kernelsu.ui.util.reboot +import me.weishu.kernelsu.ui.util.toggleModule +import me.weishu.kernelsu.ui.util.uninstallModule +import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel +import me.weishu.kernelsu.ui.webui.WebUIActivity +import okhttp3.OkHttpClient + +@Destination +@Composable +fun ModuleScreen(navigator: DestinationsNavigator) { + val viewModel = viewModel() + val context = LocalContext.current + + LaunchedEffect(Unit) { + if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) { + viewModel.fetchModuleList() + } + } + + val isSafeMode = Natives.isSafeMode + val hasMagisk = hasMagisk() + + val hideInstallButton = isSafeMode || hasMagisk + + Scaffold(topBar = { + TopBar() + }, floatingActionButton = if (hideInstallButton) { + { /* Empty */ } + } else { + { + val moduleInstall = stringResource(id = R.string.module_install) + val selectZipLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { + if (it.resultCode != RESULT_OK) { + return@rememberLauncherForActivityResult + } + val data = it.data ?: return@rememberLauncherForActivityResult + val uri = data.data ?: return@rememberLauncherForActivityResult + + navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri))) + + viewModel.markNeedRefresh() + + Log.i("ModuleScreen", "select zip result: ${it.data}") + } + + ExtendedFloatingActionButton( + onClick = { + // select the zip file to install + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.type = "application/zip" + selectZipLauncher.launch(intent) + }, + icon = { Icon(Icons.Filled.Add, moduleInstall) }, + text = { Text(text = moduleInstall) }, + ) + } + }) { innerPadding -> + + when { + hasMagisk -> { + Box( + modifier = Modifier + .fillMaxSize() + .padding(24.dp), + contentAlignment = Alignment.Center + ) { + Text( + stringResource(R.string.module_magisk_conflict), + textAlign = TextAlign.Center, + ) + } + } + + else -> { + ModuleList( + viewModel = viewModel, modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + onInstallModule = + { + navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(it))) + }, onClickModule = { id, name, hasWebUi -> + if (hasWebUi) { + context.startActivity(Intent(context, WebUIActivity::class.java) + .setData(Uri.parse("kernelsu://webui/$id")) + .putExtra("id", id) + .putExtra("name", name) + ) + } + }) + } + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun ModuleList( + viewModel: ModuleViewModel, + modifier: Modifier = Modifier, + onInstallModule: (Uri) -> Unit, + onClickModule: (id: String, name: String, hasWebUi: Boolean) -> Unit +) { + val failedEnable = stringResource(R.string.module_failed_to_enable) + val failedDisable = stringResource(R.string.module_failed_to_disable) + val failedUninstall = stringResource(R.string.module_uninstall_failed) + val successUninstall = stringResource(R.string.module_uninstall_success) + val reboot = stringResource(id = R.string.reboot) + val rebootToApply = stringResource(id = R.string.reboot_to_apply) + val moduleStr = stringResource(id = R.string.module) + val uninstall = stringResource(id = R.string.uninstall) + val cancel = stringResource(id = android.R.string.cancel) + val moduleUninstallConfirm = stringResource(id = R.string.module_uninstall_confirm) + val updateText = stringResource(R.string.module_update) + val changelogText = stringResource(R.string.module_changelog) + val downloadingText = stringResource(R.string.module_downloading) + val startDownloadingText = stringResource(R.string.module_start_downloading) + val fetchChangeLogFailed = stringResource(R.string.module_changelog_failed) + + val snackBarHost = LocalSnackbarHost.current + val context = LocalContext.current + + val loadingDialog = rememberLoadingDialog() + val confirmDialog = rememberConfirmDialog() + + suspend fun onModuleUpdate( + module: ModuleViewModel.ModuleInfo, + changelogUrl: String, + downloadUrl: String, + fileName: String + ) { + val changelogResult = loadingDialog.withLoading { + withContext(Dispatchers.IO) { + runCatching { + OkHttpClient().newCall( + okhttp3.Request.Builder().url(changelogUrl).build() + ).execute().body!!.string() + } + } + } + + val showToast: suspend (String) -> Unit = { msg -> + withContext(Dispatchers.Main) { + Toast.makeText( + context, + msg, + Toast.LENGTH_SHORT + ).show() + } + } + + val changelog = changelogResult.getOrElse { + showToast(fetchChangeLogFailed.format(it.message)) + return + }.ifBlank { + showToast(fetchChangeLogFailed.format(module.name)) + return + } + + // changelog is not empty, show it and wait for confirm + val confirmResult = confirmDialog.awaitConfirm( + changelogText, + content = changelog, + markdown = true, + confirm = updateText, + ) + + if (confirmResult != ConfirmResult.Confirmed) { + return + } + + showToast(startDownloadingText.format(module.name)) + + val downloading = downloadingText.format(module.name) + withContext(Dispatchers.IO) { + download( + context, + downloadUrl, + fileName, + downloading, + onDownloaded = onInstallModule, + onDownloading = { + launch(Dispatchers.Main) { + Toast.makeText(context, downloading, Toast.LENGTH_SHORT).show() + } + } + ) + } + } + + suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) { + val confirmResult = confirmDialog.awaitConfirm( + moduleStr, + content = moduleUninstallConfirm.format(module.name), + confirm = uninstall, + dismiss = cancel + ) + if (confirmResult != ConfirmResult.Confirmed) { + return + } + + val success = loadingDialog.withLoading { + withContext(Dispatchers.IO) { + uninstallModule(module.id) + } + } + + if (success) { + viewModel.fetchModuleList() + } + val message = if (success) { + successUninstall.format(module.name) + } else { + failedUninstall.format(module.name) + } + val actionLabel = if (success) { + reboot + } else { + null + } + val result = snackBarHost.showSnackbar(message, actionLabel = actionLabel) + if (result == SnackbarResult.ActionPerformed) { + reboot() + } + } + + val refreshState = rememberPullRefreshState(refreshing = viewModel.isRefreshing, + onRefresh = { viewModel.fetchModuleList() }) + Box(modifier.pullRefresh(refreshState)) { + val context = LocalContext.current + + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = remember { + PaddingValues( + start = 16.dp, + top = 16.dp, + end = 16.dp, + bottom = 16.dp + 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */ + ) + }, + ) { + when { + !viewModel.isOverlayAvailable -> { + item { + Box( + modifier = Modifier.fillParentMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + stringResource(R.string.module_overlay_fs_not_available), + textAlign = TextAlign.Center + ) + } + } + } + + viewModel.moduleList.isEmpty() -> { + item { + Box( + modifier = Modifier.fillParentMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + stringResource(R.string.module_empty), + textAlign = TextAlign.Center + ) + } + } + } + + else -> { + items(viewModel.moduleList) { module -> + var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) } + val scope = rememberCoroutineScope() + val updatedModule by produceState(initialValue = Triple("", "", "")) { + scope.launch(Dispatchers.IO) { + value = viewModel.checkUpdate(module) + } + } + + ModuleItem(module, isChecked, updatedModule.first, onUninstall = { + scope.launch { onModuleUninstall(module) } + }, onCheckChanged = { + scope.launch { + val success = loadingDialog.withLoading { + withContext(Dispatchers.IO) { + toggleModule(module.id, !isChecked) + } + } + if (success) { + isChecked = it + viewModel.fetchModuleList() + + val result = snackBarHost.showSnackbar( + rebootToApply, actionLabel = reboot + ) + if (result == SnackbarResult.ActionPerformed) { + reboot() + } + } else { + val message = if (isChecked) failedDisable else failedEnable + snackBarHost.showSnackbar(message.format(module.name)) + } + } + }, onUpdate = { + scope.launch { + onModuleUpdate( + module, + updatedModule.third, + updatedModule.first, + "${module.name}-${updatedModule.second}.zip" + ) + } + }, onClick = { + onClickModule(it.id, it.name, it.hasWebUi) + }) + + // fix last item shadow incomplete in LazyColumn + Spacer(Modifier.height(1.dp)) + } + } + } + } + + DownloadListener(context, onInstallModule) + + PullRefreshIndicator( + refreshing = viewModel.isRefreshing, state = refreshState, modifier = Modifier.align( + Alignment.TopCenter + ) + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar() { + TopAppBar(title = { Text(stringResource(R.string.module)) }) +} + +@Composable +private fun ModuleItem( + module: ModuleViewModel.ModuleInfo, + isChecked: Boolean, + updateUrl: String, + onUninstall: (ModuleViewModel.ModuleInfo) -> Unit, + onCheckChanged: (Boolean) -> Unit, + onUpdate: (ModuleViewModel.ModuleInfo) -> Unit, + onClick: (ModuleViewModel.ModuleInfo) -> Unit +) { + ElevatedCard( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.elevatedCardColors(containerColor = MaterialTheme.colorScheme.surface) + ) { + + val textDecoration = if (!module.remove) null else TextDecoration.LineThrough + + Column(modifier = Modifier.clickable { onClick(module) }.padding(24.dp, 16.dp, 24.dp, 0.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + val moduleVersion = stringResource(id = R.string.module_version) + val moduleAuthor = stringResource(id = R.string.module_author) + + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text( + text = module.name, + fontSize = MaterialTheme.typography.titleMedium.fontSize, + fontWeight = FontWeight.SemiBold, + lineHeight = MaterialTheme.typography.bodySmall.lineHeight, + fontFamily = MaterialTheme.typography.titleMedium.fontFamily, + textDecoration = textDecoration, + ) + + Text( + text = "$moduleVersion: ${module.version}", + fontSize = MaterialTheme.typography.bodySmall.fontSize, + lineHeight = MaterialTheme.typography.bodySmall.lineHeight, + fontFamily = MaterialTheme.typography.bodySmall.fontFamily, + textDecoration = textDecoration + ) + + Text( + text = "$moduleAuthor: ${module.author}", + fontSize = MaterialTheme.typography.bodySmall.fontSize, + lineHeight = MaterialTheme.typography.bodySmall.lineHeight, + fontFamily = MaterialTheme.typography.bodySmall.fontFamily, + textDecoration = textDecoration + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + Switch( + enabled = !module.update, + checked = isChecked, + onCheckedChange = onCheckChanged + ) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = module.description, + fontSize = MaterialTheme.typography.bodySmall.fontSize, + fontFamily = MaterialTheme.typography.bodySmall.fontFamily, + lineHeight = MaterialTheme.typography.bodySmall.lineHeight, + fontWeight = MaterialTheme.typography.bodySmall.fontWeight, + overflow = TextOverflow.Ellipsis, + maxLines = 4, + textDecoration = textDecoration + ) + + + Spacer(modifier = Modifier.height(16.dp)) + + HorizontalDivider(thickness = Dp.Hairline) + + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(modifier = Modifier.weight(1f, true)) + + if (updateUrl.isNotEmpty()) { + Button( + modifier = Modifier + .padding(0.dp) + .defaultMinSize(52.dp, 32.dp), + onClick = { onUpdate(module) }, + shape = RoundedCornerShape(6.dp), + contentPadding = PaddingValues(0.dp) + ) { + Text( + fontFamily = MaterialTheme.typography.labelMedium.fontFamily, + fontSize = MaterialTheme.typography.labelMedium.fontSize, + text = stringResource(R.string.module_update), + ) + } + } + + TextButton( + enabled = !module.remove, + onClick = { onUninstall(module) }, + ) { + Text( + fontFamily = MaterialTheme.typography.labelMedium.fontFamily, + fontSize = MaterialTheme.typography.labelMedium.fontSize, + text = stringResource(R.string.uninstall), + ) + } + + if (module.hasWebUi) { + TextButton( + onClick = { onClick(module) }, + ) { + Text( + fontFamily = MaterialTheme.typography.labelMedium.fontFamily, + fontSize = MaterialTheme.typography.labelMedium.fontSize, + text = stringResource(R.string.open), + ) + } + } + } + } + } +} + +@Preview +@Composable +fun ModuleItemPreview() { + val module = ModuleViewModel.ModuleInfo( + id = "id", + name = "name", + version = "version", + versionCode = 1, + author = "author", + description = "I am a test module and i do nothing but show a very long description", + enabled = true, + update = true, + remove = true, + updateJson = "", + hasWebUi = false, + ) + ModuleItem(module, true, "", {}, {}, {}, {}) +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt new file mode 100644 index 000000000000..4f970f7ee331 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt @@ -0,0 +1,474 @@ +package me.weishu.kernelsu.ui.screen + +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.database.Cursor +import android.net.Uri +import android.provider.OpenableColumns +import android.util.Log +import android.widget.Toast +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.Undo +import androidx.compose.material.icons.filled.BugReport +import androidx.compose.material.icons.filled.Compress +import androidx.compose.material.icons.filled.ContactPage +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.DeleteForever +import androidx.compose.material.icons.filled.DeveloperMode +import androidx.compose.material.icons.filled.Fence +import androidx.compose.material.icons.filled.RemoveModerator +import androidx.compose.material.icons.filled.Save +import androidx.compose.material.icons.filled.Share +import androidx.compose.material.icons.filled.Update +import androidx.compose.material3.BottomSheetScaffold +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +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.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.LineHeightStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.content.FileProvider +import com.maxkeppeker.sheets.core.models.base.Header +import com.maxkeppeker.sheets.core.models.base.IconSource +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.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import me.weishu.kernelsu.BuildConfig +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.component.AboutDialog +import me.weishu.kernelsu.ui.component.ConfirmResult +import me.weishu.kernelsu.ui.component.DialogHandle +import me.weishu.kernelsu.ui.component.SwitchItem +import me.weishu.kernelsu.ui.component.rememberConfirmDialog +import me.weishu.kernelsu.ui.component.rememberCustomDialog +import me.weishu.kernelsu.ui.component.rememberLoadingDialog +import me.weishu.kernelsu.ui.screen.destinations.AppProfileTemplateScreenDestination +import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination +import me.weishu.kernelsu.ui.util.getBugreportFile +import me.weishu.kernelsu.ui.util.getFileNameFromUri +import me.weishu.kernelsu.ui.util.shrinkModules +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +/** + * @author weishu + * @date 2023/1/1. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Destination +@Composable +fun SettingScreen(navigator: DestinationsNavigator) { + Scaffold( + topBar = { + TopBar(onBack = { + navigator.popBackStack() + }) + } + ) { paddingValues -> + val aboutDialog = rememberCustomDialog { + AboutDialog(it) + } + val loadingDialog = rememberLoadingDialog() + val shrinkDialog = rememberConfirmDialog() + + Column( + modifier = Modifier + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + ) { + + val context = LocalContext.current + val scope = rememberCoroutineScope() + + val profileTemplate = stringResource(id = R.string.settings_profile_template) + ListItem( + leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) }, + headlineContent = { Text(profileTemplate) }, + supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) }, + modifier = Modifier.clickable { + navigator.navigate(AppProfileTemplateScreenDestination) + } + ) + + var umountChecked by rememberSaveable { + mutableStateOf(Natives.isDefaultUmountModules()) + } + SwitchItem( + icon = Icons.Filled.RemoveModerator, + title = stringResource(id = R.string.settings_umount_modules_default), + summary = stringResource(id = R.string.settings_umount_modules_default_summary), + checked = umountChecked + ) { + if (Natives.setDefaultUmountModules(it)) { + umountChecked = it + } + } + + val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + var checkUpdate by rememberSaveable { + mutableStateOf( + prefs.getBoolean("check_update", true) + ) + } + SwitchItem( + icon = Icons.Filled.Update, + title = stringResource(id = R.string.settings_check_update), + summary = stringResource(id = R.string.settings_check_update_summary), + checked = checkUpdate + ) { + prefs.edit().putBoolean("check_update", it).apply() + checkUpdate = it + } + + var enableWebDebugging by rememberSaveable { + mutableStateOf( + prefs.getBoolean("enable_web_debugging", false) + ) + } + SwitchItem( + icon = Icons.Filled.DeveloperMode, + title = stringResource(id = R.string.enable_web_debugging), + summary = stringResource(id = R.string.enable_web_debugging_summary), + checked = enableWebDebugging + ) { + prefs.edit().putBoolean("enable_web_debugging", it).apply() + enableWebDebugging = it + } + + var showBottomsheet by remember { mutableStateOf(false) } + + ListItem( + leadingContent = { + Icon( + Icons.Filled.BugReport, + stringResource(id = R.string.send_log) + ) + }, + headlineContent = { Text(stringResource(id = R.string.send_log)) }, + modifier = Modifier.clickable { + showBottomsheet = true + } + ) + if (showBottomsheet){ + ModalBottomSheet( + onDismissRequest = { showBottomsheet = false }, + content = { + Row(modifier = Modifier.padding(10.dp) + .align(Alignment.CenterHorizontally) + + ) { + Box{ + Column( + modifier = Modifier.padding(16.dp) + .clickable { + scope.launch { + val bugreport = loadingDialog.withLoading { + withContext(Dispatchers.IO) { + getBugreportFile(context) + } + } + + val uri: Uri = + FileProvider.getUriForFile( + context, + "${BuildConfig.APPLICATION_ID}.fileprovider", + bugreport + ) + val filename = getFileNameFromUri(context , uri) + val savefile = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/zip" + putExtra(Intent.EXTRA_STREAM, uri) + putExtra(Intent.EXTRA_TITLE, filename) + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + } + context.startActivity( + Intent.createChooser( + savefile, + context.getString(R.string.save_log) + ) + ) + } + } + ) { + Icon( + Icons.Filled.Save, + contentDescription = null, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + Text( + text = stringResource(id = R.string.save_log), + modifier = Modifier.padding(top = 16.dp), + textAlign = TextAlign.Center.also { + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None + ) + } + + ) + } + + } + Box{ + Column( + modifier = Modifier.padding(16.dp) + .clickable { + scope.launch { + val bugreport = loadingDialog.withLoading { + withContext(Dispatchers.IO) { + getBugreportFile(context) + } + } + + val uri: Uri = + FileProvider.getUriForFile( + context, + "${BuildConfig.APPLICATION_ID}.fileprovider", + bugreport + ) + + val shareIntent = Intent(Intent.ACTION_SEND) + shareIntent.putExtra(Intent.EXTRA_STREAM, uri) + shareIntent.setDataAndType(uri, "application/zip") + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + + context.startActivity( + Intent.createChooser( + shareIntent, + context.getString(R.string.send_log) + ) + ) + } + } + ) { + Icon( + Icons.Filled.Share, + contentDescription = null, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + Text( + text = stringResource(id = R.string.send_log), + modifier = Modifier.padding(top = 16.dp), + textAlign = TextAlign.Center.also { + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None + ) + } + + ) + } + + } + } + } + ) + + + } + + val shrink = stringResource(id = R.string.shrink_sparse_image) + val shrinkMessage = stringResource(id = R.string.shrink_sparse_image_message) + ListItem( + leadingContent = { + Icon( + Icons.Filled.Compress, + shrink + ) + }, + headlineContent = { Text(shrink) }, + modifier = Modifier.clickable { + scope.launch { + val result = + shrinkDialog.awaitConfirm(title = shrink, content = shrinkMessage) + if (result == ConfirmResult.Confirmed) { + loadingDialog.withLoading { + shrinkModules() + } + } + } + } + ) + + val lkmMode = + Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode + if (lkmMode) { + UninstallItem(navigator) { + loadingDialog.withLoading(it) + } + } + + val about = stringResource(id = R.string.about) + ListItem( + leadingContent = { + Icon( + Icons.Filled.ContactPage, + stringResource(id = R.string.about) + ) + }, + headlineContent = { Text(about) }, + modifier = Modifier.clickable { + aboutDialog.show() + } + ) + } + } +} +@Composable +fun UninstallItem( + navigator: DestinationsNavigator, + withLoading: suspend (suspend () -> Unit) -> Unit +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + val uninstallConfirmDialog = rememberConfirmDialog() + val showTodo = { + Toast.makeText(context, "TODO", Toast.LENGTH_SHORT).show() + } + val uninstallDialog = rememberUninstallDialog { uninstallType -> + scope.launch { + val result = uninstallConfirmDialog.awaitConfirm( + title = context.getString(uninstallType.title), + content = context.getString(uninstallType.message) + ) + if (result == ConfirmResult.Confirmed) { + withLoading { + when (uninstallType) { + UninstallType.TEMPORARY -> showTodo() + UninstallType.PERMANENT -> navigator.navigate( + FlashScreenDestination(FlashIt.FlashUninstall) + ) + + UninstallType.RESTORE_STOCK_IMAGE -> navigator.navigate( + FlashScreenDestination(FlashIt.FlashRestore) + ) + + UninstallType.NONE -> Unit + } + } + } + } + } + val uninstall = stringResource(id = R.string.settings_uninstall) + ListItem( + leadingContent = { + Icon( + Icons.Filled.Delete, + uninstall + ) + }, + headlineContent = { Text(uninstall) }, + modifier = Modifier.clickable { + uninstallDialog.show() + } + ) +} + +enum class UninstallType(val title: Int, val message: Int, val icon: ImageVector) { + TEMPORARY( + R.string.settings_uninstall_temporary, + R.string.settings_uninstall_temporary_message, + Icons.Filled.Delete + ), + PERMANENT( + R.string.settings_uninstall_permanent, + R.string.settings_uninstall_permanent_message, + Icons.Filled.DeleteForever + ), + RESTORE_STOCK_IMAGE( + R.string.settings_restore_stock_image, + R.string.settings_restore_stock_image_message, + Icons.AutoMirrored.Filled.Undo + ), + NONE(0, 0, Icons.Filled.Delete) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle { + return rememberCustomDialog { dismiss -> + val options = listOf( + // UninstallType.TEMPORARY, + UninstallType.PERMANENT, + UninstallType.RESTORE_STOCK_IMAGE + ) + val listOptions = options.map { + ListOption( + titleText = stringResource(it.title), + subtitleText = if (it.message != 0) stringResource(it.message) else null, + icon = IconSource(it.icon) + ) + } + + var selection = UninstallType.NONE + ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = { + if (selection != UninstallType.NONE) { + onSelected(selection) + } + }, onCloseRequest = { + dismiss() + }), header = Header.Default( + title = stringResource(R.string.settings_uninstall), + ), selection = ListSelection.Single( + showRadioButtons = false, + options = listOptions, + ) { index, _ -> + selection = options[index] + }) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar(onBack: () -> Unit = {}) { + TopAppBar( + title = { Text(stringResource(R.string.settings)) }, + navigationIcon = { + IconButton( + onClick = onBack + ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } + }, + ) +} + +@Preview +@Composable +private fun SettingsPreview() { + SettingScreen(EmptyDestinationsNavigator) +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/SuperUser.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/SuperUser.kt new file mode 100644 index 000000000000..2eb2e770bb9d --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/SuperUser.kt @@ -0,0 +1,185 @@ +package me.weishu.kernelsu.ui.screen + +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.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import kotlinx.coroutines.launch +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.component.SearchAppBar +import me.weishu.kernelsu.ui.screen.destinations.AppProfileScreenDestination +import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel + +@OptIn(ExperimentalMaterialApi::class) +@Destination +@Composable +fun SuperUserScreen(navigator: DestinationsNavigator) { + val viewModel = viewModel() + val scope = rememberCoroutineScope() + + LaunchedEffect(Unit) { + if (viewModel.appList.isEmpty()) { + viewModel.fetchAppList() + } + } + + Scaffold( + topBar = { + SearchAppBar( + title = { Text(stringResource(R.string.superuser)) }, + searchText = viewModel.search, + onSearchTextChange = { viewModel.search = it }, + onClearClick = { viewModel.search = "" }, + dropdownContent = { + var showDropdown by remember { mutableStateOf(false) } + + IconButton( + onClick = { showDropdown = true }, + ) { + Icon( + imageVector = Icons.Filled.MoreVert, + contentDescription = stringResource(id = R.string.settings) + ) + + DropdownMenu(expanded = showDropdown, onDismissRequest = { + showDropdown = false + }) { + DropdownMenuItem(text = { + Text(stringResource(R.string.refresh)) + }, onClick = { + scope.launch { + viewModel.fetchAppList() + } + showDropdown = false + }) + DropdownMenuItem(text = { + Text( + if (viewModel.showSystemApps) { + stringResource(R.string.hide_system_apps) + } else { + stringResource(R.string.show_system_apps) + } + ) + }, onClick = { + viewModel.showSystemApps = !viewModel.showSystemApps + showDropdown = false + }) + } + } + }, + ) + } + ) { innerPadding -> + val refreshState = rememberPullRefreshState( + refreshing = viewModel.isRefreshing, + onRefresh = { scope.launch { viewModel.fetchAppList() } }, + ) + Box( + modifier = Modifier + .padding(innerPadding) + .pullRefresh(refreshState) + ) { + LazyColumn(Modifier.fillMaxSize()) { + items(viewModel.appList, key = { it.packageName + it.uid }) { app -> + AppItem(app) { + navigator.navigate(AppProfileScreenDestination(app)) + } + + } + } + + PullRefreshIndicator( + refreshing = viewModel.isRefreshing, + state = refreshState, + modifier = Modifier.align(Alignment.TopCenter) + ) + } + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +private fun AppItem( + app: SuperUserViewModel.AppInfo, + onClickListener: () -> Unit, +) { + ListItem( + modifier = Modifier.clickable(onClick = onClickListener), + headlineContent = { Text(app.label) }, + supportingContent = { + Column { + Text(app.packageName) + FlowRow { + if (app.allowSu) { + LabelText(label = "ROOT") + } else { + if (Natives.uidShouldUmount(app.uid)) { + LabelText(label = "UMOUNT") + } + } + if (app.hasCustomProfile) { + LabelText(label = "CUSTOM") + } + } + } + }, + leadingContent = { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(app.packageInfo) + .crossfade(true) + .build(), + contentDescription = app.label, + modifier = Modifier + .padding(4.dp) + .width(48.dp) + .height(48.dp) + ) + }, + ) +} + +@Composable +fun LabelText(label: String) { + Box( + modifier = Modifier + .padding(top = 4.dp, end = 4.dp) + .background( + Color.Black, + shape = RoundedCornerShape(4.dp) + ) + ) { + Text( + text = label, + modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp), + style = TextStyle( + fontSize = 8.sp, + color = Color.White, + ) + ) + } +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Template.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Template.kt new file mode 100644 index 000000000000..bf353b0afa84 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Template.kt @@ -0,0 +1,259 @@ +package me.weishu.kernelsu.ui.screen + +import android.widget.Toast +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.ImportExport +import androidx.compose.material.icons.filled.Sync +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +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.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.ramcosta.composedestinations.result.ResultRecipient +import com.ramcosta.composedestinations.result.getOr +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.screen.destinations.TemplateEditorScreenDestination +import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel + +/** + * @author weishu + * @date 2023/10/20. + */ + +@OptIn(ExperimentalMaterialApi::class) +@Destination +@Composable +fun AppProfileTemplateScreen( + navigator: DestinationsNavigator, + resultRecipient: ResultRecipient +) { + val viewModel = viewModel() + val scope = rememberCoroutineScope() + + LaunchedEffect(Unit) { + if (viewModel.templateList.isEmpty()) { + viewModel.fetchTemplates() + } + } + + // handle result from TemplateEditorScreen, refresh if needed + resultRecipient.onNavResult { result -> + if (result.getOr { false }) { + scope.launch { viewModel.fetchTemplates() } + } + } + + Scaffold( + topBar = { + val clipboardManager = LocalClipboardManager.current + val context = LocalContext.current + val showToast = fun(msg: String) { + scope.launch(Dispatchers.Main) { + Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() + } + } + TopBar(onBack = { navigator.popBackStack() }, + onSync = { + scope.launch { viewModel.fetchTemplates(true) } + }, + onImport = { + clipboardManager.getText()?.text?.let { + if (it.isEmpty()) { + showToast(context.getString(R.string.app_profile_template_import_empty)) + return@let + } + scope.launch { + viewModel.importTemplates( + it, { + showToast(context.getString(R.string.app_profile_template_import_success)) + viewModel.fetchTemplates(false) + }, + showToast + ) + } + } + }, + onExport = { + scope.launch { + viewModel.exportTemplates( + { + showToast(context.getString(R.string.app_profile_template_export_empty)) + } + ) { + clipboardManager.setText(AnnotatedString(it)) + } + } + } + ) + }, + floatingActionButton = { + ExtendedFloatingActionButton( + onClick = { + navigator.navigate( + TemplateEditorScreenDestination( + TemplateViewModel.TemplateInfo(), + false + ) + ) + }, + icon = { Icon(Icons.Filled.Add, null) }, + text = { Text(stringResource(id = R.string.app_profile_template_create)) }, + ) + }, + ) { innerPadding -> + val refreshState = rememberPullRefreshState( + refreshing = viewModel.isRefreshing, + onRefresh = { scope.launch { viewModel.fetchTemplates() } }, + ) + Box( + modifier = Modifier + .padding(innerPadding) + .pullRefresh(refreshState) + ) { + LazyColumn(Modifier.fillMaxSize(), contentPadding = remember { + PaddingValues(bottom = 16.dp + 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */) + }) { + items(viewModel.templateList, key = { it.id }) { app -> + TemplateItem(navigator, app) + } + } + + PullRefreshIndicator( + refreshing = viewModel.isRefreshing, + state = refreshState, + modifier = Modifier.align(Alignment.TopCenter) + ) + } + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +private fun TemplateItem( + navigator: DestinationsNavigator, + template: TemplateViewModel.TemplateInfo +) { + ListItem( + modifier = Modifier + .clickable { + navigator.navigate(TemplateEditorScreenDestination(template, !template.local)) + }, + headlineContent = { Text(template.name) }, + supportingContent = { + Column { + Text( + text = "${template.id}${if (template.author.isEmpty()) "" else "@${template.author}"}", + style = MaterialTheme.typography.bodySmall, + fontSize = MaterialTheme.typography.bodySmall.fontSize, + ) + Text(template.description) + FlowRow { + LabelText(label = "UID: ${template.uid}") + LabelText(label = "GID: ${template.gid}") + LabelText(label = template.context) + if (template.local) { + LabelText(label = "local") + } else { + LabelText(label = "remote") + } + } + } + }, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar( + onBack: () -> Unit, + onSync: () -> Unit = {}, + onImport: () -> Unit = {}, + onExport: () -> Unit = {} +) { + TopAppBar( + title = { + Text(stringResource(R.string.settings_profile_template)) + }, + navigationIcon = { + IconButton( + onClick = onBack + ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } + }, + actions = { + IconButton(onClick = onSync) { + Icon( + Icons.Filled.Sync, + contentDescription = stringResource(id = R.string.app_profile_template_sync) + ) + } + + var showDropdown by remember { mutableStateOf(false) } + IconButton(onClick = { + showDropdown = true + }) { + Icon( + imageVector = Icons.Filled.ImportExport, + contentDescription = stringResource(id = R.string.app_profile_import_export) + ) + + DropdownMenu(expanded = showDropdown, onDismissRequest = { + showDropdown = false + }) { + DropdownMenuItem(text = { + Text(stringResource(id = R.string.app_profile_import_from_clipboard)) + }, onClick = { + onImport() + showDropdown = false + }) + DropdownMenuItem(text = { + Text(stringResource(id = R.string.app_profile_export_to_clipboard)) + }, onClick = { + onExport() + showDropdown = false + }) + } + } + } + ) +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/TemplateEditor.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/TemplateEditor.kt new file mode 100644 index 000000000000..b6b7cc8028ec --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/TemplateEditor.kt @@ -0,0 +1,322 @@ +package me.weishu.kernelsu.ui.screen + +import android.widget.Toast +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.DeleteForever +import androidx.compose.material.icons.filled.Save +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInteropFilter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.result.ResultBackNavigator +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.component.profile.RootProfileConfig +import me.weishu.kernelsu.ui.util.deleteAppProfileTemplate +import me.weishu.kernelsu.ui.util.getAppProfileTemplate +import me.weishu.kernelsu.ui.util.setAppProfileTemplate +import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel +import me.weishu.kernelsu.ui.viewmodel.toJSON + +/** + * @author weishu + * @date 2023/10/20. + */ +@OptIn(ExperimentalComposeUiApi::class) +@Destination +@Composable +fun TemplateEditorScreen( + navigator: ResultBackNavigator, + initialTemplate: TemplateViewModel.TemplateInfo, + readOnly: Boolean = true, +) { + + val isCreation = initialTemplate.id.isBlank() + val autoSave = !isCreation + + var template by rememberSaveable { + mutableStateOf(initialTemplate) + } + + BackHandler { + navigator.navigateBack(result = !readOnly) + } + + Scaffold( + topBar = { + val author = + if (initialTemplate.author.isNotEmpty()) "@${initialTemplate.author}" else "" + val readOnlyHint = if (readOnly) { + " - ${stringResource(id = R.string.app_profile_template_readonly)}" + } else { + "" + } + val titleSummary = "${initialTemplate.id}$author$readOnlyHint" + val saveTemplateFailed = stringResource(id = R.string.app_profile_template_save_failed) + val context = LocalContext.current + + TopBar( + title = if (isCreation) { + stringResource(R.string.app_profile_template_create) + } else if (readOnly) { + stringResource(R.string.app_profile_template_view) + } else { + stringResource(R.string.app_profile_template_edit) + }, + readOnly = readOnly, + summary = titleSummary, + onBack = { navigator.navigateBack(result = !readOnly) }, + onDelete = { + if (deleteAppProfileTemplate(template.id)) { + navigator.navigateBack(result = true) + } + }, + onSave = { + if (saveTemplate(template, isCreation)) { + navigator.navigateBack(result = true) + } else { + Toast.makeText(context, saveTemplateFailed, Toast.LENGTH_SHORT).show() + } + }) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .verticalScroll(rememberScrollState()) + .pointerInteropFilter { + // disable click and ripple if readOnly + readOnly + } + ) { + if (isCreation) { + var errorHint by remember { + mutableStateOf("") + } + val idConflictError = stringResource(id = R.string.app_profile_template_id_exist) + val idInvalidError = stringResource(id = R.string.app_profile_template_id_invalid) + TextEdit( + label = stringResource(id = R.string.app_profile_template_id), + text = template.id, + errorHint = errorHint, + isError = errorHint.isNotEmpty() + ) { value -> + errorHint = if (isTemplateExist(value)) { + idConflictError + } else if (!isValidTemplateId(value)) { + idInvalidError + } else { + "" + } + template = template.copy(id = value) + } + } + + TextEdit( + label = stringResource(id = R.string.app_profile_template_name), + text = template.name + ) { value -> + template.copy(name = value).run { + if (autoSave) { + if (!saveTemplate(this)) { + // failed + return@run + } + } + template = this + } + } + TextEdit( + label = stringResource(id = R.string.app_profile_template_description), + text = template.description + ) { value -> + template.copy(description = value).run { + if (autoSave) { + if (!saveTemplate(this)) { + // failed + return@run + } + } + template = this + } + } + + RootProfileConfig(fixedName = true, + profile = toNativeProfile(template), + onProfileChange = { + template.copy( + uid = it.uid, + gid = it.gid, + groups = it.groups, + capabilities = it.capabilities, + context = it.context, + namespace = it.namespace, + rules = it.rules.split("\n") + ).run { + if (autoSave) { + if (!saveTemplate(this)) { + // failed + return@run + } + } + template = this + } + }) + } + } +} + +fun toNativeProfile(templateInfo: TemplateViewModel.TemplateInfo): Natives.Profile { + return Natives.Profile().copy(rootTemplate = templateInfo.id, + uid = templateInfo.uid, + gid = templateInfo.gid, + groups = templateInfo.groups, + capabilities = templateInfo.capabilities, + context = templateInfo.context, + namespace = templateInfo.namespace, + rules = templateInfo.rules.joinToString("\n").ifBlank { "" }) +} + +fun isTemplateValid(template: TemplateViewModel.TemplateInfo): Boolean { + if (template.id.isBlank()) { + return false + } + + if (!isValidTemplateId(template.id)) { + return false + } + + return true +} + +fun saveTemplate(template: TemplateViewModel.TemplateInfo, isCreation: Boolean = false): Boolean { + if (!isTemplateValid(template)) { + return false + } + + if (isCreation && isTemplateExist(template.id)) { + return false + } + + val json = template.toJSON() + json.put("local", true) + return setAppProfileTemplate(template.id, json.toString()) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar( + title: String, + readOnly: Boolean, + summary: String = "", + onBack: () -> Unit, + onDelete: () -> Unit = {}, + onSave: () -> Unit = {} +) { + TopAppBar(title = { + Column { + Text(title) + if (summary.isNotBlank()) { + Text( + text = summary, + style = MaterialTheme.typography.bodyMedium, + ) + } + } + }, navigationIcon = { + IconButton( + onClick = onBack + ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } + }, actions = { + if (readOnly) { + return@TopAppBar + } + IconButton(onClick = onDelete) { + Icon( + Icons.Filled.DeleteForever, + contentDescription = stringResource(id = R.string.app_profile_template_delete) + ) + } + IconButton(onClick = onSave) { + Icon( + imageVector = Icons.Filled.Save, + contentDescription = stringResource(id = R.string.app_profile_template_save) + ) + } + }) +} + +@Composable +private fun TextEdit( + label: String, + text: String, + errorHint: String = "", + isError: Boolean = false, + onValueChange: (String) -> Unit = {} +) { + ListItem(headlineContent = { + val keyboardController = LocalSoftwareKeyboardController.current + OutlinedTextField( + value = text, + modifier = Modifier.fillMaxWidth(), + label = { Text(label) }, + suffix = + if (errorHint.isNotBlank()) { + { + Text( + text = if (isError) errorHint else "", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.error + ) + } + } else { + null + }, + isError = isError, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Next + ), + keyboardActions = KeyboardActions(onDone = { + keyboardController?.hide() + }), + onValueChange = onValueChange + ) + }) +} + +private fun isValidTemplateId(id: String): Boolean { + return Regex("""^([A-Za-z]{1}[A-Za-z\d_]*\.)*[A-Za-z][A-Za-z\d_]*$""").matches(id) +} + +private fun isTemplateExist(id: String): Boolean { + return getAppProfileTemplate(id).isNotBlank() +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Color.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Color.kt new file mode 100644 index 000000000000..155cf4e248bc --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Color.kt @@ -0,0 +1,10 @@ +package me.weishu.kernelsu.ui.theme + +import androidx.compose.ui.graphics.Color + +val YELLOW = Color(0xFFeed502) +val YELLOW_LIGHT = Color(0xFFffff52) +val SECONDARY_LIGHT = Color(0xffa9817f) + +val YELLOW_DARK = Color(0xFFb7a400) +val SECONDARY_DARK = Color(0xFF4c2b2b) \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Theme.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Theme.kt new file mode 100644 index 000000000000..3b3945d0cdc5 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Theme.kt @@ -0,0 +1,64 @@ +package me.weishu.kernelsu.ui.theme + +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.google.accompanist.systemuicontroller.rememberSystemUiController + +private val DarkColorScheme = darkColorScheme( + primary = YELLOW, + secondary = YELLOW_DARK, + tertiary = SECONDARY_DARK +) + +private val LightColorScheme = lightColorScheme( + primary = YELLOW, + secondary = YELLOW_LIGHT, + tertiary = SECONDARY_LIGHT +) + +@Composable +fun KernelSUTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + val systemUiController = rememberSystemUiController() + SideEffect { + systemUiController.setStatusBarColor( + color = colorScheme.surface, + darkIcons = !darkTheme + ) + + // To match the App Navbar color + systemUiController.setNavigationBarColor( + color = colorScheme.surfaceColorAtElevation(8.dp), + darkIcons = !darkTheme, + ) + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Type.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Type.kt new file mode 100644 index 000000000000..e8d73313a330 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/theme/Type.kt @@ -0,0 +1,33 @@ +package me.weishu.kernelsu.ui.theme + +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = androidx.compose.material3.Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/CompositionProvider.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/CompositionProvider.kt new file mode 100644 index 000000000000..c1b57483a02f --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/CompositionProvider.kt @@ -0,0 +1,8 @@ +package me.weishu.kernelsu.ui.util + +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.compositionLocalOf + +val LocalSnackbarHost = compositionLocalOf { + error("CompositionLocal LocalSnackbarController not present") +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/Downloader.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/Downloader.kt new file mode 100644 index 000000000000..954cac50da30 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/Downloader.kt @@ -0,0 +1,149 @@ +package me.weishu.kernelsu.ui.util + +import android.annotation.SuppressLint +import android.app.DownloadManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.Uri +import android.os.Build +import android.os.Environment +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import me.weishu.kernelsu.ui.util.module.LatestVersionInfo + +/** + * @author weishu + * @date 2023/6/22. + */ +@SuppressLint("Range") +fun download( + context: Context, + url: String, + fileName: String, + description: String, + onDownloaded: (Uri) -> Unit = {}, + onDownloading: () -> Unit = {} +) { + val downloadManager = + context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + + val query = DownloadManager.Query() + query.setFilterByStatus(DownloadManager.STATUS_RUNNING or DownloadManager.STATUS_PAUSED or DownloadManager.STATUS_PENDING) + downloadManager.query(query).use { cursor -> + while (cursor.moveToNext()) { + val uri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI)) + val localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)) + val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) + val columnTitle = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE)) + if (url == uri || fileName == columnTitle) { + if (status == DownloadManager.STATUS_RUNNING || status == DownloadManager.STATUS_PENDING) { + onDownloading() + return + } else if (status == DownloadManager.STATUS_SUCCESSFUL) { + onDownloaded(Uri.parse(localUri)) + return + } + } + } + } + + val request = DownloadManager.Request(Uri.parse(url)) + .setDestinationInExternalPublicDir( + Environment.DIRECTORY_DOWNLOADS, + fileName + ) + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + .setMimeType("application/zip") + .setTitle(fileName) + .setDescription(description) + + downloadManager.enqueue(request) +} + +fun checkNewVersion(): LatestVersionInfo { + val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest" + // default null value if failed + val defaultValue = LatestVersionInfo() + runCatching { + okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute() + .use { response -> + if (!response.isSuccessful) { + return defaultValue + } + val body = response.body?.string() ?: return defaultValue + val json = org.json.JSONObject(body) + val changelog = json.optString("body") + + val assets = json.getJSONArray("assets") + for (i in 0 until assets.length()) { + val asset = assets.getJSONObject(i) + val name = asset.getString("name") + if (!name.endsWith(".apk")) { + continue + } + + val regex = Regex("v(.+?)_(\\d+)-") + val matchResult = regex.find(name) ?: continue + val versionName = matchResult.groupValues[1] + val versionCode = matchResult.groupValues[2].toInt() + val downloadUrl = asset.getString("browser_download_url") + + return LatestVersionInfo( + versionCode, + downloadUrl, + changelog + ) + } + + } + } + return defaultValue +} + +@Composable +fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) { + DisposableEffect(context) { + val receiver = object : BroadcastReceiver() { + @SuppressLint("Range") + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) { + val id = intent.getLongExtra( + DownloadManager.EXTRA_DOWNLOAD_ID, -1 + ) + val query = DownloadManager.Query().setFilterById(id) + val downloadManager = + context?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val cursor = downloadManager.query(query) + if (cursor.moveToFirst()) { + val status = cursor.getInt( + cursor.getColumnIndex(DownloadManager.COLUMN_STATUS) + ) + if (status == DownloadManager.STATUS_SUCCESSFUL) { + val uri = cursor.getString( + cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI) + ) + onDownloaded(Uri.parse(uri)) + } + } + } + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver( + receiver, + IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), + Context.RECEIVER_EXPORTED + ) + } else { + context.registerReceiver( + receiver, + IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) + ) + } + onDispose { + context.unregisterReceiver(receiver) + } + } +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/HanziToPinyin.java b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/HanziToPinyin.java new file mode 100644 index 000000000000..d3d57cefe2ea --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/HanziToPinyin.java @@ -0,0 +1,576 @@ +package me.weishu.kernelsu.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. + *

+ * Currently this file is aligned to zh.txt in ICU 4.6 + */ +public class HanziToPinyin { + 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. + *

+ * Each unihans is the first one within same pinyin when collator is zh_CN. + */ + public static final char[] UNIHANS = { + '\u963f', '\u54ce', '\u5b89', '\u80ae', '\u51f9', '\u516b', + '\u6300', '\u6273', '\u90a6', '\u52f9', '\u9642', '\u5954', + '\u4f3b', '\u5c44', '\u8fb9', '\u706c', '\u618b', '\u6c43', + '\u51ab', '\u7676', '\u5cec', '\u5693', '\u5072', '\u53c2', + '\u4ed3', '\u64a1', '\u518a', '\u5d7e', '\u66fd', '\u66fe', + '\u5c64', '\u53c9', '\u8286', '\u8fbf', '\u4f25', '\u6284', + '\u8f66', '\u62bb', '\u6c88', '\u6c89', '\u9637', '\u5403', + '\u5145', '\u62bd', '\u51fa', '\u6b3b', '\u63e3', '\u5ddb', + '\u5205', '\u5439', '\u65fe', '\u9034', '\u5472', '\u5306', + '\u51d1', '\u7c97', '\u6c46', '\u5d14', '\u90a8', '\u6413', + '\u5491', '\u5446', '\u4e39', '\u5f53', '\u5200', '\u561a', + '\u6265', '\u706f', '\u6c10', '\u55f2', '\u7538', '\u5201', + '\u7239', '\u4e01', '\u4e1f', '\u4e1c', '\u543a', '\u53be', + '\u8011', '\u8968', '\u5428', '\u591a', '\u59b8', '\u8bf6', + '\u5940', '\u97a5', '\u513f', '\u53d1', '\u5e06', '\u531a', + '\u98de', '\u5206', '\u4e30', '\u8985', '\u4ecf', '\u7d11', + '\u4f15', '\u65ee', '\u4f85', '\u7518', '\u5188', '\u768b', + '\u6208', '\u7ed9', '\u6839', '\u522f', '\u5de5', '\u52fe', + '\u4f30', '\u74dc', '\u4e56', '\u5173', '\u5149', '\u5f52', + '\u4e28', '\u5459', '\u54c8', '\u548d', '\u4f44', '\u592f', + '\u8320', '\u8bc3', '\u9ed2', '\u62eb', '\u4ea8', '\u5677', + '\u53ff', '\u9f41', '\u4e6f', '\u82b1', '\u6000', '\u72bf', + '\u5ddf', '\u7070', '\u660f', '\u5419', '\u4e0c', '\u52a0', + '\u620b', '\u6c5f', '\u827d', '\u9636', '\u5dfe', '\u5755', + '\u5182', '\u4e29', '\u51e5', '\u59e2', '\u5658', '\u519b', + '\u5494', '\u5f00', '\u520a', '\u5ffc', '\u5c3b', '\u533c', + '\u808e', '\u52a5', '\u7a7a', '\u62a0', '\u625d', '\u5938', + '\u84af', '\u5bbd', '\u5321', '\u4e8f', '\u5764', '\u6269', + '\u5783', '\u6765', '\u5170', '\u5577', '\u635e', '\u808b', + '\u52d2', '\u5d1a', '\u5215', '\u4fe9', '\u5941', '\u826f', + '\u64a9', '\u5217', '\u62ce', '\u5222', '\u6e9c', '\u56d6', + '\u9f99', '\u779c', '\u565c', '\u5a08', '\u7567', '\u62a1', + '\u7f57', '\u5463', '\u5988', '\u57cb', '\u5ada', '\u7264', + '\u732b', '\u4e48', '\u5445', '\u95e8', '\u753f', '\u54aa', + '\u5b80', '\u55b5', '\u4e5c', '\u6c11', '\u540d', '\u8c2c', + '\u6478', '\u54de', '\u6bea', '\u55ef', '\u62cf', '\u8149', + '\u56e1', '\u56d4', '\u5b6c', '\u7592', '\u5a1e', '\u6041', + '\u80fd', '\u59ae', '\u62c8', '\u5b22', '\u9e1f', '\u634f', + '\u56dc', '\u5b81', '\u599e', '\u519c', '\u7fba', '\u5974', + '\u597b', '\u759f', '\u9ec1', '\u90cd', '\u5594', '\u8bb4', + '\u5991', '\u62cd', '\u7705', '\u4e53', '\u629b', '\u5478', + '\u55b7', '\u5309', '\u4e15', '\u56e8', '\u527d', '\u6c15', + '\u59d8', '\u4e52', '\u948b', '\u5256', '\u4ec6', '\u4e03', + '\u6390', '\u5343', '\u545b', '\u6084', '\u767f', '\u4eb2', + '\u72c5', '\u828e', '\u4e18', '\u533a', '\u5cd1', '\u7f3a', + '\u590b', '\u5465', '\u7a63', '\u5a06', '\u60f9', '\u4eba', + '\u6254', '\u65e5', '\u8338', '\u53b9', '\u909a', '\u633c', + '\u5827', '\u5a51', '\u77a4', '\u637c', '\u4ee8', '\u6be2', + '\u4e09', '\u6852', '\u63bb', '\u95aa', '\u68ee', '\u50e7', + '\u6740', '\u7b5b', '\u5c71', '\u4f24', '\u5f30', '\u5962', + '\u7533', '\u8398', '\u6552', '\u5347', '\u5c38', '\u53ce', + '\u4e66', '\u5237', '\u8870', '\u95e9', '\u53cc', '\u8c01', + '\u542e', '\u8bf4', '\u53b6', '\u5fea', '\u635c', '\u82cf', + '\u72fb', '\u590a', '\u5b59', '\u5506', '\u4ed6', '\u56fc', + '\u574d', '\u6c64', '\u5932', '\u5fd1', '\u71a5', '\u5254', + '\u5929', '\u65eb', '\u5e16', '\u5385', '\u56f2', '\u5077', + '\u51f8', '\u6e4d', '\u63a8', '\u541e', '\u4e47', '\u7a75', + '\u6b6a', '\u5f2f', '\u5c23', '\u5371', '\u6637', '\u7fc1', + '\u631d', '\u4e4c', '\u5915', '\u8672', '\u4eda', '\u4e61', + '\u7071', '\u4e9b', '\u5fc3', '\u661f', '\u51f6', '\u4f11', + '\u5401', '\u5405', '\u524a', '\u5743', '\u4e2b', '\u6079', + '\u592e', '\u5e7a', '\u503b', '\u4e00', '\u56d9', '\u5e94', + '\u54df', '\u4f63', '\u4f18', '\u625c', '\u56e6', '\u66f0', + '\u6655', '\u7b60', '\u7b7c', '\u5e00', '\u707d', '\u5142', + '\u5328', '\u50ae', '\u5219', '\u8d3c', '\u600e', '\u5897', + '\u624e', '\u635a', '\u6cbe', '\u5f20', '\u957f', '\u9577', + '\u4f4b', '\u8707', '\u8d1e', '\u4e89', '\u4e4b', '\u5cd9', + '\u5ea2', '\u4e2d', '\u5dde', '\u6731', '\u6293', '\u62fd', + '\u4e13', '\u5986', '\u96b9', '\u5b92', '\u5353', '\u4e72', + '\u5b97', '\u90b9', '\u79df', '\u94bb', '\u539c', '\u5c0a', + '\u6628', '\u5159', '\u9fc3', '\u9fc4',}; + + /** + * Pinyin array. + *

+ * 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 = "\u963F"; + private static final String LAST_PINYIN_UNIHAN = "\u9FFF"; + + private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA); + + private static HanziToPinyin sInstance; + private final boolean mHasChinaCollator; + + 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; + } + + protected HanziToPinyin(boolean hasChinaCollator) { + mHasChinaCollator = hasChinaCollator; + } + + 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 get(final String input) { + ArrayList 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 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 tokens = get(string); + for (Token token : tokens) { + sb.append(token.target); + } + return sb.toString().toLowerCase(); + } +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/HyperlinkText.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/HyperlinkText.kt new file mode 100644 index 000000000000..4473b828aa1a --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/HyperlinkText.kt @@ -0,0 +1,87 @@ +package me.weishu.kernelsu.ui.util + +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextDecoration +import java.util.regex.Pattern + +@Composable +fun LinkifyText( + text: String, + modifier: Modifier = Modifier +) { + val uriHandler = LocalUriHandler.current + val layoutResult = remember { + mutableStateOf(null) + } + val linksList = extractUrls(text) + val annotatedString = buildAnnotatedString { + append(text) + linksList.forEach { + addStyle( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline + ), + start = it.start, + end = it.end + ) + addStringAnnotation( + tag = "URL", + annotation = it.url, + start = it.start, + end = it.end + ) + } + } + Text( + text = annotatedString, + modifier = modifier.pointerInput(Unit) { + detectTapGestures { offsetPosition -> + layoutResult.value?.let { + val position = it.getOffsetForPosition(offsetPosition) + annotatedString.getStringAnnotations(position, position).firstOrNull() + ?.let { result -> + if (result.tag == "URL") { + uriHandler.openUri(result.item) + } + } + } + } + }, + onTextLayout = { layoutResult.value = it } + ) +} + +private val urlPattern: Pattern = Pattern.compile( + "(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" + + "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*" + + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)", + Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL +) + +private data class LinkInfo( + val url: String, + val start: Int, + val end: Int +) + +private fun extractUrls(text: String): List = buildList { + val matcher = urlPattern.matcher(text) + while (matcher.find()) { + val matchStart = matcher.start(1) + val matchEnd = matcher.end() + val url = text.substring(matchStart, matchEnd).replaceFirst("http://", "https://") + add(LinkInfo(url, matchStart, matchEnd)) + } +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt new file mode 100644 index 000000000000..108e08f2a139 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt @@ -0,0 +1,428 @@ +package me.weishu.kernelsu.ui.util + +import android.content.ContentResolver +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.os.Parcelable +import android.os.SystemClock +import android.provider.OpenableColumns +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 +import me.weishu.kernelsu.BuildConfig +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.ksuApp +import org.json.JSONArray +import java.io.File + + +/** + * @author weishu + * @date 2023/1/1. + */ +private const val TAG = "KsuCli" + +private fun getKsuDaemonPath(): String { + return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so" +} + +object KsuCli { + val SHELL: Shell = createRootShell() + val GLOBAL_MNT_SHELL: Shell = createRootShell(true) +} + +fun getRootShell(globalMnt: Boolean = false): Shell { + return if (globalMnt) KsuCli.GLOBAL_MNT_SHELL else { + KsuCli.SHELL + } +} + +inline fun withNewRootShell( + globalMnt: Boolean = false, + block: Shell.() -> T +): T { + return createRootShell(globalMnt).use(block) +} + +fun getFileNameFromUri(context: Context, uri: Uri): String? { + var fileName: String? = null + val contentResolver: ContentResolver = context.contentResolver + val cursor: Cursor? = contentResolver.query(uri, null, null, null, null) + cursor?.use { + if (it.moveToFirst()) { + fileName = it.getString(it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)) + } + } + return fileName +} + +fun createRootShell(globalMnt: Boolean = false): Shell { + Shell.enableVerboseLogging = BuildConfig.DEBUG + val builder = Shell.Builder.create() + return try { + if (globalMnt) { + builder.build(getKsuDaemonPath(), "debug", "su", "-g") + } else { + builder.build(getKsuDaemonPath(), "debug", "su") + } + } catch (e: Throwable) { + Log.w(TAG, "ksu failed: ", e) + try { + if (globalMnt) { + builder.build("su") + } else { + builder.build("su", "-mm") + } + } catch (e: Throwable) { + Log.e(TAG, "su failed: ", e) + builder.build("sh") + } + } +} + +fun execKsud(args: String, newShell: Boolean = false): Boolean { + return if (newShell) { + withNewRootShell { + ShellUtils.fastCmdResult(this, "${getKsuDaemonPath()} $args") + } + } else { + ShellUtils.fastCmdResult(getRootShell(), "${getKsuDaemonPath()} $args") + } +} + +fun install() { + val start = SystemClock.elapsedRealtime() + val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so").absolutePath + val result = execKsud("install --magiskboot $magiskboot", true) + Log.w(TAG, "install result: $result, cost: ${SystemClock.elapsedRealtime() - start}ms") +} + +fun listModules(): String { + val shell = getRootShell() + + val out = + shell.newJob().add("${getKsuDaemonPath()} module list").to(ArrayList(), null).exec().out + return out.joinToString("\n").ifBlank { "[]" } +} + +fun getModuleCount(): Int { + val result = listModules() + runCatching { + val array = JSONArray(result) + return array.length() + }.getOrElse { return 0 } +} + +fun getSuperuserCount(): Int { + return Natives.allowList.size +} + +fun toggleModule(id: String, enable: Boolean): Boolean { + val cmd = if (enable) { + "module enable $id" + } else { + "module disable $id" + } + val result = execKsud(cmd, true) + Log.i(TAG, "$cmd result: $result") + return result +} + +fun uninstallModule(id: String): Boolean { + val cmd = "module uninstall $id" + val result = execKsud(cmd, true) + Log.i(TAG, "uninstall module $id result: $result") + return result +} + +private fun flashWithIO( + cmd: String, + onStdout: (String) -> Unit, + onStderr: (String) -> Unit +): Shell.Result { + + val stdoutCallback: CallbackList = object : CallbackList() { + override fun onAddElement(s: String?) { + onStdout(s ?: "") + } + } + + val stderrCallback: CallbackList = object : CallbackList() { + override fun onAddElement(s: String?) { + onStderr(s ?: "") + } + } + + return withNewRootShell { + newJob().add(cmd).to(stdoutCallback, stderrCallback).exec() + } +} + +fun flashModule( + uri: Uri, + onFinish: (Boolean, Int) -> Unit, + onStdout: (String) -> Unit, + onStderr: (String) -> Unit +): Boolean { + val resolver = ksuApp.contentResolver + with(resolver.openInputStream(uri)) { + val file = File(ksuApp.cacheDir, "module.zip") + file.outputStream().use { output -> + this?.copyTo(output) + } + val cmd = "module install ${file.absolutePath}" + val result = flashWithIO("${getKsuDaemonPath()} $cmd", onStdout, onStderr) + Log.i("KernelSU", "install module $uri result: $result") + + file.delete() + + onFinish(result.isSuccess, result.code) + return result.isSuccess + } +} + +fun restoreBoot( + onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit +): Boolean { + val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so") + val result = flashWithIO("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot", onStdout, onStderr) + onFinish(result.isSuccess, result.code) + return result.isSuccess +} + +fun uninstallPermanently( + onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit +): Boolean { + val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so") + val result = flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr) + onFinish(result.isSuccess, result.code) + return result.isSuccess +} + +suspend fun shrinkModules(): Boolean = withContext(Dispatchers.IO) { + execKsud("module shrink", true) +} + +@Parcelize +sealed class LkmSelection : Parcelable { + data class LkmUri(val uri: Uri) : LkmSelection() + data class KmiString(val value: String) : LkmSelection() + data object KmiNone : LkmSelection() +} + +fun installBoot( + bootUri: Uri?, + lkm: LkmSelection, + ota: Boolean, + onFinish: (Boolean, Int) -> Unit, + onStdout: (String) -> Unit, + onStderr: (String) -> Unit, +): Boolean { + val resolver = ksuApp.contentResolver + + val bootFile = bootUri?.let { uri -> + with(resolver.openInputStream(uri)) { + val bootFile = File(ksuApp.cacheDir, "boot.img") + bootFile.outputStream().use { output -> + this?.copyTo(output) + } + + bootFile + } + } + + val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so") + var cmd = "boot-patch --magiskboot ${magiskboot.absolutePath}" + + cmd += if (bootFile == null) { + // no boot.img, use -f to force install + " -f" + } else { + " -b ${bootFile.absolutePath}" + } + + if (ota) { + cmd += " -u" + } + + var lkmFile: File? = null + when (lkm) { + is LkmSelection.LkmUri -> { + lkmFile = with(resolver.openInputStream(lkm.uri)) { + val file = File(ksuApp.cacheDir, "kernelsu-tmp-lkm.ko") + file.outputStream().use { output -> + this?.copyTo(output) + } + + file + } + cmd += " -m ${lkmFile.absolutePath}" + } + + is LkmSelection.KmiString -> { + cmd += " --kmi ${lkm.value}" + } + + LkmSelection.KmiNone -> { + // do nothing + } + } + + // output dir + val downloadsDir = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + cmd += " -o $downloadsDir" + + val result = flashWithIO("${getKsuDaemonPath()} $cmd", onStdout, onStderr) + Log.i("KernelSU", "install boot result: ${result.isSuccess}") + + bootFile?.delete() + lkmFile?.delete() + + // if boot uri is empty, it is direct install, when success, we should show reboot button + onFinish(bootUri == null && result.isSuccess, result.code) + return result.isSuccess +} + +fun reboot(reason: String = "") { + val shell = getRootShell() + if (reason == "recovery") { + // KEYCODE_POWER = 26, hide incorrect "Factory data reset" message + ShellUtils.fastCmd(shell, "/system/bin/input keyevent 26") + } + ShellUtils.fastCmd(shell, "/system/bin/svc power reboot $reason || /system/bin/reboot $reason") +} + +fun rootAvailable(): Boolean { + val shell = getRootShell() + return shell.isRoot +} + +fun isAbDevice(): Boolean { + val shell = getRootShell() + return ShellUtils.fastCmd(shell, "getprop ro.build.ab_update").trim().toBoolean() +} + +fun isInitBoot(): Boolean { + val shell = getRootShell() + if (shell.isRoot) { + // if we have root, use /dev/block/by-name/init_boot to check + val abDevice = isAbDevice() + val initBootBlock = "/dev/block/by-name/init_boot${if (abDevice) "_a" else ""}" + val file = SuFile(initBootBlock) + file.shell = shell + return file.exists() + } + // https://source.android.com/docs/core/architecture/partitions/generic-boot + return ShellUtils.fastCmd(shell, "getprop ro.product.first_api_level").trim() + .toInt() >= Build.VERSION_CODES.TIRAMISU +} + +suspend fun getCurrentKmi(): String = withContext(Dispatchers.IO) { + val shell = getRootShell() + val cmd = "boot-info current-kmi" + ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd") +} + +suspend fun getSupportedKmis(): List = withContext(Dispatchers.IO) { + val shell = getRootShell() + val cmd = "boot-info supported-kmi" + val out = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out + out.filter { it.isNotBlank() }.map { it.trim() } +} + +fun overlayFsAvailable(): Boolean { + val shell = getRootShell() + // check /proc/filesystems + return ShellUtils.fastCmdResult(shell, "cat /proc/filesystems | grep overlay") +} + +fun hasMagisk(): Boolean { + val shell = getRootShell(true) + val result = shell.newJob().add("which magisk").exec() + Log.i(TAG, "has magisk: ${result.isSuccess}") + return result.isSuccess +} + +fun isSepolicyValid(rules: String?): Boolean { + if (rules == null) { + return true + } + val shell = getRootShell() + val result = + shell.newJob().add("${getKsuDaemonPath()} sepolicy check '$rules'").to(ArrayList(), null) + .exec() + return result.isSuccess +} + +fun getSepolicy(pkg: String): String { + val shell = getRootShell() + val result = + shell.newJob().add("${getKsuDaemonPath()} profile get-sepolicy $pkg").to(ArrayList(), null) + .exec() + Log.i(TAG, "code: ${result.code}, out: ${result.out}, err: ${result.err}") + return result.out.joinToString("\n") +} + +fun setSepolicy(pkg: String, rules: String): Boolean { + val shell = getRootShell() + val result = shell.newJob().add("${getKsuDaemonPath()} profile set-sepolicy $pkg '$rules'") + .to(ArrayList(), null).exec() + Log.i(TAG, "set sepolicy result: ${result.code}") + return result.isSuccess +} + +fun listAppProfileTemplates(): List { + val shell = getRootShell() + return shell.newJob().add("${getKsuDaemonPath()} profile list-templates").to(ArrayList(), null) + .exec().out +} + +fun getAppProfileTemplate(id: String): String { + val shell = getRootShell() + return shell.newJob().add("${getKsuDaemonPath()} profile get-template '${id}'") + .to(ArrayList(), null).exec().out.joinToString("\n") +} + +fun setAppProfileTemplate(id: String, template: String): Boolean { + val shell = getRootShell() + val escapedTemplate = template.replace("\"", "\\\"") + val cmd = """${getKsuDaemonPath()} profile set-template "$id" "$escapedTemplate'"""" + return shell.newJob().add(cmd) + .to(ArrayList(), null).exec().isSuccess +} + +fun deleteAppProfileTemplate(id: String): Boolean { + val shell = getRootShell() + return shell.newJob().add("${getKsuDaemonPath()} profile delete-template '${id}'") + .to(ArrayList(), null).exec().isSuccess +} + +fun forceStopApp(packageName: String) { + val shell = getRootShell() + val result = shell.newJob().add("am force-stop $packageName").exec() + Log.i(TAG, "force stop $packageName result: $result") +} + +fun launchApp(packageName: String) { + + val shell = getRootShell() + val result = + shell.newJob() + .add("cmd package resolve-activity --brief $packageName | tail -n 1 | xargs cmd activity start-activity -n") + .exec() + Log.i(TAG, "launch $packageName result: $result") +} + +fun restartApp(packageName: String) { + forceStopApp(packageName) + launchApp(packageName) +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt new file mode 100644 index 000000000000..19eb1df18991 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt @@ -0,0 +1,115 @@ +package me.weishu.kernelsu.ui.util + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import android.os.Build +import android.os.ParcelFileDescriptor +import android.system.Os +import com.topjohnwu.superuser.ShellUtils +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.ui.screen.getManagerVersion +import java.io.File +import java.io.FileOutputStream +import java.io.FileWriter +import java.io.PrintWriter +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +fun getBugreportFile(context: Context): File { + + val bugreportDir = File(context.cacheDir, "bugreport") + bugreportDir.mkdirs() + + val dmesgFile = File(bugreportDir, "dmesg.txt") + val logcatFile = File(bugreportDir, "logcat.txt") + val tombstonesFile = File(bugreportDir, "tombstones.tar.gz") + val dropboxFile = File(bugreportDir, "dropbox.tar.gz") + val pstoreFile = File(bugreportDir, "pstore.tar.gz") + // Xiaomi/Readmi devices have diag in /data/vendor/diag + val diagFile = File(bugreportDir, "diag.tar.gz") + val opulsFile = File(bugreportDir, "opuls.tar.gz") + val bootlogFile = File(bugreportDir, "bootlog.tar.gz") + val mountsFile = File(bugreportDir, "mounts.txt") + val fileSystemsFile = File(bugreportDir, "filesystems.txt") + val adbFileTree = File(bugreportDir, "adb_tree.txt") + val adbFileDetails = File(bugreportDir, "adb_details.txt") + val ksuFileSize = File(bugreportDir, "ksu_size.txt") + val appListFile = File(bugreportDir, "packages.txt") + val propFile = File(bugreportDir, "props.txt") + val allowListFile = File(bugreportDir, "allowlist.bin") + val procModules = File(bugreportDir, "proc_modules.txt") + val bootConfig = File(bugreportDir, "boot_config.txt") + val kernelConfig = File(bugreportDir, "defconfig.gz") + + val shell = getRootShell(true) + + shell.newJob().add("dmesg > ${dmesgFile.absolutePath}").exec() + shell.newJob().add("logcat -d > ${logcatFile.absolutePath}").exec() + shell.newJob().add("tar -czf ${tombstonesFile.absolutePath} -C /data/tombstones .").exec() + shell.newJob().add("tar -czf ${dropboxFile.absolutePath} -C /data/system/dropbox .").exec() + shell.newJob().add("tar -czf ${pstoreFile.absolutePath} -C /sys/fs/pstore .").exec() + shell.newJob().add("tar -czf ${diagFile.absolutePath} -C /data/vendor/diag . --exclude=./minidump.gz").exec() + shell.newJob().add("tar -czf ${opulsFile.absolutePath} -C /mnt/oplus/op2/media/log/boot_log/ .").exec() + shell.newJob().add("tar -czf ${bootlogFile.absolutePath} -C /data/adb/ksu/log .").exec() + + shell.newJob().add("cat /proc/1/mountinfo > ${mountsFile.absolutePath}").exec() + shell.newJob().add("cat /proc/filesystems > ${fileSystemsFile.absolutePath}").exec() + shell.newJob().add("busybox tree /data/adb > ${adbFileTree.absolutePath}").exec() + shell.newJob().add("ls -alRZ /data/adb > ${adbFileDetails.absolutePath}").exec() + shell.newJob().add("du -sh /data/adb/ksu/* > ${ksuFileSize.absolutePath}").exec() + shell.newJob().add("cp /data/system/packages.list ${appListFile.absolutePath}").exec() + shell.newJob().add("getprop > ${propFile.absolutePath}").exec() + shell.newJob().add("cp /data/adb/ksu/.allowlist ${allowListFile.absolutePath}").exec() + shell.newJob().add("cp /proc/modules ${procModules.absolutePath}").exec() + shell.newJob().add("cp /proc/bootconfig ${bootConfig.absolutePath}").exec() + shell.newJob().add("cp /proc/config.gz ${kernelConfig.absolutePath}").exec() + + val selinux = ShellUtils.fastCmd(shell, "getenforce") + + // basic information + val buildInfo = File(bugreportDir, "basic.txt") + PrintWriter(FileWriter(buildInfo)).use { pw -> + pw.println("Kernel: ${System.getProperty("os.version")}") + pw.println("BRAND: " + Build.BRAND) + pw.println("MODEL: " + Build.MODEL) + pw.println("PRODUCT: " + Build.PRODUCT) + pw.println("MANUFACTURER: " + Build.MANUFACTURER) + pw.println("SDK: " + Build.VERSION.SDK_INT) + pw.println("PREVIEW_SDK: " + Build.VERSION.PREVIEW_SDK_INT) + pw.println("FINGERPRINT: " + Build.FINGERPRINT) + pw.println("DEVICE: " + Build.DEVICE) + pw.println("Manager: " + getManagerVersion(context)) + pw.println("SELinux: $selinux") + + val uname = Os.uname() + pw.println("KernelRelease: ${uname.release}") + pw.println("KernelVersion: ${uname.version}") + pw.println("Machine: ${uname.machine}") + pw.println("Nodename: ${uname.nodename}") + pw.println("Sysname: ${uname.sysname}") + + val ksuKernel = Natives.version + pw.println("KernelSU: $ksuKernel") + val safeMode = Natives.isSafeMode + pw.println("SafeMode: $safeMode") + val lkmMode = Natives.isLkmMode + pw.println("LKM: $lkmMode") + } + + // modules + val modulesFile = File(bugreportDir, "modules.json") + modulesFile.writeText(listModules()) + + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm") + val current = LocalDateTime.now().format(formatter) + + val targetFile = File(context.cacheDir, "KernelSU_bugreport_${current}.tar.gz") + + shell.newJob().add("tar czf ${targetFile.absolutePath} -C ${bugreportDir.absolutePath} .").exec() + shell.newJob().add("rm -rf ${bugreportDir.absolutePath}").exec() + shell.newJob().add("chmod 0644 ${targetFile.absolutePath}").exec() + + return targetFile +} + diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/SELinuxChecker.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/SELinuxChecker.kt new file mode 100644 index 000000000000..78346dd9412c --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/SELinuxChecker.kt @@ -0,0 +1,34 @@ +package me.weishu.kernelsu.ui.util + +import androidx.compose.ui.res.stringResource +import androidx.compose.runtime.Composable +import com.topjohnwu.superuser.Shell +import me.weishu.kernelsu.R + +@Composable +fun getSELinuxStatus(): String { + val shell = Shell.Builder.create() + .setFlags(Shell.FLAG_REDIRECT_STDERR) + .build("sh") + + val list = ArrayList() + val result = shell.use { + it.newJob().add("getenforce").to(list, list).exec() + } + val output = result.out.joinToString("\n").trim() + + if (result.isSuccess) { + return when (output) { + "Enforcing" -> stringResource(R.string.selinux_status_enforcing) + "Permissive" -> stringResource(R.string.selinux_status_permissive) + "Disabled" -> stringResource(R.string.selinux_status_disabled) + else -> stringResource(R.string.selinux_status_unknown) + } + } + + return if (output.endsWith("Permission denied")) { + stringResource(R.string.selinux_status_enforcing) + } else { + stringResource(R.string.selinux_status_unknown) + } +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/module/LatestVersionInfo.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/module/LatestVersionInfo.kt new file mode 100644 index 000000000000..374b3853ab1a --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/util/module/LatestVersionInfo.kt @@ -0,0 +1,7 @@ +package me.weishu.kernelsu.ui.util.module + +data class LatestVersionInfo( + val versionCode : Int = 0, + val downloadUrl : String = "", + val changelog : String = "" +) diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt new file mode 100644 index 000000000000..013453fbfeed --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt @@ -0,0 +1,161 @@ +package me.weishu.kernelsu.ui.viewmodel + +import android.os.SystemClock +import android.util.Log +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import me.weishu.kernelsu.ui.util.listModules +import me.weishu.kernelsu.ui.util.overlayFsAvailable +import org.json.JSONArray +import org.json.JSONObject +import java.text.Collator +import java.util.Locale + +class ModuleViewModel : ViewModel() { + + companion object { + private const val TAG = "ModuleViewModel" + private var modules by mutableStateOf>(emptyList()) + } + + class ModuleInfo( + val id: String, + val name: String, + val author: String, + val version: String, + val versionCode: Int, + val description: String, + val enabled: Boolean, + val update: Boolean, + val remove: Boolean, + val updateJson: String, + val hasWebUi: Boolean, + ) + + data class ModuleUpdateInfo( + val version: String, + val versionCode: Int, + val zipUrl: String, + val changelog: String, + ) + + var isRefreshing by mutableStateOf(false) + private set + + var isOverlayAvailable by mutableStateOf(overlayFsAvailable()) + private set + + val moduleList by derivedStateOf { + val comparator = compareBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id) + modules.sortedWith(comparator).also { + isRefreshing = false + } + } + + var isNeedRefresh by mutableStateOf(false) + private set + + fun markNeedRefresh() { + isNeedRefresh = true + } + + fun fetchModuleList() { + viewModelScope.launch(Dispatchers.IO) { + isRefreshing = true + + val oldModuleList = modules + + val start = SystemClock.elapsedRealtime() + + kotlin.runCatching { + isOverlayAvailable = overlayFsAvailable() + + val result = listModules() + + Log.i(TAG, "result: $result") + + val array = JSONArray(result) + modules = (0 until array.length()) + .asSequence() + .map { array.getJSONObject(it) } + .map { obj -> + ModuleInfo( + obj.getString("id"), + + obj.optString("name"), + obj.optString("author", "Unknown"), + obj.optString("version", "Unknown"), + obj.optInt("versionCode", 0), + obj.optString("description"), + obj.getBoolean("enabled"), + obj.getBoolean("update"), + obj.getBoolean("remove"), + obj.optString("updateJson"), + obj.optBoolean("web") + ) + }.toList() + isNeedRefresh = false + }.onFailure { e -> + Log.e(TAG, "fetchModuleList: ", e) + isRefreshing = false + } + + // when both old and new is kotlin.collections.EmptyList + // moduleList update will don't trigger + if (oldModuleList === modules) { + isRefreshing = false + } + + Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules") + } + } + + fun checkUpdate(m: ModuleInfo): Triple { + val empty = Triple("", "", "") + if (m.updateJson.isEmpty() || m.remove || m.update || !m.enabled) { + return empty + } + // download updateJson + val result = kotlin.runCatching { + val url = m.updateJson + Log.i(TAG, "checkUpdate url: $url") + val response = okhttp3.OkHttpClient() + .newCall( + okhttp3.Request.Builder() + .url(url) + .build() + ).execute() + Log.d(TAG, "checkUpdate code: ${response.code}") + if (response.isSuccessful) { + response.body?.string() ?: "" + } else { + "" + } + }.getOrDefault("") + Log.i(TAG, "checkUpdate result: $result") + + if (result.isEmpty()) { + return empty + } + + val updateJson = kotlin.runCatching { + JSONObject(result) + }.getOrNull() ?: return empty + + val version = updateJson.optString("version", "") + val versionCode = updateJson.optInt("versionCode", 0) + val zipUrl = updateJson.optString("zipUrl", "") + val changelog = updateJson.optString("changelog", "") + if (versionCode <= m.versionCode || zipUrl.isEmpty()) { + return empty + } + + return Triple(zipUrl, version, changelog) + } +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/SuperUserViewModel.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/SuperUserViewModel.kt new file mode 100644 index 000000000000..37e05aa82aff --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/SuperUserViewModel.kt @@ -0,0 +1,160 @@ +package me.weishu.kernelsu.ui.viewmodel + +import android.content.ComponentName +import android.content.Intent +import android.content.ServiceConnection +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.os.IBinder +import android.os.Parcelable +import android.os.SystemClock +import android.util.Log +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.parcelize.Parcelize +import me.weishu.kernelsu.IKsuInterface +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.ksuApp +import me.weishu.kernelsu.ui.KsuService +import me.weishu.kernelsu.ui.util.HanziToPinyin +import me.weishu.kernelsu.ui.util.KsuCli +import java.text.Collator +import java.util.* +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +class SuperUserViewModel : ViewModel() { + + companion object { + private const val TAG = "SuperUserViewModel" + private var apps by mutableStateOf>(emptyList()) + } + + @Parcelize + data class AppInfo( + val label: String, + val packageInfo: PackageInfo, + val profile: Natives.Profile?, + ) : Parcelable { + val packageName: String + get() = packageInfo.packageName + val uid: Int + get() = packageInfo.applicationInfo.uid + + val allowSu: Boolean + get() = profile != null && profile.allowSu + val hasCustomProfile: Boolean + get() { + if (profile == null) { + return false + } + + return if (profile.allowSu) { + !profile.rootUseDefault + } else { + !profile.nonRootUseDefault + } + } + } + + var search by mutableStateOf("") + var showSystemApps by mutableStateOf(false) + var isRefreshing by mutableStateOf(false) + private set + + private val sortedList by derivedStateOf { + val comparator = compareBy { + when { + it.allowSu -> 0 + it.hasCustomProfile -> 1 + else -> 2 + } + }.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label)) + apps.sortedWith(comparator).also { + isRefreshing = false + } + } + + val appList by derivedStateOf { + sortedList.filter { + it.label.contains(search, true) || it.packageName.contains( + search, + true + ) || HanziToPinyin.getInstance() + .toPinyinString(it.label).contains(search, true) + }.filter { + it.uid == 2000 // Always show shell + || showSystemApps || it.packageInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0 + } + } + + private suspend inline fun connectKsuService( + crossinline onDisconnect: () -> Unit = {} + ): Pair = suspendCoroutine { + val connection = object : ServiceConnection { + override fun onServiceDisconnected(name: ComponentName?) { + onDisconnect() + } + + override fun onServiceConnected(name: ComponentName?, binder: IBinder?) { + it.resume(binder as IBinder to this) + } + } + + val intent = Intent(ksuApp, KsuService::class.java) + + val task = KsuService.bindOrTask( + intent, + Shell.EXECUTOR, + connection, + ) + val shell = KsuCli.SHELL + task?.let { it1 -> shell.execTask(it1) } + } + + private fun stopKsuService() { + val intent = Intent(ksuApp, KsuService::class.java) + KsuService.stop(intent) + } + + suspend fun fetchAppList() { + + isRefreshing = true + + val result = connectKsuService { + Log.w(TAG, "KsuService disconnected") + } + + withContext(Dispatchers.IO) { + val pm = ksuApp.packageManager + val start = SystemClock.elapsedRealtime() + + val binder = result.first + val allPackages = IKsuInterface.Stub.asInterface(binder).getPackages(0) + + withContext(Dispatchers.Main) { + stopKsuService() + } + + val packages = allPackages.list + + apps = packages.map { + val appInfo = it.applicationInfo + val uid = appInfo.uid + val profile = Natives.getAppProfile(it.packageName, uid) + AppInfo( + label = appInfo.loadLabel(pm).toString(), + packageInfo = it, + profile = profile, + ) + }.filter { it.packageName != ksuApp.packageName } + Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}") + } + } +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/TemplateViewModel.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/TemplateViewModel.kt new file mode 100644 index 000000000000..cbed82f21c05 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/TemplateViewModel.kt @@ -0,0 +1,328 @@ +package me.weishu.kernelsu.ui.viewmodel + +import android.os.Parcelable +import android.util.Log +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.parcelize.Parcelize +import me.weishu.kernelsu.Natives +import me.weishu.kernelsu.profile.Capabilities +import me.weishu.kernelsu.profile.Groups +import me.weishu.kernelsu.ui.util.getAppProfileTemplate +import me.weishu.kernelsu.ui.util.listAppProfileTemplates +import me.weishu.kernelsu.ui.util.setAppProfileTemplate +import okhttp3.OkHttpClient +import okhttp3.Request +import org.json.JSONArray +import org.json.JSONObject +import java.text.Collator +import java.util.Locale +import java.util.concurrent.TimeUnit + + +/** + * @author weishu + * @date 2023/10/20. + */ +const val TEMPLATE_INDEX_URL = "https://kernelsu.org/templates/index.json" +const val TEMPLATE_URL = "https://kernelsu.org/templates/%s" + +const val TAG = "TemplateViewModel" + +class TemplateViewModel : ViewModel() { + companion object { + + private var templates by mutableStateOf>(emptyList()) + } + + @Parcelize + data class TemplateInfo( + val id: String = "", + val name: String = "", + val description: String = "", + val author: String = "", + val local: Boolean = true, + + val namespace: Int = Natives.Profile.Namespace.INHERITED.ordinal, + val uid: Int = Natives.ROOT_UID, + val gid: Int = Natives.ROOT_GID, + val groups: List = mutableListOf(), + val capabilities: List = mutableListOf(), + val context: String = Natives.KERNEL_SU_DOMAIN, + val rules: List = mutableListOf(), + ) : Parcelable + + var isRefreshing by mutableStateOf(false) + private set + + val templateList by derivedStateOf { + val comparator = compareBy(TemplateInfo::local).reversed().then( + compareBy( + Collator.getInstance(Locale.getDefault()), TemplateInfo::id + ) + ) + templates.sortedWith(comparator).apply { + isRefreshing = false + } + } + + suspend fun fetchTemplates(sync: Boolean = false) { + isRefreshing = true + withContext(Dispatchers.IO) { + val localTemplateIds = listAppProfileTemplates() + Log.i(TAG, "localTemplateIds: $localTemplateIds") + if (localTemplateIds.isEmpty() || sync) { + // if no templates, fetch remote templates + fetchRemoteTemplates() + } + + // fetch templates again + templates = listAppProfileTemplates().mapNotNull(::getTemplateInfoById) + + isRefreshing = false + } + } + + suspend fun importTemplates( + templates: String, + onSuccess: suspend () -> Unit, + onFailure: suspend (String) -> Unit + ) { + withContext(Dispatchers.IO) { + runCatching { + JSONArray(templates) + }.getOrElse { + runCatching { + val json = JSONObject(templates) + JSONArray().apply { put(json) } + }.getOrElse { + onFailure("invalid templates: $templates") + return@withContext + } + }.let { + 0.until(it.length()).forEach { i -> + runCatching { + val template = it.getJSONObject(i) + val id = template.getString("id") + template.put("local", true) + setAppProfileTemplate(id, template.toString()) + }.onFailure { e -> + Log.e(TAG, "ignore invalid template: $it", e) + } + } + onSuccess() + } + } + } + + suspend fun exportTemplates(onTemplateEmpty: () -> Unit, callback: (String) -> Unit) { + withContext(Dispatchers.IO) { + val templates = listAppProfileTemplates().mapNotNull(::getTemplateInfoById).filter { + it.local + } + templates.ifEmpty { + onTemplateEmpty() + return@withContext + } + JSONArray(templates.map { + it.toJSON() + }).toString().let(callback) + } + } +} + +private fun fetchRemoteTemplates() { + runCatching { + val client: OkHttpClient = OkHttpClient.Builder() + .connectTimeout(5, TimeUnit.SECONDS) + .writeTimeout(5, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .build() + + client.newCall( + Request.Builder().url(TEMPLATE_INDEX_URL).build() + ).execute().use { response -> + if (!response.isSuccessful) { + return + } + val remoteTemplateIds = JSONArray(response.body!!.string()) + Log.i(TAG, "fetchRemoteTemplates: $remoteTemplateIds") + 0.until(remoteTemplateIds.length()).forEach { i -> + val id = remoteTemplateIds.getString(i) + Log.i(TAG, "fetch template: $id") + val templateJson = client.newCall( + Request.Builder().url(TEMPLATE_URL.format(id)).build() + ).runCatching { + execute().use { response -> + if (!response.isSuccessful) { + return@forEach + } + response.body!!.string() + } + }.getOrNull() ?: return@forEach + Log.i(TAG, "template: $templateJson") + + // validate remote template + runCatching { + val json = JSONObject(templateJson) + fromJSON(json)?.let { + // force local template + json.put("local", false) + setAppProfileTemplate(id, json.toString()) + } + }.onFailure { + Log.e(TAG, "ignore invalid template: $it", it) + return@forEach + } + } + } + }.onFailure { Log.e(TAG, "fetchRemoteTemplates: $it", it) } +} + +@Suppress("UNCHECKED_CAST") +private fun JSONArray.mapCatching( + transform: (T) -> R, onFail: (Throwable) -> Unit +): List { + return List(length()) { i -> get(i) as T }.mapNotNull { element -> + runCatching { + transform(element) + }.onFailure(onFail).getOrNull() + } +} + +private inline fun > getEnumOrdinals( + jsonArray: JSONArray?, enumClass: Class +): List { + return jsonArray?.mapCatching({ name -> + enumValueOf(name.uppercase()) + }, { + Log.e(TAG, "ignore invalid enum ${enumClass.simpleName}: $it", it) + }).orEmpty() +} + +fun getTemplateInfoById(id: String): TemplateViewModel.TemplateInfo? { + return runCatching { + fromJSON(JSONObject(getAppProfileTemplate(id))) + }.onFailure { + Log.e(TAG, "ignore invalid template: $it", it) + }.getOrNull() +} + +private fun getLocaleString(json: JSONObject, key: String): String { + val fallback = json.getString(key) + val locale = Locale.getDefault() + val localeKey = "${locale.language}_${locale.country}" + json.optJSONObject("locales")?.let { + // check locale first + it.optJSONObject(localeKey)?.let { json-> + return json.optString(key, fallback) + } + // fallback to language + it.optJSONObject(locale.language)?.let { json-> + return json.optString(key, fallback) + } + } + return fallback +} + +private fun fromJSON(templateJson: JSONObject): TemplateViewModel.TemplateInfo? { + return runCatching { + val groupsJsonArray = templateJson.optJSONArray("groups") + val capabilitiesJsonArray = templateJson.optJSONArray("capabilities") + val context = templateJson.optString("context").takeIf { it.isNotEmpty() } + ?: Natives.KERNEL_SU_DOMAIN + val namespace = templateJson.optString("namespace").takeIf { it.isNotEmpty() } + ?: Natives.Profile.Namespace.INHERITED.name + + val rulesJsonArray = templateJson.optJSONArray("rules") + val templateInfo = TemplateViewModel.TemplateInfo( + id = templateJson.getString("id"), + name = getLocaleString(templateJson, "name"), + description = getLocaleString(templateJson, "description"), + author = templateJson.optString("author"), + local = templateJson.optBoolean("local"), + namespace = Natives.Profile.Namespace.valueOf( + namespace.uppercase() + ).ordinal, + uid = templateJson.optInt("uid", Natives.ROOT_UID), + gid = templateJson.optInt("gid", Natives.ROOT_GID), + groups = getEnumOrdinals(groupsJsonArray, Groups::class.java).map { it.gid }, + capabilities = getEnumOrdinals( + capabilitiesJsonArray, Capabilities::class.java + ).map { it.cap }, + context = context, + rules = rulesJsonArray?.mapCatching({ it }, { + Log.e(TAG, "ignore invalid rule: $it", it) + }).orEmpty() + ) + templateInfo + }.onFailure { + Log.e(TAG, "ignore invalid template: $it", it) + }.getOrNull() +} + +fun TemplateViewModel.TemplateInfo.toJSON(): JSONObject { + val template = this + return JSONObject().apply { + + put("id", template.id) + put("name", template.name.ifBlank { template.id }) + put("description", template.description.ifBlank { template.id }) + if (template.author.isNotEmpty()) { + put("author", template.author) + } + put("namespace", Natives.Profile.Namespace.entries[template.namespace].name) + put("uid", template.uid) + put("gid", template.gid) + + if (template.groups.isNotEmpty()) { + put("groups", JSONArray( + Groups.entries.filter { + template.groups.contains(it.gid) + }.map { + it.name + } + )) + } + + if (template.capabilities.isNotEmpty()) { + put("capabilities", JSONArray( + Capabilities.entries.filter { + template.capabilities.contains(it.cap) + }.map { + it.name + } + )) + } + + if (template.context.isNotEmpty()) { + put("context", template.context) + } + + if (template.rules.isNotEmpty()) { + put("rules", JSONArray(template.rules)) + } + } +} + +@Suppress("unused") +fun generateTemplates() { + val templateJson = JSONObject() + templateJson.put("id", "com.example") + templateJson.put("name", "Example") + templateJson.put("description", "This is an example template") + templateJson.put("local", true) + templateJson.put("namespace", Natives.Profile.Namespace.INHERITED.name) + templateJson.put("uid", 0) + templateJson.put("gid", 0) + + templateJson.put("groups", JSONArray().apply { put(Groups.INET.name) }) + templateJson.put("capabilities", JSONArray().apply { put(Capabilities.CAP_NET_RAW.name) }) + templateJson.put("context", "u:r:su:s0") + Log.i(TAG, "$templateJson") +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/MimeUtil.java b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/MimeUtil.java new file mode 100644 index 000000000000..1fc2f4a4ceab --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/MimeUtil.java @@ -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 me.weishu.kernelsu.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; + }; + } +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/SuFilePathHandler.java b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/SuFilePathHandler.java new file mode 100644 index 000000000000..ff450e8e9cfe --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/SuFilePathHandler.java @@ -0,0 +1,191 @@ +package me.weishu.kernelsu.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 + * Android Developers + * Docs: Data and file storage overview. + *

+ * 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. + *

+ * A typical usage would be like: + *

+ * 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();
+ * 
+ */ +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. + *

+ * 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. + *

+ * 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/"}). + *

+ * The application should typically use a dedicated subdirectory for the files it intends to + * expose and keep them separate from other files. + * + * @param context {@link Context} that is used to access app's internal storage. + * @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 Context context, @NonNull File directory, Shell rootShell) { + try { + mDirectory = new File(getCanonicalDirPath(directory)); + if (!isAllowedInternalStorageDir(context)) { + 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(@NonNull Context context) 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. + *

+ * 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. + *

+ * 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; + } +} diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebUIActivity.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebUIActivity.kt new file mode 100644 index 000000000000..eccb002a1688 --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebUIActivity.kt @@ -0,0 +1,68 @@ +package me.weishu.kernelsu.ui.webui + +import android.annotation.SuppressLint +import android.app.ActivityManager +import android.content.Context +import android.os.Bundle +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.activity.ComponentActivity +import androidx.webkit.WebViewAssetLoader +import com.topjohnwu.superuser.Shell +import me.weishu.kernelsu.ui.util.createRootShell +import java.io.File + +@SuppressLint("SetJavaScriptEnabled") +class WebUIActivity : ComponentActivity() { + private lateinit var webviewInterface: WebViewInterface + + private var rootShell: Shell? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val moduleId = intent.getStringExtra("id")!! + val name = intent.getStringExtra("name")!! + setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name")) + + val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE) + WebView.setWebContentsDebuggingEnabled(prefs.getBoolean("enable_web_debugging", false)) + + val webRoot = File("/data/adb/modules/${moduleId}/webroot") + val rootShell = createRootShell(true).also { this.rootShell = it } + val webViewAssetLoader = WebViewAssetLoader.Builder() + .setDomain("mui.kernelsu.org") + .addPathHandler( + "/", + SuFilePathHandler(this, webRoot, rootShell) + ) + .build() + + val webViewClient = object : WebViewClient() { + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest + ): WebResourceResponse? { + return webViewAssetLoader.shouldInterceptRequest(request.url) + } + } + + val webView = WebView(this).apply { + settings.javaScriptEnabled = true + settings.domStorageEnabled = true + settings.allowFileAccess = false + webviewInterface = WebViewInterface(this@WebUIActivity, this) + addJavascriptInterface(webviewInterface, "ksu") + setWebViewClient(webViewClient) + loadUrl("https://mui.kernelsu.org/index.html") + } + + setContentView(webView) + } + + override fun onDestroy() { + super.onDestroy() + runCatching { rootShell?.close() } + } +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebViewInterface.kt b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebViewInterface.kt new file mode 100644 index 000000000000..4b6a3c8f755d --- /dev/null +++ b/KernelSU/manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebViewInterface.kt @@ -0,0 +1,190 @@ +package me.weishu.kernelsu.ui.webui + +import android.app.Activity +import android.content.Context +import android.os.Handler +import android.os.Looper +import android.text.TextUtils +import android.view.Window +import android.webkit.JavascriptInterface +import android.webkit.WebView +import android.widget.Toast +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat +import com.topjohnwu.superuser.CallbackList +import com.topjohnwu.superuser.ShellUtils +import me.weishu.kernelsu.ui.util.createRootShell +import me.weishu.kernelsu.ui.util.withNewRootShell +import org.json.JSONArray +import org.json.JSONObject +import java.util.concurrent.CompletableFuture + +class WebViewInterface(val context: Context, private val webView: WebView) { + + @JavascriptInterface + fun exec(cmd: String): String { + return withNewRootShell(true) { ShellUtils.fastCmd(this, cmd) } + } + + @JavascriptInterface + fun exec(cmd: String, callbackFunc: String) { + exec(cmd, null, callbackFunc) + } + + private fun processOptions(sb: StringBuilder, options: String?) { + val opts = if (options == null) JSONObject() else { + JSONObject(options) + } + + val cwd = opts.optString("cwd") + if (!TextUtils.isEmpty(cwd)) { + sb.append("cd ${cwd};") + } + + opts.optJSONObject("env")?.let { env -> + env.keys().forEach { key -> + sb.append("export ${key}=${env.getString(key)};") + } + } + } + + @JavascriptInterface + fun exec( + cmd: String, + options: String?, + callbackFunc: String + ) { + val finalCommand = StringBuilder() + processOptions(finalCommand, options) + finalCommand.append(cmd) + + val result = withNewRootShell(true) { + newJob().add(finalCommand.toString()).to(ArrayList(), ArrayList()).exec() + } + val stdout = result.out.joinToString(separator = "\n") + val stderr = result.err.joinToString(separator = "\n") + + val jsCode = + "javascript: (function() { try { ${callbackFunc}(${result.code}, ${ + JSONObject.quote( + stdout + ) + }, ${JSONObject.quote(stderr)}); } catch(e) { console.error(e); } })();" + webView.post { + webView.loadUrl(jsCode) + } + } + + @JavascriptInterface + fun spawn(command: String, args: String, options: String?, callbackFunc: String) { + val finalCommand = StringBuilder() + + processOptions(finalCommand, options) + + if (!TextUtils.isEmpty(args)) { + finalCommand.append(command).append(" ") + JSONArray(args).let { argsArray -> + for (i in 0 until argsArray.length()) { + finalCommand.append(argsArray.getString(i)) + finalCommand.append(" ") + } + } + } else { + finalCommand.append(command) + } + + val shell = createRootShell(true) + + val emitData = fun(name: String, data: String) { + val jsCode = + "javascript: (function() { try { ${callbackFunc}.${name}.emit('data', ${ + JSONObject.quote( + data + ) + }); } catch(e) { console.error('emitData', e); } })();" + webView.post { + webView.loadUrl(jsCode) + } + } + + val stdout = object : CallbackList() { + override fun onAddElement(s: String) { + emitData("stdout", s) + } + } + + val stderr = object : CallbackList() { + override fun onAddElement(s: String) { + emitData("stderr", s) + } + } + + val future = shell.newJob().add(finalCommand.toString()).to(stdout, stderr).enqueue() + val completableFuture = CompletableFuture.supplyAsync { + future.get() + } + + completableFuture.thenAccept { result -> + val emitExitCode = + "javascript: (function() { try { ${callbackFunc}.emit('exit', ${result.code}); } catch(e) { console.error(`emitExit error: \${e}`); } })();" + webView.post { + webView.loadUrl(emitExitCode) + } + + if (result.code != 0) { + val emitErrCode = + "javascript: (function() { try { var err = new Error(); err.exitCode = ${result.code}; err.message = ${ + JSONObject.quote( + result.err.joinToString( + "\n" + ) + ) + };${callbackFunc}.emit('error', err); } catch(e) { console.error('emitErr', e); } })();" + webView.post { + webView.loadUrl(emitErrCode) + } + } + }.whenComplete { _, _ -> + runCatching { shell.close() } + } + } + + @JavascriptInterface + fun toast(msg: String) { + webView.post { + Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() + } + } + + @JavascriptInterface + fun fullScreen(enable: Boolean) { + if (context is Activity) { + Handler(Looper.getMainLooper()).post { + if (enable) { + hideSystemUI(context.window) + } else { + showSystemUI(context.window) + } + } + } + } + +} + +fun hideSystemUI(window: Window) { + WindowCompat.setDecorFitsSystemWindows(window, false) + WindowInsetsControllerCompat(window, window.decorView).let { controller -> + controller.hide(WindowInsetsCompat.Type.systemBars()) + controller.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } +} + +fun showSystemUI(window: Window) { + WindowCompat.setDecorFitsSystemWindows(window, true) + WindowInsetsControllerCompat( + window, + window.decorView + ).show(WindowInsetsCompat.Type.systemBars()) +} \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/jniLibs/.gitignore b/KernelSU/manager/app/src/main/jniLibs/.gitignore new file mode 100644 index 000000000000..311f7299ba59 --- /dev/null +++ b/KernelSU/manager/app/src/main/jniLibs/.gitignore @@ -0,0 +1 @@ +libksud.so \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/drawable/ic_launcher_background.xml b/KernelSU/manager/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000000..900e2d8c829e --- /dev/null +++ b/KernelSU/manager/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/drawable/ic_launcher_foreground.xml b/KernelSU/manager/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000000..c9ad0ad4ddfe --- /dev/null +++ b/KernelSU/manager/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,22 @@ + + + + + + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/drawable/ic_launcher_monochrome.xml b/KernelSU/manager/app/src/main/res/drawable/ic_launcher_monochrome.xml new file mode 100644 index 000000000000..e4dc522eeab8 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/drawable/ic_launcher_monochrome.xml @@ -0,0 +1,22 @@ + + + + + + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/KernelSU/manager/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 000000000000..b070c763da32 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/mipmap-hdpi/ic_launcher.png b/KernelSU/manager/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000000..485b815dfee9 Binary files /dev/null and b/KernelSU/manager/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/KernelSU/manager/app/src/main/res/mipmap-ldpi/ic_launcher.png b/KernelSU/manager/app/src/main/res/mipmap-ldpi/ic_launcher.png new file mode 100644 index 000000000000..5c18cc7926c4 Binary files /dev/null and b/KernelSU/manager/app/src/main/res/mipmap-ldpi/ic_launcher.png differ diff --git a/KernelSU/manager/app/src/main/res/mipmap-mdpi/ic_launcher.png b/KernelSU/manager/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000000..9df352536389 Binary files /dev/null and b/KernelSU/manager/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/KernelSU/manager/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/KernelSU/manager/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000000..f8dc01c5de52 Binary files /dev/null and b/KernelSU/manager/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/KernelSU/manager/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/KernelSU/manager/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000000..ba22bf82fd6b Binary files /dev/null and b/KernelSU/manager/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/KernelSU/manager/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/KernelSU/manager/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000000..cddff920f64f Binary files /dev/null and b/KernelSU/manager/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/KernelSU/manager/app/src/main/res/values-ar/strings.xml b/KernelSU/manager/app/src/main/res/values-ar/strings.xml new file mode 100644 index 000000000000..c38c4fc89464 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-ar/strings.xml @@ -0,0 +1,134 @@ + + + الرئيسية + غير مثبت + إضغط للتثبيت + يعمل + الإصدار: %d + مستخدمين الجذر: %d + الإضافات: %d + غير مدعوم + KernelSU يدعم GKI kernels فقط + إصدار النواة + إصدار المدير + البصمة + وضع SELinux + معطل + مفروض + متساهل + مجهول + مستخدم خارق + فشل في تمكين الإضافة: %s + فشل تعطيل الإضافة : %s + لا توجد إضافات مثبتة + الإضافات + إلغاء التثبيت + تثبيت الوحدة + تثبيت + إعادة تشغيل + الإعدادات + إعادة تشغيل سريعة + إعادة تشغيل إلى وضع Recovery + إعادة تشغيل إلى وضع Bootloader + إعادة تشغيل إلى وضع Download + إعادة تشغيل إلى وضع EDL + من نحن + هل أنت متأكد أنك تريد إلغاء تثبيت الإضافة %s ? + تم إلغاء تثبيتها %s + فشل إلغاء التثبيت: %s + الإصدار + المطور + التراكبات غير متوفرة ، لا يمكن للإضافة أن تعمل! + إنعاش + إظهار تطبيقات النظام + إخفاء تطبيقات النظام + إرسال السجلات + الوضع الآمن + إعادة التشغيل لتطبيق التغييرات + تم تعطيل الإضافات لأنها تتعارض مع Magisk! + تعلم KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + تعرف على كيفية تثبيت KernelSU واستخدام الإضافات + إدعمنا + KernelSU سيظل دائماً مجانياً ومفتوح المصدر. مع ذلك، يمكنك أن تظهر لنا أنك تهتم بالتبرع. + إنضم إلى قناتنا في %2$s ]]> + القدرات + تحديث + تحميل الإضافة: %s + ابدأ التنزيل: %s + الإصدار الجديد: %s متاح ، انقر للتحديث + تشغيل + الإفتراضي + نموذج + موروث + عالمي + فردي + مجموعات + مُخصّص + تركيب مساحة الاسم + الغاء تحميل الإضافات + فشل تحديث ملف تعريف التطبيق لـ %s + سياق SELinux + ايقاف إجباري + الغاء تحميل الإضافات بشكل افتراضي + القيمة الافتراضية العامة لـ\"إلغاء تحميل الإضافات\" في ملفات تعريف التطبيقات. إذا تم تمكينه، إزالة جميع تعديلات الإضافات على النظام للتطبيقات التي لا تحتوي على مجموعة ملف تعريف. + سيسمح تمكين هذا الخيار لـKernelSU باستعادة أي ملفات معدلة بواسطة الإضافات لهذا التطبيق. + المجال + القواعد + إعادة تشغيل التطبيق + فشل تحديث قواعد SELinux لما يلي: %s + اسم الملف الشخصي + إصدار KernelSU الحالي %d منخفض جدًا بحيث لا يعمل المدير بشكل صحيح. الرجاء الترقية إلى الإصدار %d أو أعلى! + سجل التغييرات + تم الاستيراد بنجاح + تصدير إلى الحافظة + لا يمكن العثور على القالب المحلي للتصدير! + معرف القالب موجود بالفعل! + استيراد من الحافظة + فشل في جلب سجل التغيير: %s + الاسم + معرف القالب غير صالح + مزامنة القوالب عبر الإنترنت + إنشاء قالب + للقراءة فقط + استيراد / تصدير + فشل في حفظ القالب + تحرير القالب + المعرف + قالب ملف تعريف التطبيق + الوصف + حفظ + إدارة القالب المحلي وعبر الإنترنت لملف تعريف التطبيق + حذف + الحافظة فارغة! + عرض القالب + فشل في منح صلاحية الجذر! + فتح + التحقق تلقائيًا من وجود تحديثات عند فتح التطبيق + التحقق من التحديث + تمكين تصحيح أخطاء WebView + يمكن استخدامه لتصحيح أخطاء WebUI، يرجى تمكينه فقط عند الحاجة. + التالي + اختيار ملف + تثبيت مباشر (موصى به) + التثبيت على فتحة غير نشطة (بعد OTA) + سيتم **إجبار** جهازك على التمهيد إلى الفتحة غير النشطة الحالية بعد إعادة التشغيل! +\nاستخدم هذا الخيار فقط بعد انتهاء التحديث. +\nأستمرار؟ + اختر KMI + يوصى باستخدام صورة القسم %1$s + تصغير الصورة المتفرقة + قم بتغيير حجم الصورة المتفرقة حيث توجد الإضافة إلى حجمها الفعلي. لاحظ أن هذا قد يتسبب في عمل الإضافة بشكل غير طبيعي، لذا يرجى استخدامها فقط عند الضرورة (مثل النسخ الاحتياطي) + إلغاء التثبيت + إلغاء التثبيت مؤقتًا + إلغاء التثبيت بشكل دائم + استعادة الصورة الاصلية + ‬إلغاء تثبيت KernelSU (الجذر وجميع الوحدات) بشكل كامل ودائم. + تركيب + نجح التركيب + فشل التركيب + صورة lkm المحددة: %s + استعادة صورة المصنع المخزنة (في حالة وجود نسخة احتياطية)، والتي تُستخدم عادة قبل OTA؛ إذا كنت بحاجة إلى إلغاء تثبيت KernelSU، فيرجى استخدام \"إلغاء التثبيت الدائم\". + قم بإلغاء تثبيت KernelSU مؤقتًا، واستعد إلى حالته الأصلية بعد إعادة التشغيل التالية. + حفظ السجلات + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-az/strings.xml b/KernelSU/manager/app/src/main/res/values-az/strings.xml new file mode 100644 index 000000000000..21ec189f55c4 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-az/strings.xml @@ -0,0 +1,82 @@ + + + Ana səhifə + Super istifadəçilər: %d + Nüvə + Yüklənmədi + Yükləmək üçün toxunun + İşləyir + Versiya: %d + Modullar: %d + Hal-hazırda KernelSU yalnız GKI nüvələrini dəstəkləyir + Dəstəklənmir + Yüklə + Yüklə + Naməlum + Barmaq izi + Menecer versiyası + Qeyri-aktiv + SELinux vəziyyəti + Sərbəst + Məcburi + Super istifadəçi + Sil + Modulu aktiv etmək mümkün olmadı: %s + Modulu deaktiv etmək mümkün olmadı: %s + Heç bir modul quraşdırılmayıb + Modul + Yenidən başlat + Parametrlər + Bərpa rejimində yenidən başlat + Yüngül vəziyyətdə yenodən başlat + Bootloader rejimində yenidən başlat + Yükləmə rejimində yenidən başlat + Versiya + Sahib + Modulu silmək istədiyinizdən əminsiniz %s\? + Sistem proqramlarını göstər + Haqqında + EDL rejimində yenidən başlat + Silmək mümkün olmadı: %s + %s silindi + Sistem proqramlarını gizlət + overlayfs mövcud deyil,modul işləyə bilməyəcək! + Log-u göndər + Yenilə + Təhlükəsiz rejimi + Qüvvəyə minməsi üçün yenidən başlat + Modular deaktiv edilir,çünki o Magisk-in modulları ilə toqquşur! + KernelSU-yu öyrən + https://kernelsu.org/guide/what-is-kernelsu.html + Bizi dəstəkləyin + KernelSU-yu necə quraşdırılacağını və modulların necə istifadə ediləcəyini öyrən + Şablon + Defolt + Özəl + KernelSU pulsuz və açıq mənbəlidir,həmişə belə olacaqdır. Bununla belə, ianə etməklə bizə qayğı göstərdiyinizi göstərə bilərsiniz. + Mənbə kodlarımıza baxın %1$s
Kanalımıza %2$s qoşulun
+ Profil adı + Bacarıqlar + Modulları umount et + Miras qalmış + Qlobal + Bölmənin ad sahəsi + Fərdi + Qruplar + Defolt olaraq modulları umount et + SELinux konteksi + %s görə tətbiq profillərini güncəlləmək mümkün olmadı + Tətbiq Profillərində \"Umount modulları\" üçün qlobal standart dəyər. Aktivləşdirilərsə, o, Profil dəsti olmayan proqramlar üçün sistemdəki bütün modul dəyişikliklərini siləcək. + Domen + Qaydalar + Güncəllə + Endirməni başlat: %s + Yeni versiya: %s əlçatandır, endirmək üçün toxunun + Modul yüklənir: %s + Bu seçimi aktivləşdirmək KernelSU-ya bu proqram üçün modullar tərəfindən hər hansı dəyişdirilmiş faylları bərpa etməyə imkan verəcək. + Aç + Məcburi dayandır + Yenidən başlat + %s görə SELinux qaydalarını güncəlləmək mümkün olmadı + Girişləri Saxla +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-bn-rBD/strings.xml b/KernelSU/manager/app/src/main/res/values-bn-rBD/strings.xml new file mode 100644 index 000000000000..078d88fee7ca --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-bn-rBD/strings.xml @@ -0,0 +1,52 @@ + + + কর্নেল এস ইউ কেবল মাত্র জিকআই কর্নেল সাপোর্ট করে + এসইলিনাক্স স্টেটাস + আননোন + মোডিউল ইনেবল করা যায়নি: %s + ইন্সটল করটে চাপুন + কাজ করছে + মোডিউল: %d + অমূলক + কর্নেল + ম্যানেজার ভারসন + ফিঙ্গারপ্রিন্ট + ডিসেবল + এনফোর্সিং + সুপার ইউজার + মোডিউল + আনইন্সটল + ইন্সটল + ইন্সটল + রিবুট + সেটিংস + সফট রিবুট + গ্লোবাল + গ্রুপস + এসইলিনাক্স কন্টেক্সট + %s এর জন্য অ্যাপ প্রফাইল আপডেট করা যায়নি + বাইডিফল্ট মোডিউল আনমাউন্ট + হোম + ইন্সটল হয়নী + পারমিসিভ + মোডিউল ডিসেবল করা যায়নি: %s + কোনো মোডিউল ইন্সটল করা নেই + সংস্করণ: %d + সুপার ইউজার: %d + নেইম স্পেস মাউন্ট + ইনহেরিটেড + ইন্ডিভিজুয়াল + ক্যাপাবিলিটিস + আনমাউন্ট মোডিউলস + রিকভারিতে বুট + বুটলোডারে বুট + ডাউনলোড মডে বুট + ইমারজেন্সি ডাউনলোড মডে বুট + অ্যাবাউট + %s মোডিউল আনইনস্টলের বেপারে নিশ্চিৎ\? + %s আনইনস্টলড + %s আনইনস্টল করা যায়নি + ভার্সন + অথার + লগ সংরক্ষণ করুন + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-bn/strings.xml b/KernelSU/manager/app/src/main/res/values-bn/strings.xml new file mode 100644 index 000000000000..312672c8715a --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-bn/strings.xml @@ -0,0 +1,68 @@ + + + হোম + ইনস্টল করা হয়নি + ইনস্টল করার জন্য ক্লিক করুন + ওয়ার্কিং + ওয়ার্কিং সংস্করণ: %d + সুপার ইউজার: %d + মডিউল: %d + অসমর্থিত + KernelSU শুধুমাত্র GKI কার্নেল সমর্থন করে + কার্নেল + ম্যানেজার সংস্করণ + ফিঙ্গারপ্রিন্ট + SELinux স্টেটাস + ডিজেবল + কার্যকর + অনুমতিমূলক + অজানা + সুপার ইউজার + মডিউল সক্ষম করতে ব্যর্থ হয়েছে: %s + মডিউল নিষ্ক্রিয় করতে ব্যর্থ হয়েছে: %s + কোন মডিউল ইনস্টল করা নেই + মডিউল + আনইন্সটল + মডিউল ইনস্টল + ইনস্টল + রিবুট + সেটিংস + সফট রিবুট + রিবুট রিকোভারি + রিবুট বুটলোডার + রিবুট ডাউনলোড + রিবুট ইডিএল + এবাউট + মডিউল আনইনস্টল নিশ্চিত করুন %s? + %s আনইনস্টল সফল + আনইন্সটল ব্যর্থ: %s + ভার্সন + লেখক + ওভারলেএফএস উপলব্ধ নয়, মডিউল কাজ করতে পারে না! + রিফ্রেশ + শো সিস্টেম অ্যাপস + হাইড সিস্টেম অ্যাপস + সেন্ড লগ + সেইফ মোড + রিবুট এপ্লাই + মডিউলগুলি অক্ষম কারণ তারা ম্যাজিস্কের সাথে বিরোধিতা করে! + লার্ন কার্নেলএসইউ + https://kernelsu.org/guide/what-is-kernelsu.html + কিভাবে কার্নেলএসইউ ইনস্টল করতে হয় এবং মডিউল ব্যবহার করতে হয় তা শিখুন + সাপোর্ট টাইটেল + কার্নেলএসইউ বিনামূল্যে এবং ওপেন সোর্স, এবং সবসময় থাকবে। আপনি সবসময় একটি অনুদান দিয়ে আপনার কৃতজ্ঞতা প্রদর্শন করতে পারেন. + আমাদের %2$s চ্যানেল মার্জ করুন]]> + প্রফাইলের নাম + নেমস্পেস মাউন্ট + গ্রুপস + যোগ্যতা + এসই লিনাক্স কনটেক্সট + ডিফল্ট + টেমপ্লেট + কাস্টম + গ্লোবাল + আলাদাভাবে + আনমাউন্ট মোডিউল + ম্যানেজার সঠিকভাবে কাজ করার জন্য বর্তমান KernelSU সংস্করণ %d খুবই কম। অনুগ্রহ করে %d বা উচ্চতর সংস্করণে আপগ্রেড করুন! + লগ সংরক্ষণ করুন + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-bs/strings.xml b/KernelSU/manager/app/src/main/res/values-bs/strings.xml new file mode 100644 index 000000000000..da07863193fe --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-bs/strings.xml @@ -0,0 +1,83 @@ + + + Imenski prostor nosača + Naslijeđen + Globalan + Pojedinačan + Grupe + Sposobnosti + SELinux kontekst + Umount module + Ažuriranje Profila Aplikacije za %s nije uspjelo + Trenutna KernelSU verzija %d je preniska da bi upravitelj ispravno radio. Molimo vas da nadogradite na verziju %d ili noviju! + Umount module po zadanom + Globalna zadana vrijednost za \"Umount module\" u Profilima Aplikacije. Ako je omogućeno, uklonit će sve izmjene modula na sistemu za aplikacije koje nemaju postavljen Profil. + Uključivanjem ove opcije omogućit će KernelSU-u da vrati sve izmjenute datoteke od strane modula za ovu aplikaciju. + Ažuriranje + Skidanje module: %s + Započnite sa skidanjem: %s + Nova verzija: %s je dostupna, kliknite da skinete + Pokrenite + Prisilno Zaustavite + Resetujte + U Provođenju + Početna + Nije instalirano + Kliknite da instalirate + Superkorisnici: %d + Module: %d + Nepodržano + KernelSU samo podržava GKI kernele sad + Verzija Upravitelja + Otisak prsta + SELinux stanje + Instalirajte + Instalirajte + Ponovo pokrenite + Podešavanja + Verzija + Autor + Osvježi + Prikažite sistemske aplikacije + Sakrijte sistemske aplikacije + Sigurnosni mod + Ponovo pokrenite da bi proradilo + Module su isključene jer je u sukobu sa Magisk-om! + https://kernelsu.org/guide/what-is-kernelsu.html + Naučite kako da instalirate KernelSU i da koristite module + Podržite Nas + Pošaljite Izvještaj + Naučite KernelSU + Pogledajte izvornu kodu na %1$s
Pridružite nam se na %2$s kanalu
+ Domena + Pravila + Neuspješno ažuriranje SELinux pravila za: %s + Radi + Verzija: %d + Kernel + Permisivno + Deinstalirajte + Nepoznato + Nema instaliranih modula + Superkorisnik + Modula + Ponovo pokrenite u Pogonski Učitavatelj + Ponovo pokrenite u Oporavu + %s deinstalirana + Lagano Ponovo pokretanje + Neuspješno uključivanje module: %s + Ponovo pokrenite u Preuzimanje + Neuspješno isključivanje module: %s + Ponovo pokrenite u EDL + Neuspješna deinstalacija: %s + Isključeno + O + Jeste li sigurni da želite deinstalirati modulu %s\? + overlayfs nije dostupan, modula ne može raditi! + KernelSU je, i uvijek če biti, besplatan, i otvorenog izvora. Možete nam međutim pokazati da vas je briga s time da napravite donaciju. + Zadano + Šablon + Prilagođeno + Naziv profila + Sačuvaj Dnevnike +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-da/strings.xml b/KernelSU/manager/app/src/main/res/values-da/strings.xml new file mode 100644 index 000000000000..6ab022567f4c --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-da/strings.xml @@ -0,0 +1,83 @@ + + + Arbejder + Moduler: %d + Ikke understøttet + Kernel + KernelSU understøtter kun GKI kernels + Manager Version + SELinux-status + Deaktiveret + Tilladende + Superbruger + Håndhævende + Deaktivering af modul fejlede: %s + Intet modul installeret + Afinstaller + Installer + Installer + Genstart + Indstillinger + Blød Genstart + Genstart til Download + Genstart til EDL + Om + Er du sikker på, at du vil afinstallere modulet %s\? + %s afinstalleret + Afinstallation af: %s fejlede + overlayfs er ikke tilgængeligt, modulet kan ikke fungere! + Opdater + Send Log + Sikker tilstand + Genstart for at tage effekt + Lær KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + Lær hvordan man installerer KernelSU og moduler + Se source koden ved %1$s
Deltage i vores %2$s kanal
+ Standard + Skabelon + Monter navnerum + Arvet + Global + Grupper + Evner + SELinux-kontext + Afmonteret moduler + Afmontere moduler som standard + Aktivering af denne indstilling vil tillade KernelSU at gendanne hvilken som helst modificeret filer af modulet for denne applikation. + Opdatering + Downloader modulet: %s + Ny version: %s er tilgængelig, kilk for at downloade + Start + Tving Stop + Opdatering af SELinux-regler for: %s fejlede + Start download: %s + Klik for at installere + Version: %d + Hjem + Ikke installeret + Superbrugere: %d + Fingeraftryk + Ukendt + Aktivering af modul fejlede: %s + Genstart til Recovery + Modul + Forfatter + Genstart til Bootloader + Version + Gem system-apps + Vis system-apps + Moduler er deaktiveret, fordi der er konflikt med Magiskes! + Støt Os + KernelSU er, og vil altid være gratis og open source. Du kan stadig vise os din støtte ved at donere. + Brugerdefineret + Profilnavn + Individuel + Opdatering af App Profil for %s fejlede + Den globale standard værdi for \"Afmonter moduler\" i App Profiler. Hvis aktiveret vil den fjerne alle modulers modifikationer til system applikationerne der ikke har en sat Profil. + Domæne + Regler + Genstart + Den nuværende KernelSU version %d er for lav til manageren for at fungere ordentligt. Opgrader til version %d eller højere! + Gem Logfiler +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-de/strings.xml b/KernelSU/manager/app/src/main/res/values-de/strings.xml new file mode 100644 index 000000000000..047123467301 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-de/strings.xml @@ -0,0 +1,121 @@ + + + Startseite + Nicht installiert + Permissiv + Funktioniert + Version: %d + SuperUser + Tippen zum Installieren + Superuser: %d + Unbekannt + Erzwingen + Neustart in Bootloader + Neustart in Download-Modus + Neustart mit EDL-Modus + Autor + overlayfs nicht verfügbar, Modul kann nicht funktionieren! + Über + Module sind deaktiviert, weil es einen Konflikt mit Magisk gibt! + https://kernelsu.org/guide/what-is-kernelsu.html + Erfahren, wie KernelSU installiert und Module verwendet werden + Unterstütze uns + KernelSU ist und wird immer frei und quelloffen sein. Du kannst uns jedoch deine Unterstützung zeigen, indem du eine Spende tätigst. + SELinux-Kontext + Module standardmäßig aushängen + Globaler Standardwert für \'Module aushängen\' in App-Profilen. Falls aktiviert, werden alle Moduländerungen im System für alle Apps entfernt, für die kein Profil festgelegt ist. + Standard + Vorlage + Benutzerdefiniert + App-Profilaktualisierung für %s fehlgeschlagen + Vererbt + Global + Individuell + Domäne + Aktualisieren + Wenn du diese Option aktivierst, kann KernelSU alle von den Modulen für diese App geänderten Dateien wiederherstellen. + Regeln + Herunterladen startet: %s + Fehler beim Aktualisieren der SELinux-Regeln für: %s + Starten + Neue Version: %s verfügbar, tippen zum Aktualisieren + Stopp erzwingen + Neustart + Module: %d + Manager-Version + SELinux-Status + Deaktiviert + Modulaktivierung fehlgeschlagen: %s + Moduldeaktivierung fehlgeschlagen: %s + Keine Module installiert + Modul + Deinstallieren + Installieren + Neustarten + Einstellungen + Neustart in Recovery + %s deinstalliert + Version + Neu laden + System-Apps anzeigen + System-Apps ausblenden + Protokoll senden + KernelSU verstehen + Sicherer Modus + Neustarten, damit Änderungen wirksam werden + Quellcode unter %1$s ansehen
Unserem %2$s-Kanal beitreten
+ Profilname + Namespace einhängen + Gruppen + Fähigkeiten + Module aushängen + Modul herunterladen: %s + Nicht unterstützt + KernelSU unterstützt derzeit nur GKI-Kernel + Kernel + Fingerabdruck + Installieren + Soft-Reboot + Sicher, dass du das Modul %s deinstallieren möchtest\? + Deinstallation fehlgeschlagen: %s + Die aktuelle Kernel-Version %d ist zu alt für diese Manager-Version. Bitte auf Version %d oder höher upgraden! + Änderungsprotokoll + Erfolgreich importiert + In Zwischenablage exportieren + Kann lokale Vorlage nicht finden! + Vorlagen-ID existiert bereits! + Aus Zwischenablage importieren + Konnte Changelog nicht laden: %s + Name + Ungültige Vorlagen-ID + Online-Vorlagen synchronisieren + Vorlage erstellen + Schreibgeschützt + Import/Export + Fehler beim Speichern + Vorlage bearbeiten + ID + App-Profil-Template + Beschreibung + Speichern + verwalte lokale und online Profil Vorlagen + Löschen + Zwischenablage ist leer! + Vorlage ansehen + WebView-Debugging aktivieren + Kann verwendet werden zum Debugging von WebUI, bitte nur falls nötig aktivieren. + %1$s Partitionsabbild empfohlen + KMI auswählen + Weiter + Direkte Installation (empfohlen) + Datei auswählen + In inaktiven Slot installieren (nach OTA) + Nach einem Neustart wird dein Gerät **GEZWUNGEN** in den derzeit inaktiven Slot zu booten. +\nBenutze dies nur nach Fertigstellung des OTA. +\nFortfahren? + Root-Zugriff konnte nicht gewährt werden! + Öffnen + Updates suchen + Automatisch nach Updates suchen beim Öffnen der App + Protokolle Speichern +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-es/strings.xml b/KernelSU/manager/app/src/main/res/values-es/strings.xml new file mode 100644 index 000000000000..5623b47a2943 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-es/strings.xml @@ -0,0 +1,107 @@ + + + Inicio + No instalado + Toca para instalar + Funcionando + Versión: %d + Superusuarios: %d + Módulos: %d + No soportado + Por el momento, KernelSU solo es compatible con kernels genéricos (GKIs) + Versión del kernel + Versión del gestor + Huella del dispositivo + Estado de SELinux + + Desactivado + Enforcing de SELinux + Permisivo + Desconocido + Superusuario + No se pudo habilitar el módulo \"%s\" + No se pudo deshabilitar el módulo \"%s\" + No hay ningún módulo instalado + Módulo + Desinstalar + Instalar módulo + Instalar + Reiniciar + Ajustes + Reinicio minimo + Reiniciar en modo de recuperación + Reiniciar en modo de arranque + Reiniciar en modo Download + Reiniciar en modo EDL + Acerca de + ¿Estás seguro de que quieres desinstalar el módulo \"%s\"? + \"%s\" esta desinstalado + No se pudo desinstalar \"%s\" + Versión + Autor + El módulo no puede funcionar ya que OverlayFS no está disponible! + Recargar + Mostrar applicaciones del sistema + Ocultar applicaciones del sistema + Enviar registro de informe + Modo seguro + Reinicia para aplicar cambios + Se deshabilitaron los módulos ya que entran en conflicto con los de Magisk! + Aprende KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + Descubre cómo instalar KernelSU y utilizar módulos + Apóyanos + KernelSU es y siempre será, libre y de código abierto. De todas formas, puedes mostrarnos tu apoyo mediante una donación. + Mirar el código en %1$s
Únete a nuestro canal de %2$s
+ Predeterminado + Plantilla + Personalizado + Nombre de perfil + Montaje del espacio de nombres + Heredado + Global + Individual + Grupos + Capacidades + Contexto de SELinux + Desmontar módulos + No se pudo actualizar el perfil de la applicación para %s + Desmontar módulos por defecto + El valor global predeterminado para \"Desmontar módulos\" en los perfiles de las aplicaciones. Si la habilitas, se desharán todas las modificaciones al sistema hechas por el módulo para las applicaciones que no tengan un perfil establecido. + Si habilitas esta opción, KernelSU podrá restaurar cualquier archivo modificado por los módulos para esta app. + Dominio + Reglas + Actualizar + Descargando módulo: \"%s\" + Iniciar descarga: %s + Nueva versión: %s está disponible, toque para actualizar + Abrir Aplicacion + Forzar cierre de la aplicacion + Reiniciar aplicacion + Falló al actualizar reglas de SEpolicy por: %s + La versión actual de KernelSU %d es demasiado baja para que el gestor funcione correctamente. ¡Por favor actualiza a la versión %d o superior! + Registro de cambios + Importado con exíto + Exportar a portapapeles + No se puede encontrar plantilla local para exportar! + Ya existe un ID de la plantilla! + Importar desde portapapeles + Error en obtener los registros de cambios: %s + Nombre + ID de la plantilla es invalido + Sincronizar plantillas en linea + Crear plantilla + sololectura + Importar/Exportar + Fallo en guardar plantilla + Editar plantilla + id + Plantilla del perfil de la aplicacion + Descripción + Guardar + Administrar las plantillas locales y de en linea del perfil de la aplicacion + Eliminar + El portapapeles esta vacio! + Ver plantilla + Guardar Registros +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-et/strings.xml b/KernelSU/manager/app/src/main/res/values-et/strings.xml new file mode 100644 index 000000000000..d514fc6aea79 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-et/strings.xml @@ -0,0 +1,112 @@ + + + Töötamine + Versioon: %d + Mooduleid: %d + Tuum + Manageri versioon + Sõrmejälg + Lubav + Mooduli lubamine ebaõnnestus: %s + Mooduleid pole paigaldatud + Taaskäivita + Taaskäivita taastusesse + Kas soovid kindlasti eemaldada mooduli %s? + %s on eemaldatud + Raporteeri logi + Turvarežiim + Muudatuste rakendamiseks taaskäivita + Õpi KernelSUd + https://kernelsu.org/guide/what-is-kernelsu.html + Vaikimisi + Haagi nimeruum + Lahtihaagitud moodulid + Rakenduseprofiili uuendamine %s jaoks ebaõnnestus + Haagi moodulid vaikimisi lahti + Allalaadimise alustamine: %s + SELinux reeglite uuendamine ebaõnnestus: %s + Muuda malli + Rakenduseprofiili mall + ID + vaid lugemiseks + malli ID juba eksisteerib! + Ekspordi lõikelauale + Sünkrooni võrgumallid + Muudatuste logi hankimine ebaõnnestus: %s + Kodu + Klõpsa paigaldamiseks + Pole paigaldatud + Mittetoetatud + Superkasutajaid: %d + KernelSU toetab hetkel vaid GSI tuumasid + SELinuxi olek + Keelatud + Jõustav + Teadmata + Superkasutaja + Mooduli keelamine ebaõnnestus: %s + Moodul + Taaskäivita käivituslaadurisse + Eemalda + Paigalda + Teave + Paigalda + Seaded + Pehme taaskäivitus + Taaskäivita allalaadimisrežiimi + Taaskäivita EDL-i + Värskenda + Autor + Eemaldamine ebaõnnestus: %s + Versioon + overlayfs pole saadaval, moodul ei saa töötada! + Kuva süsteemirakendused + Peida süsteemirakendused + Moodulid on keelatud, kuna need lähevad konflikti Magiski omadega! + Õpi KernelSUd paigaldama ja mooduleid kasutama + Toeta meid + Grupid + KernelSU on, ja alati jääb, tasuta ning avatud lähtekoodiga kättesaadavaks. Sellegipoolest võid sa näidata, et hoolid, ning teha annetuse. + Mall + Vaata lähtekoodi %1$sis
Liitu meie %2$si kanaliga
+ Profiili nimi + Kohandatud + Päritud + Globaalne + Individuaalne + Võimekused + Sobimatu malli ID + SELinux kontekst + Praegune KernelSU versioon %d on liiga madal, haldur ei saa konkreetselt toimida. Palun täienda versioonile %d või kõrgem! + Domeen + Käivita + Sundpeata + Reeglid + Uuenda + Mooduli allalaadimine: %s + Uus versioon: %s on saadaval, klõpsa täiendamiseks + Taaskäivita + Muudatuste logi + Nimi + Kirjeldus + Edukalt imporditud + Salvesta + Lõikelaud on tühi! + Kustuta + Vaata malli + Impordi/ekspordi + Impordi lõikelaualt + Malli salvestamine ebaõnnestus + Loo mall + Halda kohalikke ja võrgusolevaid rakenduseprofiili malle + Selle valiku lubamine lubab KernelSU-l taastada selle rakenduse moodulite poolt mistahes muudetud faile. + Eksportimiseks kohalikku malli ei leitud! + Globaalne vaikeväärtus \"Lahtihaagitud moodulitele\" rakenduseprofiilides. Lubamisel eemaldab see kõik moodulite süsteemimuudatused rakendustele, millel ei ole profiili määratud. + Saab kasutada WebUI silumiseks, palun luba ainult vajadusel. + Juurkasutaja andmine ebaõnnestus! + Kontrolli uuendusi + Rakenduse avamisel kontrolli automaatselt uuendusi + Ava + Luba WebView silumine + Salvesta Logid +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-fa/strings.xml b/KernelSU/manager/app/src/main/res/values-fa/strings.xml new file mode 100644 index 000000000000..a5cdb2f7ff58 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-fa/strings.xml @@ -0,0 +1,67 @@ + +خانه +نصب نشده است +برای نصب ضربه بزنید +به درستی کار می‌کند +نسخه: %d +برنامه های با دسترسی روت: %d +ماژول‌ها: %d +پشتیبانی نشده +کرنل اس یو فقط هسته های gki را پشتیبانی میکند + هسته + نسخه برنامه + اثرانگشت + وضعیت SELinux +غیرفعال +قانونمند +آزاد +ناشناخته +دسترسی روت + فعال کردن ماژول ناموفق بود: %s +غیرفعال کردن ماژول ناموفق بود: %s +هیچ ماژولی نصب نشده است +ماژول +لغو نصب +نصب +نصب +راه اندازی دوباره +تنظیمات +راه اندازی نرم +راه اندازی به ریکاوری +راه اندازی به بوتلودر +راه اندازی به حالت دانلود +راه اندازی به EDL +درباره + آیا مطمئنید که میخواهید ماژول %s را پاک کنید؟ +%s پاک شد +پاک کردن ناموفق بود: %s +نسخه +سازنده +overlayfs موجود نیست. مازول کار نمیکند!! +تازه‌سازی +نمایش برنامه های سیستمی +مخفی کردن برنامه های سیستمی +ارسال وقایع +حالت امن +راه‌اندازی مجدد برای تاثیرگذاری +مازول به دلیل تعارض با مجیسک غیرفعال شده اند\'s! +یادگیری کرنل اس یو +https://kernelsu.org/guide/what-is-kernelsu.html +یاد بگیرید چگونه از کرنل اس یو و ماژول ها استفاده کنید +از ما حمایت کنید +KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است. + +Join our %2$s channel ]]> + +پروفایل برنامه +پیش‌فرض +قالب +شخصی سازی شده +اسم پروفایل +Mount namespace +اثر گرفته +گلوبال +تکی +جداکردن ماژول ها + ذخیره گزارش‌ها + diff --git a/KernelSU/manager/app/src/main/res/values-fil/strings.xml b/KernelSU/manager/app/src/main/res/values-fil/strings.xml new file mode 100644 index 000000000000..fc527bf30627 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-fil/strings.xml @@ -0,0 +1,70 @@ + + + Katayuan ng SELinux + Hindi pinagana + Enforcing + Permissive + Hindi naka-install + Home + Pindutin para mag-install + Gumagana + Bersyon: %d + Hindi matukoy + Mga Modyul: %d + Hindi Suportado + Sinusuportahan lang ng KernelSU ang mga kernel ng GKI ngayon + Nabigong paganahin ang modyul: %s + Nabigong i-disable ang modyul: %s + Walang naka-install na modyul + Modyul + I-install + I-install + I-reboot + I-soft Reboot + I-reboot sa Download + I-reboot sa EDL + Tungkol + Sigurado ka bang gusto mong i-uninstall ang modyul %s\? + Na-uninstall ang %s + Nabigong i-uninstall: %s + May-akda + Ang overlayfs ay hindi magagamit, ang modyul ay hindi gagana! + I-refresh + Ipakita ang mga application ng system + Magpadala ng Log + I-reboot para umepekto + Hindi pinagana ang mga modyul dahil salungat ito sa Magisk! + Alamin ang KernelSU + Matutunan kung paano mag-install ng KernelSU at gumamit ng mga modyul + Suportahan Kami + Ang KernelSU ay, at palaging magiging, libre, at open source. Gayunpaman, maaari mong ipakita sa amin na nagmamalasakit ka sa pamamagitan ng pagbibigay ng donasyon. + Tingnan ang source code sa %1$s
Sumali sa aming %2$s channel
+ I-mount ang namespace + Indibidwal + Mga Grupo + Mga Kakayanan + Konteksto ng SELinux + I-unmount ang mga modyul + Nabigong i-update ang App Profile para sa %s + Ang kasalukuyang bersyon ng KernelSU %d ay masyadong mababa para gumana nang maayos ang manager. Mangyaring mag-upgrade sa bersyon %d o mas mataas! + Ang pagpapagana sa opsyong ito ay magbibigay-daan sa KernelSU na ibalik ang anumang binagong file ng mga modyul para sa aplikasyon na ito. + Mga Tuntunin + Nagda-download ng modyul: %s + Simulan ang pag-download: %s + Bagong bersyon: Available ang %s, i-click upang i-download + Ilunsad + Pilit na I-hinto + I-restart + Nabigong i-update ang mga panuntunan ng SELinux para sa: %s + Bersyon ng Manager + Mga setting + I-reboot sa Recovery + I-reboot sa Bootloader + Bersyon + I-uninstall + Itago ang mga application ng system + Pangalan ng profile + Minana + Ang pangkalahatang default na halaga para sa \"Umount modules\" sa Mga Profile ng App. Kung pinagana, aalisin nito ang lahat ng mga pagbabago sa modyul sa system para sa mga aplikasyon na walang hanay ng Profile. + I-save ang mga Log +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-fr/strings.xml b/KernelSU/manager/app/src/main/res/values-fr/strings.xml new file mode 100644 index 000000000000..01c0672b87a6 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-fr/strings.xml @@ -0,0 +1,135 @@ + + + Non installé + Fonctionnel + Version : %d + Super-utilisateurs : %d + Modules : %d + Actuellement, KernelSU ne supporte que les noyaux GKI + Noyau + Empreinte digitale + Mode SELinux + Désactivé + Permissive + Inconnu + Super-utilisateur + Aucun module installé + Accueil + Appuyez ici pour installer + Non supporté + Échec de la désinstallation : %s + Version + Version du gestionnaire + Enforcing + Échec de l\'activation du module : %s + Modules + Désinstaller + Installer + Échec de la désactivation du module : %s + Redémarrer + Installer + Paramètres + Redémarrer en mode bootloader + Redémarrage progressif + Redémarrer en mode de récupération + Redémarrer en mode EDL + À propos + %s a été désinstallé + Redémarrer en mode de téléchargement + Auteur + Êtes-vous sûr(e) de vouloir désinstaller le module %s \? + Découvrir KernelSU + OverlayFS est indisponible, impossible de faire fonctionner les modules ! + Rafraîchir + Afficher les applications système + Masquer les applications système + Mode sans échec + Rapport de journal + Redémarrez pour appliquer les modifications + Les modules sont désactivés car ils sont en conflit avec ceux de Magisk ! + https://kernelsu.org/guide/what-is-kernelsu.html + Soutenez-nous + Découvrez comment installer KernelSU et utiliser les modules + KernelSU est, et restera toujours, gratuit et open source. Vous pouvez cependant nous témoigner de votre soutien en nous faisant un don. + Voir le code source sur %1$s
+\nRejoindre notre canal %2$s
+ Modèle + Par défaut + Personnalisé + Nom du profil + Espace de noms de montage + Hérité + Individuel + Contexte SELinux + Global + Groupes + Capacités + Démonter les modules + Échec de la modification du profil d\'application de %s + L\'activation de cette option permettra à KernelSU de restaurer tous les fichiers modifiés par les modules de cette application. + Démonter les modules par défaut + Valeur globale par défaut pour l\'option « Démonter les modules » dans les profils d\'application. Lorsqu\'elle est activée, les modifications apportées au système par les modules seront supprimées pour les applications qui n\'ont pas de profil défini. + Domaine + Règles + Mettre à jour + Téléchargement du module : %s + Lancer + La version %s est disponible, appuyez ici pour mettre à jour + Début du téléchargement de : %s + Forcer l\'arrêt + Relancer l\'application + Échec de la mise à jour des règles SELinux pour : %s + La version actuelle de KernelSU (%d) est trop ancienne pour que le gestionnaire fonctionne correctement. Veuillez passer à la version %d ou à une version supérieure ! + Importation réussie + Exporter vers le presse-papiers + Impossible de trouver un modèle local à exporter ! + L\'id du modèle existe déjà ! + Journal des modifications + Importer à partir du presse-papiers + Échec de récupération du journal des modifications : %s + Nom + id de modèle invalide + Synchroniser les modèles en ligne + Créer un modèle + lecture seule + Importer/exporter + Échec de l\'enregistrement du modèle + Modifier le modèle + id + Modèles de profils d\'application + Description + Enregistrer + Gérer les modèles de profils d\'application locaux et en ligne + Supprimer + Le presse-papiers est vide ! + Voir le modèle + Vérifier automatiquement les mises à jour à l\'ouverture de l\'application + Vérifier les mises à jour + Activer le débogage de WebView + Peut être utilisé pour déboguer WebUI, n\'activez cette option que si nécessaire. + Échec de l\'octroi des privilèges root ! + Ouvrir + Installation directe (recommandé) + Sélectionner un fichier + Installer dans l\'emplacement inactif (après OTA) + Votre appareil sera **FORCÉ** à démarrer sur l\'emplacement inactif actuel après un redémarrage ! +\nN\'utilisez cette option qu\'une fois la mise à jour OTA terminée. +\nContinuer ? + Suivant + L\'image de la partition %1$s est recommandée + Sélectionner une KMI + Minimiser l\'image clairsemée + Redimensionne l\'image clairsemée où se trouve le module à sa taille réelle. Notez que cela peut entraîner un dysfonctionnement du module, alors utilisez cette fonctionnalité uniquement lorsque nécessaire (pour la sauvegarde, par exemple) + Désinstaller + Désinstaller temporairement + Désinstaller définitivement + Restaurer l\'image stock + Restaurer l\'image stock d\'usine (s\'il en existe une sauvegarde), option généralement utilisée avant une mise à jour OTA ; si vous avez besoin de désinstaller KernelSU, utilisez plutôt l\'option « Désinstaller définitivement ». + Flash en cours + Flash réussi + Échec du flash + lkm sélectionné : %s + Désinstallation complète et permanente de KernelSU (root et tous les modules). + Désinstaller KernelSU temporairement et rétablir l\'état original au redémarrage suivant. + Enregistrer les Journaux +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-hi/strings.xml b/KernelSU/manager/app/src/main/res/values-hi/strings.xml new file mode 100644 index 000000000000..f930446936f3 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-hi/strings.xml @@ -0,0 +1,84 @@ + + + प्रभाव में होने के लिए रीबूट करें + जानें कि KernelSU कैसे स्थापित करें और मॉड्यूल का उपयोग कैसे करें + अज्ञात + सिस्टम एप्प दिखाए + %s अनइंस्टॉल सफल हुआ + मॉड्यूल्स अनमाउंट करें + लॉग भेजे + डिसेबल्ड (बंद) + हमें प्रोत्साहन दें + Inherited + मॉड्यूल बंद कर दिए गए हैं क्योंकि यह मैजिक के साथ टकरा रहे है! + क्या बदलाव हुए है + पर्मिसिव + डाउनलोड में रिबूट करें + डिफ़ॉल्ट रूप से मॉड्यूल अनमाउन्ट करें + इस विकल्प को चालू करने से KernelSU को इस एप्लिकेशन के लिए मॉड्यूल द्वारा किसी भी मोडिफाइड फ़ाइल को रिस्टोर करें। + Individual + %s मॉड्यूल चालू करने में विफल + जबर्दस्ती बंद करें + EDL मोड में रिबूट करें + फिर से चालू करें + क्षमताएं + सुपरयूजर : %d + %s की डाउनलोडिंग स्टार्ट करें + Global + ऐप प्रोफाइल में \"अनमाउंट मॉड्यूल\" के लिए ग्लोबल डिफ़ॉल्ट वैल्यू। यदि चालू किया गया है, तो यह एप्लीकेशंस के लिऐ सिस्टम के सभी मॉड्यूल मोडिफिकेशन को हटा देगा जिनकी प्रोफ़ाइल सेट नहीं है। + मॉड्यूल्स : %d + एनफोर्सिंग + SELinux context + फिंगरप्रिंट + डिफॉल्ट + लॉन्च करें + सेफ मोड + मैनेजर के ठीक से काम करने के लिए वर्तमान KernelSU वर्जन %d बहुत कम है। कृपया वर्जन %d या उच्चतर में अपग्रेड करें! + रिकवरी में रिबूट करें + सॉफ्ट रिबूट + प्रोफाइल का नाम + KernelSU मुफ़्त और ओपन सोर्स और हमेशा रहेगा। हालाँकि आप दान देकर हमें दिखा सकते हैं कि आप संरक्षण करते हैं। + अनइंस्टॉल करें + Namspace माउंट करें + इंस्टाल करें + इंस्टाल करने के लिए क्लिक करें + नियम + समूह + Overlayfs उपलब्ध नहीं है, मॉड्यूल काम नहीं कर सकता ! + मॉड्यूल + निर्माता + हमारे बारे में + वर्जन: %d + रीबूट करें + KernelSU अभी केवल GKI कर्नल्स को सपोर्ट करता है + SELinux स्थिति + सिस्टम एप्प छिपाए + वर्जन + सपोर्ट नहीं करता है + डोमेन + होम + कस्टम + टेम्पलेट + रिफ्रेश + %s मॉड्यूल डाउनलोड हो रहा है + अपडेट + KernelSU सीखें + क्या आप सच में मॉड्यूल %s को अनइंस्टॉल करना चाहते हैं\? + %s अनइंस्टल करने में असफल + सुपरयूजर + सेटिंग + काम कर रहा है + %s मॉड्यूल बंद करने में विफल + कोई मॉड्यूल इंस्टाल नहीं हुआ + इंस्टाल करें + कर्नल + इंस्टाल नहीं हुआ + %s के लिए ऐप प्रोफ़ाइल अपडेट करने में विफल + https://kernelsu.org/guide/what-is-kernelsu.html + %s के लिए SELinux नियमों को अपटेड करने में विफल + बुटलोडर में रिबूट करें + %1$s पर स्रोत कोड देखें
हमारे %2$s चैनल से जुड़ें
+ मैनेजर वर्जन + नया वर्जन: %s उपलब्ध है,अपग्रेड के लिए क्लिक करें + लॉग सहेजें +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-hr/strings.xml b/KernelSU/manager/app/src/main/res/values-hr/strings.xml new file mode 100644 index 000000000000..9fb1e4c67460 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-hr/strings.xml @@ -0,0 +1,83 @@ + + + Prikažite sistemske aplikacije + Sakrijte sistemske aplikacije + Pošaljite Izvještaj + Sigurnosni mod + Ponovno pokrenite da bi proradilo + Neuspješno ažuriranje SELinux pravila za: %s + Početna + Nije instalirano + Verzija: %d + Kliknite da instalirate + Radi + Superkorisnici: %d + Module: %d + Nepodržano + KernelSU samo podržava GKI kernele sad + Kernel + Verzija Voditelja + Otisak prsta + Isključeno + U Provođenju + Permisivno + SELinux stanje + Nepoznato + Superkorisnik + Neuspješno uključivanje module: %s + Neuspješno isključivanje module: %s + Nema instaliranih modula + Modula + Deinstalirajte + Instalirajte + Instalirajte + Ponovno pokrenite + Postavke + Lagano Ponovno pokretanje + Ponovno pokrenite u Oporavu + Ponovno pokrenite u Pogonski Učitavalac + Ponovno pokrenite u Preuzimanje + Ponovo pokrenite u EDL + O + Jeste li sigurni da želite deinstalirati modulu %s\? + %s deinstalirana + Neuspješna deinstalacija: %s + Verzija + Autor + Osvježi + overlayfs nije dostupan, modula ne može raditi! + Module su isključene jer je u sukobu sa Magisk-om! + Naučite KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + Naučite kako da instalirate KernelSU i da koristite module + Podržite Nas + KernelSU je, i uvijek če biti, besplatan, i otvorenog izvora. Možete nam međutim pokazati da vas je briga s time da napravite donaciju. + Pogledajte izvornu kodu na %1$s
Pridružite nam se na %2$s kanalu
+ Zadano + Šablon + Prilagođeno + Naziv profila + Naslijeđen + Imenski prostor nosača + Ažuriranje Profila Aplikacije za %s nije uspjelo + Globalan + Pojedinačan + Umount module + Grupe + Sposobnosti + SELinux kontekst + Trenutna KernelSU verzija %d je preniska da bi voditelj ispravno radio. Molimo vas da nadogradite na verziju %d ili noviju! + Umount module po zadanom + Globalna zadana vrijednost za \"Umount module\" u Profilima Aplikacije. Ako je omogućeno, uklonit će sve izmjene modula na sistemu za aplikacije koje nemaju postavljen Profil. + Domena + Uključivanjem ove opcije omogućit će KernelSU-u da vrati sve izmjenute datoteke od strane modula za ovu aplikaciju. + Pravila + Ažuriranje + Preuzimanje module: %s + Započnite sa preuzimanjem: %s + Nova verzija: %s je dostupna, kliknite da preuzmete + Pokrenite + Prisilno Zaustavite + Resetujte + Spremi Zapise +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-hu/strings.xml b/KernelSU/manager/app/src/main/res/values-hu/strings.xml new file mode 100644 index 000000000000..2b52317c1b79 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-hu/strings.xml @@ -0,0 +1,106 @@ + + + Működik + Verzió: %d + Modulok: %d + KernelSU csak GKI kerneleket támogat jelenleg + Kernel + App verziója + Build Fingerprint + Kikapcsolt + Újraindítás letöltő módba + Újraindítás EDL-be + Névjegy + Biztos vagy benne hogy eltávolítod a következő modult: %s\? + Nem sikerült eltávolítani: %s + Készítő + Overlayfs nem elérhető, a modul nem tud enélkül működni! + Újratöltés + Mutasd a rendszer alkalmazásokat + Rejtsd el a rendszer alkalmazásokat + Biztonságos mód + A modul letiltva mert ütközik a Magisk verziójával! + Tudj meg többet a KernelSU-ról + Tudd meg hogyan telepítsd a KernelSU-t és használd moduljait + Támogass minket + Tekintsd meg a forráskódot a %1$s-n
Csatlakozz a %2$s csatornánkhoz
+ Alapértelmezett + Sablon + Egyedi + Profil neve + Mountold a névteret + Örökölt + https://kernelsu.org/guide/what-is-kernelsu.html + Különálló + Csoportok + Jogosultságok + SElinux kontextus + Umountold a modulokat alpértelmezés szerint + Ennek az opciónak az engedélyezése lehetővé teszi, hogy a KernelSU visszaállítsa az alkalmazás moduljai által módosított fájlokat. + Tartomány + Szabályok + Frissítés + A %s modul letöltése folyamatban + Indítsd el a letöltést: %s + Indítás + Kényszerített leállítás + újraindítás + Kezdőlap + Nincs telepítve + Kattints a telepítéshez + Engedélyezett alkalmazások: %d + Nem támogatott + SELinux státusz + Érvényesítés + Megengedő + Ismeretlen + Super user + Nem sikerült engedélyezni a következő modult: %s + Nem sikerült letiltani a következő modulokat: %s + Nincs modul telepítve + Modulok + Eltávolítás + Telepítés + Telepítés + Újraindítás + Beállítások + Android felület újraindítása + Újraindítás recovery-módba + Újraindítás bootloader-módba + %s törölve + Verzió + Napló küldése + Indítsd újra a készüléket hogy érvényesítsd a változást + A KernelSU ingyenes és nyílt forráskódú és mindig is az lesz. Te viszont meg tudod mutatni azt, hogy törődsz ennek a projektnek a sorsával egy adomány formájában. + Globális + Unmountold a modulokat + Nem sikerült frissíteni az App Profilt ehhez %s + A „Modulok csatlakoztatása” globális alapértelmezett értéke az alkalmazásprofilokban. Ha engedélyezve van, eltávolítja a rendszer összes modul-módosítását azoknál az alkalmazásoknál, amelyeknek nincs beállított profilja. + Új verzió: %s elérhető, kattints a frissítéshez + Nem sikerült frissíteni a SELinux szabályait a következőhöz: %s + A jelenlegi KernelSU verzió %d túlságosan elavult. Kérlek frissíts a %d verzióra vagy újabbra! + Sikeresen importálva + Exportálás a vágólapból + A helyi sablon nem található az exportáláshoz! + A sablon ID már létezik! + Változások + Importálás a vágólapból + A változásnapló lekérése nem sikerült: %s + Név + Hibás sablon ID + Online sablonok szinkronizálása + Sablon készítése + csak olvasható + Import/Export + A sablon mentése sikertelen + Sablon szerkesztése + id + Alkalmazásprofil sablon + Leírás + Mentés + Az alkalmazásprofil helyi és online sablon kezelése + Törlés + A vágólap üres! + Sablon megnézése + Naplók Mentése +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-in/strings.xml b/KernelSU/manager/app/src/main/res/values-in/strings.xml new file mode 100644 index 000000000000..98698f529aa9 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-in/strings.xml @@ -0,0 +1,135 @@ + + + Beranda + Tidak terinstal + Klik untuk menginstal + Berfungsi + Versi: %d + SuperUser: %d + Modul: %d + Tidak didukung + KernelSU saat ini hanya mendukung kernel GKI + Kernel + Versi Manager + Identitas + Status SELinux + Nonaktif + Enforcing + Permissive + Unknown + SuperUser + Gagal mengaktifkan modul: %s + Gagal menonaktifkan modul: %s + Tidak ada modul + Modul + Hapus + Instal + Instal + Reboot + Pengaturan + SoftReboot + But ke Recovery + But ke Bootloader + But ke Download + But ke EDL + Tentang + Yakin menghapus modul %s? + %s berhasil dihapus + Gagal menghapus: %s + Versi + Oleh + OverlayFS tidak tersedia, modul tidak berfungsi! + Muat ulang + Tampilkan aplikasi sistem + Sembunyikan aplikasi sistem + Laporkan Log + Mode aman + Reboot agar berfungsi + Konflik dengan Magisk, fungsi modul ditiadakan! + Pelajari KernelSU + https://kernelsu.org/id_ID/guide/what-is-kernelsu.html + Pelajari cara instal KernelSU dan menggunakan modul + Dukung Kami + KernelSU akan selalu menjadi aplikasi gratis dan terbuka. Anda dapat memberikan donasi sebagai bentuk dukungan. + Lihat kode sumber di %1$s
Gabung kanal %2$s kami
+ Profil Apl + Bawaan + Templat + Khusus + Nama profil + Mount Namespace + Diwariskan + Universal + Individual + Kelompok + Kemampuan + Konteks SELinux + Umount Modul + Gagal membarui Profil pada %s + Melepas Modul secara bawaan + Menggunakan \"Umount Modul\" secara universal pada Profil aplikasi. Jika diaktifkan, akan menghapus semua modifikasi sistem untuk aplikasi yang tidak memiliki set Profil. + Aktifkan opsi ini agar KernelSU dapat memulihkan kembali berkas termodifikasi oleh modul pada aplikasi ini. + Domain + Aturan + Membarui + Mengunduh modul: %s + Mulai mengunduh: %s + Tersedia versi terbaru: %s, Klik untuk membarui + Jalankan + Paksa Berhenti + Mulai ulang + Gagal membarui aturan SELinux pada: %s + Versi KernelSU %d terlalu rendah agar manajer berfungsi normal. Harap membarui ke versi %d atau di atasnya! + Catatan Perubahan + Berhasil diimpor + Ekspor ke papan klip + Tidak ditemukan templat lokal untuk diekspor! + Id templat sudah ada! + Impor dari papan klip + Gagal mengambil Changelog: %s + Nama + Id templat tidak valid + Sinkronkan templat daring + Buat Templat + Impor/Ekspor + Gagal menyimpan templat + Edit Templat + id + Templat Profil Aplikasi + Deskripsi + Simpan + Atur templat Profil yang lokal dan daring + Hapus + Papan klip kosong! + Lihat Templat + ReadOnly + Pengawakutuan WebView + Dapat mengawakutu WebView, hanya aktifkan jika butuh. + %1$s image partisi terekomendasi + Pilih KMI + Selanjutnya + Gawai akan **DIPAKSA** untuk but ke slot nonaktif! +\nHANYA gunakan setelah proses OTA selesai. +\nLanjutkan? + Instal Langsung (rekomendasi) + Pilih berkas + Instal ke slot nonaktif (setelah OTA) + Gagal memberikan akses root! + Buka + Cek terbaru + Cek terbaru setiap membuka aplikasi + Meminimalkan sparse image + Mengembalikan sparse image, lokasi modul disimpan, ke ukuran sebenarnya. Dapat menyebabkan modul bekerja abnormal, maka gunakan saat dibutuhkan saja (mis. untuk pencadangan) + Hapus permanen KernelSU (root dan modul). + Hapus Temporer + Pulihkan Image Asal + Hapus + Hapus Temporer KernelSU, pulihkan ke kondisi asali setelah but berikutnya. + Hapus Permanen + Pulihkan image bawaan ROM (jika cadangan tersedia), umumnya dilakukan sebelum OTA; jika ingin menghapus KernelSU, gunakan fungsi \"Hapus Permanen\". + Pemasangan Berhasil + LKM dipilih: %s + Pasang + Pemasangan Gagal + Simpan Log +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-it/strings.xml b/KernelSU/manager/app/src/main/res/values-it/strings.xml new file mode 100644 index 000000000000..7bd3f9247356 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-it/strings.xml @@ -0,0 +1,134 @@ + + + Home + Non installato + Clicca per installare + In esecuzione + Versione: %d + Applicazioni con accesso root: %d + Moduli installati: %d + Non supportato + KernelSU ora supporta solo i kernel GKI + Kernel + Versione del manager + Impronta della build di Android + Stato di SELinux + Disabilitato + Enforcing + Permissive + Sconosciuto + Accesso root + Impossibile abilitare il modulo: %s + Impossibile disabilitare il modulo: %s + Nessun modulo installato + Modulo + Disinstalla + Installa + Installa + Riavvia + Impostazioni + Riavvio rapido + Riavvia in modalità Recovery + Riavvia in modalità Bootloader + Riavvia in modalità Download + Riavvia in modalità EDL + Informazioni + Sei sicuro di voler disinstallare il modulo %s? + %s disinstallato + Impossibile disinstallare: %s + Versione + Autore + overlayfs non è disponibile, i moduli non possono funzionare! + Ricarica + Mostra app di sistema + Nascondi app di sistema + Invia log + Modalità provvisoria + Riavvia per applicare la modifica + I moduli sono disabilitati perché in conflitto con quelli di Magisk! + Scopri KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + Scopri come installare KernelSU e utilizzare i moduli + Supportaci + KernelSU è, e sempre sarà, gratuito e open source. Puoi comunque mostrarci il tuo apprezzamento facendo una donazione. + Unisciti al nostro canale %2$s]]> + Nome profilo + Spazio dei nomi del mount + Globale + Gruppi + Ereditato + Individuale + Predefinito + Personalizzato + Modello + Scollega moduli + Contesto SELinux + Aggiornamento App Profile per %s fallito + Aggiorna + Apri + Capacità + Scollega moduli da default + Regole + Sto scaricando il modulo: %s + Inizia a scaricare:%s + Nuova versione: %s disponibile, tocca per aggiornare + Arresto forzato + Riavvia + Aggiornamento regole SELinux per %s fallito + Attivando questa opzione permetterai a KernelSU di ripristinare ogni file modificato dai moduli per questa app. + Dominio + Il valore predefinito per \"Scollega moduli\" in App Profile. Se attivato, rimuoverà tutte le modifiche al sistema da parte dei moduli per le applicazioni che non hanno un profilo impostato. + La versione attualmente installata di KernelSU (%d) è troppo vecchia ed il gestore non può funzionare correttamente. Si prega di aggiornare alla versione %d o successiva! + Registro aggiornamenti + Crea modello + Modifica modello + identificativo + Identificativo modello non valido + Nome + Visualizza modello + Sola lettura + L\'identificativo del modello esiste già! + Importa/Esporta + Importa dagli appunti + Esporta negli appunti + Impossibile trovare modello locale da esportare! + Importato con successo + Sincronizza i modelli remoti + Gli appunti sono vuoti! + Impossibile ottenere l\'accesso root! + Modelli App Profile + Gestisci i modelli locali e remoti di App Profile + Elimina + Descrizione + Salva + Impossibile salvare il modello + Apri + Impossibile reperire il changelog: %s + Controlla aggiornamenti + Controlla automaticamente la disponibilità di aggiornamenti all\'apertura dell\'applicazione + Abilita il Debug di WebView + Può essere usato per svolgere il debug di WebUI, è consigliato attivarlo solo quando necessario. + È consigliato usare immagine della partizione %1$s + Scegli il KMI + Avanti + Installazione diretta (Raccomandata) + Scegli un File + Installa nello Slot Inattivo (Dopo OTA) + Il tuo dispositivo sarà **FORZATO** ad avviarsi nello slot inattivo dopo il riavvio! +\nUsa questa opzione solo quando l\'applicazione dell\'aggiornamento OTA è terminata. +\nProcedere? + Riduci la dimensione dell\'immagine moduli sparse al minimo + Riduci la dimensione dell\'immagine sparse dei moduli alla sua reale dimenzione. Nota che questo potrebbe causare malfunzionamenti dei moduli quindi utilizzala solo quando necessario (ad esempio in caso di backup) + Disinstalla + Disinstalla Temporaneamente + Disinstalla Permanentemente + Ripristina immagine originale del produttore + Disinstalla temporaneamente KernelSU, ripristina lo stato originale dopo il prossimo riavvio. + Disinstalla KernelSU (Root e tutti i moduli) completamente e permanentemente. + Installazione + Installazione completata + Installazione fallita + LKM selezionato: %s + Ripristina l\'immagine di fabbrica del produttore (se il backup è presente), solitamente usato prima di applicare l\'OTA; se devi disinstallare KernelSU, utilizza invece \"Disinstalla Permanentemente\". + Salva Registri + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-iw/strings.xml b/KernelSU/manager/app/src/main/res/values-iw/strings.xml new file mode 100644 index 000000000000..18cfc638eb9d --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-iw/strings.xml @@ -0,0 +1,84 @@ + + + הפעל מחדש כדי להכניס לתוקף + למד כיצד להתקין את KernelSU ולהשתמש במודולים + לא ידוע + הצג אפליקציות מערכת + %s הוסר + הסרת טעינת מודולים + שלח לוג + מושבת + תמכו בנו + ירושה + מודולים מושבתים מכיוון שהם מתנגשים עם זה של Magisk! + יומן שינויים + התרים + הפעלה מחדש למצב הורדה + טעינת מודולים כברירת מחדל + הפעלת אפשרות זו תאפשר ל-KernelSU לשחזר קבצים שהשתנו על ידי המודולים עבור יישום זה. + אישי + הפעלת המודל נכשלה: %s + עצירה בכח + הפעלה מחדש למצב EDL + איתחול + יכולת + משתמשי על: %d + מפעיל מודל: %s + גלובלי + ערך ברירת המחדל הגלובלי עבור \"טעינת מודולים\" בפרופילי אפליקציה. אם מופעל, זה יסיר את כל שינויי המודול למערכת עבור יישומים שאין להם ערכת פרופיל. + מודלים:%d + אכיפה + הקשר SELinux + טביעת אצבע + ברירת מחדל + להשיק + מצב בטוח + גרסת KernelSU הנוכחית %d נמוכה מדי כדי שהמנהל יפעל כראוי. אנא שדרג לגרסה %d ומעלה! + הפעלה מחדש לריקברי + רך Reboot + שם פרופיל + KernelSU הוא, ותמיד יהיה, חינמי וקוד פתוח. עם זאת, תוכל להראות לנו שאכפת לך על ידי תרומה. + הסרה + טעינת מרחב שמות + התקנה + לחץ להתקנה + כללים + קבוצה + שכבות-על לא זמינות, המודול לא יכול לעבוד! + מודולים + יוצר + אודות + גרסה: %d + הפעלה מחדש + KernelSU תומך רק בליבת GKI כעת + סטטוס SELinux + הסתר אפליקציות מערכת + גרסה + אינו נתמך + תחום + בית + מותאם אישית + תבנית + רענון + מוריד מודל: %s + עדכון + למד אודות KernelSU + האם אתה בטוח שברצונך להסיר את התקנת המודל %s\? + הסרת התקנת %s נכשלה: + משתמש על + הגדרות + עובד + השבתת מודל %s נכשלה: + אין מודלים מותקנים + להתקין + Kernel + לא מותקן + נכשל עדכון פרופיל האפליקציה עבור %s + https://kernelsu.org/guide/what-is-kernelsu.html + נכשל עדכון כללי SELinux עבור: %s + הפעלה מחדש לבוטלאודר + ראה את קוד המקור ב%1$s
הצטרף אלינו %2$s בערוץ
+ גרסת מנהל + גרסה חדשה עבור: %s זמינה, לחץ כדי לשדרג + שמור יומנים +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-ja/strings.xml b/KernelSU/manager/app/src/main/res/values-ja/strings.xml new file mode 100644 index 000000000000..4ecf7420447a --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,135 @@ + + + ホーム + 未インストール + タップでインストール + 動作中 + バージョン: %d + スーパーユーザー: %d + モジュール: %d + 非対応 + 現在、 KernelSU は GKI カーネルにのみ対応しています + カーネル + アプリのバージョン + Fingerprint + SELinux の状態 + Disabled + Enforcing + Permissive + 不明 + スーパーユーザー + モジュールの有効化に失敗: %s + モジュールの無効化に失敗: %s + モジュールをインストールしていません + モジュール + アンインストール + インストール + インストール + 再起動 + 設定 + 通常の再起動 + リカバリーへ再起動 + ブートローダー へ再起動 + ダウンロードモードへ再起動 + EDL へ再起動 + アプリについて + モジュール %s をアンインストールしますか? + %s はアンインストールされました + アンインストールに失敗: %s + バージョン + 制作者 + OverlayFS が有効でないためモジュールは動作しません! + 更新 + システムアプリを表示 + システムアプリを非表示 + ログを送信 + セーフモード + 再起動すると有効化されます + Magisk と競合しているためモジュールは無効になっています! + KernelSU について + https://kernelsu.org/ja_JP/guide/what-is-kernelsu.html + KernelSU のインストール方法やモジュールの使い方はこちら + 支援する + KernelSU はこれからもずっとフリーでオープンソースです。寄付をすることで私たちを気にかけていることを示せます。 + %2$s チャンネルに参加]]> + アプリのプロファイル + 既定 + テンプレート + カスタム + プロファイル名 + 名前空間のマウント + 継承 + 共通 + 分離 + モジュールのアンマウント + グループ + SELinux コンテキスト + %s のアプリのプロファイルの更新をできませでした + ドメイン + ルール + 新しいバージョン: %s が利用可能です。タップしてダウンロード + アップデート + ダウンロードを開始: %s + 起動 + 強制停止 + 再起動 + SELinux ルールの更新に失敗しました: %s + ケーパビリティ + モジュールをダウンロード中: %s + このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。 + 既定でモジュールのマウントを解除 + アプリプロファイルの「モジュールのアンマウント」の共通のデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。 + 現在の KernelSU バージョン %d はマネージャーが適切に機能するには低すぎます。 バージョン %d 以降にアップグレードしてください! + 変更履歴 + インポート成功 + クリップボードからエクスポート + エクスポートするローカル テンプレートが見つかりません! + テンプレート id はすでに存在します! + クリップボードからインポート + 変更ログの取得に失敗しました: %s + 名前 + 無効なテンプレート id + オンラインテンプレートの同期 + テンプレートの作成 + 読み取り専用 + インポート/エクスポート + テンプレートの保存に失敗しました + テンプレートの編集 + id + アプリプロファイルのテンプレート + 説明 + 保存 + アプリプロファイルのローカルおよびオンラインテンプレートを管理する + 消去 + クリップボードが空です! + テンプレートを表示 + アップデートを確認 + アプリを開いたときにアップデートを自動的に確認する + root の付与に失敗しました! + 開く + WebView デバッグを有効にする + WebUI のデバッグに使用できます。必要な場合にのみ有効にしてください。 + %1$s パーティション イメージが推奨されます + KMI を選択してください + 次に + 非アクティブなスロットにインストール (OTA 後) + 再起動後、デバイスは**強制的に**、現在非アクティブなスロットから起動します。 +\nこのオプションは、OTA が完了した後にのみ使用してください。 +\n続く? + 直接インストール (推奨) + ファイルを選択してください + スパースイメージを最小化 + モジュールが配置されているスパースイメージのサイズを実際のサイズに変更します。 モジュールが正常に動作しなくなる可能性がありますので、必要な場合にのみご使用ください + 完全にアンインストールする + ストックイメージを復元 + 一時的にアンインストールする + アンインストール + KernelSU を一時的にアンインストールし、次回の再起動後に元の状態に戻します。 + KernelSU (ルートおよびすべてのモジュール) を完全かつ永久にアンインストールします。 + バックアップが存在する場合、工場出荷時のイメージを復元できます (OTA の前に使用してください)。KernelSU をアンインストールする必要がある場合は、「完全にアンインストールする」を使用してください。 + フラッシュ + フラッシュ成功 + フラッシュ失敗 + 選択された lkm: %s + ログを保存 + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-kn/strings.xml b/KernelSU/manager/app/src/main/res/values-kn/strings.xml new file mode 100644 index 000000000000..3c4e79fe639f --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-kn/strings.xml @@ -0,0 +1,70 @@ + + + ಪರಿಣಾಮ ಬೀರಲು ರೀಬೂಟ್ ಮಾಡಿ + KernelSU ಅನ್ನು ಹೇಗೆ ಸ್ಥಾಪಿಸಬೇಕು ಮತ್ತು ಮಾಡ್ಯೂಲ್‌ಗಳನ್ನು ಬಳಸುವುದು ಹೇಗೆ ಎಂದು ತಿಳಿಯಿರಿ + ತಿಳಿಯದ + ಸಿಸ್ಟಮ್ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ತೋರಿಸಿ + %s ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಲಾಗಿದೆ + Umount ಮಾಡ್ಯೂಲ್‌ಗಳು + ಲಾಗ್ ಕಳುಹಿಸಿ + ನಮ್ಮನ್ನು ಬೆಂಬಲಿಸಿ + ಪಿತ್ರಾರ್ಜಿತ + ಮಾಡ್ಯೂಲ್‌ಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ ಏಕೆಂದರೆ ಇದು ಮ್ಯಾಜಿಸ್ಕ್‌ನೊಂದಿಗೆ ಸಂಘರ್ಷವಾಗಿದೆ! + ಚೇಂಜ್ಲಾಗ್ + Permissive + ಡೀಫಾಲ್ಟ್ ಆಗಿ Umount ಮಾಡ್ಯೂಲ್ + ಈ ಆಯ್ಕೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುವುದರಿಂದ ಈ ಅಪ್ಲಿಕೇಶನ್‌ಗಾಗಿ ಮಾಡ್ಯೂಲ್‌ಗಳ ಮೂಲಕ ಯಾವುದೇ ಮಾರ್ಪಡಿಸಿದ ಫೈಲ್‌ಗಳನ್ನು ಮರುಸ್ಥಾಪಿಸಲು KernelSU ಗೆ ಅನುಮತಿಸುತ್ತದೆ. + ವೈಯಕ್ತಿಕ + ಮಾಡ್ಯೂಲ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ವಿಫಲವಾಗಿದೆ: %s + ಫೋರ್ಸ್ ಸ್ಟಾಪ್ + EDL ಗೆ ರೀಬೂಟ್ + ಸಾಮರ್ಥ್ಯಗಳು + ಸೂಪರ್‌ಯೂಸರ್‌ಗಳು: %d + ಡೌನ್‌ಲೋಡ್ ಮಾಡುವುದನ್ನು ಪ್ರಾರಂಭಿಸಿ: %s + ಜಾಗತಿಕ + ಅಪ್ಲಿಕೇಶನ್ ಪ್ರೊಫೈಲ್‌ಗಳಲ್ಲಿ \"Umount ಮಾಡ್ಯೂಲ್\" ಗಾಗಿ ಜಾಗತಿಕ ಡೀಫಾಲ್ಟ್ ಮೌಲ್ಯ. ಸಕ್ರಿಯಗೊಳಿಸಿದರೆ, ಪ್ರೊಫೈಲ್ ಸೆಟ್ ಅನ್ನು ಹೊಂದಿರದ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗಾಗಿ ಸಿಸ್ಟಮ್‌ಗೆ ಎಲ್ಲಾ ಮಾಡ್ಯೂಲ್ ಮಾರ್ಪಾಡುಗಳನ್ನು ಇದು ತೆಗೆದುಹಾಕುತ್ತದೆ. + ಮಾಡ್ಯೂಲ್‌ಗಳು: %d + SELinux ಸಂದರ್ಭ + ಡೀಫಾಲ್ಟ್ + ಲಾಂಚ್ + ಸುರಕ್ಷಿತ ಮೋಡ್ + ಪ್ರಸ್ತುತ KernelSU ಆವೃತ್ತಿ %d ಮ್ಯಾನೇಜರ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸಲು ತುಂಬಾ ಕಡಿಮೆಯಾಗಿದೆ. ದಯವಿಟ್ಟು ಆವೃತ್ತಿ %d ಅಥವಾ ಹೆಚ್ಚಿನದಕ್ಕೆ ಅಪ್‌ಗ್ರೇಡ್ ಮಾಡಿ! + ಸಾಫ್ಟ್ ರೀಬೂಟ್ + ಪ್ರೊಫೈಲ್ ಹೆಸರು + KernelSU ಉಚಿತ ಮತ್ತು ಮುಕ್ತ ಮೂಲವಾಗಿದೆ ಮತ್ತು ಯಾವಾಗಲೂ ಇರುತ್ತದೆ. ಆದಾಗ್ಯೂ ನೀವು ದೇಣಿಗೆ ನೀಡುವ ಮೂಲಕ ನೀವು ಕಾಳಜಿ ವಹಿಸುತ್ತೀರಿ ಎಂದು ನಮಗೆ ತೋರಿಸಬಹುದು. + ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ + ಮೌಂಟ್ ನೇಮ್‌ಸ್ಪೇಸ್ + ನಿಯಮಗಳು + ಗುಂಪುಗಳು + ಓವರ್‌ಲೇಫ್‌ಗಳು ಲಭ್ಯವಿಲ್ಲ, ಮಾಡ್ಯೂಲ್ ಕಾರ್ಯನಿರ್ವಹಿಸುವುದಿಲ್ಲ! + ಮಾಡ್ಯೂಲ್ + ಲೇಖಕ + ಬಗ್ಗೆ + ವರ್ಷನ್: %d + ರೀಬೂಟ್ + KernelSU ಈಗ GKI ಕರ್ನಲ್‌ಗಳನ್ನು ಮಾತ್ರ ಬೆಂಬಲಿಸುತ್ತದೆ + SELinux ಸ್ಥಿತಿ + ಸಿಸ್ಟಮ್ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಮರೆಮಾಡಿ + ವರ್ಷನ್ + ಬೆಂಬಲಿತವಾಗಿಲ್ಲ + ಡೊಮೇನ್ + ಮನೆ + ಕಸ್ಟಮ್ + ಟೆಂಪ್ಲೇಟ್ + ರಿಫ್ರೆಶ್ + ಮಾಡ್ಯೂಲ್ ಅನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ: %s + KernelSU ಕಲಿಯಿರಿ + %s ಮಾಡ್ಯೂಲ್ ಅನ್ನು ಅಸ್ಥಾಪಿಸಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ\? + ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಲು ವಿಫಲವಾಗಿದೆ: %s + ಸೂಪರ್ಯೂಸರ್ + ಕೆಲಸ ಮಾಡುತ್ತಿದೆ + ಮಾಡ್ಯೂಲ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ವಿಫಲವಾಗಿದೆ: %s + ಮಾಡ್ಯೂಲ್ ಅನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿಲ್ಲ + ಕರ್ನಲ್ + %s ಗಾಗಿ ಅಪ್ಲಿಕೇಶನ್ ಪ್ರೊಫೈಲ್ ಅನ್ನು ನವೀಕರಿಸಲು ವಿಫಲವಾಗಿದೆ + https://kernelsu.org/guide/what-is-kernelsu.html + %1$s ನಲ್ಲಿ ಮೂಲ ಕೋಡ್ ಅನ್ನು ವೀಕ್ಷಿಸಿ
ನಮ್ಮ %2$s ಚಾನಲ್‌ಗೆ ಸೇರಿ
+ ಮ್ಯಾನೇಜರ್ ವರ್ಷನ್ + ಹೊಸ ಆವೃತ್ತಿ: %s ಲಭ್ಯವಿದೆ, ಅಪ್‌ಗ್ರೇಡ್ ಮಾಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ + ಲಾಗ್ಗಳನ್ನು ಉಳಿಸಿ +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-ko/strings.xml b/KernelSU/manager/app/src/main/res/values-ko/strings.xml new file mode 100644 index 000000000000..7c29cc02f324 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-ko/strings.xml @@ -0,0 +1,82 @@ + + + 홈 + 설치되지 않음 + 이 곳을 눌러 설치하기 + 정상 작동 중 + 버전: %d + 루트 권한: %d개 + 설치된 모듈: %d개 + 지원되지 않음 + KernelSU는 현재 GKI 커널만 지원합니다 + 커널 + 매니저 버전 + 빌드 정보 + SELinux 상태 + 비활성화됨 + 적용 + 허용 + 알 수 없음 + 슈퍼유저 + 모듈 활성화 실패: %s + 모듈 비활성화 실패: %s + 설치된 모듈 없음 + 모듈 + 삭제 + 설치 + 설치 + 다시 시작 + 설정 + 빠른 다시 시작 + 복구 모드로 다시 시작 + 부트로더로 다시 시작 + 다운로드 모드로 다시 시작 + EDL 모드로 다시 시작 + 정보 + %s 모듈을 삭제할까요? + %s 모듈 삭제됨 + 모듈 삭제 실패: %s + 버전 + 제작자 + overlayfs 사용 불가, 모듈을 사용할 수 없습니다! + 새로고침 + 시스템 앱 보이기 + 시스템 앱 숨기기 + 로그 보내기 + 안전 모드 + 다시 시작하여 변경 사항 적용 + Magisk와의 충돌로 인해 모듈을 사용할 수 없습니다! + KernelSU 알아보기 + KernelSU 설치 방법과 모듈 사용 방법을 확인합니다 + 지원이 필요합니다 + KernelSU는 지금도, 앞으로도 항상 무료이며 오픈 소스로 유지됩니다. 기부를 통해 여러분의 관심을 보여주세요. + %2$s 채널 참가하기]]> + https://kernelsu.org/guide/what-is-kernelsu.html + 앱 프로필 메뉴의 \"모듈 사용 해제\" 설정에 대한 전역 기본값을 설정합니다. 활성화 시, 개별 프로필이 설정되지 않은 앱은 시스템에 대한 모듈의 모든 수정사항이 적용되지 않습니다. + 다시 시작 + 규칙 + 새 버전: %s 사용 가능, 여기를 눌러서 받기 + 다운로드 시작: %s + 강제 중지 + 기본값 + 사용자 지정 + 템플릿 + 프로필 이름 + 이름 공간 마운트 + 상속 + 전역 + 개별 + 사용자 그룹 + 모듈 사용 해제 + SELinux 컨텍스트 + 권한 + %s에 대한 앱 프로필 업데이트 실패 + 기본값으로 모듈 사용 해제 + 이 옵션이 활성화되면, KernelSU는 이 애플리케이션에 대한 모듈의 모든 수정사항을 복구합니다. + 업데이트 + 모듈 받는 중: %s + 도메인 + 실행 + 다음 앱에 대한 SELinux 규칙 업데이트 실패: %s + 로그 저장 + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-lt/strings.xml b/KernelSU/manager/app/src/main/res/values-lt/strings.xml new file mode 100644 index 000000000000..cba5f5b18c25 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-lt/strings.xml @@ -0,0 +1,84 @@ + + + Pirštų atspaudas + Išjungta + Priverstinas + Nežinomas + Supernaudotojai + Nepavyko įjungti modulio: %s + Nepavyko išjungti modulio: %s + Leistinas + Nėra įdiegtų modulių + Moduliai + Perkrovimas neišjungus + Perkrauti į atkūrimo rėžimą + Perkrauti į įkrovos tvarkyklę + Perkrauti į atsisiuntimo rėžimą + Apie + Nepavyko išdiegti: %s + %s išdiegtas + Versija + Autorius + overlayfs nepasiekiamas, modulis negali veikti! + Rodyti sistemos programas + Slėpti sistemos programas + Siųsti žurnalą + Paleisti iš naujo + Atšviežinti + Saugus rėžimas + Paleiskite iš naujo, kad įsigaliotų + Moduliai yra išjungti, nes jie konfliktuoja su Magisk\'s! + https://kernelsu.org/guide/what-is-kernelsu.html + Sužinokite apie KernelSU + Sužinokite, kaip įdiegti KernelSU ir naudoti modulius + Peržiūrėkite šaltinio kodą %1$s
Prisijunkite prie mūsų %2$s kanalo
+ Numatytas + Šablonas + Pasirinktinis + Profilio pavadinimas + Prijungti vardų erdvę + Paveldėtas + Globalus + Individualus + Grupės + Galimybės + SELinux kontekstas + Atjungti modulius + Atjungti modulius pagal numatytuosius parametrus + Įjungus šią parinktį, KernelSU galės atkurti visus modulių modifikuotus failus šiai programai. + Domenas + Taisyklės + Atnaujinti + Atsisiunčiamas modulis: %s + Pradedamas atsisiuntimas: %s + Nauja versija: %s pasiekiama, spustelėkite norėdami atsinaujinti + Paleisti + Priversti sustoti + Perkrauti + Nepavyko atnaujinti SELinux taisyklių: %s + Namai + Neįdiegta + KernelSU dabar palaiko tik GKI branduolius + Spustelėkite norėdami įdiegti + Veikia + Supernaudotojai: %d + Versija: %d + Nepalaikoma + Moduliai: %d + Tvarkyklės versija + Branduolys + SELinux statusas + Išdiegti + Įdiegti + Įdiegti + Parametrai + Perkrauti į EDL + Ar tikrai norite išdiegti modulį %s\? + Paremkite mus + KernelSU yra ir visada bus nemokamas ir atvirojo kodo. Tačiau galite parodyti, kad jums rūpi, paaukodami mums. + Nepavyko atnaujinti programos profilio %s + Visuotinė numatytoji „Modulių atjungimo“ reikšmė programų profiliuose. Jei įjungta, ji pašalins visus sistemos modulio pakeitimus programoms, kurios neturi profilio. + Keitimų žurnalas + Ši KernelSU versija %d yra per žema, kad šis vadybininkas galėtų tinkamai funkcionuoti. Prašome atsinaujinti į versiją %d ar aukščiau! + Saglabāt Žurnālus +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-lv/strings.xml b/KernelSU/manager/app/src/main/res/values-lv/strings.xml new file mode 100644 index 000000000000..c264b76a650d --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-lv/strings.xml @@ -0,0 +1,134 @@ + + + Iespējojot šo opciju, KernelSU varēs atjaunot visus moduļos šīs lietojumprogrammas modificētos failus. + Neizdevās atjaunināt SELinux noteikumus: %s + Pārvaldiet vietējo un tiešsaistes lietotņu profila veidni + Nederīgs veidnes id + veidnes id jau pastāv! + Eksportēt starpliktuvē + Importēt no starpliktuves + Importēts veiksmīgi + Sinhronizēt tiešsaistes veidnes + Sākums + Nav ieinstalēts + Noklikšķiniet, lai instalētu + Darbojas + Versija: %d + Superlietotāji: %d + Moduļi: %d + Neatbalstīts + KernelSU atbalsta tikai GKI kodolus + Kodols + Pārvaldnieka versija + Pirkstu nospiedums + SELinux statuss + Izpildīšana + Atspējots + Nezināms + SuperLietotājs + Neizdevās atspējot moduli: %s + Nav instalētu moduļu + Moduļi + Atinstalēt + Instalēt + Restartēt + Iestatījumi + Ātri restartēt + Restartēt uz Bootloaderu + Restartēt uz Recovery + Restartēt uz Download + Restartēt uz EDL + Par + %s ir atinstalēts + Neizdevās atinstalēt: %s + Autors + Atjaunot + Rādīt sistēmas lietotnes + Slēpt sistēmas lietotnes + Ziņot žurnālu + Restartējiet, lai stātos spēkā + Uzzināt par KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + Uzzināt, kā instalēt KernelSU un izmantot moduļus + Atbalsti mūs + Skatiet avota kodu vietnē %1$s
Pievienojies mūsu %2$s kanālam
+ Noklusējums + Veidne + Pielāgots + Profila vārds + Mount nosaukumvieta + Individuāls + Iespējas + SELinux konteksts + Atvienot moduļus + Neizdevās atjaunināt lietotnes profilu %s + Pēc noklusējuma atvienot moduļus + Globālā noklusējuma vērtība vienumam “Atvienot moduļus” lietotņu profilos. Ja tas ir iespējots, lietojumprogrammām, kurām nav iestatīts profils, tiks noņemtas visas sistēmas moduļu modifikācijas. + Domēns + Noteikumi + Atjaunināt + Lejupielādē moduli: %s + Sākt lejupielādi: %s + Jaunā versija: %s ir pieejama, noklikšķiniet, lai atjauninātu + Palaist + Piespiedu apstāšana + Restartēt aplikāciju + Izmaiņu žurnāls + Lietotnes profila veidne + Izveidot veidni + Rediģēt veidni + id + Vārds + Apraksts + Saglabāt + Dzēst + Skatīt veidni + tikai lasīt + Importēt/Eksportēt + Nevar atrast vietējo eksportējamo veidni! + Neizdevās saglabāt veidni + Starpliktuve ir tukša! + Izmaiņu žurnāla iegūšana neizdevās: %s + Visatļautība + Neizdevās iespējot moduli: %s + Instalēt + Vai tiešām vēlaties atinstalēt moduli %s? + Versija + overlayfs nav pieejams, modulis nevar darboties! + Drošais režīms + Moduļi ir atspējoti, jo tie konfliktē ar Magisk! + KernelSU ir un vienmēr būs bezmaksas un atvērtā koda. Tomēr jūs varat parādīt mums, ka jums rūp, veicot ziedojumu. + Grupas + Globāli + Pašreizējā KernelSU versija %d ir pārāk zema, lai pārvaldnieks darbotos pareizi. Lūdzu, atjauniniet uz versiju %d vai jaunāku! + Iespējot WebView atkļūdošanu + Ieteicams %1$s nodalījuma attēls + Nākamais + Mantots + Izvēlieties failu + Instalēt neaktīvajā slotā (pēc OTA) + Pēc restartēšanas jūsu ierīce tiks **PIESPIESTI** palaista pašreizējā neaktīvajā slotā! +\nIzmantojiet šo opciju tikai pēc OTA pabeigšanas +\nTurpināt? + Tiešā instalēšana (Ieteicams) + Atinstalēt + Pagaidu atinstalēšana + Atjaunot oriģinālo attēlu + Īslaicīgi atinstalēt KernelSU, pēc nākamās restartēšanas atjaunot sākotnējo stāvokli. + KernelSU (saknes un visu moduļu) pilnīga atinstalēšana. + Atjaunojot rūpnīcas attēlu (ja ir dublējums), ko parasti izmanto pirms OTA; ja nepieciešams atinstalēt KernelSU, lūdzu, izmantojiet \"Neatgriezeniski atinstalēt\". + Izvēlētais lkm: %s + Neizdevās piešķirt sakni! + Atvērt + Pārbaudīt atjauninājumus + Automātiski pārbaudīt atjauninājumus atverot aplikāciju + Var izmantot WebUI atkļūdošanai, lūdzu, izmantot tikai tad, kad tas ir nepieciešams. + Izvēlieties KMI + Neatgriezeniski atinstalēt + Instalē + Instalēts veiksmīgi + Instalēšana neizdevās + Samazināt reto attēlu + Mainīt retā attēla izmēru, kurā atrodas modulis, līdz tā faktiskajam izmēram. Ņemiet vērā, ka tas var izraisīt moduļa neparastu darbību, tāpēc, lūdzu, izmantojiet tikai nepieciešamības gadījumā (piemēram, dublēšanai) + Išsaugoti Žurnalus +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-mr/strings.xml b/KernelSU/manager/app/src/main/res/values-mr/strings.xml new file mode 100644 index 000000000000..eaaf2a255b44 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-mr/strings.xml @@ -0,0 +1,82 @@ + + + इंस्टॉल केले नाही + होम + इंस्टॉल साठी क्लिक करा + कार्यरत + आवृत्ती: %d + मॉड्यूल्स: %d + सुपरयूझर: %d + असमर्थित + KernelSU आता फक्त GKI कर्नलचे समर्थन करते + कर्नल + फिंगरप्रिंट + व्यवस्थापक आवृत्ती + SELinux स्थिती + अक्षम + एनफोर्सिंग + परमिसिव + अज्ञात + स्थापित करा + कोणतेही मॉड्यूल स्थापित केलेले नाही + रीबूट करा + सुपरयुझर + मॉड्यूल सक्षम करण्यात अयशस्वी: %s + विस्थापित करा + मॉड्यूल अक्षम करण्यात अयशस्वी: %s + मॉड्यूल + स्थापित करा + सेटिंग्ज + सॉफ्ट रीबूट + बद्दल + EDL वर रीबूट करा + तुमची खात्री आहे की तुम्ही मॉड्यूल %s विस्थापित करू इच्छिता\? + विस्थापित करण्यात अयशस्वी: %s + overlayfs उपलब्ध नाही, मॉड्यूल काम करू शकत नाही! + सिस्टम अॅप्स दाखवा + बूटलोडरवर रीबूट करा + %s विस्थापित + आवृत्ती + लेखक + रिफ्रेश करा + रिकवरी मध्ये रिबुट करा + डाउनलोड करण्यासाठी रीबूट करा + लॉग पाठवा + सुरक्षित मोड + सिस्टम अॅप्स लपवा + प्रभावी होण्यासाठी रीबूट करा + KernelSU शिका + https://kernelsu.org/guide/what-is-kernelsu.html + मॉड्यूल अक्षम केले आहेत कारण ते Magisk च्या विरोधाभास आहे! + KernelSU कसे स्थापित करायचे आणि मॉड्यूल कसे वापरायचे ते शिका + KernelSU विनामूल्य आणि मुक्त स्रोत आहे, आणि नेहमीच असेल. तथापि, देणगी देऊन तुम्ही आम्हाला दाखवू शकता की तुमची काळजी आहे. + आम्हाला पाठिंबा द्या + कस्टम + माउंट नेमस्पेस + डीफॉल्ट + साचा + वैयक्तिक + क्षमता + %1$s वर स्रोत कोड पहा
आमच्या %2$s चॅनेलमध्ये सामील व्हा
+ प्रोफाइल नाव + इनहेरीटेड + जागतिक + गट + SELinux संदर्भ + उमाउंट मॉड्यूल्स + %s साठी अॅप प्रोफाइल अपडेट करण्यात अयशस्वी + डीफॉल्टनुसार मॉड्यूल्स उमाउंट करा + अॅप प्रोफाइलमधील \"उमाउंट मॉड्यूल्स\" साठी जागतिक डीफॉल्ट मूल्य. सक्षम असल्यास, ते प्रोफाइल सेट नसलेल्या ॲप्लिकेशनचे सिस्टममधील सर्व मॉड्यूल बदल काढून टाकेल. + हा पर्याय सक्षम केल्याने KernelSU ला या ऍप्लिकेशनसाठी मॉड्यूल्सद्वारे कोणत्याही सुधारित फाइल्स पुनर्संचयित करण्यास अनुमती मिळेल. + यासाठी SELinux नियम अपडेट करण्यात अयशस्वी: %s + नियम + अपडेट करा + डोमेन + मॉड्यूल डाउनलोड करत आहे: %s + डाउनलोड करणे सुरू करा: %s + नवीन आवृत्ती: %s उपलब्ध आहे, डाउनलोड करण्यासाठी क्लिक करा + सक्तीने थांबा + लाँच करा + पुन्हा सुरू करा + लॉग जतन करा +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-ms/strings.xml b/KernelSU/manager/app/src/main/res/values-ms/strings.xml new file mode 100644 index 000000000000..46c1539c7ed0 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-ms/strings.xml @@ -0,0 +1,38 @@ + + + Tidak Diketahui + Lumpuhkan + Permisif + Mulakan semula ke Download + Modul tidak berjaya Diaktifkan:%s + Mulakan semula ke EDL + Superusers%d + Modul%d + Enforcing + Cap Jari + Mulakan semula ke Recovery + Soft reboot + Padam + Pasang + Tekan untuk memasang + Modul + Tentang + Versi%d + Mulakan semula + KernelSU ketika ini hanya menyokong kernel GKI + Status SELinux + Tidak Disokong + Layar Utama + Apakah anda pasti ingin membuang modul %s\? + SuperUser + Tetapan + Berjalan + Gagal mematikan modul:%s + Tiada modul dipasang + Pasang + Kernel + Tidak terpasang + Mulakan semula ke bootloader + Versi Manager + Simpan Log + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-night-v27/themes.xml b/KernelSU/manager/app/src/main/res/values-night-v27/themes.xml new file mode 100644 index 000000000000..10a773b16f28 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-night-v27/themes.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-night/themes.xml b/KernelSU/manager/app/src/main/res/values-night/themes.xml new file mode 100644 index 000000000000..91abf657bfdc --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-night/themes.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-nl/strings.xml b/KernelSU/manager/app/src/main/res/values-nl/strings.xml new file mode 100644 index 000000000000..bebf37890eb2 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-nl/strings.xml @@ -0,0 +1,129 @@ + + + Home + Niet geïnstalleerd + Klik om te installeren + Werkend + Versie: %d + Supergebruikers: %d + Modules: %d + Niet ondersteund + KernelSU ondersteunt alleen GKI kernels + Kernel + Manager Versie + Fingerprint + SELinux status + Uitgeschakeld + Afgedwongen + Permissief + Niet gekend + SupergeBruiker + Mislukt om module in te schakelen: %s + Mislukt om module uit te schakelen: %s + Geen geïnstalleerde modules + Module + Verwijderen + Installeren + Installeren + Herstart + Instellingen + Soft herstart + Herstart naar Recovery + Herstart naar Bootloader + Herstart om te downloaden + Herstart naar EDL + Over + Zeker van het verwijderen van module %s? + %s is verwijderd + Mislukt om te verwijderen: %s + Versie + Auteur + overlayfs is niet beschikbaar, module kan niet werken! + Vernieuwen + Toon systeem apps + Verberg systeem apps + Rapport Log + Safe mode + Herstart om effect te hebben + Modules zijn uitgeschakeld omdat ze in conflict zijn met magisk! + Leer KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + Leer hoe KernelSU te installeren en modules te gebruiken + Ondersteun ons + KernelSU is, en zal altijd, vrij en open source zijn. Je kan altijd je appreciatie tonen met een donatie. + Vervoeg ons %2$s kanaal]]> + App profiel + Standaard + Sjabloon + Aangepast + Profiel naam + Koppel naamruimte + Overgenomen + Globaal + Individuëel + Groepen + Mogelijkheden + SELinux context + Ontkoppel modules + Mislukt om App Profiel te updaten voor %s + Ontkoppel standaard de modules + De globale standaard waarde voor \"Ontkoppel modules\" in App Profielen. Indien geactiveerd, zal het alle module wijzigingen tot het systeem verwijderen voor applicaties die geen Profiel ingesteld hebben. + Met deze optie ingeschakeld zal KernelSU toelaten om alle gewijzigde bestanden door de modules voor deze applicatie te herstellen. + Domein + Regels + Update + Downloaden van module: %s + Nieuwe versie: %s is beschikbaar,klik om te upgraden + Start + Forceer Stop + Herstart + Begin met downloaden: %s + Kan SELinux-regels niet bijwerken voor: %s + De huidige KernelSU-versie %d is te laag om de manager correct te laten functioneren. Upgrade naar versie %d of hoger! + wijzigings logboek + App-profiel Sjabloon + Maken Sjabloon + Bewerkin Sjabloon + id + Ongeldige sjabloon id + Naam + Redde + Bekijken Sjabloon + Beschrijving + Beheer lokale en online sjabloon van app-profiel + Verwijderen + alleen lezen + sjabloon id bestaat al! + Synchroniseer online-sjablonen + Mislukt naar opslaan sjabloon + Klembord is leeg! + Importeren/Exporteren + Importeren vanaf klembord + Ophalen van wijzigingslogboek mislukt: %s + Exporteren naar klembord + Controleer update + Schakel WebView-foutopsporing + Kan worden gebruikt om WebUI te debuggen. Schakel dit alleen in als dat nodig is. + Kan geen lokale sjabloon vinden om te exporteren! + Succesvol geïmporteerd + Open + Controleer automatisch op updates bij het openen van de app + Directe installatie (aanbevolen) + Selecteer een bestand + Kan geen root verlenen! + Minimaliseer schaarse afbeeldingen + Wijzig het formaat van de beperkte afbeelding waar de module zich bevindt naar de werkelijke grootte. Houd er rekening mee dat dit ervoor kan zorgen dat de module abnormaal werkt, dus gebruik deze alleen wanneer dit nodig is (zoals voor back-up) + %1$s partitie-image wordt aanbevolen + Naast + Uw apparaat wordt **GEFORCEERD** om na een herstart op te starten naar het huidige inactieve slot! +\nGebruik deze optie alleen nadat OTA is voltooid. +\nDoorgaan? + Installeren in inactief slot (na OTA) + KMI selecteren + Desinstalleren + Tijdelijk verwijderen + Permanent verwijderen + Herstel stockafbeelding + Verwijder KernelSU tijdelijk en herstel het naar de oorspronkelijke staat na de volgende herstart. + Logboeken Opslaan + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-pl/strings.xml b/KernelSU/manager/app/src/main/res/values-pl/strings.xml new file mode 100644 index 000000000000..b4fe83f3dc8f --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-pl/strings.xml @@ -0,0 +1,136 @@ + + + KernelSU + Strona główna + Nie zainstalowano + Kliknij, aby zainstalować + Działa + Wersja: %d + Superużytkownicy: %d + Moduły: %d + Nieobsługiwany + KernelSU obsługuje obecnie tylko jądra GKI + Jądro + Wersja menedżera + Odcisk + Status SELinux + Wyłączony + Enforcing + Dozwolony + Nieznany + Superużytkownik + Nie udało się włączyć modułu: %s + Nie udało się wyłączyć modułu: %s + Brak zainstalowanych modułów + Moduły + Odinstaluj + Instaluj + Instaluj + Uruchom ponownie + Ustawienia + Miękki restart + Restart do trybu Recovery + Restart do trybu Bootloader + Restart do trybu Download + Restart do trybu EDL + Informacje + Czy na pewno chcesz odinstalować moduł %s? + Odinstalowano %s + Nie udało się odinstalować:: %s + Wersja + Autor + overlayfs jest niedostępny, moduł nie zadziała! + Odśwież + Pokaż aplikacje systemowe + Ukryj aplikacje systemowe + Raportuj log + Tryb bezpieczny + Uruchom ponownie, aby zastosować zmiany + Moduły są wyłączone, ponieważ są w konflikcie z modułami Magiska! + Poznaj KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + Dowiedz się jak zainstalować KernelSU i jak korzystać z modułów + Wesprzyj nas + KernelSU jest i zawsze będzie darmowy oraz otwarty. Niemniej jednak możesz nam pokazać, że Ci zależy, wysyłając darowiznę. + Dołącz do kanału %2$s]]> + Profil aplikacji + Domyślny + Szablon + Własny + Nazwa profilu + Przestrzeń nazw montowania + Odziedziczona + Globalna + Indywidualna + Grupy + Uprawnienia + Kontekst SELinux + Odmontuj moduły + Nie udało się zaktualizować profilu aplikacji dla %s + Domyślnie odmontuj moduły + Globalna wartość domyślna opcji \"Odmontuj moduły\" w profilach aplikacji. Jeśli jest włączona, wycofuje wszystkie modyfikacje dokonane przez moduły dla aplikacji, które nie mają ustawionego profilu. + Włączenie tej opcji umożliwi KernelSU przywrócenie wszelkich zmodyfikowanych plików przez moduły dla tej aplikacji. + Domena + Reguły + Zaktualizuj + Pobieranie modułu: %s + Rozpocznij pobieranie: %s + Nowa wersja: %s jest dostępna. Kliknij, aby zaktualizować + Uruchom + Wymuś zatrzymanie + Restartuj + Nie udało się zaktualizować reguł SELinux dla: %s + Obecna wersja KernelSU %d jest zbyt stara, aby menedżer działał poprawnie. Prosimy o aktualizację do wersji %d lub nowszej! + Dziennik zmian + Włącz debugowanie WebView + Może być użyte do debugowania WebUI. Włącz tylko w razie potrzeby. + Obraz partycji %1$s jest zalecany + Wybierz KMI + Dalej + Instalacja bezpośrednia (zalecane) + Wybierz plik + Zainstaluj do nieaktywnego slotu (po aktualizcji OTA) + Po ponownym uruchomieniu Twoje urządzenie zostanie **ZMUSZONE** do uruchomia się z obecnie nieaktywnego slotu! +\nUżyj tej opcji dopiero po zakończeniu aktualizacji OTA. +\nCzy chcesz kontynuować? + Stwórz szablon + Edytuj szablon + Nazwa + Opis + Zapisz + Usuń + tylko do odczytu + Importuj/Eksportuj + Importuj ze schowka + Eksportuj do schowka + Nie można znaleźć lokalnego szablonu do eksportu! + Zaimportowano pomyślnie + Nie udało się zapisać szablonu + Schowek jest pusty! + Zarządzaj lokalnym i internetowym szablonem profilu aplikacji + Synchronizuj internetowe szablony + Zobacz szablon + Błędny identyfikator szablonu + Szablon profilu aplikacji + Identyfikator + Szablon o takim identyfikatorze już istnieje! + Nie udało się przyznać roota! + Otwórz + Pobranie dziennika zmian nie powiodło się: %s + Wyszukaj aktualizacje + Wyszukuj aktualizacje automatycznie przy otwieraniu aplikacji + Minimalizuj rozrzedzony (sparse) obraz + Zmienia rozmiar obrazu rozrzedzonego(sparse), w którym znajduje się moduł, do jego rzeczywistego rozmiaru. Należy pamiętać, że może to spowodować nieprawidłowe działanie modułu, więc należy go używać tylko wtedy, gdy jest to konieczne (np. do tworzenia kopii zapasowych) + Odinstaluj zupełnie + Przywróć obraz fabryczny + Odinstaluj tymczasowo + Odinstaluj + Tymczasowo odinstaluj KernelSU, przywróć do oryginalnego stanu po następnym ponownym uruchomieniu. + Całkowite i trwałe odinstalowanie KernelSU(Root i wszystkich modułów). + Przywróć obraz fabryczny (jeśli istnieje kopia zapasowa), zwykle używany przed OTA; jeśli chcesz odinstalować KernelSU, użyj opcji \"Odinstaluj zupełnie\". + Flashowanie + Flashowanie ukończone pomyślnie + Flashowanie nieudane + Wybrano lkm: %s + Zapisz Dzienniki + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-pt-rBR/strings.xml b/KernelSU/manager/app/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000000..f092a45128fd --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,135 @@ + + + Início + Não instalado + Clique para instalar + Em execução + Versão: %d + SuperUsuários: %d + Módulos: %d + Sem suporte + KernelSU suporta apenas kernels GKI agora + Kernel + Versão do gerenciador + Impressão digital + Status do SELinux + Desativado + Impondo + Permissivo + Desconhecido + SuperUsuário + Falha ao ativar o módulo %s + Falha ao desativar o módulo %s + Nenhum módulo instalado + Módulo + Desinstalar + Instalar + Instalar + Reiniciar + Configurações + Reinicialização suave + Reiniciar em modo Recovery + Reiniciar em modo Bootloader + Reiniciar em modo Download + Reiniciar em modo EDL + Sobre + Tem certeza que deseja desinstalar o módulo %s? + %s desinstalado + Falha ao desinstalar %s + Versão + Autor + Os módulos estão indisponíveis porque OverlayFS está desabilitado pelo kernel! + Atualizar + Mostrar apps do sistema + Ocultar apps do sistema + Reportar registro + Modo de segurança + Reinicie para entrar em vigor + Os módulos estão indisponíveis devido a um conflito com Magisk! + Saiba mais sobre o KernelSU + https://kernelsu.org/pt_BR/guide/what-is-kernelsu.html + Saiba como instalar o KernelSU e usar os módulos + Apoie-nos + KernelSU sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode nos ajudar enviando uma pequena doação. + Junte-se ao nosso canal do %2$s]]> + Perfil do Aplicativo + Padrão + Modelo + Personalizado + Nome do perfil + Montar namespace + Herdado + Global + Individual + Grupos + Capacidades + Contexto do SELinux + Desmontar módulos + Falha ao atualizar o Perfil do Aplicativo para %s + Desmontar módulos por padrão + O valor padrão global para \"Desmontar módulos\" em Perfil do Aplicativo. Se ativado, ele removerá todas as modificações do módulo no sistema para apps que não possuem um perfil definido. + Ativar esta opção permitirá que o KernelSU restaure quaisquer arquivos modificados pelos módulos para este app. + Domínio + Regras + Atualizar + Baixando módulo %s + Começando a baixar %s + Nova versão %s está disponível, clique para atualizar. + Iniciar + Forçar parada + Reiniciar + Falha ao atualizar as regras do SELinux para: %s + A versão atual do KernelSU %d é muito baixa para o gerenciador funcionar corretamente. Por favor, atualize para a versão %d ou superior! + Registro de alterações + Importado com sucesso + Exportar para a área de transferência + Não foi possível encontrar o modelo local para exportar! + O ID do modelo já existe! + Importar da área de transferência + Falha ao buscar o registro de alterações: %s + Nome + ID do modelo inválido + Sincronizar modelos online + Criar modelo + Somente leitura + Importar/Exportar + Falha ao salvar o modelo + Editar modelo + ID + Modelo do Perfil do Aplicativo + Descrição + Salvar + Gerencie o modelo local e online do Perfil do Aplicativo + Excluir + A área de transferência está vazia! + Ver modelo + Verificar por atualização + Verifique automaticamente se há atualizações ao abrir o app + Falha ao conceder acesso root! + Abrir + Ativar depuração do WebView + Pode ser usado para depurar o WebUI. Por favor, ative somente quando necessário. + Selecione um arquivo + Instalação direta (recomendada) + Instalar no slot inativo (após o OTA) + Seu dispositivo será **FORÇADO** a inicializar no slot inativo atual após uma reinicialização! +\nSó use esta opção após a conclusão do OTA. +\nDeseja continuar? + Próximo + A imagem da partição %1$s é recomendada + Selecionar KMI + Minimizar imagem esparsa + Redimensione a imagem esparsa onde o módulo está localizado para seu tamanho real. Observe que isso pode fazer com que o módulo funcione de forma anormal, portanto, use-o somente quando necessário (como um backup). + Desinstalar + Desinstalar temporariamente + Desinstalar permanentemente + Restaurar imagem de fábrica + Restaure a imagem de fábrica (se existir um backup), geralmente usada antes do OTA. Se você precisar desinstalar o KernelSU, use \"Desinstalar permanentemente\". + Desinstale temporariamente o KernelSU e restaure ao estado original após a próxima reinicialização + Desinstale o KernelSU (Root e todos os módulos) completamente e permanentemente + LKM selecionado: %s + Flash falhou + Flashando + Flash bem-sucedido + Salvar Logs + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-pt/strings.xml b/KernelSU/manager/app/src/main/res/values-pt/strings.xml new file mode 100644 index 000000000000..ea1547db49b6 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-pt/strings.xml @@ -0,0 +1,83 @@ + + + Não instalado + Início + Clique para instalar + Funcionando + Super Usuário: %d + Módulos: %d + Versão: %d + Kernel + Instalar + Sem suporte + KernelSU suporta apenas kernels GKI agora + Status do SELinux + Versão do aplicativo + Falha ao desativar o módulo: %s + Impressão digital + Desabilitado + Impondo + Permissivo + Desconhecido + Super Usuário + Falha ao ativar o módulo: %s + Nenhum módulo instalado + Desinstalar + Modulos + Reiniciar + Instalar + Reinicialização Suave + Configurações + Reinicializar modo Bootloader + Reiniciar modo recuperação + Falha ao desinstalar: %s + Versão + Autor + Atualizar + Esconder apps do sistema + Reiniciar para baixar + Reiniciar em EDL + Tem certeza de que deseja desinstalar o módulo %s\? + Sobre + overlayfs não está disponível, o módulo não pode funcionar! + Enviar log + %s desinstalado + Mostrar aplicativos do sistema + Aprenda a instalar o KernelSU e usar os módulos + O KernelSU é, e sempre será, gratuito e de código aberto. No entanto, você pode nos mostrar que se importa fazendo uma doação. + Veja o código-fonte em %1$s
Junte-se ao nosso canal %2$s
+ Individual + Global + Herdado + Padrão + Modelo + Personalizado + Nome do perfil + Montar namespace + Modo de segurança + Reinicie para entrar em vigor + Aprender KernelSU + Os módulos estão desativados porque estão em conflito com os do Magisk! + Apoie-nos + Grupos + Capacidades + contexto SELinux + Domínio + Atualização + Desativar modulos + Falha ao atualizar o perfil do aplicativo para %s + Módulos desativados por padrão + O valor padrão global para \"Módulos Umount\" em Perfis de Aplicativos. Se ativado, removerá todas as modificações de módulo do sistema para aplicativos que não possuem um Perfil definido. + Regras + Ativar esta opção permitirá que o KernelSU restaure quaisquer arquivos modificados pelos módulos para este aplicativo. + Iniciar o download: %s + Baixando módulo: %s + Falha ao atualizar as regras do SELinux para: %s + https://kernelsu.org/guide/what-is-kernelsu.html + Reiniciar + Lançar + Forçar parada + Nova versão: %s está disponível, clique para baixar + A versão atual do KernelSU %d é muito baixa para o gerenciador funcionar corretamente. Atualize para a versão %d ou superior! + Salvar Registros +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-ro/strings.xml b/KernelSU/manager/app/src/main/res/values-ro/strings.xml new file mode 100644 index 000000000000..fea3ffb6958d --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-ro/strings.xml @@ -0,0 +1,132 @@ + + + Acasă + Nu este instalat + Click pentru a instala + Funcționează + Versiune: %d + Super-utilizatori: %d + Module: %d + Necompatibil + KernelSU suportă doar nuclee GKI acum + Nucleu + Versiune Manager + Amprentă + Stare SELinux + Dezactivat + Obligatoriu + Permisiv + Necunoscut + Super-Utilizator + Activarea modulului %s a eșuat + Dezactivarea modulului %s a eșuat + Niciun modul instalat + Module + Dezinstalează + Instalează + Instalează + Repornește + Setări + Repornire rapidă + Repornire în Recuperare + Repornire în Bootloader + Repornire în Download + Repornire în EDL + Despre + Sigur dorești să dezinstalezi modulul %s? + %s dezinstalat + Dezinstalare eșuată: %s + Versiune + Autor + overlayfs nu este disponibil, modulul nu poate funcționa! + Reîmprospătează + Arată aplicațiile de sistem + Ascunde aplicațiile de sistem + Raportează jurnal + Mod sigur + Repornește pentru ca modificările să intre în vigoare + Modulele sunt dezactivate deoarece sunt în conflict cu cele ale Magisk-ului! + Află mai multe despre KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + Află cum să instalezi KernelSU și să utilizezi module + Suport + KernelSU este, și va fi întotdeauna, gratuit și cu codul sursă deschis. Cu toate acestea, ne poți arăta că îți pasă făcând o donație. + Alătură-te canalului nostru %2$s]]> + Implicit + Șablon + Personalizat + Nume profil + Montare spațiu de nume + Moștenit + Global + Individual + Grupuri + Capabilități + Context SELinux + Module u-montate + Nu s-a putut actualiza profilul aplicației pentru %s + U-montează modulele în mod implicit + Valoarea implicită globală pentru „Module u-montate” în Profilurile aplicațiilor. Dacă este activat, va elimina toate modificările modulelor aduse sistemului pentru aplicațiile care nu au un profil setat. + Activarea acestei opțiuni va permite KernelSU să restaureze orice fișiere modificate de către modulele pentru această aplicație. + Domeniu + Reguli + Actualizează + Se descarcă modulul: %s + Începe descărcarea: %s + Versiune nouă: %s este disponibilă, clic pentru a actualiza + Nu s-au reușit actualizările regulilor SELinux pentru: %s + Lansare + Oprire forțată + Repornește + Versiunea actuală a KernelSU %d este prea mică pentru ca managerul să funcționeze corect. Actualizează la versiunea %d sau o versiune superioară! + Jurnalul modificărilor + Importat cu succes + Export în clipboard + Nu există șabloane locale de exportat! + ID-ul șablonului există deja! + Import din clipboard + Preluarea jurnalului de modificări a eșuat: %s + Nume + ID șablon nevalid + Sincronizează șabloanele online + Creează un șablon + doar citire + Import/Export + Nu s-a salvat șablonul + Editează șablonul + ID + Șablon de profil al aplicației + Descriere + Salvează + Gestionează șablonul local și online al Profilului aplicației + Șterge + Clipboard-ul este gol! + Vizualizare șablon + Verifică actualizarea + Se verifică automat actualizările când deschizi aplicația + Activează depanarea WebView + Poate fi folosit pentru a depana WebUI, activează numai când este necesar. + Nu s-a acordat acces root! + Deschide + Se recomandă imaginea partiției %1$s + Înainte + Dispozitivul va fi **FORȚAT** să pornească în slot-ul inactiv curent după o repornire! +\nFolosește această opțiune numai după finisarea OTA. +\nContinui? + Selectează KMI + Instalare directă (recomandat) + Selectează un fișier + Instalează într-un slot inactiv (după OTA) + Dezinstalează + Restaurare imagine stoc + Dezinstalează temporar KernelSU, se revine la starea inițială după următoarea repornire. + Lkm selectat: %s + Dezinstalează temporar + Dezinstalează definitiv + Dezinstalare KernelSU (Root și toate modulele) complet și permanent. + Restaurează imaginea stoc din fabrică (dacă există o copie de rezervă), utilizată de obicei înainte de OTA; dacă trebuie să dezinstalezi KernelSU, utilizează „Dezinstalare permanentă”. + Instalare + Instalare reușită + Instalarea a eșuat + Salvează Jurnale + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-ru/strings.xml b/KernelSU/manager/app/src/main/res/values-ru/strings.xml new file mode 100644 index 000000000000..91dd276ff0a5 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,138 @@ + + + Главная + Не установлен + Нажмите, чтобы установить + Работает + Версия: %d + Суперпользователи: %d + + Модули: %d + Не поддерживается + KernelSU поддерживает только GKI ядра + Ядро + Версия менеджера + Подпись + Состояние SELinux + Выключен + Принудительный + Разрешающий + Неизвестно + SU пользователь + + Не удалось включить модуль: %s + Не удалось отключить модуль: %s + Нет установленных модулей + Модули + Удалить + Установить + Установка + Перезагрузить + Настройки + Мягкая перезагрузка + Перезагрузить в Recovery + Перезагрузить в Bootloader + Перезагрузить в Download + Перезагрузить в EDL + О приложении + Вы уверены, что хотите удалить модуль %s? + %s удален + Не удалось удалить: %s + Версия + Автор + overlayfs недоступен, модуль не может работать! + Обновить страницу + Показать системные приложения + Скрыть системные приложения + Отправить лог + Безопасный режим + Перезагрузите, чтобы изменения вступили в силу + Модули отключены, потому что они конфликтуют с Magisk! + Узнайте о KernelSU + https://kernelsu.org/ru_RU/guide/what-is-kernelsu.html + Узнайте, как установить KernelSU и использовать модули + Поддержите нас + KernelSU был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование. + Присоединяйтесь к нашему %2$s каналу]]> + App Profile + + По умолчанию + Шаблон + Пользовательский + Название профиля + Монтировать пространство имен + Унаследованный + Глобальный + Индивидуальный + Группы + Возможности + Контекст SELinux + Размонтировать модули + Не удалось обновить App Profile для %s + Размонтировать модули по умолчанию + Глобальное значение по умолчанию для \"Размонтировать модули\" в App Profile. При включении будут удалены все модификации модулей в системе для приложений, у которых не задан Profile. + Включение этой опции позволит KernelSU восстанавливать любые измененные модулями файлы для данного приложения. + Домен + Правила + Обновить + Скачивание модуля: %s + Начало скачивания: %s + Новая версия: %s доступна, нажмите, чтобы скачать + Принудительно остановить + Не удалось обновить правила SELinux для %s + Запустить + Перезапустить + Текущая версия KernelSU %d слишком низкая для правильной работы менеджера. Пожалуйста, обновите до версии %d или выше! + Список изменений + Успешный импорт + Экспортировать в буфер обмена + Нет локальных шаблонов для экспорта! + Шаблон с таким id уже существует! + Импортировать из буфера обмена + Не удалось получить список изменений: %s + Название + Неверный id шаблона + Синхронизировать онлайн-шаблоны + Создать шаблон + только чтение + Импорт/Экспорт + Не удалось сохранить шаблон + Редактирование шаблона + Идентификационный номер + Шаблон профиля приложения + Описание + Сохранить + Управление локальным и онлайн-шаблоном профиля приложения + Удалить + Буфер обмена пуст! + Просмотр шаблона + Проверка обновлений + Автоматическая проверка обновлений при открытии приложения + Не удалось выдать root! + Открыть + Включить отладку WebView + Используется для отладки WebUI. Пожалуйста, включайте только при необходимости. + Прямая установка (Рекомендуется) + Установка в неактивный слот (После OTA) + Далее + Выбрать файл + Ваше устройство будет **ПРИНУДИТЕЛЬНО** загружено в текущий неактивный слот после перезагрузки! +\n Используйте эту опцию только после завершения OTA. +\n Продолжить? + Выбрать KMI + %1$s образ раздела рекомендуется + Минимизировать разреженный образ + Изменить размер разреженного образа в котором находятся модули, до его фактического размера. Обратите внимание, что это может вызвать ненормальную работу модулей, поэтому используйте это только при необходимости (например, для резервного копирования) + Удалить Временно + Удалить KernelSU (Root и все модули) полностью и навсегда. + Удалить Навсегда + Временно удалить KernelSU, восстановить исходное состояние после следующей перезагрузки. + Удалить + Восстановить Сток образ + Восстановить стоковый заводской образ (если существует резервная копия), обычно используется перед OTA; если вам нужно удалить KernelSU, используйте «Удалить Навсегда». + Установка выполнена + Установка + Установка не выполнена + Выбран lkm: %s + Сохранить Журналы + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-sl/strings.xml b/KernelSU/manager/app/src/main/res/values-sl/strings.xml new file mode 100644 index 000000000000..ae02e596c582 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-sl/strings.xml @@ -0,0 +1,106 @@ + + + Klikni za namestitev + V obdelavi + Verzija: %d + Superuporabniki: %d + KernelSU podpira samo GKI kernele + Kernel + Verzija upravitelja + Prstni odtis + SELinux status + Onemogočeno + Neznano + Napaka pri onemogočanju modula: %s + Ni nameščenih modulov + Modul + Odmesti + Namesti + Namesti + Mehki ponovni zagon + Ponovni zagon v Recovery + Ponovni zagon v Bootloader + Ponovni zagon v EDL + Ste prepričani, da želite odstraniti modul %s? + %s je odmeščen + Avtor + overlayfs ni na voljo, modul ne more delovati! + Skrij prikaz sistemskih aplikacij + Prijavite dnevnik + Naučite se KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + Naučite se, kako namestiti KernelSU in uporabiti module + Glej odprto kodo na %1$s
Pridružite se našem %2$s kanalu
+ Privzeto + Predloga + Imenski prostor vmestitve + Podedovano + Globalno + Pozameznik + Zmožnosti + Izvrzi module + Po privzetem izvrzi module + Domena + Posodobitev + Nalaganje modula: %s + Zaženi + Ponovni zagon + Dnevnik sprememb + Predloga za aplikacijski profil + Domov + Moduli: %d + Ne podpira + SuperUporabnik + Napaka pri omogočanju modula: %s + Znova zaženi + Nastavitve + Ponovni zagon v Download + O nas + Verzija + Napaka pri odmeščanju: %s + Osveži + Varni način + Za uveljavitev je potreben ponovni zagon + Prikaz sistemskih aplikacij + Moduli so onemogočeni, ker so v konfliktu z Magiskovimi! + Podprite nas + Po meri + Ime profila + Skupine + SELinux kontekst + KernelSU je, in bo vedno brezplačen in odprtokoden. Kljub temu, nam lahko z donacijo pokažete, da vam je mar. + Napaka pri posodobitvi aplikacijskega profila za %s + Za pravilno funkionalnost upravitelja je trenutna KernelSU verzija %d prenizka. Potrebna je nadgradnja na verzijo %d ali več! + Globalno privzeta vrednost za \"Izvrzi module\" v aplikacijskih profilih. Če je omogočena, bo to odstranilo vse sistemske modifikacije modulov za aplikacije, ki nimajo nastavljenega profila. + Omogočanje te opcije bo dovolilo KernelSU, da obnovi vse zaradi modulov spremenjene datoteke za to aplikacijo. + Prisilna ustavitev + Pravila + Začni z nalaganjem: %s + Na voljo je nova verzija: %s, kliknite za nadgradnjo + Napaka pri posodobitvi SELinux pravil za: %s + Ni nameščeno + Enforcing + Permissive + Ustvari predlogo + Uredi predlogo + Neveljaven id predloge + Opis + Shrani + Odstrani + le za branje + id predloge že obstaja! + Uvoz iz odložišča + Izvoz v odložišče + Lokalna predloga za izvoz ni bila najdena! + Napaka pri shranjevanju predloge + Odložišče je prazno! + Upravljaj z lokalnimi in spletnimi predlogami za aplikacijski profil + id + Ime + Ogled predloge + Uvoz uspešen + Sinhroniziraj predloge iz spleta + Uvoz/Izvoz + Napaka pri pridobivanju dnevnika sprememb: %s + Shrani Dnevnike +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-sr/strings.xml b/KernelSU/manager/app/src/main/res/values-sr/strings.xml new file mode 100644 index 000000000000..73920b6fb542 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-sr/strings.xml @@ -0,0 +1,11 @@ + + + Superkorisnici + Moduli: %d + Додирните да бисте инсталирали + Почетна + Није инсталирано + Верзија: %d + Ради + Сачувај Дневнике + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-te/strings.xml b/KernelSU/manager/app/src/main/res/values-te/strings.xml new file mode 100644 index 000000000000..f609542692f3 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-te/strings.xml @@ -0,0 +1,21 @@ + + + తెలియదు + మాడ్యూల్‌ని ప్రారంభించడంలో విఫలమైంది: %s + బలవంతంగా ఆపడం + పునఃప్రారంభించండి + మాడ్యూల్ + గురించి + SELinux స్థితి + హోమ్ + సూపర్యూజర్ + మాడ్యూల్‌ని నిలిపివేయడంలో విఫలమైంది: %s + మాడ్యూల్ ఏదీ ఇన్‌స్టాల్ చేయబడలేదు + ఇన్‌స్టాల్ చేయలేదు + ఇన్‌స్టాల్ చేయడానికి క్లిక్ చేయండి + పని చేస్తోంది + వెర్షన్: %d + సూపర్‌యూజర్‌లు: %d + మాడ్యూల్స్: %d + లాగ్‌లు సేవ్ చేయండి + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-th/strings.xml b/KernelSU/manager/app/src/main/res/values-th/strings.xml new file mode 100644 index 000000000000..50bb145e5b0a --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-th/strings.xml @@ -0,0 +1,134 @@ + + + หน้าหลัก + ยังไม่ได้ติดตั้ง + กดเพื่อติดตั้ง + กำลังทำงาน + เวอร์ชั่น: %d + เวอร์ชั่นตัวจัดการ + สิทธิ์ผู้ใช้ขั้นสูง: %d + โมดูล: %d + ไม่รองรับ + Enforcing + รีบูตเข้าสู่โหมดกู้คืน + ซอฟต์รีบูต + ตอนนี้ KernelSU รองรับเคอร์เนลประเภท GKI เท่านั้น + เคอร์เนล + ปิดใช้งาน + ลายนิ้วมือ + สถานะ SELinux + Permissive + ไม่ทราบ + สิทธิ์ผู้ใช้ขั้นสูง + ล้มเหลวในการเปิดใช้งานโมดูล: %s + ล้มเหลวในการปิดใช้งานโมดูล: %s + ไม่มีโมดูลที่ติดตั้ง + โมดูล + ถอนการติดตั้ง + ตั้งค่า + ติดตั้ง + ติดตั้ง + รีบูต + รีบูตเข้าสู่โหมด Bootloader + เกี่ยวกับ + รีบูตเข้าสู่โหมด Download + รีบูตเข้าสู่โหมด EDL + %s ถอนการติดตั้งสำเร็จ + ล้มเหลวในการถอนการติดตั้ง: %s + overlayfs ไม่สามารถใช้งานได้ โมดูลหยุดทำงาน! + คุณแน่ใจว่าจะถอนการติดตั้งโมดูล %s หรือไม่\? + ผู้สร้าง + เวอร์ชั่น + แสดงแอประบบ + ซ่อนแอประบบ + รีเฟรช + ส่งรายงาน Log + โหมดปลอดภัย + รีบูตเพื่อให้มีผล + โมดูลถูกปิดใช้งานเนื่องจากขัดแย้งกับ Magisk! + เรียนรู้เกี่ยวกับ KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + เรียนรู้วิธีการติดตั้ง KernelSU และวิธีใช้งานโมดูลต่าง ๆ + สนับสนุนพวกเรา + KernelSU เป็นโอเพ่นซอร์สฟรีทั้งจากนี้และตลอดไป อย่างไรก็ตาม คุณสามารถแสดงความห่วงใยได้ด้วยการบริจาค + ดูซอร์สโค้ดที่ %1$s
และเข้าร่วมช่อง %2$s ของเรา
+ กำหนดเอง + ค่าเริ่มต้น + เทมเพลต + ชื่อโปรไฟล์ + Mount เนมสเปซ + ทั่วไป + สืบทอด + ส่วนบุคคล + หมวดหมู่ + ความสามารถของแอป + การเปิดใช้งานตัวเลือกนี้จะทำให้ KernelSU สามารถกู้คืนไฟล์ที่แก้ไขโดยโมดูลสำหรับแอปพลิเคชั่นนี้ได้ + บริบท SELinux + Umount โมดูล + ไม่สามารถอัปเดตโปรไฟล์แอปสำหรับ %s ได้ + Umount โมดูลตามค่าเริ่มต้น + โดเมน + อัปเดต + กฎ + กำลังดาวน์โหลดโมดูล: %s + กำลังเริ่มดาวน์โหลด: %s + เวอร์ชั่นใหม่: %s พร้อมใช้งาน โปรดคลิกเพื่ออัปเกรด + บังคับหยุด + รีสตาร์ท + หากเปิดใช้งานค่าเริ่มต้นโดยทั่วไปสำหรับ \"Umount โมดูล\" ในโปรไฟล์แอป จะเป็นการลบการแก้ไขโมดูลทั้งหมดในระบบสำหรับแอปพลิเคชั่นที่ไม่มีการตั้งค่าโปรไฟล์ + เปิด + ไม่สามารถอัปเดตกฎ SElinux สำหรับ: %s ได้ + KernelSU เวอร์ชั่น %d ต่ำเกินไป ทำให้ตัวจัดการไม่สามารถใช้งานได้อย่างถูกต้อง โปรดอัปเกรดเป็นเวอร์ชั่น %d หรือที่สูงกว่า! + บันทึกการเปลี่ยนแปลง + นำเข้าเสร็จสิ้น + ส่งออกไปยังคลิปบอร์ด + ไม่พบเทมเพลตในเครื่องที่จะส่งออก! + มีไอดีเทมเพลตนี้อยู่แล้ว! + นำเข้าจากคลิปบอร์ด + ชื่อ + ไอดีเทมเพลตไม่ถูกต้อง + ซิงค์เทมเพลตออนไลน์ + สร้างเทมเพลต + อ่านเท่านั้น + นำเข้า/ส่งออก + ไม่สามารถบันทึกเทมเพลต + แก้ไขเทมเพลต + ไอดี + เทมเพลตโปรไฟล์แอป + คำอธิบาย + บันทึก + จัดการเทมเพลตโปรไฟล์แอปในเครื่องและเทมเพลตออนไลน์ + ลบ + คลิปบอร์ดว่างเปล่า! + ดูเทมเพลต + ดึงข้อมูลบันทึกการเปลี่ยนแปลงล้มเหลว: %s + เปิด + ไม่สามารถให้สิทธิ์รูทได้! + ตรวจสอบการอัปเดต + ตรวจสอบการอัปเดตโดยอัตโนมัติเมื่อเปิดแอป + เปิดใช้งานการแก้ไขข้อบกพร่อง WebView + เลือก KMI + ต่อไป + เลือกไฟล์ + ติดตั้งลงในสล็อตที่ไม่ใช้งาน (หลังจาก OTA) + ติดตั้งโดยตรง (แนะนำ) + แนะนำให้ใช้อิมเมจพาร์ติชั่น %1$s + สามารถใช้เพื่อดีบัก WebUI โปรดเปิดใช้งานเมื่อจำเป็นเท่านั้น + อุปกรณ์ของคุณจะถูก **บังคับ** ให้บูตไปยังสล็อตที่ไม่ได้ใช้งานหลังจากรีบูต! +\nโปรดใช้ตัวเลือกนี้หลังจาก OTA เสร็จแล้วเท่านั้น +\nดำเนินการต่อหรือไม่? + ลดความกระจายของอิมเมจ + ปรับขนาดความกระจายของอิมเมจในโมดูลให้เป็นขนาดจริง โปรดทราบว่านี่อาจทำให้โมดูลทำงานผิดปกติ โปรดใช้เมื่อจำเป็นเท่านั้น (เช่น การสำรองข้อมูล) + ถอนการติดตั้งถาวร + เรียกคืนอิมเมจดั้งเดิม + ถอนการติดตั้ง KernelSU ชั่วคราว จะคืนค่าเป็นสถานะดั้งเดิมหลังจากรีบูตในครั้งถัดไป + กำลังแฟลช + แฟลชสำเร็จ + แฟลชล้มเหลว + เลือก lkm: %s + ถอนการติดตั้ง + ถอนการติดตั้งชั่วคราว + การถอนการติดตั้ง KernelSU (การรูทและโมดูลทั้งหมด) อย่างสมบูรณ์โดยถาวร + คืนค่าโรงงานอิมเมจดั้งเดิม (หากมีข้อมูลสำรอง) ส่วนใหญ่มักใช้ก่อนทำการ OTA ซึ่งหากคุณต้องการถอนการติดตั้ง KernelSU โปรดใช้ \"ถอนการติดตั้งถาวร\" + บันทึกบันทึก +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-tr/strings.xml b/KernelSU/manager/app/src/main/res/values-tr/strings.xml new file mode 100644 index 000000000000..ed0bcd0f0d40 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-tr/strings.xml @@ -0,0 +1,136 @@ + + + KernelSU + Ana Sayfa + Kurulmadı + Kurmak için tıklayın + Çalışıyor + Sürüm: %d + Süper kullanıcılar: %d + Modüller: %d + Desteklenmiyor + KernelSU şimdilik sadece GKI çekirdeklerini destekliyor + Çekirdek + Yönetici sürümü + Parmak izi + SELinux durumu + Devre dışı + Etkin (Enforcing) + Serbest (Permissive) + Bilinmiyor + Süper kullanıcı + Modül etkinleştirilemedi: %s + Modül devre dışı bırakılamadı: %s + Kurulu modül yok + Modül + Kaldır + Kur + Kur + Cihazı yeniden başlat + Ayarlar + Hızlı yeniden başlat + Kurtarma modunda yeniden başlat + Önyükleyici modunda yeniden başlat + İndirme modunda yeniden başlat + EDL modunda yeniden başlat + Hakkında + %s modülünü kaldırmak istediğinizden emin misiniz? + %s kaldırıldı + Kaldırma başarısız: %s + Sürüm + Geliştirici + overlayfs mevcut değil, modül çalışamaz! + Yenile + Sistem uygulamalarını göster + Sistem uygulamalarını gizle + Günlük raporu gönder + Güvenli mod + Değişikliklerin uygulanması için cihazı yeniden başlat + Modüller, Magisk\'tekiler ile çakıştığı için devre dışı bırakıldı! + KernelSU\'yu öğrenin + https://kernelsu.org/guide/what-is-kernelsu.html + KernelSU\'nun nasıl kurulacağını ve modüllerin nasıl kullanılacağını öğrenin + Bizi destekleyin + KernelSU ücretsiz ve açık kaynaklı bir yazılımdır ve her zaman öyle kalacaktır. Ancak bağış yaparak bize destek olduğunuzu gösterebilirsiniz. + %1$s adresinde kaynak kodunu görüntüleyin.
%2$s kanalımıza katılın.
+ Uygulama profili + Varsayılan + Şablon + Özel + Profil adı + Ad alanını bağla + Kalıtsal + Küresel + Bireysel + Gruplar + Yetkinlikler + SELinux içeriği + Modüllerin bağlantısını kes + %s için uygulama profili güncellenemedi. + Mevcut KernelSU sürümü %d, yöneticinin düzgün çalışabilmesi için çok düşük. Lütfen %d sürümüne veya daha yüksek bir sürüme güncelleyin! + Varsayılan olarak modüllerin bağlantısını kes + Uygulama profilindeki \"Modüllerin bağlantısını kes\" seçeneği için varsayılan değer. Etkinleştirilirse, profil ayarı yapılmamış uygulamalar için modüllerin sistemde yaptığı tüm değişiklikler kaldırılacaktır. + Bu seçeneği etkinleştirmek, KernelSU\'nun bu uygulama için modüller tarafından değiştirilen dosyaları geri yüklemesine izin verir. + İsim alanı + Kurallar + Güncelle + Modül indiriliyor: %s + İndirme başladı: %s + Yeni sürüm: %s mevcut, güncellemek için tıklayın + Uygulamayı başlat + Uygulamayı durmaya zorla + Uygulamayı yeniden başlat + %s için SELinux kuralları güncellenemedi. + Değişiklik geçmişi + Uygulama profili şablonu + Yerel ve çevrimiçi uygulama profili şablonlarını yönetin + Şablon oluştur + Şablonu düzenle + Kimlik + Geçersiz şablon kimliği + İsim + Açıklama + Kaydet + Sil + Şablonu görüntüle + salt okunur + Şablon kimliği zaten mevcut! + İçe aktar/Dışa aktar + Panodan içe aktar + Panodan dışa aktar + Dışa aktarmak için yerel şablon bulunamadı! + Başarıyla içe aktarıldı + Çevrimiçi şablonları senkronize et + Şablon kaydedilemedi + Pano boş! + Değişiklik geçmişi alınamadı: %s + Güncellemeleri denetle + Uygulamayı açarken güncellemeleri otomatik denetle + Root izni verilemedi! + Aç + Web Görünümü Hata Ayıklamasını Etkinleştir + Web kullanıcı arayüzünde hata ayıklamak için kullanılabilir, lütfen yalnızca gerektiğinde etkinleştirin. + Doğrudan Kur (Tavsiye Edilen) + Bir Dosya Seçin + Etkin Olmayan Yuvaya Kur (OTA\'dan Sonra) + Cihazınız geçerli etkin olmayan yuvaya **ZORLA** yeniden başlatılacaktır! +\nBu seçeneği yalnızca OTA tamamlandıktan sonra kullanın. +\nDevam edilsin mi? + Sonraki + KMI seçin + %1$s bölüm imajı önerilir + Sparse imajını küçültün + Modülün bulunduğu sparse imajını gerçek boyutuna yeniden boyutlandırın. Bunun modülün anormal çalışmasına neden olabileceğini unutmayın, bu nedenle lütfen yalnızca gerekli olduğunda kullanın (yedekleme gibi) + Geçici Olarak Kaldır + Kalıcı Olarak Kaldır + Stok İmajı Geri Yükle + KernelSU\'yu geçici olarak kaldır, bir sonraki yeniden başlatmadan sonra orijinal durumuna geri yükle. + Kaldır + KernelSU (Root ve tüm modüller) tamamen ve kalıcı olarak kaldırılıyor. + Stok fabrika imajını geri yükler (eğer yedek varsa), genellikle OTA\'dan önce kullanılır; KernelSU\'yu kaldırmanız gerekiyorsa, lütfen \"Kalıcı Olarak Kaldır\" seçeneğini kullanın. + Flaşlama başarılı + Seçili lkm: %s + Flaşlanıyor + Flaşlama başarısız + Günlükleri Kaydet +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-uk/strings.xml b/KernelSU/manager/app/src/main/res/values-uk/strings.xml new file mode 100644 index 000000000000..919ac133c712 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-uk/strings.xml @@ -0,0 +1,135 @@ + + + Головна + Не встановлено + Натисніть щоб встановити + Працює + Версія: %d + Суперкористувачі: %d + Модулі: %d + Не підтримується + KernelSU підтримує лише GKI ядра на данний момент + Ядро + Версія менеджера + Відбиток + Статус SELinux + Вимкнено + Примусовий + Дозвільний + Невідомо + Суперкористувач + Не вдалося ввімкнути модуль: %s + Не вдалося вимкнути модуль: %s + Немає встановлених модулів + Модулі + Видалити + Встановити + Встановити + Перезавантажити + Налаштування + М\'яке перезавантаження + Перезавантажити до Recovery + Перезавантажити до Bootloader + Перезавантажити до Download + Перезавантажити до EDL + Про додаток + Ви впевнені, що хочете видалити модуль %s? + %s видалено + Не вдалося видалити: %s + Версія + Автор + overlayfs не доступний, модуль не може працювати! + Освіжати(Оновити) + Показати системні додатки + Сховати системні додатки + Надіслати логи + Безпечний режим + Перезавантажте, щоб застосувати + Модулі вимкнено, оскільки вони конфліктують із модулями Magisk! + Дізнайтеся про KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + Дізнайтеся, як інсталювати KernelSU і використовувати модулі + Підтримати нас + KernelSU є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування. + Приєднуйтесь до нашого каналу %2$s]]> + Профіль додатка + Типовий + Шаблон + Власний + Назва профілю + Змонтувати простір імен + Наслідуваний + Глобальний + Індивідуальний + Групи + Можливості + Контекст SELinux + Розмонтувати модулі + Не вдалося оновити профіль додатка для %s + Розмонтувати модулі за замовчуванням + Загальне значення за замовчуванням для \"Розмонтувати модулі\" у профілях додатків. Якщо ввімкнено, буде видалено всі модифікації модулів у системі для додатків, які не мають встановленого профілю. + Увімкнення даної опції дозволить KernelSU відновити для цього додатка будь-які файли, змінені модулями. + Домен + Правила + Оновити + Завантаження модуля: %s + Початок завантаження: %s + Запустити + Примусово зупинити + Перезапустити + Нова версія: %s доступна, натисніть, щоб завантажити + Не вдалося оновити правила SELinux для: %s + Журнал змін + Поточна версія KernelSU %d занадто низька, щоб менеджер міг працювати належним чином. Будь ласка, оновіть до версії %d або вище! + Успішно імпортовано + Експортувати в буфер обміну + Неможливо знайти локальні шаблони для експорту! + Шаблон з таким ідентифікатором вже існує! + Імпортувати з буферу обміну + Невдача при завантаженні списку змін: %s + Ім\'я + Невірний ідентифікатор шаблону + Синхронізувати мережеві шаблони + Створити шаблон + Тільки читання + Імпорт/Експорт + Помилка при збереженні шаблону + Редагувати шаблон + Ідентифікатор + Шаблон Профілю Додатку + Опис + Зберегти + Керувати локальними та мережевими шаблонами профілів додатків + Видалити + Буфер обміну пустий! + Переглянути шаблон + Увімкнути налагодження WebView + Виберіть KMI + Далі + Перевірка оновлень + Автоматична перевірка оновлень під час відкриття програми + Використовується для налагодження WebUI. Будь ласка, вмикайте тільки за потреби. + Пряме встановлення (рекомендовано) + Виберіть файл + Встановлення в неактивний слот (Після OTA) + Ваш пристрій буде **ПРИМУСОВО** завантажено в поточний неактивний слот після перезавантаження! +\n Використовуйте цю опцію тільки після завершення OTA. +\n Продовжити? + %1$s образ розділу рекомендується + Не вдалося отримати root! + Відкрити + Мінімізувати розріджений образ + Змінити розмір розрідженого образу, в якому знаходяться модулі, до його фактичного розміру. Зверніть увагу, що це може спричинити ненормальну роботу модулів, тому використовуйте це лише за потреби (наприклад, для резервного копіювання) + Тимчасово видалити + Назавжди видалити + Відновити Стоковий образ + Тимчасово видалити KernelSU, відновити початковий стан після наступного перезавантаження. + Видалити + Прошивка + Прошивку виконано + Прошивка не виконана + Обрано lkm: %s + Видалити KernelSU (Root і всі модулі) повністю і назавжди. + Відновити стоковий заводський образ (якщо є резервна копія), зазвичай використовується перед OTA; якщо вам потрібно видалити KernelSU, використовуйте \"Назавжди видалити\". + Зберегти Журнали + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-v27/themes.xml b/KernelSU/manager/app/src/main/res/values-v27/themes.xml new file mode 100644 index 000000000000..325416c0d9c2 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-v27/themes.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-vi/strings.xml b/KernelSU/manager/app/src/main/res/values-vi/strings.xml new file mode 100644 index 000000000000..04d878381f78 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-vi/strings.xml @@ -0,0 +1,135 @@ + + + Hồ sơ ứng dụng + Mặc định + Mẫu + Tuỳ chỉnh + Tên hồ sơ + Nhóm + Không thể cập nhật Hồ sơ ứng dụng cho %s + Ngắt mô-đun theo mặc định + Giá trị mặc định của \"Ngắt mô-đun\" trong Cấu hình ứng dụng. Nếu bật, KernelSU sẽ khôi phục mọi tệp hệ thống đã sửa đổi bởi mô-đun cho các ứng dụng chưa thiết lập Cấu hình. + Bật tùy chọn này sẽ khôi phục mọi tệp đã sửa đổi bởi các mô-đun cho ứng dụng này. + Cập nhật + Đang tải xuống mô-đun: %s + Bắt đầu tải xuống: %s + Phiên bản mới: %s đã có, bấm để nâng cấp + Tìm hiểu KernelSU + Tìm hiểu cách cài đặt KernelSU và sử dụng các mô-đun + Hỗ trợ chúng tôi + KernelSU sẽ luôn luôn miễn phí và mã nguồn mở. Tuy nhiên bạn có thể ủng hộ chúng tôi bằng một khoản đóng góp nhỏ. + Tham gia kênh %2$s của chúng tôi]]> + Các mô-đun bị vô hiệu hóa vì chúng xung đột với Magisk! + Bạn có muốn gỡ cài đặt mô-đun %s không\? + Nhật ký báo cáo + Trang chủ + Chưa cài đặt + Nhấn để cài đặt + Đang hoạt động + Phiên bản: %d + Không được hỗ trợ + KernelSU hiện tại chỉ hỗ trợ kernel GKI + Kernel + Phiên bản Manager + Fingerprint + Trạng thái SELinux + Vô hiệu hóa + Thực thi + Cho phép + Không rõ + SuperUser + Không thể kích hoạt mô-đun: %s + Không thể vô hiệu hóa mô-đun: %s + Chưa cài đặt mô-đun nào + Mô-đun + Gỡ cài đặt + Cài đặt + Cài đặt + Khởi động lại + Thiết đặt + Khởi động mềm + Khởi động lại vào Recovery + Khởi động lại vào Bootloader + Khởi động lại vào Download Mode + Khởi động lại vào EDL + Giới thiệu + %s được gỡ cài đặt + Lỗi khi gỡ cài đặt: %s + Phiên bản + Tác giả + overlayfs hiện không khả dụng, mô-đun không thể hoạt động! + Làm mới + Hiển thị ứng dụng hệ thống + Ẩn ứng dụng hệ thống + Chế độ an toàn + Khởi động lại để có hiệu lực + https://kernelsu.org/vi_VN/guide/what-is-kernelsu.html + Số superuser: %d + Số mô-đun: %d + Phạm vi + Quy định + Khởi chạy + Khởi động lại + Gắn namespace + Quyền + Không thể cập nhật quy định SELinux cho: %s + Buộc dừng + Thừa hưởng + Chung + Riêng + Bối cảnh SELinux + Ngắt mô-đun + KernelSU phiên bản %d quá thấp để trình quản lý hoạt động, hãy cập nhật lên %d hoặc mới hơn! + Đã nhập thành công + Xuất từ khay nhớ tạm + Không thể tìm thấy mẫu cục bộ để xuất! + id bản mẫu đã tồn tại! + Nhật ký thay đổi + Nhập từ khay nhớ tạm + Không nạp được nhật ký thay đổi: %s + Tên + Id mẫu không hợp lệ + Đồng bộ hóa các mẫu trực tuyến + Tạo Bản Mẫu + Nhập/Xuất + Không lưu được mẫu + Sửa Bản Mẫu + Mẫu Hồ Sơ Ứng Dụng + Mô tả + Lưu + Quản lý mẫu Hồ sơ Ứng dụng cục bộ và trực tuyến + Xóa + Clipboard trống! + Xem Bản Mẫu + chỉ đọc + id + Bật gỡ lỗi WebView + Có thể được sử dụng để gỡ lỗi WebUI, vui lòng chỉ bật khi cần. + Không cấp được quyền root! + Kiểm tra cập nhật + Tự động kiểm tra cập nhật khi mở ứng dụng + Mở + Cài đặt vào khe không hoạt động (Sau OTA) + Thiết bị của bạn sẽ **BẮT BUỘC** khởi động vào khe không hoạt động hiện tại sau khi khởi động lại! +\nChỉ sử dụng tùy chọn này sau khi OTA hoàn tất. +\nTiếp tục? + Tạm thời gỡ cài đặt KernelSU, khôi phục về trạng thái ban đầu sau lần khởi động lại tiếp theo. + Chọn KMI + Kế tiếp + Cài đặt trực tiếp (Được khuyến nghị) + Chọn một tệp + Gỡ cài đặt + Gỡ cài đặt tạm thời + Gỡ cài đặt vĩnh viễn + Khôi phục hình ảnh gốc + Gỡ cài đặt KernelSU (Root và tất cả các mô-đun) hoàn toàn và vĩnh viễn. + Khôi phục hình ảnh gốc của nhà máy (nếu có bản sao lưu), thường được sử dụng trước OTA; nếu bạn cần gỡ cài đặt KernelSU, vui lòng sử dụng \"Gỡ cài đặt vĩnh viễn\". + Đang cài + Cài thành công + Cài thất bại + Đã chọn lkm: %s + Nên sử dụng hình ảnh phân vùng %1$s + Giảm thiểu hình ảnh thưa thớt + Thay đổi kích thước hình ảnh thưa nơi đặt mô-đun theo kích thước thực tế của nó. Lưu ý điều này có thể khiến module hoạt động không bình thường nên vui lòng chỉ sử dụng khi cần thiết (chẳng hạn như để sao lưu) + Lưu Nhật Ký + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-zh-rCN/strings.xml b/KernelSU/manager/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000000..ff11137df5ef --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,132 @@ + + + 主页 + 未安装 + 点击安装 + 工作中 + 版本: %d + 超级用户数:%d + 不支持 + KernelSU 现在只支持 GKI 内核 + 内核版本 + 管理器版本 + 系统指纹 + SELinux 状态 + 被禁用 + 强制执行 + 宽容模式 + 未知 + 超级用户 + 无法启用模块: %s + 无法禁用模块: %s + 没有安装模块 + 模块 + 卸载 + 安装 + 安装 + 重启 + 设置 + 软重启 + 重启到 Recovery + 重启到 BootLoader + 重启到 Download + 重启到 EDL + 关于 + 确定要卸载模块 %s 吗? + %s 已卸载 + 卸载失败: %s + 版本 + 作者 + 内核不支持 overlayfs,模块功能无法运作! + 刷新 + 显示系统应用 + 隐藏系统应用 + 发送日志 + 安全模式 + 重启生效 + 所有模块已被禁用,因为它与 Magisk 的模块系统有冲突! + 模块数:%d + 了解 KernelSU + https://kernelsu.org/zh_CN/guide/what-is-kernelsu.html + 了解如何安装 KernelSU 以及如何开发模块 + 支持开发 + KernelSU 将保持免费开源,向开发者捐赠以表示支持。 + 加入我们的 %2$s 频道
加入我们的 QQ 频道]]>
+ 默认 + 模版 + 自定义 + 名称 + 命名空间 + 继承 + 全局 + 私有 + 组 + 权能 + SELinux + 卸载模块 + 为 %s 更新 App Profile 失败 + 当前 KernelSU 版本 %d 过低,管理器无法正常工作,请升级内核 KernelSU 版本至 %d 或以上! + 默认卸载模块 + App Profile 中\"卸载模块\"的全局默认值,如果启用,将会为没有设置 Profile 的应用移除所有模块针对系统的修改。 + 启用后将允许 KernelSU 为本应用还原被模块修改过的文件。 + 域 + 规则 + 更新 + 正在下载模块:%s + 开始下载:%s + 发现新版本:%s,点击升级 + 启动 + 强制停止 + 重新启动 + 为:%s 更新翻译失败 + 更新日志 + App Profile 模版 + 管理本地和在线的 App Profile 模版 + 创建模版 + 编辑模版 + 模版 id + 模版 id 不合法 + 名字 + 描述 + 保存 + 删除 + 查看模版 + 只读 + 模版 id 已存在! + 导入/导出 + 从剪切板导入 + 导出到剪切板 + 没有本地模版可以导出! + 导入成功! + 同步在线规则 + 模版保存失败! + 剪切板为空! + 获取更新日志失败:%s + 检查更新 + 在应用启动后自动检查是否有最新版 + 获取 root 失败! + 打开 + 启用 WebView 调试 + 可用于调试 WebUI ,请仅在需要时启用。 + 直接安装(推荐) + 选择一个文件 + 安装到未使用的槽位(OTA 后) + 将在重启后强制切换到另一个槽位!\n注意只能在 OTA 更新完成后的重启之前使用。\n确认? + 下一步 + 建议选择 %1$s 分区镜像 + 选择 KMI + 最小化稀疏文件 + 将模块所在的稀疏文件镜像调整为其实际大小,注意这可能导致模块工作异常,请仅在必要时(如备份)使用 + 卸载 + 临时卸载 + 永久卸载 + 恢复原厂镜像 + 临时卸载 KernelSU,下次重启后恢复 + 完全并永久移除 KernelSU 和所有模块 + 恢复原厂镜像,一般在 OTA 前使用;如需卸载请使用“永久卸载” + 刷写中 + 刷写完成 + 刷写失败 + 选择的 LKM :%s + 保存日志保存日志 +
\ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-zh-rHK/strings.xml b/KernelSU/manager/app/src/main/res/values-zh-rHK/strings.xml new file mode 100644 index 000000000000..154ddac9bb8d --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-zh-rHK/strings.xml @@ -0,0 +1,124 @@ + + + 首頁 + 未安裝 + 按一下以安裝 + 運作中 + KernelSU 版本:%d + 超級使用者:%d 個 + 已安裝模組:%d 個 + 不支援 + KernelSU 現在僅支援 GKI 核心 + 核心 + 管理器版本 + 指紋 + SELinux 狀態 + 已停用 + 強制 + 寬鬆 + 未知 + 超級使用者 + 無法啟用模組:%s + 無法停用模組:%s + 尚未安裝模組 + 模組 + 解除安裝 + 安裝 + 安裝 + 重新啟動 + 設定 + 軟啟動 + 重新啟動至 Recovery + 重新啟動至 Bootloader + 重新啟動至 Download + 重新啟動至 EDL + 關於 + 您確定要解除安裝模組「%s」嗎? + 「%s」已解除安裝 + 無法解除安裝:%s + 版本 + 作者 + OverlayFS 無法使用,模組無法正常運作! + 重新整理 + 顯示系統應用程式 + 隱藏系統應用程式 + 傳送記錄 + 安全模式 + 重新啟動以生效 + 模組已停用,因其與 Magisk 的模組存在衝突! + 深入瞭解 KernelSU + https://kernelsu.org/zh_TW/guide/what-is-kernelsu.html + 瞭解如何安裝 KernelSU 以及如何開發模組 + 支援開發 + KernelSU 將保持免費和開源,您可以考慮向開發人員贊助以表示支持。 + 加入我們的 %2$s 頻道]]> + 預設 + 設定檔名稱 + 範本 + 繼承 + 全域 + 功能 + 卸載模組 + 無法更新 %s 應用程式設定檔 + 規則 + 目前 KernelSU 版本 %d 過低,管理器無法正常運作。請升級至 %d 或更高版本! + 應用程式設定檔中「解除安裝模組」的全域預設值,如果啟用,將會為沒有設定檔的應用程式移除所有模組針對系統的修改。 + 啟用此選項將允許 KernelSU 為這個應用程式還原任何被模組修改過的檔案。 + 網域 + 更新 + 自訂 + 掛載命名空間 + 個人 + 群組 + SELinux 環境 + 預設解除安裝模組 + 正在下載模組:%s + 開始下載:%s + 新版本:%s 已可供使用,按一下以升級 + 啟動 + 強制停止 + 重新啟動 + 無法為 %s 更新 SELinux 規則 + 變更記錄 + 成功匯出 + 導出到剪貼板 + 本地沒有模板可匯出! + 模板 ID 已存在! + 從剪貼簿匯入 + 獲取更新日誌失敗:%s + 名字 + 模板 ID 無效 + 同步在線規則 + 創建模板 + 只讀 + 匯出 / 匯入 + 模板儲存失敗 + 編輯模板 + 模板 ID + App Profile 模板 + 描述 + 儲存 + 管理本地和線上的 App Profile 模板 + 刪除 + 剪貼簿沒有內容! + 查看模板 + 啟用 WebView 偵錯 + 可用於偵錯WebUI,請僅在需要時啟用。 + 直接安裝(建議) + 選擇一個文件 + 安裝到非活動插槽(OTA 後) + 重新啟動後,您的裝置將強制啟動到目前非活動插槽! +\n僅在 OTA 完成後使用此選項。 +\n繼續? + 下一個 + 選擇KMI + 建議使用 %1$s 分割區映像 + 授予root權限失敗! + 打開 + 檢查更新 + 開啟應用程式時自動檢查更新 + 最小化稀疏影像 + 將模組所在的稀疏影像調整為實際大小。 請注意,這可能會導致模組工作異常,因此請僅在必要時使用(例如備份) + 解除安裝 + 保存日志 + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values-zh-rTW/strings.xml b/KernelSU/manager/app/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000000..7917ddef9994 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,134 @@ + + + 首頁 + 未安裝 + 按一下以安裝 + 運作中 + KernelSU 版本:%d + 已授權 Root:%d 個 + 不支援 + KernelSU 現在僅支援 GKI 核心 + 核心 + 管理器版本 + 指紋 + SELinux 狀態 + 已停用 + 強制 + 寬鬆 + 未知 + Root 授權 + 無法啟用模組:%s + 無法停用模組:%s + 尚未安裝模組 + 模組 + 解除安裝 + 安裝 + 安裝 + 重新啟動 + 設定 + 軟重新啟動 + 重新啟動至 Recovery + 重新啟動至 Bootloader + 重新啟動至 Download + 重新啟動至 EDL + 關於 + 您確定要解除安裝模組「%s」嗎? + 「%s」已解除安裝 + 無法解除安裝:%s + 版本 + 作者 + OverlayFS 無法使用,模組無法正常運作! + 重新整理 + 顯示系統應用程式 + 隱藏系統應用程式 + 傳送記錄 + 安全模式 + 重新啟動以生效 + 模組已停用,因其與 Magisk 的模組存在衝突! + 已安裝模組:%d 個 + 深入瞭解 KernelSU + https://kernelsu.org/zh_TW/guide/what-is-kernelsu.html + 瞭解如何安裝 KernelSU 以及如何開發模組 + 支援開發 + KernelSU 將保持免費和開源,您可以考慮向開發人員贊助以表示支持。 + 加入我們的 %2$s 頻道]]> + 卸載模組 + 無法更新 %s 應用程式設定檔 + 目前安裝的 KernelSU 版本 %d 過低,管理器無法正常工作,請升級核心 KernelSU 版本至 %d 或以上! + 預設卸載模組 + 應用程式設定檔中「卸載模組」的全域預設值,如果啟用,將會為沒有設定檔的應用程式移除所有模組針對系統的修改。 + 啟用後將允許 KernelSU 為本應用程式還原被模組修改過的檔案。 + 預設 + 自訂 + 權限 + 規則 + 正在下載模組:%s + 重新啟動 + 模板 + 設定檔名稱 + 掛載命名空間 + 繼承 + 全域 + 私人 + 群組 + SELinux context + 域 + 更新 + 開始下載:%s + 發現新版本:%s 已可供使用,按一下即可升級 + 啟動 + 強制停止 + 無法為 %s 更新 SELinux + 變更記錄 + 模板 ID 無效 + 創建模板 + 編輯模板 + 模板 ID + App Profile 模板 + 管理本地和線上的 App Profile 模板 + 成功匯入 + 匯出至剪貼簿 + 沒有本地模板可匯出! + 模板 ID 已存在! + 從剪貼簿匯入 + 獲取更新日誌失敗:%s + 名稱 + 與線上規則同步 + 唯讀 + 匯出 / 匯入 + 模板儲存失敗 + 描述 + 儲存 + 刪除 + 剪貼簿沒有內容! + 檢查模板 + 可用於偵錯 WebUI,請僅在需要時啟用。 + 啟用 WebView 偵錯 + 取得root失敗! + 開啟 + 檢查更新 + 在打開App時自動檢查更新 + 選擇一個檔案 + 安裝到非使用中的槽位(在 OTA 更新後) + 您的裝置將在下次重新啟動後強制切換到非使用中的槽位! +\n這個選項僅在 OTA 更新完畢後使用。 +\n請問是否繼續? + 直接安裝(建議) + 下一步 + 選擇 KMI + 建議使用 %1$s 分區 + 最小化模組稀疏映像 + 將模組的稀疏映像調整為其實際大小,請注意這可能導致模組工作異常,請僅在需要時(如備份)使用 + 解除安裝 + 暫時解除安裝 + 還原原廠映像 + 暫時卸載KernelSU,下次重啟後恢復原狀。 + 永久解除安裝 + 完全的(永久的)解除安裝 KernelSU(Root 和所有模組)。 + 寫入中 + 寫入完成 + 恢復原廠映像(如果有備份),通常在OTA之前使用;如果需要解除安裝KernelSU,請使用「永久解除安裝」。 + 寫入失敗 + 選擇的 LKM :%s + 儲存日誌 + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values/strings.xml b/KernelSU/manager/app/src/main/res/values/strings.xml new file mode 100644 index 000000000000..d094969af1a2 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values/strings.xml @@ -0,0 +1,134 @@ + + + KernelSU + Home + Not installed + Click to install + Working + Version: %d + Superusers: %d + Modules: %d + Unsupported + KernelSU only supports GKI kernels now + Kernel + Manager Version + Fingerprint + SELinux status + Disabled + Enforcing + Permissive + Unknown + SuperUser + Failed to enable module: %s + Failed to disable module: %s + No installed modules + Module + Uninstall + Install + Install + Reboot + Settings + Soft Reboot + Reboot to Recovery + Reboot to Bootloader + Reboot to Download + Reboot to EDL + About + Are you sure you want to uninstall module %s? + %s is uninstalled + Failed to uninstall: %s + Version + Author + overlayfs is not available, module cannot work! + Refresh + Show system apps + Hide system apps + Report Log + Safe mode + Reboot to take effect + Modules are disabled because it is conflict with Magisk\'s! + Learn KernelSU + https://kernelsu.org/guide/what-is-kernelsu.html + Learn how to install KernelSU and use modules + Support Us + KernelSU is, and always will be, free, and open source. You can however show us that you care by making a donation. + Join our %2$s channel]]> + App Profile + Default + Template + Custom + Profile name + Mount namespace + Inherited + Global + Individual + Groups + Capabilities + SELinux context + Umount modules + Failed to update App Profile for %s + The current KernelSU version %d is too low for the manager to function properly. Please upgrade to version %d or higher! + Umount modules by default + The global default value for \"Umount modules\" in App Profiles. If enabled, it will remove all module modifications to the system for applications that do not have a Profile set. + Enabling this option will allow KernelSU to restore any modified files by the modules for this application. + Domain + Rules + Update + Downloading module: %s + Start downloading: %s + New version: %s is available, click to upgrade + Launch + Force Stop + Restart + Failed to update SELinux rules for: %s + Changelog + App Profile Template + Manage local and online template of App Profile + Create Template + Edit Template + id + Invalid template id + Name + Description + Save + Delete + View Template + readonly + template id already exists! + Import/Export + Import from clipboard + Export to clipboard + Can not find local template to export! + Imported successfully + Sync online templates + Failed to save template + Clipboard is empty! + Fetch changelog failed: %s + Check update + Automatically check for updates when opening the app + Failed to grant root! + Open + Enable WebView Debugging + Can be used to debug WebUI, please enable only when needed. + Direct Install (Recommended) + Select a File + Install to Inactive Slot (After OTA) + Your device will be **FORCED** to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue? + Next + %1$s partition image is recommended + Select KMI + Minimize sparse image + Resize the sparse image where the module is located to its actual size. Note that this may cause the module to work abnormally, so please only use when necessary (such as for backup) + Uninstall + Uninstall Temporarily + Uninstall Permanently + Restore Stock Image + Temporarily uninstall KernelSU, restore to original state after next reboot. + Uninstalling KernelSU(Root and all modules) completely and permanently. + Restore the stock factory image (if a backup exists), usually used before OTA; if you need to uninstall KernelSU, please use \"Permanent Uninstall\". + Flashing + Flash success + Flash failed + Selected lkm: %s + Save Logs + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/values/themes.xml b/KernelSU/manager/app/src/main/res/values/themes.xml new file mode 100644 index 000000000000..7d41d8ec77fc --- /dev/null +++ b/KernelSU/manager/app/src/main/res/values/themes.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/xml/backup_rules.xml b/KernelSU/manager/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 000000000000..fa0f996d2c2a --- /dev/null +++ b/KernelSU/manager/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/xml/data_extraction_rules.xml b/KernelSU/manager/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 000000000000..9ee9997b0b47 --- /dev/null +++ b/KernelSU/manager/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/xml/filepaths.xml b/KernelSU/manager/app/src/main/res/xml/filepaths.xml new file mode 100644 index 000000000000..f8a9a5c545fb --- /dev/null +++ b/KernelSU/manager/app/src/main/res/xml/filepaths.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/KernelSU/manager/app/src/main/res/xml/network_security_config.xml b/KernelSU/manager/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 000000000000..6dd26ccf046a --- /dev/null +++ b/KernelSU/manager/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,8 @@ + + + + 127.0.0.1 + 0.0.0.0 + ::1 + + diff --git a/KernelSU/manager/build.gradle.kts b/KernelSU/manager/build.gradle.kts new file mode 100644 index 000000000000..0e503df3b636 --- /dev/null +++ b/KernelSU/manager/build.gradle.kts @@ -0,0 +1,101 @@ +import com.android.build.api.dsl.ApplicationDefaultConfig +import com.android.build.api.dsl.CommonExtension +import com.android.build.gradle.api.AndroidBasePlugin +import java.io.ByteArrayOutputStream + +plugins { + alias(libs.plugins.agp.app) apply false + alias(libs.plugins.agp.lib) apply false + alias(libs.plugins.kotlin) apply false + alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.lsplugin.cmaker) +} + +cmaker { + default { + arguments.addAll( + arrayOf( + "-DANDROID_STL=c++_static", + ) + ) + val flags = arrayOf( + "-Wno-gnu-string-literal-operator-template", + "-Wno-c++2b-extensions", + ) + cFlags.addAll(flags) + cppFlags.addAll(flags) + abiFilters("arm64-v8a", "x86_64") + } + buildTypes { + if (it.name == "release") { + arguments += "-DDEBUG_SYMBOLS_PATH=${buildDir.absolutePath}/symbols" + } + } +} + +val androidMinSdkVersion = 26 +val androidTargetSdkVersion = 34 +val androidCompileSdkVersion = 34 +val androidBuildToolsVersion = "34.0.0" +val androidCompileNdkVersion = "26.3.11579264" +val androidSourceCompatibility = JavaVersion.VERSION_21 +val androidTargetCompatibility = JavaVersion.VERSION_21 +val managerVersionCode by extra(getVersionCode()) +val managerVersionName by extra(getVersionName()) + +fun getGitCommitCount(): Int { + val out = ByteArrayOutputStream() + exec { + commandLine("git", "rev-list", "--count", "HEAD") + standardOutput = out + } + return out.toString().trim().toInt() +} + +fun getGitDescribe(): String { + val out = ByteArrayOutputStream() + exec { + commandLine("git", "describe", "--tags", "--always") + standardOutput = out + } + return out.toString().trim() +} + +fun getVersionCode(): Int { + val commitCount = getGitCommitCount() + val major = 1 + return major * 10000 + commitCount + 200 +} + +fun getVersionName(): String { + return getGitDescribe() +} + +subprojects { + plugins.withType(AndroidBasePlugin::class.java) { + extensions.configure(CommonExtension::class.java) { + compileSdk = androidCompileSdkVersion + ndkVersion = androidCompileNdkVersion + buildToolsVersion = androidBuildToolsVersion + + defaultConfig { + minSdk = androidMinSdkVersion + if (this is ApplicationDefaultConfig) { + targetSdk = androidTargetSdkVersion + versionCode = managerVersionCode + versionName = managerVersionName + } + } + + lint { + abortOnError = true + checkReleaseBuilds = false + } + + compileOptions { + sourceCompatibility = androidSourceCompatibility + targetCompatibility = androidTargetCompatibility + } + } + } +} \ No newline at end of file diff --git a/KernelSU/manager/gradle.properties b/KernelSU/manager/gradle.properties new file mode 100644 index 000000000000..387da396166f --- /dev/null +++ b/KernelSU/manager/gradle.properties @@ -0,0 +1,3 @@ +android.experimental.enableNewResourceShrinker.preciseShrinking=true +android.enableAppCompileTimeRClass=true +android.useAndroidX=true diff --git a/KernelSU/manager/gradle/libs.versions.toml b/KernelSU/manager/gradle/libs.versions.toml new file mode 100644 index 000000000000..6ec08d63e90b --- /dev/null +++ b/KernelSU/manager/gradle/libs.versions.toml @@ -0,0 +1,78 @@ +[versions] +agp = "8.4.1" +kotlin = "2.0.0" +ksp = "2.0.0-1.0.21" +compose-bom = "2024.05.00" +lifecycle = "2.8.0" +accompanist = "0.34.0" +navigation = "2.7.7" +activity-compose = "1.9.0" +kotlinx-coroutines = "1.8.1" +coil-compose = "2.6.0" +compose-destination = "1.10.2" +sheets-compose-dialogs = "1.3.0" +markdown = "4.6.2" +webkit = "1.11.0" +appiconloader-coil = "1.5.0" +parcelablelist = "2.0.1" +libsu = "5.2.2" +apksign = "1.4" +cmaker = "1.2" + +[plugins] +agp-app = { id = "com.android.application", version.ref = "agp" } +agp-lib = { id = "com.android.library", version.ref = "agp" } + +kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } + +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } + +lsplugin-apksign = { id = "org.lsposed.lsplugin.apksign", version.ref = "apksign" } +lsplugin-cmaker = { id = "org.lsposed.lsplugin.cmaker", version.ref = "cmaker" } + +[libraries] +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } + +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } + +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } +androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } +androidx-compose-material = { group = "androidx.compose.material", name = "material" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } + +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } +androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" } +androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" } + +androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } + +com-google-accompanist-drawablepainter = { group = "com.google.accompanist", name = "accompanist-drawablepainter", version.ref = "accompanist" } +com-google-accompanist-navigation-animation = { group = "com.google.accompanist", name = "accompanist-navigation-animation", version.ref = "accompanist" } +com-google-accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" } +com-google-accompanist-webview = { group = "com.google.accompanist", name = "accompanist-webview", version.ref = "accompanist" } + +com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" } +com-github-topjohnwu-libsu-service = { group = "com.github.topjohnwu.libsu", name = "service", version.ref = "libsu" } +com-github-topjohnwu-libsu-io= { group = "com.github.topjohnwu.libsu", name = "io", version.ref = "libsu" } + +dev-rikka-rikkax-parcelablelist = { module = "dev.rikka.rikkax.parcelablelist:parcelablelist", version.ref = "parcelablelist" } + +io-coil-kt-coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil-compose" } + +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } + +me-zhanghai-android-appiconloader-coil = { group = "me.zhanghai.android.appiconloader", name = "appiconloader-coil", version.ref = "appiconloader-coil" } + +compose-destinations-animations-core = { group = "io.github.raamcosta.compose-destinations", name = "animations-core", version.ref = "compose-destination" } +compose-destinations-ksp = { group = "io.github.raamcosta.compose-destinations", name = "ksp", version.ref = "compose-destination" } + +sheet-compose-dialogs-core = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "core", version.ref = "sheets-compose-dialogs" } +sheet-compose-dialogs-list = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "list", version.ref = "sheets-compose-dialogs" } +sheet-compose-dialogs-input = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "input", version.ref = "sheets-compose-dialogs" } + +markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown" } \ No newline at end of file diff --git a/KernelSU/manager/gradle/wrapper/gradle-wrapper.jar b/KernelSU/manager/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000000..e6441136f3d4 Binary files /dev/null and b/KernelSU/manager/gradle/wrapper/gradle-wrapper.jar differ diff --git a/KernelSU/manager/gradle/wrapper/gradle-wrapper.properties b/KernelSU/manager/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..b82aa23a4f05 --- /dev/null +++ b/KernelSU/manager/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/KernelSU/manager/gradlew b/KernelSU/manager/gradlew new file mode 100755 index 000000000000..4b6a9cfc46fa --- /dev/null +++ b/KernelSU/manager/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/KernelSU/manager/gradlew.bat b/KernelSU/manager/gradlew.bat new file mode 100644 index 000000000000..25da30dbdeee --- /dev/null +++ b/KernelSU/manager/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/KernelSU/manager/settings.gradle.kts b/KernelSU/manager/settings.gradle.kts new file mode 100644 index 000000000000..2230bf48671a --- /dev/null +++ b/KernelSU/manager/settings.gradle.kts @@ -0,0 +1,21 @@ +@file:Suppress("UnstableApiUsage") + +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + +pluginManagement { + repositories { + google() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + maven("https://jitpack.io") + } +} + +rootProject.name = "KernelSU" +include(":app") diff --git a/KernelSU/manager/sign.example.properties b/KernelSU/manager/sign.example.properties new file mode 100644 index 000000000000..bc70a60cae89 --- /dev/null +++ b/KernelSU/manager/sign.example.properties @@ -0,0 +1,4 @@ +KEYSTORE_FILE= +KEYSTORE_PASSWORD= +KEY_ALIAS= +KEY_PASSWORD= diff --git a/KernelSU/scripts/abi_gki_all.py b/KernelSU/scripts/abi_gki_all.py new file mode 100644 index 000000000000..d433634aec2f --- /dev/null +++ b/KernelSU/scripts/abi_gki_all.py @@ -0,0 +1,10 @@ +from xml.dom.minidom import parse +import xml.dom.minidom +import sys + + +DOMTree = xml.dom.minidom.parse(sys.argv[1]) +symbols = DOMTree.getElementsByTagName("elf-symbol") +print("[abi_symbol_list]") +for symbol in symbols: + print(" " + symbol.getAttribute("name")) diff --git a/KernelSU/scripts/add_device_handler.py b/KernelSU/scripts/add_device_handler.py new file mode 100644 index 000000000000..f18ad3a215e0 --- /dev/null +++ b/KernelSU/scripts/add_device_handler.py @@ -0,0 +1,50 @@ +import json +import sys +import os + + +def main(): + assert len(sys.argv) == 2 + file_name = sys.argv[1] + github = "https://github.com/" + issue_content = os.environ["ISSUE_CONTENT"] + lines = issue_content.split("\n\n") + assert len(lines) == 6 + url = lines[1] + print(url) + device = lines[3] + print(device) + code_of_conduct = lines[5] + print(code_of_conduct) + assert code_of_conduct.find("[X]") > 0 + tmp = url.removesuffix("/").replace(github, "").split("/") + print(tmp) + assert len(tmp) == 2 + maintainer = tmp[0] + print(maintainer) + maintainer_link = "%s%s" % (github, maintainer) + print(maintainer_link) + kernel_name = tmp[1] + print(kernel_name) + kernel_link = "%s%s/%s" % (github, maintainer, kernel_name) + print(kernel_link) + with open(file_name, "r") as f: + data = json.loads(f.read()) + data.append( + { + "maintainer": maintainer, + "maintainer_link": maintainer_link, + "kernel_name": kernel_name, + "kernel_link": kernel_link, + "devices": device, + } + ) + os.remove(file_name) + with open(file_name, "w") as f: + f.write(json.dumps(data, indent=4)) + os.system("echo success=true >> $GITHUB_OUTPUT") + os.system("echo device=%s >> $GITHUB_OUTPUT" % device) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/KernelSU/scripts/allowlist.bt b/KernelSU/scripts/allowlist.bt new file mode 100644 index 000000000000..d95bb5ac49a9 --- /dev/null +++ b/KernelSU/scripts/allowlist.bt @@ -0,0 +1,89 @@ +// Define constants as per the provided structure. +#define KSU_MAX_PACKAGE_NAME 256 +#define KSU_MAX_GROUPS 32 +#define KSU_SELINUX_DOMAIN 64 + +// Define the root_profile structure with padding for 64-bit alignment. +struct root_profile { + uint32 uid; + uint32 gid; + + uint32 groups_count; + uint32 groups[KSU_MAX_GROUPS]; + char padding1[4]; // Padding for 64-bit alignment. + + struct { + uint64 effective; + uint64 permitted; + uint64 inheritable; + } capabilities; + + char selinux_domain[KSU_SELINUX_DOMAIN]; + + uint32 namespaces; + char padding2[4]; // Padding for 64-bit alignment. +}; + +// Define the non_root_profile structure with padding for 64-bit alignment. +struct non_root_profile { + byte umount_modules; + char padding[7]; // Padding to make the total size a multiple of 8. +}; + +// Define the rp_config structure with padding for 64-bit alignment. +struct rp_config_t { + byte use_default; + + char template_name[KSU_MAX_PACKAGE_NAME]; + char padding[7]; // Padding to make the total size a multiple of 8. + + struct root_profile profile; +}; + +// Define the nrp_config structure with padding for 64-bit alignment. +struct nrp_config_t { + byte use_default; + char padding1[7]; // Padding to make the total size a multiple of 8. + + struct non_root_profile profile; + char padding2[488]; // Padding to align the union +}; + +// Define the main app_profile structure +typedef struct { + uint32 version; + char key[KSU_MAX_PACKAGE_NAME]; + int32 current_uid; + int64 allow_su; + + // Based on allow_su, decide which profile to use + if (allow_su != 0) { + rp_config_t rp_config; + } else { + nrp_config_t nrp_config; + } + +} app_profile; + +// Define the file header with magic number and version +typedef struct { + uint32 magic; + uint32 version; +} file_header; + +// Main entry for parsing the file +file_header header; + +if (header.magic != 0x7f4b5355) { + Printf("Invalid file magic number.\n"); + return; +} + +FSeek(8); // Skip the header + + +// Continually read app_profile instances until end of file +while (!FEof()) { + app_profile profile; +} + diff --git a/KernelSU/scripts/bin2c.py b/KernelSU/scripts/bin2c.py new file mode 100644 index 000000000000..585131329b25 --- /dev/null +++ b/KernelSU/scripts/bin2c.py @@ -0,0 +1,51 @@ +#!/usr/bin/python3 + +import argparse +import os +import re + +line_size = 80 + + +def bin2c(filename, varname='data'): + if not os.path.isfile(filename): + print('File "%s" is not found!' % filename) + return '' + if not re.match('[a-zA-Z_][a-zA-Z0-9_]*', varname): + print('Invalid variable name "%s"' % varname) + return + with open(filename, 'rb') as in_file: + data = in_file.read() + # limit the line length + byte_len = 6 # '0x00, ' + out = 'unsigned int %s_size = %d;\n' \ + 'const char %s[%d] = {\n' % (varname, len(data), varname, len(data)) + line = '' + for byte in data: + line += '0x%02x, ' % byte + if len(line) + 4 + byte_len >= line_size: + out += ' ' * 4 + line + '\n' + line = '' + # add the last line + if len(line) + 4 + byte_len < line_size: + out += ' ' * 4 + line + '\n' + # strip the last comma + out = out.rstrip(', \n') + '\n' + out += '};' + return out + + +def main(): + """ Main func """ + parser = argparse.ArgumentParser() + parser.add_argument( + 'filename', help='filename to convert to C array') + parser.add_argument( + 'varname', nargs='?', help='variable name', default='data') + args = parser.parse_args() + # print out the data + print(bin2c(args.filename, args.varname)) + + +if __name__ == '__main__': + main() diff --git a/KernelSU/scripts/ksubot.py b/KernelSU/scripts/ksubot.py new file mode 100644 index 000000000000..c3b3672772ed --- /dev/null +++ b/KernelSU/scripts/ksubot.py @@ -0,0 +1,101 @@ +import asyncio +import os +import sys +from telethon import TelegramClient +from telethon.tl.functions.help import GetConfigRequest + +API_ID = 611335 +API_HASH = "d524b414d21f4d37f08684c1df41ac9c" + + +BOT_TOKEN = os.environ.get("BOT_TOKEN") +CHAT_ID = os.environ.get("CHAT_ID") +MESSAGE_THREAD_ID = os.environ.get("MESSAGE_THREAD_ID") +COMMIT_URL = os.environ.get("COMMIT_URL") +COMMIT_MESSAGE = os.environ.get("COMMIT_MESSAGE") +RUN_URL = os.environ.get("RUN_URL") +TITLE = os.environ.get("TITLE") +VERSION = os.environ.get("VERSION") +MSG_TEMPLATE = """ +**{title}** +#ci_{version} +``` +{commit_message} +``` +[Commit]({commit_url}) +[Workflow run]({run_url}) +""".strip() + + +def get_caption(): + msg = MSG_TEMPLATE.format( + title=TITLE, + version=VERSION, + commit_message=COMMIT_MESSAGE, + commit_url=COMMIT_URL, + run_url=RUN_URL, + ) + if len(msg) > 1024: + return COMMIT_URL + return msg + + +def check_environ(): + global CHAT_ID, MESSAGE_THREAD_ID + if BOT_TOKEN is None: + print("[-] Invalid BOT_TOKEN") + exit(1) + if CHAT_ID is None: + print("[-] Invalid CHAT_ID") + exit(1) + else: + CHAT_ID = int(CHAT_ID) + if COMMIT_URL is None: + print("[-] Invalid COMMIT_URL") + exit(1) + if COMMIT_MESSAGE is None: + print("[-] Invalid COMMIT_MESSAGE") + exit(1) + if RUN_URL is None: + print("[-] Invalid RUN_URL") + exit(1) + if TITLE is None: + print("[-] Invalid TITLE") + exit(1) + if VERSION is None: + print("[-] Invalid VERSION") + exit(1) + if MESSAGE_THREAD_ID is None: + print("[-] Invaild MESSAGE_THREAD_ID") + exit(1) + else: + MESSAGE_THREAD_ID = int(MESSAGE_THREAD_ID) + + +async def main(): + print("[+] Uploading to telegram") + check_environ() + files = sys.argv[1:] + print("[+] Files:", files) + if len(files) <= 0: + print("[-] No files to upload") + exit(1) + print("[+] Logging in Telegram with bot") + script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) + session_dir = os.path.join(script_dir, "ksubot") + async with await TelegramClient(session=session_dir, api_id=API_ID, api_hash=API_HASH).start(bot_token=BOT_TOKEN) as bot: + caption = [""] * len(files) + caption[-1] = get_caption() + print("[+] Caption: ") + print("---") + print(caption) + print("---") + print("[+] Sending") + await bot.send_file(entity=CHAT_ID, file=files, caption=caption, reply_to=MESSAGE_THREAD_ID, parse_mode="markdown") + print("[+] Done!") + +if __name__ == "__main__": + try: + asyncio.run(main()) + except Exception as e: + print(f"[-] An error occurred: {e}") diff --git a/KernelSU/userspace/ksud/.gitignore b/KernelSU/userspace/ksud/.gitignore new file mode 100644 index 000000000000..3c71873d9fb4 --- /dev/null +++ b/KernelSU/userspace/ksud/.gitignore @@ -0,0 +1,2 @@ +/target +.cargo/ \ No newline at end of file diff --git a/KernelSU/userspace/ksud/Cargo.lock b/KernelSU/userspace/ksud/Cargo.lock new file mode 100644 index 000000000000..4e0b195fb99d --- /dev/null +++ b/KernelSU/userspace/ksud/Cargo.lock @@ -0,0 +1,1860 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_log-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" + +[[package]] +name = "android_logger" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f" +dependencies = [ + "android_log-sys", + "env_logger 0.10.2", + "log", + "once_cell", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deflate64" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive-new" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "env_filter", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "extattr" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b59f8a77817ff1b795adafc535941bdf664184f5f95e0b6d1d77dd6d12815dc" +dependencies = [ + "bitflags 1.3.2", + "errno 0.2.8", + "libc", +] + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hole-punch" +version = "0.0.4-alpha.0" +source = "git+https://github.com/tiann/hole-punch#11ab7a61bfb98682b72fd7f58a47d8e5d997328e" +dependencies = [ + "cfg-if 0.1.10", + "errno 0.2.8", + "libc", + "memmap", + "thiserror", + "winapi", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "include-flate" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e11569346406931d20276cc460215ee2826e7cad43aa986999cb244dd7adb0" +dependencies = [ + "include-flate-codegen-exports", + "lazy_static", + "libflate", +] + +[[package]] +name = "include-flate-codegen" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a7d6e1419fa3129eb0802b4c99603c0d425c79fb5d76191d5a20d0ab0d664e8" +dependencies = [ + "libflate", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "include-flate-codegen-exports" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75657043ffe3d8280f1cb8aef0f505532b392ed7758e0baeac22edadcee31a03" +dependencies = [ + "include-flate-codegen", + "proc-macro-hack", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "is_executable" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8" +dependencies = [ + "winapi", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "java-properties" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37bf6f484471c451f2b51eabd9e66b3fa7274550c5ec4b6c3d6070840945117f" +dependencies = [ + "encoding_rs", + "lazy_static", + "regex", +] + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2735847566356cd2179a2a38264839308f7079fa96e6bd5a42d740460e003c56" +dependencies = [ + "crossbeam", + "rayon", +] + +[[package]] +name = "ksud" +version = "0.1.0" +dependencies = [ + "android-properties", + "android_logger", + "anyhow", + "chrono", + "clap", + "const_format", + "derive-new", + "encoding_rs", + "env_logger 0.11.3", + "extattr", + "getopts", + "hole-punch", + "humansize", + "is_executable", + "java-properties", + "jwalk", + "libc", + "log", + "loopdev", + "nom", + "procfs", + "regex", + "retry", + "rust-embed", + "rustix 0.38.30", + "serde", + "serde_json", + "sha1", + "sha256", + "tempdir", + "which", + "zip 2.1.0", + "zip-extensions", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libflate" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18" +dependencies = [ + "adler32", + "crc32fast", + "libflate_lz77", +] + +[[package]] +name = "libflate_lz77" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" +dependencies = [ + "rle-decode-fast", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "loopdev" +version = "0.5.0" +source = "git+https://github.com/Kernel-SU/loopdev#7a921f8d966477a645b1188732fac486c71a68ef" +dependencies = [ + "errno 0.2.8", + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "procfs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" +dependencies = [ + "bitflags 2.5.0", + "chrono", + "flate2", + "hex", + "lazy_static", + "procfs-core", + "rustix 0.38.34", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags 2.5.0", + "chrono", + "hex", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "retry" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + +[[package]] +name = "rust-embed" +version = "8.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" +dependencies = [ + "include-flate", + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9f96e283ec64401f30d3df8ee2aaeb2561f34c824381efa24a35f79bf40ee4" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.65", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c74a686185620830701348de757fd36bef4aa9680fd23c49fc539ddcc1af32" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.30" +source = "git+https://github.com/Kernel-SU/rustix.git?branch=main#0e270bce2d97466be6b987bb5f7ea5b1e8d84969" +dependencies = [ + "bitflags 2.5.0", + "errno 0.3.9", + "itoa", + "libc", + "linux-raw-sys", + "once_cell", + "windows-sys", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno 0.3.9", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha256" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "tokio" +version = "1.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +dependencies = [ + "backtrace", + "bytes", + "pin-project-lite", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.65", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "which" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" +dependencies = [ + "either", + "home", + "rustix 0.38.34", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd 0.11.2+zstd.1.5.2", +] + +[[package]] +name = "zip" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2568cd0f20e86cd9a7349fe05178f7bd22f22724678448ae5a9bac266df2689" +dependencies = [ + "arbitrary", + "bzip2", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "indexmap", + "memchr", + "thiserror", + "time", + "zopfli", + "zstd 0.13.1", +] + +[[package]] +name = "zip-extensions" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecf62554c4ff96bce01a7ef123d160c3ffe9180638820f8b4d545c65b221b8c" +dependencies = [ + "zip 0.6.6", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe 7.1.0", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/KernelSU/userspace/ksud/Cargo.toml b/KernelSU/userspace/ksud/Cargo.toml new file mode 100644 index 000000000000..d031660307dc --- /dev/null +++ b/KernelSU/userspace/ksud/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "ksud" +version = "0.1.0" +edition = "2021" +rust-version = "1.77.2" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1" +clap = { version = "4", features = ["derive"] } +const_format = "0.2" +zip = { version = "2", features = [ + "deflate", + "deflate64", + "bzip2", + "time", + "zstd", +], default-features = false } +zip-extensions = "0.6" +java-properties = "2" +log = "0.4" +env_logger = { version = "0.11", default-features = false } +serde = { version = "1" } +serde_json = "1" +regex = "1" +encoding_rs = "0.8" +retry = "2" +humansize = "2" +libc = "0.2" +extattr = "1" +jwalk = "0.8" +is_executable = "1" +nom = "7" +derive-new = "0.6" +rust-embed = { version = "8", features = [ + "debug-embed", + "compression", # must clean build after updating binaries +] } +which = "6" +getopts = "0.2" +sha256 = "1" +sha1 = "0.10" +tempdir = "0.3" +chrono = "0.4" +hole-punch = { git = "https://github.com/tiann/hole-punch" } + +[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] +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"] } +procfs = "0.16" +loopdev = { git = "https://github.com/Kernel-SU/loopdev" } + +[target.'cfg(target_os = "android")'.dependencies] +android_logger = "0.13" + +[profile.release] +strip = true +opt-level = "z" +lto = true \ No newline at end of file diff --git a/KernelSU/userspace/ksud/bin/.gitignore b/KernelSU/userspace/ksud/bin/.gitignore new file mode 100644 index 000000000000..1464b7ed27b3 --- /dev/null +++ b/KernelSU/userspace/ksud/bin/.gitignore @@ -0,0 +1 @@ +**/*.ko \ No newline at end of file diff --git a/KernelSU/userspace/ksud/bin/aarch64/bootctl b/KernelSU/userspace/ksud/bin/aarch64/bootctl new file mode 100644 index 000000000000..cf5c61368467 Binary files /dev/null and b/KernelSU/userspace/ksud/bin/aarch64/bootctl differ diff --git a/KernelSU/userspace/ksud/bin/aarch64/busybox b/KernelSU/userspace/ksud/bin/aarch64/busybox new file mode 100755 index 000000000000..1ddf3f73aa53 Binary files /dev/null and b/KernelSU/userspace/ksud/bin/aarch64/busybox differ diff --git a/KernelSU/userspace/ksud/bin/aarch64/ksuinit b/KernelSU/userspace/ksud/bin/aarch64/ksuinit new file mode 100755 index 000000000000..8d3ba057f9de Binary files /dev/null and b/KernelSU/userspace/ksud/bin/aarch64/ksuinit differ diff --git a/KernelSU/userspace/ksud/bin/aarch64/resetprop b/KernelSU/userspace/ksud/bin/aarch64/resetprop new file mode 100644 index 000000000000..1155e5f49a3e Binary files /dev/null and b/KernelSU/userspace/ksud/bin/aarch64/resetprop differ diff --git a/KernelSU/userspace/ksud/bin/x86_64/busybox b/KernelSU/userspace/ksud/bin/x86_64/busybox new file mode 100755 index 000000000000..2557c61d3d43 Binary files /dev/null and b/KernelSU/userspace/ksud/bin/x86_64/busybox differ diff --git a/KernelSU/userspace/ksud/bin/x86_64/ksuinit b/KernelSU/userspace/ksud/bin/x86_64/ksuinit new file mode 100755 index 000000000000..9517baf915bf Binary files /dev/null and b/KernelSU/userspace/ksud/bin/x86_64/ksuinit differ diff --git a/KernelSU/userspace/ksud/bin/x86_64/resetprop b/KernelSU/userspace/ksud/bin/x86_64/resetprop new file mode 100644 index 000000000000..dc4d9910e9ed Binary files /dev/null and b/KernelSU/userspace/ksud/bin/x86_64/resetprop differ diff --git a/KernelSU/userspace/ksud/build.rs b/KernelSU/userspace/ksud/build.rs new file mode 100644 index 000000000000..021418acf722 --- /dev/null +++ b/KernelSU/userspace/ksud/build.rs @@ -0,0 +1,56 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::Path; +use std::process::Command; + +fn get_git_version() -> Result<(u32, String), std::io::Error> { + let output = Command::new("git") + .args(["rev-list", "--count", "HEAD"]) + .output()?; + + let output = output.stdout; + let version_code = String::from_utf8(output).expect("Failed to read git count stdout"); + let version_code: u32 = version_code + .trim() + .parse() + .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "Failed to parse git count"))?; + let version_code = 10000 + 200 + version_code; // For historical reasons + + let version_name = String::from_utf8( + Command::new("git") + .args(["describe", "--tags", "--always"]) + .output()? + .stdout, + ) + .map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to read git describe stdout", + ) + })?; + let version_name = version_name.trim_start_matches('v').to_string(); + Ok((version_code, version_name)) +} + +fn main() { + let (code, name) = match get_git_version() { + Ok((code, name)) => (code, name), + Err(_) => { + // show warning if git is not installed + println!("cargo:warning=Failed to get git version, using 0.0.0"); + (0, "0.0.0".to_string()) + } + }; + let out_dir = env::var("OUT_DIR").expect("Failed to get $OUT_DIR"); + let out_dir = Path::new(&out_dir); + File::create(Path::new(out_dir).join("VERSION_CODE")) + .expect("Failed to create VERSION_CODE") + .write_all(code.to_string().as_bytes()) + .expect("Failed to write VERSION_CODE"); + + File::create(Path::new(out_dir).join("VERSION_NAME")) + .expect("Failed to create VERSION_NAME") + .write_all(name.trim().as_bytes()) + .expect("Failed to write VERSION_NAME"); +} diff --git a/KernelSU/userspace/ksud/src/apk_sign.rs b/KernelSU/userspace/ksud/src/apk_sign.rs new file mode 100644 index 000000000000..6d047ccad9be --- /dev/null +++ b/KernelSU/userspace/ksud/src/apk_sign.rs @@ -0,0 +1,115 @@ +use anyhow::{ensure, Result}; +use std::io::{Read, Seek, SeekFrom}; + +pub fn get_apk_signature(apk: &str) -> Result<(u32, String)> { + let mut buffer = [0u8; 0x10]; + let mut size4 = [0u8; 4]; + let mut size8 = [0u8; 8]; + let mut size_of_block = [0u8; 8]; + + let mut f = std::fs::File::open(apk)?; + + let mut i = 0; + loop { + let mut n = [0u8; 2]; + f.seek(SeekFrom::End(-i - 2))?; + f.read_exact(&mut n)?; + + let n = u16::from_le_bytes(n); + if i64::from(n) == i { + f.seek(SeekFrom::Current(-22))?; + f.read_exact(&mut size4)?; + + if u32::from_le_bytes(size4) ^ 0xcafe_babe_u32 == 0xccfb_f1ee_u32 { + if i > 0 { + println!("warning: comment length is {i}"); + } + break; + } + } + + ensure!(n != 0xffff, "not a zip file"); + + i += 1; + } + + f.seek(SeekFrom::Current(12))?; + // offset + f.read_exact(&mut size4)?; + f.seek(SeekFrom::Start(u64::from(u32::from_le_bytes(size4)) - 0x18))?; + + f.read_exact(&mut size8)?; + f.read_exact(&mut buffer)?; + + ensure!(&buffer == b"APK Sig Block 42", "Can not found sig block"); + + let pos = u64::from(u32::from_le_bytes(size4)) - (u64::from_le_bytes(size8) + 0x8); + f.seek(SeekFrom::Start(pos))?; + f.read_exact(&mut size_of_block)?; + + ensure!(size_of_block == size8, "not a signed apk"); + + let mut v2_signing: Option<(u32, String)> = None; + let mut v3_signing_exist = false; + let mut v3_1_signing_exist = false; + + loop { + let mut id = [0u8; 4]; + let mut offset = 4u32; + + f.read_exact(&mut size8)?; // sequence length + if size8 == size_of_block { + break; + } + + f.read_exact(&mut id)?; // id + + let id = u32::from_le_bytes(id); + if id == 0x7109_871a_u32 { + v2_signing = Some(calc_cert_sha256(&mut f, &mut size4, &mut offset)?); + } else if id == 0xf053_68c0_u32 { + // v3 signature scheme + v3_signing_exist = true; + } else if id == 0x1b93_ad61_u32 { + // v3.1 signature scheme: credits to vvb2060 + v3_1_signing_exist = true; + } + + f.seek(SeekFrom::Current( + i64::from_le_bytes(size8) - i64::from(offset), + ))?; + } + + if v3_signing_exist || v3_1_signing_exist { + return Err(anyhow::anyhow!("Unexpected v3 signature found!",)); + } + + v2_signing.ok_or(anyhow::anyhow!("No signature found!")) +} + +fn calc_cert_sha256( + f: &mut std::fs::File, + size4: &mut [u8; 4], + offset: &mut u32, +) -> Result<(u32, String)> { + f.read_exact(size4)?; // signer-sequence length + f.read_exact(size4)?; // signer length + f.read_exact(size4)?; // signed data length + *offset += 0x4 * 3; + + f.read_exact(size4)?; // digests-sequence length + let pos = u32::from_le_bytes(*size4); // skip digests + f.seek(SeekFrom::Current(i64::from(pos)))?; + *offset += 0x4 + pos; + + f.read_exact(size4)?; // certificates length + f.read_exact(size4)?; // certificate length + *offset += 0x4 * 2; + + let cert_len = u32::from_le_bytes(*size4); + let mut cert: Vec = vec![0; cert_len as usize]; + f.read_exact(&mut cert)?; + *offset += cert_len; + + Ok((cert_len, sha256::digest(&cert))) +} diff --git a/KernelSU/userspace/ksud/src/assets.rs b/KernelSU/userspace/ksud/src/assets.rs new file mode 100644 index 000000000000..e8255c8204a7 --- /dev/null +++ b/KernelSU/userspace/ksud/src/assets.rs @@ -0,0 +1,50 @@ +use anyhow::Result; +use const_format::concatcp; +use rust_embed::RustEmbed; +use std::path::Path; + +use crate::{defs::BINARY_DIR, utils}; + +pub const RESETPROP_PATH: &str = concatcp!(BINARY_DIR, "resetprop"); +pub const BUSYBOX_PATH: &str = concatcp!(BINARY_DIR, "busybox"); +pub const BOOTCTL_PATH: &str = concatcp!(BINARY_DIR, "bootctl"); + +#[cfg(all(target_arch = "x86_64", target_os = "android"))] +#[derive(RustEmbed)] +#[folder = "bin/x86_64"] +struct Asset; + +// IF NOT x86_64 ANDROID, ie. macos, linux, windows, always use aarch64 +#[cfg(not(all(target_arch = "x86_64", target_os = "android")))] +#[derive(RustEmbed)] +#[folder = "bin/aarch64"] +struct Asset; + +pub fn ensure_binaries(ignore_if_exist: bool) -> Result<()> { + for file in Asset::iter() { + if file == "ksuinit" || file.ends_with(".ko") { + // don't extract ksuinit and kernel modules + continue; + } + let asset = Asset::get(&file).ok_or(anyhow::anyhow!("asset not found: {}", file))?; + utils::ensure_binary(format!("{BINARY_DIR}{file}"), &asset.data, ignore_if_exist)? + } + Ok(()) +} + +pub fn copy_assets_to_file(name: &str, dst: impl AsRef) -> Result<()> { + let asset = Asset::get(name).ok_or(anyhow::anyhow!("asset not found: {}", name))?; + std::fs::write(dst, asset.data)?; + Ok(()) +} + +pub fn list_supported_kmi() -> Result> { + let mut list = Vec::new(); + for file in Asset::iter() { + // kmi_name = "xxx_kernelsu.ko" + if let Some(kmi) = file.strip_suffix("_kernelsu.ko") { + list.push(kmi.to_string()); + } + } + Ok(list) +} diff --git a/KernelSU/userspace/ksud/src/banner b/KernelSU/userspace/ksud/src/banner new file mode 100644 index 000000000000..6087569c3f04 --- /dev/null +++ b/KernelSU/userspace/ksud/src/banner @@ -0,0 +1,5 @@ + _ __ _ ____ _ _ + | |/ /___ _ __ _ __ ___| / ___|| | | | + | ' // _ \ '__| '_ \ / _ \ \___ \| | | | + | . \ __/ | | | | | __/ |___) | |_| | + |_|\_\___|_| |_| |_|\___|_|____/ \___/ diff --git a/KernelSU/userspace/ksud/src/boot_patch.rs b/KernelSU/userspace/ksud/src/boot_patch.rs new file mode 100644 index 000000000000..c2661c68fc76 --- /dev/null +++ b/KernelSU/userspace/ksud/src/boot_patch.rs @@ -0,0 +1,632 @@ +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; +use std::process::Stdio; + +use anyhow::anyhow; +use anyhow::bail; +use anyhow::ensure; +use anyhow::Context; +use anyhow::Result; +use which::which; + +use crate::defs; +use crate::defs::BACKUP_FILENAME; +use crate::defs::{KSU_BACKUP_DIR, KSU_BACKUP_FILE_PREFIX}; +use crate::{assets, utils}; + +#[cfg(target_os = "android")] +fn ensure_gki_kernel() -> Result<()> { + let version = get_kernel_version()?; + let is_gki = version.0 == 5 && version.1 >= 10 || version.2 > 5; + ensure!(is_gki, "only support GKI kernel"); + Ok(()) +} + +#[cfg(target_os = "android")] +pub fn get_kernel_version() -> Result<(i32, i32, i32)> { + use regex::Regex; + let uname = rustix::system::uname(); + let version = uname.release().to_string_lossy(); + let re = Regex::new(r"(\d+)\.(\d+)\.(\d+)")?; + if let Some(captures) = re.captures(&version) { + let major = captures + .get(1) + .and_then(|m| m.as_str().parse::().ok()) + .ok_or_else(|| anyhow!("Major version parse error"))?; + let minor = captures + .get(2) + .and_then(|m| m.as_str().parse::().ok()) + .ok_or_else(|| anyhow!("Minor version parse error"))?; + let patch = captures + .get(3) + .and_then(|m| m.as_str().parse::().ok()) + .ok_or_else(|| anyhow!("Patch version parse error"))?; + Ok((major, minor, patch)) + } else { + Err(anyhow!("Invalid kernel version string")) + } +} + +#[cfg(target_os = "android")] +fn parse_kmi(version: &str) -> Result { + use regex::Regex; + let re = Regex::new(r"(.* )?(\d+\.\d+)(\S+)?(android\d+)(.*)")?; + let cap = re + .captures(version) + .ok_or_else(|| anyhow::anyhow!("Failed to get KMI from boot/modules"))?; + let android_version = cap.get(4).map_or("", |m| m.as_str()); + let kernel_version = cap.get(2).map_or("", |m| m.as_str()); + Ok(format!("{android_version}-{kernel_version}")) +} + +#[cfg(target_os = "android")] +fn parse_kmi_from_uname() -> Result { + let uname = rustix::system::uname(); + let version = uname.release().to_string_lossy(); + parse_kmi(&version) +} + +#[cfg(target_os = "android")] +fn parse_kmi_from_modules() -> Result { + use std::io::BufRead; + // find a *.ko in /vendor/lib/modules + let modfile = std::fs::read_dir("/vendor/lib/modules")? + .filter_map(Result::ok) + .find(|entry| entry.path().extension().map_or(false, |ext| ext == "ko")) + .map(|entry| entry.path()) + .ok_or_else(|| anyhow!("No kernel module found"))?; + let output = Command::new("modinfo").arg(modfile).output()?; + for line in output.stdout.lines().map_while(Result::ok) { + if line.starts_with("vermagic") { + return parse_kmi(&line); + } + } + anyhow::bail!("Parse KMI from modules failed") +} + +#[cfg(target_os = "android")] +pub fn get_current_kmi() -> Result { + parse_kmi_from_uname().or_else(|_| parse_kmi_from_modules()) +} + +#[cfg(not(target_os = "android"))] +pub fn get_current_kmi() -> Result { + bail!("Unsupported platform") +} + +fn do_cpio_cmd(magiskboot: &Path, workdir: &Path, cmd: &str) -> Result<()> { + let status = Command::new(magiskboot) + .current_dir(workdir) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .arg("cpio") + .arg("ramdisk.cpio") + .arg(cmd) + .status()?; + + ensure!(status.success(), "magiskboot cpio {} failed", cmd); + Ok(()) +} + +fn is_magisk_patched(magiskboot: &Path, workdir: &Path) -> Result { + let status = Command::new(magiskboot) + .current_dir(workdir) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .args(["cpio", "ramdisk.cpio", "test"]) + .status()?; + + // 0: stock, 1: magisk + Ok(status.code() == Some(1)) +} + +fn is_kernelsu_patched(magiskboot: &Path, workdir: &Path) -> Result { + let status = Command::new(magiskboot) + .current_dir(workdir) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .args(["cpio", "ramdisk.cpio", "exists kernelsu.ko"]) + .status()?; + + Ok(status.success()) +} + +fn dd, Q: AsRef>(ifile: P, ofile: Q) -> Result<()> { + let status = Command::new("dd") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .arg(format!("if={}", ifile.as_ref().display())) + .arg(format!("of={}", ofile.as_ref().display())) + .status()?; + ensure!( + status.success(), + "dd if={:?} of={:?} failed", + ifile.as_ref(), + ofile.as_ref() + ); + Ok(()) +} + +pub fn restore( + image: Option, + magiskboot_path: Option, + flash: bool, +) -> Result<()> { + let tmpdir = tempdir::TempDir::new("KernelSU").context("create temp dir failed")?; + let workdir = tmpdir.path(); + let magiskboot = find_magiskboot(magiskboot_path, workdir)?; + + let kmi = get_current_kmi().unwrap_or_else(|_| String::from("")); + + let skip_init = kmi.starts_with("android12-"); + + let (bootimage, bootdevice) = find_boot_image(&image, skip_init, false, false, workdir)?; + + println!("- Unpacking boot image"); + let status = Command::new(&magiskboot) + .current_dir(workdir) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .arg("unpack") + .arg(bootimage.display().to_string()) + .status()?; + ensure!(status.success(), "magiskboot unpack failed"); + + let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workdir)?; + ensure!(is_kernelsu_patched, "boot image is not patched by KernelSU"); + + let mut new_boot = None; + let mut from_backup = false; + + #[cfg(target_os = "android")] + if do_cpio_cmd(&magiskboot, workdir, &format!("exists {BACKUP_FILENAME}")).is_ok() { + do_cpio_cmd( + &magiskboot, + workdir, + &format!("extract {0} {0}", BACKUP_FILENAME), + )?; + let sha = std::fs::read(workdir.join(BACKUP_FILENAME))?; + let sha = String::from_utf8(sha)?; + let sha = sha.trim(); + let backup_path = + PathBuf::from(KSU_BACKUP_DIR).join(format!("{KSU_BACKUP_FILE_PREFIX}{sha}")); + if backup_path.is_file() { + new_boot = Some(backup_path); + from_backup = true; + } else { + println!("- Warning: no backup {backup_path:?} found!"); + } + + if let Err(e) = clean_backup(sha) { + println!("- Warning: Cleanup backup image failed: {e}"); + } + } else { + println!("- Backup info is absent!"); + } + + if new_boot.is_none() { + // remove kernelsu.ko + do_cpio_cmd(&magiskboot, workdir, "rm kernelsu.ko")?; + + // if init.real exists, restore it + let status = do_cpio_cmd(&magiskboot, workdir, "exists init.real").is_ok(); + if status { + do_cpio_cmd(&magiskboot, workdir, "mv init.real init")?; + } else { + let ramdisk = workdir.join("ramdisk.cpio"); + std::fs::remove_file(ramdisk)?; + } + + println!("- Repacking boot image"); + let status = Command::new(&magiskboot) + .current_dir(workdir) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .arg("repack") + .arg(bootimage.display().to_string()) + .status()?; + ensure!(status.success(), "magiskboot repack failed"); + new_boot = Some(workdir.join("new-boot.img")); + } + + let new_boot = new_boot.unwrap(); + + if image.is_some() { + // if image is specified, write to output file + let output_dir = std::env::current_dir()?; + let now = chrono::Utc::now(); + let output_image = output_dir.join(format!( + "kernelsu_restore_{}.img", + now.format("%Y%m%d_%H%M%S") + )); + + if from_backup || std::fs::rename(&new_boot, &output_image).is_err() { + std::fs::copy(&new_boot, &output_image).context("copy out new boot failed")?; + } + println!("- Output file is written to"); + println!("- {}", output_image.display().to_string().trim_matches('"')); + } + if flash { + if from_backup { + println!("- Flashing new boot image from {}", new_boot.display()); + } else { + println!("- Flashing new boot image"); + } + flash_boot(&bootdevice, new_boot)?; + } + println!("- Done!"); + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +pub fn patch( + image: Option, + kernel: Option, + kmod: Option, + init: Option, + ota: bool, + flash: bool, + out: Option, + magiskboot: Option, + kmi: Option, +) -> Result<()> { + let result = do_patch(image, kernel, kmod, init, ota, flash, out, magiskboot, kmi); + if let Err(ref e) = result { + println!("- Install Error: {e}"); + } + result +} + +#[allow(clippy::too_many_arguments)] +fn do_patch( + image: Option, + kernel: Option, + kmod: Option, + init: Option, + ota: bool, + flash: bool, + out: Option, + magiskboot_path: Option, + kmi: Option, +) -> Result<()> { + println!(include_str!("banner")); + + let patch_file = image.is_some(); + + #[cfg(target_os = "android")] + if !patch_file { + ensure_gki_kernel()?; + } + + let is_replace_kernel = kernel.is_some(); + + if is_replace_kernel { + ensure!( + init.is_none() && kmod.is_none(), + "init and module must not be specified." + ); + } + + let tmpdir = tempdir::TempDir::new("KernelSU").context("create temp dir failed")?; + let workdir = tmpdir.path(); + + let kmi = if let Some(kmi) = kmi { + kmi + } else { + get_current_kmi().context("Unknown KMI, please choose LKM manually")? + }; + + let skip_init = kmi.starts_with("android12-"); + + let (bootimage, bootdevice) = + find_boot_image(&image, skip_init, ota, is_replace_kernel, workdir)?; + + let bootimage = bootimage.display().to_string(); + + // try extract magiskboot/bootctl + let _ = assets::ensure_binaries(false); + + // extract magiskboot + let magiskboot = find_magiskboot(magiskboot_path, workdir)?; + + if let Some(kernel) = kernel { + std::fs::copy(kernel, workdir.join("kernel")).context("copy kernel from failed")?; + } + + println!("- Preparing assets"); + + let kmod_file = workdir.join("kernelsu.ko"); + if let Some(kmod) = kmod { + std::fs::copy(kmod, kmod_file).context("copy kernel module failed")?; + } else { + // If kmod is not specified, extract from assets + println!("- KMI: {kmi}"); + let name = format!("{kmi}_kernelsu.ko"); + assets::copy_assets_to_file(&name, kmod_file) + .with_context(|| format!("Failed to copy {name}"))?; + }; + + let init_file = workdir.join("init"); + if let Some(init) = init { + std::fs::copy(init, init_file).context("copy init failed")?; + } else { + assets::copy_assets_to_file("ksuinit", init_file).context("copy ksuinit failed")?; + } + + // magiskboot unpack boot.img + // magiskboot cpio ramdisk.cpio 'cp init init.real' + // magiskboot cpio ramdisk.cpio 'add 0755 ksuinit init' + // magiskboot cpio ramdisk.cpio 'add 0755 kernelsu.ko' + + println!("- Unpacking boot image"); + let status = Command::new(&magiskboot) + .current_dir(workdir) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .arg("unpack") + .arg(&bootimage) + .status()?; + ensure!(status.success(), "magiskboot unpack failed"); + + let no_ramdisk = !workdir.join("ramdisk.cpio").exists(); + let is_magisk_patched = is_magisk_patched(&magiskboot, workdir)?; + ensure!( + no_ramdisk || !is_magisk_patched, + "Cannot work with Magisk patched image" + ); + + println!("- Adding KernelSU LKM"); + let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workdir)?; + + let mut need_backup = false; + if !is_kernelsu_patched { + // kernelsu.ko is not exist, backup init if necessary + let status = do_cpio_cmd(&magiskboot, workdir, "exists init"); + if status.is_ok() { + do_cpio_cmd(&magiskboot, workdir, "mv init init.real")?; + } + + need_backup = flash; + } + + do_cpio_cmd(&magiskboot, workdir, "add 0755 init init")?; + do_cpio_cmd(&magiskboot, workdir, "add 0755 kernelsu.ko kernelsu.ko")?; + + #[cfg(target_os = "android")] + if need_backup { + if let Err(e) = do_backup(&magiskboot, workdir, &bootimage) { + println!("- Backup stock image failed: {e}"); + } + } + + println!("- Repacking boot image"); + // magiskboot repack boot.img + let status = Command::new(&magiskboot) + .current_dir(workdir) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .arg("repack") + .arg(&bootimage) + .status()?; + ensure!(status.success(), "magiskboot repack failed"); + let new_boot = workdir.join("new-boot.img"); + + if patch_file { + // if image is specified, write to output file + let output_dir = out.unwrap_or(std::env::current_dir()?); + let now = chrono::Utc::now(); + let output_image = output_dir.join(format!( + "kernelsu_patched_{}.img", + now.format("%Y%m%d_%H%M%S") + )); + + if std::fs::rename(&new_boot, &output_image).is_err() { + std::fs::copy(&new_boot, &output_image).context("copy out new boot failed")?; + } + println!("- Output file is written to"); + println!("- {}", output_image.display().to_string().trim_matches('"')); + } + + if flash { + println!("- Flashing new boot image"); + flash_boot(&bootdevice, new_boot)?; + + if ota { + post_ota()?; + } + } + + println!("- Done!"); + Ok(()) +} + +#[cfg(target_os = "android")] +fn calculate_sha1(file_path: impl AsRef) -> Result { + use sha1::Digest; + use std::io::Read; + let mut file = std::fs::File::open(file_path.as_ref())?; + let mut hasher = sha1::Sha1::new(); + let mut buffer = [0; 1024]; + + loop { + let n = file.read(&mut buffer)?; + if n == 0 { + break; + } + hasher.update(&buffer[..n]); + } + + let result = hasher.finalize(); + Ok(format!("{:x}", result)) +} + +#[cfg(target_os = "android")] +fn do_backup(magiskboot: &Path, workdir: &Path, image: &str) -> Result<()> { + let sha1 = calculate_sha1(image)?; + let filename = format!("{KSU_BACKUP_FILE_PREFIX}{sha1}"); + + println!("- Backup stock boot image"); + // magiskboot cpio ramdisk.cpio 'add 0755 $BACKUP_FILENAME' + let target = format!("{KSU_BACKUP_DIR}{filename}"); + std::fs::copy(image, &target).with_context(|| format!("backup to {target}"))?; + std::fs::write(workdir.join(BACKUP_FILENAME), sha1.as_bytes()).context("write sha1")?; + do_cpio_cmd( + magiskboot, + workdir, + &format!("add 0755 {0} {0}", BACKUP_FILENAME), + )?; + println!("- Stock image has been backup to"); + println!("- {target}"); + Ok(()) +} + +#[cfg(target_os = "android")] +fn clean_backup(sha1: &str) -> Result<()> { + println!("- Clean up backup"); + let backup_name = format!("{}{}", KSU_BACKUP_FILE_PREFIX, sha1); + let dir = std::fs::read_dir(defs::KSU_BACKUP_DIR)?; + for entry in dir.flatten() { + let path = entry.path(); + if !path.is_file() { + continue; + } + if let Some(name) = path.file_name() { + let name = name.to_string_lossy().to_string(); + if name != backup_name + && name.starts_with(KSU_BACKUP_FILE_PREFIX) + && std::fs::remove_file(path).is_ok() + { + println!("- removed {name}"); + } + } + } + Ok(()) +} + +fn flash_boot(bootdevice: &Option, new_boot: PathBuf) -> Result<()> { + let Some(bootdevice) = bootdevice else { + bail!("boot device not found") + }; + let status = Command::new("blockdev") + .arg("--setrw") + .arg(bootdevice) + .status()?; + ensure!(status.success(), "set boot device rw failed"); + dd(new_boot, bootdevice).context("flash boot failed")?; + Ok(()) +} + +fn find_magiskboot(magiskboot_path: Option, workdir: &Path) -> Result { + let magiskboot = { + if which("magiskboot").is_ok() { + let _ = assets::ensure_binaries(true); + "magiskboot".into() + } else { + // magiskboot is not in $PATH, use builtin or specified one + let magiskboot = if let Some(magiskboot_path) = magiskboot_path { + std::fs::canonicalize(magiskboot_path)? + } else { + let magiskboot_path = workdir.join("magiskboot"); + assets::copy_assets_to_file("magiskboot", &magiskboot_path) + .context("copy magiskboot failed")?; + magiskboot_path + }; + ensure!(magiskboot.exists(), "{magiskboot:?} is not exist"); + #[cfg(unix)] + let _ = std::fs::set_permissions(&magiskboot, std::fs::Permissions::from_mode(0o755)); + magiskboot + } + }; + Ok(magiskboot) +} + +fn find_boot_image( + image: &Option, + skip_init: bool, + ota: bool, + is_replace_kernel: bool, + workdir: &Path, +) -> Result<(PathBuf, Option)> { + let bootimage; + let mut bootdevice = None; + if let Some(ref image) = *image { + ensure!(image.exists(), "boot image not found"); + bootimage = std::fs::canonicalize(image)?; + } else { + if cfg!(not(target_os = "android")) { + println!("- Current OS is not android, refusing auto bootimage/bootdevice detection"); + bail!("please specify a boot image"); + } + let mut slot_suffix = + utils::getprop("ro.boot.slot_suffix").unwrap_or_else(|| String::from("")); + + if !slot_suffix.is_empty() && ota { + if slot_suffix == "_a" { + slot_suffix = "_b".to_string() + } else { + slot_suffix = "_a".to_string() + } + }; + + let init_boot_exist = + Path::new(&format!("/dev/block/by-name/init_boot{slot_suffix}")).exists(); + let boot_partition = if !is_replace_kernel && init_boot_exist && !skip_init { + format!("/dev/block/by-name/init_boot{slot_suffix}") + } else { + format!("/dev/block/by-name/boot{slot_suffix}") + }; + + println!("- Bootdevice: {boot_partition}"); + let tmp_boot_path = workdir.join("boot.img"); + + dd(&boot_partition, &tmp_boot_path)?; + + ensure!(tmp_boot_path.exists(), "boot image not found"); + + bootimage = tmp_boot_path; + bootdevice = Some(boot_partition); + }; + Ok((bootimage, bootdevice)) +} + +fn post_ota() -> Result<()> { + use crate::defs::ADB_DIR; + use assets::BOOTCTL_PATH; + let status = Command::new(BOOTCTL_PATH).arg("hal-info").status()?; + if !status.success() { + return Ok(()); + } + + let current_slot = Command::new(BOOTCTL_PATH) + .arg("get-current-slot") + .output()? + .stdout; + let current_slot = String::from_utf8(current_slot)?; + let current_slot = current_slot.trim(); + let target_slot = if current_slot == "0" { 1 } else { 0 }; + + Command::new(BOOTCTL_PATH) + .arg(format!("set-active-boot-slot {target_slot}")) + .status()?; + + let post_fs_data = std::path::Path::new(ADB_DIR).join("post-fs-data.d"); + utils::ensure_dir_exists(&post_fs_data)?; + let post_ota_sh = post_fs_data.join("post_ota.sh"); + + let sh_content = format!( + r###" +{BOOTCTL_PATH} mark-boot-successful +rm -f {BOOTCTL_PATH} +rm -f /data/adb/post-fs-data.d/post_ota.sh +"### + ); + + std::fs::write(&post_ota_sh, sh_content)?; + #[cfg(unix)] + std::fs::set_permissions(post_ota_sh, std::fs::Permissions::from_mode(0o755))?; + + Ok(()) +} diff --git a/KernelSU/userspace/ksud/src/cli.rs b/KernelSU/userspace/ksud/src/cli.rs new file mode 100644 index 000000000000..cd96a360f346 --- /dev/null +++ b/KernelSU/userspace/ksud/src/cli.rs @@ -0,0 +1,392 @@ +use anyhow::{Ok, Result}; +use clap::Parser; +use std::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}; + +/// KernelSU userspace cli +#[derive(Parser, Debug)] +#[command(author, version = defs::VERSION_NAME, about, long_about = None)] +struct Args { + #[command(subcommand)] + command: Commands, +} + +#[derive(clap::Subcommand, Debug)] +enum Commands { + /// Manage KernelSU modules + Module { + #[command(subcommand)] + command: Module, + }, + + /// Trigger `post-fs-data` event + PostFsData, + + /// Trigger `service` event + Services, + + /// Trigger `boot-complete` event + BootCompleted, + + /// Install KernelSU userspace component to system + Install { + #[arg(long, default_value = None)] + magiskboot: Option, + }, + + /// Uninstall KernelSU modules and itself(LKM Only) + Uninstall { + /// magiskboot path, if not specified, will search from $PATH + #[arg(long, default_value = None)] + magiskboot: Option, + }, + + /// SELinux policy Patch tool + Sepolicy { + #[command(subcommand)] + command: Sepolicy, + }, + + /// Manage App Profiles + Profile { + #[command(subcommand)] + command: Profile, + }, + + /// Patch boot or init_boot images to apply KernelSU + BootPatch { + /// boot image path, if not specified, will try to find the boot image automatically + #[arg(short, long)] + boot: Option, + + /// kernel image path to replace + #[arg(short, long)] + kernel: Option, + + /// LKM module path to replace, if not specified, will use the builtin one + #[arg(short, long)] + module: Option, + + /// init to be replaced + #[arg(short, long, requires("module"))] + init: Option, + + /// will use another slot when boot image is not specified + #[arg(short = 'u', long, default_value = "false")] + ota: bool, + + /// Flash it to boot partition after patch + #[arg(short, long, default_value = "false")] + flash: bool, + + /// output path, if not specified, will use current directory + #[arg(short, long, default_value = None)] + out: Option, + + /// magiskboot path, if not specified, will search from $PATH + #[arg(long, default_value = None)] + magiskboot: Option, + + /// KMI version, if specified, will use the specified KMI + #[arg(long, default_value = None)] + kmi: Option, + }, + + /// Restore boot or init_boot images patched by KernelSU + BootRestore { + /// boot image path, if not specified, will try to find the boot image automatically + #[arg(short, long)] + boot: Option, + + /// Flash it to boot partition after patch + #[arg(short, long, default_value = "false")] + flash: bool, + + /// magiskboot path, if not specified, will search from $PATH + #[arg(long, default_value = None)] + magiskboot: Option, + }, + + /// Show boot information + BootInfo { + #[command(subcommand)] + command: BootInfo, + }, + /// For developers + Debug { + #[command(subcommand)] + command: Debug, + }, +} + +#[derive(clap::Subcommand, Debug)] +enum BootInfo { + /// show current kmi version + CurrentKmi, + + /// show supported kmi versions + SupportedKmi, +} + +#[derive(clap::Subcommand, Debug)] +enum Debug { + /// Set the manager app, kernel CONFIG_KSU_DEBUG should be enabled. + SetManager { + /// manager package name + #[arg(default_value_t = String::from("me.weishu.kernelsu"))] + apk: String, + }, + + /// Get apk size and hash + GetSign { + /// apk path + apk: String, + }, + + /// Root Shell + Su { + /// switch to gloabl mount namespace + #[arg(short, long, default_value = "false")] + global_mnt: bool, + }, + + /// Get kernel version + Version, + + Mount, + + /// Copy sparse file + Xcp { + /// source file + src: String, + /// destination file + dst: String, + /// punch hole + #[arg(short, long, default_value = "false")] + punch_hole: bool, + }, + + /// For testing + Test, +} + +#[derive(clap::Subcommand, Debug)] +enum Sepolicy { + /// Patch sepolicy + Patch { + /// sepolicy statements + sepolicy: String, + }, + + /// Apply sepolicy from file + Apply { + /// sepolicy file path + file: String, + }, + + /// Check if sepolicy statement is supported/valid + Check { + /// sepolicy statements + sepolicy: String, + }, +} + +#[derive(clap::Subcommand, Debug)] +enum Module { + /// Install module + Install { + /// module zip file path + zip: String, + }, + + /// Uninstall module + Uninstall { + /// module id + id: String, + }, + + /// enable module + Enable { + /// module id + id: String, + }, + + /// disable module + Disable { + // module id + id: String, + }, + + /// list all modules + List, + + /// Shrink module image size + Shrink, +} + +#[derive(clap::Subcommand, Debug)] +enum Profile { + /// get root profile's selinux policy of + GetSepolicy { + /// package name + package: String, + }, + + /// set root profile's selinux policy of to + SetSepolicy { + /// package name + package: String, + /// policy statements + policy: String, + }, + + /// get template of + GetTemplate { + /// template id + id: String, + }, + + /// set template of to