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