diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..94a9ed02 --- /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. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..f16cd451 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# pkNX +pkNX... for lack of a better name! diff --git a/pkNX.Containers/Container.cs b/pkNX.Containers/Container.cs new file mode 100644 index 00000000..a8fca6fa --- /dev/null +++ b/pkNX.Containers/Container.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; + +namespace pkNX.Containers +{ + public static class Container + { + /// + /// Gets a new for the provided and type. + /// + /// File location + /// File type + public static IFileContainer GetContainer(string path, ContainerType t) + { + switch (t) + { + case ContainerType.GARC: return new GARC(path); + case ContainerType.Mini: return MiniUtil.GetMini(path); + case ContainerType.SARC: return new SARC(path); + case ContainerType.Folder: return new FolderContainer(path); + case ContainerType.SingleFile: return new SingleFileContainer(path); + default: + throw new ArgumentOutOfRangeException(nameof(t), t, null); + } + } + + /// + /// Gets a for the stream. + /// + /// Path to the binary data + public static IFileContainer GetContainer(string path) + { + var fs = new FileStream(path, FileMode.Open); + var container = GetContainer(fs); + if (!(container is LargeContainer)) // not kept + fs.Dispose(); + if (container == null) + return null; + + container.FilePath = path; + return container; + } + + /// + /// Gets a for the stream. + /// + /// Stream for the binary data + public static IFileContainer GetContainer(Stream stream) + { + BinaryReader br = new BinaryReader(stream); + var container = GetContainer(br); + if (!(container is LargeContainer)) // not kept + br.Dispose(); + return container; + } + + /// + /// Gets a for the stream within the . + /// + /// Reader for the binary data + public static IFileContainer GetContainer(BinaryReader br) + { + IFileContainer container; + if ((container = GARC.GetGARC(br)) != null) + return container; + if ((container = MiniUtil.GetMini(br)) != null) + return container; + if ((container = SARC.GetSARC(br)) != null) + return container; + return null; + } + } +} diff --git a/pkNX.Containers/ContainerHandler/ContainerHandler.cs b/pkNX.Containers/ContainerHandler/ContainerHandler.cs new file mode 100644 index 00000000..c2069ace --- /dev/null +++ b/pkNX.Containers/ContainerHandler/ContainerHandler.cs @@ -0,0 +1,29 @@ +using System; +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable EventNeverSubscribedTo.Global + +namespace pkNX.Containers +{ + public sealed class ContainerHandler + { + public event EventHandler FileCountDetermined; + public event EventHandler FileProgressed; + + private int count; + + public void Initialize(int total) + { + count = total; + var args = new FileCountDeterminedEventArgs {Total = total}; + FileCountDetermined?.Invoke(null, args); + } + + public void StepFile(int ctr, int total = -1, string fileName = null) + { + if (total < 0) + total = count; + var args = new FileProgressedEventArgs {Current = ctr, Total = total, CurrentFile = fileName}; + FileProgressed?.Invoke(null, args); + } + } +} \ No newline at end of file diff --git a/pkNX.Containers/ContainerHandler/FileCountDeterminedEventArgs.cs b/pkNX.Containers/ContainerHandler/FileCountDeterminedEventArgs.cs new file mode 100644 index 00000000..359a4f9b --- /dev/null +++ b/pkNX.Containers/ContainerHandler/FileCountDeterminedEventArgs.cs @@ -0,0 +1,9 @@ +using System; + +namespace pkNX.Containers +{ + public class FileCountDeterminedEventArgs : EventArgs + { + public int Total { get; set; } + } +} \ No newline at end of file diff --git a/pkNX.Containers/ContainerHandler/FileProgressedEventArgs.cs b/pkNX.Containers/ContainerHandler/FileProgressedEventArgs.cs new file mode 100644 index 00000000..7b49e7bd --- /dev/null +++ b/pkNX.Containers/ContainerHandler/FileProgressedEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace pkNX.Containers +{ + public class FileProgressedEventArgs : EventArgs + { + public int Current { get; set; } + public int Total { get; set; } + public string CurrentFile { get; set; } + } +} \ No newline at end of file diff --git a/pkNX.Containers/ContainerParent.cs b/pkNX.Containers/ContainerParent.cs new file mode 100644 index 00000000..87b2d6be --- /dev/null +++ b/pkNX.Containers/ContainerParent.cs @@ -0,0 +1,15 @@ +namespace pkNX.Containers +{ + public enum ContainerParent + { + /// + /// File is located in the RomFS. + /// + RomFS, + + /// + /// File is located in the ExeFS. + /// + ExeFS, + } +} \ No newline at end of file diff --git a/pkNX.Containers/ContainerType.cs b/pkNX.Containers/ContainerType.cs new file mode 100644 index 00000000..ce38b7f9 --- /dev/null +++ b/pkNX.Containers/ContainerType.cs @@ -0,0 +1,11 @@ +namespace pkNX.Containers +{ + public enum ContainerType + { + GARC, + Mini, + SARC, + Folder, + SingleFile, + } +} diff --git a/pkNX.Containers/FolderContainer.cs b/pkNX.Containers/FolderContainer.cs new file mode 100644 index 00000000..a1fea5b5 --- /dev/null +++ b/pkNX.Containers/FolderContainer.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace pkNX.Containers +{ + public class FolderContainer : IFileContainer + { + private readonly List Paths = new List(); + private readonly List Data = new List(); + private readonly List TrackModify = new List(); + + public FolderContainer() { } + public FolderContainer(IEnumerable files) => AddFiles(files); + + public FolderContainer(string path) + { + FilePath = path; + AddFiles(Directory.GetFiles(path, "*", SearchOption.AllDirectories)); + } + + public void AddFile(string file, byte[] data = null) + { + Paths.Add(file); + Data.Add(data); + TrackModify.Add(false); + } + + public void AddFiles(IEnumerable files) + { + foreach (var f in files) + AddFile(f); + } + + public byte[] GetFileData(string file) + { + var index = Paths.IndexOf(file); + if (index < 0) + return null; + return Data[index] ?? (Data[index] = File.ReadAllBytes(file)); + } + + public byte[] GetFileData(int index) + { + if (index < 0 || Data.Count >= index) + return null; + return Data[index] ?? (Data[index] = File.ReadAllBytes(Paths[index])); + } + + public byte[] this[int index] + { + get => GetFileData(index); + set + { + if (value != null && Data[index] != null) + TrackModify[index] = value.SequenceEqual(Data[index]); + Data[index] = value; + } + } + + public string FilePath { get; set; } + public bool Modified => TrackModify.Count != 0; + public int Count => Paths.Count; + + public Task GetFiles() => new Task(() => Paths.Select(File.ReadAllBytes).ToArray()); + public Task GetFile(int file, int subFile = 0) => new Task(() => this[file]); + public Task SetFile(int file, byte[] value, int subFile = 0) => new Task(() => this[file] = value); + public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new Task(SaveAll); + + private void SaveAll() + { + for (int i = 0; i < Paths.Count; i++) + { + if (!TrackModify[i]) + continue; + var data = Data[i]; + if (data == null) + continue; + File.WriteAllBytes(Paths[i], data); + } + } + + public void Dump(string path, ContainerHandler handler) + { + SaveAll(); // there's really nothing to dump, just save any modified + } + } +} diff --git a/pkNX.Containers/GARC/FATB.cs b/pkNX.Containers/GARC/FATB.cs new file mode 100644 index 00000000..9e624d33 --- /dev/null +++ b/pkNX.Containers/GARC/FATB.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.IO; + +namespace pkNX.Containers +{ + internal class FATB + { + private const uint MAGIC = 0x46415442; + + private readonly uint Magic = MAGIC; + private readonly int HeaderSize = 0xC; + private readonly ushort EntryCount; + private readonly short Padding = -1; + + private FATBEntry[] Entries { get; } + public FATBEntry this[int index] => Entries[index]; + + public FATB(BinaryReader br, int DataOffset) + { + Magic = br.ReadUInt32(); + HeaderSize = br.ReadInt32(); + EntryCount = br.ReadUInt16(); + Padding = br.ReadInt16(); + + Entries = new FATBEntry[EntryCount]; + for (int i = 0; i < EntryCount; i++) + Entries[i] = new FATBEntry(br, DataOffset); + } + + public FATB(IReadOnlyList files) + { + EntryCount = (ushort)files.Count; + Entries = new FATBEntry[EntryCount]; + for (int i = 0; i < EntryCount; i++) + { + if (!Directory.Exists(files[i])) + Entries[i] = new FATBEntry(files[i]); + else + Entries[i] = new FATBEntry(Directory.GetFiles(files[i])); + } + } + + public void Write(BinaryWriter bw) + { + bw.Write(Magic); + bw.Write(HeaderSize); + bw.Write(EntryCount); + bw.Write(Padding); + foreach (var entry in Entries) + entry.Write(bw); + // sub entry writing handled separately + } + } +} \ No newline at end of file diff --git a/pkNX.Containers/GARC/FATBEntry.cs b/pkNX.Containers/GARC/FATBEntry.cs new file mode 100644 index 00000000..906b4888 --- /dev/null +++ b/pkNX.Containers/GARC/FATBEntry.cs @@ -0,0 +1,128 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace pkNX.Containers +{ + internal class FATBEntry + { + private readonly uint Vector; + public readonly bool IsFolder; + public readonly FATBSubEntry[] SubEntries; + + // ReSharper disable once UnusedMember.Local -- needed to init subentries + private FATBEntry() + { + SubEntries = new FATBSubEntry[32]; + for (int i = 0; i < SubEntries.Length; i++) + SubEntries[i] = new FATBSubEntry(); + } + + public FATBEntry(string file) + { + IsFolder = false; + Vector = 1; + SubEntries[0].Exists = true; + SubEntries[0].File = file; + } + + public FATBEntry(IEnumerable files) + { + IsFolder = true; + Vector = 0; + foreach (var f in files) + { + var fn = Path.GetFileNameWithoutExtension(f); + if (!int.TryParse(fn, out var val) || val >= SubEntries.Length) + continue; + if (FATBSubEntry.GetFileNumber(val) != fn) + continue; + + Vector |= (uint)(1 << val); + + SubEntries[val].Exists = true; + SubEntries[val].File = f; + } + } + + public FATBEntry(BinaryReader br, int DataOffset) + { + Vector = br.ReadUInt32(); + + int ctr = 0; + for (int b = 0; b < 32; b++) + { + SubEntries[b].Exists = (Vector & 1 << b) != 0; + if (!SubEntries[b].Exists) + continue; + SubEntries[b].Start = br.ReadInt32(); + SubEntries[b].End = br.ReadInt32(); + SubEntries[b].Length = br.ReadInt32(); + SubEntries[b].ParentDataPosition = DataOffset; + ctr++; + } + IsFolder = ctr > 1; + } + + public void Write(BinaryWriter bw) + { + bw.Write(Vector); + foreach (var s in SubEntries.Where(s => s.Exists)) + { + bw.Write(s.Start); + bw.Write(s.End); + bw.Write(s.Length); + } + } + + public void WriteEntries(BinaryWriter bw, Stream originalStream, int padToNearest, int DataOffset, ref int max, ref int maxPad) + { + var dest = bw.BaseStream; + + bw.Write(Vector); + foreach (var entry in SubEntries.Where(z => z.Exists)) + { + entry.Write(originalStream, dest, DataOffset); + + int padding = GetPadding(entry.Length, padToNearest); + if (max < entry.Length) + { + max = entry.Length; + maxPad = entry.Length + padding; + } + + while (padding-- > 0) + bw.Write((byte)0xFF); + } + } + + private static int GetPadding(int length, int padTo) + { + var remain = length % padTo; + return remain == 0 ? 0 : padTo - remain; + } + + public void Dump(string path, int index, string format, Stream parent, int DataOffset) + { + var fn = index.ToString(format); + if (!IsFolder) + { + var loc = Path.Combine(path, fn + ".bin"); + SubEntries[0].Dump(parent, loc, DataOffset); + return; + } + + path = Path.Combine(path, fn); + Directory.CreateDirectory(path); + for (var i = 0; i < SubEntries.Length; i++) + { + var entry = SubEntries[i]; + if (!entry.Exists) + continue; + fn = i.ToString("D2"); + var loc = Path.Combine(path, fn + ".bin"); + entry.Dump(parent, loc, DataOffset); + } + } + } +} \ No newline at end of file diff --git a/pkNX.Containers/GARC/FATBSubEntry.cs b/pkNX.Containers/GARC/FATBSubEntry.cs new file mode 100644 index 00000000..0c97dc02 --- /dev/null +++ b/pkNX.Containers/GARC/FATBSubEntry.cs @@ -0,0 +1,8 @@ +namespace pkNX.Containers +{ + public class FATBSubEntry : LargeContainerEntry + { + public bool Exists; + public static string GetFileNumber(int index) => $"{index:00}"; + } +} \ No newline at end of file diff --git a/pkNX.Containers/GARC/FATO.cs b/pkNX.Containers/GARC/FATO.cs new file mode 100644 index 00000000..4900fdda --- /dev/null +++ b/pkNX.Containers/GARC/FATO.cs @@ -0,0 +1,47 @@ +using System.IO; + +namespace pkNX.Containers +{ + internal class FATO + { + private const uint MAGIC = 0x4641544F; + + private readonly uint Magic = MAGIC; + private readonly int HeaderSize = 0xC; + private readonly ushort EntryCount; + private readonly short Padding = -1; + + private FATOEntry[] Entries { get; } + public FATOEntry this[int index] => Entries[index]; + + public FATO(BinaryReader br) + { + Magic = br.ReadUInt32(); + HeaderSize = br.ReadInt32(); + EntryCount = br.ReadUInt16(); + Padding = br.ReadInt16(); + + Entries = new FATOEntry[EntryCount]; + for (int i = 0; i < EntryCount; i++) + Entries[i] = new FATOEntry(br); + } + + public FATO(int count) + { + EntryCount = (ushort)count; + Entries = new FATOEntry[EntryCount]; + for (int i = 0; i < EntryCount; i++) + Entries[i] = new FATOEntry(); + } + + public void Write(BinaryWriter bw) + { + bw.Write(Magic); + bw.Write(HeaderSize); + bw.Write(EntryCount); + bw.Write(Padding); + foreach (var entry in Entries) + entry.Write(bw); + } + } +} \ No newline at end of file diff --git a/pkNX.Containers/GARC/FATOEntry.cs b/pkNX.Containers/GARC/FATOEntry.cs new file mode 100644 index 00000000..baf5894e --- /dev/null +++ b/pkNX.Containers/GARC/FATOEntry.cs @@ -0,0 +1,14 @@ +using System.IO; + +namespace pkNX.Containers +{ + internal class FATOEntry + { + public uint Offset { get; set; } + + internal FATOEntry(uint offset = 0) => Offset = offset; + internal FATOEntry(BinaryReader br) => Offset = br.ReadUInt32(); + + internal void Write(BinaryWriter bw) => bw.Write(Offset); + } +} \ No newline at end of file diff --git a/pkNX.Containers/GARC/GARC.cs b/pkNX.Containers/GARC/GARC.cs new file mode 100644 index 00000000..e2605603 --- /dev/null +++ b/pkNX.Containers/GARC/GARC.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace pkNX.Containers +{ + public class GARC : LargeContainer + { + private GARCHeader Header; + private FATO FATO; + private FATB FATB; + + public GARC(BinaryReader br) => OpenRead(br); + public GARC(string file) => OpenBinary(file); + + public GARC(IReadOnlyList files, GARCVersion version = GARCVersion.VER_6) + { + Header = new GARCHeader(version); + FATO = new FATO(files.Count); + FATB = new FATB(files); + } + + protected override void Initialize() + { + Reader.BaseStream.Position = 0; + Header = new GARCHeader(Reader); + FATO = new FATO(Reader); + FATB = new FATB(Reader, Header.DataOffset); + } + + protected override int GetFileOffset(int file, int subFile = 0) + { + var f = FATB[file].SubEntries[subFile]; + return f.Start; + } + + public override byte[] GetEntry(int index, int subFile) + { + var f = FATB[index].SubEntries[subFile]; + if (f.File is byte[] data) + return data; + + data = f.GetFileData(Reader.BaseStream); + f.File = data; // cache for future fetches + return data; + } + + public override void SetEntry(int index, byte[] value, int subFile) + { + var f = FATB[index].SubEntries[subFile]; + f.File = value; + } + + protected override Task Pack(BinaryWriter bw, ContainerHandler handler, CancellationToken token) + { + return Task.Run(() => PackGARC(bw, handler, token), token); + } + + private void PackGARC(BinaryWriter bw, ContainerHandler handler, CancellationToken token) + { + StartPack(); + handler.Initialize(Count); + + WriteIntro(bw); + int dataOffset = (int)bw.BaseStream.Position; + for (int i = 0; i < Count; i++) + { + if (token.IsCancellationRequested) + return; + WriteEntry(bw, i, dataOffset); + } + WriteIntro(bw, true); + bw.Flush(); + } + + private void StartPack() + { + Header.ContentLargestUnpadded = 0; + Header.ContentLargestPadded = 0; + } + + private void WriteIntro(BinaryWriter bw, bool lastPass = false) + { + Header.Write(bw); + FATO.Write(bw); + FATB.Write(bw); + if (lastPass) + Header.DataOffset = (int)bw.BaseStream.Position; + } + + private void WriteEntry(BinaryWriter bw, int i, int DataOffset) + { + FATO[i].Offset = (uint)bw.BaseStream.Position; + FATB[i].WriteEntries(bw, Reader?.BaseStream, Header.ContentPadToNearest, DataOffset, + ref Header.ContentLargestUnpadded, ref Header.ContentLargestPadded); + } + + public override void Dump(string path, ContainerHandler handler) + { + string format = this.GetFileFormatString(); + + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + + handler.Initialize(Count); + for (int i = 0; i < Count; i++) + { + FATB[i].Dump(path, i, format, Reader.BaseStream, Header.DataOffset); + handler.StepFile(i+1); + } + } + + public static GARC GetGARC(BinaryReader br) + { + if (br.BaseStream.Length < 20) + return null; + br.BaseStream.Position = 0; + if (br.ReadUInt32() != GARCHeader.MAGIC) + return null; + br.BaseStream.Position = 10; + var ver = br.ReadUInt16(); + + if (ver == 0x0600 || ver == 0x0400) + return new GARC(br); + return null; + } + } +} diff --git a/pkNX.Containers/GARC/GARCHeader.cs b/pkNX.Containers/GARC/GARCHeader.cs new file mode 100644 index 00000000..edea1eb6 --- /dev/null +++ b/pkNX.Containers/GARC/GARCHeader.cs @@ -0,0 +1,98 @@ +using System; +using System.IO; + +namespace pkNX.Containers +{ + public class GARCHeader + { + internal const uint MAGIC = 0x4E415243; // CRAG + private const ushort VER_4 = (ushort)GARCVersion.VER_4; + private const ushort VER_6 = (ushort)GARCVersion.VER_6; + + private readonly uint Magic = MAGIC; + private readonly int HeaderSize; + private readonly ushort Endianess = 0xFEFF; + private readonly ushort Version; + private readonly uint ChunkCount = 4; + + public int DataOffset { get; set; } + public uint FileSize { get; } + + public readonly int ContentPadToNearest = 4; // Format 6 Only (4 bytes is standard in VER_4, and is not stored) + public int ContentLargestUnpadded; + public int ContentLargestPadded; // Format 6 Only + + private bool VER6 => Version == VER_6; + private bool VER4 => Version == VER_4; + + public GARCHeader(GARCVersion version) + { + if (version == GARCVersion.VER_6) + Version = VER_6; + else if (version == GARCVersion.VER_4) + Version = VER_4; + else + Version = (ushort)version; + HeaderSize = VER6 ? 0x24 : 0x1C; + } + + public GARCHeader(BinaryReader br) + { + Magic = br.ReadUInt32(); + HeaderSize = br.ReadInt32(); + Endianess = br.ReadUInt16(); + Version = br.ReadUInt16(); + ChunkCount = br.ReadUInt32(); + + if (ChunkCount != 4) + throw new FormatException($"Invalid GARC Chunk Count: {ChunkCount}"); + + DataOffset = br.ReadInt32(); + FileSize = br.ReadUInt32(); + + switch (Version) + { + case VER_4: + ContentLargestUnpadded = br.ReadInt32(); + ContentPadToNearest = 4; + break; + case VER_6: + ContentLargestPadded = br.ReadInt32(); + ContentLargestUnpadded = br.ReadInt32(); + ContentPadToNearest = br.ReadInt32(); + break; + default: + throw new FormatException($"Invalid GARC Version: 0x{Version:X4}"); + } + } + + public void Write(BinaryWriter bw) + { + bw.Write(Magic); + bw.Write(HeaderSize); + bw.Write(Endianess); + bw.Write(Version); + bw.Write(ChunkCount); + + if (ChunkCount != 4) + throw new FormatException($"Invalid GARC Chunk Count: {ChunkCount}"); + + bw.Write(DataOffset); + bw.Write(FileSize); + + switch (Version) + { + case VER_4: + bw.Write(ContentLargestUnpadded); + break; + case VER_6: + bw.Write(ContentLargestPadded); + bw.Write(ContentLargestUnpadded); + bw.Write(ContentPadToNearest); + break; + default: + throw new FormatException($"Invalid GARC Version: 0x{Version:X4}"); + } + } + } +} \ No newline at end of file diff --git a/pkNX.Containers/GARC/GARCVersion.cs b/pkNX.Containers/GARC/GARCVersion.cs new file mode 100644 index 00000000..403f7320 --- /dev/null +++ b/pkNX.Containers/GARC/GARCVersion.cs @@ -0,0 +1,8 @@ +namespace pkNX.Containers +{ + public enum GARCVersion : ushort + { + VER_4 = 0x0400, + VER_6 = 0x0600, + } +} \ No newline at end of file diff --git a/pkNX.Containers/IFileContainer.cs b/pkNX.Containers/IFileContainer.cs new file mode 100644 index 00000000..803d0d9d --- /dev/null +++ b/pkNX.Containers/IFileContainer.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace pkNX.Containers +{ + /// + /// Array of Files + /// + public interface IFileContainer + { + /// + /// Path the was loaded from. + /// + string FilePath { get; set; } + + /// + /// Indication if the contents of the have been modified. + /// + bool Modified { get; } + + /// + /// Count of files inside the . + /// + int Count { get; } + + /// + /// File access for individually indexed files. + /// + /// File number to fetch + /// Data representing the file at the specified index. + byte[] this[int index] { get; set; } + + Task GetFiles(); + Task GetFile(int file, int subFile = 0); + Task SetFile(int file, byte[] value, int subFile = 0); + Task SaveAs(string path, ContainerHandler handler, CancellationToken token); + + void Dump(string path, ContainerHandler handler); + } + + public static partial class Extensions + { + public static string GetFileFormatString(this IFileContainer c) => "D" + Math.Ceiling(Math.Log10(c.Count)); + } +} diff --git a/pkNX.Containers/LargeContainer.cs b/pkNX.Containers/LargeContainer.cs new file mode 100644 index 00000000..c9be5824 --- /dev/null +++ b/pkNX.Containers/LargeContainer.cs @@ -0,0 +1,147 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using pkNX.Containers; + +namespace pkNX +{ + /// + /// More complex container format for large long lived archives. + /// + public abstract class LargeContainer : IDisposable, IFileContainer + { + public virtual int Count => Files.Length; + + public Task GetFiles() => new Task(() => { CacheAll(); return Files; }); + public Task GetFile(int file, int subFile = 0) => new Task(() => GetEntry(file, subFile)); + public Task SetFile(int file, byte[] value, int subFile = 0) => new Task(() => SetEntry(file, value, subFile)); + + public string Extension => FilePath == null ? null : Path.GetExtension(FilePath); + public string FileName => FilePath == null ? null : Path.GetFileName(FilePath); + public string FilePath { get; set; } + public bool Modified { get; private set; } + + protected byte[][] Files { get; set; } + + /// + /// Packs the to the specified writing stream. + /// + /// Stream Writer to write contents to. + /// Manager for monitoring progress. + /// Cancellation object + /// Awaitable Task + protected abstract Task Pack(BinaryWriter bw, ContainerHandler handler, CancellationToken token); + + #region File Reading + + protected BinaryReader Reader { get; private set; } + private Stream Stream; + + protected void OpenBinary(string path) + { + Stream = new FileStream(path, FileMode.Open); + Reader = new BinaryReader(Stream); + Initialize(); + } + + protected void OpenRead(BinaryReader br) + { + Reader = br; + Stream = br.BaseStream; + Initialize(); + } + + protected abstract void Initialize(); + + protected abstract int GetFileOffset(int file, int subFile = 0); + + public BinaryReader Seek(int file, long offset = 0, int subFile = 0) + { + offset += GetFileOffset(file, subFile); + Reader.BaseStream.Position = offset; + return Reader; + } + + public abstract byte[] GetEntry(int index, int subFile); + + public virtual void SetEntry(int index, byte[] value, int subFile) + { + Files[index] = value; + Modified |= value != null; + } + + private byte[] GetCachedValue(int i, int subFile) + { + return Files[i] ?? (Files[i] = GetEntry(i, subFile)); + } + + #endregion + + public byte[] this[int index] + { + get => GetCachedValue(index, 0); + set => SetEntry(index, value, 0); + } + + public void CacheAll() + { + for (int i = 0; i < Files.Length; i++) + { + if (Files[i] == null) + { + Files[i] = GetCachedValue(i, 0); + } + } + + Reader = null; + Stream.Close(); + } + + public void Reset() + { + if (Reader == null) + throw new ArgumentNullException(nameof(Reader)); + + for (int i = 0; i < Files.Length; i++) + Files[i] = null; + + Modified = false; + } + + public abstract void Dump(string path, ContainerHandler handler); + + public async Task SaveAs(string path, ContainerHandler handler, CancellationToken token = new CancellationToken()) + { + bool sameLocation = path == FilePath && Reader != null; + var writePath = sameLocation ? Path.GetTempFileName() : path; + + var stream = new FileStream(path, FileMode.CreateNew); + using (var bw = new BinaryWriter(stream)) + await Pack(bw, handler, token).ConfigureAwait(false); + + if (token.IsCancellationRequested) + { + stream.Close(); + File.Delete(path); + return; + } + + if (sameLocation && path != writePath) + { + if (File.Exists(path)) + File.Delete(path); + File.Move(writePath, path); + } + + Stream = stream; + Reader = new BinaryReader(Stream); + } + + public void Dispose() + { + Stream.Dispose(); + Reader.Dispose(); + } + } +} diff --git a/pkNX.Containers/LargeContainerEntry.cs b/pkNX.Containers/LargeContainerEntry.cs new file mode 100644 index 00000000..f6e00306 --- /dev/null +++ b/pkNX.Containers/LargeContainerEntry.cs @@ -0,0 +1,74 @@ +using System.IO; + +namespace pkNX.Containers +{ + public class LargeContainerEntry + { + public int Start { get; set; } + public int End { get; set; } + public virtual int Length { get; set; } + public object File { get; set; } + public int ParentDataPosition { get; set; } + + public byte[] GetFileData(Stream parent) + { + parent.Seek(Start + ParentDataPosition, SeekOrigin.Begin); + byte[] data = new byte[Length]; + parent.Read(data, 0, Length); + return data; + } + + public void Write(Stream parent, Stream dest, int DataOffset) + { + switch (File) + { + case string f: + WriteFrom(dest, DataOffset, f); + break; + case byte[] data: + WriteFrom(dest, DataOffset, data); + break; + default: + WriteFrom(dest, DataOffset, parent); + break; + } + } + + private void WriteFrom(Stream dest, int DataOffset, string f) + { + Start = (int)dest.Position - DataOffset; + using (var s = new FileStream(f, FileMode.Open, FileAccess.Read, FileShare.None)) + s.CopyTo(dest); + End = (int)dest.Position - DataOffset; + Length = End - Start; + } + + private void WriteFrom(Stream dest, int DataOffset, byte[] data) + { + Start = (int)dest.Position - DataOffset; + Length = data.Length; + End = Start + Length; + + File = data; + dest.Write(data, 0, data.Length); + } + + private void WriteFrom(Stream dest, int DataOffset, Stream source) + { + Start = (int)dest.Position - DataOffset; + End = Start + Length; + + source.Seek(Start + ParentDataPosition, SeekOrigin.Begin); + source.CopyTo(dest, Length); + } + + public void Dump(Stream parent, string path, int DataOffset) + { + if (File is string) + return; + + using (var file = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None)) + Write(parent, file, DataOffset); + } + } +} diff --git a/pkNX.Containers/Mini/Mini.cs b/pkNX.Containers/Mini/Mini.cs new file mode 100644 index 00000000..29c80150 --- /dev/null +++ b/pkNX.Containers/Mini/Mini.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace pkNX.Containers +{ + public class Mini : IFileContainer + { + public string Identifier { get; } + public byte[][] Files { get; } + + public Mini(byte[][] data, string ident) { Identifier = ident; Files = data; } + + public byte[] this[int index] + { + get => Files[index]; + set + { + Files[index] = value; + Modified = true; + } + } + + public string FilePath { get; set; } + + public bool Modified { get; set; } + public int Count => Files.Length; + + public Task GetFile(int file, int subFile = 0) => new Task(()=>Files[file]); + public Task SetFile(int file, byte[] value, int subFile = 0) => new Task(() => Files[file] = value); + public Task GetFiles() => new Task(() => Files); + + public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) + { + return new Task(() => + { + byte[] data = MiniUtil.PackMini(Files, Identifier); + File.WriteAllBytes(path, data); + }); + } + + public void Dump(string path, ContainerHandler handler) + { + string format = this.GetFileFormatString(); + Directory.CreateDirectory(path); + + handler.Initialize(Count); + for (int i = 0; i < Count; i++) + { + var fn = Path.Combine(path, i.ToString(format) + ".bin"); + File.WriteAllBytes(fn, Files[i]); + handler.StepFile(i + 1); + } + } + } +} diff --git a/pkNX.Containers/Mini/MiniUtil.cs b/pkNX.Containers/Mini/MiniUtil.cs new file mode 100644 index 00000000..c3f07ca9 --- /dev/null +++ b/pkNX.Containers/Mini/MiniUtil.cs @@ -0,0 +1,158 @@ +using System; +using System.IO; + +namespace pkNX.Containers +{ + /// + /// // Mini Packing Util + /// + public static class MiniUtil + { + public static byte[] PackMini(string folder, string identifier) => PackMini(Directory.GetFiles(folder), identifier); + + public static byte[] PackMini(string[] files, string identifier) + { + byte[][] fileData = new byte[files.Length][]; + for (int i = 0; i < fileData.Length; i++) + fileData[i] = File.ReadAllBytes(files[i]); + return PackMini(fileData, identifier); + } + + public static byte[] PackMini(byte[][] fileData, string identifier) + { + // Create new Binary with the relevant header bytes + byte[] data = new byte[4]; + data[0] = (byte)identifier[0]; + data[1] = (byte)identifier[1]; + Array.Copy(BitConverter.GetBytes((ushort)fileData.Length), 0, data, 2, 2); + + int count = fileData.Length; + int dataOffset = 4 + 4 + (count * 4); + + // Start the data filling. + using (MemoryStream dataout = new MemoryStream()) + using (MemoryStream offsetMap = new MemoryStream()) + using (BinaryWriter bd = new BinaryWriter(dataout)) + using (BinaryWriter bo = new BinaryWriter(offsetMap)) + { + // For each file... + for (int i = 0; i < count; i++) + { + // Write File Offset + uint fileOffset = (uint)(dataout.Position + dataOffset); + bo.Write(fileOffset); + + // Write File to Stream + bd.Write(fileData[i]); + + // Pad the Data MemoryStream with Zeroes until len%4=0; + while (dataout.Length % 4 != 0) + bd.Write((byte)0); + // File Offset will be updated as the offset is based off of the Data length. + } + // Cap the File + bo.Write((uint)(dataout.Position + dataOffset)); + + using (var newPack = new MemoryStream()) + using (var header = new MemoryStream(data)) + { + header.WriteTo(newPack); + offsetMap.WriteTo(newPack); + dataout.WriteTo(newPack); + return newPack.ToArray(); + } + } + } + + public static byte[][] UnpackMini(string file, string identifier = null) + { + byte[] fileData = File.ReadAllBytes(file); + return UnpackMini(fileData, identifier); + } + + public static byte[][] UnpackMini(byte[] fileData, string identifier = null) + { + if (fileData == null || fileData.Length < 4) + return null; + + if (identifier?.Length == 2) + { + if (identifier[0] != fileData[0] || identifier[1] != fileData[1]) + return null; + } + + int count = BitConverter.ToUInt16(fileData, 2); int ctr = 4; + int start = BitConverter.ToInt32(fileData, ctr); ctr += 4; + byte[][] returnData = new byte[count][]; + for (int i = 0; i < count; i++) + { + int end = BitConverter.ToInt32(fileData, ctr); ctr += 4; + int len = end - start; + byte[] data = new byte[len]; + Buffer.BlockCopy(fileData, start, data, 0, len); + returnData[i] = data; + start = end; + } + return returnData; + } + + public static Mini GetMini(string path) + { + using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read)) + using (var br = new BinaryReader(fs)) + return GetMini(br); + } + + public static Mini GetMini(BinaryReader br) + { + var ident = GetIsMini(br); + if (ident == null) + return null; + + br.BaseStream.Position = 0; + var data = br.ReadBytes((int)br.BaseStream.Length); + var unpack = UnpackMini(data, ident); + return new Mini(unpack, ident); + } + + public static string GetIsMini(BinaryReader br) + { + if (br.BaseStream.Length < 12) + return null; + br.BaseStream.Position = 0; + var ident = br.ReadBytes(2); + var count = br.ReadUInt16(); + + int finalLengthOfs = 4 + (count * 4); + if (br.BaseStream.Length < finalLengthOfs + 4) + return null; + br.BaseStream.Position = 4 + (count * 4); + var len = br.ReadUInt32(); + if (len != br.BaseStream.Length) + return null; + return $"{(char)ident[0]}{(char)ident[1]}"; + } + + public static string GetIsMini(string path) + { + try + { + using (var fs = new FileStream(path, FileMode.Open)) + using (var br = new BinaryReader(fs)) + return GetIsMini(br); + } + catch { return null; } + } + + public static string GetIsMini(byte[] data) + { + try + { + using (var ms = new MemoryStream(data)) + using (var br = new BinaryReader(ms)) + return GetIsMini(br); + } + catch { return null; } + } + } +} diff --git a/pkNX.Containers/Misc/FnvHash.cs b/pkNX.Containers/Misc/FnvHash.cs new file mode 100644 index 00000000..a5202cd6 --- /dev/null +++ b/pkNX.Containers/Misc/FnvHash.cs @@ -0,0 +1,114 @@ +using System.Collections.Generic; + +namespace pkNX.Containers +{ + /// + /// Fowler–Noll–Vo non-cryptographic hash + /// + public static class FnvHash + { + // 64 bit implementation + private const ulong kFnvPrime_64 = 0x00000100000001b3; + private const ulong kOffsetBasis_64 = 0xCBF29CE484222645; + + /// + /// Gets the hash code of the input sequence via the default Fnv1 method. + /// + /// Input sequence + /// Computed hash code + public static ulong HashFnv1_64(IEnumerable input) + { + ulong hash = kOffsetBasis_64; + foreach (var c in input) + { + hash *= kFnvPrime_64; + hash ^= c; + } + return hash; + } + + /// + /// Gets the hash code of the input sequence via the default Fnv1 method. + /// + /// Input sequence + /// Computed hash code + public static ulong HashFnv1_64(IEnumerable input) + { + ulong hash = kOffsetBasis_64; + foreach (var c in input) + { + hash *= kFnvPrime_64; + hash ^= c; + } + return hash; + } + + /// + /// Gets the hash code of the input sequence via the alternative Fnv1 method. + /// + /// Input sequence + /// Computed hash code + public static ulong HashFnv1a_64(IEnumerable input) + { + ulong hash = kOffsetBasis_64; + foreach (var c in input) + { + hash ^= c; + hash *= kFnvPrime_64; + } + return hash; + } + + /// + /// Gets the hash code of the input sequence via the alternative Fnv1 method. + /// + /// Input sequence + /// Computed hash code + public static ulong HashFnv1a_64(IEnumerable input) + { + ulong hash = kOffsetBasis_64; + foreach (var c in input) + { + hash ^= c; + hash *= kFnvPrime_64; + } + return hash; + } + + // 32 bit implementation + private const uint kFnvPrime_32 = 0x01000193; + private const uint kOffsetBasis_32 = 0x811C9DC5; + + /// + /// Gets the hash code of the input sequence via the default Fnv1 method. + /// + /// Input sequence + /// Computed hash code + public static uint HashFnv1_32(IEnumerable input) + { + uint hash = kOffsetBasis_32; + foreach (var c in input) + { + hash *= kFnvPrime_32; + hash ^= c; + } + return hash; + } + + /// + /// Gets the hash code of the input sequence via the alternative Fnv1 method. + /// + /// Input sequence + /// Computed hash code + public static uint HashFnv1a_32(IEnumerable input) + { + uint hash = kOffsetBasis_32; + foreach (var c in input) + { + hash ^= c; + hash *= kFnvPrime_32; + } + return hash; + } + } +} diff --git a/pkNX.Containers/Misc/GFPack.cs b/pkNX.Containers/Misc/GFPack.cs new file mode 100644 index 00000000..123617d6 --- /dev/null +++ b/pkNX.Containers/Misc/GFPack.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace pkNX.Containers +{ + public class GFPack : IEnumerable, IFileContainer + { + private const ulong Magic = 0x4B434150_584C4647; // GFLXPACK + + // Overall structure: Header, metadata, and the raw compressed files + public GFPackHeader Header { get; set; } + public FileHashPath[] HashPaths { get; set; } + public FileHashIndex[] HashIndexes { get; set; } + public FileData[] FileTable { get; set; } + public byte[][] CompressedFiles { get; set; } + public byte[][] DecompressedFiles { get; set; } + + public GFPack(string directory, string parent = @"\bin") => LoadFiles(Directory.GetFiles(directory), parent); + public GFPack(string[] directories, string parent = @"\bin") => LoadFiles(directories, parent); + + /// + /// Initializes a packed object, and unpacks the data to accessible properties. + /// + /// Packed file + public GFPack(byte[] data) + { + using (var ms = new MemoryStream(data)) + using (var br = new BinaryReader(ms)) + ReadPack(br); + } + + public GFPack(BinaryReader br) => ReadPack(br); + + private void ReadPack(BinaryReader br) + { + Header = br.ReadBytes(GFPackHeader.SIZE).ToClass(); + Debug.Assert(Header.MAGIC == Magic); + + HashPaths = new FileHashPath[Header.CountFiles]; + for (int i = 0; i < HashPaths.Length; i++) + HashPaths[i] = br.ReadBytes(FileHashPath.SIZE).ToClass(); + + HashIndexes = new FileHashIndex[Header.CountFiles]; + for (int i = 0; i < HashIndexes.Length; i++) + HashIndexes[i] = br.ReadBytes(FileHashIndex.SIZE).ToClass(); + + FileTable = new FileData[Header.CountFiles]; + for (int i = 0; i < FileTable.Length; i++) + FileTable[i] = br.ReadBytes(FileData.SIZE).ToClass(); + + CompressedFiles = new byte[Header.CountFiles][]; + for (int i = 0; i < CompressedFiles.Length; i++) + { + br.BaseStream.Position = FileTable[i].OffsetPacked; + CompressedFiles[i] = br.ReadBytes(FileTable[i].SizeCompressed); + } + + DecompressedFiles = new byte[Header.CountFiles][]; + for (int i = 0; i < DecompressedFiles.Length; i++) + DecompressedFiles[i] = Decompress(CompressedFiles[i], FileTable[i].SizeDecompressed, FileTable[i].Type); + } + + /// + /// Intro bytes to the packed binary. + /// + [StructLayout(LayoutKind.Sequential)] + public class GFPackHeader + { + public const int SIZE = 0x40; + public ulong MAGIC = Magic; + public uint Version = 0x1000; + public uint IsRelocated; // bit0 + + /// + /// Count of Files packed into the binary. + /// + public int CountFiles; + + /// + /// Count of Folders packed into the binary. + /// + public int CountFolders; + + /// + /// Data offset for the table. + /// + public long PtrHashPaths; + + /// + /// Data offset for the table. + /// + public long PtrHashIndexes; + + /// + /// Data offset for the table. + /// + public long PtrFileTable; + + public ulong Reserved1; + public ulong Reserved2; + } + + [StructLayout(LayoutKind.Sequential)] + public class FileHashPath + { + public const int SIZE = 0x08; + + /// + /// Filename (with directory details) hash. + /// + public ulong HashFnv1aPathFull; + + public bool IsMatch(string fileName) => FnvHash.HashFnv1a_64(fileName) == HashFnv1aPathFull; + } + + [StructLayout(LayoutKind.Sequential)] + public class FileHashIndex + { + public const int SIZE = 0x10; + + /// + /// Filename (without directory details) hash. + /// + public ulong HashFnv1aPathFileName; + public int Index; + public uint Padding = 0xCC; + + public bool IsMatch(string fileName) => FnvHash.HashFnv1a_64(fileName) == HashFnv1aPathFileName; + } + + [StructLayout(LayoutKind.Sequential)] + public class FileData + { + public const int SIZE = 0x18; + + public ushort Level = 9; // quality? + public CompressionType Type; + public int SizeDecompressed; + public int SizeCompressed; + public int Padding = 0xCC; + public int OffsetPacked; + public uint unused; + } + + public enum CompressionType : ushort + { + None = 0, + Zlib = 1, + Lz4 = 2, + } + + public IEnumerator GetEnumerator() => (IEnumerator)DecompressedFiles.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => DecompressedFiles.GetEnumerator(); + public int Count => Header.CountFiles; + + public byte[] this[int index] + { + get => DecompressedFiles[index]; + set + { + Modified = true; + DecompressedFiles[index] = value; + } + } + + public int GetIndexFull(ulong hash) => Array.FindIndex(HashPaths, z => z.HashFnv1aPathFull == hash); + public int GetIndexFileName(ulong hash) => Array.FindIndex(HashIndexes, z => z.HashFnv1aPathFileName == hash); + public int GetIndexFileName(string name) => Array.FindIndex(HashIndexes, z => z.IsMatch(name)); + + public void LoadFiles(string[] directories, string parent, CompressionType type = CompressionType.Lz4) + { + var files = directories.SelectMany(Directory.GetFiles).ToArray(); + Header = new GFPackHeader { CountFolders = directories.Length, CountFiles = files.Length }; + + HashPaths = new FileHashPath[files.Length]; + HashIndexes = new FileHashIndex[files.Length]; + FileTable = new FileData[files.Length]; + DecompressedFiles = new byte[files.Length][]; + CompressedFiles = new byte[files.Length][]; + for (var i = 0; i < files.Length; i++) + { + var file = files[i]; + var nameshort = Path.GetFileName(file); + var namelong = file.Substring(file.IndexOf(parent, StringComparison.Ordinal)); + ulong hashShort = FnvHash.HashFnv1a_64(nameshort); + ulong hashFull = FnvHash.HashFnv1a_64(namelong); + + HashPaths[i] = new FileHashPath {HashFnv1aPathFull = hashFull}; + HashIndexes[i] = new FileHashIndex {HashFnv1aPathFileName = hashShort, Index = i}; + FileTable[i] = new FileData {Type = type}; + DecompressedFiles[i] = File.ReadAllBytes(file); + } + Modified = true; + } + + public byte[] Write() + { + using (var ms = new MemoryStream()) + using (var bw = new BinaryWriter(ms)) + { + WriteHeaderTableList(bw); + for (var i = 0; i < DecompressedFiles.Length; i++) + { + var entry = FileTable[i]; + var f = DecompressedFiles[i]; + var c = Compress(f, entry.Type); + CompressedFiles[i] = c; + + // update entry details + entry.SizeDecompressed = f.Length; + entry.SizeCompressed = c.Length; + entry.OffsetPacked = (int)bw.BaseStream.Position; + + bw.Write(c); + // no padding + } + bw.BaseStream.Position = 0; + WriteHeaderTableList(bw); + return ms.ToArray(); + } + } + + private static byte[] Decompress(byte[] encryptedData, int decryptedLength, CompressionType type) + { + switch (type) + { + case CompressionType.None: + return encryptedData; + case CompressionType.Zlib: + return null; // not implemented + default: + return LZ4.Decode(encryptedData, decryptedLength); + } + } + + private static byte[] Compress(byte[] decryptedData, CompressionType type) + { + switch (type) + { + case CompressionType.None: + return decryptedData; + case CompressionType.Zlib: + return null; // not implemented + default: + return LZ4.Encode(decryptedData); + } + } + + private void WriteHeaderTableList(BinaryWriter bw) + { + bw.Write(Header.ToBytesClass()); + Header.PtrHashPaths = bw.BaseStream.Position; + bw.Write(HashPaths.ToBytesClass()); + Header.PtrHashIndexes = bw.BaseStream.Position; + bw.Write(HashIndexes.ToBytesClass()); + Header.PtrFileTable = bw.BaseStream.Position; + bw.Write(FileTable.ToBytesClass()); + } + + public string FilePath { get; set; } + public bool Modified { get; set; } + public Task GetFiles() => new Task(() => DecompressedFiles); + public Task GetFile(int file, int subFile = 0) => new Task(Write); + public Task SetFile(int file, byte[] value, int subFile = 0) => new Task(() => this[file] = value); + public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new Task(() => Dump(path, handler)); + + public void Dump(string path, ContainerHandler handler) + { + for (var i = 0; i < FileTable.Length; i++) + { + var hashFull = HashPaths[i].HashFnv1aPathFull; + var loc = Path.Combine(path ?? FilePath, $"{i:000} {hashFull:X16}"); + var data = DecompressedFiles[i]; + File.WriteAllBytes(loc, data); + } + } + } +} diff --git a/pkNX.Containers/Misc/LZ4.cs b/pkNX.Containers/Misc/LZ4.cs new file mode 100644 index 00000000..28f1970f --- /dev/null +++ b/pkNX.Containers/Misc/LZ4.cs @@ -0,0 +1,10 @@ +using LZ4; + +namespace pkNX.Containers +{ + public static class LZ4 + { + public static byte[] Decode(byte[] data, int decLength) => LZ4Codec.Decode(data, 0, data.Length, decLength); + public static byte[] Encode(byte[] data) => LZ4Codec.Encode(data, 0, data.Length); + } +} diff --git a/pkNX.Containers/Misc/StructConverter.cs b/pkNX.Containers/Misc/StructConverter.cs new file mode 100644 index 00000000..9c7cdbdc --- /dev/null +++ b/pkNX.Containers/Misc/StructConverter.cs @@ -0,0 +1,46 @@ +using System; +using System.Runtime.InteropServices; + +namespace pkNX.Containers +{ + public static class StructConverter + { + public static T ToStructure(this byte[] bytes) where T : struct + { + var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + try { return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); } + finally { handle.Free(); } + } + + public static T ToClass(this byte[] bytes) where T : class + { + var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + try { return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); } + finally { handle.Free(); } + } + + public static byte[] ToBytesClass(this T obj) where T : class + { + int size = Marshal.SizeOf(obj); + byte[] arr = new byte[size]; + + IntPtr ptr = Marshal.AllocHGlobal(size); + Marshal.StructureToPtr(obj, ptr, true); + Marshal.Copy(ptr, arr, 0, size); + Marshal.FreeHGlobal(ptr); + return arr; + } + + public static byte[] ToBytes(this T obj) where T : struct + { + int size = Marshal.SizeOf(obj); + byte[] arr = new byte[size]; + + IntPtr ptr = Marshal.AllocHGlobal(size); + Marshal.StructureToPtr(obj, ptr, true); + Marshal.Copy(ptr, arr, 0, size); + Marshal.FreeHGlobal(ptr); + return arr; + } + } +} diff --git a/pkNX.Containers/SARC/SARC.cs b/pkNX.Containers/SARC/SARC.cs new file mode 100644 index 00000000..b3affca4 --- /dev/null +++ b/pkNX.Containers/SARC/SARC.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace pkNX.Containers +{ + /// + /// Simple (?) ARChive + /// + public sealed class SARC : LargeContainer + { + private const string Identifier = nameof(SARC); + + public SARCHeader Header { get; set; } + public SFAT SFAT; + public SFNT SFNT; + + public override int Count => SFAT.Entries.Count; + + /// + /// The required matches the first 4 bytes of the file data. + /// + public bool SigMatches => Header.Magic == Identifier; + + /// + /// Initializes an empty . + /// + /// Files inside the . + /// Root location of the archive + public SARC(IReadOnlyList files, string baseFolder) + { + Header = new SARCHeader(); + SFAT = new SFAT(baseFolder, files); + SFNT = new SFNT(); + } + + /// + /// Initializes a from a file location. + /// + /// + public SARC(string path) => OpenBinary(path); + + /// + /// Initializes a from a provided stream. + /// + /// + public SARC(Stream fs) => OpenRead(new BinaryReader(fs)); + + /// + /// Initializes a from a provided reader. + /// + /// + public SARC(BinaryReader br) => OpenRead(br); + + /// + /// Reads the contents of the header and file info tables. + /// + protected override void Initialize() + { + Header = new SARCHeader(Reader); + if (!SigMatches) + return; + SFAT = new SFAT(Reader, Header.DataOffset); + SFNT = new SFNT(Reader); + } + + protected override Task Pack(BinaryWriter bw, ContainerHandler handler, CancellationToken token) + { + return Task.Run(() => PackSARC(bw, handler, token), token); + } + + private void PackSARC(BinaryWriter bw, ContainerHandler handler, CancellationToken token) + { + StartPack(); + var count = SFAT.Entries.Count; + handler.Initialize(count); + var currentStringOffset = SFNT.StringOffset; + + WriteIntro(bw); + for (int i = 0; i < count; i++) + { + var entr = SFAT.Entries[i]; + if (entr.FileName == null) + entr.GetFileName(Reader.BaseStream, (int)currentStringOffset); + entr.WriteFileName(Reader.BaseStream, (int)SFNT.StringOffset); + while (Reader.BaseStream.Position % 4 != 0) + bw.Write((byte)0); + } + for (int i = 0; i < count; i++) + { + if (token.IsCancellationRequested) + return; + WriteEntry(bw, i); + } + WriteIntro(bw, true); + bw.Flush(); + } + + private void StartPack() { } + + private void WriteIntro(BinaryWriter bw, bool finalPass = false) + { + Header.Write(bw); + SFAT.Write(bw); + SFNT.Write(bw); + if (finalPass) + Header.DataOffset = (int)bw.BaseStream.Position; + } + + private void WriteEntry(BinaryWriter bw, int i) + { + throw new NotImplementedException(); + } + + protected override int GetFileOffset(int file, int subFile = 0) + { + var f = SFAT[file]; + return Header.DataOffset + f.Start; + } + + public override byte[] GetEntry(int index, int subFile) + { + var f = SFAT[index]; + if (f.File is byte[] data) + return data; + + data = f.GetFileData(Reader.BaseStream); + f.File = data; // cache for future fetches + return data; + } + + public override void Dump(string path, ContainerHandler handler) + { + path = path ?? FilePath; + if (path == null) + throw new ArgumentNullException(nameof(path)); + if (File.Exists(path)) + path = Path.GetDirectoryName(path); + if (path == null) + throw new ArgumentNullException(nameof(path)); + + var folder = FileName ?? "sarc"; + string dir = Path.Combine(path, folder); + + Directory.CreateDirectory(dir); + var count = SFAT.Entries.Count; + handler.Initialize(count); + for (int i = 0; i < count; i++) + { + SFAT.Entries[i].Dump(Reader.BaseStream, path, Header.DataOffset); + handler.StepFile(i + 1); + } + } + + public static SARC GetSARC(BinaryReader br) + { + if (br.BaseStream.Length < 20) + return null; + br.BaseStream.Position = 0; + var ident = new string(br.ReadChars(4)); + if (ident != SARCHeader.Identifier) + return null; + return new SARC(br); + } + } +} \ No newline at end of file diff --git a/pkNX.Containers/SARC/SARCHeader.cs b/pkNX.Containers/SARC/SARCHeader.cs new file mode 100644 index 00000000..a2d346d1 --- /dev/null +++ b/pkNX.Containers/SARC/SARCHeader.cs @@ -0,0 +1,47 @@ +using System.IO; + +namespace pkNX.Containers +{ + public class SARCHeader + { + public const string Identifier = nameof(SARC); + + /// + /// The required matches the first 4 bytes of the file data. + /// + public bool SigMatches => Magic == Identifier; + + public string Magic = Identifier; + public ushort HeaderSize = 0x14; + public ushort Endianness = 0xFFFE; + public uint FileSize; + public int DataOffset; + public ushort Version = 0x0100; + public ushort Reserved; + + public SARCHeader() { } + + public SARCHeader(BinaryReader Reader) + { + Magic = new string(Reader.ReadChars(4)); + HeaderSize = Reader.ReadUInt16(); + Endianness = Reader.ReadUInt16(); + FileSize = Reader.ReadUInt32(); + DataOffset = Reader.ReadInt32(); + Version = Reader.ReadUInt16(); + Reserved = Reader.ReadUInt16(); + } + + public void Write(BinaryWriter bw) + { + foreach (var c in Magic) + bw.Write((byte)c); + bw.Write(HeaderSize); + bw.Write(FileSize); + bw.Write(FileSize); + bw.Write(DataOffset); + bw.Write(Version); + bw.Write(Reserved); + } + } +} diff --git a/pkNX.Containers/SARC/SFAT.cs b/pkNX.Containers/SARC/SFAT.cs new file mode 100644 index 00000000..f538ea59 --- /dev/null +++ b/pkNX.Containers/SARC/SFAT.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace pkNX.Containers +{ + /// + /// File Access Table + /// + public class SFAT + { + public const string Identifier = nameof(SFAT); + + /// + /// The required matches the first 4 bytes of the file data. + /// + public bool SigMatches => Magic == Identifier; + + private readonly string Magic = Identifier; + private readonly ushort HeaderSize = 0xC; + private readonly ushort EntryCount; + private readonly uint HashMult = 0x65; + public readonly List Entries; + + public SFATEntry this[int index] + { + get => Entries[index]; + set => Entries[index] = value; + } + + public SFAT(string baseFolder, IReadOnlyList files) + { + EntryCount = (ushort)files.Count; + Entries = new List(EntryCount); + for (int i = 0; i < EntryCount; i++) + { + Entries[i] = new SFATEntry {File = files[i]}; + + var fn = files[i].Remove(baseFolder.Length); + Entries[i].SetFileName(fn, HashMult); + } + } + + public SFAT(BinaryReader br, int DataOffset) + { + Magic = new string(br.ReadChars(4)); + if (!SigMatches) + throw new FormatException(nameof(SFAT)); + + HeaderSize = br.ReadUInt16(); + EntryCount = br.ReadUInt16(); + HashMult = br.ReadUInt32(); + Entries = new List(EntryCount); + + for (int i = 0; i < EntryCount; i++) + Entries.Add(new SFATEntry(br, DataOffset)); + } + + public string GetFileName(int index, Stream parent, int StringOffset) => this[index].GetFileName(parent, StringOffset); + public void SetFileName(int index, string value) => this[index].SetFileName(value, HashMult); + + public void Write(BinaryWriter bw) + { + foreach (var c in Magic) + bw.Write((byte)c); + bw.Write(HeaderSize); + bw.Write(EntryCount); + bw.Write(HashMult); + WriteEntries(bw); + } + + public void WriteEntries(BinaryWriter bw) + { + foreach (var entry in Entries) + entry.Write(bw); + } + } +} \ No newline at end of file diff --git a/pkNX.Containers/SARC/SFATEntry.cs b/pkNX.Containers/SARC/SFATEntry.cs new file mode 100644 index 00000000..b4e40fad --- /dev/null +++ b/pkNX.Containers/SARC/SFATEntry.cs @@ -0,0 +1,81 @@ +using System.IO; +using System.Text; + +namespace pkNX.Containers +{ + /// + /// File Access Table () Entry + /// + public class SFATEntry : LargeContainerEntry + { + public uint FileNameHash; + public int FileNameOffset; + + public string FileName { get; private set; } + + private static uint GetHash(string name, int length, uint multiplier) + { + uint result = 0; + for (int i = 0; i < length; i++) + result = name[i] + (result * multiplier); + return result; + } + + public override int Length + { + get => End - Start; + set { } + } + + public SFATEntry() { } + + public SFATEntry(BinaryReader br, int DataOffset) + { + FileNameHash = br.ReadUInt32(); + FileNameOffset = br.ReadInt32(); + Start = br.ReadInt32(); + End = br.ReadInt32(); + ParentDataPosition = DataOffset; + } + + public void Write(BinaryWriter bw) + { + bw.Write(FileNameHash); + bw.Write(FileNameOffset); + bw.Write(Start); + bw.Write(End); + } + + public string GetFileName(Stream parent, int StringOffset) + { + if (FileName != null) + return FileName; + + var ofs = ((FileNameOffset & 0x00FFFFFF) * 4) + StringOffset; + parent.Seek(ofs, SeekOrigin.Begin); + var sb = new StringBuilder(); + + for (char c = (char)parent.ReadByte(); c != 0; c = (char)parent.ReadByte()) + sb.Append(c); + + FileName = sb.Replace('/', Path.DirectorySeparatorChar).ToString(); + return FileName; + } + + public void WriteFileName(Stream parent, int StringOffset) + { + FileNameOffset = (int)(parent.Position - StringOffset) / 4; + + var str = FileName.Replace(Path.DirectorySeparatorChar, '/'); + foreach (var b in str) + parent.WriteByte((byte)b); + parent.WriteByte(0); // \0 + } + + public void SetFileName(string value, uint hashMult) + { + FileName = value; + FileNameHash = GetHash(value, value.Length, hashMult); + } + } +} \ No newline at end of file diff --git a/pkNX.Containers/SARC/SFNT.cs b/pkNX.Containers/SARC/SFNT.cs new file mode 100644 index 00000000..a956b49d --- /dev/null +++ b/pkNX.Containers/SARC/SFNT.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; + +namespace pkNX.Containers +{ + /// + /// File Name Table + /// + public class SFNT + { + public const string Identifier = nameof(SFNT); + + /// + /// The required matches the first 4 bytes of the file data. + /// + public bool SigMatches => Magic == Identifier; + + public string Magic = Identifier; + public ushort HeaderSize; + public ushort Reserved; + public uint StringOffset; + + public SFNT() { } + + public SFNT(BinaryReader br) + { + Magic = new string(br.ReadChars(4)); + if (!SigMatches) + throw new FormatException(nameof(SFNT)); + + HeaderSize = br.ReadUInt16(); + Reserved = br.ReadUInt16(); + StringOffset = (uint)br.BaseStream.Position; + } + + public void Write(BinaryWriter bw) + { + foreach (var c in Magic) + bw.Write((byte)c); + bw.Write(HeaderSize); + bw.Write(Reserved); + StringOffset = (uint)bw.BaseStream.Position; + } + } +} \ No newline at end of file diff --git a/pkNX.Containers/SingleFileContainer.cs b/pkNX.Containers/SingleFileContainer.cs new file mode 100644 index 00000000..5683059c --- /dev/null +++ b/pkNX.Containers/SingleFileContainer.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace pkNX.Containers +{ + public class SingleFileContainer : IFileContainer + { + public string FilePath { get; set; } + public bool Modified { get; set; } + public int Count => 1; + + public byte[] Data; + public SingleFileContainer(byte[] data) => Data = data; + public SingleFileContainer(BinaryReader br) => Data = br.ReadBytes((int) br.BaseStream.Length); + + public SingleFileContainer(string path) + { + FilePath = path; + Data = File.ReadAllBytes(path); + } + + public byte[] this[int index] { get => Data; set => Data = value; } + public Task GetFiles() => new Task(() => new[] {Data}); + public Task GetFile(int file, int subFile = 0) => new Task(() => Data); + public Task SetFile(int file, byte[] value, int subFile = 0) => new Task(() => Data = value); + public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new Task(() => Dump(path, handler)); + public void Dump(string path, ContainerHandler handler) => File.WriteAllBytes(path ?? FilePath, Data); + } +} diff --git a/pkNX.Containers/pkNX.Containers.csproj b/pkNX.Containers/pkNX.Containers.csproj new file mode 100644 index 00000000..2ce8de8a --- /dev/null +++ b/pkNX.Containers/pkNX.Containers.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0;net46 + Packing & Unpacking + + + + + + + diff --git a/pkNX.Game/Editors/EditorBase.cs b/pkNX.Game/Editors/EditorBase.cs new file mode 100644 index 00000000..45f1d7dc --- /dev/null +++ b/pkNX.Game/Editors/EditorBase.cs @@ -0,0 +1,7 @@ +namespace pkNX.Game +{ + public class EditorBase + { + + } +} diff --git a/pkNX.Game/Editors/EditorFactory.cs b/pkNX.Game/Editors/EditorFactory.cs new file mode 100644 index 00000000..b1ee68e4 --- /dev/null +++ b/pkNX.Game/Editors/EditorFactory.cs @@ -0,0 +1,6 @@ +namespace pkNX.Game +{ + class EditorFactory + { + } +} diff --git a/pkNX.Game/File/GameFile.cs b/pkNX.Game/File/GameFile.cs new file mode 100644 index 00000000..794808c1 --- /dev/null +++ b/pkNX.Game/File/GameFile.cs @@ -0,0 +1,146 @@ +namespace pkNX.Game +{ + /// + /// Simple descriptor related to what purpose the game data serves. + /// + public enum GameFile + { + /// Contains the game text that is commonly re-used, not related to the storyline or general overworld content. + GameText, + + /// Contains the story text that is used to tell the story via overworld events and interactions. + StoryText, + + /// Localized Game Text for . + GameText0, + + /// Localized Game Text for . + GameText1, + + /// Localized Game Text for . + GameText2, + + /// Localized Game Text for . + GameText3, + + /// Localized Game Text for . + GameText4, + + /// Localized Game Text for . + GameText5, + + /// Localized Game Text for . + GameText6, + + /// Localized Game Text for . + GameText7, + + /// Localized Game Text for . + GameText8, + + /// Localized Game Text for . + GameText9, + + /// Localized Story Text for . + StoryText0, + + /// Localized Story Text for . + StoryText1, + + /// Localized Story Text for . + StoryText2, + + /// Localized Story Text for . + StoryText3, + + /// Localized Story Text for . + StoryText4, + + /// Localized Story Text for . + StoryText5, + + /// Localized Story Text for . + StoryText6, + + /// Localized Story Text for . + StoryText7, + + /// Localized Story Text for . + StoryText8, + + /// Localized Story Text for . + StoryText9, + + /// Overworld grass/etc encounterable species data. + Encounters, + + /// Trainer Data related to Trainers of a shared type. + TrainerClass, + + /// Trainer Data for individual Trainers that can be battled. + TrainerData, + + /// Trainer PKM template data for regular battles. + TrainerPoke, + + /// Move data that defines the properties of in-game moves. + MoveStats, + + /// Egg Moves a species can learn when bred. + EggMoves, + + /// Moves a species can learn via level up. + Learnsets, + + /// Evolutions a species can have under specified conditions. + Evolutions, + + /// Mega Evolutions a species can have under specified conditions. + MegaEvolutions, + + /// In-game stats a species can have. + PersonalStats, + + /// Properties of in-game posessible items. + ItemStats, + + /// Static (fixed position/condition) encounter table. + EncounterStatic, + + /// Post-game roulette trainer data with normal difficulty. + FacilityTrainerNormal, + + /// Post-game roulette trainer data with heightened difficulty. + FacilityTrainerSuper, + + /// Post-game roulette PKM template data for normal difficulty trainers. + FacilityPokeNormal, + + /// Post-game roulette PKM template data for heightened difficulty trainers. + FacilityPokeSuper, + + /// Title Screen staging data. + TitleScreen, + + /// Box Interface wallpapers. + Wallpaper, + + /// Walk/Collision data for individual Maps. + MapMatrix, + + /// Zone assembling information to build large maps from individual small zones. + MapGameRegion, + + /// Area settings and permissives related to in-game areas the player travels to. + ZoneData, + + /// Post-battle items that can be picked up. + Pickup, + + /// Map data for individual zones. + WorldData, + + /// UI Sprites for pretty in-game move descriptors. + MoveSprites, + } +} \ No newline at end of file diff --git a/pkNX.Game/File/GameFileMapping.cs b/pkNX.Game/File/GameFileMapping.cs new file mode 100644 index 00000000..b76bf3bd --- /dev/null +++ b/pkNX.Game/File/GameFileMapping.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using pkNX.Containers; +using pkNX.Structures; + +namespace pkNX.Game +{ + /// + /// Handles file retrieval and lifetime management for a 's data. + /// + public class GameFileMapping + { + private readonly Dictionary Cache = new Dictionary(); + private readonly IReadOnlyCollection FileMap; + + public readonly ContainerHandler ProgressTracker = new ContainerHandler(); + public readonly CancellationTokenSource TokenSource = new CancellationTokenSource(); + + private readonly GameLocation ROM; + public GameFileMapping(GameLocation rom) => FileMap = GetMapping((ROM = rom).Game); + + internal IFileContainer GetFile(GameFile file, int language) + { + if (file == GameFile.GameText || file == GameFile.StoryText) + file += language; // shift to localized language + + if (Cache.TryGetValue(file, out var container)) + return container; + + var info = FileMap.FirstOrDefault(f => f.File == file); + if (info == null) + throw new ArgumentException($"Unknown {nameof(GameFile)} provided.", file.ToString()); + + var basePath = info.Parent == ContainerParent.ExeFS ? ROM.ExeFS : ROM.RomFS; + container = info.Get(basePath); + Cache.Add(file, container); + return container; + } + + internal void SaveAll() + { + foreach (var container in Cache) + { + var c = container.Value; + c.SaveAs(c.FilePath, ProgressTracker, TokenSource.Token); + Cache.Remove(container.Key); + } + } + + public static IReadOnlyCollection GetMapping(GameVersion game) + { + switch (game) + { + case GameVersion.SN: return SN; + case GameVersion.MN: return MN; + case GameVersion.US: return US; + case GameVersion.UM: return UM; + case GameVersion.XY: return XY; + case GameVersion.GG: return GG; + case GameVersion.ORASDEMO: + case GameVersion.ORAS: return AO; + case GameVersion.SMDEMO: return SMDEMO; + } + return null; + } + + #region Games + private static readonly GameFileReference[] XY = + { + new GameFileReference(005, GameFile.MoveSprites), + new GameFileReference(012, GameFile.Encounters), + new GameFileReference(038, GameFile.TrainerData), + new GameFileReference(039, GameFile.TrainerClass), + new GameFileReference(040, GameFile.TrainerPoke), + new GameFileReference(041, GameFile.MapGameRegion), + new GameFileReference(042, GameFile.MapMatrix), + + new GameFileReference(072, GameFile.GameText0), + new GameFileReference(073, GameFile.GameText1), + new GameFileReference(074, GameFile.GameText2), + new GameFileReference(075, GameFile.GameText3), + new GameFileReference(076, GameFile.GameText4), + new GameFileReference(077, GameFile.GameText5), + new GameFileReference(078, GameFile.GameText6), + new GameFileReference(079, GameFile.GameText7), + new GameFileReference(080, GameFile.StoryText0), + new GameFileReference(081, GameFile.StoryText1), + new GameFileReference(082, GameFile.StoryText2), + new GameFileReference(083, GameFile.StoryText3), + new GameFileReference(084, GameFile.StoryText4), + new GameFileReference(085, GameFile.StoryText5), + new GameFileReference(086, GameFile.StoryText6), + new GameFileReference(087, GameFile.StoryText7), + + new GameFileReference(104, GameFile.Wallpaper), + new GameFileReference(165, GameFile.TitleScreen), + new GameFileReference(203, GameFile.FacilityPokeNormal), + new GameFileReference(204, GameFile.FacilityTrainerNormal), + new GameFileReference(205, GameFile.FacilityPokeSuper), + new GameFileReference(206, GameFile.FacilityTrainerSuper), + new GameFileReference(212, GameFile.MoveStats), + new GameFileReference(213, GameFile.EggMoves), + new GameFileReference(214, GameFile.Learnsets), + new GameFileReference(215, GameFile.Evolutions), + new GameFileReference(216, GameFile.MegaEvolutions), + new GameFileReference(218, GameFile.PersonalStats), + new GameFileReference(220, GameFile.ItemStats), + }; + + private static readonly GameFileReference[] AO = + { + new GameFileReference(013, GameFile.Encounters), + new GameFileReference(036, GameFile.TrainerData), + new GameFileReference(037, GameFile.TrainerClass), + new GameFileReference(038, GameFile.TrainerPoke), + new GameFileReference(039, GameFile.MapGameRegion), + new GameFileReference(040, GameFile.MapMatrix), + + new GameFileReference(071, GameFile.GameText0), + new GameFileReference(072, GameFile.GameText1), + new GameFileReference(073, GameFile.GameText2), + new GameFileReference(074, GameFile.GameText3), + new GameFileReference(075, GameFile.GameText4), + new GameFileReference(076, GameFile.GameText5), + new GameFileReference(077, GameFile.GameText6), + new GameFileReference(078, GameFile.GameText7), + new GameFileReference(079, GameFile.StoryText0), + new GameFileReference(080, GameFile.StoryText1), + new GameFileReference(081, GameFile.StoryText2), + new GameFileReference(082, GameFile.StoryText3), + new GameFileReference(083, GameFile.StoryText4), + new GameFileReference(084, GameFile.StoryText5), + new GameFileReference(085, GameFile.StoryText6), + new GameFileReference(086, GameFile.StoryText7), + + new GameFileReference(103, GameFile.Wallpaper), + new GameFileReference(152, GameFile.TitleScreen), + new GameFileReference(182, GameFile.FacilityPokeNormal), + new GameFileReference(183, GameFile.FacilityTrainerNormal), + new GameFileReference(184, GameFile.FacilityPokeSuper), + new GameFileReference(185, GameFile.FacilityTrainerSuper), + new GameFileReference(189, GameFile.MoveStats), + new GameFileReference(190, GameFile.EggMoves), + new GameFileReference(191, GameFile.Learnsets), + new GameFileReference(192, GameFile.Evolutions), + new GameFileReference(193, GameFile.MegaEvolutions), + new GameFileReference(195, GameFile.PersonalStats), + new GameFileReference(197, GameFile.ItemStats), + }; + #endregion + + #region Gen7 + private static readonly GameFileReference[] SMDEMO = + { + new GameFileReference(011, GameFile.MoveStats), + new GameFileReference(012, GameFile.EggMoves), + new GameFileReference(013, GameFile.Learnsets), + new GameFileReference(014, GameFile.Evolutions), + new GameFileReference(015, GameFile.MegaEvolutions), + new GameFileReference(017, GameFile.PersonalStats), + new GameFileReference(019, GameFile.ItemStats), + + new GameFileReference(030, GameFile.GameText0), + new GameFileReference(031, GameFile.GameText1), + new GameFileReference(032, GameFile.GameText2), + new GameFileReference(033, GameFile.GameText3), + new GameFileReference(034, GameFile.GameText4), + new GameFileReference(035, GameFile.GameText5), + new GameFileReference(036, GameFile.GameText6), + new GameFileReference(037, GameFile.GameText7), + new GameFileReference(038, GameFile.GameText8), + new GameFileReference(039, GameFile.GameText9), + new GameFileReference(040, GameFile.StoryText0), + new GameFileReference(041, GameFile.StoryText1), + new GameFileReference(042, GameFile.StoryText2), + new GameFileReference(043, GameFile.StoryText3), + new GameFileReference(044, GameFile.StoryText4), + new GameFileReference(045, GameFile.StoryText5), + new GameFileReference(046, GameFile.StoryText6), + new GameFileReference(047, GameFile.StoryText7), + new GameFileReference(048, GameFile.StoryText8), + new GameFileReference(049, GameFile.StoryText9), + + new GameFileReference(076, GameFile.ZoneData), + new GameFileReference(091, GameFile.WorldData), + + new GameFileReference(101, GameFile.TrainerClass), + new GameFileReference(102, GameFile.TrainerData), + new GameFileReference(103, GameFile.TrainerPoke), + }; + + private static readonly GameFileReference[] SM = + { + new GameFileReference(011, GameFile.MoveStats), + new GameFileReference(012, GameFile.EggMoves), + new GameFileReference(013, GameFile.Learnsets), + new GameFileReference(014, GameFile.Evolutions), + new GameFileReference(015, GameFile.MegaEvolutions), + new GameFileReference(017, GameFile.PersonalStats), + new GameFileReference(019, GameFile.ItemStats), + + new GameFileReference(030, GameFile.GameText0), + new GameFileReference(031, GameFile.GameText1), + new GameFileReference(032, GameFile.GameText2), + new GameFileReference(033, GameFile.GameText3), + new GameFileReference(034, GameFile.GameText4), + new GameFileReference(035, GameFile.GameText5), + new GameFileReference(036, GameFile.GameText6), + new GameFileReference(037, GameFile.GameText7), + new GameFileReference(038, GameFile.GameText8), + new GameFileReference(039, GameFile.GameText9), + new GameFileReference(040, GameFile.StoryText0), + new GameFileReference(041, GameFile.StoryText1), + new GameFileReference(042, GameFile.StoryText2), + new GameFileReference(043, GameFile.StoryText3), + new GameFileReference(044, GameFile.StoryText4), + new GameFileReference(045, GameFile.StoryText5), + new GameFileReference(046, GameFile.StoryText6), + new GameFileReference(047, GameFile.StoryText7), + new GameFileReference(048, GameFile.StoryText8), + new GameFileReference(049, GameFile.StoryText9), + + new GameFileReference(077, GameFile.ZoneData), + new GameFileReference(091, GameFile.WorldData), + + new GameFileReference(104, GameFile.TrainerClass), + new GameFileReference(105, GameFile.TrainerData), + new GameFileReference(106, GameFile.TrainerPoke), + + new GameFileReference(155, GameFile.EncounterStatic), + + new GameFileReference(267, GameFile.Pickup), + + new GameFileReference(277, GameFile.FacilityPokeNormal), + new GameFileReference(278, GameFile.FacilityTrainerNormal), + new GameFileReference(279, GameFile.FacilityPokeSuper), + new GameFileReference(280, GameFile.FacilityTrainerSuper), + }; + + /// + /// Ultra Sun & Ultra Moon + /// + private static readonly GameFileReference[] UU = + { + new GameFileReference(011, GameFile.MoveStats), + new GameFileReference(012, GameFile.EggMoves), + new GameFileReference(013, GameFile.Learnsets), + new GameFileReference(014, GameFile.Evolutions), + new GameFileReference(015, GameFile.MegaEvolutions), + new GameFileReference(017, GameFile.PersonalStats), + new GameFileReference(019, GameFile.ItemStats), + + new GameFileReference(030, GameFile.GameText0), + new GameFileReference(031, GameFile.GameText1), + new GameFileReference(032, GameFile.GameText2), + new GameFileReference(033, GameFile.GameText3), + new GameFileReference(034, GameFile.GameText4), + new GameFileReference(035, GameFile.GameText5), + new GameFileReference(036, GameFile.GameText6), + new GameFileReference(037, GameFile.GameText7), + new GameFileReference(038, GameFile.GameText8), + new GameFileReference(039, GameFile.GameText9), + new GameFileReference(040, GameFile.StoryText0), + new GameFileReference(041, GameFile.StoryText1), + new GameFileReference(042, GameFile.StoryText2), + new GameFileReference(043, GameFile.StoryText3), + new GameFileReference(044, GameFile.StoryText4), + new GameFileReference(045, GameFile.StoryText5), + new GameFileReference(046, GameFile.StoryText6), + new GameFileReference(047, GameFile.StoryText7), + new GameFileReference(048, GameFile.StoryText8), + new GameFileReference(049, GameFile.StoryText9), + + new GameFileReference(077, GameFile.ZoneData), + new GameFileReference(091, GameFile.WorldData), + + new GameFileReference(105, GameFile.TrainerClass), + new GameFileReference(106, GameFile.TrainerData), + new GameFileReference(107, GameFile.TrainerPoke), + + new GameFileReference(159, GameFile.EncounterStatic), + + new GameFileReference(271, GameFile.Pickup), + + new GameFileReference(281, GameFile.FacilityPokeNormal), + new GameFileReference(282, GameFile.FacilityTrainerNormal), + new GameFileReference(283, GameFile.FacilityPokeSuper), + new GameFileReference(284, GameFile.FacilityTrainerSuper), + }; + + /// + /// Let's Go Pikachu & Let's Go Eevee + /// + private static readonly GameFileReference[] GG = + { + + }; + #endregion + + #region Split Versions + private static readonly GameFileReference[] SN = SM.Concat(new[] {new GameFileReference(082, GameFile.Encounters)}).ToArray(); + private static readonly GameFileReference[] MN = SM.Concat(new[] {new GameFileReference(083, GameFile.Encounters)}).ToArray(); + private static readonly GameFileReference[] US = UU.Concat(new[] {new GameFileReference(082, GameFile.Encounters)}).ToArray(); + private static readonly GameFileReference[] UM = UU.Concat(new[] {new GameFileReference(083, GameFile.Encounters)}).ToArray(); + #endregion + } +} diff --git a/pkNX.Game/File/GameFileReference.cs b/pkNX.Game/File/GameFileReference.cs new file mode 100644 index 00000000..71340a61 --- /dev/null +++ b/pkNX.Game/File/GameFileReference.cs @@ -0,0 +1,64 @@ +using System.IO; +using pkNX.Containers; + +namespace pkNX.Game +{ + /// + /// File reference pointing to the location of the data. + /// + public class GameFileReference + { + /// + /// Type of data the file contains. + /// + public GameFile File { get; } + + /// + /// Location of the file in the user's environment. + /// + public string RelativePath { get; } + + /// + /// Type of container the data is within. + /// + public ContainerType Type { get; } + + /// + /// Toggle to indicate that the data is localized and should be shifted. + /// + public bool LanguageVariant { get; } + + /// + /// Indicates the parent of the data. + /// + public ContainerParent Parent { get; set; } + + internal GameFileReference(int FileNumber, GameFile ident, ContainerType t = ContainerType.GARC) + { + File = ident; + Type = t; + + int A = FileNumber / 100 % 10; + int B = FileNumber / 10 % 10; + int C = FileNumber / 1 % 10; + RelativePath = Path.Combine("a", A.ToString(), B.ToString(), C.ToString()); + } + + internal GameFileReference(string relPath, ContainerType t, GameFile ident, bool variant = false) + { + File = ident; + Type = t; + LanguageVariant = variant; + + RelativePath = relPath; + } + + public IFileContainer Get(string basePath) + { + var path = Path.Combine(basePath, RelativePath); + var container = Container.GetContainer(path, Type); + container.FilePath = path; + return container; + } + } +} \ No newline at end of file diff --git a/pkNX.Game/GameData.cs b/pkNX.Game/GameData.cs new file mode 100644 index 00000000..4744210e --- /dev/null +++ b/pkNX.Game/GameData.cs @@ -0,0 +1,18 @@ +using pkNX.Structures; + +namespace pkNX.Game +{ + /// + /// Cached data reusable by multiple editors. + /// + public class GameData + { + public PersonalTable PersonalData { get; internal set; } + public MegaEvolutionTable MegaEvolutionData { get; internal set; } + + public string[][] GameText { get; internal set; } + public Move[] MoveData { get; internal set; } + public EvolutionSet[] EvolutionData { get; internal set; } + public Learnset[] LevelUpData { get; internal set; } + } +} diff --git a/pkNX.Game/GameLanguage.cs b/pkNX.Game/GameLanguage.cs new file mode 100644 index 00000000..5cd955be --- /dev/null +++ b/pkNX.Game/GameLanguage.cs @@ -0,0 +1,58 @@ +namespace pkNX.Game +{ + /// + /// Game Language IDs to index consecutive localized files + /// + public enum GameLanguage + { + /// + /// Japanese (katakana) + /// + カタカナ = 0, + + /// + /// Japanese (hiragana) + /// + 漢字 = 1, + + /// + /// English + /// + English = 2, + + /// + /// French + /// + Français = 3, + + /// + /// Italian + /// + Italiano = 4, + + /// + /// German + /// + Deutsch = 5, + + /// + /// Spanish + /// + Español = 6, + + /// + /// Korean + /// + 한국 = 7, + + /// + /// Chinese (Simplified) + /// + 汉字简化方案 = 8, + + /// + /// Chinese (Traditional) + /// + 漢字簡化方案 = 9, + } +} diff --git a/pkNX.Game/GameLocation.cs b/pkNX.Game/GameLocation.cs new file mode 100644 index 00000000..f606c41d --- /dev/null +++ b/pkNX.Game/GameLocation.cs @@ -0,0 +1,96 @@ +using System; +using System.IO; +using pkNX.Structures; + +namespace pkNX.Game +{ + /// + /// Environment Data where the Game's Data is located on the user's machine. + /// + public sealed class GameLocation + { + /// + /// Location of the RomFS + /// + public string RomFS { get; } + + /// + /// Location of the ExeFS. + /// + public string ExeFS { get; } + + /// + /// Game version the files belong to. + /// + public GameVersion Game { get; } + + private GameLocation(string romfs, string exefs, GameVersion game) + { + Game = game; + RomFS = romfs; + ExeFS = exefs; + } + + /// + /// Determines the of the input directory and detects the location of files for editing. + /// + /// Directory the game data is in + /// New object with references to file paths. + public static GameLocation GetGame(string dir) + { + if (dir == null || !Directory.Exists(dir)) + return null; + + var dirs = Directory.GetDirectories(dir); + var romfs = Array.Find(dirs, z => Path.GetFileName(z).StartsWith("rom")); + var exefs = Array.Find(dirs, z => Path.GetFileName(z).StartsWith("exe")); + + if (romfs == null || exefs == null) + return null; + + var files = Directory.GetFiles(romfs, "*", SearchOption.AllDirectories); + var game = GetGameFromCount(files.Length, romfs); + if (game == GameVersion.Invalid) + return null; + return new GameLocation(romfs, exefs, game); + } + + private const int FILECOUNT_XY = 271; + private const int FILECOUNT_ORASDEMO = 301; + private const int FILECOUNT_ORAS = 299; + private const int FILECOUNT_SMDEMO = 239; + private const int FILECOUNT_SM = 311; + private const int FILECOUNT_USUM = 333; + private const int FILECOUNT_GG = 27818; + + private static GameVersion GetGameFromCount(int fileCount, string romfs) + { + switch (fileCount) + { + case FILECOUNT_XY: return GameVersion.XY; + case FILECOUNT_ORASDEMO: return GameVersion.ORASDEMO; + case FILECOUNT_ORAS: return GameVersion.ORAS; + case FILECOUNT_SMDEMO: return GameVersion.SMDEMO; + case FILECOUNT_SM: + { + var encdata = Path.Combine(romfs, "a", "0", "8", "2"); + if (File.Exists(encdata) && new FileInfo(encdata).Length != 0) + return GameVersion.SN; + return GameVersion.MN; + } + case FILECOUNT_USUM: + { + var encdata = Path.Combine(romfs, "a", "0", "8", "2"); + if (File.Exists(encdata) && new FileInfo(encdata).Length != 0) + return GameVersion.US; + return GameVersion.UM; + } + case FILECOUNT_GG: + return GameVersion.GG; + + default: + return GameVersion.Invalid; + } + } + } +} diff --git a/pkNX.Game/GameManager.cs b/pkNX.Game/GameManager.cs new file mode 100644 index 00000000..964a587f --- /dev/null +++ b/pkNX.Game/GameManager.cs @@ -0,0 +1,91 @@ +using pkNX.Containers; +using pkNX.Structures; + +namespace pkNX.Game +{ + /// + /// Manages fetching of game data. + /// + public sealed class GameManager + { + private readonly GameLocation ROM; + private readonly TextManager Text; // GameText + private readonly GameFileMapping FileMap; + + /// + /// Language to use when fetching string & graphic assets. + /// + public int Language { get; set; } + + /// + /// Current the data represents. + /// + public GameVersion Game => ROM.Game; + + /// + /// Generally useful game data that can be used by multiple editors. + /// + public GameData Data { get; private set; } + + /// + /// Initializes a new for the input with initial . + /// + /// + /// + public GameManager(GameLocation rom, int language) + { + ROM = rom; + Language = language; + FileMap = new GameFileMapping(rom); + Text = new TextManager(Game); + + Initialize(); + } + + /// + /// Fetches a from the Game data. + /// + /// File type to fetch + /// Container that contains the game data requested. + /// Sugar for the other method. + public IFileContainer this[GameFile file] => GetFile(file); + + /// + /// Fetches a from the Game data. + /// + /// File type to fetch + /// Container that contains the game data requested. + public IFileContainer GetFile(GameFile file) => FileMap.GetFile(file, Language); + + /// + /// Fetches strings for the input . + /// + /// Text file to fetch + /// Array of strings from the requested text file. + public string[] GetStrings(TextName text) + { + var arc = this[GameFile.GameText]; + var lines = Text.GetStrings(text, arc); + return lines; + } + + /// + /// Saves all open files and finalizes the ROM data. + /// + /// Skip re-initialization of game data. + public void SaveAll(bool closing) + { + FileMap.SaveAll(); + if (!closing) + Initialize(); + } + + private void Initialize() + { + Data = new GameData + { + MoveData = this[GameFile.MoveStats].GetFiles().GetArray(z => (Move)new Move6(z)), + }; + } + } +} diff --git a/pkNX.Game/Text/TextManager.cs b/pkNX.Game/Text/TextManager.cs new file mode 100644 index 00000000..1ca5c560 --- /dev/null +++ b/pkNX.Game/Text/TextManager.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using pkNX.Containers; +using pkNX.Structures; + +namespace pkNX.Game +{ + public class TextManager + { + private readonly TextConfig Config; + private readonly IReadOnlyCollection References; + + private readonly Dictionary Cache = new Dictionary(); + + public TextManager(GameVersion game, TextConfig config = null) + { + References = TextMapping.GetMapping(game); + Config = config ?? new TextConfig(game); + } + + internal string[] GetStrings(byte[] data, bool remap = false) + { + var txt = new TextFile(data, Config, remap); + return txt.Lines; + } + + internal string[] GetStrings(TextName file, IFileContainer textFile, bool remap = false) + { + if (Cache.TryGetValue(file, out var container)) + return container; + + var info = References.FirstOrDefault(f => f.Name == file); + if (info == null) + throw new ArgumentException($"Unknown {nameof(TextName)} provided.", file.ToString()); + + var data = textFile[info.Index]; + var lines = GetStrings(data, remap); + Cache.Add(file, lines); + return lines; + } + } +} diff --git a/pkNX.Game/Text/TextMapping.cs b/pkNX.Game/Text/TextMapping.cs new file mode 100644 index 00000000..e22c9ee7 --- /dev/null +++ b/pkNX.Game/Text/TextMapping.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using pkNX.Structures; + +namespace pkNX.Game +{ + public static class TextMapping + { + public static IReadOnlyCollection GetMapping(GameVersion game) + { + switch (game) + { + case GameVersion.XY: return XY; + case GameVersion.ORASDEMO: + case GameVersion.ORAS: return AO; + case GameVersion.SMDEMO: return SMDEMO; + case GameVersion.SN: + case GameVersion.MN: return SM; + case GameVersion.US: + case GameVersion.UM: return USUM; + case GameVersion.GG: return GG; + } + return null; + } + + private static readonly TextReference[] XY = + { + new TextReference(005, TextName.Forms), + new TextReference(013, TextName.MoveNames), + new TextReference(015, TextName.MoveFlavor), + new TextReference(017, TextName.Types), + new TextReference(020, TextName.TrainerClasses), + new TextReference(021, TextName.TrainerNames), + new TextReference(022, TextName.TrainerText), + new TextReference(034, TextName.AbilityNames), + new TextReference(047, TextName.Natures), + new TextReference(072, TextName.metlist_000000), + new TextReference(080, TextName.SpeciesNames), + new TextReference(096, TextName.ItemNames), + new TextReference(099, TextName.ItemFlavor), + new TextReference(130, TextName.MaisonTrainerNames), + new TextReference(131, TextName.SuperTrainerNames), + new TextReference(141, TextName.OPowerFlavor), + }; + + private static readonly TextReference[] AO = + { + new TextReference(005, TextName.Forms), + new TextReference(014, TextName.MoveNames), + new TextReference(016, TextName.MoveFlavor), + new TextReference(018, TextName.Types), + new TextReference(021, TextName.TrainerClasses), + new TextReference(022, TextName.TrainerNames), + new TextReference(023, TextName.TrainerText), + new TextReference(037, TextName.AbilityNames), + new TextReference(051, TextName.Natures), + new TextReference(090, TextName.metlist_000000), + new TextReference(098, TextName.SpeciesNames), + new TextReference(114, TextName.ItemNames), + new TextReference(117, TextName.ItemFlavor), + new TextReference(153, TextName.MaisonTrainerNames), + new TextReference(154, TextName.SuperTrainerNames), + new TextReference(165, TextName.OPowerFlavor), + }; + + private static readonly TextReference[] SMDEMO = + { + new TextReference(020, TextName.ItemFlavor), + new TextReference(021, TextName.ItemNames), + new TextReference(026, TextName.SpeciesNames), + new TextReference(030, TextName.metlist_000000), + new TextReference(044, TextName.Forms), + new TextReference(044, TextName.Natures), + new TextReference(046, TextName.AbilityNames), + new TextReference(049, TextName.TrainerText), + new TextReference(050, TextName.TrainerNames), + new TextReference(051, TextName.TrainerClasses), + new TextReference(052, TextName.Types), + new TextReference(054, TextName.MoveFlavor), + new TextReference(055, TextName.MoveNames), + }; + + private static readonly TextReference[] SM = + { + new TextReference(035, TextName.ItemFlavor), + new TextReference(036, TextName.ItemNames), + new TextReference(055, TextName.SpeciesNames), + new TextReference(067, TextName.metlist_000000), + new TextReference(086, TextName.BattleRoyalNames), + new TextReference(087, TextName.Natures), + new TextReference(096, TextName.AbilityNames), + new TextReference(099, TextName.BattleTreeNames), + new TextReference(104, TextName.TrainerText), + new TextReference(105, TextName.TrainerNames), + new TextReference(106, TextName.TrainerClasses), + new TextReference(107, TextName.Types), + new TextReference(112, TextName.MoveFlavor), + new TextReference(113, TextName.MoveNames), + new TextReference(114, TextName.Forms), + new TextReference(116, TextName.SpeciesClassifications), + new TextReference(119, TextName.PokedexEntry1), + new TextReference(120, TextName.PokedexEntry2) + }; + + private static readonly TextReference[] USUM = + { + new TextReference(039, TextName.ItemFlavor), + new TextReference(040, TextName.ItemNames), + new TextReference(060, TextName.SpeciesNames), + new TextReference(072, TextName.metlist_000000), + new TextReference(091, TextName.BattleRoyalNames), + new TextReference(092, TextName.Natures), + new TextReference(101, TextName.AbilityNames), + new TextReference(104, TextName.BattleTreeNames), + new TextReference(109, TextName.TrainerText), + new TextReference(110, TextName.TrainerNames), + new TextReference(111, TextName.TrainerClasses), + new TextReference(112, TextName.Types), + new TextReference(117, TextName.MoveFlavor), + new TextReference(118, TextName.MoveNames), + new TextReference(119, TextName.Forms), + new TextReference(121, TextName.SpeciesClassifications), + new TextReference(124, TextName.PokedexEntry1), + new TextReference(125, TextName.PokedexEntry2) + }; + + private static readonly TextReference[] GG = + { + + }; + } +} \ No newline at end of file diff --git a/pkNX.Game/Text/TextName.cs b/pkNX.Game/Text/TextName.cs new file mode 100644 index 00000000..7666eb1b --- /dev/null +++ b/pkNX.Game/Text/TextName.cs @@ -0,0 +1,31 @@ +namespace pkNX.Game +{ + public enum TextName + { + AbilityNames, + MoveNames, + MoveFlavor, + + ItemNames, + ItemFlavor, + + SpeciesNames, + Types, + Natures, + Forms, + + TrainerNames, + TrainerClasses, + TrainerText, + metlist_000000, + OPowerFlavor, + MaisonTrainerNames, + SuperTrainerNames, + BattleRoyalNames, + BattleTreeNames, + + SpeciesClassifications, + PokedexEntry1, + PokedexEntry2 + } +} \ No newline at end of file diff --git a/pkNX.Game/Text/TextReference.cs b/pkNX.Game/Text/TextReference.cs new file mode 100644 index 00000000..478de13a --- /dev/null +++ b/pkNX.Game/Text/TextReference.cs @@ -0,0 +1,15 @@ + +namespace pkNX.Game +{ + public class TextReference + { + public readonly int Index; + public readonly TextName Name; + + internal TextReference(int index, TextName name) + { + Index = index; + Name = name; + } + } +} diff --git a/pkNX.Game/pkNX.Game.csproj b/pkNX.Game/pkNX.Game.csproj new file mode 100644 index 00000000..abdef8f0 --- /dev/null +++ b/pkNX.Game/pkNX.Game.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0;net46 + Game Data Manager + + + + + + + + + diff --git a/pkNX.Randomization/Randomizers/EvolutionRandomizer.cs b/pkNX.Randomization/Randomizers/EvolutionRandomizer.cs new file mode 100644 index 00000000..8a13dfae --- /dev/null +++ b/pkNX.Randomization/Randomizers/EvolutionRandomizer.cs @@ -0,0 +1,97 @@ +using pkNX.Structures; + +namespace pkNX.Randomization +{ + /// + /// randomizer. + /// + public class EvolutionRandomizer : Randomizer + { + private readonly EvolutionSet[] Evolutions; + private readonly GameInfo Game; + public readonly SpeciesRandomizer Randomizer; + + public EvolutionRandomizer(GameInfo game, EvolutionSet[] evolutions, PersonalTable t) + { + Game = game; + Evolutions = evolutions; + Randomizer = new SpeciesRandomizer(Game, t); + } + + public override void Execute() + { + for (var i = 0; i < Evolutions.Length; i++) + { + var evo = Evolutions[i]; + Randomize(evo, i); + } + } + + public void ExecuteTrade() + { + for (var i = 0; i < Evolutions.Length; i++) + { + var evo = Evolutions[i]; + ReplaceTradeMethods(evo, i); + } + } + + private void Randomize(EvolutionSet evos, int species) + { + foreach (var evo in evos.PossibleEvolutions) + { + if (evo.Method != 0) + { + evo.Species = Randomizer.GetRandomSpecies(evo.Species, species); + } + } + } + + private void ReplaceTradeMethods(EvolutionSet evos, int species) + { + for (var i = 0; i < evos.PossibleEvolutions.Length; i++) + { + var evo = evos.PossibleEvolutions[i]; + ReplaceTradeMethod(evo, species, i); + } + } + + private void ReplaceTradeMethod(EvolutionMethod evo, int species, int evoIndex) + { + switch (evo.Method) + { + case EvolutionType.Trade when Game.Generation == 6: + evo.Method = EvolutionType.LevelUp; // trade -> level up + evo.Argument = 30; + return; + case EvolutionType.Trade when Game.Generation == 7: + evo.Method = EvolutionType.LevelUp; // trade -> level up + evo.Level = 30; + return; + case EvolutionType.TradeHeldItem: + evo.Method = EvolutionType.LevelUpHeldItemDay; + return; + case EvolutionType.TradeSpecies: + evo.Method = EvolutionType.LevelUpWithTeammate; + if (species == 588) + evo.Argument = 616; // Karrablast with Shelmet + if (species == 616) + evo.Argument = 588; // Shelmet with Karrablast + return; + + case EvolutionType.LevelUpVersion: + evo.Method = evoIndex == 0 ? EvolutionType.LevelUpECl5 : EvolutionType.LevelUpECgeq5; + evo.Argument = 0; // clear ver + return; + case EvolutionType.LevelUpVersionDay: + evo.Method = EvolutionType.LevelUpFriendshipMorning; + evo.Argument = 0; // clear ver + return; + case EvolutionType.LevelUpVersionNight: + evo.Method = EvolutionType.LevelUpFriendshipNight; + evo.Argument = 0; // clear ver + return; + } + } + } +} \ No newline at end of file diff --git a/pkNX.Randomization/Randomizers/FormRandomizer.cs b/pkNX.Randomization/Randomizers/FormRandomizer.cs new file mode 100644 index 00000000..c792d69c --- /dev/null +++ b/pkNX.Randomization/Randomizers/FormRandomizer.cs @@ -0,0 +1,47 @@ +using pkNX.Structures; +using System.Linq; + +namespace pkNX.Randomization +{ + public class FormRandomizer + { + private readonly PersonalTable Table; + + public FormRandomizer(PersonalTable t) + { + Table = t; + } + + public bool AllowMega { get; set; } = false; + public bool AllowAlolanForm { get; set; } = true; + + public int GetRandomForme(int species, PersonalInfo[] stats = null) + { + if (stats == null) + stats = Table.Table; + if (stats[species].FormeCount <= 1) + return 0; + + switch (species) + { + case 201: // Unown + case 586: // Sawsbuck + return 31; // Random + case 658 when !AllowMega: + return 0; + case 664: + case 665: + case 666: // Vivillon evo chain + return 30; // save file specific + case 774: // Minior + return Util.Random.Next(7); + } + + if (AllowAlolanForm && Legal.EvolveToAlolanForms.Contains(species)) + return Util.Random.Next(2); + if (!Legal.BattleExclusiveForms.Contains(species) || AllowMega) + return Util.Random.Next(stats[species].FormeCount); // Slot-Random + return 0; + } + } +} diff --git a/pkNX.Randomization/Randomizers/GenericRandomizer.cs b/pkNX.Randomization/Randomizers/GenericRandomizer.cs new file mode 100644 index 00000000..3e8e8480 --- /dev/null +++ b/pkNX.Randomization/Randomizers/GenericRandomizer.cs @@ -0,0 +1,34 @@ +namespace pkNX.Randomization +{ + /// Cyclical Shuffled Randomizer + /// + /// The shuffled list is iterated over, and reshuffled when exhausted. + /// The list does not repeat values until the list is exhausted. + /// + public class GenericRandomizer + { + public GenericRandomizer(int[] randomValues) + { + RandomValues = randomValues; + } + + private readonly int[] RandomValues; + private int ctr; + + public void Reset() + { + ctr = 0; + Util.Shuffle(RandomValues); + } + + public int Next() + { + if (ctr == 0) + Util.Shuffle(RandomValues); + + int value = RandomValues[ctr++]; + ctr %= RandomValues.Length; + return value; + } + } +} diff --git a/pkNX.Randomization/Randomizers/Personal/Consistency.cs b/pkNX.Randomization/Randomizers/Personal/Consistency.cs new file mode 100644 index 00000000..99b286ad --- /dev/null +++ b/pkNX.Randomization/Randomizers/Personal/Consistency.cs @@ -0,0 +1,29 @@ +namespace pkNX.Randomization +{ + public enum RandSetting + { + Unchanged, + Randomized, + } + + public enum Permissive + { + Yes, + No, + } + + public enum ModifyState + { + Shared, + One, + Two, + All, + } + + public enum CatchRate + { + Unchanged, + BSTScaled, + Random, + } +} diff --git a/pkNX.Randomization/Randomizers/Personal/PersonalRandSettings.cs b/pkNX.Randomization/Randomizers/Personal/PersonalRandSettings.cs new file mode 100644 index 00000000..015b1038 --- /dev/null +++ b/pkNX.Randomization/Randomizers/Personal/PersonalRandSettings.cs @@ -0,0 +1,206 @@ +using System.ComponentModel; +using pkNX.Structures; + +namespace pkNX.Randomization +{ + /// + /// Randomization settings when randomizing a . + /// + public class PersonalRandSettings : RandSettings + { + private const string Moves = nameof(Moves); + private const string Types = nameof(Types); + private const string Stats = nameof(Stats); + private const string Abilities = nameof(Abilities); + private const string Misc = nameof(Misc); + private const string Evolutions = nameof(Evolutions); + + /// Toggle to use Evolution data for inherited properties. + [Category("A1"), Description("Evolution Chain Species Randomization instead of Pure Random. Refer to the Evolutions settings when using this mode.")] + public bool ModifyByEvolutions { get; set; } = true; + + #region Moves + /// Permits randomizing the values. + [Category(Moves), Description("Enables randomizing the TM (Technical Machine) compatibility flags.")] + public bool ModifyLearnsetTM { get; set; } = true; + + /// Permits randomizing the values. + [Category(Moves), Description("Enables randomizing the HM (Hidden Machine) compatibility flags.")] + public bool ModifyLearnsetHM { get; set; } = true; + + /// Permits randomizing the values. + [Category(Moves), Description("Enables randomizing the move tutor compatibility flags.")] + public bool ModifyLearnsetMoveTutors { get; set; } = true; + + /// Permits randomizing the values. + [Category(Moves), Description("Enables randomizing the type tutor compatibility flags.")] + public bool ModifyLearnsetTypeTutors { get; set; } = false; + + /// Percent chance to learn a TMHM move (0-100). + /// Average Learnable TMs is 35.260. + [Category(Moves), Description("Percentage chance to learn a given TM move.")] + public decimal LearnTMPercent { get; set; } = 35; + + /// Percent chance to learn a type tutor move (0-100). + /// 136 special tutor moves learnable by species in Untouched ORAS. + [Category(Moves), Description("Percentage chance to learn a given special Type Tutor move.")] + public decimal LearnTypeTutorPercent { get; set; } = 2; + + /// Percent chance to learn a tutor move (0-100). + /// 10001 tutor moves learnable by 826 species in Untouched ORAS. + [Category(Moves), Description("Percentage chance to learn a given Move Tutor move.")] + public decimal LearnMoveTutorPercent { get; set; } = 30; + #endregion + + #region Types + /// Permits modification of . + [Category(Types), Description("Enables a PKM's Type to be modified.")] + public bool ModifyTypes { get; set; } = true; + + /// Option to modify the . + [Category(Types), Description("Option to modify the Types depending on the specified setting.")] + public ModifyState Type { get; set; } = ModifyState.All; + + /// Chance that both types are the same. + [Category(Types), Description("Chance that both types are the same.")] + public decimal SameTypeChance { get; set; } = 50; + #endregion + + #region Ability + /// Toggle to permit modification of . + [Category(Abilities), Description("Enables a PKM's Abilities to be modified.")] + public bool ModifyAbility { get; set; } = true; + + /// Permits Wonder Guard as a random ability. + [Category(Abilities), Description("Permits Wonder Guard as a random ability.")] + public Permissive WonderGuard { get; set; } = Permissive.No; + + /// Option to modify the . + [Category(Abilities), Description("Option to modify the Abilities depending on the specified setting.")] + public ModifyState Ability { get; set; } = ModifyState.All; + + /// Chance that both abilities are the same. + [Category(Abilities), Description("Chance that both abilities are the same.")] + public decimal SameAbilityChance { get; set; } = 100; + #endregion + + #region Stats + /// Permits modification of . + [Category(Stats), Description("Enables a PKM's base stats to be modified.")] + public bool ModifyStats { get; set; } = true; + + /// Amount a Base Stat is amplified as a low bound. + [Category(Stats), Description("Minimum Percentage bound a Base Stat is after randomizing. 100 corresponds to an unchanged minimum.")] + public int StatDeviationMin { get; set; } = 75; + + /// Amount a Base Stat is amplified as a high bound. + [Category(Stats), Description("Maximum Percentage bound a Base Stat is after randomizing. 100 corresponds to an unchanged maximum.")] + public int StatDeviationMax { get; set; } = 125; + + /// Toggle to permit shuffling of . + [Category(Stats), Description("Shuffles the PKM's base stats after any modifications have been made.")] + public bool ShuffleStats { get; set; } = true; + + /// Permits randomizing the stat. + [Category(Stats), Description("Permits randomizing the HP base stat.")] + public bool HP { get; set; } = true; + + /// Permits randomizing the stat. + [Category(Stats), Description("Permits randomizing the Attack base stat.")] + public bool ATK { get; set; } = true; + + /// Permits randomizing the stat. + [Category(Stats), Description("Permits randomizing the Defense base stat.")] + public bool DEF { get; set; } = true; + + /// Permits randomizing the stat. + [Category(Stats), Description("Permits randomizing the Special Attack base stat.")] + public bool SPA { get; set; } = true; + + /// Permits randomizing the stat. + [Category(Stats), Description("Permits randomizing the Special Defense base stat.")] + public bool SPD { get; set; } = true; + + /// Permits randomizing the stat. + [Category(Stats), Description("Permits randomizing the Speed base stat.")] + public bool SPE { get; set; } = true; + + /// + /// Flags to edit the stats when randomizing. + /// + public bool[] StatsToRandomize => new[] {HP, ATK, DEF, SPE, SPA, SPD}; + #endregion + + #region Misc + /// Option permitting modification of . + [Category(Misc), Description("Enables a PKM's catch rate to be modified. Can inversely scale off BST.")] + public CatchRate CatchRate { get; set; } = CatchRate.Unchanged; + + /// Permits modification of . + [Category(Misc), Description("Enables a PKM's base stats to be modified.")] + public bool ModifyHeldItems { get; set; } = true; + + /// Chance all held items are the same. + [Category(Misc), Description("Percentage chance that all Held Items are the same, resulting in a 100% chance of having the held item.")] + public decimal AlwaysHeldItemChance { get; set; } = 20; + + /// Permits modification of . + [Category(Misc), Description("Enables a PKM's egg groups to be modified.")] + public bool ModifyEgg { get; set; } = false; + + /// Chance both egg groups are the same. + [Category(Misc), Description("Percentage chance that both egg groups will be the same.")] + public decimal SameEggGroupChance { get; set; } = 50; + #endregion + + #region Evolutions + /// Toggles inheriting types from the pre-evolution that evolves into this species/form. + [Category(Evolutions), Description("Toggles inheriting types from the pre-evolution that evolves into this species/form.")] + public bool InheritType { get; set; } = true; + + /// Maximum amount of Types that can be different from the pre-evolution. + [Category(Evolutions), Description("Maximum amount of Types that can be different from the pre-evolutionn.")] + public ModifyState InheritTypeSetting { get; set; } = ModifyState.One; + + /// Percentage chance that only one type will be inherited, and a new random one will replace the other. + [Category(Evolutions), Description("Percentage chance that only one type will be inherited, and a new random one will replace the other.")] + public decimal InheritTypeOnlyOneChance { get; set; } = 65; + + /// Percentage chance that neither one type will be inherited, and new random ones will replace the others. + [Category(Evolutions), Description("Percentage chance that neither type will be inherited, and new random ones will replace the others.")] + public decimal InheritTypeNeitherChance { get; set; } = 30; + + /// Toggles chance that neither one type will be inherited, and new random ones will replace the others. + [Category(Evolutions), Description("Percentage chance that neither type will be inherited, and new random ones will replace the others.")] + public ModifyState InheritAbilitySetting { get; set; } = ModifyState.One; + + /// Toggles inheriting abilities from the pre-evolution that evolves into this species/form. + [Category(Evolutions), Description("Toggles inheriting abilities from the pre-evolution that evolves into this species/form.")] + public bool InheritAbility { get; set; } = true; + + /// Percentage chance that only one ability will be inherited, and a new random one will replace the other. + [Category(Evolutions), Description("Percentage chance that only one ability will be inherited, and a new random one will replace the other.")] + public decimal InheritAbilityOnlyOneChance { get; set; } = 45; + + /// Percentage chance that neither one ability will be inherited, and new random ones will replace the others. + [Category(Evolutions), Description("Percentage chance that neither ability will be inherited, and new random ones will replace the others.")] + public decimal InheritAbilityNeitherChance { get; set; } = 20; + + /// Inherit the held item values from the pre-evolution. + [Category(Evolutions), Description("Inherit the held item values from the pre-evolution.")] + public bool InheritHeldItem { get; set; } = true; + + /// Inherit the TM/HM compatibility from the pre-evolution. + [Category(Evolutions), Description("Inherit the TM/HM compatibility values from the pre-evolution.")] + public bool InheritChildTM { get; set; } = true; + + /// Inherit the Tutor compatibility from the pre-evolution. + [Category(Evolutions), Description("Inherit the Tutor compatibility values from the pre-evolution.")] + public bool InheritChildTutor { get; set; } = true; + + /// Inherit the Type Tutor values from the pre-evolution. + [Category(Evolutions), Description("Inherit the Type Tutor compatibility values from the pre-evolution.")] + public bool InheritChildSpecial { get; set; } = true; + #endregion + } +} \ No newline at end of file diff --git a/pkNX.Randomization/Randomizers/Personal/PersonalRandomizer.cs b/pkNX.Randomization/Randomizers/Personal/PersonalRandomizer.cs new file mode 100644 index 00000000..e68c1dd8 --- /dev/null +++ b/pkNX.Randomization/Randomizers/Personal/PersonalRandomizer.cs @@ -0,0 +1,428 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using pkNX.Structures; + +namespace pkNX.Randomization +{ + public class PersonalRandomizer : Randomizer + { + private const int tmcount = 100; + private const int eggGroupCount = 16; + private const int TypeCount = 18; + + private readonly GameInfo Game; + private readonly PersonalTable Table; + private readonly EvolutionSet[] Evolutions; + + public PersonalRandSettings Settings { get; set; } + + public PersonalRandomizer(PersonalTable table, GameInfo game, EvolutionSet[] evolutions) + { + Game = game; + Table = table; + Evolutions = evolutions; + if (File.Exists("bannedabilites.txt")) + { + var data = File.ReadAllLines("bannedabilities.txt"); + var list = new List(BannedAbilities); + list.AddRange(data.Select(z => Convert.ToInt32(z))); + BannedAbilities = list; + } + } + + public override void Execute() + { + if (Settings.ModifyByEvolutions) + RandomizeChains(); + else + RandomizeAllSpecies(); + } + + private void RandomizeAllSpecies() + { + for (var species = 0; species <= Game.MaxSpeciesID; species++) + RandomizeSpecies(species); + } + + private bool[] processed; + + private void RandomizeChains() + { + processed = new bool[Table.Table.Length]; + for (var species = 0; species <= Game.MaxSpeciesID; species++) + { + for (int f = 0; f <= Table[species].FormeCount; f++) + RandomizeChain(species, f); + } + } + + private bool AlreadyProcessed(int index) + { + if (processed[index]) + return true; + processed[index] = true; + return false; + } + + private void RandomizeChain(int species, int forme) + { + var index = Table.GetFormeIndex(species, forme); + if (AlreadyProcessed(index)) + return; + processed[index] = true; + + var entry = Table[index]; + Randomize(entry, species); + ProcessEvolutions(species, forme, index); + } + + private void ProcessEvolutions(int species, int forme, int devolvedIndex) + { + var evoindex = GetEvolutionEntry(species, forme); + var evos = Evolutions[evoindex]; + + if (evos.PossibleEvolutions.Length == 1) + { + var evo = evos.PossibleEvolutions[0]; + var ei = Table.GetFormeIndex(evo.Species, evo.Form); + + if (AlreadyProcessed(evoindex)) + return; + RandomizeSingleChain(evo, devolvedIndex); + ProcessEvolutions(evo.Species, evo.Form, ei); + } + else + { + foreach (var evo in evos.PossibleEvolutions) + { + var ei = Table.GetFormeIndex(evo.Species, evo.Form); + if (AlreadyProcessed(evoindex)) + return; + RandomizeSplitChain(evo, devolvedIndex); + ProcessEvolutions(evo.Species, evo.Form, ei); + } + } + } + + private void RandomizeSingleChain(EvolutionMethod evo, int devolvedIndex) + { + var child = Table[devolvedIndex]; + var z = Table.GetFormeEntry(evo.Species, evo.Form); + RandomizeFrom(z, child, evo.Species); + } + + private void RandomizeSplitChain(EvolutionMethod evo, int devolvedIndex) + { + var child = Table[devolvedIndex]; + var z = Table.GetFormeEntry(evo.Species, evo.Form); + RandomizeFrom(z, child, evo.Species); + } + + private int GetEvolutionEntry(int species, int form) + { + if (Game.Generation < 7) + return species; + return Table.GetFormeIndex(species, form); + } + + private void RandomizeSpecies(int species) + { + var entry = Table[species]; + Randomize(entry, species); + var formeCount = entry.FormeCount; + for (int forme = 1; forme <= formeCount; forme++) + { + entry = Table.GetFormeEntry(species, forme); + Randomize(entry, species); + } + } + + public void RandomizeFrom(PersonalInfo z, PersonalInfo child, int species) + { + if (Settings.ModifyStats) + RandomizeStats(z); + if (Settings.ShuffleStats) + RandomShuffledStats(z); + + if (Settings.ModifyTypes) + { + if (Settings.InheritType && Settings.InheritTypeSetting != ModifyState.All) + { + var types = child.Types; + switch (Settings.InheritTypeSetting) + { + case ModifyState.Two when Rand.Next(100) < Settings.InheritTypeNeitherChance: + types = new[] { GetRandomType(), GetRandomType() }; + break; + case ModifyState.Two when Rand.Next(100) < Settings.InheritTypeOnlyOneChance: + case ModifyState.One when Rand.Next(100) < Settings.InheritTypeOnlyOneChance: + types[Rand.Next(2)] = GetRandomType(); + break; + } + if (Rand.Next(0, 100) < Settings.SameTypeChance) + { + int index = Rand.Next(2); + types[index ^ 1] = types[index]; + } + z.Types = types; + } + else + { + RandomizeTypes(z); + } + } + + if (Settings.ModifyAbility) + { + if (Settings.InheritAbility) + { + var abils = child.Abilities; + GetRandomAbilities(abils, Settings.InheritAbilitySetting); + z.Abilities = abils; + } + else + { + RandomizeAbilities(z); + } + } + + if (Settings.ModifyLearnsetTM || Settings.ModifyLearnsetHM) + { + if (Settings.InheritChildTM) + z.TMHM = child.TMHM; + else + RandomizeTMHM(z); + } + + if (Settings.ModifyLearnsetTypeTutors) + { + if (Settings.InheritChildSpecial) + z.TypeTutors = child.TypeTutors; + else + RandomizeTypeTutors(z, species); + } + + if (Settings.ModifyLearnsetMoveTutors) + { + if (Settings.InheritChildTutor) + z.SpecialTutors = child.SpecialTutors; + else + RandomizeSpecialTutors(z); + } + + if (Settings.ModifyEgg) + z.EggGroups = child.EggGroups; + + if (Settings.ModifyHeldItems) + { + if (Settings.InheritHeldItem) + z.Items = child.Items; + else + RandomizeHeldItems(z); + } + + ExecuteCatchRate(z); + } + + private void GetRandomAbilities(int[] abils, ModifyState setting) + { + switch (setting) + { + case ModifyState.All: + GetRandomAbilities(abils); + break; + case ModifyState.Two when Rand.Next(100) < Settings.InheritAbilityNeitherChance: + GetRandomAbilities(abils, 1); + break; + case ModifyState.Two when Rand.Next(100) < Settings.InheritAbilityOnlyOneChance: + case ModifyState.One when Rand.Next(100) < Settings.InheritAbilityOnlyOneChance: + GetRandomAbilities(abils, 2); + break; + } + if (Rand.Next(100) < Settings.SameAbilityChance) + { + int index = Rand.Next(2); + abils[index^1] = abils[index]; + } + } + + public void Randomize(PersonalInfo z, int species) + { + if (Settings.ModifyStats) + RandomizeStats(z); + if (Settings.ShuffleStats) + RandomShuffledStats(z); + + if (Settings.ModifyTypes) + RandomizeTypes(z); + + if (Settings.ModifyAbility) + RandomizeAbilities(z); + + if (Settings.ModifyLearnsetTM || Settings.ModifyLearnsetHM) + RandomizeTMHM(z); + + if (Settings.ModifyLearnsetTypeTutors) + RandomizeTypeTutors(z, species); + + if (Settings.ModifyLearnsetMoveTutors) + RandomizeSpecialTutors(z); + + if (Settings.ModifyEgg) + RandomizeEggGroups(z); + + if (Settings.ModifyHeldItems) + RandomizeHeldItems(z); + + ExecuteCatchRate(z); + } + + private void ExecuteCatchRate(PersonalInfo z) + { + if (Settings.CatchRate == CatchRate.Random) + z.CatchRate = Rand.Next(3, 251); // Random Catch Rate between 3 and 250. + else if (Settings.CatchRate == CatchRate.BSTScaled) + z.CatchRate = GetBSTCatchRate(z.BST); + } + + private static int GetBSTCatchRate(int BST) + { + var c = 11 * (Math.Sqrt(Math.Max(0, 600 - BST))); + + const int min = 3; + return (int)Math.Min(255, min + c); + } + + private void RandomizeTMHM(PersonalInfo z) + { + var tms = z.TMHM; + + if (Settings.ModifyLearnsetTM) + { + for (int j = 0; j < tmcount; j++) + tms[j] = Rand.Next(100) < Settings.LearnTMPercent; + } + + if (Settings.ModifyLearnsetHM) + { + for (int j = tmcount; j < tms.Length; j++) + tms[j] = Rand.Next(100) < Settings.LearnTMPercent; + } + + z.TMHM = tms; + } + + private void RandomizeTypeTutors(PersonalInfo z, int species) + { + var t = z.TypeTutors; + for (int i = 0; i < t.Length; i++) + t[i] = Rand.Next(100) < Settings.LearnTypeTutorPercent; + + // Make sure Rayquaza can learn Dragon Ascent. + if (!Game.XY && species == 384) + t[7] = true; + + z.TypeTutors = t; + } + + private void RandomizeSpecialTutors(PersonalInfo z) + { + var tutors = z.SpecialTutors; + foreach (bool[] tutor in tutors) + { + for (int i = 0; i < tutor.Length; i++) + tutor[i] = Rand.Next(100) < Settings.LearnMoveTutorPercent; + } + + z.SpecialTutors = tutors; + } + + private void RandomizeAbilities(PersonalInfo z) + { + var abils = z.Abilities; + GetRandomAbilities(abils, Settings.Ability); + z.Abilities = abils; + } + + private void GetRandomAbilities(int[] abils, int skip = 0) + { + for (int i = 0; i < abils.Length - skip; i++) + abils[i] = GetRandomAbility(); + } + + private void RandomizeEggGroups(PersonalInfo z) + { + var egg = z.EggGroups; + egg[0] = GetRandomEggGroup(); + egg[1] = Rand.Next(100) < Settings.SameEggGroupChance ? egg[0] : GetRandomEggGroup(); + z.EggGroups = egg; + } + + private void RandomizeHeldItems(PersonalInfo z) + { + var item = z.Items; + for (int j = 0; j < item.Length; j++) + item[j] = GetRandomHeldItem(); + z.Items = item; + } + + private void RandomizeTypes(PersonalInfo z) + { + var t = z.Types; + t[0] = GetRandomType(); + t[1] = Rand.Next(0, 100) < Settings.SameTypeChance ? t[0] : GetRandomType(); + z.Types = t; + } + + private void RandomizeStats(PersonalInfo z) + { + // Fiddle with Base Stats, don't muck with Shedinja. + var stats = z.Stats; + if (stats[0] == 1) + return; + int RandDeviation() => Rand.Next(Settings.StatDeviationMin, Settings.StatDeviationMax); + for (int i = 0; i < stats.Length; i++) + { + if (!Settings.StatsToRandomize[i]) + continue; + + var val = stats[i] * RandDeviation() / 100; + var stat = Math.Max(1, Math.Min(255, val)); + } + z.Stats = stats; + } + + private void RandomShuffledStats(PersonalInfo z) + { + // Fiddle with Base Stats, don't muck with Shedinja. + var stats = z.Stats; + if (stats[0] == 1) + return; + for (int i = 0; i < stats.Length; i++) + Util.Shuffle(stats); + z.Stats = stats; + } + + private int GetRandomType() => Rand.Next(0, TypeCount); + private int GetRandomEggGroup() => Rand.Next(1, eggGroupCount); + private int GetRandomHeldItem() => Game.HeldItems[Rand.Next(1, Game.HeldItems.Length)]; + private readonly IList BannedAbilities = Array.Empty(); + + private int GetRandomAbility() + { + const int WonderGuard = 25; + while (true) + { + int newabil = Rand.Next(1, Game.MaxAbilityID + 1); + if (newabil == WonderGuard && Settings.WonderGuard == Permissive.No) + continue; + if (BannedAbilities.Contains(newabil)) + continue; + return newabil; + } + } + } +} diff --git a/pkNX.Randomization/Randomizers/RandSettings.cs b/pkNX.Randomization/Randomizers/RandSettings.cs new file mode 100644 index 00000000..bfa16d6a --- /dev/null +++ b/pkNX.Randomization/Randomizers/RandSettings.cs @@ -0,0 +1,7 @@ +namespace pkNX.Randomization +{ + public class RandSettings + { + + } +} \ No newline at end of file diff --git a/pkNX.Randomization/Randomizers/Randomizer.cs b/pkNX.Randomization/Randomizers/Randomizer.cs new file mode 100644 index 00000000..52fbbce9 --- /dev/null +++ b/pkNX.Randomization/Randomizers/Randomizer.cs @@ -0,0 +1,11 @@ +using System; + +namespace pkNX.Randomization +{ + public abstract class Randomizer + { + public abstract void Execute(); + + protected readonly Random Rand = Util.Random; + } +} diff --git a/pkNX.Randomization/Randomizers/SpeciesRandomizer.cs b/pkNX.Randomization/Randomizers/SpeciesRandomizer.cs new file mode 100644 index 00000000..5face416 --- /dev/null +++ b/pkNX.Randomization/Randomizers/SpeciesRandomizer.cs @@ -0,0 +1,137 @@ +using System.Linq; +using pkNX.Structures; + +namespace pkNX.Randomization +{ + public class SpeciesRandomizer + { + private readonly PersonalTable SpeciesStat; + private readonly int MaxSpeciesID; + private readonly GameInfo Game; + + private SpeciesSettings s; + + public SpeciesRandomizer(GameInfo game, PersonalTable t) + { + Game = game; + MaxSpeciesID = Game.MaxSpeciesID; + SpeciesStat = t; + } + + /// + /// Initializes the according to the provided settings. + /// + /// + public void Initialize(SpeciesSettings settings) + { + s = settings; + var list = s.GetSpecies(Game.MaxSpeciesID, Game.Generation); + RandSpec = new GenericRandomizer(list); + } + + #region Random Species Filtering Parameters + private GenericRandomizer RandSpec; + private int loopctr; + private const int l = 10; // tweakable scalars + private const int h = 11; + #endregion + + internal int GetRandomSpecies(int oldSpecies, params int[] bannedSpecies) + { + // Get a new random species + var oldpkm = SpeciesStat[oldSpecies]; + + loopctr = 0; // altering calculations to prevent infinite loops + int newSpecies; + while (!GetNewSpecies(oldSpecies, oldpkm, out newSpecies) || bannedSpecies.Contains(newSpecies)) + loopctr++; + return newSpecies; + } + + public int GetRandomSpeciesType(int oldSpecies, int type) + { + // Get a new random species + PersonalInfo oldpkm = SpeciesStat[oldSpecies]; + + loopctr = 0; // altering calculations to prevent infinite loops + int newSpecies; + while (!GetNewSpecies(oldSpecies, oldpkm, out newSpecies) || !GetIsTypeMatch(newSpecies, type)) + loopctr++; + return newSpecies; + } + + private bool GetIsTypeMatch(int newSpecies, int type) => type == -1 || SpeciesStat[newSpecies].Types.Any(z => z == type) || loopctr > 9000; + + public int GetRandomSpecies(int oldSpecies) + { + // Get a new random species + var oldpkm = SpeciesStat[oldSpecies]; + + loopctr = 0; // altering calculations to prevent infinite loops + int newSpecies; + while (!GetNewSpecies(oldSpecies, oldpkm, out newSpecies)) + { + if (loopctr > 0x0001_0000) + { + var pkm = SpeciesStat[newSpecies]; + if (IsSpeciesBSTBad(oldpkm, pkm) && loopctr > 0x0001_1000) // keep trying for at minimum BST + continue; + return newSpecies; // failed to find any match based on criteria, return random species that may or may not match criteria + } + loopctr++; + } + return newSpecies; + } + + public int[] RandomSpeciesList => Enumerable.Range(1, MaxSpeciesID).ToArray(); + + private bool GetNewSpecies(int currentSpecies, PersonalInfo oldpkm, out int newSpecies) + { + newSpecies = RandSpec.Next(); + var pkm = SpeciesStat[newSpecies]; + + if (IsSpeciesReplacementBad(newSpecies, currentSpecies)) // no A->A randomization + return false; + return IsCriteriaMatch(oldpkm, pkm); + } + + private bool IsSpeciesReplacementBad(int newSpecies, int currentSpecies) + { + if (newSpecies != currentSpecies) + return false; + return loopctr < MaxSpeciesID * 10; + } + + private bool IsCriteriaMatch(PersonalInfo oldpkm, PersonalInfo pkm) + { + if (IsSpeciesEXPRateBad(oldpkm, pkm)) + return false; + if (IsSpeciesTypeBad(oldpkm, pkm)) + return false; + if (IsSpeciesBSTBad(oldpkm, pkm)) + return false; + return true; + } + + private bool IsSpeciesEXPRateBad(PersonalInfo oldpkm, PersonalInfo pkm) + { + return s.EXPGroup && oldpkm.EXPGrowth == pkm.EXPGrowth; + } + + private bool IsSpeciesTypeBad(PersonalInfo oldpkm, PersonalInfo pkm) + { + return s.Type && oldpkm.Types.Intersect(pkm.Types).Any(); + } + + private bool IsSpeciesBSTBad(PersonalInfo oldpkm, PersonalInfo pkm) + { + if (!s.BST) + return false; + // Base stat total has to be close to original BST + int expand = loopctr / MaxSpeciesID; + int lo = oldpkm.BST * l / (h + expand); + int hi = oldpkm.BST * (h + expand) / l; + return lo > pkm.BST || pkm.BST > hi; + } + } +} diff --git a/pkNX.Randomization/Randomizers/SpeciesSettings.cs b/pkNX.Randomization/Randomizers/SpeciesSettings.cs new file mode 100644 index 00000000..3bbfcb86 --- /dev/null +++ b/pkNX.Randomization/Randomizers/SpeciesSettings.cs @@ -0,0 +1,192 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using pkNX.Structures; + +namespace pkNX.Randomization +{ + /// + /// Settings for what Species are permitted during randomization. + /// + public class SpeciesSettings : RandSettings + { + private const string General = nameof(General); + private const string Misc = nameof(Misc); + + /// Allows Generation 1 species when randomizing. + [Category(General), Description("Allows Generation 1 species when randomizing.")] + public bool Gen1 { get; set; } = true; + + /// Allows Generation 2 species when randomizing. + [Category(General), Description("Allows Generation 2 species when randomizing.")] + public bool Gen2 { get; set; } = true; + + /// Allows Generation 3 species when randomizing. + [Category(General), Description("Allows Generation 3 species when randomizing.")] + public bool Gen3 { get; set; } = true; + + /// Allows Generation 4 species when randomizing. + [Category(General), Description("Allows Generation 4 species when randomizing.")] + public bool Gen4 { get; set; } = true; + + /// Allows Generation 5 species when randomizing. + [Category(General), Description("Allows Generation 5 species when randomizing.")] + public bool Gen5 { get; set; } = true; + + /// Allows Generation 6 species when randomizing. + [Category(General), Description("Allows Generation 6 species when randomizing.")] + public bool Gen6 { get; set; } = true; + + /// Allows Generation 7 species when randomizing. + [Category(General), Description("Allows Generation 7 species when randomizing.")] + public bool Gen7 { get; set; } = true; + + /// Allows Legendary species when randomizing. + [Category(Misc), Description("Allows Legendary species when randomizing.")] + public bool Legends { get; set; } = false; + + /// Allows Event-only species when randomizing. + [Category(Misc), Description("Allows Event-only species when randomizing.")] + public bool Events { get; set; } = false; + + /// Allows Shedinja as a random species when randomizing. + [Category(Misc), Description("Allows Shedinja as a random species when randomizing.")] + public bool Shedinja { get; set; } = false; + + /// Requires the randomized species to be in the same EXP Group as the original species. + [Category(Misc), Description("Requires the randomized species to be in the same EXP Group as the original species. Note: might not be used by all randomizers.")] + public bool EXPGroup { get; set; } = false; + + /// Requires the randomized species to have a similar Base Stat Total as the original species. + [Category(Misc), Description("Requires the randomized species to have a similar Base Stat Total as the original species. Note: might not be used by all randomizers.")] + public bool BST { get; set; } = true; + + /// Requires the randomized species to have a similar typing as the original species. + [Category(Misc), Description("Requires the randomized species to have a similar typing as the original species. Note: might not be used by all randomizers.")] + public bool Type { get; set; } = false; + + /// + /// Gets an array of Species according to the specified settings. + /// + /// Max Species ID + /// + /// + public int[] GetSpecies(int maxSpecies, int generation) + { + var list = new List(); + if (Gen1) AddGen1Species(list, maxSpecies); + if (Gen2) AddGen2Species(list, maxSpecies); + if (Gen3) AddGen3Species(list, maxSpecies); + if (Gen4) AddGen4Species(list, maxSpecies); + if (Gen5) AddGen5Species(list, maxSpecies); + if (Gen6) AddGen6Species(list, maxSpecies); + if (Gen7) AddGen7Species(list, maxSpecies); + + if (generation == 7 && Gen1 && maxSpecies <= Legal.MaxSpeciesID_7_GG) + AddGGEvents(list); + + return list.Count == 0 ? GetSpeciesAll(maxSpecies) : list.ToArray(); + } + + private int[] GetSpeciesAll(int maxSpecies) => Enumerable.Range(1, maxSpecies).ToArray(); + + private void AddGen1Species(List list, int maxSpecies) + { + if (maxSpecies <= 0) + return; + list.AddRange(Enumerable.Range(1, 143)); // Bulbasaur - Snorlax + list.AddRange(Enumerable.Range(147, 3)); // Dratini - Dragonite + + if (Legends) + { + list.AddRange(Enumerable.Range(144, 3)); // Birds + list.Add(150); // Mewtwo + } + if (Events) list.Add(151); // Mew + } + + private void AddGen2Species(List list, int maxSpecies) + { + if (maxSpecies <= 151) + return; + list.AddRange(Enumerable.Range(152, 91)); // Chikorita - Blissey + list.AddRange(Enumerable.Range(246, 3)); // Larvitar - Tyranitar + + if (Legends) + { + list.AddRange(Enumerable.Range(243, 3)); // Beasts + list.AddRange(Enumerable.Range(249, 2)); // Lugia & Ho-Oh + } + if (Events) list.Add(251); // Celebi + } + + private void AddGen3Species(List list, int maxSpecies) + { + if (maxSpecies <= 251) + return; + list.AddRange(Enumerable.Range(252, 40)); // Treecko - Ninjask + list.AddRange(Enumerable.Range(293, 84)); // Whismur - Metagross + if (Shedinja) list.Add(292); // Shedinja + if (Legends) list.AddRange(Enumerable.Range(377, 8)); // Regi, Lati, Mascot + if (Events) list.AddRange(Enumerable.Range(385, 2)); // Jirachi/Deoxys + } + + private void AddGen4Species(List list, int maxSpecies) + { + if (maxSpecies <= 386) + return; + list.AddRange(Enumerable.Range(387, 93)); // Turtwig - Rotom + if (Legends) list.AddRange(Enumerable.Range(480, 9)); // Sinnoh Legends + if (Events) list.AddRange(Enumerable.Range(489, 5)); // Phione, Manaphy, Darkrai, Shaymin, Arceus + } + + private void AddGen5Species(List list, int maxSpecies) + { + if (maxSpecies <= 493) + return; + list.AddRange(Enumerable.Range(495, 143)); // Snivy - Volcarona + if (Legends) list.AddRange(Enumerable.Range(638, 9)); // Unova Legends + if (Events) list.Add(494); list.AddRange(Enumerable.Range(647, 3)); // Victini, Keldeo, Meloetta, Genesect + } + + private void AddGen6Species(List list, int maxSpecies) + { + if (maxSpecies <= 649) + return; + list.AddRange(Enumerable.Range(650, 66)); // Chespin - Noivern + if (Legends) list.AddRange(Enumerable.Range(716, 3)); // Kalos Legends + if (Events) list.AddRange(Enumerable.Range(719, 3)); // Diancie, Hoopa, Volcanion + } + + private void AddGen7Species(List list, int maxSpecies) + { + if (maxSpecies <= 721) + return; + list.AddRange(Enumerable.Range(722, 50)); // Rowlet - Pyukumuku + list.AddRange(Enumerable.Range(774, 11)); // Minior - Kommo-o + + if (Legends) + { + list.AddRange(Enumerable.Range(772, 2)); // Type: Null, Silvally + list.AddRange(Enumerable.Range(785, 16)); // Tapus, Legends, UBs + } + if (Events) list.AddRange(Enumerable.Range(801, 2)); // Magearna, Marshadow + + if (maxSpecies >= Legal.MaxSpeciesID_7_USUM) // USUM + { + if (Legends) list.AddRange(Enumerable.Range(803, 4)); // Poipole, Naganadel, Stakataka, Blacephalon + if (Events) list.Add(807); // Zeraora + } + if (maxSpecies >= Legal.MaxSpeciesID_7_GG) // USUM + { + if (Events) + AddGGEvents(list); + } + } + + private void AddGGEvents(List list) + { + list.AddRange(Enumerable.Range(808, Legal.MaxSpeciesID_7_GG - Legal.MaxAbilityID_7_USUM)); // * + } + } +} diff --git a/pkNX.Randomization/SpeciesSetting.cs b/pkNX.Randomization/SpeciesSetting.cs new file mode 100644 index 00000000..e928ef59 --- /dev/null +++ b/pkNX.Randomization/SpeciesSetting.cs @@ -0,0 +1,38 @@ +using pkNX.Structures; + +namespace pkNX.Randomization +{ + public class SpeciesSetting + { + public bool Gen1 { get; set; } + public bool Gen2 { get; set; } + public bool Gen3 { get; set; } + public bool Gen4 { get; set; } + public bool Gen5 { get; set; } + public bool Gen6 { get; set; } + public bool Gen7 { get; set; } + + public bool L { get; set; } = false; + public bool E { get; set; } = false; + public bool Shedinja = false; + public bool EXP { get; set; } = false; + public bool BST { get; set; } = true; + public bool Type { get; set; } = false; + + private readonly GameVersion Game; + private readonly int Generation; + + public SpeciesSetting(GameVersion game) + { + Game = game; + Generation = Game.GetGeneration(); + Gen1 = Generation <= 1; + Gen2 = Generation <= 2; + Gen3 = Generation <= 3; + Gen4 = Generation <= 4; + Gen5 = Generation <= 5; + Gen6 = Generation <= 6; + Gen7 = Generation <= 7; + } + } +} diff --git a/pkNX.Randomization/Util.cs b/pkNX.Randomization/Util.cs new file mode 100644 index 00000000..c405c445 --- /dev/null +++ b/pkNX.Randomization/Util.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace pkNX.Randomization +{ + public static class Util + { + public static Random Random { get; private set; } = new Random(); + + public static void ReseedRand(int seed) + { + Random = new Random(seed); + Structures.Util.Rand = Random; + } + + public static uint Rand32() => (uint)Random.Next(1 << 30) << 2 | (uint)Random.Next(1 << 2); + + public static void Shuffle(IList array) + { + int n = array.Count; + for (int i = 0; i < n; i++) + { + int r = i + (int)(Random.NextDouble() * (n - i)); + var t = array[r]; + array[r] = array[i]; + array[i] = t; + } + } + } +} diff --git a/pkNX.Randomization/pkNX.Randomization.csproj b/pkNX.Randomization/pkNX.Randomization.csproj new file mode 100644 index 00000000..32a0fc16 --- /dev/null +++ b/pkNX.Randomization/pkNX.Randomization.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0;net46 + Randomizer Utility + + + + + + + diff --git a/pkNX.Structures/CodePattern.cs b/pkNX.Structures/CodePattern.cs new file mode 100644 index 00000000..288d990b --- /dev/null +++ b/pkNX.Structures/CodePattern.cs @@ -0,0 +1,89 @@ +using System; +using System.Diagnostics; + +namespace pkNX.Structures +{ + public static class CodePattern + { + /// + /// Finds a provided within the supplied . + /// + /// Array to look in + /// Pattern to look for + /// Starting offset to look from + /// Amount of entries to look through + /// Index the pattern occurrs at; if not found, returns -1. + public static int IndexOfBytes(byte[] array, byte[] pattern, int startIndex = 0, int length = -1) + { + int len = pattern.Length; + int endIndex = length > 0 + ? startIndex + length + : array.Length - len - startIndex; + + endIndex = Math.Min(array.Length - pattern.Length, endIndex); + + int i = startIndex; + int j = 0; + while (true) + { + if (pattern[j] != array[i + j]) + { + if (++i == endIndex) + return -1; + j = 0; + } + else if (++j == len) + { + return i; + } + } + } + + /// + /// Finds a provided within the supplied . + /// + /// Array to look in + /// Pattern to look for + /// Wildcard byte to be ignored, incrementally as bitflags. + /// Starting offset to look from + /// Amount of entries to look through + /// Index the pattern occurrs at; if not found, returns -1. + public static int IndexOfPattern(byte[] array, byte[] pattern, ulong wildCard, int startIndex = 0, int length = -1) + { + Debug.Assert(pattern.Length <= 8*sizeof(ulong)); + + int len = pattern.Length; + int endIndex = length > 0 + ? startIndex + length + : array.Length - len - startIndex; + + endIndex = Math.Min(array.Length - pattern.Length, endIndex); + + int i = startIndex; + int j = 0; + while (true) + { + if (pattern[j] != array[i + j] && ((wildCard >> j) & 1) == 0) + { + if (++i == endIndex) + return -1; + j = 0; + } + else if (++j == len) + { + return i; + } + } + } + + /// + /// byte pattern which precedes the TMHM list. This list is the tail end of item IDs for each TM(01->100). + /// + public static readonly byte[] TMHM_GG = + { + 0xA0, 0x01, 0xA1, 0x01, 0xA2, 0x01, 0xA3, 0x01, + 0x6A, 0x02, 0x6B, 0x02, 0x6C, 0x02, 0xB2, 0x02, + 0xB3, 0x02, 0xB4, 0x02, 0xB5, 0x02, 0xB6, 0x02, + }; + } +} diff --git a/pkNX.Structures/EggMove/EggMoves.cs b/pkNX.Structures/EggMove/EggMoves.cs new file mode 100644 index 00000000..59319454 --- /dev/null +++ b/pkNX.Structures/EggMove/EggMoves.cs @@ -0,0 +1,11 @@ +namespace pkNX.Structures +{ + public abstract class EggMoves + { + public int Count; + public int[] Moves; + public int FormTableIndex; + + public abstract byte[] Write(); + } +} diff --git a/pkNX.Structures/EggMove/EggMoves6.cs b/pkNX.Structures/EggMove/EggMoves6.cs new file mode 100644 index 00000000..6e22daec --- /dev/null +++ b/pkNX.Structures/EggMove/EggMoves6.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; + +namespace pkNX.Structures +{ + public class EggMoves6 : EggMoves + { + public EggMoves6(byte[] data) + { + if (data.Length < 2 || data.Length % 2 != 0) + { Count = 0; Moves = Array.Empty(); return; } + using (BinaryReader br = new BinaryReader(new MemoryStream(data))) + { + Moves = new int[Count = br.ReadUInt16()]; + for (int i = 0; i < Count; i++) + Moves[i] = br.ReadUInt16(); + } + } + + public override byte[] Write() + { + Count = Moves.Length; + if (Count == 0) return Array.Empty(); + using (MemoryStream ms = new MemoryStream()) + using (BinaryWriter bw = new BinaryWriter(ms)) + { + bw.Write((ushort)Count); + for (int i = 0; i < Count; i++) + bw.Write((ushort)Moves[i]); + + return ms.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/pkNX.Structures/EggMove/EggMoves7.cs b/pkNX.Structures/EggMove/EggMoves7.cs new file mode 100644 index 00000000..b59ca62d --- /dev/null +++ b/pkNX.Structures/EggMove/EggMoves7.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; + +namespace pkNX.Structures +{ + public class EggMoves7 : EggMoves + { + public EggMoves7(byte[] data) + { + if (data.Length < 2 || data.Length % 2 != 0) + { Count = 0; Moves = Array.Empty(); return; } + using (BinaryReader br = new BinaryReader(new MemoryStream(data))) + { + FormTableIndex = br.ReadUInt16(); + Count = br.ReadUInt16(); + Moves = new int[Count]; + for (int i = 0; i < Count; i++) + Moves[i] = br.ReadUInt16(); + } + } + + public override byte[] Write() + { + Count = Moves.Length; + using (MemoryStream ms = new MemoryStream()) + using (BinaryWriter bw = new BinaryWriter(ms)) + { + bw.Write((ushort)FormTableIndex); + bw.Write((ushort)Count); + for (int i = 0; i < Count; i++) + bw.Write((ushort)Moves[i]); + + return ms.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/pkNX.Structures/Encounter/EncounterGift.cs b/pkNX.Structures/Encounter/EncounterGift.cs new file mode 100644 index 00000000..a5ae0bc7 --- /dev/null +++ b/pkNX.Structures/Encounter/EncounterGift.cs @@ -0,0 +1,42 @@ +using System; + +namespace pkNX.Structures +{ + public abstract class EncounterGift + { + protected byte[] Data { get; set; } + public virtual byte[] Write() => (byte[])Data.Clone(); + + public abstract int Species { get; set; } + public virtual int HeldItem { get; set; } + public abstract int Level { get; set; } + public abstract int Form { get; set; } + public abstract FixedGender Gender { get; set; } + + public virtual Nature Nature { get; set; } = Nature.Random; + public virtual int Ability { get; set; } + public virtual bool ShinyLock { get; set; } + + public virtual bool IV3 { get; set; } + public virtual int[] RelearnMoves { get; set; } = Array.Empty(); + public abstract Shiny Shiny { get; set; } + + public abstract int IV_HP { get; set; } + public abstract int IV_ATK { get; set; } + public abstract int IV_DEF { get; set; } + public abstract int IV_SPE { get; set; } + public abstract int IV_SPA { get; set; } + public abstract int IV_SPD { get; set; } + + public int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set + { + if (value?.Length != 6) return; + IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; + IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; + } + } + } +} diff --git a/pkNX.Structures/Encounter/EncounterGift6AO.cs b/pkNX.Structures/Encounter/EncounterGift6AO.cs new file mode 100644 index 00000000..b9b9d235 --- /dev/null +++ b/pkNX.Structures/Encounter/EncounterGift6AO.cs @@ -0,0 +1,44 @@ +using System; + +namespace pkNX.Structures +{ + public class EncounterGift6AO : EncounterGift + { + public override int Species { get => BitConverter.ToUInt16(Data, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x00); } + public int _02 { get => BitConverter.ToUInt16(Data, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x02); } + public override int Form { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public override int Level { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public override int Ability { get => (sbyte)Data[0x06]; set => Data[0x06] = (byte)value; } + public override Nature Nature { get => (Nature)Data[0x07]; set => Data[0x07] = (byte)value; } + public override Shiny Shiny { get => (Shiny)Data[0x08]; set => Data[0x08] = (byte)value; } + + // padding? + public int _09 { get => Data[0x09]; set => Data[0x09] = (byte)value; } + public int _0A { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } + public int _0B { get => Data[0x0B]; set => Data[0x0B] = (byte)value; } + + public override int HeldItem { get => BitConverter.ToUInt16(Data, 0x0C); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0C); } + public override FixedGender Gender { get => (FixedGender)Data[0x10]; set => Data[0x10] = (byte)value; } + + // padding? + public int _11 { get => (sbyte)Data[0x11]; set => Data[0x11] = (byte)value; } + public short MetLocation { get => BitConverter.ToInt16(Data, 0x12); set => BitConverter.GetBytes(value).CopyTo(Data, 0x12); } + public int Move { get => BitConverter.ToUInt16(Data, 0x14); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x14); } + + public override int IV_HP { get => (sbyte)Data[0x16]; set => Data[0x16] = (byte)value; } + public override int IV_ATK { get => (sbyte)Data[0x17]; set => Data[0x17] = (byte)value; } + public override int IV_DEF { get => (sbyte)Data[0x18]; set => Data[0x18] = (byte)value; } + public override int IV_SPA { get => (sbyte)Data[0x19]; set => Data[0x19] = (byte)value; } + public override int IV_SPD { get => (sbyte)Data[0x1A]; set => Data[0x1A] = (byte)value; } + public override int IV_SPE { get => (sbyte)Data[0x1B]; set => Data[0x1B] = (byte)value; } + + public int CNT_Cool { get => (sbyte)Data[0x1C]; set => Data[0x1C] = (byte)value; } + public int CNT_Beauty{ get => (sbyte)Data[0x1D]; set => Data[0x1D] = (byte)value; } + public int CNT_Cute { get => (sbyte)Data[0x1E]; set => Data[0x1E] = (byte)value; } + public int CNT_Smart { get => (sbyte)Data[0x1F]; set => Data[0x1F] = (byte)value; } + public int CNT_Tough { get => (sbyte)Data[0x20]; set => Data[0x20] = (byte)value; } + public int CNT_Sheen { get => (sbyte)Data[0x21]; set => Data[0x21] = (byte)value; } + + public int _22 { get => (sbyte)Data[0x22]; set => Data[0x22] = (byte)value; } + } +} \ No newline at end of file diff --git a/pkNX.Structures/Encounter/EncounterGift6XY.cs b/pkNX.Structures/Encounter/EncounterGift6XY.cs new file mode 100644 index 00000000..d62e849b --- /dev/null +++ b/pkNX.Structures/Encounter/EncounterGift6XY.cs @@ -0,0 +1,33 @@ +using System; + +namespace pkNX.Structures +{ + public class EncounterGift6XY : EncounterGift + { + public override int Species { get => BitConverter.ToUInt16(Data, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x00); } + public int _02 { get => BitConverter.ToUInt16(Data, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x02); } + public override int Form { get => Data[0x04]; set => Data[0x04] = (byte) value; } + public override int Level { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public override int Ability { get => (sbyte)Data[0x06]; set => Data[0x06] = (byte)value; } + public override Nature Nature { get => (Nature)Data[0x07]; set => Data[0x07] = (byte)value; } + public override Shiny Shiny { get => (Shiny)Data[0x08]; set => Data[0x08] = (byte)value; } + + // padding + public int _09 { get => Data[0x09]; set => Data[0x09] = (byte)value; } + public int _0A { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } + public int _0B { get => Data[0x0B]; set => Data[0x0B] = (byte)value; } + + public override int HeldItem { get => BitConverter.ToUInt16(Data, 0x0C); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0C); } + public override FixedGender Gender { get => (FixedGender)Data[0x10]; set => Data[0x10] = (byte)value; } + + public override int IV_HP { get => (sbyte)Data[0x11]; set => Data[0x11] = (byte)value; } + public override int IV_ATK { get => (sbyte)Data[0x12]; set => Data[0x12] = (byte)value; } + public override int IV_DEF { get => (sbyte)Data[0x13]; set => Data[0x13] = (byte)value; } + public override int IV_SPA { get => (sbyte)Data[0x14]; set => Data[0x14] = (byte)value; } + public override int IV_SPD { get => (sbyte)Data[0x15]; set => Data[0x15] = (byte)value; } + public override int IV_SPE { get => (sbyte)Data[0x16]; set => Data[0x16] = (byte)value; } + + // padding + public int _17 { get => (sbyte)Data[0x17]; set => Data[0x17] = (byte)value; } + } +} \ No newline at end of file diff --git a/pkNX.Structures/Encounter/EncounterGift7.cs b/pkNX.Structures/Encounter/EncounterGift7.cs new file mode 100644 index 00000000..efca1d38 --- /dev/null +++ b/pkNX.Structures/Encounter/EncounterGift7.cs @@ -0,0 +1,34 @@ +using System; + +namespace pkNX.Structures +{ + public class EncounterGift7 : EncounterGift + { + public const int SIZE = 0x14; + + public EncounterGift7(byte[] data) => Data = data; + + public override int Species { get => BitConverter.ToUInt16(Data, 0x0); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0); } + public override int Form { get => Data[0x2]; set => Data[0x2] = (byte)value; } + public override int Level { get => Data[0x3]; set => Data[0x3] = (byte)value; } + + public override Shiny Shiny { get => (Shiny)Data[0x4]; set => Data[0x4] = (byte)value; } + public override FixedGender Gender { get => (FixedGender)Data[0x5]; set => Data[0x5] = (byte)value; } + public override int Ability { get => (sbyte)Data[0x6]; set => Data[0x6] = (byte)value; } + public override Nature Nature { get => (Nature)Data[0x7]; set => Data[0x7] = (byte)value; } + + public override int HeldItem { get => BitConverter.ToUInt16(Data, 0x8); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x8); } + + public bool IsEgg { get => Data[0xA] == 1; set => Data[0xA] = (byte)(value ? 1 : 0); } + + public int SpecialMove { get => BitConverter.ToUInt16(Data, 0xC); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xC); } + + public override bool IV3 => (sbyte)Data[0xE] < 0 && (sbyte)Data[0xE] + 1 == -3; + public override int IV_HP { get; set; } = -1; + public override int IV_ATK { get; set; } = -1; + public override int IV_DEF { get; set; } = -1; + public override int IV_SPE { get; set; } = -1; + public override int IV_SPA { get; set; } = -1; + public override int IV_SPD { get; set; } = -1; + } +} \ No newline at end of file diff --git a/pkNX.Structures/Encounter/EncounterGift7b.cs b/pkNX.Structures/Encounter/EncounterGift7b.cs new file mode 100644 index 00000000..2758fbbe --- /dev/null +++ b/pkNX.Structures/Encounter/EncounterGift7b.cs @@ -0,0 +1,74 @@ +using System; + +namespace pkNX.Structures +{ + public class EncounterGift7b : EncounterGift + { + private const int SIZE = 0x20; + public EncounterGift7b(byte[] data) => Data = data ?? new byte[SIZE]; + + public ulong Hash => BitConverter.ToUInt64(Data, 0); + + public override int Species + { + get => BitConverter.ToUInt16(Data, 0x08); + set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x08); + } + + public override int Form { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } + public override int Level { get => Data[0x0B]; set => Data[0x0B] = (byte)value; } + + public override Shiny Shiny // 0 = Random, 1 = Always, 2 = Never + { + get => (Shiny)(Data[0x0C] & 3); + set => Data[0x0C] = (byte)((Data[0x0C] & ~3) | ((byte)value & 3)); + } + + public override FixedGender Gender // 0 = Random, 1 = Male, 2 = Female, 3 = Panic + { + get => (FixedGender)(Data[0x0D] & 3); + set => Data[0x0D] = (byte)((Data[0x0D] & ~3) | ((byte)value & 3)); + } + + public override Nature Nature { get => (Nature)Data[0x0E]; set => Data[0x0E] = (byte)value; } // 25 = random (sets the nature rand to 1) + public override int Ability { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } // 0 = rand + + public int SpecialMove // sub_71002B7AD0 checks nonzero, pushes move + { + get => BitConverter.ToUInt16(Data, 0x10); + set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x10); + } + + public override int IV_HP { get => (sbyte)Data[0x12]; set => Data[0x12] = (byte)value; } + public override int IV_ATK { get => (sbyte)Data[0x13]; set => Data[0x13] = (byte)value; } + public override int IV_DEF { get => (sbyte)Data[0x14]; set => Data[0x14] = (byte)value; } + public override int IV_SPA { get => (sbyte)Data[0x15]; set => Data[0x15] = (byte)value; } + public override int IV_SPD { get => (sbyte)Data[0x16]; set => Data[0x16] = (byte)value; } + public override int IV_SPE { get => (sbyte)Data[0x17]; set => Data[0x17] = (byte)value; } + + public int AV_HP { get => (sbyte)Data[0x18]; set => Data[0x18] = (byte)value; } + public int AV_ATK { get => (sbyte)Data[0x19]; set => Data[0x19] = (byte)value; } + public int AV_DEF { get => (sbyte)Data[0x1A]; set => Data[0x1A] = (byte)value; } + public int AV_SPA { get => (sbyte)Data[0x1B]; set => Data[0x1B] = (byte)value; } + public int AV_SPD { get => (sbyte)Data[0x1C]; set => Data[0x1C] = (byte)value; } + public int AV_SPE { get => (sbyte)Data[0x1D]; set => Data[0x1D] = (byte)value; } + + public override int[] RelearnMoves + { + get => new int[] + { + BitConverter.ToUInt16(Data, 0x18), + BitConverter.ToUInt16(Data, 0x1A), + BitConverter.ToUInt16(Data, 0x1C), + BitConverter.ToUInt16(Data, 0x1E), + }; + set + { + if (value?.Length != 4) + return; + for (int i = 0; i < 4; i++) + BitConverter.GetBytes((ushort)value[i]).CopyTo(Data, 0x18 + (i * 2)); + } + } + } +} diff --git a/pkNX.Structures/Encounter/EncounterStatic.cs b/pkNX.Structures/Encounter/EncounterStatic.cs new file mode 100644 index 00000000..fd5b6ddf --- /dev/null +++ b/pkNX.Structures/Encounter/EncounterStatic.cs @@ -0,0 +1,95 @@ +using System; +using System.Linq; + +namespace pkNX.Structures +{ + public abstract class EncounterStatic + { + protected byte[] Data { get; set; } + public virtual byte[] Write() => (byte[])Data.Clone(); + + public abstract int Species { get; set; } + public virtual int HeldItem { get; set; } + public abstract int Level { get; set; } + public abstract int Form { get; set; } + public abstract FixedGender Gender { get; set; } + + public virtual Nature Nature { get; set; } = Nature.Random; + public virtual int Ability { get; set; } + public virtual bool ShinyLock { get; set; } + + public virtual bool IV3 { get; set; } + public virtual int[] RelearnMoves { get; set; } = Array.Empty(); + public abstract Shiny Shiny { get; set; } + + public virtual int IV_HP { get; set; } = -1; + public virtual int IV_ATK { get; set; } = -1; + public virtual int IV_DEF { get; set; } = -1; + public virtual int IV_SPE { get; set; } = -1; + public virtual int IV_SPA { get; set; } = -1; + public virtual int IV_SPD { get; set; } = -1; + public virtual int EV_HP { get; set; } + public virtual int EV_ATK { get; set; } + public virtual int EV_DEF { get; set; } + public virtual int EV_SPE { get; set; } + public virtual int EV_SPA { get; set; } + public virtual int EV_SPD { get; set; } + + public int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set + { + if (value?.Length != 6) return; + IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; + IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; + } + } + + public int[] EVs + { + get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; + set + { + if (value?.Length != 6) return; + EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; + EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; + } + } + + public string GetSummary() + { + var str = $"new EncounterStatic {{ Species = {Species:000}, Level = {Level:00}, Location = -01, "; + if (Ability != 0) + str += $"Ability = {1 << (Ability - 1)}, "; + if (ShinyLock) + str += "Shiny = false, "; + + if (IV3) + { + str += "IV3 = true, "; + } + else if (IVs.Any(z => z >= 0)) + { + var iv = IVs.Select(z => z >= 0 ? $"{z:00}" : "-1"); + str += $"IVs = new[] {{{string.Join(",", iv)}}}, "; + } + if (RelearnMoves.Any(z => z != 0)) + { + var mv = RelearnMoves.Select(z => $"{z:000}"); + str += $"Relearn = new[] {{{string.Join(",", mv)}}}, "; + } + if (Form != 0) + str += $"Form = {Form}, "; + if (Gender != 0) + str += $"Gender = {Gender - 1}, "; + if (HeldItem > 0) + str += $"HeldItem = {HeldItem}, "; + if (Nature >= 0 && Nature < Nature.Random25) + str += $"Nature = {Nature - 1}, "; + + str += " },"; + return str; + } + } +} diff --git a/pkNX.Structures/Encounter/EncounterStatic6.cs b/pkNX.Structures/Encounter/EncounterStatic6.cs new file mode 100644 index 00000000..1021d4ac --- /dev/null +++ b/pkNX.Structures/Encounter/EncounterStatic6.cs @@ -0,0 +1,50 @@ +using System; + +namespace pkNX.Structures.Encounter +{ + public sealed class EncounterStatic6 : EncounterStatic + { + private const int SIZE = 0xC; + public EncounterStatic6(byte[] data) => Data = data ?? new byte[SIZE]; + + public override int Species { get => BitConverter.ToUInt16(Data, 0x0); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0); } + public override int Form { get => Data[0x2]; set => Data[0x2] = (byte)value; } + public override int Level { get => Data[0x3]; set => Data[0x3] = (byte)value; } + + public override int HeldItem + { + get => Math.Max(0, (int)BitConverter.ToInt16(Data, 0x4)); + set => BitConverter.GetBytes((short)(value <= 0 ? -1 : value)).CopyTo(Data, 0x4); + } + + public override Shiny Shiny + { + get => (Shiny)(Data[0x6] & 3); + set => Data[0x6] = (byte)((Data[0x6] & ~3) | ((byte)value & 3)); + } + + public override FixedGender Gender + { + get => (FixedGender)((Data[0x6] & 0x0C) >> 2); + set => Data[0x6] = (byte)((Data[0x6] & ~0xC) | (((byte)value & 3) << 2)); + } + + public override int Ability + { + get => (Data[0x6] & 0x70) >> 4; + set => Data[0x6] = (byte)((Data[0x6] & ~0x70) | ((value & 7) << 4)); + } + + public override bool IV3 + { + get => (Data[0x7] & 1) >> 0 == 1; + set => Data[0x7] = (byte)((Data[0x7] & ~1) | (value ? 1 : 0)); + } + + public bool IV3_1 + { + get => (Data[0x7] & 2) >> 1 == 1; + set => Data[0x7] = (byte)((Data[0x7] & ~2) | (value ? 2 : 0)); + } + } +} diff --git a/pkNX.Structures/Encounter/EncounterStatic7.cs b/pkNX.Structures/Encounter/EncounterStatic7.cs new file mode 100644 index 00000000..618c5dda --- /dev/null +++ b/pkNX.Structures/Encounter/EncounterStatic7.cs @@ -0,0 +1,134 @@ +using System; + +namespace pkNX.Structures +{ + public sealed class EncounterStatic7 : EncounterStatic + { + public const int SIZE = 0x38; + public EncounterStatic7(byte[] data) => Data = data ?? new byte[SIZE]; + + public override int Species + { + get => BitConverter.ToUInt16(Data, 0x0); + set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0); + } + + public override int Form + { + get => Data[0x2]; + set => Data[0x2] = (byte)value; + } + + public override int Level + { + get => Data[0x3]; + set => Data[0x3] = (byte)value; + } + + public override int HeldItem + { + get => Math.Max(0, (int)BitConverter.ToInt16(Data, 0x4)); + set => BitConverter.GetBytes((short)(value <= 0 ? -1 : value)).CopyTo(Data, 0x4); + } + + public override Shiny Shiny + { + get => (Shiny) (Data[0x6] & 3); + set => Data[0x6] = (byte)((Data[0x6] & ~3) | ((byte)value & 3)); + } + + public override FixedGender Gender + { + get => (FixedGender)((Data[0x6] & 0x0C) >> 2); + set => Data[0x6] = (byte)((Data[0x6] & ~0xC) | (((byte)value & 3) << 2)); + } + + public override int Ability + { + get => (Data[0x6] & 0x70) >> 4; + set => Data[0x6] = (byte)((Data[0x6] & ~0x70) | ((value & 7) << 4)); + } + + public bool _7 + { + get => (Data[0x7] & 1) >> 0 == 1; + set => Data[0x7] = (byte)((Data[0x7] & ~1) | (value ? 1 : 0)); + } + + public bool _7_1 + { + get => (Data[0x7] & 2) >> 1 == 1; + set => Data[0x7] = (byte)((Data[0x7] & ~2) | (value ? 2 : 0)); + } + + public int Map + { + get => BitConverter.ToInt16(Data, 0x8) - 1; + set => BitConverter.GetBytes((short)(value + 1)).CopyTo(Data, 0x8); + } + + public override int[] RelearnMoves + { + get => new int[] + { + BitConverter.ToUInt16(Data, 0xC), + BitConverter.ToUInt16(Data, 0xE), + BitConverter.ToUInt16(Data, 0x10), + BitConverter.ToUInt16(Data, 0x12), + }; + set + { + if (value.Length != 4) + return; + for (int i = 0; i < 4; i++) + BitConverter.GetBytes((ushort)value[i]).CopyTo(Data, 0xC + (i * 2)); + } + } + + public override Nature Nature + { + get => (Nature)Data[0x14]; + set => Data[0x14] = (byte)value; + } + + public override int IV_HP { get => (sbyte)Data[0x15]; set => Data[0x15] = (byte)value; } + public override int IV_ATK { get => (sbyte)Data[0x16]; set => Data[0x16] = (byte)value; } + public override int IV_DEF { get => (sbyte)Data[0x17]; set => Data[0x17] = (byte)value; } + public override int IV_SPA { get => (sbyte)Data[0x18]; set => Data[0x18] = (byte)value; } + public override int IV_SPD { get => (sbyte)Data[0x19]; set => Data[0x19] = (byte)value; } + public override int IV_SPE { get => (sbyte)Data[0x1A]; set => Data[0x1A] = (byte)value; } + + public override int EV_HP { get => (sbyte)Data[0x1B]; set => Data[0x1B] = (byte)value; } + public override int EV_ATK { get => (sbyte)Data[0x1C]; set => Data[0x1C] = (byte)value; } + public override int EV_DEF { get => (sbyte)Data[0x1D]; set => Data[0x1D] = (byte)value; } + public override int EV_SPA { get => (sbyte)Data[0x1E]; set => Data[0x1E] = (byte)value; } + public override int EV_SPD { get => (sbyte)Data[0x1F]; set => Data[0x1F] = (byte)value; } + public override int EV_SPE { get => (sbyte)Data[0x20]; set => Data[0x20] = (byte)value; } + + public int Aura + { + get => Data[0x25]; + set => Data[0x25] = (byte)value; + } + + public int Allies + { + get => Data[0x27]; + set => Data[0x27] = (byte)value; + } + + public int Ally1 + { + get => Data[0x28]; + set => Data[0x28] = (byte)value; + } + + public int Ally2 + { + get => Data[0x2C]; + set => Data[0x2C] = (byte)value; + } + + public override bool IV3 => (sbyte)Data[0x15] < 0 && (sbyte)Data[0x15] + 1 == -3; + } +} diff --git a/pkNX.Structures/Encounter/EncounterStatic7b.cs b/pkNX.Structures/Encounter/EncounterStatic7b.cs new file mode 100644 index 00000000..62d47b54 --- /dev/null +++ b/pkNX.Structures/Encounter/EncounterStatic7b.cs @@ -0,0 +1,110 @@ +using System; +using System.Runtime.InteropServices; + +namespace pkNX.Structures.Encounter +{ + public sealed class EncounterStatic7b : EncounterStatic + { + private const int SIZE = 0x40; + public EncounterStatic7b(byte[] data) => Data = data ?? new byte[SIZE]; + + public ulong Hash => BitConverter.ToUInt64(Data, 0); + + public override int Species + { + get => BitConverter.ToUInt16(Data, 0x08); + set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x08); + } + + public override int Form { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } + public override int Level { get => Data[0x0B]; set => Data[0x0B] = (byte)value; } + + public override Shiny Shiny // 0 = 0x3..., 1 = 0x2, 2 = 0x1 + { + get => (Shiny)(Data[0x0C] & 3); + set => Data[0x0C] = (byte)((Data[0x0C] & ~3) | ((byte)value & 3)); + } + + public override FixedGender Gender // 0 = Random, 1 = Male, 2 = Female, 3 = Panic + { + get => (FixedGender)(Data[0x0D] & 3); + set => Data[0x0D] = (byte)((Data[0x0D] & ~3) | ((byte)value & 3)); + } + + public override Nature Nature { get => (Nature)Data[0x0E]; set => Data[0x0E] = (byte)value; } // 25 = random (sets the nature rand to 1) + public override int Ability { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } + + public uint[] Ptrs // 0x10-0x1F -- are these text line references? + { + get => new[] + { + BitConverter.ToUInt32(Data, 0x10), + BitConverter.ToUInt32(Data, 0x14), + BitConverter.ToUInt32(Data, 0x18), + BitConverter.ToUInt32(Data, 0x1C), + }; + set { } + } + + public override int[] RelearnMoves // 0x20-0x27 -- these are actually just moves + { + get => new int[] + { + BitConverter.ToUInt16(Data, 0x20), + BitConverter.ToUInt16(Data, 0x22), + BitConverter.ToUInt16(Data, 0x24), + BitConverter.ToUInt16(Data, 0x26), + }; + set + { + if (value?.Length != 4) + return; + for (int i = 0; i < 4; i++) + BitConverter.GetBytes((ushort)value[i]).CopyTo(Data, 0x20 + (i * 2)); + } + } + + public int V1 + { + get => BitConverter.ToInt16(Data, 0x28); + set => BitConverter.GetBytes((short)value).CopyTo(Data, 0x28); + } + + public int V2 + { + get => BitConverter.ToInt16(Data, 0x2A); + set => BitConverter.GetBytes((short)value).CopyTo(Data, 0x2A); + } + + // 0x2C-0x31 + public override int IV_HP { get => (sbyte)Data[0x2C]; set => Data[0x2C] = (byte)value; } + public override int IV_ATK { get => (sbyte)Data[0x2D]; set => Data[0x2D] = (byte)value; } + public override int IV_DEF { get => (sbyte)Data[0x2E]; set => Data[0x2E] = (byte)value; } + public override int IV_SPA { get => (sbyte)Data[0x2F]; set => Data[0x2F] = (byte)value; } + public override int IV_SPD { get => (sbyte)Data[0x30]; set => Data[0x30] = (byte)value; } + public override int IV_SPE { get => (sbyte)Data[0x31]; set => Data[0x31] = (byte)value; } + + // 0x32-0x37 + public override int EV_HP { get => (sbyte)Data[0x32]; set => Data[0x32] = (byte)value; } + public override int EV_ATK { get => (sbyte)Data[0x33]; set => Data[0x33] = (byte)value; } + public override int EV_DEF { get => (sbyte)Data[0x34]; set => Data[0x34] = (byte)value; } + public override int EV_SPA { get => (sbyte)Data[0x35]; set => Data[0x35] = (byte)value; } + public override int EV_SPD { get => (sbyte)Data[0x36]; set => Data[0x36] = (byte)value; } + public override int EV_SPE { get => (sbyte)Data[0x37]; set => Data[0x37] = (byte)value; } + + // Stat Boost levels/flags + public int Boost_ATK { get => (sbyte)Data[0x38]; set => Data[0x38] = (byte)value; } + public int Boost_DEF { get => (sbyte)Data[0x39]; set => Data[0x39] = (byte)value; } + public int Boost_SPA { get => (sbyte)Data[0x3A]; set => Data[0x3A] = (byte)value; } + public int Boost_SPD { get => (sbyte)Data[0x3B]; set => Data[0x3B] = (byte)value; } + public int Boost_SPE { get => (sbyte)Data[0x3C]; set => Data[0x3C] = (byte)value; } + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3)] + public byte[] UnknownFlags; // 0x3D-0x3F + + public string Dump() + { + return $"new EncounterStatic {{ Species = {Species:000}, Level = {Level:00}, Location = -1, Ability = {Ability:0}, Shiny = Shiny.{Shiny} }},"; + } + } +} diff --git a/pkNX.Structures/Encounter/EncounterTrade.cs b/pkNX.Structures/Encounter/EncounterTrade.cs new file mode 100644 index 00000000..680b6a8f --- /dev/null +++ b/pkNX.Structures/Encounter/EncounterTrade.cs @@ -0,0 +1,42 @@ +using System; + +namespace pkNX.Structures +{ + public abstract class EncounterTrade + { + protected byte[] Data { get; set; } + public virtual byte[] Write() => (byte[])Data.Clone(); + + public abstract int Species { get; set; } + public virtual int HeldItem { get; set; } + public abstract int Level { get; set; } + public abstract int Form { get; set; } + public abstract FixedGender Gender { get; set; } + + public virtual Nature Nature { get; set; } = Nature.Random; + public virtual int Ability { get; set; } + public virtual bool ShinyLock { get; set; } + + public virtual bool IV3 { get; set; } + public virtual int[] RelearnMoves { get; set; } = Array.Empty(); + public abstract Shiny Shiny { get; set; } + + public abstract int IV_HP { get; set; } + public abstract int IV_ATK { get; set; } + public abstract int IV_DEF { get; set; } + public abstract int IV_SPE { get; set; } + public abstract int IV_SPA { get; set; } + public abstract int IV_SPD { get; set; } + + public int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set + { + if (value?.Length != 6) return; + IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; + IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; + } + } + } +} diff --git a/pkNX.Structures/Encounter/EncounterTrade7b.cs b/pkNX.Structures/Encounter/EncounterTrade7b.cs new file mode 100644 index 00000000..a675594a --- /dev/null +++ b/pkNX.Structures/Encounter/EncounterTrade7b.cs @@ -0,0 +1,50 @@ +using System; + +namespace pkNX.Structures +{ + public class EncounterTrade7b : EncounterTrade + { + private const int SIZE = 0x58; + public EncounterTrade7b(byte[] data) => Data = data ?? new byte[SIZE]; + + public ulong Hash1 => BitConverter.ToUInt64(Data, 0x00); + public ulong Hash2 => BitConverter.ToUInt64(Data, 0x30); + public ulong Hash3 => BitConverter.ToUInt64(Data, 0x50); + + public override int Species + { + get => BitConverter.ToUInt16(Data, 0x08); + set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x08); + } + + public override int Form { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } + public override int Level { get => Data[0x0C]; set => Data[0x0C] = (byte)value; } // minimum level? + + public override Shiny Shiny // 0 = 0x3..., 1 = 0x2, 2 = 0x1 + { + get => (Shiny)(Data[0x0D] & 3); + set => Data[0x0D] = (byte)((Data[0x0D] & ~3) | ((byte)value & 3)); + } + + public override FixedGender Gender // 0 = Random, 1 = Male, 2 = Female, 3 = Panic + { + get => (FixedGender)(Data[0x0D] & 3); + set => Data[0x0D] = (byte)((Data[0x0D] & ~3) | ((byte)value & 3)); + } + + public int RequiredSpecies + { + get => BitConverter.ToUInt16(Data, 0x42); + set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x42); + } + + public int RequiredForm { get => Data[0x44]; set => Data[0x44] = (byte)value; } + + public override int IV_HP { get => (sbyte)Data[0x46]; set => Data[0x46] = (byte)value; } + public override int IV_ATK { get => (sbyte)Data[0x47]; set => Data[0x47] = (byte)value; } + public override int IV_DEF { get => (sbyte)Data[0x48]; set => Data[0x48] = (byte)value; } + public override int IV_SPA { get => (sbyte)Data[0x49]; set => Data[0x49] = (byte)value; } + public override int IV_SPD { get => (sbyte)Data[0x4A]; set => Data[0x4A] = (byte)value; } + public override int IV_SPE { get => (sbyte)Data[0x4B]; set => Data[0x4B] = (byte)value; } + } +} diff --git a/pkNX.Structures/Encounter/FixedGender.cs b/pkNX.Structures/Encounter/FixedGender.cs new file mode 100644 index 00000000..63f757f4 --- /dev/null +++ b/pkNX.Structures/Encounter/FixedGender.cs @@ -0,0 +1,11 @@ +namespace pkNX.Structures +{ + public enum FixedGender : byte + { + Random = 0, + Male = 1, + Female = 2, + + Genderless = Random, + } +} \ No newline at end of file diff --git a/pkNX.Structures/Encounter/Nature.cs b/pkNX.Structures/Encounter/Nature.cs new file mode 100644 index 00000000..31e0a3b2 --- /dev/null +++ b/pkNX.Structures/Encounter/Nature.cs @@ -0,0 +1,39 @@ +namespace pkNX.Structures +{ + public enum Nature : sbyte + { + Random = -1, + + Hardy, + Lonely, + Brave, + Adamant, + Naughty, + Bold, + + Docile, + Relaxed, + Impish, + Lax, + Timid, + Hasty, + + Serious, + Jolly, + Naive, + Modest, + Mild, + Quiet, + + Bashful, + Rash, + Calm, + Gentle, + Sassy, + Careful, + + Quirky, + + Random25 = 25, + } +} diff --git a/pkNX.Structures/Encounter/Shiny.cs b/pkNX.Structures/Encounter/Shiny.cs new file mode 100644 index 00000000..1245a7e1 --- /dev/null +++ b/pkNX.Structures/Encounter/Shiny.cs @@ -0,0 +1,20 @@ +namespace pkNX.Structures +{ + public enum Shiny : byte + { + /// + /// PID is purely random; can be shiny or not shiny. + /// + Random = 0, + + /// + /// PID is randomly created and forced to be shiny. + /// + Always = 1, + + /// + /// PID is randomly created and forced to be not shiny. + /// + Never = 2, + } +} \ No newline at end of file diff --git a/pkNX.Structures/Evolution/EvolutionMethod.cs b/pkNX.Structures/Evolution/EvolutionMethod.cs new file mode 100644 index 00000000..1139406c --- /dev/null +++ b/pkNX.Structures/Evolution/EvolutionMethod.cs @@ -0,0 +1,28 @@ +namespace pkNX.Structures +{ + /// + /// Criteria for evolving to another species. + /// + public class EvolutionMethod + { + public EvolutionType Method; + public int Species; + public int Argument; + public int Form = -1; + public int Level; + + public EvolutionMethod Copy(int species = -1) + { + if (species < 0) + species = Species; + return new EvolutionMethod + { + Method = Method, + Species = species, + Argument = Argument, + Form = Form, + Level = Level + }; + } + } +} \ No newline at end of file diff --git a/pkNX.Structures/Evolution/EvolutionSet.cs b/pkNX.Structures/Evolution/EvolutionSet.cs new file mode 100644 index 00000000..0290df3d --- /dev/null +++ b/pkNX.Structures/Evolution/EvolutionSet.cs @@ -0,0 +1,11 @@ +namespace pkNX.Structures +{ + /// + /// Table of Evolution Branch Entries + /// + public abstract class EvolutionSet + { + public EvolutionMethod[] PossibleEvolutions; + public abstract byte[] Write(); + } +} diff --git a/pkNX.Structures/Evolution/EvolutionSet6.cs b/pkNX.Structures/Evolution/EvolutionSet6.cs new file mode 100644 index 00000000..42f7bf47 --- /dev/null +++ b/pkNX.Structures/Evolution/EvolutionSet6.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace pkNX.Structures +{ + /// + /// Generation 6 Evolution Branch Entries + /// + public class EvolutionSet6 : EvolutionSet + { + private const int ENTRY_SIZE = 6; + private const int ENTRY_COUNT = 8; + public const int SIZE = ENTRY_COUNT * ENTRY_SIZE; + private static readonly HashSet argEvos = new HashSet { 6, 8, 16, 17, 18, 19, 20, 21, 22, 29, 30, 32, 33, 34 }; + + public EvolutionSet6(byte[] data) + { + if (data.Length != SIZE) + return; + PossibleEvolutions = data.GetArray(GetEvo, SIZE); + } + + private static EvolutionMethod GetEvo(byte[] data, int offset) + { + var evo = new EvolutionMethod + { + Method = (EvolutionType)BitConverter.ToUInt16(data, offset + 0), + Argument = BitConverter.ToUInt16(data, offset + 2), + Species = BitConverter.ToUInt16(data, offset + 4), + // Copy + Level = BitConverter.ToUInt16(data, offset + 2), + }; + + // Argument is used by both Level argument and Item/Move/etc. Clear if appropriate. + if (argEvos.Contains((int)evo.Method)) + evo.Level = 0; + return evo; + } + + public override byte[] Write() + { + using (MemoryStream ms = new MemoryStream()) + using (BinaryWriter bw = new BinaryWriter(ms)) + { + foreach (EvolutionMethod evo in PossibleEvolutions) + { + bw.Write((ushort)evo.Method); + bw.Write((ushort)evo.Argument); + bw.Write((ushort)evo.Species); + } + return ms.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/pkNX.Structures/Evolution/EvolutionSet7.cs b/pkNX.Structures/Evolution/EvolutionSet7.cs new file mode 100644 index 00000000..940d86c4 --- /dev/null +++ b/pkNX.Structures/Evolution/EvolutionSet7.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; + +namespace pkNX.Structures +{ + public class EvolutionSet7 : EvolutionSet + { + private const int ENTRY_SIZE = 8; + private const int ENTRY_COUNT = 8; + public const int SIZE = ENTRY_COUNT * ENTRY_SIZE; + + public EvolutionSet7(byte[] data) + { + if (data.Length != SIZE) + return; + PossibleEvolutions = data.GetArray(GetEvo, SIZE); + } + + private static EvolutionMethod GetEvo(byte[] data, int offset) + { + return new EvolutionMethod + { + Method = (EvolutionType)BitConverter.ToUInt16(data, offset + 0), + Argument = BitConverter.ToUInt16(data, offset + 2), + Species = BitConverter.ToUInt16(data, offset + 4), + Form = (sbyte)data[offset + 6], + Level = data[offset + 7], + }; + } + + public override byte[] Write() + { + using (MemoryStream ms = new MemoryStream()) + using (BinaryWriter bw = new BinaryWriter(ms)) + { + foreach (EvolutionMethod evo in PossibleEvolutions) + { + bw.Write((ushort)evo.Method); + bw.Write((ushort)evo.Argument); + bw.Write((ushort)evo.Species); + bw.Write((sbyte)evo.Form); + bw.Write((byte)evo.Level); + } + return ms.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/pkNX.Structures/Evolution/EvolutionType.cs b/pkNX.Structures/Evolution/EvolutionType.cs new file mode 100644 index 00000000..e72f6590 --- /dev/null +++ b/pkNX.Structures/Evolution/EvolutionType.cs @@ -0,0 +1,46 @@ +namespace pkNX.Structures +{ + public enum EvolutionType : byte + { + None, + LevelUpFriendship, + LevelUpFriendshipMorning, + LevelUpFriendshipNight, + LevelUp, + Trade, + TradeHeldItem, + TradeSpecies, + UseItem, + LevelUpATK, + LevelUpDEF, + LevelUpAeqD, + LevelUpECl5, + LevelUpECgeq5, + LevelUpNinjask, + LevelUpShedinja, + LevelUpBauty, + UseItemMale, + UseItemFemale, + LevelUpHeldItemDay, + LevelUpHeldItemNight, + LevelUpKnowMove, + LevelUpWithTeammate, + LevelUpMale, + LevelUpFemale, + LevelUpElectric, + LevelUpForest, + LevelUpCold, + LevelUpInverted, + LevelUpAffection50MoveType, + LevelUpFairy, + LevelUpWeather, + LevelUpMorning, + LevelUpNight, + LevelUpFormFemale1, + UNUSED, + LevelUpVersion, + LevelUpVersionDay, + LevelUpVersionNight, + LevelUpSummit, + } +} diff --git a/pkNX.Structures/Evolution/SeedPokeTable.cs b/pkNX.Structures/Evolution/SeedPokeTable.cs new file mode 100644 index 00000000..c4c9160f --- /dev/null +++ b/pkNX.Structures/Evolution/SeedPokeTable.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace pkNX.Structures +{ + public sealed class SeedPokeTable + { + private readonly ushort[] Table; + + public SeedPokeTable(byte[] data) + { + Table = new ushort[data.Length/2]; + for (int i = 0; i < Table.Length; i++) + Table[i] = BitConverter.ToUInt16(data, i * 2); + } + + public ushort this[int index] => Table[index]; + + public byte[] Write() + { + using (var ms = new MemoryStream()) + using (var bw = new BinaryWriter(ms)) + { + foreach (var seed in Table) + bw.Write(seed); + return ms.ToArray(); + } + } + + public IEnumerable Dump(string[] specNames) + { + return Table.Select((t, i) => $"{i:000}\t{specNames[i]}\t{specNames[t]}"); + } + } +} diff --git a/pkNX.Structures/Evolution/ZukanEvolutionTable.cs b/pkNX.Structures/Evolution/ZukanEvolutionTable.cs new file mode 100644 index 00000000..fa609f03 --- /dev/null +++ b/pkNX.Structures/Evolution/ZukanEvolutionTable.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using System.Linq; + +namespace pkNX.Structures +{ + public sealed class ZukanEvolutionTable + { + private const int SIZE = 0x14; + private readonly ushort[][] Table; + + public ZukanEvolutionTable(byte[] data) + { + if (data.Length % SIZE != 0) + throw new ArgumentException(nameof(data) + " length should be a multiple of " + SIZE); + + Table = new ushort[data.Length / SIZE][]; + for (int i = 0; i < Table.Length; i++) + { + var evos = new ushort[(SIZE / 2) - 1]; + for (int j = 0; j < evos.Length; j++) + evos[i] = BitConverter.ToUInt16(data, (i * SIZE) + (j * 2)); + } + } + + public byte[] Write() + { + using (var ms = new MemoryStream()) + using (var bw = new BinaryWriter(ms)) + { + foreach (var t in Table) + { + foreach (var e in t) + bw.Write(e); + bw.Write(t.Count(x => x != 0)); + } + return ms.ToArray(); + } + } + } +} diff --git a/pkNX.Structures/GameUtil.cs b/pkNX.Structures/GameUtil.cs new file mode 100644 index 00000000..d47289dc --- /dev/null +++ b/pkNX.Structures/GameUtil.cs @@ -0,0 +1,253 @@ +using System; +using System.Linq; +using static pkNX.Structures.GameVersion; + +namespace pkNX.Structures +{ + /// + /// Utility class for logic. + /// + public static class GameUtil + { + /// + /// List of possible values that are stored in PKM data. + /// + /// Ordered roughly by most recent games first. + public static readonly GameVersion[] GameVersions = ((GameVersion[])Enum.GetValues(typeof(GameVersion))).Where(z => z < RB && z > 0).Reverse().ToArray(); + + /// + /// Indicates if the value is a value used by the games or is an aggregate indicator. + /// + /// Game to check + public static bool IsValidSavedVersion(this GameVersion game) => 0 < game && game <= RB; + + /// Determines the Version Grouping of an input Version ID + /// Version of which to determine the group + /// Version Group Identifier or Invalid if type cannot be determined. + public static GameVersion GetMetLocationVersionGroup(GameVersion Version) + { + switch (Version) + { + // Sidegame + case CXD: + return CXD; + case GO: + return GO; + + // Gen1 + case RBY: + case RD: + case BU: + case YW: + case GN: + return RBY; + + // Gen2 + case GS: + case GD: + case SV: + case C: + return GSC; + + // Gen3 + case R: + case S: + return RS; + case E: + return E; + case FR: + case LG: + return FR; + + // Gen4 + case D: + case P: + return DP; + case Pt: + return Pt; + case HG: + case SS: + return HGSS; + + // Gen5 + case B: + case W: + return BW; + case B2: + case W2: + return B2W2; + + // Gen6 + case X: + case Y: + return XY; + case OR: + case AS: + return ORAS; + + // Gen7 + case SN: + case MN: + return SM; + case US: + case UM: + return USUM; + case GP: + case GE: + return GG; + + default: + return Invalid; + } + } + + /// + /// Gets a Version ID from the end of that Generation + /// + /// Generation ID + /// Version ID from requested generation. If none, return . + public static GameVersion GetVersion(int generation) + { + switch (generation) + { + case 1: return RBY; + case 2: return C; + case 3: return E; + case 4: return SS; + case 5: return W2; + case 6: return AS; + case 7: return UM; + default: + return Invalid; + } + } + + /// + /// Gets the Generation the belongs to. + /// + /// Game to retrieve the generation for + /// Generation ID + public static int GetGeneration(this GameVersion game) + { + if (Gen1.Contains(game)) return 1; + if (Gen2.Contains(game)) return 2; + if (Gen3.Contains(game)) return 3; + if (Gen4.Contains(game)) return 4; + if (Gen5.Contains(game)) return 5; + if (Gen6.Contains(game)) return 6; + if (Gen7.Contains(game)) return 7; + return -1; + } + + /// + /// Gets the Generation the belongs to. + /// + /// Game to retrieve the generation for + /// Generation ID + public static int GetMaxSpeciesID(this GameVersion game) + { + if (Gen1.Contains(game)) return 151; + if (Gen2.Contains(game)) return 251; + if (Gen3.Contains(game)) return 384; + if (Gen4.Contains(game)) return 493; + if (Gen5.Contains(game)) return 649; + if (Gen6.Contains(game)) return Legal.MaxSpeciesID_6; + if (Gen7.Contains(game)) + { + if (SM.Contains(game)) + return 802; + if (USUM.Contains(game)) + return 807; + return Legal.MaxSpeciesID_7_GG; + } + return -1; + } + + /// + /// Checks if the version (or subset versions) is equivalent to . + /// + /// Version (set) + /// Individual version + public static bool Contains(this GameVersion g1, int g2) => g1.Contains((GameVersion)g2); + + /// + /// Checks if the version (or subset versions) is equivalent to . + /// + /// Version (set) + /// Individual version + public static bool Contains(this GameVersion g1, GameVersion g2) + { + if (g1 == g2 || g1 == Any) + return true; + + switch (g1) + { + case RB: + return g2 == RD || g2 == BU || g2 == GN; + case Stadium: + case EventsGBGen1: + case VCEvents: + case RBY: + return RB.Contains(g2) || g2 == YW; + case Gen1: + return RBY.Contains(g2) || g2 == Stadium || g2 == EventsGBGen1 || g2 == VCEvents; + + case GS: return g2 == GD || g2 == SV; + case Stadium2: + case EventsGBGen2: + case GSC: + return GS.Contains(g2) || g2 == C; + case Gen2: + return GSC.Contains(g2) || g2 == Stadium2 || g2 == EventsGBGen2; + case GBCartEraOnly: + return g2 == Stadium || g2 == Stadium2 || g2 == EventsGBGen1 || g2 == EventsGBGen2; + + case RS: return g2 == R || g2 == S; + case RSE: + return RS.Contains(g2) || g2 == E; + case FRLG: return g2 == FR || g2 == LG; + case COLO: + case XD: return g2 == CXD; + case CXD: return g2 == COLO || g2 == XD; + case RSBOX: return RS.Contains(g2) || g2 == E || FRLG.Contains(g2); + case Gen3: + return RSE.Contains(g2) || FRLG.Contains(g2) || CXD.Contains(g2) || g2 == RSBOX; + + case DP: return g2 == D || g2 == P; + case HGSS: return g2 == HG || g2 == SS; + case DPPt: + return DP.Contains(g2) || g2 == Pt; + case BATREV: return DP.Contains(g2) || g2 == Pt || HGSS.Contains(g2); + case Gen4: + return DPPt.Contains(g2) || HGSS.Contains(g2) || g2 == BATREV; + + case BW: return g2 == B || g2 == W; + case B2W2: return g2 == B2 || g2 == W2; + case Gen5: + return BW.Contains(g2) || B2W2.Contains(g2); + + case XY: return g2 == X || g2 == Y; + case ORAS: return g2 == OR || g2 == AS; + case Gen6: + return XY.Contains(g2) || ORAS.Contains(g2); + + case SM: + return g2 == SN || g2 == MN; + case USUM: + return g2 == US || g2 == UM; + case GG: + return g2 == GP || g2 == GE; + case Gen7: + return SM.Contains(g2) || USUM.Contains(g2) || GG.Contains(g2); + + default: return false; + } + } + + /// + /// List of possible values within the provided . + /// + /// Generation to look within + public static GameVersion[] GetVersionsInGeneration(int generation) => GameVersions.Where(z => z.GetGeneration() == generation).ToArray(); + } +} diff --git a/pkNX.Structures/GameVersion.cs b/pkNX.Structures/GameVersion.cs new file mode 100644 index 00000000..c6f30943 --- /dev/null +++ b/pkNX.Structures/GameVersion.cs @@ -0,0 +1,456 @@ +namespace pkNX.Structures +{ + /// + /// Game Version ID enum shared between actual Version IDs and lumped version groupings. + /// + public enum GameVersion + { + #region Indicators for method empty arguments & result indication. Not stored values. + Invalid = -2, + Any = -1, + Unknown = 0, + #endregion + + // The following values are IDs stored within PKM data, and can also identify individual games. + + #region Gen3 + /// + /// Pokémon Sapphire (GBA) + /// + S = 1, + + /// + /// Pokémon Ruby (GBA) + /// + R = 2, + + /// + /// Pokémon Emerald (GBA) + /// + E = 3, + + /// + /// Pokémon FireRed (GBA) + /// + FR = 4, + + /// + /// Pokémon LeafGreen (GBA) + /// + LG = 5, + + /// + /// Pokémon Colosseum & Pokémon XD (GameCube) + /// + CXD = 15, + #endregion + + #region Gen4 + /// + /// Pokémon Diamond (NDS) + /// + D = 10, + + /// + /// Pokémon Pearl (NDS) + /// + P = 11, + + /// + /// Pokémon Platinum (NDS) + /// + Pt = 12, + + /// + /// Pokémon Heart Gold (NDS) + /// + HG = 7, + + /// + /// Pokémon Soul Silver (NDS) + /// + SS = 8, + #endregion + + #region Gen5 + /// + /// Pokémon White (NDS) + /// + W = 20, + + /// + /// Pokémon Black (NDS) + /// + B = 21, + + /// + /// Pokémon White 2 (NDS) + /// + W2 = 22, + + /// + /// Pokémon Black 2 (NDS) + /// + B2 = 23, + #endregion + + #region Gen6 + /// + /// Pokémon X (3DS) + /// + X = 24, + + /// + /// Pokémon Y (3DS) + /// + Y = 25, + + /// + /// Pokémon Alpha Sapphire (3DS) + /// + AS = 26, + + /// + /// Pokémon Omega Ruby (3DS) + /// + OR = 27, + #endregion + + #region Gen7 + /// + /// Pokémon Sun (3DS) + /// + SN = 30, + + /// + /// Pokémon Moon (3DS) + /// + MN = 31, + + /// + /// Pokémon Ultra Sun (3DS) + /// + US = 32, + + /// + /// Pokémon Ultra Moon (3DS) + /// + UM = 33, + #endregion + + /// + /// Pokémon Pokémon GO (Unused) + /// + GO = 34, + + #region Virtual Console (3DS) Gen1 + /// + /// Pokémon Red (3DS Virtual Console) + /// + RD = 35, + + /// + /// Pokémon Green[JP]/Blue[INT] (3DS Virtual Console) + /// + GN = 36, + + /// + /// Pokémon Blue[JP] (3DS Virtual Console) + /// + BU = 37, + + /// + /// Pokémon Yellow [JP] (3DS Virtual Console) + /// + YW = 38, + #endregion + + #region Virtual Console (3DS) Gen2 + /// + /// Pokémon Gold (3DS Virtual Console) + /// + GD = 39, + + /// + /// Pokémon Silver (3DS Virtual Console) + /// + SV = 40, + + /// + /// Pokémon Crystal (3DS Virtual Console) + /// + C = 41, + #endregion + + #region Nintendo Switch + /// + /// Pokémon Let's Go Pikachu (NX) + /// + GP = 42, + + /// + /// Pokémon Let's Go Eevee (NX) + /// + GE = 43, + #endregion + + // The following values are not actually stored values in pkm data, + // These values are assigned within PKHeX as properties for various logic branching. + + #region Game Groupings (SaveFile type, roughly) + /// + /// Pokémon Red & Blue identifier. + /// + /// + /// + /// + RB, + + /// + /// Pokémon Red/Blue/Yello identifier. + /// + /// + /// + /// + /// + RBY, + + /// + /// Pokémon Gold & Silver identifier. + /// + /// + /// + GS, + + /// + /// Pokémon Gold/Silver/Crystal identifier. + /// + /// + /// + /// + GSC, + + /// + /// Pokémon Ruby & Sapphire identifier. + /// + /// + /// + RS, + + /// + /// Pokémon Ruby/Sapphire/Emerald identifier. + /// + /// + /// + /// + RSE, + + /// + /// Pokémon FireRed/LeafGreen identifier. + /// + /// + /// + FRLG, + + /// + /// Pokémon Box Ruby & Sapphire identifier. + /// + RSBOX, + + /// + /// Pokémon Colosseum identifier. + /// + /// + /// Also used to mark Colosseum-only origin data as this game shares a version ID with + COLO, + + /// + /// Pokémon XD identifier. + /// + /// + /// Also used to mark XD-only origin data as this game shares a version ID with + XD, + + /// + /// Pokémon Diamond & Pearl identifier. + /// + /// + /// + DP, + + /// + /// Pokémon Diamond/Pearl/Platinum version group. + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + /// + DPPt, + + /// + /// Pokémon Heart Gold & Soul Silver identifier. + /// + /// + /// + HGSS, + + /// + /// Pokémon Battle Revolution identifier. + /// + BATREV, + + /// + /// Pokémon Black & White version group. + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + BW, + + /// + /// Pokémon Black 2 & White 2 version group. + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + B2W2, + + /// + /// Pokémon X & Y + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + XY, + + /// + /// Pokémon Omega Ruby & Alpha Sapphire Demo identifier. + /// + /// + ORASDEMO, + + /// + /// Pokémon Omega Ruby & Alpha Sapphire version group. + /// + /// + /// + /// Used to lump data from the associated games as data assets are shared. + ORAS, + + /// + /// Pokémon Sun & Moon + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + SM, + + /// + /// Pokémon Sun & Moon Demo + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + SMDEMO, + + /// + /// Pokémon Ultra Sun & Ultra Moon + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + USUM, + + /// + /// Pokémon Let's Go (TBD) + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + GG, + + /// + /// Generation 1 Games + /// + /// + Gen1, + + /// + /// Generation 2 Games + /// + /// + Gen2, + + /// + /// Generation 3 Games + /// + /// + /// + Gen3, + + /// + /// Generation 4 Games + /// + /// + /// + Gen4, + + /// + /// Generation 5 Games + /// + /// + /// + Gen5, + + /// + /// Generation 6 Games + /// + /// + /// + Gen6, + + /// + /// Generation 7 Games + /// + /// + /// + Gen7, + + /// + /// Generation 1/2 Game Boy Cartridge Era Only + /// + /// + /// Since the original run of and could not transfer to future games, + /// any special encounters (event data) can only be allowed if the savedata originated from that era. + /// + GBCartEraOnly, + + /// + /// Pokémon Stadium data origin identifier + /// + /// + Stadium, + + /// + /// Pokémon Stadium 2 data origin identifier + /// + /// + Stadium2, + + /// + /// Generation 1 Game Boy Cartridge Era Only data origin identifier + /// + EventsGBGen1, + + /// + /// Generation 2 Game Boy Cartridge Era Only data origin identifier + /// + EventsGBGen2, + + /// + /// Generation 1/2 3DS Virtual Console data origin identifier + /// + VCEvents, + #endregion + } +} diff --git a/pkNX.Structures/Item/BattlePocket.cs b/pkNX.Structures/Item/BattlePocket.cs new file mode 100644 index 00000000..6cba8c27 --- /dev/null +++ b/pkNX.Structures/Item/BattlePocket.cs @@ -0,0 +1,14 @@ +using System; + +namespace pkNX.Structures +{ + [Flags] + public enum BattlePocket : byte + { + None, + Ball = 1 << 0, + Boosts = 1 << 1, + Restore = 1 << 2, + Misc = 1 << 3, + } +} \ No newline at end of file diff --git a/pkNX.Structures/Item/BattleStatusFlags.cs b/pkNX.Structures/Item/BattleStatusFlags.cs new file mode 100644 index 00000000..c68d477e --- /dev/null +++ b/pkNX.Structures/Item/BattleStatusFlags.cs @@ -0,0 +1,50 @@ +using System; + +namespace pkNX.Structures +{ + [Flags] + public enum BattleStatusFlags : byte + { + None, + + /// + /// Sleep + /// + SLP = 1 << 0, + + /// + /// Poison + /// + PSN = 1 << 1, + + /// + /// Burn + /// + BRN = 1 << 2, + + /// + /// Freeze + /// + FRZ = 1 << 3, + + /// + /// Paralysis + /// + PAR = 1 << 4, + + /// + /// Confusion + /// + CFZ = 1 << 5, + + /// + /// Infatuation + /// + INF = 1 << 6, + + /// + /// Guard Spec. + /// + GSP = 1 << 7, + } +} diff --git a/pkNX.Structures/Item/Item.cs b/pkNX.Structures/Item/Item.cs new file mode 100644 index 00000000..55106d5f --- /dev/null +++ b/pkNX.Structures/Item/Item.cs @@ -0,0 +1,132 @@ +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace pkNX.Structures +{ + [StructLayout(LayoutKind.Sequential)] + public struct Item + { + public Item(byte[] data) => this = data.ToStructure(); + public byte[] Write() => this.ToBytes(); + private const string Battle = "Battle"; + private const string Field = "Field"; + private const string Mart = "Mart"; + private const string Heal = "Heal"; + + #region Structure + private ushort Price; + + [Category(Battle)] + public byte HeldEffect { get; set; } + + public byte HeldArgument { get; set; } + public byte NaturalGiftEffect { get; set; } + public byte FlingEffect { get; set; } + public byte FlingPower { get; set; } + public byte NaturalGiftPower { get; set; } + public ushort Packed { get; set; } + + [Category(Field), Description("Routine # to call when used; 0=unusable.")] + public byte EffectField { get; set; } + + [Category(Battle), Description("Routine # to call when used; 0=unusable.")] + public byte EffectBattle { get; set; } // Battle Type + + public byte _0xC { get; set; } // 0 or 1 + public byte _0xD { get; set; } // Classification (0-3 Battle, 4 Balls, 5 Mail) + public byte Consumable { get; set; } + public byte SortIndex { get; set; } + public BattleStatusFlags CureInflict { get; set; } // Bitflags + private byte Boost0; // Revive 1, Sacred Ash 3, Rare Candy 5, EvoStone 8, upper4 for BoostAtk + private byte Boost1; // DEF, SPA + private byte Boost2; // SPD, SPE + private byte Boost3; // ACC, CRIT PPUpFlags + public ItemFlags1 FunctionFlags0 { get; set; } + public ItemFlags2 FunctionFlags1 { get; set; } + + [Category(Field), Description("Adds EVs to the HP stat.")] + public sbyte EVHP { get; set; } + + [Category(Field), Description("Adds EVs to the Attack stat.")] + public sbyte EVATK { get; set; } + + [Category(Field), Description("Adds EVs to the Defense stat.")] + public sbyte EVDEF { get; set; } + + [Category(Field), Description("Adds EVs to the Speed stat.")] + public sbyte EVSPE { get; set; } + + [Category(Field), Description("Adds EVs to the Sp. Attack stat.")] + public sbyte EVSPA { get; set; } + + [Category(Field), Description("Adds EVs to the Sp. Defense stat.")] + public sbyte EVSPD { get; set; } + + [Category(Heal), Description("Determines the healing percent, or if a flat value is used."), RefreshProperties(RefreshProperties.All)] + public Heal HealAmount { get; set; } + + [Category(Field), Description("PP to be added to the move's current PP if used.")] + public byte PPGain { get; set; } + + public sbyte Friendship1 { get; set; } + public sbyte Friendship2 { get; set; } + public sbyte Friendship3 { get; set; } + public byte _0x23, _0x24; + #endregion + + [Category(Mart), RefreshProperties(RefreshProperties.All)] + public int BuyPrice { get => Price * 10; set => Price = (ushort)(value / 10); } + + [Category(Mart), ReadOnly(true)] + public int SellPrice { get => Price * 5; set => Price = (ushort)(value / 5); } + + [Category(Battle)] + public int NaturalGiftType { get => Packed & 0x1F; set => Packed = (ushort)((NaturalGiftEffect & ~0x1F) | value); } + + [Category(Battle)] + public bool Flag1 { get => ((Packed >> 5) & 1) == 1; set => Packed = (ushort)((Packed & ~(1 << 5)) | ((value ? 1 : 0) << 5)); } + + [Category(Battle)] + public bool Flag2 { get => ((Packed >> 6) & 1) == 1; set => Packed = (ushort)((Packed & ~(1 << 6)) | ((value ? 1 : 0) << 6)); } + + [Category(Field)] + public int PocketField { get => (Packed >> 7) & 0xF; set => Packed = (ushort)((Packed & 0xF87F) | ((value & 0xF) << 7)); } + + [Category(Battle)] + public BattlePocket PocketBattle { get => (BattlePocket)(Packed >> 11); set => Packed = (ushort)((Packed & 0x077F) | (((byte)value & 0x1F) << 11)); } + + [Category(Battle)] + public int FieldEffect { get => Boost0 & 0xF; set => Boost0 = (byte)((Boost0 & ~0xF) | (value & 0xF)); } + + [Category(Battle)] + public int BoostATK { get => Boost0 >> 4; set => Boost0 = (byte)((Boost0 & 0xF) | (value << 4)); } + + [Category(Battle)] + public int BoostDEF { get => Boost1 & 0xF; set => Boost1 = (byte)((Boost1 & ~0xF) | (value & 0xF)); } + + [Category(Battle)] + public int BoostSPA { get => Boost1 >> 4; set => Boost1 = (byte)((Boost1 & 0xF) | (value << 4)); } + + [Category(Battle)] + public int BoostSPD { get => Boost2 & 0xF; set => Boost2 = (byte)((Boost2 & ~0xF) | (value & 0xF)); } + + [Category(Battle)] + public int BoostSPE { get => Boost2 >> 4; set => Boost2 = (byte)((Boost2 & 0xF) | (value << 4)); } + + [Category(Battle)] + public int BoostACC { get => Boost0 & 0xF; set => Boost0 = (byte)((Boost3 & ~0xF) | (value & 0xF)); } + + [Category(Battle)] + public int BoostCRIT { get => (Boost3 >> 4) & 3; set => Boost3 = (byte)((Boost3 & ~0x30) | ((value & 3) << 4)); } + + [Category(Battle), Description("1 = One PP Up, 3 = PP Max")] + public int BoostPP { get => (Boost3 >> 6) & 3; set => Boost3 = (byte)((Boost3 & 0x3F) | ((value & 3) << 6)); } + + [Category(Heal), Description("Raw value of the Heal enum."), RefreshProperties(RefreshProperties.All)] + public int HealValue + { + get => (int)HealAmount; + set => HealAmount = (Heal)value; + } + } +} diff --git a/pkNX.Structures/Item/ItemFlags1.cs b/pkNX.Structures/Item/ItemFlags1.cs new file mode 100644 index 00000000..ccef4331 --- /dev/null +++ b/pkNX.Structures/Item/ItemFlags1.cs @@ -0,0 +1,18 @@ +using System; + +namespace pkNX.Structures +{ + [Flags] + public enum ItemFlags1 : byte + { + None, + RestorePP = 1 << 0, + RestorePPAll = 1 << 1, + RestoreHP = 1 << 2, + AddEVHP = 1 << 3, + AddEVAtk = 1 << 4, + AddEVDef = 1 << 5, + AddEVSpe = 1 << 6, + AddEVSpA = 1 << 7, + } +} \ No newline at end of file diff --git a/pkNX.Structures/Item/ItemFlags2.cs b/pkNX.Structures/Item/ItemFlags2.cs new file mode 100644 index 00000000..8afb6955 --- /dev/null +++ b/pkNX.Structures/Item/ItemFlags2.cs @@ -0,0 +1,18 @@ +using System; + +namespace pkNX.Structures +{ + [Flags] + public enum ItemFlags2 : byte + { + None, + AddEVSpD = 1 << 0, + AddEVAbove100 = 1 << 1, + AddFriendship1 = 1 << 2, + AddFriendship2 = 1 << 3, + AddFriendship3 = 1 << 4, + Unused1 = 1 << 5, + Unused2 = 1 << 6, + Unused3 = 1 << 7, + } +} \ No newline at end of file diff --git a/pkNX.Structures/Learnset/Learnset.cs b/pkNX.Structures/Learnset/Learnset.cs new file mode 100644 index 00000000..a4d12762 --- /dev/null +++ b/pkNX.Structures/Learnset/Learnset.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; + +namespace pkNX.Structures +{ + public abstract class Learnset + { + public int Count { get; protected set; } + public int[] Moves { get; protected set; } + public int[] Levels { get; protected set; } + + public abstract byte[] Write(); + + /// + /// Returns the moves a Pokémon can learn between the specified level range. + /// + /// Maximum level + /// Minimum level + /// Array of Move IDs + public int[] GetMoves(int maxLevel, int minLevel = 0) + { + if (minLevel <= 1 && maxLevel >= 100) + return Moves; + if (minLevel > maxLevel) + return Array.Empty(); + int start = Array.FindIndex(Levels, z => z >= minLevel); + if (start < 0) + return Array.Empty(); + int end = Array.FindLastIndex(Levels, z => z <= maxLevel); + if (end < 0) + return Array.Empty(); + int[] result = new int[end - start + 1]; + Array.Copy(Moves, start, result, 0, result.Length); + return result; + } + + /// Returns the moves a Pokémon would have if it were encountered at the specified level. + /// In Generation 1, it is not possible to learn any moves lower than these encounter moves. + /// The level the Pokémon was encountered at. + /// Array of Move IDs + public int[] GetEncounterMoves(int level) + { + const int count = 4; + IList moves = new int[count]; + int ctr = 0; + for (int i = 0; i < Moves.Length; i++) + { + if (Levels[i] > level) + break; + int move = Moves[i]; + if (moves.Contains(move)) + continue; + + moves[ctr++] = move; + ctr &= 3; + } + return (int[])moves; + } + + /// Returns the index of the lowest level move if the Pokémon were encountered at the specified level. + /// Helps determine the minimum level an encounter can be at. + /// The level the Pokémon was encountered at. + /// Array of Move IDs + public int GetMinMoveLevel(int level) + { + if (Levels.Length == 0) + return 1; + + int end = Array.FindLastIndex(Levels, z => z <= level); + return Math.Max(end - 4, 1); + } + + /// Returns the level that a Pokémon can learn the specified move. + /// Move ID + /// Level the move is learned at. If the result is below 0, it cannot be learned by levelup. + public int GetLevelLearnMove(int move) + { + int index = Array.IndexOf(Moves, move); + return index < 0 ? index : Levels[index]; + } + } +} diff --git a/pkNX.Structures/Learnset/Learnset6.cs b/pkNX.Structures/Learnset/Learnset6.cs new file mode 100644 index 00000000..c010f5b7 --- /dev/null +++ b/pkNX.Structures/Learnset/Learnset6.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; + +namespace pkNX.Structures +{ + public class Learnset6 : Learnset + { + public Learnset6(byte[] data) + { + if (data.Length < 4 || data.Length % 4 != 0) + { Count = 0; Levels = Moves = Array.Empty(); return; } + Count = (data.Length / 4) - 1; + Moves = new int[Count]; + Levels = new int[Count]; + using (var ms = new MemoryStream(data)) + using (var br = new BinaryReader(ms)) + { + for (int i = 0; i < Count; i++) + { + Moves[i] = br.ReadInt16(); + Levels[i] = br.ReadInt16(); + } + } + } + + public override byte[] Write() + { + Count = (ushort)Moves.Length; + using (MemoryStream ms = new MemoryStream()) + using (BinaryWriter bw = new BinaryWriter(ms)) + { + for (int i = 0; i < Count; i++) + { + bw.Write((short)Moves[i]); + bw.Write((short)Levels[i]); + } + bw.Write(-1); + return ms.ToArray(); + } + } + + public static Learnset[] GetArray(byte[][] entries) + { + Learnset[] data = new Learnset[entries.Length]; + for (int i = 0; i < data.Length; i++) + data[i] = new Learnset6(entries[i]); + return data; + } + } +} \ No newline at end of file diff --git a/pkNX.Structures/Legality/Encounters.cs b/pkNX.Structures/Legality/Encounters.cs new file mode 100644 index 00000000..a68455b9 --- /dev/null +++ b/pkNX.Structures/Legality/Encounters.cs @@ -0,0 +1,19 @@ +using System.Linq; + +namespace pkNX.Structures +{ + public static partial class Legal + { + public static readonly int[] BasicStarters_6 = + { + 001, 004, 007, 010, 013, 016, 029, 032, 041, 043, 060, 063, 066, 069, 074, 081, 092, 111, 116, 137, 147, 152, 155, 158, 172, 173, 174, 175, 179, 187, 220, 239, 240, 246, 252, 255, 258, + 265, 270, 273, 280, 287, 293, 298, 304, 328, 355, 363, 371, 374, 387, 390, 393, 396, 403, 406, 440, 443, 495, 498, 501, 506, 519, 524, 532, 535, 540, 543, 551, 574, 577, 582, 599, 602, + 607, 610, 633, 650, 653, 656, 661, 664, 669, 679, 704 + }; + + public static readonly int[] BasicStarters_7 = BasicStarters_6.Concat(new[] + { + 722, 725, 728, 731, 736, 761, 782, 789 + }).ToArray(); + } +} diff --git a/pkNX.Structures/Legality/GameInfo.cs b/pkNX.Structures/Legality/GameInfo.cs new file mode 100644 index 00000000..6e0dd503 --- /dev/null +++ b/pkNX.Structures/Legality/GameInfo.cs @@ -0,0 +1,98 @@ +using System; + +namespace pkNX.Structures +{ + /// + /// Misc information pertaining to the game. + /// + public class GameInfo + { + public readonly GameVersion Game; + public readonly int Generation; + + public int MaxSpeciesID { get; private set; } + public int MaxItemID { get; private set; } + public int MaxMoveID { get; private set; } + public ushort[] HeldItems { get; private set; } + public int MaxAbilityID { get; private set; } + + public bool XY { get; private set; } + public bool AO { get; private set; } + public bool SM { get; private set; } + public bool USUM { get; private set; } + public bool GG { get; private set; } + + public GameInfo(GameVersion game) + { + Game = game; + Generation = game.GetGeneration(); + GetInitMethod(game)(); + } + + private Action GetInitMethod(GameVersion game) + { + switch (game) + { + case GameVersion.XY: return LoadXY; + case GameVersion.ORASDEMO: + case GameVersion.ORAS: return LoadAO; + case GameVersion.SMDEMO: + case GameVersion.SM: return LoadSM; + case GameVersion.USUM: return LoadUSUM; + case GameVersion.GG: return LoadGG; + default: + throw new ArgumentException(nameof(game)); + } + } + + private void LoadXY() + { + XY = true; + MaxSpeciesID = Legal.MaxSpeciesID_6; + MaxMoveID = Legal.MaxMoveID_6_XY; + MaxItemID = Legal.MaxItemID_6_XY; + HeldItems = Legal.HeldItem_XY; + MaxAbilityID = Legal.MaxAbilityID_6_XY; + } + + private void LoadAO() + { + AO = true; + MaxSpeciesID = Legal.MaxSpeciesID_6; + MaxMoveID = Legal.MaxMoveID_6_AO; + MaxItemID = Legal.MaxItemID_6_AO; + HeldItems = Legal.HeldItem_AO; + MaxAbilityID = Legal.MaxAbilityID_6_AO; + } + + private void LoadSM() + { + SM = true; + MaxSpeciesID = Legal.MaxSpeciesID_7_SM; + MaxMoveID = Legal.MaxMoveID_7_SM; + MaxItemID = Legal.MaxItemID_7_SM; + HeldItems = Legal.HeldItems_SM; + MaxAbilityID = Legal.MaxAbilityID_7_SM; + } + + private void LoadUSUM() + { + USUM = true; + MaxSpeciesID = Legal.MaxSpeciesID_7_USUM; + MaxMoveID = Legal.MaxMoveID_7_USUM; + MaxItemID = Legal.MaxItemID_7_USUM; + HeldItems = Legal.HeldItems_USUM; + MaxAbilityID = Legal.MaxAbilityID_7_USUM; + } + + private void LoadGG() + { + GG = true; + MaxSpeciesID = Legal.MaxSpeciesID_7_GG; + MaxMoveID = Legal.MaxMoveID_7_GG; + MaxItemID = Legal.MaxItemID_7_GG; + HeldItems = new ushort[1]; + MaxAbilityID = Legal.MaxAbilityID_7_GG; + } + } +} diff --git a/pkNX.Structures/Legality/Item.cs b/pkNX.Structures/Legality/Item.cs new file mode 100644 index 00000000..d03bfb49 --- /dev/null +++ b/pkNX.Structures/Legality/Item.cs @@ -0,0 +1,46 @@ +using System.Linq; + +namespace pkNX.Structures +{ + public static partial class Legal + { + private static readonly int[] Items_HeldXY = + { + /* 000, */ 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 017, 018, 019, 020, 021, 022, + 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, + 036, 037, 038, 039, 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, + 058, 059, 060, 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, + 071, 072, 073, 074, 075, 076, 077, 078, 079, 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, + 093, 094, 099, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 112, 116, 117, 118, 119, 134, 135, 136, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, + 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, + 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, + 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, + 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, + 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, + 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, + 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, + 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 504, 537, 538, 539, 540, 541, 542, 543, 544, + 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, + 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 577, 580, 581, 582, 583, 584, + 585, 586, 587, 588, 589, 590, 591, 639, 640, 644, 645, 646, 647, + 648, 649, 650, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, + 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, + 684, 685, 686, 687, 688, 699, 704, 708, 709, 710, 711, 715, + }; + + private static readonly int[] Items_HeldAO = Items_HeldXY.Concat(new[] + { + 534, 535, + 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 767, 768, 769, 770, + }).ToArray(); + + private static readonly int[] Items_Ball = + { + 000, 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, + 013, 014, 015, 016, 492, 493, 494, 495, 496, 497, 498, 499, 576, + }; + } +} diff --git a/pkNX.Structures/Legality/Legal.cs b/pkNX.Structures/Legality/Legal.cs new file mode 100644 index 00000000..cdfdba9d --- /dev/null +++ b/pkNX.Structures/Legality/Legal.cs @@ -0,0 +1,58 @@ +using System.Linq; + +namespace pkNX.Structures +{ + public static partial class Legal + { + internal static int GetRandomForme(int species, bool mega, bool alola, PersonalInfo[] stats = null) + { + if (stats == null) + return 0; + if (stats[species].FormeCount <= 1) + return 0; + + if (species == 658 && !mega) // Greninja + return 0; + if (species == 664 || species == 665 || species == 666) // vivillon + return 30; // save file specific + if (species == 774) // minior + return Util.Rand.Next(7); + + if (alola && EvolveToAlolanForms.Contains(species)) + return Util.Rand.Next(2); + if (!BattleExclusiveForms.Contains(species) || mega) + return Util.Rand.Next(stats[species].FormeCount); // Slot-Random + return 0; + } + + /// + /// Multiplies the current level with a scaling factor, returning a modified level. + /// + /// Current Level. + /// Modification factor. + /// Boosted (or reduced) level. + internal static int GetModifiedLevel(int level, decimal factor) + { + int newlvl = (int)(level * factor); + if (newlvl < 1) + return 1; + if (newlvl > 100) + return 100; + return newlvl; + } + + internal static int[] GetRandomItemList(GameVersion game) + { + if (GameVersion.ORAS.Contains(game) || game == GameVersion.ORASDEMO) + return Items_HeldAO.Concat(Items_Ball).Where(i => i != 0).ToArray(); + + if (GameVersion.XY.Contains(game)) + return Items_HeldXY.Concat(Items_Ball).Where(i => i != 0).ToArray(); + + if (GameVersion.SM.Contains(game) || GameVersion.USUM.Contains(game)) + return HeldItemsBuy_SM.Select(i => (int)i).Concat(Items_Ball).Where(i => i != 0).ToArray(); + + return new int[1]; + } + } +} diff --git a/pkNX.Structures/Legality/Species.cs b/pkNX.Structures/Legality/Species.cs new file mode 100644 index 00000000..9fa02f14 --- /dev/null +++ b/pkNX.Structures/Legality/Species.cs @@ -0,0 +1,57 @@ +using System.Linq; + +namespace pkNX.Structures +{ + public static partial class Legal + { + /// + /// All final evolutions, excluding Event-exclusive Forms that do not evolve (Pikachu, Floette, etc). Used for forcing fully evolved species. + /// Overrides all other randomizer settings, allowing all Generations and Legendary/Mythical Pokemon as well. + /// + public static readonly int[] FinalEvolutions_6 = + { + 003, 006, 009, 012, 015, 018, 020, 022, 024, 026, 028, 031, 034, 036, 038, 040, 045, 047, 049, 051, 053, 055, 057, 059, 062, 065, 068, 071, 073, 076, 078, 080, 083, 085, 087, 089, 091, + 094, 097, 099, 101, 103, 105, 106, 107, 110, 115, 119, 121, 122, 124, 127, 128, 130, 131, 132, 134, 135, 136, 139, 141, 142, 143, 144, 145, 146, 149, 150, 151, 154, 157, 160, 162, 164, + 166, 168, 169, 171, 178, 181, 182, 184, 185, 186, 189, 192, 195, 196, 197, 199, 201, 202, 203, 205, 206, 208, 210, 211, 212, 213, 214, 217, 219, 222, 224, 225, 226, 227, 229, 230, 232, + 234, 235, 237, 241, 242, 243, 244, 245, 248, 249, 250, 251, 254, 257, 260, 262, 264, 267, 269, 272, 275, 277, 279, 282, 284, 286, 289, 291, 292, 295, 297, 301, 302, 303, 306, 308, 310, + 311, 312, 313, 314, 317, 319, 321, 323, 324, 326, 327, 330, 332, 334, 335, 336, 337, 338, 340, 342, 344, 346, 348, 350, 351, 352, 354, 357, 358, 359, 362, 365, 367, 368, 369, 370, 373, + 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 389, 392, 395, 398, 400, 402, 405, 407, 409, 411, 413, 414, 416, 417, 419, 421, 423, 424, 426, 428, 429, 430, 432, 435, 437, 441, + 442, 445, 448, 450, 452, 454, 455, 457, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, + 489, 490, 491, 492, 493, 494, 497, 500, 503, 505, 508, 510, 512, 514, 516, 518, 521, 523, 526, 528, 530, 531, 534, 537, 538, 539, 542, 545, 547, 549, 550, 553, 555, 556, 558, 560, 561, + 563, 565, 567, 569, 571, 573, 576, 579, 581, 584, 586, 587, 589, 591, 593, 594, 596, 598, 601, 604, 606, 609, 612, 614, 615, 617, 618, 620, 621, 623, 625, 626, 628, 630, 631, 632, 635, + 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 652, 655, 658, 660, 663, 666, 668, 671, 673, 675, 676, 678, 681, 683, 685, 687, 689, 691, 693, 695, 697, 699, 700, 701, + 702, 703, 706, 707, 709, 711, 713, 715, 716, 717, 718, 719, 720, 721 + }; + + public static readonly int[] FinalEvolutions_SM = FinalEvolutions_6.Concat(new[] + { + 724, 727, 730, 733, 735, 738, 740, 741, 743, 745, 746, 748, 750, 752, 754, 756, 758, 760, 763, 764, 765, 766, 768, 770, 771, 773, 774, 775, 776, 777, 779, 780, 781, 784, 785, 786, 787, + 788, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802 + }).ToArray(); + + public static readonly int[] FinalEvolutions_USUM = FinalEvolutions_SM.Concat(new[] + { + 804, 805, 806, 807 + }).ToArray(); + + /// + /// All Legendary and Mythical Pokemon. Used for Legendary-for-Legendary replacement. + /// Does not include un-evolved species in the array due to potential error with Force Fully Evolved. + /// + public static readonly int[] Legendary_Mythical_6 = + { + 144, 145, 146, 150, 151, 243, 244, 245, 249, 250, 251, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 638, + 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 716, 717, 718, 719, 720, 721 + }; + + public static readonly int[] Legendary_Mythical_SM = Legendary_Mythical_6.Concat(new[] + { + 773, 785, 786, 787, 788, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802 + }).ToArray(); + + public static readonly int[] Legendary_Mythical_USUM = Legendary_Mythical_SM.Concat(new[] + { + 804, 805, 806, 807 + }).ToArray(); + } +} diff --git a/pkNX.Structures/Legality/Tables6.cs b/pkNX.Structures/Legality/Tables6.cs new file mode 100644 index 00000000..600c7905 --- /dev/null +++ b/pkNX.Structures/Legality/Tables6.cs @@ -0,0 +1,557 @@ +using System.Collections.Generic; +using System.Linq; + +namespace pkNX.Structures +{ + public static partial class Legal + { + public const int MaxSpeciesID_6 = 721; + public const int MaxMoveID_6_XY = 617; + public const int MaxMoveID_6_AO = 621; + public const int MaxItemID_6_XY = 717; + public const int MaxItemID_6_AO = 775; + public const int MaxAbilityID_6_XY = 188; + public const int MaxAbilityID_6_AO = 191; + public const int MaxBallID_6 = 0x19; + public const int MaxGameID_6 = 27; // OR + + #region Met Locations + public static readonly int[] Met_XYc = {0, 60002, 30002,}; + + public static readonly int[] Met_XY_0 = + { + /* XY */ 2, 6, 8, 10, 12, 14, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, + 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 82, 84, 86, 88, + 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, + 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, /* ORAS */ 170, 172, + 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, + 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, + 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, + 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, + 350, 352, 354, + }; + + public static readonly int[] Met_XY_3 = + { + 30001, 30003, 30004, 30005, 30006, 30007, 30008, 30009, 30010, 30011, + }; + + public static readonly int[] Met_XY_4 = + { + 40001, 40002, 40003, 40004, 40005, 40006, 40007, 40008, 40009, 40010, + 40011, 40012, 40013, 40014, 40015, 40016, 40017, 40018, 40019, 40020, 40021, 40022, 40023, 40024, 40025, + 40026, 40027, 40028, 40029, 40030, 40031, 40032, 40033, 40034, 40035, 40036, 40037, 40038, 40039, 40040, + 40041, 40042, 40043, 40044, 40045, 40046, 40047, 40048, 40049, 40050, 40051, 40052, 40053, 40054, 40055, + 40056, 40057, 40058, 40059, 40060, 40061, 40062, 40063, 40064, 40065, 40066, 40067, 40068, 40069, 40070, + 40071, 40072, 40073, 40074, 40075, 40076, 40077, 40078, 40079, + }; + + public static readonly int[] Met_XY_6 = {/* XY */ 60001, 60003, /* ORAS */ 60004,}; + + #endregion + + #region Inventory Pouch + + public static readonly ushort[] Pouch_Items_XY = + { + 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 055, 056, + 057, 058, 059, 060, 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, + 076, 077, 078, 079, 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, + 099, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 112, 116, 117, 118, 119, 135, 136, + 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, + 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, + 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, + 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, + 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, + 492, 493, 494, 495, 496, 497, 498, 499, 500, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, + 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 571, + 572, 573, 576, 577, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 639, 640, 644, 646, + 647, 648, 649, 650, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, + 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, + 699, 704, 710, 711, 715, + }; + + public static readonly ushort[] Pouch_Items_AO = + { + 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 055, 056, + 057, 058, 059, 060, 061, 062, 063, 064, 068, 069, 070, 071, 072, 073, 074, 075, + 076, 077, 078, 079, 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, + 099, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 112, 116, 117, 118, 119, 135, 136, + 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, + 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, + 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, + 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, + 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, + 492, 493, 494, 495, 496, 497, 498, 499, 500, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, + 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 571, + 572, 573, 576, 577, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 639, 640, 644, 646, + 647, 648, 649, 650, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, + 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, + 699, 704, 710, 711, 715, + + // ORAS + 534, 535, + 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 767, 768, 769, 770, + }; + + public static readonly ushort[] Pouch_Key_XY = + { + 216, 431, 442, 445, 446, 447, 450, 465, 466, 471, 628, + 629, 631, 632, 638, 641, 642, 643, 689, 695, 696, 697, 698, + 700, 701, 702, 703, 705, 706, 707, 712, 713, 714, + + // Illegal + 716, 717, // For the cheaters who want useless items... + }; + + public static readonly ushort[] Pouch_Key_AO = + { + 216, 445, 446, 447, 465, 466, 471, 628, + 629, 631, 632, 638, 697, + + // Illegal + 716, 717, 745, 746, 747, 748, 749, 750, // For the cheaters who want useless items... + + // ORAS + 457, 474, 503, + + 718, 719, + 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, + 730, 731, 732, 733, 734, 735, 736, 738, 739, + 740, 741, 742, 743, 744, + 751, 765, 766, 771, 772, 774, 775, + }; + + public static readonly ushort[] Pouch_TMHM_XY = + { + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, + 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, + 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, 618, 619, 620, 690, 691, 692, 693, 694, + + 420, 421, 422, 423, 424, + }; + + public static readonly ushort[] Pouch_TMHM_AO = + { + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, + 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, + 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, 618, 619, 620, 690, 691, 692, 693, 694, + + 420, 421, 422, 423, 424, + + // ORAS + 425, 737, + }; + + public static readonly ushort[] Pouch_Medicine_XY = + { + 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, + 034, 035, 036, 037, 038, 039, 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, + 052, 053, 054, 134, 504, 565, 566, 567, 568, 569, 570, 571, 591, 645, 708, 709, + }; + + public static readonly ushort[] Pouch_Medicine_AO = + { + 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, + 034, 035, 036, 037, 038, 039, 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, + 052, 053, 054, 134, 504, 565, 566, 567, 568, 569, 570, 571, 591, 645, 708, 709, + + //ORAS + 065, 066, 067 + }; + + public static readonly ushort[] Pouch_Berry_XY = + { + 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, + 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, + 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, + 208, 209, 210, 211, 212, 686, 687, 688, + }; + + public static readonly ushort[] HeldItem_XY = new ushort[1].Concat(Pouch_Items_XY).Concat(Pouch_Medicine_XY).Concat(Pouch_Berry_XY).ToArray(); + public static readonly ushort[] HeldItem_AO = new ushort[1].Concat(Pouch_Items_AO).Concat(Pouch_Medicine_AO).Concat(Pouch_Berry_XY).ToArray(); + #endregion + + #region TMHM + + public static readonly int[] TMHM_AO = + { + 468, 337, 473, 347, 046, 092, 258, 339, 474, 237, + 241, 269, 058, 059, 063, 113, 182, 240, 355, 219, + 218, 076, 479, 085, 087, 089, 216, 091, 094, 247, + 280, 104, 115, 482, 053, 188, 201, 126, 317, 332, + 259, 263, 488, 156, 213, 168, 490, 496, 497, 315, + 211, 411, 412, 206, 503, 374, 451, 507, 510, 511, + 261, 512, 373, 153, 421, 371, 514, 416, 397, 148, + 444, 521, 086, 360, 014, 522, 244, 523, 524, 157, + 404, 525, 611, 398, 138, 447, 207, 214, 369, 164, + 430, 433, 528, 290, 555, 267, 399, 612, 605, 590, + + 15, 19, 57, 70, 127, 249, 291, + }; + + public static readonly int[] TMHM_XY = + { + 468, 337, 473, 347, 046, 092, 258, 339, 474, 237, + 241, 269, 058, 059, 063, 113, 182, 240, 355, 219, + 218, 076, 479, 085, 087, 089, 216, 091, 094, 247, + 280, 104, 115, 482, 053, 188, 201, 126, 317, 332, + 259, 263, 488, 156, 213, 168, 490, 496, 497, 315, + 211, 411, 412, 206, 503, 374, 451, 507, 510, 511, + 261, 512, 373, 153, 421, 371, 514, 416, 397, 148, + 444, 521, 086, 360, 014, 522, 244, 523, 524, 157, + 404, 525, 611, 398, 138, 447, 207, 214, 369, 164, + 430, 433, 528, 249, 555, 267, 399, 612, 605, 590, + + 15, 19, 57, 70, 127, + }; + + public static readonly int[] TypeTutor6 = {520, 519, 518, 338, 307, 308, 434, 620}; + + public static readonly int[][] Tutors_AO = + { + new[] {450, 343, 162, 530, 324, 442, 402, 529, 340, 067, 441, 253, 009, 007, 008}, + new[] {277, 335, 414, 492, 356, 393, 334, 387, 276, 527, 196, 401, 399, 428, 406, 304, 231}, + new[] {020, 173, 282, 235, 257, 272, 215, 366, 143, 220, 202, 409, 355, 264, 351, 352}, + new[] {380, 388, 180, 495, 270, 271, 478, 472, 283, 200, 278, 289, 446, 214, 285}, + }; + + #endregion + + public static readonly int[] EggLocations6 = {60002, 30002}; + + public static readonly int[] ValidMet_XY = + { + 006, 008, 009, 010, 012, 013, 014, 016, 017, 018, 020, 021, 022, 024, 026, 028, 029, 030, 032, 034, 035, 036, + 038, 039, 040, 042, 043, 044, 046, 047, 048, 050, 051, 052, 054, 055, 056, 058, 060, 062, 063, 064, 066, 067, + 068, 069, 070, 072, 074, 075, 076, 078, 079, 082, 084, 085, 086, 088, 089, 090, 092, 093, 094, 096, 097, 098, + 100, 101, 102, 103, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 135, 136, + 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168 + }; + + public static readonly int[] ValidMet_AO = + { + 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, + 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, + 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, + 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, + 332, 334, 336, 338, 340, 342, 344, 346, 348, 350, 352, 354 + }; + + public static readonly int[] FriendSafari = + { + 190, 206, 216, 506, 294, 352, 531, 572, 113, 132, 133, 235, + 012, 046, 165, 415, 267, 284, 313, 314, 049, 127, 214, 666, + 262, 274, 624, 629, 215, 332, 342, 551, 302, 359, 510, 686, + 444, 611, 148, 372, 714, 621, 705, + 101, 417, 587, 702, 025, 125, 618, 694, 310, 404, 523, 596, + 175, 209, 281, 702, 039, 303, 682, 684, 035, 670, + 056, 067, 307, 619, 538, 539, 674, 236, 286, 297, 447, + 058, 077, 126, 513, 005, 218, 636, 668, 038, 654, 662, + 016, 021, 083, 084, 163, 520, 527, 581, 357, 627, 662, 701, + 353, 608, 708, 710, 356, 426, 442, 623, + 043, 114, 191, 511, 002, 541, 548, 586, 556, 651, 673, + 027, 194, 231, 328, 051, 105, 290, 323, 423, 536, 660, + 225, 361, 363, 459, 215, 614, 712, 087, 091, 131, 221, + 014, 044, 268, 336, 049, 168, 317, 569, 089, 452, 454, 544, + 063, 096, 326, 517, 202, 561, 677, 178, 203, 575, 578, + 299, 525, 557, 095, 219, 222, 247, 112, 213, 689, + 082, 303, 597, 205, 227, 375, 600, 437, 530, 707, + 098, 224, 400, 515, 008, 130, 195, 419, 061, 184, 657 + }; + + public static readonly int[] CosplayPikachuMoves = { 309, 556, 577, 604, 560, 0 }; + public static readonly int[] WildPokeballs6 = { 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; + + public static readonly int[] Ban_NoHidden6 = + { + //Not avaliable at Friend Safari or Horde Encounter + 669 + (2 << 11), //Flabébé-Orange + 670 + (2 << 11), //Floette-Orange + 671 + (2 << 11), //Florges-Orange + 669 + (4 << 11), //Flabébé-White + 670 + (4 << 11), //Floette-White + 671 + (4 << 11), //Florges-White + + 710 + (1 << 11), //Pumpkaboo-Small + 711 + (1 << 11), //Gourgeist-Small + 710 + (2 << 11), //Pumpkaboo-Large + 711 + (2 << 11), //Gourgeist-Large + //Super Size can be obtained as a Pumpkaboo from event distributions + }; + #region Ball Table + public static readonly int[] Inherit_Sport = + { + 010, 013, 046, 048, 123, 127, 265, 290, 314, 401, 415, + + 313, // Via Illumise + }; + + public static readonly int[] Inherit_Safari = + { + 016, 019, 020, 021, 022, 023, 024, 025, 027, 029, 035, 039, 041, + 043, 046, 048, 050, 054, 055, 060, 063, 066, 069, 070, 074, 077, 079, 080, 083, 084, 088, 092, 095, 096, 098, + 099, 102, 104, 108, 109, 111, 113, 114, 115, 118, 122, 123, 125, 126, 127, 129, 131, 147, 161, 163, 165, 167, + 177, 179, 183, 187, 189, 190, 191, 193, 194, 198, 200, 202, 203, 204, 207, 209, 213, 214, 216, 223, 228, 229, + 231, 234, 235, 241, 246, 263, 264, 270, 271, 273, 283, 284, 285, 286, 288, 298, 299, 304, 305, 307, 308, 309, + 310, 314, 315, 316, 318, 324, 327, 328, 331, 332, 335, 336, 339, 341, 352, 353, 354, 355, 356, 357, 358, 363, + 364, 371, 372, 397, 399, 400, 403, 404, 406, 417, 418, 419, 433, 443, 447, 449, 451, 453, 455, + + 032, // Via Nidoran-F + 313, // Via Illumise + + 173, // Via Clefairy + 239, // Via Electabuzz + 240, // Via Magmar + 298, // Via Marill + 360, // Via Wobbuffet + 406, // Via Roselia + 433, // Via Chimecho + 439, // Via Mr. Mime + 440, // Via Chansey + }; + + public static readonly int[] Inherit_Dream = + { + 010, 013, 016, 019, 021, 023, 027, 029, 037, 041, 043, 046, 048, 050, 052, 054, 056, 058, 060, 063, 066, 069, + 072, 074, 077, 079, 083, 084, 086, 088, 090, 092, 095, 096, 098, 102, 104, 108, 109, 111, 113, 114, 115, 116, + 118, 122, 123, 127, 129, 131, 133, 138, 140, 142, 143, 147, 161, 163, 165, 167, 170, 173, 174, 175, 177, 179, + 183, 185, 187, 190, 191, 193, 194, 198, 200, 202, 203, 204, 206, 207, 209, 211, 213, 214, 215, 216, 218, 220, + 222, 223, 225, 226, 227, 228, 231, 234, 235, 238, 239, 240, 241, 246, 261, 263, 265, 270, 273, 276, 278, 280, + 283, 285, 287, 290, 293, 296, 299, 300, 302, 303, 304, 307, 309, 311, 312, 314, 315, 316, 318, 320, 322, 324, + 325, 327, 328, 331, 333, 335, 336, 339, 341, 345, 347, 349, 351, 352, 353, 355, 357, 358, 359, 361, 363, 366, + 369, 370, 371, 396, 399, 401, 403, 408, 410, 412, 415, 417, 418, 420, 422, 425, 427, 431, 434, 441, 442, 443, + 447, 449, 451, 453, 455, 456, 459, 517, 519, 525, 529, 531, 533, 535, 545, 546, 548, 550, 553, 556, 558, 559, + 561, 564, 578, 580, 583, 587, 588, 594, 596, 605, 610, 616, 618, 621, 624, 631, 632, + + 032, // Via Nidoran-F + 313, // Via Illumise + + // Via Evolution + 524, // Roggenrola + 532, // Timburr + 543, // Venipede + 551, // Sandile + 557, // Dwebble + 577, // Solosis + 582, // Vanillite + 595, // Joltik + + // Via Incense Breeding + 298, 360, 406, 433, 438, 439, 440, 446, 458, + }; + + public static readonly int[] Ban_DreamHidden = + { + 311, // Plusle + 312, // Minun + 352, // Kecleon + 355, // Duskull + + // Via Pre-Evolution + 356, // Dusclops + 477, // Dusknoir + }; + + public static readonly int[] Ban_Gen3Ball = + { + 252, 255, 258, //1 - Treeko, Torchic, Mudkip + 253, 256, 259, //2 + 254, 257, 260, //3 + 387, 390, 393, //1 - Turtwig, Chimchar, Piplup + 388, 391, 394, //2 + 389, 392, 395, //3 + 495, 498, 501, //1 - Snivy, Tepig, Oshawott + 496, 499, 502, //2 + 497, 500, 503, //3 + 566, 567, 696, 697, 698, 699 // Fossil Only obtain + }; + + public static readonly int[] Ban_Gen3BallHidden = + { + // can have HA and can be in gen 3 ball as eggs but can not at same time. + 152, 155, 158, //1 - Gen2 Starters + 153, 156, 159, //2 + 154, 157, 160, //3 + 585 + (1 << 11), //Deerling-Summer + 586 + (1 << 11), //Sawsbuck-Summer + 585 + (2 << 11), //Deerling-Autumn + 586 + (2 << 11), //Sawsbuck-Autumn + 585 + (3 << 11), //Deerling-Winter + 586 + (3 << 11), //Sawsbuck-Winter + 710 + (3 << 11), //Pumpkaboo-Super + 711 + (3 << 11), //Gourgeist-Super + }; + + public static readonly int[] Ban_Gen4Ball_6 = + { + 152, 155, 158, //1 - Chikorita, Cyndaquil, Totodile + 153, 156, 159, //2 + 154, 157, 160, //3 + 252, 255, 258, //1 - Treeko, Torchic, Mudkip + 253, 256, 259, //2 + 254, 257, 260, //3 + 387, 390, 393, //1 - Turtwig, Chimchar, Piplup + 388, 391, 394, //2 + 389, 392, 395, //3 + 495, 498, 501, //1 - Snivy, Tepig, Oshawott + 496, 499, 502, //2 + 497, 500, 503, //3 + 566, 567, 696, 697, 698, 699 // Fossil Only obtain + }; + + public static readonly int[] WurmpleEvolutions = + { + 266, 267, // Silcoon Beautifly + 268, 269, // Cascoon Dustox + }; + #endregion + #region Memory Table + public static readonly int[] Memory_NotXY = + { + 65, // {0} was with {1} when (he/she) built a Secret Base. {4} that {3}. + 66, // {0} participated in a contest with {1} and impressed many people. {4} that {3}. + 67, // {0} participated in a contest with {1} and won the title. {4} that {3}. + 68, // {0} soared through the sky with {1} and went to many different places. {4} that {3}. + 69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}. + }; + + public static readonly int[] Memory_NotAO = + { + 11, // {0} went clothes shopping with {1}. {4} that {3}. + 43, // {0} was impressed by the speed of the train it took with {1}. {4} that {3}. + 44, // {0} encountered {2} with {1} using the Poké Radar. {4} that {3}. + 56, // {0} was with {1} when (he/she) went to a boutique and tried on clothes, but (he/she) left the boutique without buying anything. {4} that {3}. + 57, // {0} went to a nice restaurant with {1} and ate until it got totally full. {4} that {3}. + 62, // {0} saw itself in a mirror in a mirror cave that it went to with {1}. {4} that {3}. + }; + + public static readonly int[][] MoveSpecificMemories = + { + new[] { + 20, // {0} surfed across the water, carrying {1} on its back. {4} that {3}. + 24, // {0} flew, carrying {1} on its back, to {2}. {4} that {3}. + 35, // {0} proudly used Strength at {1}’s instruction in... {2}. {4} that {3}. + 36, // {0} proudly used Cut at {1}’s instruction in... {2}. {4} that {3}. + 37, // {0} shattered rocks to its heart’s content at {1}’s instruction in... {2}. {4} that {3}. + 38, // {0} used Waterfall while carrying {1} on its back in... {2}. {4} that {3}. + 69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}. + }, + new[] { 57, 19, 70, 15, 249, 127, 291}, // Move IDs + }; + + public static readonly int[][] LocationsWithPKCenter = + { + new[] { + // Kalos locations with a PKMN CENTER + 18, // Santalune City + 22, // Lumiose City + 30, // Camphrier Town + 40, // Cyllage City + 44, // Ambrette Town + 52, // Geosenge Towny + 58, // Shalour City + 64, // Coumarine City + 70, // Laverre City + 76, // Dendemille Town + 86, // Anistar City + 90, // Couriway Town + 94, // Snowbelle City + 106, // Pokémon League (X/Y) + // Hoenn locations with a PKMN CENTER + 172, // Oldale Town + 174, // Dewford Town + 176, // Lavaridge Town + 178, // Fallarbor Town + 180, // Verdanturf Town + 182, // Pacifidlog Town + 184, // Petalburg City + 186, // Slateport City + 188, // Mauville City + 190, // Rustboro City + 192, // Fortree City + 194, // Lilycove City + 196, // Mossdeep City + 198, // Sootopolis City + 200, // Ever Grande City + 202, // Pokémon League (OR/AS) + }, + new[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // Region matching + }; + + public static readonly HashSet MemoryGeneral = new HashSet { 1, 2, 3, 4, 19, 24, 31, 32, 33, 35, 36, 37, 38, 39, 42, 52, 59 }; + public static readonly HashSet MemorySpecific = new HashSet { 6 }; + public static readonly HashSet MemoryMove = new HashSet { 12, 16, 48, 49 }; + public static readonly HashSet MemoryItem = new HashSet { 5, 15, 26, 34, 40, 51 }; + public static readonly HashSet MemorySpecies = new HashSet { 7, 9, 13, 14, 17, 21, 18, 25, 29, 44, 45, 50, 60 }; + #endregion + + public static readonly int[] MovePP_XY = + { + 00, + 35, 25, 10, 15, 20, 20, 15, 15, 15, 35, 30, 05, 10, 20, 30, 35, 35, 20, 15, 20, 20, 25, 20, 30, 05, 10, 15, 15, 15, 25, 20, 05, 35, 15, 20, 20, 10, 15, 30, 35, 20, 20, 30, 25, 40, 20, 15, 20, 20, 20, + 30, 25, 15, 30, 25, 05, 15, 10, 05, 20, 20, 20, 05, 35, 20, 25, 20, 20, 20, 15, 25, 15, 10, 20, 25, 10, 35, 30, 15, 10, 40, 10, 15, 30, 15, 20, 10, 15, 10, 05, 10, 10, 25, 10, 20, 40, 30, 30, 20, 20, + 15, 10, 40, 15, 10, 30, 10, 20, 10, 40, 40, 20, 30, 30, 20, 30, 10, 10, 20, 05, 10, 30, 20, 20, 20, 05, 15, 15, 20, 10, 15, 35, 20, 15, 10, 10, 30, 15, 40, 20, 15, 10, 05, 10, 30, 10, 15, 20, 15, 40, + 20, 10, 05, 15, 10, 10, 10, 15, 30, 30, 10, 10, 20, 10, 01, 01, 10, 25, 10, 05, 15, 25, 15, 10, 15, 30, 05, 40, 15, 10, 25, 10, 30, 10, 20, 10, 10, 10, 10, 10, 20, 05, 40, 05, 05, 15, 05, 10, 05, 10, + 10, 10, 10, 20, 20, 40, 15, 10, 20, 20, 25, 05, 15, 10, 05, 20, 15, 20, 25, 20, 05, 30, 05, 10, 20, 40, 05, 20, 40, 20, 15, 35, 10, 05, 05, 05, 15, 05, 20, 05, 05, 15, 20, 10, 05, 05, 15, 10, 15, 15, + 10, 10, 10, 20, 10, 10, 10, 10, 15, 15, 15, 10, 20, 20, 10, 20, 20, 20, 20, 20, 10, 10, 10, 20, 20, 05, 15, 10, 10, 15, 10, 20, 05, 05, 10, 10, 20, 05, 10, 20, 10, 20, 20, 20, 05, 05, 15, 20, 10, 15, + 20, 15, 10, 10, 15, 10, 05, 05, 10, 15, 10, 05, 20, 25, 05, 40, 15, 05, 40, 15, 20, 20, 05, 15, 20, 20, 15, 15, 05, 10, 30, 20, 30, 15, 05, 40, 15, 05, 20, 05, 15, 25, 25, 15, 20, 15, 20, 15, 20, 10, + 20, 20, 05, 05, 10, 05, 40, 10, 10, 05, 10, 10, 15, 10, 20, 15, 30, 10, 20, 05, 10, 10, 15, 10, 10, 05, 15, 05, 10, 10, 30, 20, 20, 10, 10, 05, 05, 10, 05, 20, 10, 20, 10, 15, 10, 20, 20, 20, 15, 15, + 10, 15, 15, 15, 10, 10, 10, 20, 10, 30, 05, 10, 15, 10, 10, 05, 20, 30, 10, 30, 15, 15, 15, 15, 30, 10, 20, 15, 10, 10, 20, 15, 05, 05, 15, 15, 05, 10, 05, 20, 05, 15, 20, 05, 20, 20, 20, 20, 10, 20, + 10, 15, 20, 15, 10, 10, 05, 10, 05, 05, 10, 05, 05, 10, 05, 05, 05, 15, 10, 10, 10, 10, 10, 10, 15, 20, 15, 10, 15, 10, 15, 10, 20, 10, 15, 10, 20, 20, 20, 20, 20, 15, 15, 15, 15, 15, 15, 20, 15, 10, + 15, 15, 15, 15, 10, 10, 10, 10, 10, 15, 15, 15, 15, 05, 05, 15, 05, 10, 10, 10, 20, 20, 20, 10, 10, 30, 15, 15, 10, 15, 25, 10, 15, 10, 10, 10, 20, 10, 10, 10, 10, 10, 15, 15, 05, 05, 10, 10, 10, 05, + 05, 10, 05, 05, 15, 10, 05, 05, 05, 10, 10, 10, 10, 20, 25, 10, 20, 30, 25, 20, 20, 15, 20, 15, 20, 20, 10, 10, 10, 10, 10, 20, 10, 30, 15, 10, 10, 10, 20, 20, 05, 05, 05, 20, 10, 10, 20, 15, 20, 20, + 10, 20, 30, 10, 10, 40, 40, 30, 20, 40, 20, 20, 10, 10, 10, 10, 05, 10, 10, 05, 05, + }; + + public static readonly int[] UnreleasedItems_6 = + { + 005, // Safari Ball + 016, // Cherish Ball + 492, // Fast Ball + 493, // Level Ball + 494, // Lure Ball + 495, // Heavy Ball + 496, // Love Ball + 497, // Friend Ball + 498, // Moon Ball + 499, // Sport Ball + 500, // Park Ball + 548, // Fire Gem + 549, // Water Gem + 550, // Electric Gem + 551, // Grass Gem + 552, // Ice Gem + 553, // Fighting Gem + 554, // Poison Gem + 555, // Ground Gem + 556, // Flying Gem + 557, // Psychic Gem + 558, // Bug Gem + 559, // Rock Gem + 560, // Ghost Gem + 561, // Dragon Gem + 562, // Dark Gem + 563, // Steel Gem + 576, // Dream Ball + 584, // Relic Copper + 585, // Relic Silver + 587, // Relic Vase + 588, // Relic Band + 589, // Relic Statue + 590, // Relic Crown + 715, // Fairy Gem + }; + + public static readonly bool[] ReleasedHeldItems_6 = Enumerable.Range(0, MaxItemID_6_AO+1).Select(i => HeldItem_AO.Contains((ushort)i) && !UnreleasedItems_6.Contains(i)).ToArray(); + } +} diff --git a/pkNX.Structures/Legality/Tables7.cs b/pkNX.Structures/Legality/Tables7.cs new file mode 100644 index 00000000..283f2c62 --- /dev/null +++ b/pkNX.Structures/Legality/Tables7.cs @@ -0,0 +1,273 @@ +using System.Collections.Generic; +using System.Linq; + +namespace pkNX.Structures +{ + public static partial class Legal + { + public const int MaxSpeciesID_7_SM = 802; + public const int MaxMoveID_7_SM = 719; + public const int MaxItemID_7_SM = 920; + public const int MaxAbilityID_7_SM = 232; + public const int MaxBallID_7_SM = 0x1A; // 26 + public const int MaxGameID_7 = 41; // Crystal (VC?) + + public const int MaxSpeciesID_7_USUM = 807; + public const int MaxMoveID_7_USUM = 728; + public const int MaxItemID_7_USUM = 959; + public const int MaxAbilityID_7_USUM = 233; + + public const int MaxSpeciesID_7_GG = 809; + public const int MaxMoveID_7_GG = 728; // todo + public const int MaxItemID_7_GG = 959; // todo + public const int MaxAbilityID_7_GG = MaxAbilityID_7_USUM; + + public static readonly ushort[] Pouch_Regular_SM = // 00 + { + 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, 080, 081, 082, 083, 084, 085, 086, 087, + 088, 089, 090, 091, 092, 093, 094, 099, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 116, 117, 118, 119, 135, 136, 137, 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, + 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, + 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, + 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, + 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, + 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, + 326, 327, 499, 534, 535, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, + 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 571, 572, 573, 580, 581, 582, 583, + 584, 585, 586, 587, 588, 589, 590, 639, 640, 644, 646, 647, 648, 649, 650, 656, 657, 658, 659, + 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, + 680, 681, 682, 683, 684, 685, 699, 704, 710, 711, 715, 752, 753, 754, 755, 756, 757, 758, 759, 760, + 761, 762, 763, 764, 767, 768, 769, 770, 795, 796, 844, 849, 853, 854, 855, 856, 879, 880, 881, 882, + 883, 884, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, + }; + + public static readonly ushort[] Pouch_Ball_SM = { // 08 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 492, 493, 494, 495, 496, 497, 498, 576, + 851 + }; + + public static readonly ushort[] Pouch_Battle_SM = { // 16 + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 577, + 846, + }; + + public static readonly ushort[] Pouch_Items_SM = Pouch_Regular_SM.Concat(Pouch_Ball_SM).Concat(Pouch_Battle_SM).ToArray(); + + public static readonly ushort[] Pouch_Key_SM = { + 216, 465, 466, 628, 629, 631, 632, 633, 638, 696, + 705, 706, 765, 773, 797, + 841, 842, 843, 845, 847, 850, 857, 858, 860, + }; + + public static readonly ushort[] Pouch_TMHM_SM = { // 02 + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, + 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, + 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, 618, 619, 620, 690, 691, 692, 693, 694, + }; + + public static readonly ushort[] Pouch_Medicine_SM = { // 32 + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 65, 66, 67, 134, + 504, 565, 566, 567, 568, 569, 570, 591, 645, 708, 709, + 852, + }; + + public static readonly ushort[] Pouch_Berries_SM = { + 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, + 686, 687, 688, + }; + + public static readonly ushort[] Pouch_ZCrystal_SM = { // Bead + 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, + }; + + public static readonly ushort[] Pouch_ZCrystalHeld_SM = { // Piece + 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 798, 799, 800, 801, 802, 803, 804, 805, 806, 836 + }; + + public static readonly ushort[] Pouch_ZCrystal_USUM = { // Bead + 927, 928, 929, 930, 931, 932 + }; + + public static readonly ushort[] Pouch_ZCrystalHeld_USUM = { // Piece + 921, 922, 923, 924, 925, 926 + }; + + public static readonly ushort[] HeldItems_SM = new ushort[1].Concat(Pouch_Items_SM).Concat(Pouch_Berries_SM).Concat(Pouch_Medicine_SM).Concat(Pouch_ZCrystalHeld_SM).ToArray(); + public static readonly ushort[] HeldItems_USUM = new ushort[1].Concat(HeldItems_SM).Concat(Pouch_ZCrystalHeld_USUM).ToArray(); + + public static readonly int[] AlolanOriginForms = + { + 019, // Rattata + 020, // Raticate + 027, // Sandshrew + 028, // Sandslash + 037, // Vulpix + 038, // Ninetales + 050, // Diglett + 051, // Dugtrio + 052, // Meowth + 053, // Persian + 074, // Geodude + 075, // Graveler + 076, // Golem + 088, // Grimer + 089, // Muk + }; + + public static readonly int[] EvolveToAlolanForms = new[] + { + 026, // Raichu + 103, // Exeggutor + 105, // Marowak + }.Concat(AlolanOriginForms).ToArray(); + + #region Unreleased Items + public static readonly int[] UnreleasedHeldItems_7 = + { + 005, // Safari Ball + 016, // Cherish Ball + 064, // Fluffy Tail + 065, // Blue Flute + 066, // Yellow Flute + 067, // Red Flute + 068, // Black Flute + 069, // White Flute + 070, // Shoal Salt + 071, // Shoal Shell + 099, // Root Fossil + 100, // Claw Fossil + 101, // Helix Fossil + 102, // Dome Fossil + 103, // Old Amber + 111, // Odd Keystone + 164, // Razz Berry + 166, // Nanab Berry + 167, // Wepear Berry + 175, // Cornn Berry + 176, // Magost Berry + 177, // Rabuta Berry + 178, // Nomel Berry + 179, // Spelon Berry + 180, // Pamtre Berry + 181, // Watmel Berry + 182, // Durin Berry + 183, // Belue Berry + 208, // Enigma Berry + 209, // Micle Berry + 210, // Custap Berry + 211, // Jaboca Berry + 212, // Rowap Berry + 215, // Macho Brace + 260, // Red Scarf + 261, // Blue Scarf + 262, // Pink Scarf + 263, // Green Scarf + 264, // Yellow Scarf + 548, // Fire Gem + 549, // Water Gem + 550, // Electric Gem + 551, // Grass Gem + 552, // Ice Gem + 553, // Fighting Gem + 554, // Poison Gem + 555, // Ground Gem + 556, // Flying Gem + 557, // Psychic Gem + 558, // Bug Gem + 559, // Rock Gem + 560, // Ghost Gem + 561, // Dragon Gem + 562, // Dark Gem + 563, // Steel Gem + 576, // Dream Ball + 584, // Relic Copper + 585, // Relic Silver + 586, // Relic Gold + 587, // Relic Vase + 588, // Relic Band + 589, // Relic Statue + 590, // Relic Crown + 710, // Jaw Fossil + 711, // Sail Fossil + 715, // Fairy Gem + }; + #endregion + public static readonly bool[] ReleasedHeldItems_7 = Enumerable.Range(0, MaxItemID_7_SM+1).Select(i => HeldItems_SM.Contains((ushort)i) && !UnreleasedHeldItems_7.Contains(i)).ToArray(); + + public static readonly HashSet Totem_Alolan = new HashSet + { + 020, // Raticate (Normal, Alolan, Totem) + 105, // Marowak (Normal, Alolan, Totem) + 778, // Mimikyu (Normal, Busted, Totem, Totem_Busted) + }; + + public static readonly HashSet Totem_SM = new HashSet + { + 020, // Raticate + 735, // Gumshoos + //746, // Wishiwashi + 758, // Salazzle + 754, // Lurantis + 738, // Vikavolt + 778, // Mimikyu + 784, // Kommo-o + }; + + public static readonly HashSet Totem_USUM = new HashSet + { + 020, // Raticate + 735, // Gumshoos + //746, // Wishiwashi + 758, // Salazzle + 754, // Lurantis + 738, // Vikavolt + 778, // Mimikyu + 784, // Kommo-o + 105, // Marowak + 752, // Araquanid + 777, // Togedemaru + 743, // Ribombee + }; + + public static readonly ushort[] HeldItemsBuy_SM = new ushort[1].Concat(Pouch_Items_SM).Concat(Pouch_Medicine_SM).ToArray(); + + public static readonly HashSet BattleForms = new HashSet + { + 351, // Castform + 421, // Cherrim + 555, // Darmanitan + 648, // Meloetta + 681, // Aegislash + 716, // Xerneas + 746, // Wishiwashi + 778, // Mimikyu + }; + + public static readonly HashSet BattleMegas = new HashSet + { + // XY + 3,6,9,65,80, + 115,127,130,142,150,181, + 212,214,229,248,282, + 303,306,308,310,354,359,380,381, + 445,448,460, + + // AO + 15,18,94, + 208,254,257,260, + 302,319,323,334,362,373,376,384, + 428,475, + 531, + 719, + + // USUM + 800, // Ultra Necrozma + }; + + public static readonly HashSet BattlePrimals = new HashSet { 382, 383 }; + public static HashSet BattleExclusiveForms = new HashSet(BattleForms.Concat(BattleMegas.Concat(BattlePrimals))); + } +} diff --git a/pkNX.Structures/Legality/Tables7b.cs b/pkNX.Structures/Legality/Tables7b.cs new file mode 100644 index 00000000..27eb3dcc --- /dev/null +++ b/pkNX.Structures/Legality/Tables7b.cs @@ -0,0 +1,248 @@ +using System.Linq; + +namespace pkNX.Structures +{ + public static partial class Legal + { + internal const int MaxSpeciesID_7b = 809; // Melmetal + internal const int MaxMoveID_7b = 742; // Double Iron Bash + internal const int MaxItemID_7b = 1057; // Magmar Candy + internal const int MaxGameID_7b = (int)GameVersion.GE; + internal const int MaxAbilityID_7b = MaxAbilityID_7_USUM; + internal static readonly ushort[] HeldItems_GG = new ushort[1]; + public const int AwakeningMax = 200; + + #region Met Locations + + internal static readonly int[] Met_GG_0 = + { + 002, // Invalid + 003, 004, 005, 006, 007, 008, 009, + 010, 011, 012, 012, 013, 014, 015, 016, 017, 018, 019, + 020, 021, 022, 022, 023, 024, 025, 026, 027, 028, 029, + 030, 031, 032, 032, 033, 034, 035, 036, 037, 038, 039, + 040, 041, 042, 042, 043, 044, 045, 046, 047, 048, 049, + 050, 051, 052, 053, + }; + + internal static readonly int[] Met_GG_3 = + { + 30001, 30003, 30004, 30005, 30006, 30007, 30008, 30009, 30010, 30011, 30012, 30013, 30014, 30015, 30016, 30017 + }; + + internal static readonly int[] Met_GG_4 = + { + 40001, 40002, 40003, 40004, 40005, 40006, 40007, 40008, 40009, + 40010, 40011, 40012, 40013, 40014, 40015, 40016, 40017, 40018, 40019, + 40020, 40021, 40022, 40023, 40024, 40025, 40026, 40027, 40028, 40029, + 40030, 40031, 40032, 40033, 40034, 40035, 40036, 40037, 40038, 40039, + 40040, 40041, 40042, 40043, 40044, 40045, 40046, 40047, 40048, 40049, + 40050, 40051, 40052, 40053, 40054, 40055, 40056, 40057, 40058, 40059, + 40060, 40061, 40062, 40063, 40064, 40065, 40066, 40067, 40068, 40069, + 40070, 40071, 40072, 40073, 40074, 40075, 40076, 40077, + }; + + internal static readonly int[] Met_GG_6 = {/* XY */ 60001, 60003, /* ORAS */ 60004, }; + + #endregion + + #region Items + + internal static readonly ushort[] Pouch_Candy_GG_Regular = + { + 050, // Rare Candy + 960, 961, 962, 963, 964, 965, // S + 966, 967, 968, 969, 970, 971, // L + 972, 973, 974, 975, 976, 977, // XL + }; + + internal static readonly ushort[] Pouch_Candy_GG_Species = + { + 979, + 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, + 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, + 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, + 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, + 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, + 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, + 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, + 1050, 1051, 1052, 1053, 1054, 1055, 1056, + 1057, + }; + + internal static readonly ushort[] Pouch_Candy_GG = Pouch_Candy_GG_Regular.Concat(Pouch_Candy_GG_Species).ToArray(); + + internal static readonly ushort[] Pouch_Medicine_GG = + { + 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 038, 039, 040, 041, 709, 903, + }; + + internal static readonly ushort[] Pouch_TM_GG = + { + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, + 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, + 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, + 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, + 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, + 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, + }; + + internal static readonly ushort[] Pouch_PowerUp_GG = + { + 051, 053, 081, 082, 083, 084, 085, + 849, + }; + + internal static readonly ushort[] Pouch_Catching_GG = + { + 001, 002, 003, 004, 012, 164, 166, 168, + 861, 862, 863, 864, 865, 866, + }; + + internal static readonly ushort[] Pouch_Battle_GG = + { + 055, 056, 057, 058, 059, 060, 061, 062, + 656, 659, 660, 661, 662, 663, 671, 672, 675, 676, 678, 679, + 760, 762, 770, 773, + }; + + internal static readonly ushort[] Pouch_Regular_GG = + { + 076, 077, 078, 079, 086, 087, 088, 089, + 090, 091, 092, 093, 101, 102, 103, 113, 115, + 121, 122, 123, 124, 125, 126, 127, 128, + 442, + 571, + 632, 651, + 795, 796, + 872, 873, 874, 875, 876, 877, 878, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 900, 901, 902, + }; + + internal static readonly ushort[] Pouch_Regular_GG_Item = + { + 076, // Super Repel + 077, // Max Repel + 078, // Escape Rope + 079, // Repel + 086, // Tiny Mushroom + 087, // Big Mushroom + 088, // Pearl + 089, // Big Pearl + 090, // Stardust + 091, // Star Piece + 092, // Nugget + 093, // Heart Scale + + 571, // Pretty Wing + 795, // Bottle Cap + 796, // Gold Bottle Cap + + 900, // Lure + 901, // Super Lure + 902, // Max Lure + }; + + internal static readonly ushort[] Pouch_Regular_GG_Key = + { + 101, // Helix Fossil + 102, // Dome Fossil + 103, // Old Amber + 113, // Tea + 115, // Autograph + 121, // Pokémon Box + 122, // Medicine Pocket + 123, // TM Case + 124, // Candy Jar + 125, // Power-Up Pocket + 126, // Clothing Trunk + 127, // Catching Pocket + 128, // Battle Pocket + 442, // Town Map + 632, // Shiny Charm + 651, // Poké Flute + + 872, // Secret Key + 873, // S.S. Ticket + 874, // Silph Scope + 875, // Parcel + 876, // Card Key + 877, // Gold Teeth + 878, // Lift Key + 885, // Stretchy Spring + 886, // Chalky Stone + 887, // Marble + 888, // Lone Earring + 889, // Beach Glass + 890, // Gold Leaf + 891, // Silver Leaf + 892, // Polished Mud Ball + 893, // Tropical Shell + 894, // Leaf Letter (P) + 895, // Leaf Letter (E) + 896, // Small Bouquet + }; + + #endregion + + #region Moves + + internal static readonly int[] MovePP_GG = + { + // Absorb: 25 -> 15 (damage buffed from 20->40) + // Mega Drain: 15 -> 10 (damage buffed from 40->75) + + 00, + 35, 25, 10, 15, 20, 20, 15, 15, 15, 35, 30, 05, 10, 20, 30, 35, 35, 20, 15, 20, 20, 25, 20, 30, 05, 10, 15, 15, 15, 25, 20, 05, 35, 15, 20, 20, 10, 15, 30, 35, 20, 20, 30, 25, 40, 20, 15, 20, 20, 20, + 30, 25, 15, 30, 25, 05, 15, 10, 05, 20, 20, 20, 05, 35, 20, 20, 20, 20, 20, 15, 15, 10, 10, 20, 25, 10, 35, 30, 15, 10, 40, 10, 15, 30, 15, 20, 10, 15, 10, 05, 10, 10, 25, 10, 20, 40, 30, 30, 20, 20, + 15, 10, 40, 15, 10, 30, 10, 20, 10, 40, 40, 20, 30, 30, 20, 30, 10, 10, 20, 05, 10, 30, 20, 20, 20, 05, 15, 15, 20, 10, 15, 35, 20, 15, 10, 10, 30, 15, 40, 20, 10, 10, 05, 10, 30, 10, 15, 20, 15, 40, + 20, 10, 05, 15, 10, 10, 10, 15, 30, 30, 10, 10, 20, 10, 01, 01, 10, 25, 10, 05, 15, 25, 15, 10, 15, 30, 05, 40, 15, 10, 25, 10, 30, 10, 20, 10, 10, 10, 10, 10, 20, 05, 40, 05, 05, 15, 05, 10, 05, 10, + 10, 10, 10, 20, 20, 40, 15, 10, 20, 20, 25, 05, 15, 10, 05, 20, 15, 20, 25, 20, 05, 30, 05, 10, 20, 40, 05, 20, 40, 20, 15, 35, 10, 05, 05, 05, 15, 05, 20, 05, 05, 15, 20, 10, 05, 05, 15, 10, 15, 15, + 10, 10, 10, 20, 10, 10, 10, 10, 15, 15, 15, 10, 20, 20, 10, 20, 20, 20, 20, 20, 10, 10, 10, 20, 20, 05, 15, 10, 10, 15, 10, 20, 05, 05, 10, 10, 20, 05, 10, 20, 10, 20, 20, 20, 05, 05, 15, 20, 10, 15, + 20, 15, 10, 10, 15, 10, 05, 05, 10, 15, 10, 05, 20, 25, 05, 40, 15, 05, 40, 15, 20, 20, 05, 15, 20, 20, 15, 15, 05, 10, 30, 20, 30, 15, 05, 40, 15, 05, 20, 05, 15, 25, 25, 15, 20, 15, 20, 15, 20, 10, + 20, 20, 05, 05, 10, 05, 40, 10, 10, 05, 10, 10, 15, 10, 20, 15, 30, 10, 20, 05, 10, 10, 15, 10, 10, 05, 15, 05, 10, 10, 30, 20, 20, 10, 10, 05, 05, 10, 05, 20, 10, 20, 10, 15, 10, 20, 20, 20, 15, 15, + 10, 15, 15, 15, 10, 10, 10, 20, 10, 30, 05, 10, 15, 10, 10, 05, 20, 30, 10, 30, 15, 15, 15, 15, 30, 10, 20, 15, 10, 10, 20, 15, 05, 05, 15, 15, 05, 10, 05, 20, 05, 15, 20, 05, 20, 20, 20, 20, 10, 20, + 10, 15, 20, 15, 10, 10, 05, 10, 05, 05, 10, 05, 05, 10, 05, 05, 05, 15, 10, 10, 10, 10, 10, 10, 15, 20, 15, 10, 15, 10, 15, 10, 20, 10, 10, 10, 20, 20, 20, 20, 20, 15, 15, 15, 15, 15, 15, 20, 15, 10, + 15, 15, 15, 15, 10, 10, 10, 10, 10, 15, 15, 15, 15, 05, 05, 15, 05, 10, 10, 10, 20, 20, 20, 10, 10, 30, 15, 15, 10, 15, 25, 10, 15, 10, 10, 10, 20, 10, 10, 10, 10, 10, 15, 15, 05, 05, 10, 10, 10, 05, + 05, 10, 05, 05, 15, 10, 05, 05, 05, 10, 10, 10, 10, 20, 25, 10, 20, 30, 25, 20, 20, 15, 20, 15, 20, 20, 10, 10, 10, 10, 10, 20, 10, 30, 15, 10, 10, 10, 20, 20, 05, 05, 05, 20, 10, 10, 20, 15, 20, 20, + 10, 20, 30, 10, 10, 40, 40, 30, 20, 40, 20, 20, 10, 10, 10, 10, 05, 10, 10, 05, 05, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, + 01, 01, 01, 01, 01, 01, 01, 01, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 40, 15, 20, 30, 20, 15, 15, 20, 10, 15, 15, 10, 05, 10, 10, 20, 15, 10, 15, 15, 15, 05, 15, 20, 20, 01, 01, 01, 01, 01, 01, + 01, 01, 01, 05, 05, 10, 10, 10, 20, 10, 10, 10, 05, 05, 20, 10, 10, 10, 01, 05, 15, 05, 01, 01, 01, 01, 01, 01, 15, 15, 15, 20, 15, 15, 15, 15, 15, 15, 15, 15, 20, 05, + }; + + internal static readonly int[] TMHM_GG = + { + 029, 269, 270, 100, 156, 113, 182, 164, 115, 091, + 261, 263, 280, 019, 069, 086, 525, 369, 231, 399, + 492, 157, 009, 404, 127, 398, 092, 161, 503, 339, + 007, 605, 347, 406, 008, 085, 053, 087, 200, 094, + 089, 120, 247, 583, 076, 126, 057, 063, 276, 355, + 059, 188, 072, 430, 058, 446, 006, 529, 138, 224, + // rest are same as SM, unused + + // No HMs + }; + + internal static readonly int[] Tutor_StarterPikachu = + { + 729, // Zippy Zap + 730, // Splishy Splash + 731, // Floaty Fall + 732, // Pika Papow + }; + + internal static readonly int[] Tutor_StarterEevee = + { + 733, // Bouncy Bubble + 734, // Buzzy Buzz + 735, // Sizzly Slide + 736, // Glitzy Glow + 737, // Baddy Bad + 738, // Sappy Seed + 739, // Freezy Frost + 740, // Sparkly Swirl + 741, // Veevee Volley + }; + + #endregion + } +} diff --git a/pkNX.Structures/Legality/Trainers.cs b/pkNX.Structures/Legality/Trainers.cs new file mode 100644 index 00000000..f6af468d --- /dev/null +++ b/pkNX.Structures/Legality/Trainers.cs @@ -0,0 +1,337 @@ +using System.Linq; + +namespace pkNX.Structures +{ + public static partial class Legal + { + public static readonly ushort[] Mega_XY = + { + 003, 006, 009, 065, 080, 115, 127, 130, 142, 150, + 181, 212, 214, 229, 248, + 257, 282, 303, 306, 308, 310, 354, 359, 380, 381, + 445, 448, 460 + }; + + public static readonly ushort[] Mega_ORAS = Mega_XY.Concat(new ushort[] + { + 015, 018, 094, + 208, + 254, 260, 302, 319, 323, 334, 362, 373, 376, 384, + 428, 475, + 531, + 719 + }).ToArray(); + + public static readonly int[] SpecialClasses_XY = + { + #region Classes + 000, // Pokémon Trainer + 001, // Pokémon Trainer + 004, // Leader + 018, // Team Flare + 019, // Team Flare + 020, // Team Flare + 021, // Team Flare + 022, // Team Flare + 035, // Elite Four + 036, // Elite Four + 037, // Elite Four + 038, // Elite Four + 039, // Leader + 040, // Leader + 041, // Leader + 042, // Leader + 043, // Leader + 044, // Leader + 045, // Leader + 053, // Champion + 055, // Pokémon Trainer + 056, // Pokémon Trainer + 057, // Pokémon Trainer + 064, // Battle Chatelaine + 065, // Battle Chatelaine + 066, // Battle Chatelaine + 067, // Battle Chatelaine + 081, // Team Flare + 102, // Pokémon Trainer + 103, // Pokémon Trainer + 104, // Pokémon Trainer + 105, // Pokémon Professor + 139, // Marchioness + 140, // Marquis + 141, // Marchioness + 142, // Marquis + 143, // Marquis + 144, // Marchioness + 145, // Marchioness + 146, // Marquis + 151, // Grand Duchess + 160, // Pokémon Trainer + 161, // Pokémon Trainer + 170, // Pokémon Trainer + 171, // Pokémon Trainer + 172, // Pokémon Trainer + 173, // Team Flare + 174, // Team Flare + 175, // Team Flare Boss + 176, // Successor + 177, // Leader + #endregion + }; + + public static readonly int[] SpecialClasses_ORAS = + { + #region Classes + 064, // Battle Chatelaine + 065, // Battle Chatelaine + 066, // Battle Chatelaine + 067, // Battle Chatelaine + 127, // Pokémon Trainer + 128, // Pokémon Trainer + 174, // Aqua Leader + 175, // Aqua Admin + 178, // Magma Leader + 180, // Magma Admin + 182, // Magma Admin + 186, // Aqua Admin + 187, // Magma Admin + 192, // Pokémon Trainer + 194, // Elite Four + 195, // Elite Four + 196, // Elite Four + 197, // Elite Four + 198, // Champion + 200, // Leader + 201, // Leader + 202, // Leader + 203, // Leader + 204, // Leader + 205, // Leader + 206, // Leaders + 207, // Leader + 219, // Pokémon Trainer + 221, // Lorekeeper + 232, // Pokémon Trainer + 233, // Pokémon Trainer + 234, // Pokémon Trainer + 236, // Secret Base Expert + 267, // Pokémon Trainer + 268, // Sootopolitan + 270, // Pokémon Trainer + 271, // Pokémon Trainer + 272, // Pokémon Trainer + 273, // Elite Four + 274, // Elite Four + 275, // Elite Four + 276, // Elite Four + 277, // Champion + 278, // Pokémon Trainer + 279, // Pokémon Trainer + #endregion + }; + + public static readonly int[] SpecialClasses_SM = + { + #region Classes + 000, // Pokémon Trainer + 001, // Pokémon Trainer + 030, // Pokémon Trainer + 031, // Island Kahuna + 038, // Captain + 040, // Pokémon Trainer + 041, // Pokémon Trainer + 043, // Captain + 044, // Captain + 045, // Captain + 046, // Captain + 047, // Captain + 048, // Captain + 049, // Island Kahuna + 050, // Island Kahuna + 051, // Island Kahuna + 071, // Aether President + 072, // Aether Branch Chief + 076, // Team Skull Boss + 077, // Pokémon Trainer + 078, // Team Skull Admin + 079, // Pokémon Trainer + 080, // Elite Four + 081, // Pokémon Trainer + 082, // Aether President + 083, // Pokémon Trainer + 084, // Pokémon Trainer + 085, // Pokémon Trainer + 086, // Pokémon Trainer + 087, // Pokémon Trainer + 088, // Pokémon Trainer + 089, // Pokémon Trainer + 090, // Pokémon Trainer + 091, // Pokémon Trainer + 092, // Pro Wrestler + 093, // Pokémon Trainer + 097, // Pokémon Trainer + 098, // Pokémon Trainer + 099, // Pokémon Trainer + 100, // Pokémon Trainer + 101, // Pokémon Trainer + 102, // Pokémon Trainer + 103, // Pokémon Trainer + 104, // Pokémon Trainer + 105, // Pokémon Trainer + 106, // Pokémon Trainer + 107, // Elite Four + 108, // Pokémon Trainer + 109, // Elite Four + 110, // Elite Four + 111, // Pokémon Professor + 128, // Pokémon Trainer + 139, // GAME FREAK + 140, // Pokémon Trainer + 141, // Island Kahuna + 142, // Captain + 143, // Pokémon Trainer + 150, // Pokémon Trainer + 153, // Captain + 154, // Pokémon Professor + 164, // Island Kahuna + 166, // Pokémon Trainer + 167, // Pokémon Trainer + 168, // Pokémon Trainer + 169, // Pokémon Trainer + 170, // Pokémon Trainer + 171, // Pokémon Trainer + 165, // Pokémon Professor + 183, // Battle Legend + 184, // Battle Legend + #endregion + }; + + public static readonly int[] SpecialClasses_USUM = + { + #region Classes + 000, // Pokémon Trainer + 001, // Pokémon Trainer + 030, // Pokémon Trainer + 031, // Island Kahuna + 038, // Captain + 040, // Pokémon Trainer + 041, // Pokémon Trainer + 043, // Captain + 044, // Captain + 045, // Captain + 046, // Captain + 047, // Captain + 048, // Captain + 049, // Island Kahuna + 050, // Island Kahuna + 051, // Island Kahuna + 071, // Aether President + 072, // Aether Branch Chief + 076, // Team Skull Boss + 077, // Pokémon Trainer + 078, // Team Skull Admin + 079, // Pokémon Trainer + 080, // Elite Four + 081, // Pokémon Trainer + 082, // Aether President + 083, // Pokémon Trainer + 084, // Pokémon Trainer + 085, // Pokémon Trainer + 086, // Pokémon Trainer + 087, // Pokémon Trainer + 088, // Pokémon Trainer + 089, // Pokémon Trainer + 090, // Pokémon Trainer + 091, // Pokémon Trainer + 092, // Pro Wrestler + 093, // Pokémon Trainer + 097, // Pokémon Trainer + 098, // Pokémon Trainer + 099, // Pokémon Trainer + 100, // Pokémon Trainer + 101, // Pokémon Trainer + 102, // Pokémon Trainer + 103, // Pokémon Trainer + 104, // Pokémon Trainer + 105, // Pokémon Trainer + 106, // Pokémon Trainer + 107, // Elite Four + 108, // Pokémon Trainer + 109, // Elite Four + 110, // Elite Four + 111, // Pokémon Professor + 128, // Pokémon Trainer + 139, // GAME FREAK + 140, // Pokémon Trainer + 141, // Island Kahuna + 142, // Captain + 143, // Pokémon Trainer + 150, // Pokémon Trainer + 153, // Captain + 154, // Pokémon Professor + 164, // Island Kahuna + 166, // Pokémon Trainer + 167, // Pokémon Trainer + 168, // Pokémon Trainer + 169, // Pokémon Trainer + 170, // Pokémon Trainer + 171, // Pokémon Trainer + 165, // Pokémon Professor + 183, // Battle Legend + 184, // Battle Legend + 186, // Pokémon Trainer + 187, // Pokémon Trainer + 188, // Pokémon Trainer + 189, // Pokémon Trainer + 190, // Pokémon Trainer + 191, // Elite Four + 192, // Ultra Recon Squad + 193, // Ultra Recon Squad + 194, // Pokémon Trainer + 198, // Team Aqua + 199, // Team Galactic + 200, // Team Magma + 201, // Team Plasma + 202, // Team Flare + 205, // GAME FREAK + 206, // Team Rainbow Rocket + 207, // Pokémon Trainer + 219, // Pokémon Trainer + 220, // Aether President + 221, // Pokémon Trainer + 222, // Pokémon Trainer + #endregion + }; + + public static readonly int[] Model_XY = + { + 018, 019, 020, 021, 022, 055, 056, 057, 077, 078, 079, 080, 081, 102, 103, 104, 105, 107, 108, 173, 174, 175 + }; + + public static readonly int[] Model_AO = + { + 127, 128, 174, 178, 192, 198, 219, 221, 267, 272, 277, 278, 279 + }; + + public static readonly int[] Z_Moves = + { + 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, + 695, 696, 697, 698, 699, 700, 701, 702, 703, 719, 723, 724, 725, 726, 727, 728 + }; + + public static readonly int[] ImportantTrainers_SM = + { + 012, 013, 014, 023, 052, 074, 075, 076, 077, 078, 079, 089, 090, 129, 131, 132, 138, 144, 146, 149, 152, 153, 154, 155, 156, 158, 159, 160, 164, 167, 215, 216, 217, 218, 219, 220, 221, + 222, 235, 236, 238, 239, 240, 241, 349, 350, 351, 352, 356, 357, 358, 359, 360, 392, 396, 398, 400, 401, 403, 405, 409, 410, 412, 413, 414, 415, 416, 417, 418, 419, 435, 438, 439, 440, + 441, 447, 448, 449, 450, 451, 452, 467, 477, 478, 479, 480, 481, 482, 483, 484 + }; + + public static readonly int[] ImportantTrainers_USUM = + { + 012, 013, 014, 023, 052, 074, 075, 076, 077, 078, 079, 089, 090, 131, 132, 138, 144, 146, 149, 153, 154, 156, 159, 160, 215, 216, 217, 218, 219, 220, 221, 222, 235, 236, 238, 239, 240, + 241, 350, 351, 352, 356, 358, 359, 396, 398, 401, 405, 409, 410, 412, 415, 416, 417, 418, 419, 438, 439, 440, 441, 447, 448, 449, 450, 451, 452, 477, 478, 479, 480, 489, 490, 494, 495, + 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 541, 542, 543, 555, 556, 557, 558, 559, 560, 561, 562, 572, 573, 578, 580, 582, 583, 623, 630, 644, 645, 647, 648, 649, + 650, 651, 652 + }; + } +} diff --git a/pkNX.Structures/Maison/Maison6.cs b/pkNX.Structures/Maison/Maison6.cs new file mode 100644 index 00000000..38d710be --- /dev/null +++ b/pkNX.Structures/Maison/Maison6.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; + +namespace pkNX.Structures +{ + public static class Maison6 + { + public class Trainer + { + public ushort Class; + public ushort Count; + public ushort[] Choices; + + public Trainer() { } + + public Trainer(byte[] data) + { + Class = BitConverter.ToUInt16(data, 0); + Count = BitConverter.ToUInt16(data, 2); + Choices = new ushort[Count]; + for (int i = 0; i < Count; i++) + Choices[i] = BitConverter.ToUInt16(data, 4 + (2 * i)); + } + + public byte[] Write() + { + using (var ms = new MemoryStream()) + using (var bw = new BinaryWriter(ms)) + { + bw.Write(Class); + bw.Write(Count); + foreach (ushort Choice in Choices) + bw.Write(Choice); + return ms.ToArray(); + } + } + } + + public class Pokemon + { + public ushort Species; + public readonly ushort[] Moves = new ushort[4]; + private readonly byte EV; + public readonly bool[] EVs = new bool[6]; + public byte Nature; + public ushort Item; + public ushort Form; + + public int Move1 { get => Moves[0]; set => Moves[0] = (ushort)value; } + public int Move2 { get => Moves[1]; set => Moves[1] = (ushort)value; } + public int Move3 { get => Moves[2]; set => Moves[2] = (ushort)value; } + public int Move4 { get => Moves[3]; set => Moves[3] = (ushort)value; } + public bool HP { get => EVs[0]; set => EVs[0] = value; } + public bool ATK { get => EVs[1]; set => EVs[1] = value; } + public bool DEF { get => EVs[2]; set => EVs[2] = value; } + public bool SPE { get => EVs[3]; set => EVs[3] = value; } + public bool SPA { get => EVs[4]; set => EVs[4] = value; } + public bool SPD { get => EVs[5]; set => EVs[5] = value; } + + public Pokemon(byte[] data) + { + Species = BitConverter.ToUInt16(data, 0); + for (int i = 0; i < 4; i++) + Moves[i] = BitConverter.ToUInt16(data, 2 + (2 * i)); + EV = data[0xA]; + for (int i = 0; i < 6; i++) + EVs[i] = ((EV >> i) & 1) == 1; + Nature = data[0xB]; + Item = BitConverter.ToUInt16(data, 0xC); + Form = BitConverter.ToUInt16(data, 0xE); + } + + public byte[] Write() + { + using (var ms = new MemoryStream()) + using (var bw = new BinaryWriter(ms)) + { + bw.Write(Species); + foreach (ushort Move in Moves) + bw.Write(Move); + + int ev = EV & 0xC0; + for (int i = 0; i < EVs.Length; i++) + ev |= EVs[i] ? 1 << i : 0; + bw.Write((byte)ev); + + bw.Write(Nature); + bw.Write(Item); + bw.Write(Form); + return ms.ToArray(); + } + } + } + } +} diff --git a/pkNX.Structures/MegaEvolution/MegaEvolutionMethod.cs b/pkNX.Structures/MegaEvolution/MegaEvolutionMethod.cs new file mode 100644 index 00000000..1ac73585 --- /dev/null +++ b/pkNX.Structures/MegaEvolution/MegaEvolutionMethod.cs @@ -0,0 +1,10 @@ +namespace pkNX.Structures +{ + public enum MegaEvolutionMethod + { + None = 0, + Item = 1, + DragonAscent = 2, + NoRequirement = 3, + } +} \ No newline at end of file diff --git a/pkNX.Structures/MegaEvolution/MegaEvolutionSet.cs b/pkNX.Structures/MegaEvolution/MegaEvolutionSet.cs new file mode 100644 index 00000000..c7cf0c2d --- /dev/null +++ b/pkNX.Structures/MegaEvolution/MegaEvolutionSet.cs @@ -0,0 +1,53 @@ +using System; +using System.Diagnostics; + +namespace pkNX.Structures +{ + public class MegaEvolutionSet + { + private readonly byte[] Data; + private const int OFS_FORM = 0; + private const int OFS_METHOD = 2; + private const int OFS_ARGUMENT = 4; + internal const int SIZE = 8; + + public MegaEvolutionSet(byte[] data, int index) + { + Debug.Assert(data.Length % SIZE == 0); + Data = new byte[SIZE]; + Array.Copy(data, index*SIZE, Data, 0, SIZE); + } + + public int ToForm + { + get => BitConverter.ToUInt16(Data, OFS_FORM); + set => BitConverter.GetBytes((ushort) value).CopyTo(Data, OFS_FORM); + } + + public int Method + { + get => BitConverter.ToUInt16(Data, OFS_METHOD); + set => BitConverter.GetBytes((ushort)value).CopyTo(Data, OFS_METHOD); + } + + public int Argument + { + get => BitConverter.ToUInt16(Data, OFS_ARGUMENT); + set => BitConverter.GetBytes((ushort)value).CopyTo(Data, OFS_ARGUMENT); + } + + public void Clear() + { + for (int i = 0; i < Data.Length; i++) + Data[i] = 0; + } + + public void Write(byte[] data, int index) => Data.CopyTo(data, index * SIZE); + + public void RemoveRestrictions() + { + if (Method != (int) MegaEvolutionMethod.None) + Method = (int) MegaEvolutionMethod.NoRequirement; + } + } +} diff --git a/pkNX.Structures/MegaEvolution/MegaEvolutionTable.cs b/pkNX.Structures/MegaEvolution/MegaEvolutionTable.cs new file mode 100644 index 00000000..1ff48fab --- /dev/null +++ b/pkNX.Structures/MegaEvolution/MegaEvolutionTable.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; + +namespace pkNX.Structures +{ + /// + /// Represents all possible Mega Evolutions for all entries in the . + /// + public sealed class MegaEvolutionTable + { + private readonly MegaEvolutionSet[][] Table; + + public MegaEvolutionTable(IList data) + { + Table = new MegaEvolutionSet[data.Count][]; + for (int i = 0; i < data.Count; i++) + { + int count = data[i].Length / MegaEvolutionSet.SIZE; + Table[i] = new MegaEvolutionSet[count]; + for (int j = 0; j < count; j++) + Table[i][j] = new MegaEvolutionSet(data[i], j); + } + } + + private static byte[] Write(IList entry) + { + int count = entry.Count; + byte[] data = new byte[MegaEvolutionSet.SIZE]; + for (int i = 0; i < count; i++) + entry[i].Write(data, i); + return data; + } + + public MegaEvolutionSet[] this[int index] + { + get => Table[index]; + set => Table[index] = value; + } + + /// + /// Removes any restriction for Mega Evolution + /// + /// + /// uses ; not supported in prior games and maybe not future games. + /// This might not be useful in the long run. + /// + public void RemoveRestrictions() + { + foreach (var t in Table) + { + foreach (var s in t) + s.RemoveRestrictions(); + } + } + + /// + /// Writes the entire to its component files. + /// + /// Pack-ready array of files. + public byte[][] Write() + { + byte[][] data = new byte[Table.Length][]; + for (int i = 0; i < data.Length; i++) + data[i] = Write(Table[i]); + return data; + } + } +} diff --git a/pkNX.Structures/Misc/CaptureReward.cs b/pkNX.Structures/Misc/CaptureReward.cs new file mode 100644 index 00000000..78c8286c --- /dev/null +++ b/pkNX.Structures/Misc/CaptureReward.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.IO; + +namespace pkNX.Structures +{ + /// + /// Rewards given by after capturing a Pokémon. + /// + /// They do this because the Pickup Ability no longer exists, and to ease the grind of marts. + public class CaptureRewardTable + { + public class Group + { + public int CaptureCount; + public int EntryCount; + + public List Entries; + } + + public class Entry + { + public int Item; + public int Count; + public int Rate; + } + + public List Table = new List(); + + public CaptureRewardTable(byte[] data) + { + using (var ms = new MemoryStream(data)) + using (var br = new BinaryReader(ms)) + { + ReadHeader(br); + ReadEntries(br); + } + } + + private void ReadEntries(BinaryReader br) + { + foreach (var g in Table) + ReadTable(br, g); + } + + private static void ReadTable(BinaryReader br, Group g) + { + for (int i = 0; i < g.EntryCount; i++) + g.Entries.Add(ReadEntry(br)); + } + + private static Entry ReadEntry(BinaryReader br) + { + return new Entry + { + Item = br.ReadInt32(), + Count = br.ReadInt32(), + Rate = br.ReadInt32(), + }; + } + + private void ReadHeader(BinaryReader br) + { + while (true) + { + var count = br.ReadInt32(); + if (Table.Count != 0 && Table[Table.Count - 1].CaptureCount > count) + { + br.BaseStream.Position -= 4; + break; + } + var entries = br.ReadInt32(); + var group = new Group + { + CaptureCount = count, + EntryCount = entries, + Entries = new List(entries), + }; + Table.Add(group); + } + } + + public byte[] Write() + { + using (var ms = new MemoryStream()) + using (var bw = new BinaryWriter(ms)) + { + foreach (var g in Table) + { + bw.Write(g.CaptureCount); + bw.Write(g.Entries.Count); // instead of using the read value, use the list count (allow modification) + } + foreach (var g in Table) + { + foreach (var e in g.Entries) + { + bw.Write(e.Item); + bw.Write(e.Count); + bw.Write(e.Rate); + } + } + return ms.ToArray(); + } + } + + public IEnumerable Dump(string[] itemNames) + { + foreach (var g in Table) + { + yield return "========"; + yield return $"Count: {g.CaptureCount}"; + yield return "========"; + foreach (var item in g.Entries) + yield return $"{item.Rate:00}%\tx{item.Count}\t{itemNames[item.Item]}"; + yield return ""; + } + } + } +} diff --git a/pkNX.Structures/Misc/Heal.cs b/pkNX.Structures/Misc/Heal.cs new file mode 100644 index 00000000..9f40b34b --- /dev/null +++ b/pkNX.Structures/Misc/Heal.cs @@ -0,0 +1,15 @@ +namespace pkNX.Structures +{ + /// + /// Indicates how much a heal item/move heals. + /// + /// Any other non-enumerated value will be treated as a fixed value heal equal to the value. + public enum Heal : byte + { + None = 0, + + Quarter = 253, + Half = 254, + Full = 255, + } +} diff --git a/pkNX.Structures/Move/Move.cs b/pkNX.Structures/Move/Move.cs new file mode 100644 index 00000000..ccb99339 --- /dev/null +++ b/pkNX.Structures/Move/Move.cs @@ -0,0 +1,41 @@ +namespace pkNX.Structures +{ + public abstract class Move + { + protected readonly byte[] Data; + protected abstract int SIZE { get; } + protected Move(byte[] data = null) => Data = data ?? new byte[SIZE]; + + public byte[] Write() => Data; + + public abstract int Type { get; set; } + public abstract int Quality { get; set; } + public abstract int Category { get; set; } + public abstract int Power { get; set; } + public abstract int Accuracy { get; set; } + public abstract int PP { get; set; } + public abstract int Priority { get; set; } + public abstract int HitMin { get; set; } + public abstract int HitMax { get; set; } + public abstract int Inflict { get; set; } + public abstract int InflictPercent { get; set; } + public abstract MoveInflictDuration InflictCount { get; set; } + public abstract int TurnMin { get; set; } + public abstract int TurnMax { get; set; } + public abstract int CritStage { get; set; } + public abstract int Flinch { get; set; } + public abstract int EffectSequence { get; set; } + public abstract int Recoil { get; set; } + public abstract Heal Healing { get; set; } + public abstract MoveTarget Target { get; set; } + public abstract int Stat1 { get; set; } + public abstract int Stat2 { get; set; } + public abstract int Stat3 { get; set; } + public abstract int Stat1Stage { get; set; } + public abstract int Stat2Stage { get; set; } + public abstract int Stat3Stage { get; set; } + public abstract int Stat1Percent { get; set; } + public abstract int Stat2Percent { get; set; } + public abstract int Stat3Percent { get; set; } + } +} \ No newline at end of file diff --git a/pkNX.Structures/Move/Move6.cs b/pkNX.Structures/Move/Move6.cs new file mode 100644 index 00000000..0c258a18 --- /dev/null +++ b/pkNX.Structures/Move/Move6.cs @@ -0,0 +1,43 @@ +using System; + +namespace pkNX.Structures +{ + public class Move6 : Move + { + protected override int SIZE => 0x22; + public Move6() { } + public Move6(byte[] data = null) : base(data) { } + + public override int Type { get => Data[0x00]; set => Data[0x00] = (byte)value; } + public override int Quality { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public override int Category { get => Data[0x02]; set => Data[0x02] = (byte)value; } + public override int Power { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public override int Accuracy { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public override int PP { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public override int Priority { get => Data[0x06]; set => Data[0x06] = (byte)value; } + public override int HitMin { get => Data[0x07] & 0xF; set => Data[0x07] = (byte)(HitMax << 4 | value); } + public override int HitMax { get => Data[0x07] >> 4; set => Data[0x07] = (byte)(value << 4 | HitMin); } + public override int Inflict { get => BitConverter.ToUInt16(Data, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x08); } + public override int InflictPercent { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } + public override MoveInflictDuration InflictCount { get => (MoveInflictDuration)Data[0x0B]; set => Data[0x0B] = (byte)value; } + public override int TurnMin { get => Data[0x0C]; set => Data[0x0C] = (byte)value; } + public override int TurnMax { get => Data[0x0D]; set => Data[0x0D] = (byte)value; } + public override int CritStage { get => Data[0x0E]; set => Data[0x0E] = (byte)value; } + public override int Flinch { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } + public override int EffectSequence { get => BitConverter.ToUInt16(Data, 0x10); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x10); } + public override int Recoil { get => Data[0x12]; set => Data[0x12] = (byte)value; } + public override Heal Healing { get => (Heal)Data[0x13]; set => Data[0x13] = (byte)value; } + public override MoveTarget Target { get => (MoveTarget)Data[0x14]; set => Data[0x14] = (byte)value; } + public override int Stat1 { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public override int Stat2 { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public override int Stat3 { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public override int Stat1Stage { get => Data[0x18]; set => Data[0x18] = (byte)value; } + public override int Stat2Stage { get => Data[0x19]; set => Data[0x19] = (byte)value; } + public override int Stat3Stage { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } + public override int Stat1Percent { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } + public override int Stat2Percent { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } + public override int Stat3Percent { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + + public MoveFlag6 Flags { get => (MoveFlag6)BitConverter.ToUInt32(Data, 0x1E); set => BitConverter.GetBytes((uint)value).CopyTo(Data, 0x1E); } + } +} diff --git a/pkNX.Structures/Move/Move7.cs b/pkNX.Structures/Move/Move7.cs new file mode 100644 index 00000000..494f5937 --- /dev/null +++ b/pkNX.Structures/Move/Move7.cs @@ -0,0 +1,50 @@ +using System; + +namespace pkNX.Structures +{ + public class Move7 : Move + { + protected override int SIZE => 0x28; + public Move7() { } + public Move7(byte[] data = null) : base(data) { } + + public override int Type { get => Data[0x00]; set => Data[0x00] = (byte)value; } + public override int Quality { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public override int Category { get => Data[0x02]; set => Data[0x02] = (byte)value; } + public override int Power { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public override int Accuracy { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public override int PP { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public override int Priority { get => Data[0x06]; set => Data[0x06] = (byte)value; } + public override int HitMin { get => Data[0x07] & 0xF; set => Data[0x07] = (byte)(HitMax << 4 | value); } + public override int HitMax { get => Data[0x07] >> 4; set => Data[0x07] = (byte)(value << 4 | HitMin); } + public override int Inflict { get => BitConverter.ToUInt16(Data, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x08); } + public override int InflictPercent { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } + public override MoveInflictDuration InflictCount { get => (MoveInflictDuration)Data[0x0B]; set => Data[0x0B] = (byte)value; } + public override int TurnMin { get => Data[0x0C]; set => Data[0x0C] = (byte)value; } + public override int TurnMax { get => Data[0x0D]; set => Data[0x0D] = (byte)value; } + public override int CritStage { get => Data[0x0E]; set => Data[0x0E] = (byte)value; } + public override int Flinch { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } + public override int EffectSequence { get => BitConverter.ToUInt16(Data, 0x10); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x10); } + public override int Recoil { get => Data[0x12]; set => Data[0x12] = (byte)value; } + public override Heal Healing { get => (Heal)Data[0x13]; set => Data[0x13] = (byte)value; } + public override MoveTarget Target { get => (MoveTarget)Data[0x14]; set => Data[0x14] = (byte)value; } + public override int Stat1 { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public override int Stat2 { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public override int Stat3 { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public override int Stat1Stage { get => Data[0x18]; set => Data[0x18] = (byte)value; } + public override int Stat2Stage { get => Data[0x19]; set => Data[0x19] = (byte)value; } + public override int Stat3Stage { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } + public override int Stat1Percent { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } + public override int Stat2Percent { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } + public override int Stat3Percent { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + + public int ZMove { get => BitConverter.ToUInt16(Data, 0x1E); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x1E); } // 32 + public int ZPower { get => Data[0x20]; set => Data[0x20] = (byte)value; } // 33 + public int ZEffect { get => Data[0x21]; set => Data[0x21] = (byte)value; } // 34 + + public RefreshType RefreshAfflictType { get => (RefreshType)Data[0x22]; set => Data[0x22] = (byte)value; } // 35 + public int RefreshAfflictPercent { get => Data[0x23]; set => Data[0x23] = (byte)value; } // 36 + + public MoveFlag7 Flags { get => (MoveFlag7)BitConverter.ToUInt32(Data, 0x24); set => BitConverter.GetBytes((uint)value).CopyTo(Data, 0x24); } + } +} diff --git a/pkNX.Structures/Move/MoveFlag6.cs b/pkNX.Structures/Move/MoveFlag6.cs new file mode 100644 index 00000000..813c623a --- /dev/null +++ b/pkNX.Structures/Move/MoveFlag6.cs @@ -0,0 +1,46 @@ +using System; + +namespace pkNX.Structures +{ + [Flags] + public enum MoveFlag6 : uint + { + None, + + MakesContact = 1u << 00, // Makes contact. + Charge = 1u << 01, // The user is unable to make a move between turns. + Recharge = 1u << 02, // If this move is successful, the user must recharge on the following turn and cannot make a move. + Protect = 1u << 03, // Blocked by Detect, Protect, Spiky Shield, and if not a Status move, King's Shield. + Reflectable = 1u << 04, // Bounced back to the original user by Magic Coat or the Magic Bounce Ability. + Snatch = 1u << 05, // Can be stolen from the original user and instead used by another Pokemon using Snatch. + Mirror = 1u << 06, // Can be copied by Mirror Move. + Punch = 1u << 07, // Power is multiplied when used by a Pokemon with the Iron Fist Ability. + + Sound = 1u << 08, // Has no effect on Pokemon with the Soundproof Ability. + Gravity = 1u << 09, // Prevented from being executed or selected during Gravity's effect. + Defrost = 1u << 10, // Thaws the user if executed successfully while the user is frozen. + DistanceTriple = 1u << 11, // Can target a Pokemon positioned anywhere in a Triple Battle. + Heal = 1u << 12, // Prevented from being executed or selected during Heal Block's effect. + IgnoreSubstitute = 1u << 13, // Ignores a target's substitute. + FailSkyBattle = 1u << 14, // Prevented from being executed or selected in a Sky Battle. + AnimateAlly = 1u << 15, // Always animate the move when used on an ally. + + F17 = 1u << 16, // Dancer in future games + F18 = 1u << 17, + F19 = 1u << 18, + F20 = 1u << 19, + F21 = 1u << 20, + F22 = 1u << 21, + F23 = 1u << 22, + F24 = 1u << 23, + + F25 = 1u << 24, + F26 = 1u << 25, + F27 = 1u << 26, + F28 = 1u << 27, + F29 = 1u << 28, + F30 = 1u << 29, + F31 = 1u << 30, + F32 = 1u << 31, + } +} \ No newline at end of file diff --git a/pkNX.Structures/Move/MoveFlag7.cs b/pkNX.Structures/Move/MoveFlag7.cs new file mode 100644 index 00000000..4d8610c4 --- /dev/null +++ b/pkNX.Structures/Move/MoveFlag7.cs @@ -0,0 +1,46 @@ +using System; + +namespace pkNX.Structures +{ + [Flags] + public enum MoveFlag7 : uint + { + None, + + MakesContact = 1u << 00, // Makes contact. + Charge = 1u << 01, // The user is unable to make a move between turns. + Recharge = 1u << 02, // If this move is successful, the user must recharge on the following turn and cannot make a move. + Protect = 1u << 03, // Blocked by Detect, Protect, Spiky Shield, and if not a Status move, King's Shield. + Reflectable = 1u << 04, // Bounced back to the original user by Magic Coat or the Magic Bounce Ability. + Snatch = 1u << 05, // Can be stolen from the original user and instead used by another Pokemon using Snatch. + Mirror = 1u << 06, // Can be copied by Mirror Move. + Punch = 1u << 07, // Power is multiplied when used by a Pokemon with the Iron Fist Ability. + + Sound = 1u << 08, // Has no effect on Pokemon with the Soundproof Ability. + Gravity = 1u << 09, // Prevented from being executed or selected during Gravity's effect. + Defrost = 1u << 10, // Thaws the user if executed successfully while the user is frozen. + DistanceTriple = 1u << 11, // Can target a Pokemon positioned anywhere in a Triple Battle. + Heal = 1u << 12, // Prevented from being executed or selected during Heal Block's effect. + IgnoreSubstitute = 1u << 13, // Ignores a target's substitute. + FailSkyBattle = 1u << 14, // Prevented from being executed or selected in a Sky Battle. + AnimateAlly = 1u << 15, // Always animate the move when used on an ally. + + Dance = 1u << 16, // When used by a Pokemon, other Pokemon with the Dancer Ability can attempt to execute the same move. + F18 = 1u << 17, + F19 = 1u << 18, + F20 = 1u << 19, + F21 = 1u << 20, + F22 = 1u << 21, + F23 = 1u << 22, + F24 = 1u << 23, + + F25 = 1u << 24, + F26 = 1u << 25, + F27 = 1u << 26, + F28 = 1u << 27, + F29 = 1u << 28, + F30 = 1u << 29, + F31 = 1u << 30, + F32 = 1u << 31, + } +} \ No newline at end of file diff --git a/pkNX.Structures/Move/MoveFlagExtensions.cs b/pkNX.Structures/Move/MoveFlagExtensions.cs new file mode 100644 index 00000000..e9d2f1d3 --- /dev/null +++ b/pkNX.Structures/Move/MoveFlagExtensions.cs @@ -0,0 +1,15 @@ +namespace pkNX.Structures +{ + public static class MoveFlagExtensions + { + public static bool HasFlagFast(this MoveFlag6 value, MoveFlag6 flag) + { + return (value & flag) != 0; + } + + public static bool HasFlagFast(this MoveFlag7 value, MoveFlag7 flag) + { + return (value & flag) != 0; + } + } +} \ No newline at end of file diff --git a/pkNX.Structures/Move/MoveInflictDuration.cs b/pkNX.Structures/Move/MoveInflictDuration.cs new file mode 100644 index 00000000..849062b3 --- /dev/null +++ b/pkNX.Structures/Move/MoveInflictDuration.cs @@ -0,0 +1,11 @@ +namespace pkNX.Structures +{ + public enum MoveInflictDuration + { + None = 0, + Permanent, + TurnCountSwitch, + PermanentSwitch, + TurnCountNoSwitch, + }; +} \ No newline at end of file diff --git a/pkNX.Structures/Move/MoveTarget.cs b/pkNX.Structures/Move/MoveTarget.cs new file mode 100644 index 00000000..1330b603 --- /dev/null +++ b/pkNX.Structures/Move/MoveTarget.cs @@ -0,0 +1,23 @@ +namespace pkNX.Structures +{ + public enum MoveTarget : byte + { + // Specific target + AnyExceptSelf, + AllyOrSelf, + Ally, + Opponent, + AllAdjacent, + AllAdjacentOpponents, + AllAllies, + Self, + All, + RandomOpponent, + + // No pkm target + SideAll, + SideOpponent, + SideSelf, + Counter, + } +} \ No newline at end of file diff --git a/pkNX.Structures/Move/RefreshType.cs b/pkNX.Structures/Move/RefreshType.cs new file mode 100644 index 00000000..f11ae531 --- /dev/null +++ b/pkNX.Structures/Move/RefreshType.cs @@ -0,0 +1,11 @@ +namespace pkNX.Structures +{ + public enum RefreshType : byte + { + None, + Disheveled, + Mud, + Dust, + Dry, + } +} \ No newline at end of file diff --git a/pkNX.Structures/Personal/PersonalInfo.cs b/pkNX.Structures/Personal/PersonalInfo.cs new file mode 100644 index 00000000..63b784ed --- /dev/null +++ b/pkNX.Structures/Personal/PersonalInfo.cs @@ -0,0 +1,182 @@ +using System; + +namespace pkNX.Structures +{ + /// + /// Stat/misc data for individual species or their associated alternate forme data. + /// + public abstract class PersonalInfo + { + protected byte[] Data; + public abstract byte[] Write(); + public abstract int HP { get; set; } + public abstract int ATK { get; set; } + public abstract int DEF { get; set; } + public abstract int SPE { get; set; } + public abstract int SPA { get; set; } + public abstract int SPD { get; set; } + + public int[] Stats + { + get => new[] { HP, ATK, DEF, SPE, SPA, SPD }; + set + { + HP = value[0]; + ATK = value[1]; + DEF = value[2]; + SPE = value[3]; + SPA = value[4]; + SPD = value[5]; + } + } + + public abstract int EV_HP { get; set; } + public abstract int EV_ATK { get; set; } + public abstract int EV_DEF { get; set; } + public abstract int EV_SPE { get; set; } + public abstract int EV_SPA { get; set; } + public abstract int EV_SPD { get; set; } + public abstract int Type1 { get; set; } + public abstract int Type2 { get; set; } + public abstract int EggGroup1 { get; set; } + public abstract int EggGroup2 { get; set; } + public abstract int CatchRate { get; set; } + public virtual int EvoStage { get; set; } + public abstract int[] Items { get; set; } + public abstract int Gender { get; set; } + public abstract int HatchCycles { get; set; } + public abstract int BaseFriendship { get; set; } + public abstract int EXPGrowth { get; set; } + public abstract int [] Abilities { get; set; } + public abstract int EscapeRate { get; set; } + public virtual int FormeCount { get; set; } = 1; + protected internal virtual int FormStatsIndex { get; set; } + public virtual int FormeSprite { get; set; } + public abstract int BaseEXP { get; set; } + public abstract int Color { get; set; } + + public virtual int Height { get; set; } = 0; + public virtual int Weight { get; set; } = 0; + + public int[] Types + { + get => new[] { Type1, Type2 }; + set + { + if (value?.Length != 2) return; + Type1 = value[0]; + Type2 = value[1]; + } + } + + public int[] EggGroups + { + get => new[] { EggGroup1, EggGroup2 }; + set + { + if (value?.Length != 2) return; + EggGroup1 = (byte)value[0]; + EggGroup2 = (byte)value[1]; + } + } + + /// + /// TM/HM learn compatibility flags for individual moves. + /// + public bool[] TMHM { get; set; } + + /// + /// Grass-Fire-Water-Etc typed learn compatibility flags for individual moves. + /// + public bool[] TypeTutors { get; set; } + + /// + /// Special tutor learn compatibility flags for individual moves. + /// + public bool[][] SpecialTutors { get; set; } = Array.Empty(); + + protected static bool[] GetBits(byte[] data, int start = 0, int length = -1) + { + if (length < 0) + length = data.Length; + bool[] r = new bool[length << 3]; + for (int i = 0; i < r.Length; i++) + r[i] = (data[start + (i >> 3)] >> (i & 7) & 0x1) == 1; + return r; + } + + protected static byte[] SetBits(bool[] bits) + { + byte[] data = new byte[bits.Length>>3]; + for (int i = 0; i < bits.Length; i++) + data[i>>3] |= (byte)(bits[i] ? 1 << (i&0x7) : 0); + return data; + } + + /// + /// Injects supplementary TM/HM compatibility which is not present in the generation specific format. + /// + /// Data to read from + /// Starting offset to read at + /// Amount of bytes to decompose into bits + internal void AddTMHM(byte[] data, int start = 0, int length = -1) => TMHM = GetBits(data, start, length); + + /// + /// Injects supplementary Type Tutor compatibility which is not present in the generation specific format. + /// + /// Data to read from + /// Starting offset to read at + /// Amount of bytes to decompose into bits + internal void AddTypeTutors(byte[] data, int start = 0, int length = -1) => TypeTutors = GetBits(data, start, length); + + /// + /// Gets the entry index for the input criteria, with fallback for the original species entry. + /// + /// Species to retrieve for + /// Alternate Forme to retrieve for + /// Index the Alternate Forme exists as in the . + public int FormeIndex(int species, int forme) + { + if (forme <= 0) // no forme requested + return species; + if (FormStatsIndex <= 0) // no formes present + return species; + if (forme >= FormeCount) // beyond range of species' formes + return species; + + return FormStatsIndex + forme - 1; + } + + public int RandomGender + { + get + { + if (Genderless) + return 2; + if (OnlyFemale) + return 1; + if (OnlyMale) + return 0; + return Util.Rand.Next(2); + } + } + + public bool Genderless => Gender == 255; + public bool OnlyFemale => Gender == 254; + public bool OnlyMale => Gender == 0; + public bool HasFormes => FormeCount > 1; + public int BST => HP + ATK + DEF + SPE + SPA + SPD; + + public bool IsFormeWithinRange(int forme) + { + if (forme == 0) + return true; + return forme < FormeCount; + } + + public bool IsValidTypeCombination(int type1, int type2) => Type1 == type1 && Type2 == type2; + public bool IsType(int type1) => Type1 == type1 || Type2 == type1; + public bool IsType(int type1, int type2) => (Type1 == type1 || Type2 == type1) && (Type1 == type2 || Type2 == type2); + public bool IsEggGroup(int group) => EggGroup1 == group || EggGroup2 == group; + } +} diff --git a/pkNX.Structures/Personal/PersonalInfoB2W2.cs b/pkNX.Structures/Personal/PersonalInfoB2W2.cs new file mode 100644 index 00000000..a6fa4d5e --- /dev/null +++ b/pkNX.Structures/Personal/PersonalInfoB2W2.cs @@ -0,0 +1,39 @@ +namespace pkNX.Structures +{ + /// + /// class with values from the Black 2 & White 2 games. + /// + public class PersonalInfoB2W2 : PersonalInfoBW + { + public new const int SIZE = 0x4C; + + public PersonalInfoB2W2(byte[] data) + { + if (data.Length != SIZE) + return; + Data = data; + + // Unpack TMHM & Tutors + TMHM = GetBits(Data, 0x28, 0x10); + TypeTutors = GetBits(Data, 0x3, 0x4); + SpecialTutors = new[] + { + GetBits(Data, 0x3C, 0x04), + GetBits(Data, 0x40, 0x04), + GetBits(Data, 0x44, 0x04), + GetBits(Data, 0x48, 0x04), + }; + } + + public override byte[] Write() + { + SetBits(TMHM).CopyTo(Data, 0x28); + SetBits(TypeTutors).CopyTo(Data, 0x38); + SetBits(SpecialTutors[0]).CopyTo(Data, 0x3C); + SetBits(SpecialTutors[1]).CopyTo(Data, 0x40); + SetBits(SpecialTutors[2]).CopyTo(Data, 0x44); + SetBits(SpecialTutors[3]).CopyTo(Data, 0x48); + return Data; + } + } +} diff --git a/pkNX.Structures/Personal/PersonalInfoBW.cs b/pkNX.Structures/Personal/PersonalInfoBW.cs new file mode 100644 index 00000000..1884228a --- /dev/null +++ b/pkNX.Structures/Personal/PersonalInfoBW.cs @@ -0,0 +1,93 @@ +using System; + +namespace pkNX.Structures +{ + /// + /// class with values from the Black & White games. + /// + public class PersonalInfoBW : PersonalInfo + { + protected PersonalInfoBW() { } + public const int SIZE = 0x3C; + + public PersonalInfoBW(byte[] data) + { + if (data.Length != SIZE) + return; + Data = data; + + // Unpack TMHM & Tutors + TMHM = GetBits(Data, 0x28, 0x10); + TypeTutors = GetBits(Data, 0x38, 0x4); + } + + public override byte[] Write() + { + SetBits(TMHM).CopyTo(Data, 0x28); + SetBits(TypeTutors).CopyTo(Data, 0x38); + return Data; + } + + public override int HP { get => Data[0x00]; set => Data[0x00] = (byte)value; } + public override int ATK { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public override int DEF { get => Data[0x02]; set => Data[0x02] = (byte)value; } + public override int SPE { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public override int SPA { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public override int SPD { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public override int Type1 { get => Data[0x06]; set => Data[0x06] = (byte)value; } + public override int Type2 { get => Data[0x07]; set => Data[0x07] = (byte)value; } + public override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } + public override int EvoStage { get => Data[0x09]; set => Data[0x09] = (byte)value; } + private int EVYield { get => BitConverter.ToUInt16(Data, 0x0A); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0A); } + public override int EV_HP { get => EVYield >> 0 & 0x3; set => EVYield = (EVYield & ~(0x3 << 0)) | (value & 0x3) << 0; } + public override int EV_ATK { get => EVYield >> 2 & 0x3; set => EVYield = (EVYield & ~(0x3 << 2)) | (value & 0x3) << 2; } + public override int EV_DEF { get => EVYield >> 4 & 0x3; set => EVYield = (EVYield & ~(0x3 << 4)) | (value & 0x3) << 4; } + public override int EV_SPE { get => EVYield >> 6 & 0x3; set => EVYield = (EVYield & ~(0x3 << 6)) | (value & 0x3) << 6; } + public override int EV_SPA { get => EVYield >> 8 & 0x3; set => EVYield = (EVYield & ~(0x3 << 8)) | (value & 0x3) << 8; } + public override int EV_SPD { get => EVYield >> 10 & 0x3; set => EVYield = (EVYield & ~(0x3 << 10)) | (value & 0x3) << 10; } + public int Item1 { get => BitConverter.ToInt16(Data, 0x0C); set => BitConverter.GetBytes((short)value).CopyTo(Data, 0x0C); } + public int Item2 { get => BitConverter.ToInt16(Data, 0x0E); set => BitConverter.GetBytes((short)value).CopyTo(Data, 0x0E); } + public int Item3 { get => BitConverter.ToInt16(Data, 0x10); set => BitConverter.GetBytes((short)value).CopyTo(Data, 0x10); } + public override int Gender { get => Data[0x12]; set => Data[0x12] = (byte)value; } + public override int HatchCycles { get => Data[0x13]; set => Data[0x13] = (byte)value; } + public override int BaseFriendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public override int EXPGrowth { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public override int EggGroup1 { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public override int EggGroup2 { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public int Ability1 { get => Data[0x18]; set => Data[0x18] = (byte)value; } + public int Ability2 { get => Data[0x19]; set => Data[0x19] = (byte)value; } + public int AbilityH { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } + public override int EscapeRate { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } + protected internal override int FormStatsIndex { get => BitConverter.ToUInt16(Data, 0x1C); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x1C); } + public override int FormeSprite { get => BitConverter.ToUInt16(Data, 0x1E); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x1E); } + public override int FormeCount { get => Data[0x20]; set => Data[0x20] = (byte)value; } + public override int Color { get => Data[0x21]; set => Data[0x21] = (byte)value; } + public override int BaseEXP { get => BitConverter.ToUInt16(Data, 0x22); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x22); } + public override int Height { get => BitConverter.ToUInt16(Data, 0x24); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x24); } + public override int Weight { get => BitConverter.ToUInt16(Data, 0x26); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x26); } + + public override int[] Items + { + get => new[] { Item1, Item2, Item3 }; + set + { + if (value?.Length != 3) return; + Item1 = value[0]; + Item2 = value[1]; + Item3 = value[2]; + } + } + + public override int[] Abilities + { + get => new[] { Ability1, Ability2, AbilityH }; + set + { + if (value?.Length != 3) return; + Ability1 = (byte)value[0]; + Ability2 = (byte)value[1]; + AbilityH = (byte)value[2]; + } + } + } +} diff --git a/pkNX.Structures/Personal/PersonalInfoGG.cs b/pkNX.Structures/Personal/PersonalInfoGG.cs new file mode 100644 index 00000000..ebf245b8 --- /dev/null +++ b/pkNX.Structures/Personal/PersonalInfoGG.cs @@ -0,0 +1,13 @@ +using System; + +namespace pkNX.Structures +{ + /// + /// class with values from the LGPE games. + /// + public class PersonalInfoGG : PersonalInfoSM + { + public PersonalInfoGG(byte[] data) : base(data) { } + public int GoSpecies { get => BitConverter.ToUInt16(Data, 0x48); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x48); } + } +} diff --git a/pkNX.Structures/Personal/PersonalInfoORAS.cs b/pkNX.Structures/Personal/PersonalInfoORAS.cs new file mode 100644 index 00000000..a26e90dc --- /dev/null +++ b/pkNX.Structures/Personal/PersonalInfoORAS.cs @@ -0,0 +1,40 @@ +namespace pkNX.Structures +{ + /// + /// class with values from the OR & AS games. + /// + public class PersonalInfoORAS : PersonalInfoXY + { + public new const int SIZE = 0x50; + + public PersonalInfoORAS(byte[] data) + { + if (data.Length != SIZE) + return; + Data = data; + + // Unpack TMHM & Tutors + TMHM = GetBits(Data, 0x28, 0x10); + TypeTutors = GetBits(Data, 0x38, 0x4); + // 0x3C-0x40 unknown + SpecialTutors = new[] + { + GetBits(Data, 0x40, 0x04), + GetBits(Data, 0x44, 0x04), + GetBits(Data, 0x48, 0x04), + GetBits(Data, 0x4C, 0x04), + }; + } + + public override byte[] Write() + { + SetBits(TMHM).CopyTo(Data, 0x28); + SetBits(TypeTutors).CopyTo(Data, 0x38); + SetBits(SpecialTutors[0]).CopyTo(Data, 0x40); + SetBits(SpecialTutors[1]).CopyTo(Data, 0x44); + SetBits(SpecialTutors[2]).CopyTo(Data, 0x48); + SetBits(SpecialTutors[3]).CopyTo(Data, 0x4C); + return Data; + } + } +} diff --git a/pkNX.Structures/Personal/PersonalInfoSM.cs b/pkNX.Structures/Personal/PersonalInfoSM.cs new file mode 100644 index 00000000..90dd6788 --- /dev/null +++ b/pkNX.Structures/Personal/PersonalInfoSM.cs @@ -0,0 +1,40 @@ +using System; + +namespace pkNX.Structures +{ + /// + /// class with values from the Sun/Moon games. + /// + public class PersonalInfoSM : PersonalInfoXY + { + public new const int SIZE = 0x54; + + public PersonalInfoSM(byte[] data) + { + if (data.Length != SIZE) + return; + Data = data; + + TMHM = GetBits(Data, 0x28, 0x10); // 36-39 + TypeTutors = GetBits(Data, 0x38, 0x4); // 40 + + SpecialTutors = new[] + { + GetBits(Data, 0x3C, 0x0A), + }; + } + + public override byte[] Write() + { + SetBits(TMHM).CopyTo(Data, 0x28); + SetBits(TypeTutors).CopyTo(Data, 0x38); + SetBits(SpecialTutors[0]).CopyTo(Data, 0x3C); + return Data; + } + + public int SpecialZ_Item { get => BitConverter.ToUInt16(Data, 0x4C); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x4C); } + public int SpecialZ_BaseMove { get => BitConverter.ToUInt16(Data, 0x4E); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x4E); } + public int SpecialZ_ZMove { get => BitConverter.ToUInt16(Data, 0x50); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x50); } + public bool LocalVariant { get => Data[0x52] == 1; set => Data[0x52] = (byte)(value ? 1 : 0); } + } +} diff --git a/pkNX.Structures/Personal/PersonalInfoXY.cs b/pkNX.Structures/Personal/PersonalInfoXY.cs new file mode 100644 index 00000000..85d2764a --- /dev/null +++ b/pkNX.Structures/Personal/PersonalInfoXY.cs @@ -0,0 +1,30 @@ +namespace pkNX.Structures +{ + /// + /// class with values from the X & Y games. + /// + public class PersonalInfoXY : PersonalInfoBW + { + protected PersonalInfoXY() { } // For ORAS + public new const int SIZE = 0x40; + + public PersonalInfoXY(byte[] data) + { + if (data.Length != SIZE) + return; + Data = data; + + // Unpack TMHM & Tutors + TMHM = GetBits(Data, 0x28, 0x10); + TypeTutors = GetBits(Data, 0x38, 0x4); + // 0x3C-0x40 unknown + } + + public override byte[] Write() + { + SetBits(TMHM).CopyTo(Data, 0x28); + SetBits(TypeTutors).CopyTo(Data, 0x38); + return Data; + } + } +} diff --git a/pkNX.Structures/Personal/PersonalTable.cs b/pkNX.Structures/Personal/PersonalTable.cs new file mode 100644 index 00000000..4f8e7405 --- /dev/null +++ b/pkNX.Structures/Personal/PersonalTable.cs @@ -0,0 +1,158 @@ +using System; + +namespace pkNX.Structures +{ + public class PersonalTable + { + private static byte[][] SplitBytes(byte[] data, int size) + { + byte[][] r = new byte[data.Length / size][]; + for (int i = 0; i < data.Length; i += size) + { + r[i / size] = new byte[size]; + Array.Copy(data, i, r[i / size], 0, size); + } + return r; + } + + public PersonalTable(byte[] data, GameVersion format) + { + int size = 0; + switch (format) + { + case GameVersion.XY: size = PersonalInfoXY.SIZE; break; + case GameVersion.ORASDEMO: + case GameVersion.ORAS: size = PersonalInfoORAS.SIZE; break; + case GameVersion.SMDEMO: + case GameVersion.SM: + case GameVersion.USUM: size = PersonalInfoSM.SIZE; break; + } + + if (size == 0) + { Table = null; return; } + + byte[][] entries = SplitBytes(data, size); + PersonalInfo[] d = new PersonalInfo[data.Length / size]; + + switch (format) + { + case GameVersion.XY: + for (int i = 0; i < d.Length; i++) + d[i] = new PersonalInfoXY(entries[i]); + break; + case GameVersion.ORASDEMO: + case GameVersion.ORAS: + for (int i = 0; i < d.Length; i++) + d[i] = new PersonalInfoORAS(entries[i]); + break; + case GameVersion.SMDEMO: + case GameVersion.SM: + case GameVersion.USUM: + for (int i = 0; i < d.Length; i++) + d[i] = new PersonalInfoSM(entries[i]); + break; + } + Table = d; + } + + public readonly PersonalInfo[] Table; + + public PersonalInfo this[int index] + { + get + { + if (index < Table.Length) + return Table[index]; + return Table[0]; + } + set + { + if (index < Table.Length) + return; + Table[index] = value; + } + } + + public int[] GetAbilities(int species, int forme) + { + if (species >= Table.Length) + { species = 0; Console.WriteLine("Requested out of bounds SpeciesID"); } + return this[GetFormeIndex(species, forme)].Abilities; + } + + public int GetFormeIndex(int species, int forme) + { + if (species >= Table.Length) + { species = 0; Console.WriteLine("Requested out of bounds SpeciesID"); } + return this[species].FormeIndex(species, forme); + } + + public PersonalInfo GetFormeEntry(int species, int forme) + { + return this[GetFormeIndex(species, forme)]; + } + + /// + /// Gets form names for every species. + /// + /// Raw string resource (Species) for the corresponding table. + /// Max Species ID + /// Array of species containing an array of form names for that species. + public string[][] GetFormList(string[] species, int MaxSpecies) + { + string[][] FormList = new string[MaxSpecies + 1][]; + for (int i = 0; i < FormList.Length; i++) + { + int FormCount = this[i].FormeCount; + FormList[i] = new string[FormCount]; + if (FormCount <= 0) continue; + FormList[i][0] = species[i]; + for (int j = 1; j < FormCount; j++) + FormList[i][j] = $"{species[i]} {j}"; + } + + return FormList; + } + + public string[] GetPersonalEntryList(string[][] AltForms, string[] species, int MaxSpecies, out int[] baseForm, out int[] formVal) + { + string[] result = new string[Table.Length]; + baseForm = new int[result.Length]; + formVal = new int[result.Length]; + for (int i = 0; i <= MaxSpecies; i++) + { + result[i] = species[i]; + if (AltForms[i].Length == 0) continue; + int altformpointer = this[i].FormStatsIndex; + if (altformpointer <= 0) continue; + for (int j = 1; j < AltForms[i].Length; j++) + { + int ptr = altformpointer + j - 1; + baseForm[ptr] = i; + formVal[ptr] = j; + result[ptr] = AltForms[i][j]; + } + } + return result; + } + + public int[] GetSpeciesForm(int PersonalEntry, int MaxSpeciesID) + { + if (PersonalEntry < MaxSpeciesID) return new[] { PersonalEntry, 0 }; + + for (int i = 0; i < MaxSpeciesID; i++) + { + var altformpointer = this[i].FormStatsIndex; + if (altformpointer <= 0) continue; + int FormCount = this[i].FormeCount - 1; // Mons with no alt forms have a FormCount of 1. + for (int j = 0; j < FormCount; j++) + { + if (altformpointer + j == PersonalEntry) + return new[] { i, j }; + } + } + + return new[] { -1, -1 }; + } + } +} diff --git a/pkNX.Structures/Scripts/PawnUtil.cs b/pkNX.Structures/Scripts/PawnUtil.cs new file mode 100644 index 00000000..4c95f97c --- /dev/null +++ b/pkNX.Structures/Scripts/PawnUtil.cs @@ -0,0 +1,769 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace pkNX.Structures +{ + public static class PawnUtil + { + // FireFly's (github.com/FireyFly) concise decompression (ported c->c#): + // https://github.com/FireyFly/poketools/blob/e74538a5b5e5dab1e78c1cd313c55d158f37534d/src/formats/script.c#L61 + public static uint[] QuickDecompress(byte[] data, int count) + { + uint[] code = new uint[count]; + uint i = 0, j = 0, x = 0, f = 0; + while (i < code.Length) + { + int b = data[f++], + v = b & 0x7F; + if (++j == 1) // sign extension possible + x = (uint)((((v >> 6 == 0 ? 1 : 0) - 1) << 6) | v); // only for bit6 being set + else + x = (x << 7) | (byte)v; // shift data into place + + if ((b & 0x80) != 0) + continue; // more data to read + code[i++] = x; j = 0; // write finalized instruction + } + return code; + } + + // Compression + public static byte[] CompressScript(byte[] data) + { + if (data == null || data.Length % 4 != 0) // Bad Input + return null; + using (MemoryStream mn = new MemoryStream()) + using (BinaryWriter bw = new BinaryWriter(mn)) + { + int pos = 0; + while (pos < data.Length) + { + byte[] db = data.Skip(pos += 4).Take(4).ToArray(); + byte[] cb = CompressBytes(db); + bw.Write(cb); + } + return mn.ToArray(); + } + } + + public static byte[] CompressBytes(byte[] db) + { + short cmd = BitConverter.ToInt16(db, 0); + short val = BitConverter.ToInt16(db, 2); + + byte[] cb = Array.Empty(); + bool sign4 = val < 0 && cmd < 0 && db[0] >= 0xC0; // 4 byte signed + bool sign3 = val < 0 && cmd < 0 && db[0] < 0xC0; // 3 byte signed + bool sign2 = val < 0 && cmd > 0; // 2 byte signed + bool liter = cmd >= 0 && cmd < 0x40; // Literal + bool manyb = cmd >= 0x40; // manybit + + if (sign4) + { + int dev = 0x40 + BitConverter.ToInt32(db, 0); + if (dev < 0) // BADLOGIC + return cb; + cb = new[] { (byte)((dev & 0x3F) | 0x40) }; + } + else if (sign3) + { + byte dev = (byte)(((db[1] << 1) + 0x40) | 0xC0 | db[0] >> 7); + byte low = db[0]; + cb = new[] { dev, low }; + } + else if (sign2) + { + if (manyb) + { + byte dev = (byte)(((db[2] << 2) + 0x40) | 0xC0 | db[1] >> 7); + byte low1 = (byte)(0x80 | (db[0] >> 7) | (db[1] & 0x80)); + byte low0 = (byte)(db[0] & 0x80); + cb = new[] { low0, low1, dev }; + } + else // Dunno if this ever naturally happens; the command reader may ignore db[1] if db[0] < 0x80... needs verification. + { + byte dev = (byte)(((db[1] << 2) + 0x40) | 0xC0 | db[0] >> 6); + byte low0 = (byte)(db[0] & 0x3F); + cb = new[] { low0, dev }; + } + } + else if (manyb) + { + ulong bitStorage = 0; + + uint dv = BitConverter.ToUInt32(db, 0); + int ctr = 0; + while (dv != 0) // bits remaining + { + byte bits = (byte)((byte)dv & 0x7F); dv >>= 7; // Take off 7 bits at a time + bitStorage |= (byte)(bits << (ctr * 8)); // Write the 7 bits into storage + bitStorage |= (byte)(1 << (7 + (ctr++ * 8))); // continue reading flag + } + byte[] compressedBits = BitConverter.GetBytes(bitStorage); + + Array.Reverse(compressedBits); + // Trim off leading zero-bytes + cb = compressedBits.SkipWhile(v => v == 0).ToArray(); + } + else if (liter) + { + cb = new[] { (byte)cmd }; + } + return cb; + } + + // Interpreting + public static string[] ParseScript(uint[] cmd, int sanity = -1) + { + // sub_148CBC Moon v1.0 + List parse = new List(); + int sanityMode = 0; + + int i = 0; // Current Offset of decompressed instructions + while (i < cmd.Length) // read away + { + // Read a Command + int line = i; + uint c = cmd[i++]; + + string op; + switch (c & 0x7FFF) + { + default: + throw new ArgumentException("Invalid Command ID"); + case 0x01: + case 0x02: + case 0x05: + case 0x06: + case 0x0F: + case 0x10: + case 0x13: + case 0x14: + case 0x6D: + case 0x72: + { + // Peek at next value + var next = (int)cmd[i++]; + // Check Value against negative and zero... ? + + op = EA(c, next); + break; + } + case 0x03: + case 0x04: + case 0x07: + case 0x08: + case 0x11: + case 0x12: + case 0x15: + case 0x16: + case 0x6E: + case 0x73: + { + // Peek at next value + var next = (int)cmd[i++]; + // Check Value against negative... ? + + op = EA(c, next); + break; + } + case 0x09: + case 0x17: + case 0x19: + case 0x1B: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x2A: + case 0x2B: + case 0x2E: // Begin + case 0x2F: + case 0x30: // Return + case 0x41: + case 0x42: + case 0x43: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: // Add? + case 0x4F: + case 0x50: + case 0x51: // Cmp? + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x59: // ClearAll + case 0x5A: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x6B: + case 0x6C: + case 0x6F: + case 0x70: + case 0x71: + case 0x74: + case 0x7A: + case 0x83: + case 0x84: + case 0x86: + case 0x89: // LineNo? + case 0xAA: + case 0xAB: // PushConst2 + case 0xAC: // CmpConst2 + case 0xAD: + case 0xAE: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: // PushConst + case 0xBD: + case 0xBE: + case 0xBF: // AdjustStack + case 0xC0: + case 0xC1: + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: // CmpLocal + case 0xC9: // CmpConst + case 0xCA: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + { + // no sanity checks + var arg = (short)(c >> 16); + + op = EA(c & 0xFF, arg); + + if ((c & 0xFF) == 0x30) // return + op += Environment.NewLine; + break; + } + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x18: + case 0x1A: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case 0x20: + case 0x26: + case 0x27: // PushConst + case 0x28: + case 0x29: + case 0x2C: + case 0x2D: + case 0x34: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x57: + case 0x58: + case 0x5B: + case 0x5C: + case 0x69: + case 0x6A: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x85: + { + var next = (int)cmd[i++]; + // No sanity check needed + + op = EA(c, next); + break; + } + + case 0x31: // CallFunc + case 0x33: + case 0x35: // Jump!= + case 0x36: // Jump== + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x81: // Jump + { + var delta = (int)cmd[i++]; + // sanity check range... + // negative.. weird + + int newOfs = (line * 4) + delta; + op = $"{Commands[c]} => 0x{newOfs:X4} ({delta})"; + break; + } + case 0x7B: + { + var next = (int)cmd[i++]; + sanityMode |= 1; // flag mode 1 + + op = EA(c, next); + break; + } + case 0x82: // JumpIfElse + { + //var jOffset = (i * 4) - 4; // this may be the correct jump start point... + var count = cmd[i++]; // switch case table + // sanity check + + // Populate If-Case Tree + var tree = new List(); + + // Cases + for (int j = 0; j < count; j++) + { + var jmp = (int)cmd[i++]; + var toOffset = ((i - 2) * 4) + jmp; + var ifValue = (int)cmd[i++]; + tree.Add($"\t{ifValue} => 0x{toOffset:X4} ({jmp})"); + } + // Default + { + int jmp = (int)cmd[i++]; + var toOffset = ((i - 2) * 4) + jmp; + tree.Add($"\t* => 0x{toOffset:X4} ({jmp})"); + } + + op = Commands[c] + Environment.NewLine + string.Join(Environment.NewLine, tree); + break; + } + case 0x87: + { + var next1 = (int)cmd[i++]; + var next2 = (int)cmd[i++]; + sanityMode |= 2; // flag mode 2 + + op = EA(c, next1, next2); + break; + } + + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x9C: + case 0x9D: + { + var next1 = (int)cmd[i++]; + var next2 = (int)cmd[i++]; + + op = EA(c, next1, next2); + break; + } + + case 0x8E: + case 0x8F: + case 0x90: + case 0x91: + { + var next1 = cmd[i++]; + var next2 = cmd[i++]; + var next3 = cmd[i++]; + + op = EF(c, next1, next2, next3); + break; + } + + case 0x92: + case 0x93: + case 0x94: + case 0x95: + { + var next1 = (int)cmd[i++]; + var next2 = (int)cmd[i++]; + var next3 = (int)cmd[i++]; + var next4 = (int)cmd[i++]; + + op = EA(c, next1, next2, next3, next4); + break; + } + + case 0x96: // float + case 0x97: + case 0x98: + case 0x99: + { + var next1 = cmd[i++]; + var next2 = cmd[i++]; + var next3 = cmd[i++]; + var next4 = cmd[i++]; + var next5 = cmd[i++]; + + op = EF(c, next1, next2, next3, next4, next5); + break; + } + + case 0x9A: + { + var next1 = (int)cmd[i++]; + var next2 = (int)cmd[i++]; + // a bunch of sanity checking + + op = EA(c, next1, next2); + break; + } + + case 0x9B: // Copy + { + var next1 = (int)cmd[i++]; + var next2 = (int)cmd[i++]; + // a bunch of sanity checking + + op = EA(c, next1, next2); + break; + } + + case 0x9E: + { + var next1 = (int)cmd[i++]; + // perm check a1 + 0x14 + // can return error code 0x1C + + op = EA(c, next1); + break; + } + + case 0x9F: + { + // perm check a1 + 0x14 + // same permission checking as 0x9E + // can return error code 0x1C + + op = EA(c); + break; + } + + case 0xA1: // Goto + { + // minimal sanity checks + // can return error code 0x1C + int newPos = i + (int)(1 + (2 * (cmd[i] / 4)) + 1); + + op = EA(c, newPos); + break; + } + + case 0xA2: // GetGlobal2 + case 0xA3: // GetGlobal + case 0xA6: + case 0xA7: + case 0xAF: // SetGlobal + case 0xB0: + case 0xB3: + case 0xB4: + case 0xCB: + case 0xCD: + { + // sanity check arg + var arg = (short)(c >> 16); + + op = EA(c & 0xFF, arg); + break; + } + case 0xA4: // GetGlobal4 + case 0xA5: + case 0xA8: + case 0xA9: + case 0xB1: // SetLocal + case 0xB2: + case 0xB5: + case 0xB6: + case 0xCC: + case 0xCE: + { + // sanity check arg, slightly different + var arg = (short)(c >> 16); + + op = EA(c & 0xFF, arg); + break; + } + } + parse.Add($"0x{line * 4:X4}: [{c & 0x7FF:X2}] {op}"); + } + + if (sanity >= 0 && sanity != sanityMode) + throw new ArgumentException(); + + return parse.ToArray(); + } + + internal static string[] ParseMovement(uint[] cmd) => Util.GetHexLines(cmd); + + internal static string EA(uint c, params int[] arr) + { + string cmd = Commands[c]; + string parameters = arr.Length == 0 ? "" : string.Join(", ", arr.Select(z => Math.Abs(z) < 100 ? z.ToString() : "0x" + z.ToString("X4"))); + return $"{cmd}({parameters})"; + } + + private static readonly Func getFloat = val => BitConverter.ToSingle(BitConverter.GetBytes(val), 0); + + internal static string EF(uint c, params uint[] arr) + { + string cmd = Commands[c]; + string parameters = arr.Length == 1 ? "" : string.Join(", ", arr.Select(getFloat)); + return $"{cmd}({parameters})"; + } + + internal static readonly Dictionary Commands = new Dictionary + {// { 0x00, "$00" }, // Invalid Code + { 0x01, "$01" }, + { 0x02, "$02" }, + { 0x03, "$03" }, + { 0x04, "$04" }, + { 0x05, "$05" }, + { 0x06, "$06" }, + { 0x07, "$07" }, + { 0x08, "$08" }, + { 0x09, "$09" }, + { 0x0A, "$0A" }, + { 0x0B, "$0B" }, + { 0x0C, "$0C" }, + { 0x0D, "$0D" }, + { 0x0E, "$0E" }, + { 0x0F, "$0F" }, + { 0x10, "$10" }, + { 0x11, "$11" }, + { 0x12, "$12" }, + { 0x13, "$13" }, + { 0x14, "$14" }, + { 0x15, "$15" }, + { 0x16, "$16" }, + { 0x17, "$17" }, + { 0x18, "$18" }, + { 0x19, "$19" }, + { 0x1A, "$1A" }, + { 0x1B, "$1B" }, + { 0x1C, "$1C" }, + { 0x1D, "$1D" }, + { 0x1E, "$1E" }, + { 0x1F, "$1F" }, + { 0x20, "$20" }, + { 0x21, "$21" }, + { 0x22, "$22" }, + { 0x23, "$23" }, + { 0x24, "$24" }, + { 0x25, "$25" }, + { 0x26, "$26" }, + { 0x27, "PushConst" }, + { 0x28, "$28" }, + { 0x29, "$29" }, + { 0x2A, "$2A" }, + { 0x2B, "$2B" }, + { 0x2C, "$2C" }, + { 0x2D, "$2D" }, + { 0x2E, "Begin" }, + { 0x2F, "$2F" }, + { 0x30, "Return" }, + { 0x31, "CallFunc" }, + { 0x32, "$32" }, + { 0x33, "$33" }, + { 0x34, "$34" }, + { 0x35, "BNE" }, + { 0x36, "BEQ" }, + { 0x37, "$37" }, + { 0x38, "$38" }, + { 0x39, "$39" }, + { 0x3A, "$3A" }, + { 0x3B, "$3B" }, + { 0x3C, "$3C" }, + { 0x3D, "$3D" }, + { 0x3E, "$3E" }, + { 0x3F, "$3F" }, + { 0x40, "$40" }, + { 0x41, "$41" }, + { 0x42, "$42" }, + { 0x43, "$43" }, + { 0x44, "$44" }, + { 0x45, "$45" }, + { 0x46, "$46" }, + { 0x47, "$47" }, + { 0x48, "$48" }, + { 0x49, "$49" }, + { 0x4A, "$4A" }, + { 0x4B, "$4B" }, + { 0x4C, "$4C" }, + { 0x4D, "$4D" }, + { 0x4E, "Add?" }, + { 0x4F, "$4F" }, + { 0x50, "$50" }, + { 0x51, "Cmp?" }, + { 0x52, "$52" }, + { 0x53, "$53" }, + { 0x54, "$54" }, + { 0x55, "$55" }, + { 0x56, "$56" }, + { 0x57, "$57" }, + { 0x58, "$58" }, + { 0x59, "ClearAll" }, + { 0x5A, "$5A" }, + { 0x5B, "$5B" }, + { 0x5C, "$5C" }, + { 0x5D, "$5D" }, + { 0x5E, "$5E" }, + { 0x5F, "$5F" }, + { 0x60, "$60" }, + { 0x61, "$61" }, + { 0x62, "$62" }, + { 0x63, "$63" }, + { 0x64, "$64" }, + { 0x65, "$65" }, + { 0x66, "$66" }, + { 0x67, "$67" }, + { 0x68, "$68" }, + { 0x69, "$69" }, + { 0x6A, "$6A" }, + { 0x6B, "$6B" }, + { 0x6C, "$6C" }, + { 0x6D, "$6D" }, + { 0x6E, "$6E" }, + { 0x6F, "$6F" }, + { 0x70, "$70" }, + { 0x71, "$71" }, + { 0x72, "$72" }, + { 0x73, "$73" }, + { 0x74, "$74" }, + { 0x75, "$75" }, + { 0x76, "$76" }, + { 0x77, "$77" }, + { 0x78, "$78" }, + { 0x79, "$79" }, + { 0x7A, "$7A" }, + { 0x7B, "$7B" }, + // { 0x7C, "$7C" }, // Invalid Code + // { 0x7D, "$7D" }, // Invalid Code + // { 0x7E, "$7E" }, // Invalid Code + // { 0x7F, "$7F" }, // Invalid Code + // { 0x80, "$80" }, // Invalid Code + { 0x81, "JMP" }, + { 0x82, "switch" }, + { 0x83, "$83" }, + { 0x84, "$84" }, + { 0x85, "$85" }, + { 0x86, "$86" }, + { 0x87, "DoCommand?" }, + // { 0x88, "$88" }, // Invalid Code + { 0x89, "LineNo?" }, + { 0x8A, "$8A" }, + { 0x8B, "$8B" }, + { 0x8C, "$8C" }, + { 0x8D, "$8D" }, + { 0x8E, "$8E" }, + { 0x8F, "$8F" }, + { 0x90, "$90" }, + { 0x91, "$91" }, + { 0x92, "$92" }, + { 0x93, "$93" }, + { 0x94, "$94" }, + { 0x95, "$95" }, + { 0x96, "$96" }, + { 0x97, "$97" }, + { 0x98, "$98" }, + { 0x99, "$99" }, + { 0x9A, "$9A" }, + { 0x9B, "Copy" }, + { 0x9C, "$9C" }, + { 0x9D, "$9D" }, + { 0x9E, "$9E" }, + { 0x9F, "$9F" }, + { 0xA0, "$A0" }, + { 0xA1, "$A1" }, + { 0xA2, "GetGlobal2" }, + { 0xA3, "GetGlobal" }, + { 0xA4, "GetGlobal4" }, + { 0xA5, "$A5" }, + { 0xA6, "$A6" }, + { 0xA7, "$A7" }, + { 0xA8, "$A8" }, + { 0xA9, "$A9" }, + { 0xAA, "$AA" }, + { 0xAB, "PushConst2" }, + { 0xAC, "CmpConst2" }, + { 0xAD, "$AD" }, + { 0xAE, "$AE" }, + { 0xAF, "SetGlobal" }, + { 0xB0, "$B0" }, + { 0xB1, "SetLocal" }, + { 0xB2, "$B2" }, + { 0xB3, "$B3" }, + { 0xB4, "$B4" }, + { 0xB5, "$B5" }, + { 0xB6, "$B6" }, + { 0xB7, "$B7" }, + { 0xB8, "$B8" }, + { 0xB9, "$B9" }, + { 0xBA, "$BA" }, + { 0xBB, "$BB" }, + { 0xBC, "PushConst" }, + { 0xBD, "GetGlobal3" }, + { 0xBE, "GetArg" }, + { 0xBF, "AdjustStack" }, + { 0xC0, "$C0" }, + { 0xC1, "$C1" }, + { 0xC2, "$C2" }, + { 0xC3, "$C3" }, + { 0xC4, "$C4" }, + { 0xC5, "$C5" }, + { 0xC6, "$C6" }, + { 0xC7, "$C7" }, + { 0xC8, "CmpLocal" }, + { 0xC9, "CmpConst" }, + { 0xCA, "$CA" }, + { 0xCB, "$CB" }, + { 0xCC, "$CC" }, + { 0xCD, "$CD" }, + { 0xCE, "$CE" }, + { 0xCF, "$CF" }, + { 0xD0, "$D0" }, + + { 0xD1, "$D1" }, + { 0xD2, "$D2" }, + { 0xD3, "$D3" }, + { 0xD4, "$D4" }, + { 0xD5, "Mode1" }, + { 0xD6, "Mode2" }, + }; + } +} \ No newline at end of file diff --git a/pkNX.Structures/Scripts/Script.cs b/pkNX.Structures/Scripts/Script.cs new file mode 100644 index 00000000..0035fe6d --- /dev/null +++ b/pkNX.Structures/Scripts/Script.cs @@ -0,0 +1,62 @@ +using System; +using System.Linq; + +namespace pkNX.Structures +{ + /// + /// Pawn Script (.amx) Script File + /// + /// https://github.com/compuphase/pawn + public class Script + { + public int Length => BitConverter.ToInt32(Raw, 0x00); + public uint Magic => BitConverter.ToUInt32(Raw, 0x04); + // case 0x0A0AF1E0: code = read_code_block(f); break; + // case 0x0A0AF1EF: debug = read_debug_block(f); break; + public bool Debug => Magic == 0x0A0AF1EF; + + public ushort PtrOffset => BitConverter.ToUInt16(Raw, 0x08); + public ushort PtrCount => BitConverter.ToUInt16(Raw, 0x0A); + + public int ScriptInstructionStart => BitConverter.ToInt32(Raw, 0x0C); + public int ScriptMovementStart => BitConverter.ToInt32(Raw, 0x10); + public int FinalOffset => BitConverter.ToInt32(Raw, 0x14); + public int AllocatedMemory => BitConverter.ToInt32(Raw, 0x18); + + // Generated Attributes + public int CompressedLength => Length - ScriptInstructionStart; + public byte[] CompressedBytes => Raw.Skip(ScriptInstructionStart).ToArray(); + public int DecompressedLength => FinalOffset - ScriptInstructionStart; + public uint[] DecompressedInstructions => PawnUtil.QuickDecompress(CompressedBytes, DecompressedLength / 4); + + public uint[] ScriptCommands => DecompressedInstructions.Take((ScriptMovementStart - ScriptInstructionStart) / 4).ToArray(); + public uint[] MoveCommands => DecompressedInstructions.Skip((ScriptMovementStart - ScriptInstructionStart) / 4).ToArray(); + public string[] ParseScript => PawnUtil.ParseScript(ScriptCommands); + public string[] ParseMoves => PawnUtil.ParseMovement(MoveCommands); + + public string Info => "Data Start: 0x" + ScriptInstructionStart.ToString("X4") + + Environment.NewLine + "Movement Offset: 0x" + ScriptMovementStart.ToString("X4") + + Environment.NewLine + "Total Used Size: 0x" + FinalOffset.ToString("X4") + + Environment.NewLine + "Reserved Size: 0x" + AllocatedMemory.ToString("X4") + + Environment.NewLine + "Compressed Len: 0x" + CompressedLength.ToString("X4") + + Environment.NewLine + "Decompressed Len: 0x" + DecompressedLength.ToString("X4") + + Environment.NewLine + "Compression Ratio: " + + ((DecompressedLength - CompressedLength) / (decimal)DecompressedLength).ToString("p1"); + + public byte[] Raw; + + public Script(byte[] data = null) + { + Raw = data ?? Array.Empty(); + + // sub_51AAFC + if ((Raw[8] & 1) != 0) + throw new ArgumentException("Multi-environment script!?"); + } + + public byte[] Write() + { + return Raw; + } + } +} diff --git a/pkNX.Structures/StructConverter.cs b/pkNX.Structures/StructConverter.cs new file mode 100644 index 00000000..6670ffb1 --- /dev/null +++ b/pkNX.Structures/StructConverter.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.InteropServices; + +namespace pkNX.Structures +{ + public static class StructConverter + { + public static T ToStructure(this byte[] bytes) where T : struct + { + var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + try { return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); } + finally { handle.Free(); } + } + + public static T ToClass(this byte[] bytes) where T : class + { + var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + try { return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); } + finally { handle.Free(); } + } + + public static byte[] ToBytes(this T obj) where T : struct + { + int size = Marshal.SizeOf(obj); + byte[] arr = new byte[size]; + + IntPtr ptr = Marshal.AllocHGlobal(size); + Marshal.StructureToPtr(obj, ptr, true); + Marshal.Copy(ptr, arr, 0, size); + Marshal.FreeHGlobal(ptr); + return arr; + } + } +} diff --git a/pkNX.Structures/TableUtil.cs b/pkNX.Structures/TableUtil.cs new file mode 100644 index 00000000..cf72f1e6 --- /dev/null +++ b/pkNX.Structures/TableUtil.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace pkNX.Structures +{ + public static class TableUtil + { + /// + /// Converts an enumerable list of to a tab separated sheet. + /// + /// Object Type + /// Array of type T + /// 2 dimensional sheet of cells + public static string GetTable(IEnumerable arr) where T : new() => string.Join(Environment.NewLine, GetTableRaw(arr)); + + private const string sep = "\t"; + private static IEnumerable GetTableRaw(IEnumerable arr) => Table(arr).Select(row => string.Join(sep, row)); + + public static string GetNamedTable(IEnumerable arr, IList names, string name = null) + { + var list = GetTableRaw(arr).ToArray(); + + // slap in name to column header + list[0] = $"Index{sep}{name ?? typeof(T).Name}{sep}{list[0]}"; + + // slap in row name to row + for (int i = 1; i < list.Length; i++) + list[i] = $"{i - 1}{sep}{names[i - 1]}{sep}{list[i]}"; + + return string.Join(Environment.NewLine, list); + } + + private static IEnumerable> Table(IEnumerable arr) + { + var type = typeof(T); + yield return GetNames(type); + foreach (var z in arr) + yield return GetValues(z, type); + } + + private static IEnumerable GetNames(Type type) + { + foreach (var z in type.GetProperties()) + yield return z.Name; + foreach (var z in type.GetFields()) + yield return z.Name; + } + + private static IEnumerable GetValues(object obj, Type type) + { + foreach (var z in type.GetProperties()) + yield return z.GetValue(obj, null)?.ToString() ?? ""; + foreach (var z in type.GetFields()) + yield return z.GetValue(obj)?.ToString() ?? ""; + } + } +} diff --git a/pkNX.Structures/Text/TextConfig.cs b/pkNX.Structures/Text/TextConfig.cs new file mode 100644 index 00000000..85bb3701 --- /dev/null +++ b/pkNX.Structures/Text/TextConfig.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace pkNX.Structures +{ + /// + /// Version specific text parsing configuration object to interact with text variable codes. + /// + public class TextConfig + { + internal static readonly TextConfig Default = new TextConfig(GameVersion.Any); + private static readonly char[] _trim_hex = { '0', 'x' }; + private readonly TextVariableCode[] Variables; + + public IEnumerable GetVariableList() => Variables.Select(z => $"{z.Code:X4}={z.Name}"); + public TextConfig(GameVersion game) => Variables = TextVariableCode.GetVariables(game); + + private TextVariableCode GetCode(string name) => Array.Find(Variables, v => v.Name == name); + private TextVariableCode GetName(int value) => Array.Find(Variables, v => v.Code == value); + + /// + /// Gets the machine-friendly variable instruction code to be written to the data. + /// + /// Variable name + public ushort GetVariableNumber(string variable) + { + var v = GetCode(variable); + if (v != null) + return (ushort)v.Code; + if (ushort.TryParse(variable.TrimStart(_trim_hex), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) + return result; + throw new ArgumentException($"Variable parse error: {variable}. Expected a hexadecimal value or standard variable code."); + } + + /// + /// Gets the human-friendly variable instruction name to be written to the output text line. + /// + /// Variable code + public string GetVariableString(ushort variable) + { + var v = GetName(variable); + return v?.Name ?? variable.ToString("X4"); + } + } +} diff --git a/pkNX.Structures/Text/TextFile.cs b/pkNX.Structures/Text/TextFile.cs new file mode 100644 index 00000000..38178589 --- /dev/null +++ b/pkNX.Structures/Text/TextFile.cs @@ -0,0 +1,392 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace pkNX.Structures +{ + public class TextFile + { + // Text Formatting Config + private const ushort KEY_BASE = 0x7C89; + private const ushort KEY_ADVANCE = 0x2983; + private const ushort KEY_VARIABLE = 0x0010; + private const ushort KEY_TERMINATOR = 0x0000; + private const ushort KEY_TEXTRETURN = 0xBE00; + private const ushort KEY_TEXTCLEAR = 0xBE01; + private const ushort KEY_TEXTWAIT = 0xBE02; + private const ushort KEY_TEXTNULL = 0xBDFF; + private const bool SETEMPTYTEXT = true; + private static readonly byte[] emptyTextFile = { 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00 }; + + public TextFile(byte[] data = null, TextConfig config = null, bool remapChars = false) + { + Data = (byte[])(data ?? emptyTextFile).Clone(); + + if (InitialKey != 0) + throw new Exception("Invalid initial key! Not 0?"); + if (SectionDataOffset + TotalLength != Data.Length || TextSections != 1) + throw new Exception("Invalid Text File"); + if (SectionLength != TotalLength) + throw new Exception("Section size and overall size do not match."); + + Config = config ?? TextConfig.Default; + RemapChars = remapChars; + } + + public byte[] Data; + private readonly TextConfig Config; + private readonly bool RemapChars; + + private ushort TextSections { get => BitConverter.ToUInt16(Data, 0x0); set => BitConverter.GetBytes(value).CopyTo(Data, 0x0); } // Always 0x0001 + private ushort LineCount { get => BitConverter.ToUInt16(Data, 0x2); set => BitConverter.GetBytes(value).CopyTo(Data, 0x2); } + private uint TotalLength { get => BitConverter.ToUInt32(Data, 0x4); set => BitConverter.GetBytes(value).CopyTo(Data, 0x4); } + private uint InitialKey => BitConverter.ToUInt32(Data, 0x8); // Always 0x00000000 + private uint SectionDataOffset { get => BitConverter.ToUInt32(Data, 0xC); set => BitConverter.GetBytes(value).CopyTo(Data, 0xC); } // Always 0x0010 + private uint SectionLength { get => BitConverter.ToUInt32(Data, (int)SectionDataOffset); set => BitConverter.GetBytes(value).CopyTo(Data, SectionDataOffset); } + + private TextLine[] LineOffsets + { + get + { + TextLine[] result = new TextLine[LineCount]; + int sdo = (int)SectionDataOffset; + for (int i = 0; i < result.Length; i++) + { + result[i] = new TextLine + { + Offset = BitConverter.ToInt32(Data, (i * 8) + sdo + 4) + sdo, + Length = BitConverter.ToInt16(Data, (i * 8) + sdo + 8) + }; + } + + return result; + } + set + { + if (value == null) + return; + int sdo = (int)SectionDataOffset; + for (int i = 0; i < value.Length; i++) + { + BitConverter.GetBytes(value[i].Offset).CopyTo(Data, (i * 8) + sdo + 4); + BitConverter.GetBytes(value[i].Length).CopyTo(Data, (i * 8) + sdo + 8); + } + } + } + + public byte[] this[int index] + { + get + { + ushort key = GetLineKey(index); + var line = LineOffsets[index]; + byte[] EncryptedLineData = new byte[line.Length * 2]; + Array.Copy(Data, line.Offset, EncryptedLineData, 0, EncryptedLineData.Length); + + return CryptLineData(EncryptedLineData, key); + } + } + + private static ushort GetLineKey(int index) + { + ushort key = KEY_BASE; + for (int i = 0; i < index; i++) + key += KEY_ADVANCE; + return key; + } + + public byte[][] LineData + { + get + { + ushort key = KEY_BASE; + var result = new byte[LineCount][]; + var lines = LineOffsets; + for (int i = 0; i < lines.Length; i++) + { + byte[] EncryptedLineData = new byte[lines[i].Length * 2]; + Array.Copy(Data, lines[i].Offset, EncryptedLineData, 0, EncryptedLineData.Length); + + result[i] = CryptLineData(EncryptedLineData, key); + key += KEY_ADVANCE; + } + return result; + } + set + { + // rebuild LineInfo + var lines = new TextLine[value.Length]; + int bytesUsed = 0; + for (int i = 0; i < lines.Length; i++) + { + lines[i] = new TextLine { Offset = 4 + (8 * value.Length) + bytesUsed, Length = value[i].Length / 2 }; + bytesUsed += value[i].Length; + } + + // Apply Line Data + int sdo = (int)SectionDataOffset; + Array.Resize(ref Data, sdo + 4 + (8 * value.Length) + bytesUsed); + LineOffsets = lines; + value.SelectMany(i => i).ToArray().CopyTo(Data, Data.Length - bytesUsed); + TotalLength = SectionLength = (uint)(Data.Length - sdo); + LineCount = (ushort)value.Length; + } + } + + public string[] Lines + { + get => LineData.Select(GetLineString).ToArray(); + set => LineData = ConvertLinesToData(value); + } + + private byte[][] ConvertLinesToData(string[] value) + { + if (value == null) + value = Array.Empty(); + + ushort key = KEY_BASE; + var lineData = new byte[value.Length][]; + for (int i = 0; i < value.Length; i++) + { + string text = value[i]?.Trim() ?? string.Empty; + if (text.Length == 0 && SETEMPTYTEXT) + text = $"[~ {i}]"; + byte[] DecryptedLineData = GetLineData(text); + lineData[i] = CryptLineData(DecryptedLineData, key); + if (lineData[i].Length % 4 == 2) + Array.Resize(ref lineData[i], lineData[i].Length + 2); + key += KEY_ADVANCE; + } + + return lineData; + } + + private static byte[] CryptLineData(byte[] data, ushort key) + { + byte[] result = (byte[])data.Clone(); + for (int i = 0; i < result.Length; i += 2) + { + result[i + 0] ^= (byte)key; + result[i + 1] ^= (byte)(key >> 8); + key = (ushort)(key << 3 | key >> 13); + } + return result; + } + + private byte[] GetLineData(string line) + { + if (line == null) + return new byte[2]; + + using (var ms = new MemoryStream()) + using (var bw = new BinaryWriter(ms)) + { + int i = 0; + while (i < line.Length) + { + ushort val = line[i++]; + val = TryRemapChar(val); + + switch (val) + { + case '[': + // grab the string + int bracket = line.IndexOf(']', i); + if (bracket < 0) + throw new ArgumentException("Variable text is not capped properly: " + line); + string varText = line.Substring(i, bracket - i); + var varValues = GetVariableValues(varText); + foreach (ushort v in varValues) bw.Write(v); + i += 1 + varText.Length; + break; + case '\\': + var escapeValues = GetEscapeValues(line[i++]); + foreach (ushort v in escapeValues) + bw.Write(v); + break; + default: + bw.Write(val); + break; + } + } + bw.Write(KEY_TERMINATOR); // cap the line off + return ms.ToArray(); + } + } + + private ushort TryRemapChar(ushort val) + { + if (!RemapChars) + return val; + switch (val) + { + case 0x202F: return 0xE07F; // nbsp + case 0x2026: return 0xE08D; // … + case 0x2642: return 0xE08E; // ♂ + case 0x2640: return 0xE08F; // ♀ + default: return val; + } + } + + private ushort TryUnmapChar(ushort val) + { + if (!RemapChars) + return val; + switch (val) + { + case 0xE07F: return 0x202F; // nbsp + case 0xE08D: return 0x2026; // … + case 0xE08E: return 0x2642; // ♂ + case 0xE08F: return 0x2640; // ♀ + default: return val; + } + } + + private string GetLineString(byte[] data) + { + if (data == null) + return null; + + var s = new StringBuilder(); + int i = 0; + while (i < data.Length) + { + ushort val = BitConverter.ToUInt16(data, i); + if (val == KEY_TERMINATOR) break; + i += 2; + + switch (val) + { + case KEY_TERMINATOR: return s.ToString(); + case KEY_VARIABLE: s.Append(GetVariableString(Config, data, ref i)); break; + case '\n': s.Append(@"\n"); break; + case '\\': s.Append(@"\\"); break; + case '[': s.Append(@"\["); break; + default: s.Append((char)TryUnmapChar(val)); break; + } + } + return s.ToString(); // Shouldn't get hit if the string is properly terminated. + } + + private static string GetVariableString(TextConfig config, byte[] data, ref int i) + { + var s = new StringBuilder(); + ushort count = BitConverter.ToUInt16(data, i); i += 2; + ushort variable = BitConverter.ToUInt16(data, i); i += 2; + + switch (variable) + { + case KEY_TEXTRETURN: // "Waitbutton then scroll text \r" + return "\\r"; + case KEY_TEXTCLEAR: // "Waitbutton then clear text \c" + return "\\c"; + case KEY_TEXTWAIT: // Dramatic pause for a text line. New! + ushort time = BitConverter.ToUInt16(data, i); i += 2; + return $"[WAIT {time}]"; + case KEY_TEXTNULL: // Empty Text line? Includes linenum so maybe for betatest finding used-unused lines? + ushort line = BitConverter.ToUInt16(data, i); i += 2; + return $"[~ {line}]"; + } + + string varName = config.GetVariableString(variable); + + s.Append("[VAR").Append(" ").Append(varName); + if (count > 1) + { + s.Append('('); + while (count > 1) + { + ushort arg = BitConverter.ToUInt16(data, i); i += 2; + s.Append(arg.ToString("X4")); + if (--count == 1) break; + s.Append(","); + } + s.Append(')'); + } + s.Append("]"); + return s.ToString(); + } + + private IEnumerable GetEscapeValues(char esc) + { + var vals = new List(); + switch (esc) + { + case 'n': vals.Add('\n'); return vals; + case '\\': vals.Add('\\'); return vals; + case '[': vals.Add('['); return vals; + case 'r': vals.AddRange(new ushort[] { KEY_VARIABLE, 1, KEY_TEXTRETURN }); return vals; + case 'c': vals.AddRange(new ushort[] { KEY_VARIABLE, 1, KEY_TEXTCLEAR }); return vals; + default: throw new Exception("Invalid terminated line: \\" + esc); + } + } + + private IEnumerable GetVariableValues(string variable) + { + string[] split = variable.Split(' '); + if (split.Length < 2) + throw new ArgumentException("Incorrectly formatted variable text: " + variable); + + var vals = new List { KEY_VARIABLE }; + switch (split[0]) + { + case "~": // Blank Text Line Variable (No text set - debug/quality testing variable?) + vals.Add(1); + vals.Add(KEY_TEXTNULL); + vals.Add(Convert.ToUInt16(split[1])); + break; + case "WAIT": // Event pause Variable. + vals.Add(1); + vals.Add(KEY_TEXTWAIT); + vals.Add(Convert.ToUInt16(split[1])); + break; + case "VAR": // Text Variable + vals.AddRange(GetVariableParameters(split[1])); + break; + default: throw new Exception("Unknown variable method type: " + variable); + } + return vals; + } + + private IEnumerable GetVariableParameters(string text) + { + var vals = new List(); + int bracket = text.IndexOf('('); + bool noArgs = bracket < 0; + string variable = noArgs ? text : text.Substring(0, bracket); + ushort varVal = Config.GetVariableNumber(variable); + + if (!noArgs) + { + string[] args = text.Substring(bracket + 1, text.Length - bracket - 2).Split(','); + vals.Add((ushort)(1 + args.Length)); + vals.Add(varVal); + vals.AddRange(args.Select(t => Convert.ToUInt16(t, 16))); + } + else + { + vals.Add(1); + vals.Add(varVal); + } + return vals; + } + + // Exposed Methods + public static string[] GetStrings(byte[] data, TextConfig config = null, bool remapChars = false) + { + try + { + var t = new TextFile(data, config, remapChars); + return t.Lines; + } + catch { return null; } + } + + public static byte[] GetBytes(string[] lines, TextConfig config = null, bool remapChars = false) + { + return new TextFile(config: config, remapChars: remapChars) { Lines = lines }.Data; + } + } +} diff --git a/pkNX.Structures/Text/TextLineInfo.cs b/pkNX.Structures/Text/TextLineInfo.cs new file mode 100644 index 00000000..3e11cf7c --- /dev/null +++ b/pkNX.Structures/Text/TextLineInfo.cs @@ -0,0 +1,8 @@ +namespace pkNX.Structures +{ + internal class TextLine + { + public int Offset; + public int Length; + } +} diff --git a/pkNX.Structures/Text/TextVariableCode.cs b/pkNX.Structures/Text/TextVariableCode.cs new file mode 100644 index 00000000..551bca20 --- /dev/null +++ b/pkNX.Structures/Text/TextVariableCode.cs @@ -0,0 +1,241 @@ +using System; + +namespace pkNX.Structures +{ + public sealed class TextVariableCode + { + public readonly string Name; + public readonly int Code; + + private TextVariableCode(int code, string name) + { + Code = code; + Name = name; + } + + public static TextVariableCode[] GetVariables(GameVersion game) + { + if (game == GameVersion.Any) + return Array.Empty(); + + if (GameVersion.GG.Contains(game)) + return GG; + if (GameVersion.XY.Contains(game)) + return XY; + if (GameVersion.ORAS.Contains(game) || GameVersion.ORASDEMO == game) + return AO; + if (GameVersion.SM.Contains(game) || GameVersion.USUM.Contains(game)) + return SM; + + return Array.Empty(); + } + + private static readonly TextVariableCode[] XY = + { + new TextVariableCode(0xFF00, "COLOR"), + new TextVariableCode(0x0100, "TRNAME"), + new TextVariableCode(0x0101, "PKNAME"), + new TextVariableCode(0x0102, "PKNICK"), + new TextVariableCode(0x0103, "TYPE"), + new TextVariableCode(0x0105, "LOCATION"), + new TextVariableCode(0x0106, "ABILITY"), + new TextVariableCode(0x0107, "MOVE"), + new TextVariableCode(0x0108, "ITEM1"), + new TextVariableCode(0x0109, "ITEM2"), + new TextVariableCode(0x010A, "sTRBAG"), + new TextVariableCode(0x010B, "BOX"), + new TextVariableCode(0x010D, "EVSTAT"), + new TextVariableCode(0x0110, "OPOWER"), + new TextVariableCode(0x0127, "RIBBON"), + new TextVariableCode(0x0134, "MIINAME"), + new TextVariableCode(0x013E, "WEATHER"), + new TextVariableCode(0x0189, "TRNICK"), + new TextVariableCode(0x018A, "1stchrTR"), + new TextVariableCode(0x018B, "SHOUTOUT"), + new TextVariableCode(0x018E, "BERRY"), + new TextVariableCode(0x018F, "REMFEEL"), + new TextVariableCode(0x0190, "REMQUAL"), + new TextVariableCode(0x0191, "WEBSITE"), + new TextVariableCode(0x019C, "CHOICECOS"), + new TextVariableCode(0x01A1, "GSYNCID"), + new TextVariableCode(0x0192, "PRVIDSAY"), + new TextVariableCode(0x0193, "BTLTEST"), + new TextVariableCode(0x0195, "GENLOC"), + new TextVariableCode(0x0199, "CHOICEFOOD"), + new TextVariableCode(0x019A, "HOTELITEM"), + new TextVariableCode(0x019B, "TAXISTOP"), + new TextVariableCode(0x019F, "MAISTITLE"), + new TextVariableCode(0x1000, "ITEMPLUR0"), + new TextVariableCode(0x1001, "ITEMPLUR1"), + new TextVariableCode(0x1100, "GENDBR"), + new TextVariableCode(0x1101, "NUMBRNCH"), + new TextVariableCode(0x1302, "iCOLOR2"), + new TextVariableCode(0x1303, "iCOLOR3"), + new TextVariableCode(0x0200, "NUM1"), + new TextVariableCode(0x0201, "NUM2"), + new TextVariableCode(0x0202, "NUM3"), + new TextVariableCode(0x0203, "NUM4"), + new TextVariableCode(0x0204, "NUM5"), + new TextVariableCode(0x0205, "NUM6"), + new TextVariableCode(0x0206, "NUM7"), + new TextVariableCode(0x0207, "NUM8"), + new TextVariableCode(0x0208, "NUM9"), + }; + + private static readonly TextVariableCode[] AO = + { + new TextVariableCode(0xFF00, "COLOR"), + new TextVariableCode(0x0100, "TRNAME"), + new TextVariableCode(0x0101, "PKNAME"), + new TextVariableCode(0x0102, "PKNICK"), + new TextVariableCode(0x0103, "TYPE"), + new TextVariableCode(0x0105, "LOCATION"), + new TextVariableCode(0x0106, "ABILITY"), + new TextVariableCode(0x0107, "MOVE"), + new TextVariableCode(0x0108, "ITEM1"), + new TextVariableCode(0x0109, "ITEM2"), + new TextVariableCode(0x010A, "sTRBAG"), + new TextVariableCode(0x010B, "BOX"), + new TextVariableCode(0x010D, "EVSTAT"), + new TextVariableCode(0x0110, "OPOWER"), + new TextVariableCode(0x0127, "RIBBON"), + new TextVariableCode(0x0134, "MIINAME"), + new TextVariableCode(0x013E, "WEATHER"), + new TextVariableCode(0x0189, "TRNICK"), + new TextVariableCode(0x018A, "1stchrTR"), + new TextVariableCode(0x018B, "SHOUTOUT"), + new TextVariableCode(0x018E, "BERRY"), + new TextVariableCode(0x018F, "REMFEEL"), + new TextVariableCode(0x0190, "REMQUAL"), + new TextVariableCode(0x0191, "WEBSITE"), + new TextVariableCode(0x019C, "CHOICECOS"), + new TextVariableCode(0x01A1, "GSYNCID"), + new TextVariableCode(0x0192, "PRVIDSAY"), + new TextVariableCode(0x0193, "BTLTEST"), + new TextVariableCode(0x0195, "GENLOC"), + new TextVariableCode(0x0199, "CHOICEFOOD"), + new TextVariableCode(0x019A, "HOTELITEM"), + new TextVariableCode(0x019B, "TAXISTOP"), + new TextVariableCode(0x019F, "MAISTITLE"), + new TextVariableCode(0x1000, "ITEMPLUR0"), + new TextVariableCode(0x1001, "ITEMPLUR1"), + new TextVariableCode(0x1100, "GENDBR"), + new TextVariableCode(0x1101, "NUMBRNCH"), + new TextVariableCode(0x1302, "iCOLOR2"), + new TextVariableCode(0x1303, "iCOLOR3"), + new TextVariableCode(0x0200, "NUM1"), + new TextVariableCode(0x0201, "NUM2"), + new TextVariableCode(0x0202, "NUM3"), + new TextVariableCode(0x0203, "NUM4"), + new TextVariableCode(0x0204, "NUM5"), + new TextVariableCode(0x0205, "NUM6"), + new TextVariableCode(0x0206, "NUM7"), + new TextVariableCode(0x0207, "NUM8"), + new TextVariableCode(0x0208, "NUM9"), + }; + + private static readonly TextVariableCode[] SM = + { + new TextVariableCode(0xFF00, "COLOR"), + new TextVariableCode(0x0100, "TRNAME"), + new TextVariableCode(0x0101, "PKNAME"), + new TextVariableCode(0x0102, "PKNICK"), + new TextVariableCode(0x0103, "TYPE"), + new TextVariableCode(0x0105, "LOCATION"), + new TextVariableCode(0x0106, "ABILITY"), + new TextVariableCode(0x0107, "MOVE"), + new TextVariableCode(0x0108, "ITEM1"), + new TextVariableCode(0x0109, "ITEM2"), + new TextVariableCode(0x010A, "sTRBAG"), + new TextVariableCode(0x010B, "BOX"), + new TextVariableCode(0x010D, "EVSTAT"), + new TextVariableCode(0x0110, "OPOWER"), + new TextVariableCode(0x0127, "RIBBON"), + new TextVariableCode(0x0134, "MIINAME"), + new TextVariableCode(0x013E, "WEATHER"), + new TextVariableCode(0x0189, "TRNICK"), + new TextVariableCode(0x018A, "1stchrTR"), + new TextVariableCode(0x018B, "SHOUTOUT"), + new TextVariableCode(0x018E, "BERRY"), + new TextVariableCode(0x018F, "REMFEEL"), + new TextVariableCode(0x0190, "REMQUAL"), + new TextVariableCode(0x0191, "WEBSITE"), + new TextVariableCode(0x019C, "CHOICECOS"), + new TextVariableCode(0x01A1, "GSYNCID"), + new TextVariableCode(0x0192, "PRVIDSAY"), + new TextVariableCode(0x0193, "BTLTEST"), + new TextVariableCode(0x0195, "GENLOC"), + new TextVariableCode(0x0199, "CHOICEFOOD"), + new TextVariableCode(0x019A, "HOTELITEM"), + new TextVariableCode(0x019B, "TAXISTOP"), + new TextVariableCode(0x019F, "MAISTITLE"), + new TextVariableCode(0x1000, "ITEMPLUR0"), + new TextVariableCode(0x1001, "ITEMPLUR1"), + new TextVariableCode(0x1100, "GENDBR"), + new TextVariableCode(0x1101, "NUMBRNCH"), + new TextVariableCode(0x1302, "iCOLOR2"), + new TextVariableCode(0x1303, "iCOLOR3"), + new TextVariableCode(0x0200, "NUM1"), + new TextVariableCode(0x0201, "NUM2"), + new TextVariableCode(0x0202, "NUM3"), + new TextVariableCode(0x0203, "NUM4"), + new TextVariableCode(0x0204, "NUM5"), + new TextVariableCode(0x0205, "NUM6"), + new TextVariableCode(0x0206, "NUM7"), + new TextVariableCode(0x0207, "NUM8"), + new TextVariableCode(0x0208, "NUM9"), + }; + + private static readonly TextVariableCode[] GG = + { + new TextVariableCode(0xFF00, "COLOR"), + new TextVariableCode(0x0100, "TRNAME"), + new TextVariableCode(0x0101, "PKNAME"), + new TextVariableCode(0x0102, "PKNICK"), + new TextVariableCode(0x0103, "TYPE"), + new TextVariableCode(0x0105, "LOCATION"), + new TextVariableCode(0x0106, "ABILITY"), + new TextVariableCode(0x0107, "MOVE"), + new TextVariableCode(0x0108, "ITEM1"), + new TextVariableCode(0x0109, "ITEM2"), + new TextVariableCode(0x010A, "sTRBAG"), + new TextVariableCode(0x010B, "BOX"), + new TextVariableCode(0x010D, "EVSTAT"), + new TextVariableCode(0x0110, "OPOWER"), + new TextVariableCode(0x0127, "RIBBON"), + new TextVariableCode(0x0134, "MIINAME"), + new TextVariableCode(0x013E, "WEATHER"), + new TextVariableCode(0x0189, "TRNICK"), + new TextVariableCode(0x018A, "1stchrTR"), + new TextVariableCode(0x018B, "SHOUTOUT"), + new TextVariableCode(0x018E, "BERRY"), + new TextVariableCode(0x018F, "REMFEEL"), + new TextVariableCode(0x0190, "REMQUAL"), + new TextVariableCode(0x0191, "WEBSITE"), + new TextVariableCode(0x019C, "CHOICECOS"), + new TextVariableCode(0x01A1, "GSYNCID"), + new TextVariableCode(0x0192, "PRVIDSAY"), + new TextVariableCode(0x0193, "BTLTEST"), + new TextVariableCode(0x0195, "GENLOC"), + new TextVariableCode(0x0199, "CHOICEFOOD"), + new TextVariableCode(0x019A, "HOTELITEM"), + new TextVariableCode(0x019B, "TAXISTOP"), + new TextVariableCode(0x019F, "MAISTITLE"), + new TextVariableCode(0x1000, "ITEMPLUR0"), + new TextVariableCode(0x1001, "ITEMPLUR1"), + new TextVariableCode(0x1100, "GENDBR"), + new TextVariableCode(0x1101, "NUMBRNCH"), + new TextVariableCode(0x1302, "iCOLOR2"), + new TextVariableCode(0x1303, "iCOLOR3"), + new TextVariableCode(0x0200, "NUM1"), + new TextVariableCode(0x0201, "NUM2"), + new TextVariableCode(0x0202, "NUM3"), + new TextVariableCode(0x0203, "NUM4"), + new TextVariableCode(0x0204, "NUM5"), + new TextVariableCode(0x0205, "NUM6"), + new TextVariableCode(0x0206, "NUM7"), + new TextVariableCode(0x0207, "NUM8"), + new TextVariableCode(0x0208, "NUM9"), + }; + } +} \ No newline at end of file diff --git a/pkNX.Structures/Util.cs b/pkNX.Structures/Util.cs new file mode 100644 index 00000000..3955507d --- /dev/null +++ b/pkNX.Structures/Util.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace pkNX.Structures +{ + public static class Util + { + public static Random Rand { get; set; } = new Random(); + internal static uint Rand32() => (uint)Rand.Next(1 << 30) << 2 | (uint)Rand.Next(1 << 2); + + private static T[] GetArray(IReadOnlyList entries, Func del) + { + var data = new T[entries.Count]; + for (int i = 0; i < data.Length; i++) + data[i] = del(entries[i]); + return data; + } + + public static T[] GetArray(this byte[] entries, Func del, int size) + { + if (entries == null || entries.Length < size) + return Array.Empty(); + + var data = new T[entries.Length/size]; + for (int i = 0; i < data.Length; i+= size) + data[i] = del(entries, i); + return data; + } + + public static T[] GetArray(this Task task, Func del) => GetArray(task.Result, del); + public static T[] GetArray(this Task task, Func del, int size) => GetArray(task.Result, del, size); + + public static string[] GetHexLines(byte[] data, int count = 4) + { + if (data == null) + return Array.Empty(); + + // Generates an x-byte wide space separated string array; leftovers included at the end. + string[] s = new string[(data.Length / count) + (data.Length % count > 0 ? 1 : 0)]; + for (int i = 0; i < s.Length; i++) + s[i] = BitConverter.ToString(data.Skip(i * count).Take(count).ToArray()).Replace('-', ' '); + return s; + } + + public static string[] GetHexLines(uint[] data) => GetHexLines(GetBytes(data)); + + public static byte[] GetBytes(uint[] data) + { + byte[] result = new byte[data.Length * 4]; + for (int i = 0; i < data.Length; i++) + { + int o = i * 4; + var val = data[i]; + result[o + 0] = (byte)(val >> 0); + result[o + 1] = (byte)(val >> 8); + result[o + 2] = (byte)(val >> 16); + result[o + 3] = (byte)(val >> 24); + } + return result; + } + } +} diff --git a/pkNX.Structures/VsTrainer/3DS/TrData6.cs b/pkNX.Structures/VsTrainer/3DS/TrData6.cs new file mode 100644 index 00000000..4b10adfe --- /dev/null +++ b/pkNX.Structures/VsTrainer/3DS/TrData6.cs @@ -0,0 +1,45 @@ +using System; + +namespace pkNX.Structures +{ + public abstract class TrData6 : TrainerData + { + protected abstract int Format { get; set; } + public bool HasItem { get => (Format & 1) == 1; set => Format = (ushort)((Format & ~1) | (value ? 1 : 0)); } + public bool HasMoves { get => (Format & 2) == 2; set => Format = (ushort)((Format & ~2) | (value ? 2 : 0)); } + + public TrPoke6[] Team { get; set; } + + protected TrData6(byte[] trData) : base(trData) { } + public byte[] WriteTeam() => WriteTeam(Team, HasItem, HasMoves); + public TrPoke6[] GetTeam(byte[] trPoke) => GetTeam(trPoke, NumPokemon, HasItem, HasMoves); + + public static byte[] WriteTeam(TrPoke6[] team, bool HasItem, bool HasMoves) + { + if (team.Length == 0) + return Array.Empty(); + var first = team[0].Write(HasItem, HasMoves); + byte[] result = new byte[first.Length * team.Length]; + first.CopyTo(result, 0); + for (int i = 1; i < team.Length; i++) + team[i].Write(HasItem, HasMoves).CopyTo(result, first.Length * i); + return result; + } + + public static TrPoke6[] GetTeam(byte[] trPoke, int numPokemon, bool item, bool moves) + { + var team = new TrPoke6[numPokemon]; + byte[][] teamData = new byte[numPokemon][]; + int dataLen = trPoke.Length / numPokemon; + for (int i = 0; i < teamData.Length; i++) + { + var arr = teamData[i] = new byte[dataLen]; + Array.Copy(trPoke, i * dataLen, arr, 0, dataLen); + } + + for (int i = 0; i < numPokemon; i++) + team[i] = new TrPoke6(teamData[i], item, moves); + return team; + } + } +} \ No newline at end of file diff --git a/pkNX.Structures/VsTrainer/3DS/TrData6AO.cs b/pkNX.Structures/VsTrainer/3DS/TrData6AO.cs new file mode 100644 index 00000000..aa465188 --- /dev/null +++ b/pkNX.Structures/VsTrainer/3DS/TrData6AO.cs @@ -0,0 +1,24 @@ +using System; + +namespace pkNX.Structures +{ + public sealed class TrData6AO : TrData6 + { + public override int SIZE => 0x18; + public TrData6AO(byte[] trData = null) : base(trData) { } + + protected override int Format { get => BitConverter.ToUInt16(Data, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x00); } + public override int Class { get => BitConverter.ToUInt16(Data, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x02); } + public ushort Unused { get => BitConverter.ToUInt16(Data, 0x04); set => BitConverter.GetBytes(value).CopyTo(Data, 0x04); } + public override BattleMode Mode { get => (BattleMode) Data[0x06]; set => Data[0x06] = (byte) value; } + public override int NumPokemon { get => Data[0x07]; set => Data[0x07] = (byte)value; } + public override int Item1 { get => BitConverter.ToUInt16(Data, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x08); } + public override int Item2 { get => BitConverter.ToUInt16(Data, 0x0A); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0A); } + public override int Item3 { get => BitConverter.ToUInt16(Data, 0x0C); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0C); } + public override int Item4 { get => BitConverter.ToUInt16(Data, 0x0E); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0E); } + public override uint AI { get => BitConverter.ToUInt32(Data, 0x10); set => BitConverter.GetBytes(value).CopyTo(Data, 0x10); } + public override bool Heal { get => Data[0x14] == 1; set => Data[0x14] = (byte)(value ? 1 : 0); } + public override int Money { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public override int Gift { get => BitConverter.ToUInt16(Data, 0x16); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x16); } + } +} \ No newline at end of file diff --git a/pkNX.Structures/VsTrainer/3DS/TrData6XY.cs b/pkNX.Structures/VsTrainer/3DS/TrData6XY.cs new file mode 100644 index 00000000..f29ff877 --- /dev/null +++ b/pkNX.Structures/VsTrainer/3DS/TrData6XY.cs @@ -0,0 +1,23 @@ +using System; + +namespace pkNX.Structures +{ + public sealed class TrData6XY : TrData6 + { + public override int SIZE => 0x14; + public TrData6XY(byte[] trData = null) : base(trData) { } + + protected override int Format { get => Data[0x00]; set => Data[0x00] = (byte) value; } + public override int Class { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public override BattleMode Mode { get => (BattleMode)Data[0x02]; set => Data[0x02] = (byte)value; } + public override int NumPokemon { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public override int Item1 { get => BitConverter.ToUInt16(Data, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x04); } + public override int Item2 { get => BitConverter.ToUInt16(Data, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x06); } + public override int Item3 { get => BitConverter.ToUInt16(Data, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x08); } + public override int Item4 { get => BitConverter.ToUInt16(Data, 0x0A); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0A); } + public override uint AI { get => BitConverter.ToUInt32(Data, 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, 0x0C); } + public override bool Heal { get => Data[0x10] == 1; set => Data[0x10] = (byte)(value ? 1 : 0); } + public override int Money { get => Data[0x11]; set => Data[0x11] = (byte)value; } + public override int Gift { get => BitConverter.ToUInt16(Data, 0x12); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x12); } + } +} \ No newline at end of file diff --git a/pkNX.Structures/VsTrainer/3DS/TrPoke6.cs b/pkNX.Structures/VsTrainer/3DS/TrPoke6.cs new file mode 100644 index 00000000..f0090df6 --- /dev/null +++ b/pkNX.Structures/VsTrainer/3DS/TrPoke6.cs @@ -0,0 +1,68 @@ +using System.IO; + +namespace pkNX.Structures +{ + public class TrPoke6 + { + public byte IVs; + public byte PID; + public ushort Level; + public ushort Species; + public ushort Form; + public int Ability; + public int Gender; + public int uBit; + public ushort Item; + public ushort[] Moves = new ushort[4]; + + public TrPoke6(byte[] data, bool HasItem, bool HasMoves) + { + using (var ms = new MemoryStream(data)) + using (var br = new BinaryReader(ms)) + { + IVs = br.ReadByte(); + PID = br.ReadByte(); + Level = br.ReadUInt16(); + Species = br.ReadUInt16(); + Form = br.ReadUInt16(); + + Ability = PID >> 4; + Gender = PID & 3; + uBit = (PID >> 3) & 1; + + if (HasItem) + Item = br.ReadUInt16(); + + if (!HasMoves) + return; + + for (int i = 0; i < 4; i++) + Moves[i] = br.ReadUInt16(); + } + } + + public byte[] Write(bool HasItem, bool HasMoves) + { + using (var ms = new MemoryStream()) + using (var bw = new BinaryWriter(ms)) + { + bw.Write(IVs); + PID = (byte)(((Ability & 0xF) << 4) | ((uBit & 1) << 3) | (Gender & 0x7)); + bw.Write(PID); + bw.Write(Level); + bw.Write(Species); + bw.Write(Form); + + if (HasItem) + bw.Write(Item); + if (!HasMoves) + return ms.ToArray(); + + foreach (ushort Move in Moves) + bw.Write(Move); + + return ms.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/pkNX.Structures/VsTrainer/3DS/TrainerClass6.cs b/pkNX.Structures/VsTrainer/3DS/TrainerClass6.cs new file mode 100644 index 00000000..44434b43 --- /dev/null +++ b/pkNX.Structures/VsTrainer/3DS/TrainerClass6.cs @@ -0,0 +1,7 @@ +namespace pkNX.Structures +{ + public class TrainerClass6 + { + // todo + } +} diff --git a/pkNX.Structures/VsTrainer/Base/TrainerClass.cs b/pkNX.Structures/VsTrainer/Base/TrainerClass.cs new file mode 100644 index 00000000..181e791b --- /dev/null +++ b/pkNX.Structures/VsTrainer/Base/TrainerClass.cs @@ -0,0 +1,18 @@ +namespace pkNX.Structures +{ + public abstract class TrainerClass + { + public abstract int SIZE { get; } + protected byte[] Data; + + public virtual int Gender { get; set; } = 0; + public virtual int Multi { get; set; } = 0; + public virtual int Group { get; set; } = 0; + public virtual int BallID { get; set; } = 4; + public virtual int BattleBackground { get; set; } = 0; + public virtual int EyeCatchBGM { get; set; } = 0; + + public virtual bool IsBoss => false; + public virtual int MegaItemID => 773; + } +} diff --git a/pkNX.Structures/VsTrainer/Base/TrainerData.cs b/pkNX.Structures/VsTrainer/Base/TrainerData.cs new file mode 100644 index 00000000..7d645a14 --- /dev/null +++ b/pkNX.Structures/VsTrainer/Base/TrainerData.cs @@ -0,0 +1,27 @@ +namespace pkNX.Structures +{ + public abstract class TrainerData + { + public abstract int SIZE { get; } + protected byte[] Data; + + public abstract int Class { get; set; } + public abstract BattleMode Mode { get; set; } + public abstract int NumPokemon { get; set; } + public abstract int Item1 { get; set; } + public abstract int Item2 { get; set; } + public abstract int Item3 { get; set; } + public abstract int Item4 { get; set; } + + public abstract uint AI { get; set; } + public abstract bool Heal { get; set; } + public abstract int Money { get; set; } + public abstract int Gift { get; set; } + + // derived + public bool HasAllyTrainer => (AI & 8) != 0; + + public byte[] Write() => Data; + protected TrainerData(byte[] trData) => Data = trData ?? new byte[SIZE]; + } +} diff --git a/pkNX.Structures/VsTrainer/Base/TrainerPoke.cs b/pkNX.Structures/VsTrainer/Base/TrainerPoke.cs new file mode 100644 index 00000000..d4db6091 --- /dev/null +++ b/pkNX.Structures/VsTrainer/Base/TrainerPoke.cs @@ -0,0 +1,94 @@ +namespace pkNX.Structures +{ + public abstract class TrainerPoke + { + protected byte[] Data; + + public abstract int Species { get; set; } + public abstract int Form { get; set; } + public abstract int Level { get; set; } + public abstract int HeldItem { get; set; } + public abstract int Nature { get; set; } + public abstract int Gender { get; set; } + public abstract int Ability { get; set; } + public abstract int Friendship { get; set; } + + public abstract bool Shiny { get; set; } + public abstract bool CanMegaEvolve { get; set; } + + public abstract int Move1 { get; set; } + public abstract int Move2 { get; set; } + public abstract int Move3 { get; set; } + public abstract int Move4 { get; set; } + + public abstract uint IV32 { get; set; } + public abstract int IV_HP { get; set; } + public abstract int IV_ATK { get; set; } + public abstract int IV_DEF { get; set; } + public abstract int IV_SPE { get; set; } + public abstract int IV_SPA { get; set; } + public abstract int IV_SPD { get; set; } + + public abstract int EV_HP { get; set; } + public abstract int EV_ATK { get; set; } + public abstract int EV_DEF { get; set; } + public abstract int EV_SPA { get; set; } + public abstract int EV_SPD { get; set; } + public abstract int EV_SPE { get; set; } + + public abstract int AV_HP { get; set; } + public abstract int AV_ATK { get; set; } + public abstract int AV_DEF { get; set; } + public abstract int AV_SPA { get; set; } + public abstract int AV_SPD { get; set; } + public abstract int AV_SPE { get; set; } + + public abstract int Rank { get; set; } + + public byte[] Write() => (byte[])Data.Clone(); + public abstract TrainerPoke Clone(); + + #region Derived + + public int[] Moves + { + get => new[] { Move1, Move2, Move3, Move4 }; + set { if (value?.Length != 4) return; Move1 = value[0]; Move2 = value[1]; Move3 = value[2]; Move4 = value[3]; } + } + + public int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set + { + if (value?.Length != 6) return; + IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; + IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; + } + } + + public int[] EVs + { + get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; + set + { + if (value?.Length != 6) return; + EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; + EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; + } + } + + public int[] AVs + { + get => new[] { AV_HP, AV_ATK, AV_DEF, AV_SPE, AV_SPA, AV_SPD }; + set + { + if (value?.Length != 6) return; + AV_HP = value[0]; AV_ATK = value[1]; AV_DEF = value[2]; + AV_SPE = value[3]; AV_SPA = value[4]; AV_SPD = value[5]; + } + } + + #endregion + } +} diff --git a/pkNX.Structures/VsTrainer/Enums/AIType.cs b/pkNX.Structures/VsTrainer/Enums/AIType.cs new file mode 100644 index 00000000..91e21330 --- /dev/null +++ b/pkNX.Structures/VsTrainer/Enums/AIType.cs @@ -0,0 +1,18 @@ +using System; + +namespace pkNX.Structures +{ + [Flags] + public enum AIType : ushort + { + None = 0, + Basic = 1 << 0, + Strong = 1 << 1, + Expert = 1 << 2, + + Double = 1 << 7, + // Allowance, + // UseItem, + // Switch, + } +} \ No newline at end of file diff --git a/pkNX.Structures/VsTrainer/Enums/BattleMode.cs b/pkNX.Structures/VsTrainer/Enums/BattleMode.cs new file mode 100644 index 00000000..8e35567e --- /dev/null +++ b/pkNX.Structures/VsTrainer/Enums/BattleMode.cs @@ -0,0 +1,9 @@ +namespace pkNX.Structures +{ + public enum BattleMode : byte + { + Singles, + Doubles, + Multi, + } +} \ No newline at end of file diff --git a/pkNX.Structures/VsTrainer/GG/Trainer7b.cs b/pkNX.Structures/VsTrainer/GG/Trainer7b.cs new file mode 100644 index 00000000..75c1b382 --- /dev/null +++ b/pkNX.Structures/VsTrainer/GG/Trainer7b.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; + +namespace pkNX.Structures +{ + public class Trainer7b : VsTrainer + { + public Trainer7b(byte[] tr = null, byte[] tp = null) + { + Trainer = new TrainerData7b(tr); + LoadTeam(tp); + } + + private void LoadTeam(byte[] tp) + { + var pokes = tp.GetArray((data, offset) => new TrainerPoke7b(offset, data), TrainerPoke7b.SIZE); + Debug.Assert(pokes.Length == Trainer.NumPokemon); + Team.AddRange(pokes); + } + } +} diff --git a/pkNX.Structures/VsTrainer/GG/TrainerClass7b.cs b/pkNX.Structures/VsTrainer/GG/TrainerClass7b.cs new file mode 100644 index 00000000..81cf9be2 --- /dev/null +++ b/pkNX.Structures/VsTrainer/GG/TrainerClass7b.cs @@ -0,0 +1,17 @@ +namespace pkNX.Structures +{ + public class TrainerClass7b : TrainerClass + { + public sealed override int SIZE => 0x24; + public TrainerClass7b(byte[] data = null) => Data = data ?? new byte[SIZE]; + + public byte _0x00 { get => Data[0]; set => Data[0] = value; } + public override int Group { get => Data[1]; set => Data[1] = (byte)value; } + public override int BallID { get => Data[2]; set => Data[2] = (byte)value; } + public byte _0x03 { get => Data[3]; set => Data[3] = value; } + + // model hash at end (name, form, variation)? + + public override int MegaItemID => 773; + } +} \ No newline at end of file diff --git a/pkNX.Structures/VsTrainer/GG/TrainerData7b.cs b/pkNX.Structures/VsTrainer/GG/TrainerData7b.cs new file mode 100644 index 00000000..cbd6747d --- /dev/null +++ b/pkNX.Structures/VsTrainer/GG/TrainerData7b.cs @@ -0,0 +1,27 @@ +using System; + +namespace pkNX.Structures +{ + public sealed class TrainerData7b : TrainerData + { + public override int SIZE => 0x17; + public TrainerData7b(byte[] data = null) : base(data) { } + + public override int Class { get => BitConverter.ToUInt16(Data, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x00); } + public override BattleMode Mode { get => (BattleMode)Data[2]; set => Data[2] = (byte)value; } // Not sure + public override int NumPokemon { get => Data[3]; set => Data[3] = (byte)(value % 7); } + public override int Item1 { get => BitConverter.ToUInt16(Data, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x04); } + public override int Item2 { get => BitConverter.ToUInt16(Data, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x06); } + public override int Item3 { get => BitConverter.ToUInt16(Data, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x08); } + public override int Item4 { get => BitConverter.ToUInt16(Data, 0x0A); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0A); } + + public override uint AI { get => BitConverter.ToUInt32(Data, 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, 0x0C); } + public override bool Heal { get => Data[0x10] == 1; set => Data[0x10] = (byte)(value ? 1 : 0); } + public override int Money { get => Data[0x11]; set => Data[0x11] = (byte)value; } + public override int Gift { get => BitConverter.ToUInt16(Data, 0x12); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x12); } + + public int Val1 { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public int Mina { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public int Val2 { get => Data[0x16]; set => Data[0x16] = (byte)value; } + } +} \ No newline at end of file diff --git a/pkNX.Structures/VsTrainer/GG/TrainerPoke7b.cs b/pkNX.Structures/VsTrainer/GG/TrainerPoke7b.cs new file mode 100644 index 00000000..f0d855c6 --- /dev/null +++ b/pkNX.Structures/VsTrainer/GG/TrainerPoke7b.cs @@ -0,0 +1,79 @@ +using System; + +namespace pkNX.Structures +{ + public class TrainerPoke7b : TrainerPoke + { + internal const int SIZE = 0x28; + public override TrainerPoke Clone() => new TrainerPoke7b(Write()); + public TrainerPoke7b(byte[] data = null) => Data = data ?? new byte[SIZE]; + + public TrainerPoke7b(int index, byte[] data = null) + { + Data = new byte[SIZE]; + if (data == null || data.Length < (index + 1) * SIZE) + return; + Array.Copy(data, index * SIZE, Data, 0, SIZE); + } + + public override int Gender + { + get => Data[0] & 0x3; + set => Data[0] = (byte)((Data[0] & 0xFC) | (value & 0x3)); + } + + public override int Ability + { + get => (Data[0] >> 4) & 0x3; + set => Data[0] = (byte)((Data[0] & 0xCF) | ((value & 0x3) << 4)); + } + + public override int Nature { get => Data[0x01]; set => Data[0x01] = (byte)value; } + + public override int EV_HP { get => Data[0x02]; set => Data[0x02] = (byte)value; } + public override int EV_ATK { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public override int EV_DEF { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public override int EV_SPA { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public override int EV_SPD { get => Data[0x06]; set => Data[0x06] = (byte)value; } + public override int EV_SPE { get => Data[0x07]; set => Data[0x07] = (byte)value; } + + public override int AV_HP { get => Data[0x08]; set => Data[0x08] = (byte)value; } + public override int AV_ATK { get => Data[0x09]; set => Data[0x09] = (byte)value; } + public override int AV_DEF { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } + public override int AV_SPA { get => Data[0x0B]; set => Data[0x0B] = (byte)value; } + public override int AV_SPD { get => Data[0x0C]; set => Data[0x0C] = (byte)value; } + public override int AV_SPE { get => Data[0x0D]; set => Data[0x0D] = (byte)value; } + + public override int Friendship { get => Data[0x0E]; set => Data[0x0E] = (byte)value; } + public override int Rank { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } + + public override uint IV32 { get => Data[0x10]; set => Data[0x10] = (byte)value; } + public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 00)) | (uint)((value > 31 ? 31 : value) << 00)); } + public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 05)) | (uint)((value > 31 ? 31 : value) << 05)); } + public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 10)) | (uint)((value > 31 ? 31 : value) << 10)); } + public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 15)) | (uint)((value > 31 ? 31 : value) << 15)); } + public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 20)) | (uint)((value > 31 ? 31 : value) << 20)); } + public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 25)) | (uint)((value > 31 ? 31 : value) << 25)); } + public override bool Shiny { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (uint)((IV32 & ~0x40000000) | (uint)(value ? 0x40000000 : 0)); } + + public override bool CanMegaEvolve + { + get => ((IV32 >> 31) & 1) == 1; + set => IV32 = (IV32 & ~(1 << 31)) | (uint)((value ? 1 : 0) << 31); + } + + // 14-15 unused + + public override int Level { get => BitConverter.ToUInt16(Data, 0x16); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x16); } + public override int Species { get => BitConverter.ToUInt16(Data, 0x18); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x18); } + public override int Form { get => BitConverter.ToUInt16(Data, 0x1A); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x1A); } + public override int HeldItem { get => BitConverter.ToUInt16(Data, 0x1C); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x1C); } + + // 1E-1F unused + + public override int Move1 { get => BitConverter.ToUInt16(Data, 0x20); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x20); } + public override int Move2 { get => BitConverter.ToUInt16(Data, 0x22); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x22); } + public override int Move3 { get => BitConverter.ToUInt16(Data, 0x24); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x24); } + public override int Move4 { get => BitConverter.ToUInt16(Data, 0x26); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x26); } + } +} \ No newline at end of file diff --git a/pkNX.Structures/VsTrainer/VsTrainer.cs b/pkNX.Structures/VsTrainer/VsTrainer.cs new file mode 100644 index 00000000..dd429eb2 --- /dev/null +++ b/pkNX.Structures/VsTrainer/VsTrainer.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace pkNX.Structures +{ + public class VsTrainer + { + public int ID { get; set; } + public string Name { get; set; } + public TrainerData Trainer { get; set; } + public readonly List Team = new List(6); + + public TrainerClass GetClass(IList list) => list[Trainer.Class]; + } +} diff --git a/pkNX.Structures/pkNX.Structures.csproj b/pkNX.Structures/pkNX.Structures.csproj new file mode 100644 index 00000000..c7962628 --- /dev/null +++ b/pkNX.Structures/pkNX.Structures.csproj @@ -0,0 +1,8 @@ + + + + netstandard2.0;net46 + Data Structures + + + diff --git a/pkNX.Tests/UnitTest1.cs b/pkNX.Tests/UnitTest1.cs new file mode 100644 index 00000000..f858324e --- /dev/null +++ b/pkNX.Tests/UnitTest1.cs @@ -0,0 +1,13 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace pkNX.Tests +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + } + } +} diff --git a/pkNX.Tests/pkNX.Tests.csproj b/pkNX.Tests/pkNX.Tests.csproj new file mode 100644 index 00000000..9c19c607 --- /dev/null +++ b/pkNX.Tests/pkNX.Tests.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp2.0 + + false + + Ensuring functionality works as intended + + + + + + + + + + + + + + + diff --git a/pkNX.WinForms/Main.Designer.cs b/pkNX.WinForms/Main.Designer.cs new file mode 100644 index 00000000..89a74ccb --- /dev/null +++ b/pkNX.WinForms/Main.Designer.cs @@ -0,0 +1,194 @@ +namespace pkNX.WinForms +{ + partial class Main + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.TB_Path = new System.Windows.Forms.TextBox(); + this.menuStrip1 = new System.Windows.Forms.MenuStrip(); + this.Menu_File = new System.Windows.Forms.ToolStripMenuItem(); + this.Menu_Open = new System.Windows.Forms.ToolStripMenuItem(); + this.Menu_Exit = new System.Windows.Forms.ToolStripMenuItem(); + this.Menu_Options = new System.Windows.Forms.ToolStripMenuItem(); + this.Menu_Language = new System.Windows.Forms.ToolStripMenuItem(); + this.CB_Lang = new System.Windows.Forms.ToolStripComboBox(); + this.randomizationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.Menu_SetRNGSeed = new System.Windows.Forms.ToolStripMenuItem(); + this.Menu_Restore = new System.Windows.Forms.ToolStripMenuItem(); + this.FLP_Controls = new System.Windows.Forms.FlowLayoutPanel(); + this.menuStrip1.SuspendLayout(); + this.SuspendLayout(); + // + // TB_Path + // + this.TB_Path.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.TB_Path.Location = new System.Drawing.Point(124, 4); + this.TB_Path.Name = "TB_Path"; + this.TB_Path.ReadOnly = true; + this.TB_Path.Size = new System.Drawing.Size(277, 20); + this.TB_Path.TabIndex = 10; + this.TB_Path.Text = "No Game Loaded"; + // + // menuStrip1 + // + this.menuStrip1.BackColor = System.Drawing.SystemColors.Control; + this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.Menu_File, + this.Menu_Options}); + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Size = new System.Drawing.Size(413, 24); + this.menuStrip1.TabIndex = 12; + this.menuStrip1.Text = "menuStrip1"; + // + // Menu_File + // + this.Menu_File.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.Menu_Open, + this.Menu_Exit}); + this.Menu_File.Name = "Menu_File"; + this.Menu_File.Size = new System.Drawing.Size(37, 20); + this.Menu_File.Text = "File"; + // + // Menu_Open + // + this.Menu_Open.Name = "Menu_Open"; + this.Menu_Open.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); + this.Menu_Open.Size = new System.Drawing.Size(155, 22); + this.Menu_Open.Text = "&Open..."; + this.Menu_Open.Click += new System.EventHandler(this.Menu_Open_Click); + // + // Menu_Exit + // + this.Menu_Exit.Name = "Menu_Exit"; + this.Menu_Exit.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Q))); + this.Menu_Exit.Size = new System.Drawing.Size(155, 22); + this.Menu_Exit.Text = "&Exit"; + this.Menu_Exit.Click += new System.EventHandler(this.Menu_Exit_Click); + // + // Menu_Options + // + this.Menu_Options.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.Menu_Language, + this.randomizationToolStripMenuItem, + this.Menu_Restore}); + this.Menu_Options.Name = "Menu_Options"; + this.Menu_Options.Size = new System.Drawing.Size(61, 20); + this.Menu_Options.Text = "Options"; + // + // Menu_Language + // + this.Menu_Language.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.CB_Lang}); + this.Menu_Language.Name = "Menu_Language"; + this.Menu_Language.Size = new System.Drawing.Size(184, 22); + this.Menu_Language.Text = "Language"; + // + // CB_Lang + // + this.CB_Lang.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.CB_Lang.Items.AddRange(new object[] { + "カタカナ", + "漢字", + "English", + "Français", + "Italiano", + "Deutsch", + "Español", + "한국", + "汉字简化方案", + "漢字簡化方案"}); + this.CB_Lang.Name = "CB_Lang"; + this.CB_Lang.Size = new System.Drawing.Size(121, 23); + this.CB_Lang.SelectedIndexChanged += new System.EventHandler(this.ChangeLanguage); + // + // randomizationToolStripMenuItem + // + this.randomizationToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.Menu_SetRNGSeed}); + this.randomizationToolStripMenuItem.Name = "randomizationToolStripMenuItem"; + this.randomizationToolStripMenuItem.Size = new System.Drawing.Size(184, 22); + this.randomizationToolStripMenuItem.Text = "Randomization"; + // + // Menu_SetRNGSeed + // + this.Menu_SetRNGSeed.Name = "Menu_SetRNGSeed"; + this.Menu_SetRNGSeed.Size = new System.Drawing.Size(146, 22); + this.Menu_SetRNGSeed.Text = "Set int32 seed"; + this.Menu_SetRNGSeed.Click += new System.EventHandler(this.Menu_SetRNGSeed_Click); + // + // Menu_Restore + // + this.Menu_Restore.Enabled = false; + this.Menu_Restore.Name = "Menu_Restore"; + this.Menu_Restore.Size = new System.Drawing.Size(184, 22); + this.Menu_Restore.Text = "Restore Original Files"; + // + // FLP_Controls + // + this.FLP_Controls.Dock = System.Windows.Forms.DockStyle.Fill; + this.FLP_Controls.Location = new System.Drawing.Point(0, 24); + this.FLP_Controls.Name = "FLP_Controls"; + this.FLP_Controls.Size = new System.Drawing.Size(413, 238); + this.FLP_Controls.TabIndex = 13; + // + // Main + // + this.AllowDrop = true; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(413, 262); + this.Controls.Add(this.FLP_Controls); + this.Controls.Add(this.TB_Path); + this.Controls.Add(this.menuStrip1); + this.Name = "Main"; + this.Text = "pkNX"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Main_FormClosing); + this.menuStrip1.ResumeLayout(false); + this.menuStrip1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.TextBox TB_Path; + private System.Windows.Forms.MenuStrip menuStrip1; + private System.Windows.Forms.ToolStripMenuItem Menu_File; + private System.Windows.Forms.ToolStripMenuItem Menu_Open; + private System.Windows.Forms.ToolStripMenuItem Menu_Exit; + private System.Windows.Forms.ToolStripMenuItem Menu_Options; + private System.Windows.Forms.ToolStripMenuItem Menu_Language; + private System.Windows.Forms.ToolStripComboBox CB_Lang; + private System.Windows.Forms.ToolStripMenuItem randomizationToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem Menu_SetRNGSeed; + private System.Windows.Forms.ToolStripMenuItem Menu_Restore; + private System.Windows.Forms.FlowLayoutPanel FLP_Controls; + } +} + diff --git a/pkNX.WinForms/Main.cs b/pkNX.WinForms/Main.cs new file mode 100644 index 00000000..01a4a5da --- /dev/null +++ b/pkNX.WinForms/Main.cs @@ -0,0 +1,138 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Windows.Forms; +using pkNX.Structures; +using pkNX.WinForms.Properties; +using EditorBase = pkNX.WinForms.Controls.EditorBase; + +namespace pkNX.WinForms +{ + public partial class Main : Form + { + private int Language + { + get => CB_Lang.SelectedIndex; + set => CB_Lang.SelectedIndex = value; + } + + private EditorBase Editor; + + public Main() + { + InitializeComponent(); + + CB_Lang.SelectedIndex = Settings.Default.Language; + if (!string.IsNullOrWhiteSpace(Settings.Default.GamePath)) + OpenPath(Settings.Default.GamePath); + + DragDrop += (s, e) => + { + var files = (string[]) e.Data.GetData(DataFormats.FileDrop); + foreach (var f in files) + OpenPath(f); + }; + DragEnter += (s, e) => + { + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + e.Effect = DragDropEffects.Copy; + }; + } + + private void ChangeLanguage(object sender, EventArgs e) + { + Menu_Options.DropDown.Close(); + if (Editor == null) + return; + + if (Editor.Game.GetGeneration() < 7 && Language > 7) + { + WinFormsUtil.Alert("Selected Language is not available for this game", "Defaulting to English."); + CB_Lang.SelectedIndex = 2; + return; + } + Editor.Language = Language; + } + + private void Menu_Open_Click(object sender, EventArgs e) + { + var fbd = new FolderBrowserDialog(); + if (fbd.ShowDialog() == DialogResult.OK) + OpenPath(fbd.SelectedPath); + } + + private void Main_FormClosing(object sender, FormClosingEventArgs e) + { + if (Editor == null) + return; + + Editor.Close(); + Settings.Default.Language = CB_Lang.SelectedIndex; + Settings.Default.GamePath = TB_Path.Text; + Settings.Default.Save(); + } + + private void Menu_Exit_Click(object sender, EventArgs e) => Close(); + + private void Menu_SetRNGSeed_Click(object sender, EventArgs e) + { + var result = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Reseed RNG?", + "If yes, copy the 32 bit (not hex) integer seed to the clipboard before hitting Yes."); + if (DialogResult.Yes != result) + return; + + string val = string.Empty; + try { val = Clipboard.GetText(); } + catch { } + if (int.TryParse(val, out int seed)) + { + Util.Rand = new Random(seed); + WinFormsUtil.Alert($"Reseeded RNG to seed: {seed}"); + return; + } + WinFormsUtil.Alert("Unable to set seed."); + } + + private void OpenPath(string path) + { + if (Directory.Exists(path)) + OpenFolder(path); + else + OpenFile(path); + } + + private void OpenFile(string path) + { + var result = FileRipper.TryOpenFile(path); + if (result.Code != RipResultCode.Success) + { + WinFormsUtil.Alert("Invalid file loaded." + Environment.NewLine + $"Unable to recognize data: ${result}.", path); + return; + } + + System.Media.SystemSounds.Asterisk.Play(); + Process.Start("explorer.exe", result.ResultPath); + } + + private void OpenFolder(string path) + { + var editor = EditorBase.GetEditor(path, Language); + if (editor == null) + WinFormsUtil.Alert("Invalid folder loaded." + Environment.NewLine + "Unable to recognize game data.", path); + else + LoadROM(editor); + } + + private void LoadROM(EditorBase editor) + { + Editor = editor; + var ctrl = Editor.GetControls(80, 20); + FLP_Controls.Controls.Clear(); + foreach (var c in ctrl) + FLP_Controls.Controls.Add(c); + + Text = $"{nameof(pkNX)} - {Editor.Game}"; + System.Media.SystemSounds.Asterisk.Play(); + } + } +} diff --git a/pkNX.WinForms/Main.resx b/pkNX.WinForms/Main.resx new file mode 100644 index 00000000..59dbc9db --- /dev/null +++ b/pkNX.WinForms/Main.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + True + + \ No newline at end of file diff --git a/pkNX.WinForms/MainEditor/EditorAO.cs b/pkNX.WinForms/MainEditor/EditorAO.cs new file mode 100644 index 00000000..2b0aeecb --- /dev/null +++ b/pkNX.WinForms/MainEditor/EditorAO.cs @@ -0,0 +1,9 @@ +using pkNX.Game; + +namespace pkNX.WinForms.Controls +{ + internal class EditorAO : EditorBase + { + protected internal EditorAO(GameManager rom) : base(rom) { } + } +} \ No newline at end of file diff --git a/pkNX.WinForms/MainEditor/EditorGG.cs b/pkNX.WinForms/MainEditor/EditorGG.cs new file mode 100644 index 00000000..06854646 --- /dev/null +++ b/pkNX.WinForms/MainEditor/EditorGG.cs @@ -0,0 +1,9 @@ +using pkNX.Game; + +namespace pkNX.WinForms.Controls +{ + internal class EditorGG : EditorBase + { + protected internal EditorGG(GameManager rom) : base(rom) { } + } +} \ No newline at end of file diff --git a/pkNX.WinForms/MainEditor/EditorORASDEMO.cs b/pkNX.WinForms/MainEditor/EditorORASDEMO.cs new file mode 100644 index 00000000..fd11bc2b --- /dev/null +++ b/pkNX.WinForms/MainEditor/EditorORASDEMO.cs @@ -0,0 +1,9 @@ +using pkNX.Game; + +namespace pkNX.WinForms.Controls +{ + internal class EditorORASDEMO : EditorBase + { + protected internal EditorORASDEMO(GameManager rom) : base(rom) { } + } +} \ No newline at end of file diff --git a/pkNX.WinForms/MainEditor/EditorProvider.cs b/pkNX.WinForms/MainEditor/EditorProvider.cs new file mode 100644 index 00000000..1727198d --- /dev/null +++ b/pkNX.WinForms/MainEditor/EditorProvider.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Reflection; +using System.Windows.Forms; +using pkNX.Game; +using pkNX.Structures; + +namespace pkNX.WinForms.Controls +{ + public abstract class EditorBase + { + protected readonly GameManager ROM; + + public GameVersion Game => ROM.Game; + public int Language { get => ROM.Language; set => ROM.Language = value; } + + protected EditorBase(GameManager rom) => ROM = rom; + + public IEnumerable