commit 62800acc07dec08d2b4db946d95e1ec52ddda744 Author: Dmitry Isaenko Date: Sat May 18 03:37:05 2019 +0300 initial commit diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..893070c --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,21 @@ +pipeline { + agent { + docker { + image 'maven:3-jdk-11' + args '-v /home/docker/jenkins/files/m2:/root/.m2' + } + } + + stages { + stage('Build') { + steps { + sh 'mvn -B -DskipTests clean package' + } + } + } + post { + always { + archiveArtifacts artifacts: 'target/*-jar-with-dependencies.jar', onlyIfSuccessful: true + } + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/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. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +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: + + {project} Copyright (C) {year} {fullname} + 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/README.md b/README.md new file mode 100644 index 0000000..e3debe3 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# konogonka + +Deep WIP multi-tool to work with XCI/NSP/NCA/NRO(?) files + +### License + +[GNU General Public License v3](https://github.com/developersu/konogonka/blob/master/LICENSE) + +### Used libraries & resources +* [Java-XTS-AES](https://github.com/horrorho/Java-XTS-AES) by horrorho with minimal changes. +* [OpenJFX](https://wiki.openjdk.java.net/display/OpenJFX/Main) +* Few icons taken from: [materialdesignicons.com](http://materialdesignicons.com/) + +### System requirements + +JRE/JDK 8u60 or higher. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..88a71f2 --- /dev/null +++ b/pom.xml @@ -0,0 +1,215 @@ + + + 4.0.0 + + loper + konogonka + + konogonka + 0.1-SNAPSHOT + + https://github.com/developersu/${project.name}}/ + + NSP and XCI multitool + + 2019 + + Dmitry Isaenko + https://developersu.blogspot.com/ + + + + + GPLv3 + LICENSE + manual + + + + + + developer.su + Dmitry Isaenko + + Developer + + +3 + https://developersu.blogspot.com/ + + + + + UTF-8 + + + + GitHub + https://github.com/developer_su/${project.artifactId}/issues + + + + + org.openjfx + javafx-controls + 11 + linux + compile + + + org.openjfx + javafx-media + 11 + linux + compile + + + org.openjfx + javafx-fxml + 11 + linux + compile + + + org.openjfx + javafx-graphics + 11 + linux + compile + + + + org.openjfx + javafx-controls + 11 + win + compile + + + org.openjfx + javafx-media + 11 + win + compile + + + org.openjfx + javafx-fxml + 11 + win + compile + + + org.openjfx + javafx-graphics + 11 + win + compile + + + + org.openjfx + javafx-controls + 11 + mac + compile + + + org.openjfx + javafx-media + 11 + mac + compile + + + org.openjfx + javafx-fxml + 11 + mac + compile + + + org.openjfx + javafx-graphics + 11 + mac + compile + + + + org.bouncycastle + bcprov-jdk15on + 1.54 + compile + + + net.jcip + jcip-annotations + 1.0 + compile + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + + maven-jar-plugin + 2.4 + + + default-jar + none + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.0 + + + + konogonka.Main + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/src/main/java/konogonka/AppPreferences.java b/src/main/java/konogonka/AppPreferences.java new file mode 100644 index 0000000..c0287e7 --- /dev/null +++ b/src/main/java/konogonka/AppPreferences.java @@ -0,0 +1,89 @@ +package konogonka; + +import java.util.HashMap; +import java.util.prefs.Preferences; + +public class AppPreferences { + private static final AppPreferences INSTANCE = new AppPreferences(); + public static AppPreferences getInstance() { return INSTANCE; } + + private Preferences preferences; + + private AppPreferences(){ preferences = Preferences.userRoot().node("konogonka"); } + + public void setAll( + String xciHeaderKey, + String headerKey, + String applicationKey0, + String applicationKey1, + String applicationKey2, + String applicationKey3, + String applicationKey4, + String applicationKey5, + String oceanKey0, + String oceanKey1, + String oceanKey2, + String oceanKey3, + String oceanKey4, + String oceanKey5, + String systemKey0, + String systemKey1, + String systemKey2, + String systemKey3, + String systemKey4, + String systemKey5 + ) { + setXciHeaderKey(xciHeaderKey); + setHeaderKey(headerKey); + + setApplicationKey(0, applicationKey0); + setApplicationKey(1, applicationKey1); + setApplicationKey(2, applicationKey2); + setApplicationKey(3, applicationKey3); + setApplicationKey(4, applicationKey4); + setApplicationKey(5, applicationKey5); + + setOceanKey(0, oceanKey0); + setOceanKey(1, oceanKey1); + setOceanKey(2, oceanKey2); + setOceanKey(3, oceanKey3); + setOceanKey(4, oceanKey4); + setOceanKey(5, oceanKey5); + + setSystemKey(0, systemKey0); + setSystemKey(1, systemKey1); + setSystemKey(2, systemKey2); + setSystemKey(3, systemKey3); + setSystemKey(4, systemKey4); + setSystemKey(5, systemKey5); + } + + public String getRecentPath(){return preferences.get("recent_path", System.getProperty("user.home"));} + public void setRecentPath(String path){preferences.put("recent_path", path);} + + /* + public HashMap getKeys(){ + + } + public void setKeys(HashMap keysMap){ + preferences.put("xci_header_key", ); + preferences.put("header_key", ); + } + */ + + public String getXciHeaderKey(){ return preferences.get("xci_header_key", "");} + public void setXciHeaderKey(String key){ preferences.put("xci_header_key", key); } + + public String getHeaderKey(){ return preferences.get("header_key", "");} + public void setHeaderKey(String key){ preferences.put("header_key", key); } + + public String getApplicationKey(int number){ return preferences.get("key_area_key_application_0"+number, "");} + public void setApplicationKey(int number, String key){ preferences.put("key_area_key_application_0"+number, key); } + + public String getOceanKey(int number){ return preferences.get("key_area_key_ocean_0"+number, "");} + public void setOceanKey(int number, String key){ preferences.put("key_area_key_ocean_0"+number, key); } + + public String getSystemKey(int number){ return preferences.get("key_area_key_system_0"+number, "");} + public void setSystemKey(int number, String key){ preferences.put("key_area_key_system_0"+number, key); } + +} diff --git a/src/main/java/konogonka/Controllers/IRowModel.java b/src/main/java/konogonka/Controllers/IRowModel.java new file mode 100644 index 0000000..6173658 --- /dev/null +++ b/src/main/java/konogonka/Controllers/IRowModel.java @@ -0,0 +1,10 @@ +package konogonka.Controllers; + +public interface IRowModel { + int getNumber(); + String getFileName(); + long getFileSize(); + long getFileOffset(); + boolean isMarkSelected(); + void setMarkSelected(boolean value); +} diff --git a/src/main/java/konogonka/Controllers/MainController.java b/src/main/java/konogonka/Controllers/MainController.java new file mode 100644 index 0000000..93b9d01 --- /dev/null +++ b/src/main/java/konogonka/Controllers/MainController.java @@ -0,0 +1,120 @@ +package konogonka.Controllers; + +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.*; +import javafx.scene.layout.AnchorPane; +import javafx.stage.FileChooser; +import konogonka.AppPreferences; +import konogonka.Controllers.NCA.NCAController; +import konogonka.Controllers.NSP.NSPController; +import konogonka.Controllers.XCI.XCIController; +import konogonka.MediatorControl; +import konogonka.Settings.SettingsWindow; + +import java.io.*; +import java.net.URL; +import java.util.ResourceBundle; + +public class MainController implements Initializable { + @FXML + private SplitPane splitPane; + @FXML + private AnchorPane logPane; + @FXML + private TabPane tabPane; + + @FXML + public TextArea logArea; + @FXML + public ProgressBar progressBar; + @FXML + private Label filenameSelected; + @FXML + private Button analyzeBtn, settingsBtn; + + private String previouslyOpenedPath; + + @FXML + private NSPController NSPTabController; + @FXML + private XCIController XCITabController; + @FXML + private NCAController NCATabController; + + private File selectedFile; + + private ResourceBundle rb; + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + this.rb = resourceBundle; + MediatorControl.getInstance().setController(this); + + progressBar.setPrefWidth(Double.POSITIVE_INFINITY); + previouslyOpenedPath = AppPreferences.getInstance().getRecentPath(); + + analyzeBtn.setOnAction(e->this.analyzeFile()); + + splitPane.getItems().remove(logPane); + settingsBtn.setOnAction(e->{ + new SettingsWindow(); + }); + } + + /** + * Functionality for selecting NSP button. + * */ + public void selectFilesBtnAction(){ + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle(rb.getString("btnFileOpen")); + + File validator = new File(previouslyOpenedPath); + if (validator.exists()) + fileChooser.setInitialDirectory(validator); + else + fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); + + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NS ROM", "*.nsp", "*.xci", "*.nca")); + + this.selectedFile = fileChooser.showOpenDialog(analyzeBtn.getScene().getWindow()); + + analyzeBtn.setDisable(true); + NSPTabController.resetTab(); + XCITabController.resetTab(); + NCATabController.resetTab(); + + if (this.selectedFile != null && this.selectedFile.exists()) { + filenameSelected.setText(this.selectedFile.getAbsolutePath()); + previouslyOpenedPath = this.selectedFile.getParent(); + analyzeBtn.setDisable(false); + if (this.selectedFile.getName().toLowerCase().endsWith(".nsp")) + tabPane.getSelectionModel().select(0); + else if (this.selectedFile.getName().toLowerCase().endsWith(".xci")) + tabPane.getSelectionModel().select(1); + else if (this.selectedFile.getName().toLowerCase().endsWith(".nca")) + tabPane.getSelectionModel().select(2); + } + + logArea.clear(); + } + /** + * Start analyze + * */ + private void analyzeFile(){ + if (selectedFile.getName().toLowerCase().endsWith("nsp")) + NSPTabController.analyze(selectedFile); // TODO: NSP OR XCI + else if (selectedFile.getName().toLowerCase().endsWith("xci")) + XCITabController.analyze(selectedFile); + else if (selectedFile.getName().toLowerCase().endsWith("nca")) + NCATabController.analyze(selectedFile); + } + @FXML + private void showHideLogs(){ + if (splitPane.getItems().size() == 2) + splitPane.getItems().remove(logPane); + else + splitPane.getItems().add(logPane); + } + + public void exit(){ AppPreferences.getInstance().setRecentPath(previouslyOpenedPath); } +} \ No newline at end of file diff --git a/src/main/java/konogonka/Controllers/NCA/NCAController.java b/src/main/java/konogonka/Controllers/NCA/NCAController.java new file mode 100644 index 0000000..36ebc8b --- /dev/null +++ b/src/main/java/konogonka/Controllers/NCA/NCAController.java @@ -0,0 +1,187 @@ +package konogonka.Controllers.NCA; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import konogonka.AppPreferences; +import konogonka.Controllers.TabController; +import konogonka.Tools.NCA.NCAContentPFS0; +import konogonka.Tools.NCA.NCAProvider; +import konogonka.Workers.AnalyzerNCA; + +import java.io.File; +import java.net.URL; +import java.util.HashMap; +import java.util.ResourceBundle; + +import static konogonka.LoperConverter.byteArrToHexString; + +public class NCAController implements TabController { + + private File selectedFile; + @FXML + private NCASectionHeaderBlockController + NCASectionHeaderFirstController, + NCASectionHeaderSecondController, + NCASectionHeaderThirdController, + NCASectionHeaderFourthController; + @FXML + private NCASectionContentController + NCASectionContentFirstController, + NCASectionContentSecondController, + NCASectionContentThirdController, + NCASectionContentFourthController; + @FXML + private NCATableController + NCATable1Controller, + NCATable2Controller, + NCATable3Controller, + NCATable4Controller; + @FXML + private Label + magicnumLbl, + systemOrGcIndLbl, + contentTypeLbl, + cryptoType1Lbl, + keyIndexLbl, + ncaSizeLbl, + titleIdLbl, + sdkVersionLbl, + cryptoType2Lbl, + ticketLbl; + @FXML + private TextField + rsa2048oneTF, + rsa2048twoTF, + sha256section1TF, + sha256section2TF, + sha256section3TF, + sha256section4TF, + keyAreaEnKey0TF, + keyAreaEnKey1TF, + keyAreaEnKey2TF, + keyAreaEnKey3TF, + keyAreaDecKey0TF, + keyAreaDecKey1TF, + keyAreaDecKey2TF, + keyAreaDecKey3TF; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + + } + + @Override + public void analyze(File file) { + this.selectedFile = file; + HashMap keysMap = new HashMap<>(); + keysMap.put("header_key", AppPreferences.getInstance().getHeaderKey()); + for (int i = 0; i < 6; i++){ + keysMap.put("key_area_key_application_0"+i, AppPreferences.getInstance().getApplicationKey(i)); + keysMap.put("key_area_key_ocean_0"+i, AppPreferences.getInstance().getOceanKey(i)); + keysMap.put("key_area_key_system_0"+i, AppPreferences.getInstance().getSystemKey(i)); + } + + AnalyzerNCA analyzerNCA = new AnalyzerNCA(file, keysMap); + analyzerNCA.setOnSucceeded(e->{ + populateFields(analyzerNCA.getValue()); + }); + Thread workThread = new Thread(analyzerNCA); + workThread.setDaemon(true); + workThread.start(); + } + + @Override + public void resetTab() { + // Header + rsa2048oneTF.setText("-"); + rsa2048twoTF.setText("-"); + magicnumLbl.setText("-"); + systemOrGcIndLbl.setText("-"); + contentTypeLbl.setText("-"); + cryptoType1Lbl.setText("-"); + keyIndexLbl.setText("-"); + ncaSizeLbl.setText("-"); + titleIdLbl.setText("-"); + sdkVersionLbl.setText("-"); + cryptoType2Lbl.setText("-"); + ticketLbl.setText("-"); + sha256section1TF.setText("-"); + sha256section2TF.setText("-"); + sha256section3TF.setText("-"); + sha256section4TF.setText("-"); + keyAreaEnKey0TF.setText("-"); + keyAreaEnKey1TF.setText("-"); + keyAreaEnKey2TF.setText("-"); + keyAreaEnKey3TF.setText("-"); + + keyAreaDecKey0TF.setText("-"); + keyAreaDecKey1TF.setText("-"); + keyAreaDecKey2TF.setText("-"); + keyAreaDecKey3TF.setText("-"); + // Tables + NCATable1Controller.resetTab(); + NCATable2Controller.resetTab(); + NCATable3Controller.resetTab(); + NCATable4Controller.resetTab(); + // Table blocks + NCASectionHeaderFirstController.resetTab(); + NCASectionHeaderSecondController.resetTab(); + NCASectionHeaderThirdController.resetTab(); + NCASectionHeaderFourthController.resetTab(); + // Section content blocks + NCASectionContentFirstController.resetTab(); + NCASectionContentSecondController.resetTab(); + NCASectionContentThirdController.resetTab(); + NCASectionContentFourthController.resetTab(); + } + + private void populateFields(NCAProvider ncaProvider){ + if (ncaProvider != null){ + rsa2048oneTF.setText(byteArrToHexString(ncaProvider.getRsa2048one())); + rsa2048twoTF.setText(byteArrToHexString(ncaProvider.getRsa2048two())); + magicnumLbl.setText(ncaProvider.getMagicnum()); + systemOrGcIndLbl.setText(Byte.toString(ncaProvider.getSystemOrGcIndicator())); + contentTypeLbl.setText(Byte.toString(ncaProvider.getContentType())); + cryptoType1Lbl.setText(Byte.toString(ncaProvider.getCryptoType1())); + keyIndexLbl.setText(Byte.toString(ncaProvider.getKeyIndex())); + ncaSizeLbl.setText(Long.toString(ncaProvider.getNcaSize())); + titleIdLbl.setText(byteArrToHexString(ncaProvider.getTitleId())); + sdkVersionLbl.setText(ncaProvider.getSdkVersion()[3] + +"."+ncaProvider.getSdkVersion()[2] + +"."+ncaProvider.getSdkVersion()[1] + +"."+ncaProvider.getSdkVersion()[0]); + cryptoType2Lbl.setText(Byte.toString(ncaProvider.getCryptoType2())); + ticketLbl.setText(byteArrToHexString(ncaProvider.getRightsId())); + sha256section1TF.setText(byteArrToHexString(ncaProvider.getSha256hash0())); + sha256section2TF.setText(byteArrToHexString(ncaProvider.getSha256hash1())); + sha256section3TF.setText(byteArrToHexString(ncaProvider.getSha256hash2())); + sha256section4TF.setText(byteArrToHexString(ncaProvider.getSha256hash3())); + keyAreaEnKey0TF.setText(byteArrToHexString(ncaProvider.getEncryptedKey0())); + keyAreaEnKey1TF.setText(byteArrToHexString(ncaProvider.getEncryptedKey1())); + keyAreaEnKey2TF.setText(byteArrToHexString(ncaProvider.getEncryptedKey2())); + keyAreaEnKey3TF.setText(byteArrToHexString(ncaProvider.getEncryptedKey3())); + + keyAreaDecKey0TF.setText(byteArrToHexString(ncaProvider.getDecryptedKey0())); + keyAreaDecKey1TF.setText(byteArrToHexString(ncaProvider.getDecryptedKey1())); + keyAreaDecKey2TF.setText(byteArrToHexString(ncaProvider.getDecryptedKey2())); + keyAreaDecKey3TF.setText(byteArrToHexString(ncaProvider.getDecryptedKey3())); + // Tables + NCATable1Controller.populateTab(ncaProvider.getTableEntry0()); + NCATable2Controller.populateTab(ncaProvider.getTableEntry1()); + NCATable3Controller.populateTab(ncaProvider.getTableEntry2()); + NCATable4Controller.populateTab(ncaProvider.getTableEntry3()); + // Table blocks + NCASectionHeaderFirstController.populateTab(ncaProvider.getSectionBlock0()); + NCASectionHeaderSecondController.populateTab(ncaProvider.getSectionBlock1()); + NCASectionHeaderThirdController.populateTab(ncaProvider.getSectionBlock2()); + NCASectionHeaderFourthController.populateTab(ncaProvider.getSectionBlock3()); + // Section content blocks + // TODO: FIX: This code executes getNCAContentPFS0() method twice + NCASectionContentFirstController.populateFields(ncaProvider.getNCAContentPFS0(0).getPfs0(), selectedFile, ncaProvider.getNCAContentPFS0(0).getSHA256hashes()); + NCASectionContentSecondController.populateFields(ncaProvider.getNCAContentPFS0(1).getPfs0(), selectedFile, ncaProvider.getNCAContentPFS0(1).getSHA256hashes()); + NCASectionContentThirdController.populateFields(ncaProvider.getNCAContentPFS0(2).getPfs0(), selectedFile, ncaProvider.getNCAContentPFS0(2).getSHA256hashes()); + NCASectionContentFourthController.populateFields(ncaProvider.getNCAContentPFS0(3).getPfs0(), selectedFile, ncaProvider.getNCAContentPFS0(3).getSHA256hashes()); + } + } +} diff --git a/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java b/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java new file mode 100644 index 0000000..4a47bba --- /dev/null +++ b/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java @@ -0,0 +1,40 @@ +package konogonka.Controllers.NCA; + +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import konogonka.Controllers.NSP.NSPController; +import konogonka.LoperConverter; +import konogonka.Tools.PFS0.PFS0Provider; + +import java.io.File; +import java.util.LinkedList; + +public class NCASectionContentController{ + @FXML + private NSPController SectionPFS0Controller; + @FXML + private VBox sha256pane; + + public void resetTab() { + SectionPFS0Controller.resetTab(); + sha256pane.getChildren().clear(); + } + + public void populateFields(PFS0Provider pfs0, File file, LinkedList sha256hashList) { + resetTab(); + SectionPFS0Controller.setData(pfs0, file); + if (sha256hashList != null){ + for (int i = 0; i < sha256hashList.size(); i++){ + Label numberLblTmp = new Label(String.format("%10d", i)); + numberLblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0)); + Label sha256LblTmp = new Label(LoperConverter.byteArrToHexString(sha256hashList.get(i))); + sha256LblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0)); + + sha256pane.getChildren().add(new HBox(numberLblTmp, sha256LblTmp)); + } + } + } +} diff --git a/src/main/java/konogonka/Controllers/NCA/NCASectionHeaderBlockController.java b/src/main/java/konogonka/Controllers/NCA/NCASectionHeaderBlockController.java new file mode 100644 index 0000000..ae8aa46 --- /dev/null +++ b/src/main/java/konogonka/Controllers/NCA/NCASectionHeaderBlockController.java @@ -0,0 +1,262 @@ +package konogonka.Controllers.NCA; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.control.TitledPane; +import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock; + +import static konogonka.LoperConverter.byteArrToHexString; + +public class NCASectionHeaderBlockController { + @FXML + private Label + versionLbl, + fsTypeLbl, + hashTypeLbl, + cryptoTypeLbl, + paddingLbl; + @FXML + private TextField + BKTRHeaderTf; + @FXML + private TitledPane + romFsTitlePanel, + pfs0TitlePanel; + // PFS0Provider + @FXML + private Label + pfs0blockSizeLbl, + pfs0unknownNumberTwoLbl, + pfs0hashTableOffsetLbl, + pfs0hashTableSizeLbl, + pfs0relativeToSectionStartOffsetLbl, + pfs0sizePfs0Lbl; + @FXML + private TextField + pfs0SHA256hashTf, + pfs0zeroesTf; + // RomFS (IVFC) + @FXML + private Label + romFsMagicLbl, + romFsMagicNumberLbl, + romFsMasterHashSizeLbl, + romFsTotalNumberOfLevelsLbl, + romFsLvl1OffsetLbl, + romFsLvl1SizeLbl, + romFsLvl1SBlockSizeLbl, + romFsReserved1Lbl, + + romFsLvl2OffsetLbl, + romFsLvl2SizeLbl, + romFsLvl2SBlockSizeLbl, + romFsReserved2Lbl, + + romFsLvl3OffsetLbl, + romFsLvl3SizeLbl, + romFsLvl3SBlockSizeLbl, + romFsReserved3Lbl, + + romFsLvl4OffsetLbl, + romFsLvl4SizeLbl, + romFsLvl4SBlockSizeLbl, + romFsReserved4Lbl, + + romFsLvl5OffsetLbl, + romFsLvl5SizeLbl, + romFsLvl5SBlockSizeLbl, + romFsReserved5Lbl, + + romFsLvl6OffsetLbl, + romFsLvl6SizeLbl, + romFsLvl6SBlockSizeLbl, + romFsReserved6Lbl; + @FXML + private TextField + romFsUnknownTf, + romFsHashTf; + + @FXML + private Label + BKTRoffsetSection1Lbl, + BKTRsizeSection1Lbl, + BKTRmagicSection1Lbl, + BKTRu32Section1Lbl, + BKTRs32Section1Lbl, + BKTRunknownSection1Lbl, + BKTRoffsetSection2Lbl, + BKTRsizeSection2Lbl, + BKTRmagicSection2Lbl, + BKTRu32Section2Lbl, + BKTRs32Section2Lbl, + BKTRunknownSection2Lbl, + BKTRunknown1Lbl, + BKTRunknown2Lbl; + + public void resetTab() { + versionLbl.setText("-"); + fsTypeLbl.setText("-"); + hashTypeLbl.setText("-"); + cryptoTypeLbl.setText("-"); + paddingLbl.setText("-"); + + romFsTitlePanel.setDisable(false); + romFsTitlePanel.setExpanded(false); + pfs0TitlePanel.setDisable(false); + pfs0TitlePanel.setExpanded(false); + //RomFS + romFsMagicLbl.setText("-"); + romFsMagicNumberLbl.setText("-"); + romFsMasterHashSizeLbl.setText("-"); + romFsTotalNumberOfLevelsLbl.setText("-"); + romFsLvl1OffsetLbl.setText("-"); + romFsLvl1SizeLbl.setText("-"); + romFsLvl1SBlockSizeLbl.setText("-"); + romFsReserved1Lbl.setText("-"); + + romFsLvl2OffsetLbl.setText("-"); + romFsLvl2SizeLbl.setText("-"); + romFsLvl2SBlockSizeLbl.setText("-"); + romFsReserved2Lbl.setText("-"); + + romFsLvl3OffsetLbl.setText("-"); + romFsLvl3SizeLbl.setText("-"); + romFsLvl3SBlockSizeLbl.setText("-"); + romFsReserved3Lbl.setText("-"); + + romFsLvl4OffsetLbl.setText("-"); + romFsLvl4SizeLbl.setText("-"); + romFsLvl4SBlockSizeLbl.setText("-"); + romFsReserved4Lbl.setText("-"); + + romFsLvl5OffsetLbl.setText("-"); + romFsLvl5SizeLbl.setText("-"); + romFsLvl5SBlockSizeLbl.setText("-"); + romFsReserved5Lbl.setText("-"); + + romFsLvl6OffsetLbl.setText("-"); + romFsLvl6SizeLbl.setText("-"); + romFsLvl6SBlockSizeLbl.setText("-"); + romFsReserved6Lbl.setText("-"); + + romFsUnknownTf.setText("-"); + romFsHashTf.setText("-"); + + // PFS0Provider + pfs0SHA256hashTf.setText("-"); + pfs0blockSizeLbl.setText("-"); + pfs0unknownNumberTwoLbl.setText("-"); + pfs0hashTableOffsetLbl.setText("-"); + pfs0hashTableSizeLbl.setText("-"); + pfs0relativeToSectionStartOffsetLbl.setText("-"); + pfs0sizePfs0Lbl.setText("-"); + pfs0zeroesTf.setText("-"); + + BKTRHeaderTf.setText("-"); + BKTRoffsetSection1Lbl.setText("-"); + BKTRsizeSection1Lbl.setText("-"); + BKTRmagicSection1Lbl.setText("-"); + BKTRu32Section1Lbl.setText("-"); + BKTRs32Section1Lbl.setText("-"); + BKTRunknownSection1Lbl.setText("-"); + BKTRoffsetSection2Lbl.setText("-"); + BKTRsizeSection2Lbl.setText("-"); + BKTRmagicSection2Lbl.setText("-"); + BKTRu32Section2Lbl.setText("-"); + BKTRs32Section2Lbl.setText("-"); + BKTRunknownSection2Lbl.setText("-"); + BKTRunknown1Lbl.setText("-"); + BKTRunknown2Lbl.setText("-"); + } + + public void populateTab(NCASectionBlock ncaSectionBlock){ + versionLbl.setText(byteArrToHexString(ncaSectionBlock.getVersion())); + StringBuilder sb = new StringBuilder(); + sb.append(String.format("%02x ", ncaSectionBlock.getFsType())); + if (ncaSectionBlock.getFsType() == 0) + sb.append("(RomFS)"); + else if (ncaSectionBlock.getFsType() == 1) + sb.append("(PFS0)"); + fsTypeLbl.setText(sb.toString()); + sb = new StringBuilder(); + sb.append(String.format("%02x ", ncaSectionBlock.getHashType())); + if (ncaSectionBlock.getHashType() == 0x3) + sb.append("(RomFS)"); + else if (ncaSectionBlock.getHashType() == 0x2) + sb.append("(PFS0)"); + hashTypeLbl.setText(sb.toString()); + cryptoTypeLbl.setText(String.format("%02x ", ncaSectionBlock.getCryptoType())); + paddingLbl.setText(String.format("%02x ", ncaSectionBlock.getPadding())); + if ((ncaSectionBlock.getFsType() == 0) && (ncaSectionBlock.getHashType() == 0x3)){ + romFsMagicLbl.setText(ncaSectionBlock.getSuperBlockIVFC().getMagic()); + romFsMagicNumberLbl.setText(ncaSectionBlock.getSuperBlockIVFC().getMagicNumber() + +(ncaSectionBlock.getSuperBlockIVFC().getMagicNumber() == 0x20000? " (OK)":" (wrong magic number)")); + romFsMasterHashSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getMasterHashSize())); + romFsTotalNumberOfLevelsLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getTotalNumberOfLevels())); + romFsLvl1OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1Offset())); + romFsLvl1SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1Size())); + romFsLvl1SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1SBlockSize())); + romFsReserved1Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved1())); + + romFsLvl2OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2Offset())); + romFsLvl2SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2Size())); + romFsLvl2SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2SBlockSize())); + romFsReserved2Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved2())); + + romFsLvl3OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3Offset())); + romFsLvl3SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3Size())); + romFsLvl3SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3SBlockSize())); + romFsReserved3Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved3())); + + romFsLvl4OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4Offset())); + romFsLvl4SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4Size())); + romFsLvl4SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4SBlockSize())); + romFsReserved4Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved4())); + + romFsLvl5OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5Offset())); + romFsLvl5SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5Size())); + romFsLvl5SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5SBlockSize())); + romFsReserved5Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved5())); + + romFsLvl6OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset())); + romFsLvl6SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6Size())); + romFsLvl6SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6SBlockSize())); + romFsReserved6Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved6())); + + romFsUnknownTf.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getUnknown())); + romFsHashTf.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getHash())); + pfs0TitlePanel.setDisable(true); + } + else if ((ncaSectionBlock.getFsType() == 0x1) && (ncaSectionBlock.getHashType() == 0x2)){ + pfs0SHA256hashTf.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockPFS0().getSHA256hash())); + pfs0blockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockPFS0().getBlockSize())); + pfs0unknownNumberTwoLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockPFS0().getUnknownNumberTwo())); + pfs0hashTableOffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset())); + pfs0hashTableSizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockPFS0().getHashTableSize())); + pfs0relativeToSectionStartOffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockPFS0().getPfs0offset())); + pfs0sizePfs0Lbl.setText(Long.toString(ncaSectionBlock.getSuperBlockPFS0().getPfs0size())); + pfs0zeroesTf.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockPFS0().getZeroes())); + romFsTitlePanel.setDisable(true); + } + else { + pfs0TitlePanel.setDisable(true); + romFsTitlePanel.setDisable(true); + } + BKTRHeaderTf.setText(byteArrToHexString(ncaSectionBlock.getBKTRfullHeader())); + BKTRoffsetSection1Lbl.setText(Long.toString(ncaSectionBlock.getBKTRoffsetSection1())); + BKTRsizeSection1Lbl.setText(Long.toString(ncaSectionBlock.getBKTRsizeSection1())); + BKTRmagicSection1Lbl.setText(ncaSectionBlock.getBKTRmagicSection1()); + BKTRu32Section1Lbl.setText(Integer.toString(ncaSectionBlock.getBKTRu32Section1())); + BKTRs32Section1Lbl.setText(Integer.toString(ncaSectionBlock.getBKTRs32Section1())); + BKTRunknownSection1Lbl.setText(byteArrToHexString(ncaSectionBlock.getBKTRunknownSection1())); + BKTRoffsetSection2Lbl.setText(Long.toString(ncaSectionBlock.getBKTRoffsetSection2())); + BKTRsizeSection2Lbl.setText(Long.toString(ncaSectionBlock.getBKTRsizeSection2())); + BKTRmagicSection2Lbl.setText(ncaSectionBlock.getBKTRmagicSection2()); + BKTRu32Section2Lbl.setText(Integer.toString(ncaSectionBlock.getBKTRu32Section2())); + BKTRs32Section2Lbl.setText(Integer.toString(ncaSectionBlock.getBKTRs32Section2())); + BKTRunknownSection2Lbl.setText(byteArrToHexString(ncaSectionBlock.getBKTRunknownSection2())); + BKTRunknown1Lbl.setText(byteArrToHexString(ncaSectionBlock.getBKTRunknown1())); + BKTRunknown2Lbl.setText(byteArrToHexString(ncaSectionBlock.getBKTRunknown2())); + } +} diff --git a/src/main/java/konogonka/Controllers/NCA/NCATableController.java b/src/main/java/konogonka/Controllers/NCA/NCATableController.java new file mode 100644 index 0000000..e56de12 --- /dev/null +++ b/src/main/java/konogonka/Controllers/NCA/NCATableController.java @@ -0,0 +1,30 @@ +package konogonka.Controllers.NCA; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import konogonka.Tools.NCA.NCAHeaderTableEntry; + +import static konogonka.LoperConverter.byteArrToHexString; + +public class NCATableController { + @FXML + private Label + mediaStartOffsetLbl, + mediaEndOffsetLbl, + unknwn1Lbl, + unknwn2Lbl; + + public void resetTab() { + mediaStartOffsetLbl.setText("-"); + mediaEndOffsetLbl.setText("-"); + unknwn1Lbl.setText("-"); + unknwn2Lbl.setText("-"); + } + + public void populateTab(NCAHeaderTableEntry ncaHeaderTableEntry){ + mediaStartOffsetLbl.setText(Long.toString(ncaHeaderTableEntry.getMediaStartOffset())); + mediaEndOffsetLbl.setText(Long.toString(ncaHeaderTableEntry.getMediaEndOffset())); + unknwn1Lbl.setText(byteArrToHexString(ncaHeaderTableEntry.getUnknwn1())); + unknwn2Lbl.setText(byteArrToHexString(ncaHeaderTableEntry.getUnknwn2())); + } +} diff --git a/src/main/java/konogonka/Controllers/NSP/NSPController.java b/src/main/java/konogonka/Controllers/NSP/NSPController.java new file mode 100644 index 0000000..7b56de7 --- /dev/null +++ b/src/main/java/konogonka/Controllers/NSP/NSPController.java @@ -0,0 +1,129 @@ +package konogonka.Controllers.NSP; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import konogonka.Controllers.IRowModel; +import konogonka.Controllers.TabController; +import konogonka.MediatorControl; +import konogonka.Tools.PFS0.PFS0Provider; +import konogonka.Workers.AnalyzerNSP; +import konogonka.Workers.NspXciExtractor; + +import java.io.File; +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; + +import static konogonka.LoperConverter.byteArrToHexString; + +public class NSPController implements TabController { + + @FXML + private Button extractBtn; + @FXML + private Pfs0TableViewController tableFilesListController; + @FXML + private Label filesCountLbl, + RawDataStartLbl, + NSPSizeLbl, + magicLbl, + stringTableSizeLbl, + paddingLbl, + fileEntryTableSizeLbl, + stringsTableSizeLbl, + stringsTableOffsetLbl, + rawFileDataOffsetLbl; + + private long rawFileDataStart; + + private File selectedFile; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + extractBtn.setOnAction(e->this.extractFiles()); + } + + private void extractFiles(){ + List models = tableFilesListController.getFilesForDump(); + if (models != null && !models.isEmpty()){ + + File dir = new File(System.getProperty("user.dir")+File.separator+selectedFile.getName()+" extracted"); + try { + dir.mkdir(); + } + catch (SecurityException se){ + MediatorControl.getInstance().getContoller().logArea.setText("Can't create dir to store files."); + } + if (!dir.exists()) + return; + + extractBtn.setDisable(true); + + NspXciExtractor extractor = new NspXciExtractor(rawFileDataStart, models, dir.getAbsolutePath()+File.separator, selectedFile); + extractor.setOnSucceeded(e->{ + extractBtn.setDisable(false); + }); + Thread workThread = new Thread(extractor); + workThread.setDaemon(true); + workThread.start(); + } + } + @Override + public void resetTab(){ + tableFilesListController.setNSPToTable(null); + filesCountLbl.setText("-"); + RawDataStartLbl.setText("-"); + NSPSizeLbl.setText("-"); + magicLbl.setText("-"); + stringTableSizeLbl.setText("-"); + paddingLbl.setText("-"); + fileEntryTableSizeLbl.setText("X"); + stringsTableSizeLbl.setText("Y"); + stringsTableOffsetLbl.setText("0x10+X"); + rawFileDataOffsetLbl.setText("0x10+X+Y"); + selectedFile = null; + } + /** + * Start analyze NSP + * */ + @Override + public void analyze(File selectedFile){ + this.selectedFile = selectedFile; + AnalyzerNSP analyzerNSP = new AnalyzerNSP(selectedFile); + analyzerNSP.setOnSucceeded(e->{ + PFS0Provider pfs0 = analyzerNSP.getValue(); + this.setData(pfs0, null); + }); + Thread workThread = new Thread(analyzerNSP); + workThread.setDaemon(true); + workThread.start(); + } + /** + * Just populate fields by already analyzed PFS0 + * */ + public void setData(PFS0Provider pfs0, File fileWithNca){ + if (pfs0 != null){ + if (fileWithNca != null) + this.selectedFile = fileWithNca; + filesCountLbl.setText(Integer.toString(pfs0.getFilesCount())); + RawDataStartLbl.setText(Long.toString(pfs0.getRawFileDataStart())); + rawFileDataStart = pfs0.getRawFileDataStart(); + tableFilesListController.setNSPToTable(pfs0); + if (fileWithNca != null) + NSPSizeLbl.setText("skipping calculation for in-file PFS0"); + else + NSPSizeLbl.setText(Long.toString(selectedFile.length())); + + extractBtn.setDisable(false); + magicLbl.setText(pfs0.getMagic()); + stringTableSizeLbl.setText(Integer.toString(pfs0.getStringTableSize())); + paddingLbl.setText(byteArrToHexString(pfs0.getPadding())); + + fileEntryTableSizeLbl.setText(String.format("0x%02x", 0x18* pfs0.getFilesCount())); + stringsTableSizeLbl.setText(String.format("0x%02x", pfs0.getStringTableSize())); + stringsTableOffsetLbl.setText(String.format("0x%02x", 0x18* pfs0.getFilesCount()+0x10)); + rawFileDataOffsetLbl.setText(String.format("0x%02x", 0x18* pfs0.getFilesCount()+0x10+ pfs0.getStringTableSize())); // same to RawFileDataStart for NSP ONLY + } + } +} diff --git a/src/main/java/konogonka/Controllers/NSP/Pfs0RowModel.java b/src/main/java/konogonka/Controllers/NSP/Pfs0RowModel.java new file mode 100644 index 0000000..0282684 --- /dev/null +++ b/src/main/java/konogonka/Controllers/NSP/Pfs0RowModel.java @@ -0,0 +1,40 @@ +package konogonka.Controllers.NSP; + +import konogonka.Controllers.IRowModel; + +public class Pfs0RowModel implements IRowModel { + + private static int numberCnt = 0; + public static void resetNumCnt(){ numberCnt = 0; } + + private int number; + private String fileName; + private long fileSize; + private long fileOffset; + private boolean markForUpload; + + Pfs0RowModel(String fileName, long size, long offset){ + this.markForUpload = false; + this.fileName = fileName; + this.fileOffset = offset; + this.fileSize = size; + this.number = numberCnt++; + } + // Model methods start + public int getNumber(){ + return number; + } + public String getFileName(){ + return fileName; + } + public long getFileSize() { return fileSize; } + public long getFileOffset() {return fileOffset; } + public boolean isMarkSelected() { + return markForUpload; + } + // Model methods end + + public void setMarkSelected(boolean value){ + markForUpload = value; + } +} diff --git a/src/main/java/konogonka/Controllers/NSP/Pfs0TableViewController.java b/src/main/java/konogonka/Controllers/NSP/Pfs0TableViewController.java new file mode 100644 index 0000000..422f98f --- /dev/null +++ b/src/main/java/konogonka/Controllers/NSP/Pfs0TableViewController.java @@ -0,0 +1,180 @@ +package konogonka.Controllers.NSP; + +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.*; +import javafx.scene.control.cell.CheckBoxTableCell; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.util.Callback; +import konogonka.Controllers.IRowModel; +import konogonka.Tools.PFS0.PFS0Provider; + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; + +public class Pfs0TableViewController implements Initializable { + @FXML + private TableView table; + private ObservableList rowsObsLst; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + rowsObsLst = FXCollections.observableArrayList(); + + table.setPlaceholder(new Label()); + table.setEditable(false); // At least with hacks it works as expected. Otherwise - null pointer exception + table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + table.setOnKeyPressed(new EventHandler() { + @Override + public void handle(KeyEvent keyEvent) { + if (!rowsObsLst.isEmpty()) { + if (keyEvent.getCode() == KeyCode.SPACE) { + for (Pfs0RowModel item : table.getSelectionModel().getSelectedItems()) { + item.setMarkSelected(!item.isMarkSelected()); + } + table.refresh(); + } + } + keyEvent.consume(); + } + }); + + TableColumn numberColumn = new TableColumn<>(resourceBundle.getString("tableNumberLbl")); + TableColumn fileNameColumn = new TableColumn<>(resourceBundle.getString("tableFileNameLbl")); + TableColumn fileOffsetColumn = new TableColumn<>(resourceBundle.getString("tableOffsetLbl")); + TableColumn fileSizeColumn = new TableColumn<>(resourceBundle.getString("tableSizeLbl")); + TableColumn uploadColumn = new TableColumn<>(resourceBundle.getString("tableUploadLbl")); + + numberColumn.setEditable(false); + fileNameColumn.setEditable(false); + fileOffsetColumn.setEditable(false); + fileSizeColumn.setEditable(false); + uploadColumn.setEditable(true); + + // See https://bugs.openjdk.java.net/browse/JDK-8157687 + numberColumn.setMinWidth(30.0); + numberColumn.setPrefWidth(30.0); + numberColumn.setMaxWidth(30.0); + numberColumn.setResizable(false); + + fileNameColumn.setMinWidth(25.0); + + fileOffsetColumn.setMinWidth(130.0); + fileOffsetColumn.setPrefWidth(130.0); + fileOffsetColumn.setMaxWidth(130.0); + fileOffsetColumn.setResizable(false); + + fileSizeColumn.setMinWidth(120.0); + fileSizeColumn.setPrefWidth(120.0); + fileSizeColumn.setMaxWidth(120.0); + fileSizeColumn.setResizable(false); + + uploadColumn.setMinWidth(120.0); + uploadColumn.setPrefWidth(120.0); + uploadColumn.setMaxWidth(120.0); + uploadColumn.setResizable(false); + + numberColumn.setCellValueFactory(new PropertyValueFactory<>("number")); + fileNameColumn.setCellValueFactory(new PropertyValueFactory<>("fileName")); + fileSizeColumn.setCellValueFactory(new PropertyValueFactory<>("fileSize")); + fileOffsetColumn.setCellValueFactory(new PropertyValueFactory<>("fileOffset")); + // >< + uploadColumn.setCellValueFactory(new Callback, ObservableValue>() { + @Override + public ObservableValue call(TableColumn.CellDataFeatures paramFeatures) { + Pfs0RowModel model = paramFeatures.getValue(); + + SimpleBooleanProperty booleanProperty = new SimpleBooleanProperty(model.isMarkSelected()); + + booleanProperty.addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observableValue, Boolean oldValue, Boolean newValue) { + model.setMarkSelected(newValue); + table.refresh(); + } + }); + return booleanProperty; + } + }); + + uploadColumn.setCellFactory(new Callback, TableCell>() { + @Override + public TableCell call(TableColumn paramFeatures) { + CheckBoxTableCell cell = new CheckBoxTableCell<>(); + return cell; + } + }); + table.setRowFactory( // this shit is made to implement context menu. It's such a pain.. + new Callback, TableRow>() { + @Override + public TableRow call(TableView nslRowModelTableView) { + final TableRow row = new TableRow<>(); + row.setOnMouseClicked(new EventHandler() { // Just.. don't ask.. + @Override + public void handle(MouseEvent mouseEvent) { + if (!row.isEmpty() && mouseEvent.getButton() == MouseButton.PRIMARY){ + Pfs0RowModel thisItem = row.getItem(); + thisItem.setMarkSelected(!thisItem.isMarkSelected()); + table.refresh(); + } + mouseEvent.consume(); + } + }); + return row; + } + } + ); + table.setItems(rowsObsLst); + table.getColumns().addAll(numberColumn, fileNameColumn, fileOffsetColumn, fileSizeColumn, uploadColumn); + } + /** + * Add files when user selected them + * */ + public void setNSPToTable(PFS0Provider pfs){ + rowsObsLst.clear(); + Pfs0RowModel.resetNumCnt(); + if (pfs == null) { + table.refresh(); + return; + } + + for (int i=0; i < pfs.getFilesCount(); i++){ + rowsObsLst.add(new Pfs0RowModel( + pfs.getPfs0subFiles()[i].getName(), + pfs.getPfs0subFiles()[i].getSize(), + pfs.getPfs0subFiles()[i].getOffset() + )); + } + table.refresh(); + } + /** + * Return files ready for upload. Requested from NSLMainController only -> uploadBtnAction() //TODO: set undefined + * @return null if no files marked for upload + * List if there are files + * */ + public List getFilesForDump(){ + List models = new ArrayList<>(); + if (rowsObsLst.isEmpty()) + return null; + for (Pfs0RowModel model: rowsObsLst) { + if (model.isMarkSelected()) { + models.add(model); + } + } + return models; + } +} \ No newline at end of file diff --git a/src/main/java/konogonka/Controllers/TabController.java b/src/main/java/konogonka/Controllers/TabController.java new file mode 100644 index 0000000..b131c28 --- /dev/null +++ b/src/main/java/konogonka/Controllers/TabController.java @@ -0,0 +1,10 @@ +package konogonka.Controllers; + +import javafx.fxml.Initializable; + +import java.io.File; + +public interface TabController extends Initializable { + void analyze(File file); + void resetTab(); +} diff --git a/src/main/java/konogonka/Controllers/XCI/HFSBlockController.java b/src/main/java/konogonka/Controllers/XCI/HFSBlockController.java new file mode 100644 index 0000000..9b72736 --- /dev/null +++ b/src/main/java/konogonka/Controllers/XCI/HFSBlockController.java @@ -0,0 +1,116 @@ +package konogonka.Controllers.XCI; + +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TitledPane; +import konogonka.Controllers.IRowModel; +import konogonka.MediatorControl; +import konogonka.Tools.XCI.HFS0Provider; +import konogonka.Workers.NspXciExtractor; + +import java.io.File; +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; + +public class HFSBlockController implements Initializable { + @FXML + private TitledPane currentTitledPane; + + @FXML + private Button extractMainBtn; + + @FXML + private Label hfs0mainMagicNumLbl, + hfs0mainFileCntLbl, + hfs0mainStrTblSizeLbl, + hfs0mainPaddingLbl, + hfs0mainRawFileDataStartLbl; + + @FXML + private Hfs0TableViewController + hfs0tableFilesListMainController; + + private long bodySize; + private String + type, + paneName; + + private static File selectedFile; + + public static void setSelectedFile(File file){ + selectedFile = file; + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + extractMainBtn.setOnAction(e->{ + extractFiles(); + }); + } + + public void setTitlePaneTypeName(String type, String name){ + this.currentTitledPane.setText(name); + paneName = name; + this.type = type; + } + + public void resetTab(){ + currentTitledPane.setExpanded(false); + hfs0mainMagicNumLbl.setText("-"); + hfs0mainFileCntLbl.setText("-"); + hfs0mainStrTblSizeLbl.setText("-"); + hfs0mainPaddingLbl.setText("-"); + hfs0mainRawFileDataStartLbl.setText("-"); + hfs0tableFilesListMainController.setContentToTable(null); + extractMainBtn.setDisable(true); + currentTitledPane.setText(paneName); + } + public void populateTab(HFS0Provider hfs0Provider){ + if (hfs0Provider != null){ + bodySize = hfs0Provider.getRawFileDataStart(); + hfs0mainMagicNumLbl.setText(Boolean.toString(hfs0Provider.isMagicHFS0())); + hfs0mainFileCntLbl.setText(Integer.toString(hfs0Provider.getFilesCnt())); + hfs0mainStrTblSizeLbl.setText(Integer.toString(hfs0Provider.getStringTableSize())); + hfs0mainPaddingLbl.setText(Boolean.toString(hfs0Provider.isPaddingHfs0())); + hfs0mainRawFileDataStartLbl.setText(Long.toString(hfs0Provider.getRawFileDataStart())); + hfs0tableFilesListMainController.setContentToTable(hfs0Provider); + if (hfs0Provider.getFilesCnt() > 0) + extractMainBtn.setDisable(false); + } + else { + currentTitledPane.setText(paneName+" (doesn't exist in this file)"); + currentTitledPane.setExpanded(false); + } + } + + private void extractFiles(){ + List models; + + models = hfs0tableFilesListMainController.getFilesForDump(); + + if (models != null && !models.isEmpty()){ + File dir = new File(System.getProperty("user.dir")+File.separator+selectedFile.getName()+" "+type+" extracted"); + try { + dir.mkdir(); + } + catch (SecurityException se){ + MediatorControl.getInstance().getContoller().logArea.setText("Can't create dir to store files."); + } + if (!dir.exists()) + return; + + extractMainBtn.setDisable(true); + System.out.println(dir.getAbsolutePath()+File.separator); + NspXciExtractor extractor = new NspXciExtractor(bodySize, models, dir.getAbsolutePath()+File.separator, selectedFile); + extractor.setOnSucceeded(e->{ + extractMainBtn.setDisable(false); + }); + Thread workThread = new Thread(extractor); + workThread.setDaemon(true); + workThread.start(); + } + } +} diff --git a/src/main/java/konogonka/Controllers/XCI/Hfs0RowModel.java b/src/main/java/konogonka/Controllers/XCI/Hfs0RowModel.java new file mode 100644 index 0000000..b2549db --- /dev/null +++ b/src/main/java/konogonka/Controllers/XCI/Hfs0RowModel.java @@ -0,0 +1,53 @@ +package konogonka.Controllers.XCI; + +import konogonka.Controllers.IRowModel; + +public class Hfs0RowModel implements IRowModel { + + private static int numberCnt = 0; + public static void resetNumCnt(){ numberCnt = 0; } + + private int number; + private String fileName; + private long fileSize; + private long fileOffset; + private long hashedRegionSize; + private boolean padding; + private String SHA256Hash; + private boolean markForUpload; + + Hfs0RowModel(String fileName, long size, long offset, long hashedRegionSize, boolean padding, byte[] SHA256Hash){ + this.markForUpload = false; + this.fileName = fileName; + this.fileOffset = offset; + this.fileSize = size; + this.hashedRegionSize = hashedRegionSize; + this.padding = padding; + StringBuilder sb = new StringBuilder(); + for (byte b: SHA256Hash) + sb.append(String.format("%02x", b)); + this.SHA256Hash = sb.toString(); + this.number = numberCnt++; + } + // Model methods start + public int getNumber(){ + return number; + } + public String getFileName(){ + return fileName; + } + public long getFileSize() { return fileSize; } + public long getFileOffset() {return fileOffset; } + + public long getHashedRegionSize() { return hashedRegionSize; } + public boolean isPadding() { return padding; } + public String getSHA256Hash() { return SHA256Hash; } + + public boolean isMarkSelected() { + return markForUpload; + } + // Model methods end + public void setMarkSelected(boolean value){ + markForUpload = value; + } +} diff --git a/src/main/java/konogonka/Controllers/XCI/Hfs0TableViewController.java b/src/main/java/konogonka/Controllers/XCI/Hfs0TableViewController.java new file mode 100644 index 0000000..42b8a3d --- /dev/null +++ b/src/main/java/konogonka/Controllers/XCI/Hfs0TableViewController.java @@ -0,0 +1,206 @@ +package konogonka.Controllers.XCI; + +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.*; +import javafx.scene.control.cell.CheckBoxTableCell; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.util.Callback; +import konogonka.Controllers.IRowModel; +import konogonka.Tools.XCI.HFS0Provider; + + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; + +public class Hfs0TableViewController implements Initializable { + @FXML + private TableView table; + private ObservableList rowsObsLst; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + rowsObsLst = FXCollections.observableArrayList(); + + table.setPlaceholder(new Label()); + table.setEditable(false); // At least with hacks it works as expected. Otherwise - null pointer exception + table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + table.setOnKeyPressed(new EventHandler() { + @Override + public void handle(KeyEvent keyEvent) { + if (!rowsObsLst.isEmpty()) { + if (keyEvent.getCode() == KeyCode.SPACE) { + for (Hfs0RowModel item : table.getSelectionModel().getSelectedItems()) { + item.setMarkSelected(!item.isMarkSelected()); + } + table.refresh(); + } + } + keyEvent.consume(); + } + }); + + TableColumn numberColumn = new TableColumn<>(resourceBundle.getString("tableNumberLbl")); + TableColumn fileNameColumn = new TableColumn<>(resourceBundle.getString("tableFileNameLbl")); + TableColumn fileOffsetColumn = new TableColumn<>(resourceBundle.getString("tableOffsetLbl")); + TableColumn fileSizeColumn = new TableColumn<>(resourceBundle.getString("tableSizeLbl")); + TableColumn fileHashedRegionSizeColumn = new TableColumn<>("Hashed Region Size"); + TableColumn filePaddingColumn = new TableColumn<>("Padding"); + TableColumn fileSHA256HashColumn = new TableColumn<>("SHA-256 Hash"); + TableColumn uploadColumn = new TableColumn<>(resourceBundle.getString("tableUploadLbl")); + + numberColumn.setEditable(false); + fileNameColumn.setEditable(false); + fileOffsetColumn.setEditable(false); + fileSizeColumn.setEditable(false); + fileHashedRegionSizeColumn.setEditable(false); + uploadColumn.setEditable(true); + + // See https://bugs.openjdk.java.net/browse/JDK-8157687 + numberColumn.setMinWidth(50.0); + numberColumn.setPrefWidth(50.0); + numberColumn.setMaxWidth(50.0); + numberColumn.setResizable(false); + + fileNameColumn.setMinWidth(25.0); + + fileOffsetColumn.setMinWidth(130.0); + fileOffsetColumn.setPrefWidth(130.0); + fileOffsetColumn.setMaxWidth(130.0); + fileOffsetColumn.setResizable(false); + + fileSizeColumn.setMinWidth(120.0); + fileSizeColumn.setPrefWidth(120.0); + fileSizeColumn.setMaxWidth(120.0); + fileSizeColumn.setResizable(false); + + fileHashedRegionSizeColumn.setMinWidth(120.0); + fileHashedRegionSizeColumn.setPrefWidth(120.0); + fileHashedRegionSizeColumn.setMaxWidth(120.0); + fileHashedRegionSizeColumn.setResizable(false); + + filePaddingColumn.setMinWidth(80.0); + filePaddingColumn.setPrefWidth(80.0); + filePaddingColumn.setMaxWidth(80.0); + filePaddingColumn.setResizable(false); + + fileSHA256HashColumn.setMinWidth(600.0); + fileSHA256HashColumn.setPrefWidth(600.0); + fileSHA256HashColumn.setMaxWidth(600.0); + fileSHA256HashColumn.setResizable(false); + + uploadColumn.setMinWidth(120.0); + uploadColumn.setPrefWidth(120.0); + uploadColumn.setMaxWidth(120.0); + uploadColumn.setResizable(false); + + numberColumn.setCellValueFactory(new PropertyValueFactory<>("number")); + fileNameColumn.setCellValueFactory(new PropertyValueFactory<>("fileName")); + fileSizeColumn.setCellValueFactory(new PropertyValueFactory<>("fileSize")); + fileOffsetColumn.setCellValueFactory(new PropertyValueFactory<>("fileOffset")); + filePaddingColumn.setCellValueFactory(new PropertyValueFactory<>("padding")); + fileSHA256HashColumn.setCellValueFactory(new PropertyValueFactory<>("SHA256Hash")); + fileHashedRegionSizeColumn.setCellValueFactory(new PropertyValueFactory<>("hashedRegionSize")); + // >< + uploadColumn.setCellValueFactory(new Callback, ObservableValue>() { + @Override + public ObservableValue call(TableColumn.CellDataFeatures paramFeatures) { + Hfs0RowModel model = paramFeatures.getValue(); + + SimpleBooleanProperty booleanProperty = new SimpleBooleanProperty(model.isMarkSelected()); + + booleanProperty.addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observableValue, Boolean oldValue, Boolean newValue) { + model.setMarkSelected(newValue); + table.refresh(); + } + }); + return booleanProperty; + } + }); + + uploadColumn.setCellFactory(new Callback, TableCell>() { + @Override + public TableCell call(TableColumn paramFeatures) { + CheckBoxTableCell cell = new CheckBoxTableCell<>(); + return cell; + } + }); + table.setRowFactory( // this shit is made to implement context menu. It's such a pain.. + new Callback, TableRow>() { + @Override + public TableRow call(TableView nslHfs0RowModelTableView) { + final TableRow row = new TableRow<>(); + row.setOnMouseClicked(new EventHandler() { // Just.. don't ask.. + @Override + public void handle(MouseEvent mouseEvent) { + if (!row.isEmpty() && mouseEvent.getButton() == MouseButton.PRIMARY){ + Hfs0RowModel thisItem = row.getItem(); + thisItem.setMarkSelected(!thisItem.isMarkSelected()); + table.refresh(); + } + mouseEvent.consume(); + } + }); + return row; + } + } + ); + table.setItems(rowsObsLst); + table.getColumns().addAll(numberColumn, fileNameColumn, fileOffsetColumn, fileSizeColumn, fileHashedRegionSizeColumn, filePaddingColumn, fileSHA256HashColumn, uploadColumn); + } + /** + * Add files when user selected them + * */ + public void setContentToTable(HFS0Provider hfs0){ + rowsObsLst.clear(); + Hfs0RowModel.resetNumCnt(); + if (hfs0 == null) { + table.refresh(); + return; + } + for (int i = 0; i < hfs0.getFilesCnt(); i++){ + rowsObsLst.add(new Hfs0RowModel( + hfs0.getHfs0Files()[i].getName(), + hfs0.getHfs0Files()[i].getSize(), + hfs0.getHfs0Files()[i].getOffset(), + hfs0.getHfs0Files()[i].getHashedRegionSize(), + hfs0.getHfs0Files()[i].isPadding(), + hfs0.getHfs0Files()[i].getSHA256Hash() + )); + } + + table.refresh(); + } + /** + * Return files ready for upload. Requested from NSLMainController only -> uploadBtnAction() //TODO: set undefined + * @return null if no files marked for upload + * List if there are files + * */ + public List getFilesForDump(){ + List models = new ArrayList<>(); + if (rowsObsLst.isEmpty()) + return null; + for (Hfs0RowModel model: rowsObsLst) { + if (model.isMarkSelected()) { + models.add(model); + } + } + return models; + } +} \ No newline at end of file diff --git a/src/main/java/konogonka/Controllers/XCI/XCIController.java b/src/main/java/konogonka/Controllers/XCI/XCIController.java new file mode 100644 index 0000000..86b58f5 --- /dev/null +++ b/src/main/java/konogonka/Controllers/XCI/XCIController.java @@ -0,0 +1,273 @@ +package konogonka.Controllers.XCI; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import konogonka.AppPreferences; +import konogonka.Controllers.TabController; +import konogonka.Tools.XCI.XCIProvider; +import konogonka.Workers.AnalyzerXCI; + +import java.io.File; +import java.net.URL; +import java.util.ResourceBundle; + +import static konogonka.LoperConverter.byteArrToHexString; + +public class XCIController implements TabController { + + /* Header */ + @FXML + private TextField rsa2048pkcs1TF, + secureAreaStartAddrTF, + pkgIdTF, + gcInfoIVTF, + Hfs0headerSHA256Lbl, + Hfs0initDataSHA256Lbl, + normAreaEndAddrTF; + @FXML + private Label headLbl, + bkupAreaStartAddrLbl, + titleKEKIndexLbl, + gcSizeLbl, + gcHdrVersLbl, + gcFlags, + vDataEndAddrLbl, + hfs0partOffLbl, + hfs0hdrSizeLbl, + secureModeFlagLbl, + titleKeyFlagLbl, + keyFlagLbl; + /* GC Info */ + @FXML + private TextField fwModeTF, + cupVersionTF, + emptyPadding1TF, + updPartHashTF, + cupIDTF, + emptyPadding2TF; + @FXML + private Label fwVersionLbl, + accessCtrlFlagsLbl, + readWaitTime1Lbl, + readWaitTime2Lbl, + writeWaitTime1, + writeWaitTime2, + emptyPadding1Lbl, + emptyPadding2Lbl; + /* GC Cert */ + @FXML + private TextField rsa2048PKCS1sigCertTF, + magicCertCertTF, + unknown1CertTF, + unknown2CertTF, + deviceIDCertTF, + unknown3CertTF, + encryptedDataCertTF; + @FXML + private Label kekIndexCertLbl, + magicCertCertOkLbl; + + @FXML + private HFSBlockController + HFSBlockMainController, + HFSBlockUpdateController, + HFSBlockNormalController, + HFSBlockSecureController, + HFSBlockLogoController; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + HFSBlockMainController.setTitlePaneTypeName("main", "HFS0 (Primary): 0xF000"); + HFSBlockUpdateController.setTitlePaneTypeName("update", "HFS0 update"); + HFSBlockNormalController.setTitlePaneTypeName("normal", "HFS0 normal"); + HFSBlockSecureController.setTitlePaneTypeName("secure", "HFS0 secure"); + HFSBlockLogoController.setTitlePaneTypeName("logo", "HFS0 logo"); + } + + /** + * Start analyze XCI + * */ + @Override + public void analyze(File selectedFile){ + HFSBlockController.setSelectedFile(selectedFile); + + AnalyzerXCI analyzerXCI = new AnalyzerXCI(selectedFile, AppPreferences.getInstance().getXciHeaderKey()); + analyzerXCI.setOnSucceeded(e->{ + populateFields(analyzerXCI.getValue()); + }); + Thread workThread = new Thread(analyzerXCI); + workThread.setDaemon(true); + workThread.start(); + } + @Override + public void resetTab(){ + HFSBlockController.setSelectedFile(null); + /* Header */ + rsa2048pkcs1TF.setText("-"); + headLbl.setText("-"); + secureAreaStartAddrTF.setText("-"); + bkupAreaStartAddrLbl.setText("-"); + titleKEKIndexLbl.setText("-"); + gcSizeLbl.setText("-"); + gcHdrVersLbl.setText("-"); + gcFlags.setText("-"); + pkgIdTF.setText("-"); + vDataEndAddrLbl.setText("-"); + gcInfoIVTF.setText("-"); + hfs0partOffLbl.setText("-"); + hfs0hdrSizeLbl.setText("-"); + Hfs0headerSHA256Lbl.setText("-"); + Hfs0initDataSHA256Lbl.setText("-"); + secureModeFlagLbl.setText("-"); + titleKeyFlagLbl.setText("-"); + keyFlagLbl.setText("-"); + normAreaEndAddrTF.setText("-"); + /* GC Info */ + fwVersionLbl.setText("-"); + accessCtrlFlagsLbl.setText("-"); + readWaitTime1Lbl.setText("-"); + readWaitTime2Lbl.setText("-"); + writeWaitTime1.setText("-"); + writeWaitTime2.setText("-"); + fwModeTF.setText("-"); + cupVersionTF.setText("-"); + emptyPadding1TF.setText("-"); + updPartHashTF.setText("-"); + cupIDTF.setText("-"); + emptyPadding2TF.setText("-"); + emptyPadding1Lbl.setText("-"); + emptyPadding2Lbl.setText("-"); + + /* Certificate */ + rsa2048PKCS1sigCertTF.setText("-"); + magicCertCertTF.setText("-"); + magicCertCertOkLbl.setText("-"); + unknown1CertTF.setText("-"); + kekIndexCertLbl.setText("-"); + unknown2CertTF.setText("-"); + deviceIDCertTF.setText("-"); + unknown3CertTF.setText("-"); + encryptedDataCertTF.setText("-"); + + /* HFS0 */ + HFSBlockMainController.resetTab(); + HFSBlockUpdateController.resetTab(); + HFSBlockNormalController.resetTab(); + HFSBlockSecureController.resetTab(); + HFSBlockLogoController.resetTab(); + } + private void populateFields(XCIProvider xci){ + if (xci != null){ + /* Header */ + rsa2048pkcs1TF.setText(byteArrToHexString(xci.getGCHeader().getRsa2048PKCS1sig())); + //RainbowHexDump.hexDumpUTF8(xci.getGCHeader().getRsa2048PKCS1sig()); + headLbl.setText(Boolean.toString(xci.getGCHeader().isMagicHeadOk())); + //System.out.println(xci.getGCHeader().isMagicHeadOk()); + secureAreaStartAddrTF.setText(byteArrToHexString(xci.getGCHeader().getSecureAreaStartAddr())); + //System.out.println(xci.getGCHeader().isSecureAreaStartAddrOk()); + bkupAreaStartAddrLbl.setText(Boolean.toString(xci.getGCHeader().isBkupAreaStartAddrOk())); + //System.out.println(xci.getGCHeader().isBkupAreaStartAddrOk()); + titleKEKIndexLbl.setText(String.format("%02x", xci.getGCHeader().getTitleKEKIndexBoth())+": " + +String.format("%02x", xci.getGCHeader().getTitleKEKIndex())+" " + +String.format("%02x", xci.getGCHeader().getKEKIndex()) + ); + //System.out.print(String.format("%02x", xci.getGCHeader().getTitleKEKIndexBoth())+" "); + //System.out.print(String.format("%02x", xci.getGCHeader().getTitleKEKIndex())+" "); + //System.out.println(String.format("%02x", xci.getGCHeader().getKEKIndex())); + switch (xci.getGCHeader().getGcSize()){ + case (byte) 0xFA: // 1 GB + gcSizeLbl.setText("1 GB"); + break; + case (byte) 0xF8: // 2 GB + gcSizeLbl.setText("2 GB"); + break; + case (byte) 0xF0: // 4 GB + gcSizeLbl.setText("4 GB"); + break; + case (byte) 0xE0: // 8 GB + gcSizeLbl.setText("8 GB"); + break; + case (byte) 0xE1: // 16 GB + gcSizeLbl.setText("16 GB"); + break; + case (byte) 0xE2: // 32 GB + gcSizeLbl.setText("32 GB"); + break; + default: + gcSizeLbl.setText("? "+String.format("%02x", xci.getGCHeader().getGcSize())); + } + //System.out.println(String.format("%02x", xci.getGCHeader().getGcSize())); + gcHdrVersLbl.setText(String.format("%02x", xci.getGCHeader().getGcVersion())); + //System.out.println(String.format("%02x", xci.getGCHeader().getGcVersion())); + gcFlags.setText(String.format("%02x", xci.getGCHeader().getGcFlags())); + //System.out.println(String.format("%02x", xci.getGCHeader().getGcFlags())); + pkgIdTF.setText(byteArrToHexString(xci.getGCHeader().getPkgID())); + //RainbowHexDump.hexDumpUTF8(xci.getGCHeader().getPkgID()); + vDataEndAddrLbl.setText(Long.toString(xci.getGCHeader().getValDataEndAddr())); + //System.out.println(xci.getGCHeader().getValDataEndAddr()); + gcInfoIVTF.setText(byteArrToHexString(xci.getGCHeader().getGcInfoIV())); + //RainbowHexDump.hexDumpUTF8(xci.getGCHeader().getGcInfoIV()); + hfs0partOffLbl.setText(Long.toString(xci.getGCHeader().getHfs0partOffset())); + //System.out.println(xci.getGCHeader().getHfs0partOffset()); + hfs0hdrSizeLbl.setText(Long.toString(xci.getGCHeader().getHfs0headerSize())); + //System.out.println(xci.getGCHeader().getHfs0headerSize()); + Hfs0headerSHA256Lbl.setText(byteArrToHexString(xci.getGCHeader().getHfs0headerSHA256())); + //RainbowHexDump.hexDumpUTF8(xci.getGCHeader().getHfs0headerSHA256()); + Hfs0initDataSHA256Lbl.setText(byteArrToHexString(xci.getGCHeader().getHfs0initDataSHA256())); + //RainbowHexDump.hexDumpUTF8(xci.getGCHeader().getHfs0initDataSHA256() ); + secureModeFlagLbl.setText(xci.getGCHeader().isSecureModeFlagOk()+" ("+xci.getGCHeader().getSecureModeFlag()+")"); + //System.out.print(xci.getGCHeader().getSecureModeFlag()); + //System.out.println(xci.getGCHeader().isSecureModeFlagOk()); + titleKeyFlagLbl.setText(xci.getGCHeader().istitleKeyFlagOk()+" ("+xci.getGCHeader().getTitleKeyFlag()+")"); + //System.out.print(xci.getGCHeader().getTitleKeyFlag()); + //System.out.println(xci.getGCHeader().istitleKeyFlagOk()); + keyFlagLbl.setText(xci.getGCHeader().iskeyFlagOk()+" ("+xci.getGCHeader().getKeyFlag()+")"); + //System.out.print(xci.getGCHeader().getKeyFlag()); + //System.out.println(xci.getGCHeader().iskeyFlagOk()); + normAreaEndAddrTF.setText(byteArrToHexString(xci.getGCHeader().getNormAreaEndAddr())); + //System.out.println(xci.getGCHeader().isNormAreaEndAddrOk()); + + /* GC Info */ + fwVersionLbl.setText(Long.toString(xci.getGCInfo().getFwVersion())); + String tempACF = byteArrToHexString(xci.getGCInfo().getAccessCtrlFlags()); + if (tempACF.equals("1100a100")) + accessCtrlFlagsLbl.setText("25Mhz [0x1100a100]"); + else if(tempACF.equals("1000a100")) + accessCtrlFlagsLbl.setText("50Mhz [0x1000a100]"); + else + accessCtrlFlagsLbl.setText("??? ["+tempACF+"]"); + readWaitTime1Lbl.setText(Long.toString(xci.getGCInfo().getReadWaitTime1())); + readWaitTime2Lbl.setText(Long.toString(xci.getGCInfo().getReadWaitTime2())); + writeWaitTime1.setText(Long.toString(xci.getGCInfo().getWriteWaitTime1())); + writeWaitTime2.setText(Long.toString(xci.getGCInfo().getWriteWaitTime2())); + fwModeTF.setText(byteArrToHexString(xci.getGCInfo().getFwMode())); + cupVersionTF.setText(byteArrToHexString(xci.getGCInfo().getCupVersion())); + emptyPadding1Lbl.setText(Boolean.toString(xci.getGCInfo().isEmptyPadding1())); + emptyPadding1TF.setText(byteArrToHexString(xci.getGCInfo().getEmptyPadding1())); + updPartHashTF.setText(byteArrToHexString(xci.getGCInfo().getUpdPartHash())); + cupIDTF.setText(byteArrToHexString(xci.getGCInfo().getCupID())); + emptyPadding2Lbl.setText(Boolean.toString(xci.getGCInfo().isEmptyPadding2())); + emptyPadding2TF.setText(byteArrToHexString(xci.getGCInfo().getEmptyPadding2())); + + /* Certificate */ + rsa2048PKCS1sigCertTF.setText(byteArrToHexString(xci.getGCCert().getRsa2048PKCS1sig())); + magicCertCertTF.setText(byteArrToHexString(xci.getGCCert().getMagicCert())); + magicCertCertOkLbl.setText(Boolean.toString(xci.getGCCert().isMagicCertOk())); + unknown1CertTF.setText(byteArrToHexString(xci.getGCCert().getUnknown1())); + kekIndexCertLbl.setText(String.format("%02x", xci.getGCCert().getKekIndex())); + unknown2CertTF.setText(byteArrToHexString(xci.getGCCert().getUnknown2())); + deviceIDCertTF.setText(byteArrToHexString(xci.getGCCert().getDeviceID())); + unknown3CertTF.setText(byteArrToHexString(xci.getGCCert().getUnknown3())); + encryptedDataCertTF.setText(byteArrToHexString(xci.getGCCert().getEncryptedData())); + + /* HFS0 */ + HFSBlockMainController.populateTab(xci.getHfs0ProviderMain()); + HFSBlockUpdateController.populateTab(xci.getHfs0ProviderUpdate()); + HFSBlockNormalController.populateTab(xci.getHfs0ProviderNormal()); + HFSBlockSecureController.populateTab(xci.getHfs0ProviderSecure()); + HFSBlockLogoController.populateTab(xci.getHfs0ProviderLogo()); + } + } +} \ No newline at end of file diff --git a/src/main/java/konogonka/LoperConverter.java b/src/main/java/konogonka/LoperConverter.java new file mode 100644 index 0000000..f1c2c57 --- /dev/null +++ b/src/main/java/konogonka/LoperConverter.java @@ -0,0 +1,22 @@ +package konogonka; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class LoperConverter { + public static int getLEint(byte[] bytes, int fromOffset){ + return ByteBuffer.wrap(bytes, fromOffset, 0x4).order(ByteOrder.LITTLE_ENDIAN).getInt(); + } + + public static long getLElong(byte[] bytes, int fromOffset){ + return ByteBuffer.wrap(bytes, fromOffset, 0x8).order(ByteOrder.LITTLE_ENDIAN).getLong(); + } + public static String byteArrToHexString(byte[] bArr){ + if (bArr == null) + return ""; + StringBuilder sb = new StringBuilder(); + for (byte b: bArr) + sb.append(String.format("%02x", b)); + return sb.toString(); + } +} diff --git a/src/main/java/konogonka/Main.java b/src/main/java/konogonka/Main.java new file mode 100644 index 0000000..140e06f --- /dev/null +++ b/src/main/java/konogonka/Main.java @@ -0,0 +1,5 @@ +package konogonka; + +public class Main { + public static void main(String[] args){ MainFx.main(args); } +} diff --git a/src/main/java/konogonka/MainFx.java b/src/main/java/konogonka/MainFx.java new file mode 100644 index 0000000..f21fdbf --- /dev/null +++ b/src/main/java/konogonka/MainFx.java @@ -0,0 +1,52 @@ +package konogonka; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Stage; +import konogonka.Controllers.MainController; + +import java.util.Locale; +import java.util.ResourceBundle; + +public class MainFx extends Application { + public static final String appVersion = "v0.1-DEV"; + + @Override + public void start(Stage primaryStage) throws Exception{ + + FXMLLoader loader = new FXMLLoader(getClass().getResource("/FXML/landingPage.fxml")); + + Locale userLocale = new Locale(Locale.getDefault().getISO3Language()); // NOTE: user locale based on ISO3 Language codes + ResourceBundle rb = ResourceBundle.getBundle("locale", userLocale); + + loader.setResources(rb); + + Parent root = loader.load(); + + primaryStage.getIcons().addAll( + new Image(getClass().getResourceAsStream("/res/app_icon32x32.png")), + new Image(getClass().getResourceAsStream("/res/app_icon48x48.png")), + new Image(getClass().getResourceAsStream("/res/app_icon64x64.png")), + new Image(getClass().getResourceAsStream("/res/app_icon128x128.png")) + ); + primaryStage.setTitle("konogonka "+appVersion); + + Scene mainScene = new Scene(root, 1200, 800); + mainScene.getStylesheets().add("/res/app_light.css"); + + primaryStage.setScene(mainScene); + primaryStage.setMinWidth(1000.0); + primaryStage.setMinHeight(750.0); + primaryStage.show(); + + MainController controller = loader.getController(); + primaryStage.setOnHidden(e-> controller.exit()); + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/src/main/java/konogonka/MediatorControl.java b/src/main/java/konogonka/MediatorControl.java new file mode 100644 index 0000000..ad6c1ae --- /dev/null +++ b/src/main/java/konogonka/MediatorControl.java @@ -0,0 +1,19 @@ +package konogonka; + +import konogonka.Controllers.MainController; + +public class MediatorControl { + private MainController applicationController; + + public static MediatorControl getInstance(){ + return MediatorControlHold.INSTANCE; + } + + private static class MediatorControlHold { + private static final MediatorControl INSTANCE = new MediatorControl(); + } + public void setController(MainController controller){ + this.applicationController = controller; + } + public MainController getContoller(){ return this.applicationController; } +} diff --git a/src/main/java/konogonka/ModelControllers/EMsgType.java b/src/main/java/konogonka/ModelControllers/EMsgType.java new file mode 100644 index 0000000..c6dada0 --- /dev/null +++ b/src/main/java/konogonka/ModelControllers/EMsgType.java @@ -0,0 +1,5 @@ +package konogonka.ModelControllers; + +public enum EMsgType { + PASS, FAIL, INFO, WARNING +} diff --git a/src/main/java/konogonka/ModelControllers/LogPrinter.java b/src/main/java/konogonka/ModelControllers/LogPrinter.java new file mode 100644 index 0000000..930d18f --- /dev/null +++ b/src/main/java/konogonka/ModelControllers/LogPrinter.java @@ -0,0 +1,54 @@ +package konogonka.ModelControllers; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public class LogPrinter { + private MessagesConsumer msgConsumer; + private BlockingQueue msgQueue; + private BlockingQueue progressQueue; + + public LogPrinter(){ + this.msgQueue = new LinkedBlockingQueue<>(); + this.progressQueue = new LinkedBlockingQueue<>(); + this.msgConsumer = new MessagesConsumer(this.msgQueue, this.progressQueue); + this.msgConsumer.start(); + } + /** + * This is what will print to textArea of the application. + * */ + public void print(String message, EMsgType type){ + try { + switch (type){ + case PASS: + msgQueue.put("[ PASS ] "+message+"\n"); + break; + case FAIL: + msgQueue.put("[ FAIL ] "+message+"\n"); + break; + case INFO: + msgQueue.put("[ INFO ] "+message+"\n"); + break; + case WARNING: + msgQueue.put("[ WARN ] "+message+"\n"); + break; + default: + msgQueue.put(message); + } + }catch (InterruptedException ie){ + ie.printStackTrace(); + } + } + /** + * Update progress for progress bar + * */ + public void updateProgress(Double value) throws InterruptedException{ + progressQueue.put(value); + } + /** + * When we're done - close it + * */ + public void close(){ + msgConsumer.interrupt(); + } +} diff --git a/src/main/java/konogonka/ModelControllers/MessagesConsumer.java b/src/main/java/konogonka/ModelControllers/MessagesConsumer.java new file mode 100644 index 0000000..73d03ce --- /dev/null +++ b/src/main/java/konogonka/ModelControllers/MessagesConsumer.java @@ -0,0 +1,63 @@ +package konogonka.ModelControllers; + +import javafx.animation.AnimationTimer; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.TextArea; +import konogonka.MediatorControl; + +import java.util.ArrayList; +import java.util.concurrent.BlockingQueue; + +public class MessagesConsumer extends AnimationTimer { + private final BlockingQueue msgQueue; + private final TextArea logsArea; + + private final BlockingQueue progressQueue; + private final ProgressBar progressBar; + + private boolean isInterrupted; + + MessagesConsumer(BlockingQueue msgQueue, BlockingQueue progressQueue){ + this.isInterrupted = false; + + this.msgQueue = msgQueue; + this.logsArea = MediatorControl.getInstance().getContoller().logArea; + + this.progressQueue = progressQueue; + this.progressBar = MediatorControl.getInstance().getContoller().progressBar; + + progressBar.setProgress(0.0); + + progressBar.setProgress(ProgressIndicator.INDETERMINATE_PROGRESS); + } + + @Override + public void handle(long l) { + ArrayList messages = new ArrayList<>(); + int msgRecieved = msgQueue.drainTo(messages); + if (msgRecieved > 0) + messages.forEach(msg -> logsArea.appendText(msg)); + + ArrayList progress = new ArrayList<>(); + int progressRecieved = progressQueue.drainTo(progress); + if (progressRecieved > 0) { + progress.forEach(prg -> { + if (prg != 1.0) + progressBar.setProgress(prg); + else + progressBar.setProgress(ProgressIndicator.INDETERMINATE_PROGRESS); + }); + } + + if (isInterrupted) { + progressBar.setProgress(0.0); + + this.stop(); + } + } + + public void interrupt(){ + this.isInterrupted = true; + } +} \ No newline at end of file diff --git a/src/main/java/konogonka/RainbowHexDump.java b/src/main/java/konogonka/RainbowHexDump.java new file mode 100644 index 0000000..9a29400 --- /dev/null +++ b/src/main/java/konogonka/RainbowHexDump.java @@ -0,0 +1,32 @@ +package konogonka; + +import java.nio.charset.StandardCharsets; + +/** + * Debug tool like hexdump <3 + */ +public class RainbowHexDump { + private static final String ANSI_RESET = "\u001B[0m"; + private static final String ANSI_BLACK = "\u001B[30m"; + private static final String ANSI_RED = "\u001B[31m"; + private static final String ANSI_GREEN = "\u001B[32m"; + private static final String ANSI_YELLOW = "\u001B[33m"; + private static final String ANSI_BLUE = "\u001B[34m"; + private static final String ANSI_PURPLE = "\u001B[35m"; + private static final String ANSI_CYAN = "\u001B[36m"; + private static final String ANSI_WHITE = "\u001B[37m"; + + + public static void hexDumpUTF8(byte[] byteArray){ + if (byteArray == null || byteArray.length == 0) + return; + System.out.print(ANSI_BLUE); + for (int i=0; i < byteArray.length; i++) + System.out.print(String.format("%02d-", i%100)); + System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET); + for (byte b: byteArray) + System.out.print(String.format("%02x ", b)); + System.out.println(); + System.out.print(new String(byteArray, StandardCharsets.UTF_8)+"\n"); + } +} diff --git a/src/main/java/konogonka/ServiceWindow.java b/src/main/java/konogonka/ServiceWindow.java new file mode 100644 index 0000000..33358b6 --- /dev/null +++ b/src/main/java/konogonka/ServiceWindow.java @@ -0,0 +1,52 @@ +package konogonka; + +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.layout.Region; +import javafx.stage.Stage; + +import java.util.Optional; + +public class ServiceWindow { + /** Create window with error notification */ + public static void getErrorNotification(String title, String body){ + getNotification(title, body, Alert.AlertType.ERROR); + } + /** Create window with information notification */ + public static void getInfoNotification(String title, String body){ + getNotification(title, body, Alert.AlertType.INFORMATION); + } + /** Real window creator */ + private static void getNotification(String title, String body, Alert.AlertType type){ + Alert alertBox = new Alert(type); + alertBox.setTitle(title); + alertBox.setHeaderText(null); + alertBox.setContentText(body); + alertBox.getDialogPane().setMinWidth(Region.USE_PREF_SIZE); + alertBox.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alertBox.setResizable(true); // Java bug workaround for JDR11/OpenJFX. TODO: nothing. really. + alertBox.getDialogPane().getStylesheets().add("/res/app_light.css"); + + Stage dialogStage = (Stage) alertBox.getDialogPane().getScene().getWindow(); + dialogStage.setAlwaysOnTop(true); + dialogStage.toFront(); + + alertBox.show(); + } + /** + * Create notification window with confirm/deny + * */ + public static boolean getConfirmationWindow(String title, String body){ + Alert alertBox = new Alert(Alert.AlertType.CONFIRMATION); + alertBox.setTitle(title); + alertBox.setHeaderText(null); + alertBox.setContentText(body); + alertBox.getDialogPane().setMinWidth(Region.USE_PREF_SIZE); + alertBox.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alertBox.setResizable(true); // Java bug workaround for JDR11/OpenJFX. TODO: nothing. really. + alertBox.getDialogPane().getStylesheets().add("/res/app_light.css"); + Optional result = alertBox.showAndWait(); + + return (result.isPresent() && result.get() == ButtonType.OK); + } +} diff --git a/src/main/java/konogonka/Settings/SettingsController.java b/src/main/java/konogonka/Settings/SettingsController.java new file mode 100644 index 0000000..ef1908c --- /dev/null +++ b/src/main/java/konogonka/Settings/SettingsController.java @@ -0,0 +1,183 @@ +package konogonka.Settings; + +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.TextField; +import javafx.scene.control.TextFormatter; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import konogonka.AppPreferences; + + +import java.io.*; +import java.net.URL; +import java.util.HashMap; +import java.util.ResourceBundle; + +public class SettingsController implements Initializable { + @FXML + private Button okBtn, cancelBtn, importBtn; + @FXML + private TextField + xciHdrKeyTF, + hdrKeyTF, + keyApp0TF, + keyApp1TF, + keyApp2TF, + keyApp3TF, + keyApp4TF, + keyApp5TF, + keyOcean0TF, + keyOcean1TF, + keyOcean2TF, + keyOcean3TF, + keyOcean4TF, + keyOcean5TF, + keySys0TF, + keySys1TF, + keySys2TF, + keySys3TF, + keySys4TF, + keySys5TF; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + xciHdrKeyTF.setText(AppPreferences.getInstance().getXciHeaderKey()); + hdrKeyTF.setText(AppPreferences.getInstance().getHeaderKey()); + keyApp0TF.setText(AppPreferences.getInstance().getApplicationKey(0)); + keyApp1TF.setText(AppPreferences.getInstance().getApplicationKey(1)); + keyApp2TF.setText(AppPreferences.getInstance().getApplicationKey(2)); + keyApp3TF.setText(AppPreferences.getInstance().getApplicationKey(3)); + keyApp4TF.setText(AppPreferences.getInstance().getApplicationKey(4)); + keyApp5TF.setText(AppPreferences.getInstance().getApplicationKey(5)); + keyOcean0TF.setText(AppPreferences.getInstance().getOceanKey(0)); + keyOcean1TF.setText(AppPreferences.getInstance().getOceanKey(1)); + keyOcean2TF.setText(AppPreferences.getInstance().getOceanKey(2)); + keyOcean3TF.setText(AppPreferences.getInstance().getOceanKey(3)); + keyOcean4TF.setText(AppPreferences.getInstance().getOceanKey(4)); + keyOcean5TF.setText(AppPreferences.getInstance().getOceanKey(5)); + keySys0TF.setText(AppPreferences.getInstance().getSystemKey(0)); + keySys1TF.setText(AppPreferences.getInstance().getSystemKey(1)); + keySys2TF.setText(AppPreferences.getInstance().getSystemKey(2)); + keySys3TF.setText(AppPreferences.getInstance().getSystemKey(3)); + keySys4TF.setText(AppPreferences.getInstance().getSystemKey(4)); + keySys5TF.setText(AppPreferences.getInstance().getSystemKey(5)); + + setTextValidation(xciHdrKeyTF); + setTextValidation(hdrKeyTF); + setTextValidation(keyApp0TF); + setTextValidation(keyApp1TF); + setTextValidation(keyApp2TF); + setTextValidation(keyApp3TF); + setTextValidation(keyApp4TF); + setTextValidation(keyApp5TF); + setTextValidation(keyOcean0TF); + setTextValidation(keyOcean1TF); + setTextValidation(keyOcean2TF); + setTextValidation(keyOcean3TF); + setTextValidation(keyOcean4TF); + setTextValidation(keyOcean5TF); + setTextValidation(keySys0TF); + setTextValidation(keySys1TF); + setTextValidation(keySys2TF); + setTextValidation(keySys3TF); + setTextValidation(keySys4TF); + setTextValidation(keySys5TF); + + importBtn.setOnAction(e->{ + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("prod.keys"); + fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("prod.keys", "prod.keys")); + + File prodKeysFile = fileChooser.showOpenDialog(importBtn.getScene().getWindow()); + + if (prodKeysFile != null && prodKeysFile.exists()) { + HashMap fileMap = new HashMap<>(); + try { + BufferedReader br = new BufferedReader( + new FileReader(prodKeysFile) + ); + + String fileLine; + String[] keyValue; + while ((fileLine = br.readLine()) != null){ + keyValue = fileLine.trim().split("\\s+?=\\s+?", 2); + if (keyValue.length == 2) + fileMap.put(keyValue[0], keyValue[1]); + } + hdrKeyTF.setText(fileMap.get("header_key")); + keyApp0TF.setText(fileMap.get("key_area_key_application_00")); + keyApp1TF.setText(fileMap.get("key_area_key_application_01")); + keyApp2TF.setText(fileMap.get("key_area_key_application_02")); + keyApp3TF.setText(fileMap.get("key_area_key_application_03")); + keyApp4TF.setText(fileMap.get("key_area_key_application_04")); + keyApp5TF.setText(fileMap.get("key_area_key_application_05")); + keyOcean0TF.setText(fileMap.get("key_area_key_ocean_00")); + keyOcean1TF.setText(fileMap.get("key_area_key_ocean_01")); + keyOcean2TF.setText(fileMap.get("key_area_key_ocean_02")); + keyOcean3TF.setText(fileMap.get("key_area_key_ocean_03")); + keyOcean4TF.setText(fileMap.get("key_area_key_ocean_04")); + keyOcean5TF.setText(fileMap.get("key_area_key_ocean_05")); + keySys0TF.setText(fileMap.get("key_area_key_system_00")); + keySys1TF.setText(fileMap.get("key_area_key_system_01")); + keySys2TF.setText(fileMap.get("key_area_key_system_02")); + keySys3TF.setText(fileMap.get("key_area_key_system_03")); + keySys4TF.setText(fileMap.get("key_area_key_system_04")); + keySys5TF.setText(fileMap.get("key_area_key_system_05")); + } + catch (IOException ioe){ + ioe.printStackTrace(); + } + /* + for (String key: fileMap.keySet()){ + System.out.print(key+ " - "); + System.out.println(fileMap.get(key)); + } + //*/ + } + }); + + cancelBtn.setOnAction(e->{ + Stage thisStage = (Stage)cancelBtn.getScene().getWindow(); + thisStage.close(); + }); + + okBtn.setOnAction(e->{ + Stage thisStage = (Stage)cancelBtn.getScene().getWindow(); + AppPreferences.getInstance().setAll( + xciHdrKeyTF.getText(), + hdrKeyTF.getText(), + keyApp0TF.getText(), + keyApp1TF.getText(), + keyApp2TF.getText(), + keyApp3TF.getText(), + keyApp4TF.getText(), + keyApp5TF.getText(), + keyOcean0TF.getText(), + keyOcean1TF.getText(), + keyOcean2TF.getText(), + keyOcean3TF.getText(), + keyOcean4TF.getText(), + keyOcean5TF.getText(), + keySys0TF.getText(), + keySys1TF.getText(), + keySys2TF.getText(), + keySys3TF.getText(), + keySys4TF.getText(), + keySys5TF.getText() + ); + thisStage.close(); + }); + } + + private void setTextValidation(TextField tf){ + tf.setTextFormatter(new TextFormatter(change -> { + if (change.getControlNewText().contains(" ") | change.getControlNewText().contains("\t")) + return null; + else + return change; + })); + } +} \ No newline at end of file diff --git a/src/main/java/konogonka/Settings/SettingsWindow.java b/src/main/java/konogonka/Settings/SettingsWindow.java new file mode 100644 index 0000000..eea6b76 --- /dev/null +++ b/src/main/java/konogonka/Settings/SettingsWindow.java @@ -0,0 +1,50 @@ +package konogonka.Settings; + +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Stage; +import konogonka.MainFx; + +import java.io.IOException; +import java.util.Locale; +import java.util.ResourceBundle; + +public class SettingsWindow { + public SettingsWindow(){ + Stage stageSettings = new Stage(); + + stageSettings.setMinWidth(570); + stageSettings.setMinHeight(500); + + FXMLLoader loaderSettings = new FXMLLoader(getClass().getResource("/FXML/Settings/SettingsLayout.fxml")); + + Locale userLocale = new Locale(Locale.getDefault().getISO3Language()); + + ResourceBundle resourceBundle = ResourceBundle.getBundle("locale", userLocale); + + loaderSettings.setResources(resourceBundle); + + try { + Parent parentAbout = loaderSettings.load(); + + stageSettings.setTitle(resourceBundle.getString("settings_SettingsName")); + + stageSettings.getIcons().addAll( + new Image(MainFx.class.getResourceAsStream("/res/settings_icon32x32.png")), + new Image(MainFx.class.getResourceAsStream("/res/settings_icon48x48.png")), + new Image(MainFx.class.getResourceAsStream("/res/settings_icon64x64.png")), + new Image(MainFx.class.getResourceAsStream("/res/settings_icon128x128.png")) + ); + + stageSettings.setScene(new Scene(parentAbout, 570, 500)); + stageSettings.setMinWidth(550.0); + stageSettings.setMinHeight(550.0); + stageSettings.show(); + + } catch (IOException e){ + e.printStackTrace(); + } + } +} diff --git a/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java b/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java new file mode 100644 index 0000000..dfbb6b0 --- /dev/null +++ b/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java @@ -0,0 +1,126 @@ +package konogonka.Tools.NCA; + +import konogonka.RainbowHexDump; +import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock; +import konogonka.Tools.PFS0.PFS0Provider; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.File; +import java.io.RandomAccessFile; +import java.security.Security; +import java.util.LinkedList; + +public class NCAContentPFS0 { + private LinkedList SHA256hashes; + private PFS0Provider pfs0; + + public NCAContentPFS0(File file, long offsetPosition, NCASectionBlock ncaSectionBlock, NCAHeaderTableEntry ncaHeaderTableEntry, byte[] decryptedKey){ + SHA256hashes = new LinkedList<>(); + try { + RandomAccessFile raf = new RandomAccessFile(file, "r"); + // If it's PFS0Provider + if (ncaSectionBlock.getSuperBlockPFS0() != null){ + // IF NO ENCRYPTION + if (ncaSectionBlock.getCryptoType() == 0x1) { + long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200); + long hashTableLocation = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(); + long pfs0Location = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(); + + raf.seek(hashTableLocation); + + byte[] rawData; + long sha256recordsNumber = ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20; + // Collect hashes + for (int i = 0; i < sha256recordsNumber; i++){ + rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash + if (raf.read(rawData) != -1) + SHA256hashes.add(rawData); + else + break; // TODO: fix + } + // Get pfs0 + raf.seek(pfs0Location); + pfs0 = new PFS0Provider(file, pfs0Location); + + raf.close(); + } + // If encrypted (regular) + else if (ncaSectionBlock.getCryptoType() == 0x3){ + long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200); // todo: use this location for CTR + long hashTableLocation = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(); + long pfs0Location = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(); + + raf.seek(hashTableLocation); + + byte[] rawData; + long sha256recordsNumber = ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20; + // Collect hashes + for (int i = 0; i < sha256recordsNumber; i++){ + rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash + if (raf.read(rawData) != -1) + SHA256hashes.add(rawData); + else + break; // TODO: fix + } + // Get pfs0 + raf.seek(pfs0Location); + + rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash + + if (raf.read(rawData) != -1) { + System.out.println("Encrypted"); + RainbowHexDump.hexDumpUTF8(rawData); + } + try { + /* + System.out.println("Decrypted?"); + Security.addProvider(new BouncyCastleProvider()); // TODO: DO FUCKING REMEMBER THIS SHIT FOR CTR + IvParameterSpec iv = new IvParameterSpec(new byte[10]); + SecretKeySpec key = new SecretKeySpec(decryptedKey, "AES"); + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC"); + cipher.init(Cipher.DECRYPT_MODE, key, iv); + byte[] decr = cipher.doFinal(rawData); + RainbowHexDump.hexDumpUTF8(decr); + + */ + } + catch (Exception e){ + e.printStackTrace(); + } + + + + + + + + + + + + + + + + + raf.close(); + } + } + else if (ncaSectionBlock.getSuperBlockIVFC() != null){ + + } + else { + return; // TODO: FIX THIS STUFF + } + } + catch (Exception e){ + e.printStackTrace(); + } + } + + public LinkedList getSHA256hashes() { return SHA256hashes; } + public PFS0Provider getPfs0() { return pfs0; } +} diff --git a/src/main/java/konogonka/Tools/NCA/NCAHeaderTableEntry.java b/src/main/java/konogonka/Tools/NCA/NCAHeaderTableEntry.java new file mode 100644 index 0000000..326bf5d --- /dev/null +++ b/src/main/java/konogonka/Tools/NCA/NCAHeaderTableEntry.java @@ -0,0 +1,35 @@ +package konogonka.Tools.NCA; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +public class NCAHeaderTableEntry { + + private long mediaStartOffset; + private long mediaEndOffset; + private byte[] unknwn1; + private byte[] unknwn2; + + public NCAHeaderTableEntry(byte[] table) throws Exception{ + if (table.length < 0x10) + throw new Exception("Section Table size is too small."); + + this.mediaStartOffset = convertUnsignedIntBytesToLong(Arrays.copyOfRange(table, 0x0, 0x4)); + this.mediaEndOffset = convertUnsignedIntBytesToLong(Arrays.copyOfRange(table, 0x4, 0x8)); + this.unknwn1 = Arrays.copyOfRange(table, 0x8, 0xC); + this.unknwn2 = Arrays.copyOfRange(table, 0xC, 0x10); + } + + private long convertUnsignedIntBytesToLong(byte[] intBytes){ + if (intBytes.length == 4) + return ByteBuffer.wrap(Arrays.copyOf(intBytes, 8)).order(ByteOrder.LITTLE_ENDIAN).getLong(); + else + return -1; + } + + public long getMediaStartOffset() { return mediaStartOffset; } + public long getMediaEndOffset() { return mediaEndOffset; } + public byte[] getUnknwn1() { return unknwn1; } + public byte[] getUnknwn2() { return unknwn2; } +} \ No newline at end of file diff --git a/src/main/java/konogonka/Tools/NCA/NCAProvider.java b/src/main/java/konogonka/Tools/NCA/NCAProvider.java new file mode 100644 index 0000000..6e2ceb6 --- /dev/null +++ b/src/main/java/konogonka/Tools/NCA/NCAProvider.java @@ -0,0 +1,282 @@ +package konogonka.Tools.NCA; + +import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock; +import konogonka.xtsaes.XTSAESCipher; +import org.bouncycastle.crypto.params.KeyParameter; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; + +import static konogonka.LoperConverter.getLElong; + +// TODO: check file size +public class NCAProvider { + private File file; // File that contains NCA + private long offset; // Offset where NCA actually located + private HashMap keys; // hashmap with keys using _0x naming (where x number 0-N) + // Header + private byte[] rsa2048one; + private byte[] rsa2048two; + private String magicnum; + private byte systemOrGcIndicator; + private byte contentType; + private byte cryptoType1; // keyblob index. Considering as number within application/ocean/system + private byte keyIndex; // application/ocean/system (kaek index?) + private long ncaSize; // Size of this NCA (bytes) + private byte[] titleId; + private byte[] sdkVersion; // version ver_revision.ver_micro.vev_minor.ver_major + private byte cryptoType2; // keyblob index. Considering as number within application/ocean/system + private byte[] rightsId; + + private byte[] sha256hash0; + private byte[] sha256hash1; + private byte[] sha256hash2; + private byte[] sha256hash3; + + private byte[] encryptedKey0; + private byte[] encryptedKey1; + private byte[] encryptedKey2; + private byte[] encryptedKey3; + + private byte[] decryptedKey0; + private byte[] decryptedKey1; + private byte[] decryptedKey2; + private byte[] decryptedKey3; + + private NCAHeaderTableEntry tableEntry0; + private NCAHeaderTableEntry tableEntry1; + private NCAHeaderTableEntry tableEntry2; + private NCAHeaderTableEntry tableEntry3; + + private NCASectionBlock sectionBlock0; + private NCASectionBlock sectionBlock1; + private NCASectionBlock sectionBlock2; + private NCASectionBlock sectionBlock3; + + public NCAProvider(File file, HashMap keys) throws Exception{ + this(file, keys, 0); + } + + public NCAProvider (File file, HashMap keys, long offsetPosition) throws Exception{ + this.keys = keys; + String header_key = keys.get("header_key"); + if (header_key == null ) + throw new Exception("header_key is not found within key set provided."); + if (header_key.length() != 64) + throw new Exception("header_key is too small or too big. Must be 64 symbols."); + + this.file = file; + this.offset = offsetPosition; + + KeyParameter key1 = new KeyParameter( + hexStrToByteArray(header_key.substring(0, 32)) + ); + KeyParameter key2 = new KeyParameter( + hexStrToByteArray(header_key.substring(32, 64)) + ); + + XTSAESCipher xtsaesCipher = new XTSAESCipher(false); + xtsaesCipher.init(false, key1, key2); + //------------------------------------------------------------------------------------------------------------------------- + byte[] decryptedHeader = new byte[0xC00]; + + RandomAccessFile raf = new RandomAccessFile(file, "r"); + byte[] encryptedSequence = new byte[0x200]; + byte[] decryptedSequence; + + raf.seek(offsetPosition); + + for (int i = 0; i < 6; i++){ + if (raf.read(encryptedSequence) != 0x200) + throw new Exception("Read error "+i); + decryptedSequence = new byte[0x200]; + xtsaesCipher.processDataUnit(encryptedSequence, 0, 0x200, decryptedSequence, 0, i); + System.arraycopy(decryptedSequence, 0, decryptedHeader, i * 0x200, 0x200); + } + + getHeader(decryptedHeader); + + raf.close(); + + /* + //--------------------------------------------------------------------- + FileInputStream fis = new FileInputStream(file); + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("/tmp/decrypted.nca")); + int i = 0; + byte[] block = new byte[0x200]; + while (fis.read(block) != -1){ + decryptedSequence = new byte[0x200]; + xtsaesCipher.processDataUnit(block, 0, 0x200, decryptedSequence, 0, i++); + bos.write(decryptedSequence); + } + bos.close(); + //---------------------------------------------------------------------*/ + } + + private byte[] hexStrToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + private void getHeader(byte[] decryptedData) throws Exception{ + rsa2048one = Arrays.copyOfRange(decryptedData, 0, 0x100); + rsa2048two = Arrays.copyOfRange(decryptedData, 0x100, 0x200); + magicnum = new String(decryptedData, 0x200, 0x4, StandardCharsets.US_ASCII); + systemOrGcIndicator = decryptedData[0x204]; + contentType = decryptedData[0x205]; + cryptoType1 = decryptedData[0x206]; + keyIndex = decryptedData[0x207]; + ncaSize = getLElong(decryptedData, 0x208); + titleId = Arrays.copyOfRange(decryptedData, 0x210, 0x21C); // 0x218 ? + sdkVersion = Arrays.copyOfRange(decryptedData, 0x21c, 0x220); + cryptoType2 = decryptedData[0x220]; + rightsId = Arrays.copyOfRange(decryptedData, 0x230, 0x240); + byte[] tableBytes = Arrays.copyOfRange(decryptedData, 0x240, 0x280); + byte[] sha256tableBytes = Arrays.copyOfRange(decryptedData, 0x280, 0x300); + sha256hash0 = Arrays.copyOfRange(sha256tableBytes, 0, 0x20); + sha256hash1 = Arrays.copyOfRange(sha256tableBytes, 0x20, 0x40); + sha256hash2 = Arrays.copyOfRange(sha256tableBytes, 0x40, 0x60); + sha256hash3 = Arrays.copyOfRange(sha256tableBytes, 0x60, 0x80); + byte [] encryptedKeysArea = Arrays.copyOfRange(decryptedData, 0x300, 0x340); + + encryptedKey0 = Arrays.copyOfRange(encryptedKeysArea, 0, 0x10); + encryptedKey1 = Arrays.copyOfRange(encryptedKeysArea, 0x10, 0x20); + encryptedKey2 = Arrays.copyOfRange(encryptedKeysArea, 0x20, 0x30); + encryptedKey3 = Arrays.copyOfRange(encryptedKeysArea, 0x30, 0x40); + + //todo: if nca3 proceed + // If no rights ID (ticket?) exists + if (Arrays.equals(rightsId, new byte[0x10])) { + byte realCryptoType; + if (cryptoType1 < cryptoType2) + realCryptoType = cryptoType2; + else + realCryptoType = cryptoType1; + + if (realCryptoType > 0) // TODO: CLARIFY WHY THEH FUCK IS IT FAIR???? + realCryptoType -= 1; + + + String keyAreaKey; + switch (keyIndex){ + case 0: + keyAreaKey = keys.get("key_area_key_application_0"+realCryptoType); + System.out.println("Using key_area_key_application_0"+realCryptoType); + break; + case 1: + keyAreaKey = keys.get("key_area_key_ocean_0"+realCryptoType); + System.out.println("Using key_area_key_ocean_0"+realCryptoType); + break; + case 2: + keyAreaKey = keys.get("key_area_key_system_0"+realCryptoType); + System.out.println("Using key_area_key_system_0"+realCryptoType); + break; + default: + keyAreaKey = null; + } + + if (keyAreaKey != null){ + SecretKeySpec skSpec = new SecretKeySpec(hexStrToByteArray(keyAreaKey), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, skSpec); + decryptedKey0 = cipher.doFinal(encryptedKey0); + decryptedKey1 = cipher.doFinal(encryptedKey1); + decryptedKey2 = cipher.doFinal(encryptedKey2); + decryptedKey3 = cipher.doFinal(encryptedKey3); + } + } + else { + + + } + + tableEntry0 = new NCAHeaderTableEntry(tableBytes); + tableEntry1 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x10, 0x20)); + tableEntry2 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x20, 0x30)); + tableEntry3 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x30, 0x40)); + + sectionBlock0 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x400, 0x600)); + sectionBlock1 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x600, 0x800)); + sectionBlock2 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x800, 0xa00)); + sectionBlock3 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0xa00, 0xc00)); + } + + public byte[] getRsa2048one() { return rsa2048one; } + public byte[] getRsa2048two() { return rsa2048two; } + public String getMagicnum() { return magicnum; } + public byte getSystemOrGcIndicator() { return systemOrGcIndicator; } + public byte getContentType() { return contentType; } + public byte getCryptoType1() { return cryptoType1; } + public byte getKeyIndex() { return keyIndex; } + public long getNcaSize() { return ncaSize; } + public byte[] getTitleId() { return titleId; } + public byte[] getSdkVersion() { return sdkVersion; } + public byte getCryptoType2() { return cryptoType2; } + public byte[] getRightsId() { return rightsId; } + + public byte[] getSha256hash0() { return sha256hash0; } + public byte[] getSha256hash1() { return sha256hash1; } + public byte[] getSha256hash2() { return sha256hash2; } + public byte[] getSha256hash3() { return sha256hash3; } + + public byte[] getEncryptedKey0() { return encryptedKey0; } + public byte[] getEncryptedKey1() { return encryptedKey1; } + public byte[] getEncryptedKey2() { return encryptedKey2; } + public byte[] getEncryptedKey3() { return encryptedKey3; } + + public byte[] getDecryptedKey0() { return decryptedKey0; } + public byte[] getDecryptedKey1() { return decryptedKey1; } + public byte[] getDecryptedKey2() { return decryptedKey2; } + public byte[] getDecryptedKey3() { return decryptedKey3; } + + public NCAHeaderTableEntry getTableEntry0() { return tableEntry0; } + public NCAHeaderTableEntry getTableEntry1() { return tableEntry1; } + public NCAHeaderTableEntry getTableEntry2() { return tableEntry2; } + public NCAHeaderTableEntry getTableEntry3() { return tableEntry3; } + + public NCASectionBlock getSectionBlock0() { return sectionBlock0; } + public NCASectionBlock getSectionBlock1() { return sectionBlock1; } + public NCASectionBlock getSectionBlock2() { return sectionBlock2; } + public NCASectionBlock getSectionBlock3() { return sectionBlock3; } + + /** + * Get content for the selected section + * @param sectionNumber should be 1-4 + * */ + public NCAContentPFS0 getNCAContentPFS0(int sectionNumber){ + switch (sectionNumber){ + case 0: + return new NCAContentPFS0(file, offset, sectionBlock0, tableEntry0, decryptedKey2); // TODO: remove decryptedKey2 + case 1: + return new NCAContentPFS0(file, offset, sectionBlock1, tableEntry1, decryptedKey2); + case 2: + return new NCAContentPFS0(file, offset, sectionBlock2, tableEntry2, decryptedKey2); + case 3: + return new NCAContentPFS0(file, offset, sectionBlock3, tableEntry3, decryptedKey2); + default: + return null; + } + } +} +// 0 OR 2 crypto type +// 0,1,2 kaek index +//settings.keyset.key_area_keys[ctx->crypto_type][ctx->header.kaek_ind] + /* + 0x207 = + 0: key_area_key_application_ 0x206 range:[0-6]; usually used 0 or 2 + 1: key_area_key_ocean [0-6] + 2: key_area_key_system [0-6] + + if(ncahdr_x206 < ncahdr_x220){ret = ncahdr_x220; } else { ret = ncahdr_x206; } return ret; + + ret > 0? ret-- + + */ \ No newline at end of file diff --git a/src/main/java/konogonka/Tools/NCA/NCASectionTableBlock/NCASectionBlock.java b/src/main/java/konogonka/Tools/NCA/NCASectionTableBlock/NCASectionBlock.java new file mode 100644 index 0000000..697d570 --- /dev/null +++ b/src/main/java/konogonka/Tools/NCA/NCASectionTableBlock/NCASectionBlock.java @@ -0,0 +1,96 @@ +package konogonka.Tools.NCA.NCASectionTableBlock; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static konogonka.LoperConverter.getLEint; +import static konogonka.LoperConverter.getLElong; + +public class NCASectionBlock { + private byte[] version; + private byte fsType; + private byte hashType; + private byte cryptoType; + private byte padding; + private SuperBlockIVFC superBlockIVFC; + private SuperBlockPFS0 superBlockPFS0; + private byte[] BKTRfullHeader; + // BKTR extended + private long BKTRoffsetSection1; + private long BKTRsizeSection1; + private String BKTRmagicSection1; + private int BKTRu32Section1; + private int BKTRs32Section1; + private byte[] BKTRunknownSection1; + + private long BKTRoffsetSection2; + private long BKTRsizeSection2; + private String BKTRmagicSection2; + private int BKTRu32Section2; + private int BKTRs32Section2; + private byte[] BKTRunknownSection2; + + private byte[] BKTRunknown1; + private byte[] BKTRunknown2; + + public NCASectionBlock(byte[] tableBlockBytes) throws Exception{ + if (tableBlockBytes.length != 0x200) + throw new Exception("Table Block Section size is incorrect."); + version = Arrays.copyOfRange(tableBlockBytes, 0, 0x2); + fsType = tableBlockBytes[0x2]; + hashType = tableBlockBytes[0x3]; + cryptoType = tableBlockBytes[0x4]; + padding = tableBlockBytes[0x5]; + byte[] superBlockBytes = Arrays.copyOfRange(tableBlockBytes, 0x8, 0xf8); + + if ((fsType == 0) && (hashType == 0x3)) + superBlockIVFC = new SuperBlockIVFC(superBlockBytes); + else if ((fsType == 0x1) && (hashType == 0x2)) + superBlockPFS0 = new SuperBlockPFS0(superBlockBytes); + + BKTRfullHeader = Arrays.copyOfRange(tableBlockBytes, 0x100, 0x200); + + BKTRoffsetSection1 = getLElong(BKTRfullHeader, 0); + BKTRsizeSection1 = getLElong(BKTRfullHeader, 0x8); + BKTRmagicSection1 = new String(Arrays.copyOfRange(BKTRfullHeader, 0x10, 0x14), StandardCharsets.US_ASCII); + BKTRu32Section1 = getLEint(BKTRfullHeader, 0x14); + BKTRs32Section1 = getLEint(BKTRfullHeader, 0x18); + BKTRunknownSection1 = Arrays.copyOfRange(tableBlockBytes, 0x1c, 0x20); + + BKTRoffsetSection2 = getLElong(BKTRfullHeader, 0x20); + BKTRsizeSection2 = getLElong(BKTRfullHeader, 0x28); + BKTRmagicSection2 = new String(Arrays.copyOfRange(BKTRfullHeader, 0x30, 0x34), StandardCharsets.US_ASCII); + BKTRu32Section2 = getLEint(BKTRfullHeader, 0x34); + BKTRs32Section2 = getLEint(BKTRfullHeader, 0x38); + BKTRunknownSection2 = Arrays.copyOfRange(tableBlockBytes, 0x3c, 0x40); + + BKTRunknown1 = Arrays.copyOfRange(tableBlockBytes, 0x40, 0x44); + BKTRunknown2 = Arrays.copyOfRange(tableBlockBytes, 0x44, 0x48); + } + + public byte[] getVersion() { return version; } + public byte getFsType() { return fsType; } + public byte getHashType() { return hashType; } + public byte getCryptoType() { return cryptoType; } + public byte getPadding() { return padding; } + public SuperBlockIVFC getSuperBlockIVFC() { return superBlockIVFC; } + public SuperBlockPFS0 getSuperBlockPFS0() { return superBlockPFS0; } + public byte[] getBKTRfullHeader() { return BKTRfullHeader; } + + public long getBKTRoffsetSection1() { return BKTRoffsetSection1; } + public long getBKTRsizeSection1() { return BKTRsizeSection1; } + public String getBKTRmagicSection1() { return BKTRmagicSection1; } + public int getBKTRu32Section1() { return BKTRu32Section1; } + public int getBKTRs32Section1() { return BKTRs32Section1; } + public byte[] getBKTRunknownSection1() { return BKTRunknownSection1; } + public long getBKTRoffsetSection2() { return BKTRoffsetSection2; } + public long getBKTRsizeSection2() { return BKTRsizeSection2; } + public String getBKTRmagicSection2() { return BKTRmagicSection2; } + public int getBKTRu32Section2() { return BKTRu32Section2; } + public int getBKTRs32Section2() { return BKTRs32Section2; } + public byte[] getBKTRunknownSection2() { return BKTRunknownSection2; } + public byte[] getBKTRunknown1() { return BKTRunknown1; } + public byte[] getBKTRunknown2() { return BKTRunknown2; } + +} + diff --git a/src/main/java/konogonka/Tools/NCA/NCASectionTableBlock/SuperBlockIVFC.java b/src/main/java/konogonka/Tools/NCA/NCASectionTableBlock/SuperBlockIVFC.java new file mode 100644 index 0000000..be267cb --- /dev/null +++ b/src/main/java/konogonka/Tools/NCA/NCASectionTableBlock/SuperBlockIVFC.java @@ -0,0 +1,155 @@ +package konogonka.Tools.NCA.NCASectionTableBlock; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static konogonka.LoperConverter.getLEint; +import static konogonka.LoperConverter.getLElong; + +public class SuperBlockIVFC { + private String magic; + private int magicNumber; + private int masterHashSize; + private int totalNumberOfLevels; + private long lvl1Offset; + private long lvl1Size; + private int lvl1SBlockSize; + private byte[] reserved1; + + private long lvl2Offset; + private long lvl2Size; + private int lvl2SBlockSize; + private byte[] reserved2; + + private long lvl3Offset; + private long lvl3Size; + private int lvl3SBlockSize; + private byte[] reserved3; + + private long lvl4Offset; + private long lvl4Size; + private int lvl4SBlockSize; + private byte[] reserved4; + + private long lvl5Offset; + private long lvl5Size; + private int lvl5SBlockSize; + private byte[] reserved5; + + private long lvl6Offset; + private long lvl6Size; + private int lvl6SBlockSize; + private byte[] reserved6; + + private byte[] unknown; + private byte[] hash; + + SuperBlockIVFC(byte[] sbBytes){ + this.magic = new String(Arrays.copyOfRange(sbBytes, 0, 4), StandardCharsets.US_ASCII); + this.magicNumber = getLEint(sbBytes, 0x4); + this.masterHashSize = getLEint(sbBytes, 0x8); + this.totalNumberOfLevels = getLEint(sbBytes, 0xc); + + this.lvl1Offset = getLElong(sbBytes, 0x10); + this.lvl1Size = getLElong(sbBytes, 0x18); + this.lvl1SBlockSize = getLEint(sbBytes, 0x20); + this.reserved1 = Arrays.copyOfRange(sbBytes, 0x24, 0x28); + + this.lvl2Offset = getLElong(sbBytes, 0x28); + this.lvl2Size = getLElong(sbBytes, 0x30); + this.lvl2SBlockSize = getLEint(sbBytes, 0x38); + this.reserved2 = Arrays.copyOfRange(sbBytes, 0x3c, 0x40); + + this.lvl3Offset = getLElong(sbBytes, 0x40); + this.lvl3Size = getLElong(sbBytes, 0x48); + this.lvl3SBlockSize = getLEint(sbBytes, 0x50); + this.reserved3 = Arrays.copyOfRange(sbBytes, 0x54, 0x58); + + this.lvl4Offset = getLElong(sbBytes, 0x58); + this.lvl4Size = getLElong(sbBytes, 0x60); + this.lvl4SBlockSize = getLEint(sbBytes, 0x68); + this.reserved4 = Arrays.copyOfRange(sbBytes, 0x6c, 0x70); + + this.lvl5Offset = getLElong(sbBytes, 0x70); + this.lvl5Size = getLElong(sbBytes, 0x78); + this.lvl5SBlockSize = getLEint(sbBytes, 0x80); + this.reserved5 = Arrays.copyOfRange(sbBytes, 0x84, 0x88); + + this.lvl6Offset = getLElong(sbBytes, 0x88); + this.lvl6Size = getLElong(sbBytes, 0x90); + this.lvl6SBlockSize = getLEint(sbBytes, 0x98); + this.reserved6 = Arrays.copyOfRange(sbBytes, 0x9c, 0xa0); + + this.unknown = Arrays.copyOfRange(sbBytes, 0xa0, 0xc0); + this.hash = Arrays.copyOfRange(sbBytes, 0xc0, 0xe0); + /* + System.out.println(magic); + System.out.println(magicNumber); + System.out.println(masterHashSize); + System.out.println(totalNumberOfLevels); + System.out.println(lvl1Offset); + System.out.println(lvl1Size); + System.out.println(lvl1SBlockSize); + RainbowHexDump.hexDumpUTF8(reserved1); + + System.out.println(lvl2Offset); + System.out.println(lvl2Size); + System.out.println(lvl2SBlockSize); + RainbowHexDump.hexDumpUTF8(reserved2); + + System.out.println(lvl3Offset); + System.out.println(lvl3Size); + System.out.println(lvl3SBlockSize); + RainbowHexDump.hexDumpUTF8(reserved3); + + System.out.println(lvl4Offset); + System.out.println(lvl4Size); + System.out.println(lvl4SBlockSize); + RainbowHexDump.hexDumpUTF8(reserved4); + + System.out.println(lvl5Offset); + System.out.println(lvl5Size); + System.out.println(lvl5SBlockSize); + RainbowHexDump.hexDumpUTF8(reserved5); + + System.out.println(lvl6Offset); + System.out.println(lvl6Size); + System.out.println(lvl6SBlockSize); + RainbowHexDump.hexDumpUTF8(reserved6); + + RainbowHexDump.hexDumpUTF8(unknown); + RainbowHexDump.hexDumpUTF8(hash); + // */ + } + + public String getMagic() { return magic; } + public int getMagicNumber() { return magicNumber; } + public int getMasterHashSize() { return masterHashSize; } + public int getTotalNumberOfLevels() { return totalNumberOfLevels; } + public long getLvl1Offset() { return lvl1Offset; } + public long getLvl1Size() { return lvl1Size; } + public int getLvl1SBlockSize() { return lvl1SBlockSize; } + public byte[] getReserved1() { return reserved1; } + public long getLvl2Offset() { return lvl2Offset; } + public long getLvl2Size() { return lvl2Size; } + public int getLvl2SBlockSize() { return lvl2SBlockSize; } + public byte[] getReserved2() { return reserved2; } + public long getLvl3Offset() { return lvl3Offset; } + public long getLvl3Size() { return lvl3Size; } + public int getLvl3SBlockSize() { return lvl3SBlockSize; } + public byte[] getReserved3() { return reserved3; } + public long getLvl4Offset() { return lvl4Offset; } + public long getLvl4Size() { return lvl4Size; } + public int getLvl4SBlockSize() { return lvl4SBlockSize; } + public byte[] getReserved4() { return reserved4; } + public long getLvl5Offset() { return lvl5Offset; } + public long getLvl5Size() { return lvl5Size; } + public int getLvl5SBlockSize() { return lvl5SBlockSize; } + public byte[] getReserved5() { return reserved5; } + public long getLvl6Offset() { return lvl6Offset; } + public long getLvl6Size() { return lvl6Size; } + public int getLvl6SBlockSize() { return lvl6SBlockSize; } + public byte[] getReserved6() { return reserved6; } + public byte[] getUnknown() { return unknown; } + public byte[] getHash() { return hash; } +} diff --git a/src/main/java/konogonka/Tools/NCA/NCASectionTableBlock/SuperBlockPFS0.java b/src/main/java/konogonka/Tools/NCA/NCASectionTableBlock/SuperBlockPFS0.java new file mode 100644 index 0000000..9704eef --- /dev/null +++ b/src/main/java/konogonka/Tools/NCA/NCASectionTableBlock/SuperBlockPFS0.java @@ -0,0 +1,47 @@ +package konogonka.Tools.NCA.NCASectionTableBlock; + +import java.util.Arrays; + +import static konogonka.LoperConverter.getLEint; +import static konogonka.LoperConverter.getLElong; + +public class SuperBlockPFS0 { + private byte[] SHA256hash; + private int blockSize; + private int unknownNumberTwo; + private long hashTableOffset; + private long hashTableSize; + private long pfs0offset; + private long pfs0size; + private byte[] zeroes; + + SuperBlockPFS0(byte[] sbBytes){ + SHA256hash = Arrays.copyOfRange(sbBytes, 0, 0x20); + blockSize = getLEint(sbBytes, 0x20); + unknownNumberTwo = getLEint(sbBytes, 0x24); + hashTableOffset = getLElong(sbBytes, 0x28); + hashTableSize = getLElong(sbBytes, 0x30); + pfs0offset = getLElong(sbBytes, 0x38); + pfs0size = getLElong(sbBytes, 0x40); + zeroes = Arrays.copyOfRange(sbBytes, 0x48, 0xf8); + /* + RainbowHexDump.hexDumpUTF8(SHA256hash); + System.out.println(blockSize); + System.out.println(unknownNumberTwo); + System.out.println(hashTableOffset); + System.out.println(hashTableSize); + System.out.println(pfs0offset); + System.out.println(pfs0size); + RainbowHexDump.hexDumpUTF8(zeroes); + */ + } + + public byte[] getSHA256hash() { return SHA256hash; } + public int getBlockSize() { return blockSize; } + public int getUnknownNumberTwo() { return unknownNumberTwo; } + public long getHashTableOffset() { return hashTableOffset; } + public long getHashTableSize() { return hashTableSize; } + public long getPfs0offset() { return pfs0offset; } + public long getPfs0size() { return pfs0size; } + public byte[] getZeroes() { return zeroes; } +} diff --git a/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java b/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java new file mode 100644 index 0000000..2be1ac7 --- /dev/null +++ b/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java @@ -0,0 +1,112 @@ +package konogonka.Tools.PFS0; + +import konogonka.RainbowHexDump; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static konogonka.LoperConverter.*; + +public class PFS0Provider { + private long pfs0offsetPosition; + private long rawFileDataStart; + + private String magic; + private int filesCount; + private int stringTableSize; + private byte[] padding; + private PFS0subFile[] pfs0subFiles; + + public PFS0Provider(File fileWithPfs0) throws Exception{ this(fileWithPfs0, 0); } + + public PFS0Provider(File fileWithPfs0, long pfs0offsetPosition) throws Exception{ + this.pfs0offsetPosition = pfs0offsetPosition; + + try { + RandomAccessFile raf = new RandomAccessFile(fileWithPfs0, "r"); + + raf.seek(pfs0offsetPosition); + byte[] fileStartingBytes = new byte[0x10]; + // Read PFS0Provider, files count, header, padding (4 zero bytes) + if (raf.read(fileStartingBytes) != 0x10){ + raf.close(); + throw new Exception("PFS0Provider: Unable to read starting bytes"); + } + // Check PFS0Provider + magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII); + if (! magic.equals("PFS0")){ + raf.close(); + throw new Exception("PFS0Provider: Bad magic"); + } + // Get files count + filesCount = getLEint(fileStartingBytes, 0x4); + if (filesCount <= 0 ) { + raf.close(); + throw new Exception("PFS0Provider: Files count is too small"); + } + // Get string table + stringTableSize = getLEint(fileStartingBytes, 0x8); + if (stringTableSize <= 0 ){ + raf.close(); + throw new Exception("PFS0Provider: String table is too small"); + } + padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10); + //--------------------------------------------------------------------------------------------------------- + pfs0subFiles = new PFS0subFile[filesCount]; + + long[] offsetsSubFiles = new long[filesCount]; + long[] sizesSubFiles = new long[filesCount]; + int[] strTableOffsets = new int[filesCount]; + byte[][] zeroBytes = new byte[filesCount][]; + + byte[] fileEntryTable = new byte[0x18]; + for (int i=0; i> 4) & (byte) 0x0F); + KEKIndex = (byte) (titleKEKIndexBoth & 0x0F); + gcSize = headerBytes[269]; + gcVersion = headerBytes[270]; + gcFlags = headerBytes[271]; + pkgID = Arrays.copyOfRange(headerBytes, 272, 280); + valDataEndAddr = getLElong(headerBytes, 280); //TODO: FIX/simplify // + gcInfoIV = reverseBytes(Arrays.copyOfRange(headerBytes, 288, 304)); + hfs0partOffset = getLElong(headerBytes, 304); + hfs0headerSize = getLElong(headerBytes, 312); + hfs0headerSHA256 = Arrays.copyOfRange(headerBytes, 320, 352); + hfs0initDataSHA256 = Arrays.copyOfRange(headerBytes, 352, 384); + secureModeFlag = getLEint(headerBytes, 384); + titleKeyFlag = getLEint(headerBytes, 388); + keyFlag = getLEint(headerBytes, 392); + normAreaEndAddr = Arrays.copyOfRange(headerBytes, 396, 400); + } + + public byte[] getRsa2048PKCS1sig() { return rsa2048PKCS1sig; } + public boolean isMagicHeadOk() { return magicHead; } + public byte[] getSecureAreaStartAddr() { return SecureAreaStartAddr; } + public boolean isBkupAreaStartAddrOk() { return bkupAreaStartAddr; } + public byte getTitleKEKIndexBoth() { return titleKEKIndexBoth; } + public byte getTitleKEKIndex() { return titleKEKIndex; } + public byte getKEKIndex() { return KEKIndex; } + + public byte getGcSize() { + return gcSize; + } + public byte getGcVersion() { + return gcVersion; + } + public byte getGcFlags() { + return gcFlags; + } + public byte[] getPkgID() { + return pkgID; + } + public long getValDataEndAddr() { + return valDataEndAddr; + } + public byte[] getGcInfoIV() { + return gcInfoIV; + } + public long getHfs0partOffset() { + return hfs0partOffset; + } + public long getHfs0headerSize() { + return hfs0headerSize; + } + public byte[] getHfs0headerSHA256() { + return hfs0headerSHA256; + } + public byte[] getHfs0initDataSHA256() { + return hfs0initDataSHA256; + } + public int getSecureModeFlag() { + return secureModeFlag; + } + public boolean isSecureModeFlagOk(){ + return secureModeFlag == 1; + } + public int getTitleKeyFlag() { + return titleKeyFlag; + } + public boolean istitleKeyFlagOk(){ + return titleKeyFlag == 2; + } + public int getKeyFlag() { + return keyFlag; + } + public boolean iskeyFlagOk(){ + return keyFlag == 0; + } + public byte[] getNormAreaEndAddr() { + return normAreaEndAddr; + } + + private byte[] reverseBytes(byte[] bArr){ + Byte[] objArr = new Byte[bArr.length]; + for (int i=0;i < bArr.length; i++) + objArr[i] = bArr[i]; + List bytesList = Arrays.asList(objArr); + Collections.reverse(bytesList); + for (int i=0;i < bArr.length; i++) + bArr[i] = objArr[i]; + return bArr; + } +} \ No newline at end of file diff --git a/src/main/java/konogonka/Tools/XCI/XCIGamecardInfo.java b/src/main/java/konogonka/Tools/XCI/XCIGamecardInfo.java new file mode 100644 index 0000000..b9e5bc8 --- /dev/null +++ b/src/main/java/konogonka/Tools/XCI/XCIGamecardInfo.java @@ -0,0 +1,101 @@ +package konogonka.Tools.XCI; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.util.Arrays; + +import static konogonka.LoperConverter.getLEint; +import static konogonka.LoperConverter.getLElong; + +/** + * Gamecard Info + * */ +public class XCIGamecardInfo{ + + private long fwVersion; + private byte[] accessCtrlFlags; // 0x00A10011 for 25MHz access or 0x00A10010 for 50MHz access + private int readWaitTime1; + private int readWaitTime2; + private int writeWaitTime1; + private int writeWaitTime2; + private byte[] fwMode; + private byte[] cupVersion; + private byte[] emptyPadding1; + private byte[] updPartHash; + private byte[] cupID; + private byte[] emptyPadding2; + // todo: Add factory function instead + XCIGamecardInfo(byte[] infoBytes, byte[] IV, String XCI_HEADER_KEY) throws Exception { + if (XCI_HEADER_KEY.trim().isEmpty()) + return; + if (infoBytes.length != 112) + throw new Exception("XCIGamecardInfo Incorrect array size. Expected 112 bytes while received "+infoBytes.length); + + IvParameterSpec gciIV = new IvParameterSpec(IV); + SecretKeySpec skSpec = new SecretKeySpec(hexStrToByteArray(XCI_HEADER_KEY), "AES"); + + try { + // NOTE: CBC + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, skSpec, gciIV); + + byte[] decrypted = cipher.doFinal(infoBytes); + + fwVersion = getLElong(decrypted, 0); + accessCtrlFlags = Arrays.copyOfRange(decrypted, 8, 12); + readWaitTime1 = getLEint(decrypted, 12); + readWaitTime2 = getLEint(decrypted, 16); + writeWaitTime1 = getLEint(decrypted, 20); + writeWaitTime2 = getLEint(decrypted, 24); + fwMode = Arrays.copyOfRange(decrypted, 28, 32); + cupVersion = Arrays.copyOfRange(decrypted, 32, 36); + emptyPadding1 = Arrays.copyOfRange(decrypted, 36, 40); + updPartHash = Arrays.copyOfRange(decrypted, 40, 48); + cupID = Arrays.copyOfRange(decrypted, 48, 56); + emptyPadding2 = Arrays.copyOfRange(decrypted, 56, 112); + /* + System.out.println(fwVersion); + RainbowHexDump.hexDumpUTF8(accessCtrlFlags); + System.out.println(readWaitTime1); + System.out.println(readWaitTime2); + System.out.println(writeWaitTime1); + System.out.println(writeWaitTime2); + RainbowHexDump.hexDumpUTF8(fwMode); + RainbowHexDump.hexDumpUTF8(cupVersion); + RainbowHexDump.hexDumpUTF8(emptyPadding1); + RainbowHexDump.hexDumpUTF8(updPartHash); + RainbowHexDump.hexDumpUTF8(cupID); + RainbowHexDump.hexDumpUTF8(emptyPadding2); + */ + } catch (Exception e) { + throw new Exception("XCIGamecardInfo Decryption failed: \n "+e.getMessage()); + } + + } + private byte[] hexStrToByteArray(String s) { // thanks stackoverflow + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + + public long getFwVersion() { return fwVersion; } + public byte[] getAccessCtrlFlags() { return accessCtrlFlags; } + public int getReadWaitTime1() { return readWaitTime1; } + public int getReadWaitTime2() { return readWaitTime2; } + public int getWriteWaitTime1() { return writeWaitTime1; } + public int getWriteWaitTime2() { return writeWaitTime2; } + + public byte[] getFwMode() { return fwMode; } + public byte[] getCupVersion() { return cupVersion; } + public boolean isEmptyPadding1() { return Arrays.equals(emptyPadding1, new byte[4]); } + public byte[] getEmptyPadding1() { return emptyPadding1; } + public byte[] getUpdPartHash() { return updPartHash; } + public byte[] getCupID() { return cupID; } + public boolean isEmptyPadding2() { return Arrays.equals(emptyPadding2, new byte[56]); } + public byte[] getEmptyPadding2() { return emptyPadding2; } +} diff --git a/src/main/java/konogonka/Tools/XCI/XCIProvider.java b/src/main/java/konogonka/Tools/XCI/XCIProvider.java new file mode 100644 index 0000000..29cdd9c --- /dev/null +++ b/src/main/java/konogonka/Tools/XCI/XCIProvider.java @@ -0,0 +1,100 @@ +package konogonka.Tools.XCI; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +public class XCIProvider{ + // TODO: Since LOGO partition added, we have to handle it properly. Is it works?? + + //private BufferedInputStream xciBIS; + private XCIGamecardHeader xciGamecardHeader; + private XCIGamecardInfo xciGamecardInfo; + private XCIGamecardCert xciGamecardCert; + private HFS0Provider hfs0ProviderMain, + hfs0ProviderUpdate, + hfs0ProviderNormal, + hfs0ProviderSecure, + hfs0ProviderLogo; + + public XCIProvider(File file, String XCI_HEADER_KEY) throws Exception{ // TODO: ADD FILE SIZE CHECK !!! Check xciHdrKey + RandomAccessFile raf; + + try { + //xciBIS = new BufferedInputStream(new FileInputStream(file)); + raf = new RandomAccessFile(file, "r"); + } + catch (FileNotFoundException fnfe){ + throw new Exception("XCI File not found: \n "+fnfe.getMessage()); + } + + if (file.length() < 0xf010) + throw new Exception("XCI File is too small."); + + try{ + byte[] gamecardHeaderBytes = new byte[400]; + byte[] gamecardInfoBytes = new byte[112]; + byte[] gamecardCertBytes = new byte[512]; + + // Creating GC Header class + if (raf.read(gamecardHeaderBytes) != 400) { + raf.close(); + throw new Exception("XCI Can't read Gamecard Header bytes."); + } + xciGamecardHeader = new XCIGamecardHeader(gamecardHeaderBytes); // throws exception + // Creating GC Info class + if (raf.read(gamecardInfoBytes) != 112) { + raf.close(); + throw new Exception("XCI Can't read Gamecard Header bytes."); + } + xciGamecardInfo = new XCIGamecardInfo(gamecardInfoBytes, xciGamecardHeader.getGcInfoIV(), XCI_HEADER_KEY); + // Creating GC Cerfificate class + raf.seek(0x7000); + if (raf.read(gamecardCertBytes) != 512) { + raf.close(); + throw new Exception("XCI Can't read Gamecard certificate bytes."); + } + xciGamecardCert = new XCIGamecardCert(gamecardCertBytes); + + hfs0ProviderMain = new HFS0Provider(0xf000, raf); + if (hfs0ProviderMain.getFilesCnt() < 3){ + raf.close(); + throw new Exception("XCI Can't read Gamecard certificate bytes."); + } + // Get all partitions from the main HFS0 file + String partition; + for (HFS0File hfs0File: hfs0ProviderMain.getHfs0Files()){ + partition = hfs0File.getName(); + if (partition.equals("update")) { + hfs0ProviderUpdate = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf); + continue; + } + if (partition.equals("normal")) { + hfs0ProviderNormal = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf); + continue; + } + if (partition.equals("secure")) { + hfs0ProviderSecure = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf); + continue; + } + if (partition.equals("logo")) { + hfs0ProviderLogo = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf); + } + } + raf.close(); + } + catch (IOException ioe){ + throw new Exception("XCI Failed file analyze for ["+file.getName()+"]\n "+ioe.getMessage()); + } + } + /** + * Getters + * */ + public XCIGamecardHeader getGCHeader(){ return this.xciGamecardHeader; } + public XCIGamecardInfo getGCInfo(){ return this.xciGamecardInfo; } + public XCIGamecardCert getGCCert(){ return this.xciGamecardCert; } + public HFS0Provider getHfs0ProviderMain() { return this.hfs0ProviderMain; } + public HFS0Provider getHfs0ProviderUpdate() { return this.hfs0ProviderUpdate; } + public HFS0Provider getHfs0ProviderNormal() { return this.hfs0ProviderNormal; } + public HFS0Provider getHfs0ProviderSecure() { return this.hfs0ProviderSecure; } + public HFS0Provider getHfs0ProviderLogo() { return this.hfs0ProviderLogo; } +} \ No newline at end of file diff --git a/src/main/java/konogonka/Workers/AnalyzerNCA.java b/src/main/java/konogonka/Workers/AnalyzerNCA.java new file mode 100644 index 0000000..21ffef7 --- /dev/null +++ b/src/main/java/konogonka/Workers/AnalyzerNCA.java @@ -0,0 +1,40 @@ +package konogonka.Workers; + +import javafx.concurrent.Task; +import konogonka.ModelControllers.EMsgType; +import konogonka.ModelControllers.LogPrinter; +import konogonka.Tools.NCA.NCAProvider; + +import java.io.File; +import java.util.HashMap; + +public class AnalyzerNCA extends Task { + + private File file; + private LogPrinter logPrinter; + private HashMap keysMap; + + public AnalyzerNCA(File file, HashMap keysMap){ + this.file = file; + this.logPrinter = new LogPrinter(); + this.keysMap = keysMap; + } + + @Override + protected NCAProvider call() { + logPrinter.print("\tStart chain: NCA", EMsgType.INFO); + + NCAProvider ncaProvider; + + try { + ncaProvider = new NCAProvider(file, keysMap); + }catch (Exception e){ + logPrinter.print(e.getMessage(), EMsgType.FAIL); + ncaProvider = null; + }finally { + logPrinter.print("\tEnd chain: NCA", EMsgType.INFO); + logPrinter.close(); + } + return ncaProvider; + } +} diff --git a/src/main/java/konogonka/Workers/AnalyzerNSP.java b/src/main/java/konogonka/Workers/AnalyzerNSP.java new file mode 100644 index 0000000..d5190e7 --- /dev/null +++ b/src/main/java/konogonka/Workers/AnalyzerNSP.java @@ -0,0 +1,40 @@ +package konogonka.Workers; + +import javafx.concurrent.Task; +import konogonka.ModelControllers.EMsgType; +import konogonka.ModelControllers.LogPrinter; +import konogonka.Tools.PFS0.PFS0Provider; + + +import java.io.File; + +public class AnalyzerNSP extends Task { + + private File file; + private LogPrinter logPrinter; + + public AnalyzerNSP(File file){ + this.file = file; + this.logPrinter = new LogPrinter(); + } + + @Override + protected PFS0Provider call() { + logPrinter.print("\tStart chain: NSP", EMsgType.INFO); + try{ + return new PFS0Provider(file); + } + catch (Exception e){ + logPrinter.print("\tException: "+e.getMessage(), EMsgType.FAIL); + return null; + } + finally { + close(); + } + } + + private void close(){ + logPrinter.print("\tEnd chain: NSP", EMsgType.INFO); + logPrinter.close(); + } +} diff --git a/src/main/java/konogonka/Workers/AnalyzerXCI.java b/src/main/java/konogonka/Workers/AnalyzerXCI.java new file mode 100644 index 0000000..778fdb9 --- /dev/null +++ b/src/main/java/konogonka/Workers/AnalyzerXCI.java @@ -0,0 +1,39 @@ +package konogonka.Workers; + +import javafx.concurrent.Task; +import konogonka.ModelControllers.EMsgType; +import konogonka.ModelControllers.LogPrinter; +import konogonka.Tools.XCI.XCIProvider; + +import java.io.File; + +public class AnalyzerXCI extends Task { + + private File file; + private LogPrinter logPrinter; + private String xciHdrKey; + + public AnalyzerXCI(File file, String xciHdrKey){ + this.file = file; + this.logPrinter = new LogPrinter(); + this.xciHdrKey = xciHdrKey; + } + + @Override + protected XCIProvider call() { + logPrinter.print("\tStart chain: XCI", EMsgType.INFO); + + XCIProvider xciProvider; + + try { + xciProvider = new XCIProvider(file, xciHdrKey); + }catch (Exception e){ + logPrinter.print(e.getMessage(), EMsgType.FAIL); + xciProvider = null; + }finally { + logPrinter.print("\tEnd chain: XCI", EMsgType.INFO); + logPrinter.close(); + } + return xciProvider; + } +} diff --git a/src/main/java/konogonka/Workers/NspXciExtractor.java b/src/main/java/konogonka/Workers/NspXciExtractor.java new file mode 100644 index 0000000..87f05d7 --- /dev/null +++ b/src/main/java/konogonka/Workers/NspXciExtractor.java @@ -0,0 +1,99 @@ +package konogonka.Workers; + +import javafx.concurrent.Task; +import konogonka.Controllers.IRowModel; +import konogonka.ModelControllers.EMsgType; +import konogonka.ModelControllers.LogPrinter; + +import java.io.*; +import java.util.List; + +public class NspXciExtractor extends Task { + + private long rawDataStartPos; + private List models; + private String filesDestPath; + private LogPrinter logPrinter; + private File NspXciFile; + + public NspXciExtractor(long rawDataStartPos, List models, String filesDestPath, File NspXciFile){ + this.rawDataStartPos = rawDataStartPos; + this.models = models; + this.filesDestPath = filesDestPath; + this.NspXciFile = NspXciFile; + this.logPrinter = new LogPrinter(); + } + + @Override + protected Void call() { + logPrinter.print("\tStart extracting", EMsgType.INFO); + for (IRowModel model: models){ + logPrinter.print(filesDestPath+model.getFileName(), EMsgType.INFO); + + File contentFile = new File(filesDestPath+model.getFileName()); + + long realFileOffset = rawDataStartPos + model.getFileOffset(); + long realFileSize = model.getFileSize(); + + long readFrom = 0; + + int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576 + byte[] readBuf; + + try{ + BufferedOutputStream extractedFileOS = new BufferedOutputStream(new FileOutputStream(contentFile)); + + BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(NspXciFile)); // TODO: refactor? + if (bufferedInStream.skip(realFileOffset) != realFileOffset) { + logPrinter.print("File length is less than offset noted", EMsgType.FAIL); + return null; + } + + while (readFrom < realFileSize){ +/* + if (isCancelled()) // Check if user interrupted process. + return false; +*/ + if (realFileSize - readFrom < readPice) + readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee + readBuf = new byte[readPice]; + if (bufferedInStream.read(readBuf) != readPice) { + logPrinter.print("Can't read required chunk from file", EMsgType.FAIL); + return null; + } + + extractedFileOS.write(readBuf, 0, readPice); + //-----------------------------------------/ + try { + logPrinter.updateProgress((readFrom+readPice)/(realFileSize/100.0) / 100.0); + }catch (InterruptedException ie){ + getException().printStackTrace(); // TODO: Do something with this + } + //-----------------------------------------/ + readFrom += readPice; + } + bufferedInStream.close(); + extractedFileOS.close(); + //-----------------------------------------/ + try{ + logPrinter.updateProgress(1.0); + } + catch (InterruptedException ie){ + getException().printStackTrace(); // TODO: Do something with this + } + //-----------------------------------------/ + } + catch (IOException ioe){ + logPrinter.print("\tRead/Write error\n\t"+ioe.getMessage(), EMsgType.INFO); + return null; + } + } + close(); + return null; + } + + private void close(){ + logPrinter.print("\tEnd extracting", EMsgType.INFO); + logPrinter.close(); + } +} \ No newline at end of file diff --git a/src/main/java/konogonka/xtsaes/XTSAESBlockCipher.java b/src/main/java/konogonka/xtsaes/XTSAESBlockCipher.java new file mode 100644 index 0000000..a98ce1f --- /dev/null +++ b/src/main/java/konogonka/xtsaes/XTSAESBlockCipher.java @@ -0,0 +1,116 @@ +/* + * The MIT License + * + * Copyright 2016 Ahseya. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package konogonka.xtsaes; + +import net.jcip.annotations.NotThreadSafe; +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.KeyParameter; + +import java.util.Objects; +import java.util.function.LongFunction; + +/** + * XTS-AES implemented as a BlockCipher. + * + * @author Ahseya + */ +@NotThreadSafe +public class XTSAESBlockCipher implements BlockCipher { + + private final XTSCore core; + private final int blockSize; + private final int dataUnitSize; + private long dataUnit; + private int index; + + XTSAESBlockCipher( + XTSCore core, + int blockSize, + int dataUnitSize, + long dataUnit, + int index) { + + this.core = Objects.requireNonNull(core, "core"); + this.blockSize = blockSize; + this.dataUnitSize = dataUnitSize; + this.dataUnit = dataUnit; + this.index = index; + } + + XTSAESBlockCipher(XTSCore core, LongFunction tweakValueFunction, int dataUnitSize) { + this(new XTSCore(new XTSTweak(tweakValueFunction)), core.getBlockSize(), dataUnitSize, 0, 0); + } + + public XTSAESBlockCipher(boolean isDefault, LongFunction tweakValueFunction, int dataUnitSize) { + this(new XTSCore(isDefault), tweakValueFunction, dataUnitSize); + } + + public XTSAESBlockCipher(boolean isDefault, int dataUnitSize) { + this(new XTSCore(isDefault), isDefault?XTSTweak::defaultTweakFunction:XTSTweak::nintTweakFunction, dataUnitSize); + } + + @Override + public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException { + if (params instanceof KeyParameter) { + core.init(forEncryption, (KeyParameter) params); + return; + } + throw new IllegalArgumentException("invalid params: " + params.getClass().getName()); + } + + @Override + public String getAlgorithmName() { + return core.getAlgorithmName(); + } + + @Override + public int getBlockSize() { + return blockSize; + } + + @Override + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) + throws DataLengthException, IllegalStateException { + + if (index == 0) { + core.reset(dataUnit); + } + + if ((index += blockSize) == dataUnitSize) { + dataUnit++; + index = 0; + } + + return core.processBlock(in, inOff, out, outOff); + } + + @Override + public void reset() { + index = 0; + dataUnit = 0; + } +} +// TODO consider passing initial data unit sequence number in addition to key via CipherParameters subclass diff --git a/src/main/java/konogonka/xtsaes/XTSAESCipher.java b/src/main/java/konogonka/xtsaes/XTSAESCipher.java new file mode 100644 index 0000000..b75d09e --- /dev/null +++ b/src/main/java/konogonka/xtsaes/XTSAESCipher.java @@ -0,0 +1,114 @@ +/* + * The MIT License + * + * Copyright 2016 Ahseya. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package konogonka.xtsaes; + +import net.jcip.annotations.NotThreadSafe; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.KeyParameter; + +import java.util.Objects; +import java.util.function.LongFunction; + +/** + * XTS-AES cipher with arbitrary (non 128-bit aligned) data unit lengths. + * + * @author Ahseya + * @author Dmitry Isaenko (updates for special usage) + */ +@NotThreadSafe +public class XTSAESCipher { + + private final XTSCore core; + private final int blockSize; + + XTSAESCipher(XTSCore core) { + this.core = Objects.requireNonNull(core, "core"); + blockSize = core.getBlockSize(); + } + + public XTSAESCipher(LongFunction tweakFunction) { + this(new XTSCore(new XTSTweak(tweakFunction))); + } + + public XTSAESCipher(boolean isDefault) { + this(new XTSCore(isDefault)); + } + + String getAlgorithmName() { + return core.getAlgorithmName(); + } + + int getBlockSize() { + return blockSize; + } + + public XTSAESCipher init(boolean forEncryption, KeyParameter key) throws IllegalArgumentException { + core.init(forEncryption, key); + return this; + } + + public XTSAESCipher init(boolean forEncryption, KeyParameter key1, KeyParameter key2) + throws IllegalArgumentException { + + core.init(forEncryption, key1, key2); + return this; + } + + public int processDataUnit(byte[] in, int inOff, int length, byte[] out, int outOff, long sequenceNumber) + throws DataLengthException, IllegalStateException { + + core.reset(sequenceNumber); + return process(in, inOff, length, out, outOff); + } + + int process(byte[] in, int inOff, int length, byte[] out, int outOff) + throws DataLengthException, IllegalStateException { + + if (length < blockSize) { + throw new DataLengthException("data unit size too small: " + length); + } + + if (inOff + length > in.length) { + throw new DataLengthException("input buffer too small for data unit size: " + length); + } + + if (outOff + length > out.length) { + throw new DataLengthException("output buffer too small for data unit size: " + length); + } + + int to = length % blockSize == 0 + ? length + : length - (blockSize * 2); + + int i; + for (i = 0; i < to; i += blockSize) { + core.processBlock(in, inOff + i, out, outOff + i); + } + + if (length > i) { + core.processPartial(in, inOff + i, out, outOff + i, length - i); + } + return length; + } +} diff --git a/src/main/java/konogonka/xtsaes/XTSCore.java b/src/main/java/konogonka/xtsaes/XTSCore.java new file mode 100644 index 0000000..32f7a84 --- /dev/null +++ b/src/main/java/konogonka/xtsaes/XTSCore.java @@ -0,0 +1,151 @@ +/* + * The MIT License + * + * Copyright 2016 Ahseya. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package konogonka.xtsaes; + +import net.jcip.annotations.NotThreadSafe; +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.engines.AESFastEngine; +import org.bouncycastle.crypto.params.KeyParameter; + +import java.util.Arrays; +import java.util.Objects; + +/** + * XTS core functions. + * + * @author Ahseya + * @author Dmitry Isaenko (updates for special usage) + */ +@NotThreadSafe +class XTSCore { + + private static final int BLOCK_SIZE = 16; + + private final BlockCipher cipher; + private final XTSTweak tweak; + private boolean forEncryption; + + XTSCore(BlockCipher cipher, XTSTweak tweak) { + this.cipher = Objects.requireNonNull(cipher, "cipher"); + this.tweak = Objects.requireNonNull(tweak, "tweak"); + } + + XTSCore(XTSTweak tweak) { + this(new AESFastEngine(), tweak); + } + + XTSCore(boolean isDefault) { + this(new XTSTweak(isDefault)); + } + + XTSCore init(boolean forEncryption, KeyParameter key) throws IllegalArgumentException { + byte[] k = ((KeyParameter) key).getKey(); + if (k.length != 32 && k.length != 64) { + throw new IllegalArgumentException("bad key length: " + k.length); + } + + byte[] key1 = Arrays.copyOfRange(k, 0, k.length / 2); + byte[] key2 = Arrays.copyOfRange(k, k.length / 2, k.length); + + return init(forEncryption, new KeyParameter(key1), new KeyParameter(key2)); + } + + XTSCore init(boolean forEncryption, KeyParameter key1, KeyParameter key2) throws IllegalArgumentException { + cipher.init(forEncryption, key1); + tweak.init(key2); + this.forEncryption = forEncryption; + return this; + } + + XTSCore reset(long tweakValue) throws DataLengthException, IllegalStateException { + tweak.reset(tweakValue); + return this; + } + + String getAlgorithmName() { + return cipher.getAlgorithmName(); + } + + int getBlockSize() { + return BLOCK_SIZE; + } + + int processBlock(byte[] in, int inOff, byte[] out, int outOff) throws DataLengthException, IllegalStateException { + byte[] tweakValue = tweak.value(); + doProcessBlock(in, inOff, out, outOff, tweakValue); + tweak.next(); + return BLOCK_SIZE; + } + + int doProcessBlock(byte[] in, int inOff, byte[] out, int outOff, byte[] tweakValue) + throws DataLengthException, IllegalStateException { + + merge(in, inOff, out, outOff, tweakValue); + cipher.processBlock(out, outOff, out, outOff); + merge(out, outOff, out, outOff, tweakValue); + return BLOCK_SIZE; + } + + void merge(byte[] in, int inOff, byte[] out, int outOff, byte[] tweak) { + for (int i = 0; i < BLOCK_SIZE; i++) { + out[i + outOff] = (byte) (in[i + inOff] ^ tweak[i]); + } + } + + int processPartial(byte[] in, int inOff, byte[] out, int outOff, int length) { + if (length <= BLOCK_SIZE) { + throw new DataLengthException("input buffer too small/ missing last two blocks: " + length); + } + + if (length >= BLOCK_SIZE * 2) { + throw new DataLengthException("input buffer too large/ non-partial final block: " + length); + } + + byte[] tweakA = tweak.value(); + byte[] tweakB = tweak.next().value(); + + return forEncryption + ? XTSCore.this.doProcessPartial(in, inOff, out, outOff, length, tweakA, tweakB) + : XTSCore.this.doProcessPartial(in, inOff, out, outOff, length, tweakB, tweakA); + } + + int doProcessPartial(byte[] in, int inOff, byte[] out, int outOff, int length, byte[] tweakA, byte[] tweakB) + throws DataLengthException, IllegalStateException { + // M-1 block + doProcessBlock(in, inOff, out, outOff, tweakA); + + // Cipher stealing + byte[] buffer = Arrays.copyOfRange(out, outOff, outOff + BLOCK_SIZE); + System.arraycopy(in, inOff + BLOCK_SIZE, buffer, 0, length - BLOCK_SIZE); + + // M block + doProcessBlock(buffer, 0, buffer, 0, tweakB); + + // Copy over final block pair + System.arraycopy(out, outOff, out, outOff + BLOCK_SIZE, length - BLOCK_SIZE); + System.arraycopy(buffer, 0, out, outOff, BLOCK_SIZE); + return length; + } +} diff --git a/src/main/java/konogonka/xtsaes/XTSTweak.java b/src/main/java/konogonka/xtsaes/XTSTweak.java new file mode 100644 index 0000000..83f15cc --- /dev/null +++ b/src/main/java/konogonka/xtsaes/XTSTweak.java @@ -0,0 +1,126 @@ +/* + * The MIT License + * + * Copyright 2016 Ahseya. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package konogonka.xtsaes; + +import net.jcip.annotations.NotThreadSafe; +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.engines.AESFastEngine; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.Pack; + +import java.util.Arrays; +import java.util.Objects; +import java.util.function.LongFunction; + +/** + * XTS tweak with pluggable tweak function. + * + * @author Ahseya + * @author Dmitry Isaenko (updates for special usage) + */ +@NotThreadSafe +class XTSTweak { + static byte[] nintTweakFunction(long tweakValue) { + byte[] bs = new byte[BLOCK_SIZE]; + byte[] twk = Pack.longToBigEndian(tweakValue); + int j = BLOCK_SIZE-twk.length; + for (byte b: twk){ + bs[j++] = b; + } + return bs; + } + + static byte[] defaultTweakFunction(long tweakValue) { + byte[] bs = Pack.longToLittleEndian(tweakValue); + bs = Arrays.copyOfRange(bs, 0, BLOCK_SIZE); + return bs; + } + + private static final long FDBK = 0x87; + private static final long MSB = 0x8000000000000000L; + private static final int BLOCK_SIZE = 16; + + private final BlockCipher cipher; + private final LongFunction tweakFunction; + private final byte[] tweak; + + XTSTweak(BlockCipher cipher, LongFunction tweakFunction, byte[] tweak) { + this.cipher = Objects.requireNonNull(cipher, "cipher"); + this.tweakFunction = Objects.requireNonNull(tweakFunction, "tweakFunction"); + this.tweak = Objects.requireNonNull(tweak, "tweak"); + + if (cipher.getBlockSize() != BLOCK_SIZE) { + throw new IllegalArgumentException("bad block size: " + cipher.getBlockSize()); + } + } + + XTSTweak(BlockCipher cipher, LongFunction tweakFunction) { + this(cipher, tweakFunction, new byte[cipher.getBlockSize()]); + } + + XTSTweak(LongFunction tweakFunction) { + this(new AESFastEngine(), tweakFunction); + } + + XTSTweak(boolean isDefault) { + this(isDefault + ? XTSTweak::defaultTweakFunction + : XTSTweak::nintTweakFunction); + } + + XTSTweak init(KeyParameter key) throws IllegalArgumentException { + cipher.init(true, key); + return this; + } + + XTSTweak reset(long tweakValue) throws DataLengthException, IllegalStateException { + return reset(tweakFunction.apply(tweakValue)); + } + + XTSTweak reset(byte[] tweakBytes) throws DataLengthException, IllegalStateException { + cipher.processBlock(tweakBytes, 0, tweak, 0); + return this; + } + + byte[] value() { + return Arrays.copyOf(tweak, tweak.length); + } + + XTSTweak next() { + long lo = Pack.littleEndianToLong(tweak, 0); + long hi = Pack.littleEndianToLong(tweak, 8); + + long fdbk = (hi & MSB) == 0 + ? 0L + : FDBK; + + hi = (hi << 1) | (lo >>> 63); + lo = (lo << 1) ^ fdbk; + + Pack.longToLittleEndian(lo, tweak, 0); + Pack.longToLittleEndian(hi, tweak, 8); + return this; + } +} diff --git a/src/main/resources/FXML/NCA/NCASectionContent.fxml b/src/main/resources/FXML/NCA/NCASectionContent.fxml new file mode 100644 index 0000000..0ad2f62 --- /dev/null +++ b/src/main/resources/FXML/NCA/NCASectionContent.fxml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/FXML/NCA/NCASectionHeaderBlock.fxml b/src/main/resources/FXML/NCA/NCASectionHeaderBlock.fxml new file mode 100644 index 0000000..d8e81cb --- /dev/null +++ b/src/main/resources/FXML/NCA/NCASectionHeaderBlock.fxml @@ -0,0 +1,1618 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/FXML/NCA/NCATab.fxml b/src/main/resources/FXML/NCA/NCATab.fxml new file mode 100644 index 0000000..99779e3 --- /dev/null +++ b/src/main/resources/FXML/NCA/NCATab.fxml @@ -0,0 +1,717 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/FXML/NCA/NCATable.fxml b/src/main/resources/FXML/NCA/NCATable.fxml new file mode 100644 index 0000000..d9ae494 --- /dev/null +++ b/src/main/resources/FXML/NCA/NCATable.fxml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/FXML/NSP/NSPTab.fxml b/src/main/resources/FXML/NSP/NSPTab.fxml new file mode 100644 index 0000000..8834097 --- /dev/null +++ b/src/main/resources/FXML/NSP/NSPTab.fxml @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+
+ + + +
diff --git a/src/main/resources/FXML/NSP/TableView.fxml b/src/main/resources/FXML/NSP/TableView.fxml new file mode 100644 index 0000000..5b8c2b8 --- /dev/null +++ b/src/main/resources/FXML/NSP/TableView.fxml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/main/resources/FXML/Settings/SettingsLayout.fxml b/src/main/resources/FXML/Settings/SettingsLayout.fxml new file mode 100644 index 0000000..0b0636a --- /dev/null +++ b/src/main/resources/FXML/Settings/SettingsLayout.fxml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/FXML/XCI/Hfs0TableView.fxml b/src/main/resources/FXML/XCI/Hfs0TableView.fxml new file mode 100644 index 0000000..8ca0d98 --- /dev/null +++ b/src/main/resources/FXML/XCI/Hfs0TableView.fxml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/main/resources/FXML/XCI/XCITab.fxml b/src/main/resources/FXML/XCI/XCITab.fxml new file mode 100644 index 0000000..14f9789 --- /dev/null +++ b/src/main/resources/FXML/XCI/XCITab.fxml @@ -0,0 +1,1205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/FXML/landingPage.fxml b/src/main/resources/FXML/landingPage.fxml new file mode 100644 index 0000000..aebe046 --- /dev/null +++ b/src/main/resources/FXML/landingPage.fxml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +