From 93c08e50746237e74536ed3b8887c938659bbef0 Mon Sep 17 00:00:00 2001 From: ExplosBlue Date: Wed, 6 Jun 2018 16:13:25 +0100 Subject: [PATCH] Add Files --- .gitignore | 107 +++++ LICENSE | 674 +++++++++++++++++++++++++++++ PointWidget.py | 214 +++++++++ README.md | 10 + RouteEdit.py | 129 ++++++ RouteEditData/icons/copy.png | Bin 0 -> 754 bytes RouteEditData/icons/cut.png | Bin 0 -> 1300 bytes RouteEditData/icons/delete.png | Bin 0 -> 502 bytes RouteEditData/icons/file.png | Bin 0 -> 283 bytes RouteEditData/icons/file_image.png | Bin 0 -> 529 bytes RouteEditData/icons/folder.png | Bin 0 -> 428 bytes RouteEditData/icons/paste.png | Bin 0 -> 751 bytes RouteEditData/icons/save.png | Bin 0 -> 443 bytes RouteWidget.py | 176 ++++++++ SARC.py | 577 ++++++++++++++++++++++++ bossPathWidget.py | 266 ++++++++++++ bytes.py | 44 ++ 17 files changed, 2197 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 PointWidget.py create mode 100644 README.md create mode 100644 RouteEdit.py create mode 100644 RouteEditData/icons/copy.png create mode 100644 RouteEditData/icons/cut.png create mode 100644 RouteEditData/icons/delete.png create mode 100644 RouteEditData/icons/file.png create mode 100644 RouteEditData/icons/file_image.png create mode 100644 RouteEditData/icons/folder.png create mode 100644 RouteEditData/icons/paste.png create mode 100644 RouteEditData/icons/save.png create mode 100644 RouteWidget.py create mode 100644 SARC.py create mode 100644 bossPathWidget.py create mode 100644 bytes.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8ed32b --- /dev/null +++ b/.gitignore @@ -0,0 +1,107 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + + +.idea/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /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/PointWidget.py b/PointWidget.py new file mode 100644 index 0000000..b5f4568 --- /dev/null +++ b/PointWidget.py @@ -0,0 +1,214 @@ +import re +from PyQt5 import QtCore, QtGui, QtWidgets +Qt = QtCore.Qt + +class pointEditorWidget(QtWidgets.QWidget): + def __init__(self, arc, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + + self.arc = arc + self.fileLoaded = False + self.currentLoadedFile = "" + self.selectedFile = "" + self.layout = QtWidgets.QVBoxLayout(self) + + # Create Widgets + self.fileSelector = QtWidgets.QComboBox() + self.scrollArea = QtWidgets.QScrollArea() + self.pointEntries = pointEntryContainer() + self.header = header() + + # Setup Scroll Area + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setWidget(self.pointEntries) + + # Setup file selector + for file in self.arc: + self.fileSelector.addItem(str(file.name)[5:-4]) + + self.fileSelector.currentIndexChanged.connect(self.fileIndexChanged) + + # add widgets to layout + self.layout.addWidget(self.fileSelector) + self.layout.addWidget(self.header) + self.layout.addWidget(self.scrollArea) + + # load initial file + self.fileIndexChanged() + + + def fileIndexChanged(self): + + # store the currently selected file's name + self.selectedFile = "point" + self.fileSelector.currentText() + ".csv" + + # check if a file is already open + if self.fileLoaded: + # if a file is already open, store the changes made and close the file + self.storeChanges() + self.pointEntries.reset() + self.loadDataFromFile() + else: + self.loadDataFromFile() + + + def loadDataFromFile(self): + + dataArray = [] + + # load the data for the file the user selected + for file in self.arc: + if file.name == self.selectedFile: + data = file.data + data = data.decode('shiftjis') + + # split data by row + rows = str(data).split() + + # split rows elements but don't break substrings + for row in rows: + row = re.split(''',(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', row) + dataArray.append(row) + + # create a point entry container + self.pointEntries.populate(dataArray) + + self.fileLoaded = True + self.currentLoadedFile = self.selectedFile + + + def storeChanges(self): + data = self.pointEntries.pointsToString() + data = data.encode('shiftjis') + + for file in self.arc: + if str(file.name) == self.currentLoadedFile: + file.data = data + + def getArc(self): + self.storeChanges() + return self.arc + +class pointEntryContainer(QtWidgets.QWidget): + def __init__(self, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + self.layout = QtWidgets.QVBoxLayout(self) + + self.pointEntries = [] + + def populate(self, dataArray): + i = 0 + while i < len(dataArray): + point = pointEntry(dataArray[i]) + self.layout.addWidget(point) + self.pointEntries.append(point) + i += 1 + + def pointsToString(self): + temp = [] + for point in self.pointEntries: + temp.append(point.valuesToString()) + outString = "\r\n".join(temp) + outString = outString + "\r\n" + return outString + + def reset(self): + self.clearLayout(self.layout) + self.pointEntries = [] # f + + def clearLayout(self, layout): + while layout.count(): + child = layout.takeAt(0) + if child.widget() is not None: + child.widget().deleteLater() + elif child.layout() is not None: + self.clearLayout(child.layout()) + +class pointEntry(QtWidgets.QWidget): + def __init__(self, data, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + self.layout = QtWidgets.QHBoxLayout(self) + + self.idLabel = QtWidgets.QLabel(data[0]) + self.nodeName = QtWidgets.QLineEdit(data[1]) + self.nodeFlag = QtWidgets.QLineEdit(data[2]) + self.nodeUnlocks = QtWidgets.QLineEdit(data[3]) + self.pathUnlocks = QtWidgets.QLineEdit(data[4]) + self.pipeDest = QtWidgets.QLineEdit(data[5]) + self.seNodeUnlocks = QtWidgets.QLineEdit(data[6]) + self.sePathUnlocks = QtWidgets.QLineEdit(data[7]) + self.unk1 = QtWidgets.QLineEdit(data[8]) + + # if data[2] == 'stop': + # self.stopFlag.setChecked(True) + + self.setMaximumHeight(75) + + self.layout.addWidget(self.idLabel) + self.layout.addWidget(self.nodeName) + self.layout.addWidget(self.nodeFlag) + self.layout.addWidget(self.nodeUnlocks) + self.layout.addWidget(self.pathUnlocks) + self.layout.addWidget(self.pipeDest) + self.layout.addWidget(self.seNodeUnlocks) + self.layout.addWidget(self.sePathUnlocks) + self.layout.addWidget(self.unk1) + + # set background colour + pal = QtGui.QPalette() + pal.setColor(QtGui.QPalette.Background, QtGui.QColor(249, 249, 249)) + self.setAutoFillBackground(True) + self.setPalette(pal) + + def valuesToString(self): + temp = [] + temp.append(self.idLabel.text()) + temp.append(self.nodeName.text()) + temp.append(self.nodeFlag.text()) + # if self.stopFlag.isChecked(): + # temp.append('stop') + # else: + # temp.append('') + temp.append(self.nodeUnlocks.text()) + temp.append(self.pathUnlocks.text()) + temp.append(self.pipeDest.text()) + temp.append(self.seNodeUnlocks.text()) + temp.append(self.sePathUnlocks.text()) + temp.append(self.unk1.text()) + output = ','.join(temp) + output.encode('shiftjis') + + return output + +class header(QtWidgets.QWidget): + def __init__(self, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + self.layout = QtWidgets.QHBoxLayout(self) + + idLabel = QtWidgets.QLabel('ID') + nodeNameLabel = QtWidgets.QLabel('Node Name') + stopFlagLabel = QtWidgets.QLabel('Stop Flag') + nodeUnlocksLabel = QtWidgets.QLabel('Node Unlocks') + pathUnlocksLabel = QtWidgets.QLabel('Path Unlocks') + pipeDestLabel = QtWidgets.QLabel('Pipe Destination') + seNodeUnlocksLabel = QtWidgets.QLabel('Secondary Node Unlocks') + sePathUnlocksLabel = QtWidgets.QLabel('Secondary Path Unlocks') + unk1Label = QtWidgets.QLabel('Unk1') + + self.setMaximumHeight(75) + + self.layout.addWidget(idLabel) + self.layout.addWidget(nodeNameLabel) + self.layout.addWidget(stopFlagLabel) + self.layout.addWidget(nodeUnlocksLabel) + self.layout.addWidget(pathUnlocksLabel) + self.layout.addWidget(pipeDestLabel) + self.layout.addWidget(seNodeUnlocksLabel) + self.layout.addWidget(sePathUnlocksLabel) + self.layout.addWidget(unk1Label) + + # set background colour + pal = QtGui.QPalette() + pal.setColor(QtGui.QPalette.Background, QtGui.QColor(249, 249, 249)) + self.setAutoFillBackground(True) + self.setPalette(pal) diff --git a/README.md b/README.md new file mode 100644 index 0000000..68d5620 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# RouteEdit +##An editor for NSMB2's routeInfo file +Uses Python 3 and PyQt5 +---------------------------------------------------------------- +### Credits +#### RoutEdit +* Explos -- Creator + +#### SARCLib +* Taken from Miyamoto! level editor -- https://github.com/aboood40091/Miyamoto \ No newline at end of file diff --git a/RouteEdit.py b/RouteEdit.py new file mode 100644 index 0000000..485b3c0 --- /dev/null +++ b/RouteEdit.py @@ -0,0 +1,129 @@ +import PointWidget +import RouteWidget +import bossPathWidget +import SARC as SarcLib +import sys +from PyQt5 import QtCore, QtGui, QtWidgets +Qt = QtCore.Qt + +class MainWindow(QtWidgets.QMainWindow): + """ + Main RoutEdit window + """ + + def __init__(self): + super().__init__(None) + self.setWindowTitle('RouteEdit') + self.setGeometry(500, 500, 1000, 500) + self.initUi() + + def initUi(self): + + # setup menu bar + mainMenu = self.menuBar() + fileMenu = mainMenu.addMenu('&File') + toolBar = self.addToolBar('File') + + toolBar.setMovable(False) + + self.openFile = QtWidgets.QAction(QtGui.QIcon('RouteEditData/icons/folder.png'), '&Open', self) + self.saveFile = QtWidgets.QAction(QtGui.QIcon('RouteEditData/icons/save.png'), '&Save', self) + + self.openFile.setShortcut('Ctrl+O') + self.saveFile.setShortcut('Ctrl+S') + + self.openFile.setStatusTip('Open a file') + self.openFile.setStatusTip('Save Changes') + + self.openFile.triggered.connect(self.loadSarc) + self.saveFile.triggered.connect(self.saveSarc) + + self.saveFile.setDisabled(True) + + fileMenu.addAction(self.openFile) + fileMenu.addAction(self.saveFile) + + toolBar.addAction(self.openFile) + toolBar.addSeparator() + toolBar.addAction(self.saveFile) + + + def loadSarc(self): + fileName = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', '', 'SARC files (*.sarc)')[0] + + if fileName == '': + return + + with open(fileName, 'rb') as fileObj: + data = fileObj.read() + + arc = SarcLib.SARC_Archive(data) + arc.load(data) + + self.editor = editorTabWidget(arc.contents) + self.setCentralWidget(self.editor) + + self.saveFile.setDisabled(False) + + for file in arc.contents: + print('Filename: ' + str(file.name), ' Contents: ' + str(file.data)) + + + def saveSarc(self): + arcContents = self.editor.getDataFromWidgets() + newArchive = SarcLib.SARC_Archive() + + for file in arcContents: + newArchive.addFile(file) + + outFile = newArchive.save() + + fileName = QtWidgets.QFileDialog.getSaveFileName(self, 'Open file', '', 'SARC files (*.sarc)')[0] + + with open(fileName, 'wb+') as f: + f.write(outFile) + f.close() + + +class editorTabWidget(QtWidgets.QTabWidget): + def __init__(self, arc, parent=None): + QtWidgets.QTabWidget.__init__(self, parent) + + #TODO Change this so each editor is only passed the files they need instead of the whole archive + + pointFiles = [] + routeFiles = [] + bossPathFiles = [] + + for file in arc: + if not str(file.name).find('point'): + pointFiles.append(file) + if not str(file.name).find('route'): + routeFiles.append(file) + if not str(file.name).find('worldIn') or not str(file.name).find('toCastle'): + bossPathFiles.append(file) + + # Create the editor widgets + self.pointEditor = PointWidget.pointEditorWidget(pointFiles) + self.routeEditor = RouteWidget.routeEditorWidget(routeFiles) + self.bossPathEditor = bossPathWidget.bossPathEditorWidget(bossPathFiles) + self.addTab(self.pointEditor, 'Node Unlocks') + self.addTab(self.routeEditor, 'Path Settings') + self.addTab(self.bossPathEditor, 'Boss Path') + + + def getDataFromWidgets(self): + pointFiles = self.pointEditor.getArc() + routeFiles = self.routeEditor.getArc() + bossPathFiles = self.bossPathEditor.getArc() + + arc = pointFiles + routeFiles + bossPathFiles + + return arc + +if __name__ == '__main__': + app = QtWidgets.QApplication(sys.argv) + window = MainWindow() + window.show() + + sys.exit(app.exec_()) diff --git a/RouteEditData/icons/copy.png b/RouteEditData/icons/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..fba16a7bb1e3130464c64c851e884474b222e369 GIT binary patch literal 754 zcmV`Rt$+VD0Sg z?3vuq_}71={QxdJF4Oiku})uU1M1}$H_49J21g_u7Ne7K8IB&3XQW2u<)y`9 zL~My8Jrhtp0Qb&%Asv&Z8x18l`=}Jfx7r)+nE(&xRRHF)O%fSUsOCYvT=pP8RvU+-HxlT=_xl}_xEdhY%^ zDojw6K((|3#ln^$jat2HVls=IP0BE+N>hx)(!yK1=Tl{7;+TciACUj>#gGakB)0BE zEF?0LQeS%TnjUVqVh^c($<&j_k1>S{8(0go;==}4O((IOhv^NdJ!Q`4m>3NuP+0vA z)l%6oo>WHw0zpH^CU3s}w!sZbnNDU{XKN?{o25%-qpWr1ff9(19kB)Rhh&?+9|}r< zCm??X`bOgyQ>+fOUeu>=zu?*Ox(*4)^k@*SW!cpUEKmaRu^|W#nu+TV1&lC>s1L3p zfpx>6AKe0QRFBfbIKjTW8#j~yuLIUAs0^n7qTXiQwaqGe1ryiPO*^L!C4kid{)OFc zf{{Q8V2*2BRap7FjhQqto-3!u%{*!@S~|28TkQVYwC58@M0}9Q`uZl;LE!l<_D3!V zdu64z2o!E`*r^WO-H#;X8;OM9Z8vp5v4ZhM=l(=BC%g#z;?OJ1vXqF2!)JhR#MLVf)z17C z|CW4Iuo#RDQ031f467eMQhwnI9{^C(!DuYd^v2xj?8GRnLUuj z843F*ASa;oThO;ITU!R{s@BQ6`r0(AZhf$Of_MQGqj7Mi2vJkRt~vWLriR@`=gH+2 zb_{-|)%``vw(uBLb*)BeeObt965#_%oS#)ycpq(P&h+xc3vijDmSm#gR9PA4(G9F^ zJ#_V-@4mh&#c`^_uNy_*;~W(uJOKFRB4xN{0B$9h=2(YQ{2_r+~8blj>gOrBdiTr2%9m<7OHfG5}``czT- z$^$he_%)sMw$?1A)p`;s>16z>LzpVu()87YyX|*)h60_! z%_Q!M#&JhCs?ili@P7tJHqcG@EIPWVqbv<)p_|KR_E>-yQ&@U1C>_^V5zEUS=W0c# zri?z(k~26DT(TTnO-K8Sw0KA>{Jl!D$w?;^7KdIEmNn7LG&)s~*U}bDy*o+Zb;P{p zmSwC<;!8^Py8t_T;(Eicg`?&vsYtwQ}Gl2g}oB;)kid+;R9Jxz2=1t5b>*9Q)YR#ze$5LFJuMMv8% z<$h6nPDi4TSW*R2nucWO)*h-U`UusD<@y5m6+pY%?pG%E3GVkCf2@v1LnGH!=iwII z3%)HqW6<&7z(JERhH!(@W^w(_Cx3_0tHd8f-J=3K!eiBr@J1Djo$@b^ihp6IqW1D( zvz72XW~Xs*_J|8zi`z_6XkyNhg889o*6Iz&m*@^`kL0@!ttOHwi5?CM9?LGLe@tb3 z!}AqBcW?k*yfJzZS>oQOsMi6V5AUG;Rso+r*EI*9B7|8mumip4j<&`h$*aC*>i zbO`KvK)b}{&^?O%3un23GeO&K3Sycd${fQB&!X-=_6WT_{J1!@A3ZzaE(nf^^Ht&L z;P#2oNq1o|&*OX6!c&XuULcka5t9b^1v|xS)7q(`u5MPlb#!%rNm+u4Tdnqh83XOu zVG3B?H&_~X&st}Q^3hTI4~qKZvkVH8E4jm5kBN7Hs;>>jsXk9cskp<-j4?G@^BC;w zCStmeg+7Qa3@(TNmSkFS=z*ByKE%cmrR2x@uV!EHCt$yUH<>p;yyxdS$G@3?JB~^+ zn>+2>rnY?W%>Z0o%R+0000< KMNUMnLSTX<^oNB2 literal 0 HcmV?d00001 diff --git a/RouteEditData/icons/delete.png b/RouteEditData/icons/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..afe9db623b63bd86a1eb9ce7dae1b5604a4a8d4f GIT binary patch literal 502 zcmVen2WgBG`+CT8WL=3D&^}LK28k^4f9s%mtF%?L{G2xM5~zzHerixi3pD0Q&Ra z6<|LRAhCPBi3~HeXOz6t9xTpRGwGGL&VmY?s$zFdL;=`6=Xlsz6M|jT8@NpT8F|m9 z93Z}PxxfuJ;hZKACS{JuLNaza$EC#HmErJ$;mvV+E$3jjXu6RO$2i+Zgsm2CCd4f^ z)3!i-iU{cx05~vG3{Gv}v2YI_=t&}`#pk{1z3`UB*QJW8UQY$+X|na2Q0?`tRjXo~ zR18sLgxu9U6>#nD1L>&%V-O9`1XBTv+u!$Rr2)la1;)pQ>@obXY|0m^N4cl>tjxV! z`8$v~d6b)N`AX#)JimTGcsgjG<%jJix`fHt?C&n&_W}K1|NIW<0ki^I0j+>mz)yj) z^EMb9Fj{Xy$@yW~1A>+8s91akz;kD>X>b@Bwj2n4m>Tl|v~DRXO*ud^Hg`gQ!w&%P sMR`hl5eeP_;zm3^RiIp(RzRu17YqJ;j4$K>SpWb407*qoM6N<$g2Ni!ga7~l literal 0 HcmV?d00001 diff --git a/RouteEditData/icons/file.png b/RouteEditData/icons/file.png new file mode 100644 index 0000000000000000000000000000000000000000..0633557a262c49afcb211170d6090e82e232af27 GIT binary patch literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v}|{o-U3d z5uC{h5-%GBqZxcG{uYZKu5Sy-DPlSL&p1S6#^!RN5cbdmEK659X>uAGZ!j<}`7-~{ z%>xc93sy0^?2wSi5si?z5h?Mivy>%Nmf_PD26g3TQ_j8xyO$l+ z;GY2Z0)Y;`S(XfU1%>_opJ41` zXce>FKb9!ILmFWYmJ!}EY;3dK7_VgQxw3-U%{jq@1&#P%Hr=g7h2emQwjTelKM#R^ OWAJqKb6Mw<&;$S}8DdKS literal 0 HcmV?d00001 diff --git a/RouteEditData/icons/file_image.png b/RouteEditData/icons/file_image.png new file mode 100644 index 0000000000000000000000000000000000000000..a390d9be1d9554d322455203d100c39965c2bfb8 GIT binary patch literal 529 zcmV+s0`C2ZP)(9_1dPMrYED`#{PgrqJO8G~*2Ah?|+VX@{(_XrA zAdxB{7)>>}gOMZ%MixKvB@lfmVAN)kqS2(0P&-hr)^L6ihafZ=Nq#0`_AwVqTpix) z7mPbBa1NWAO&S@s1KvOag<`dhuE-nloUKTBwZ`N0Dv3|=J&^#43zdM_eFl+ux{Z!8 znP9M3Tkv$A6PzvpFyEE&;XhIK{$3EUd)FLM+995CrS{0b9R8 zt`z(RdlBJ^^FTZ8MNnJ0aH1gNMA+3wpf8Q;*tkeW>UxuyuHU5y&lr;b62+;1^ghy z(jqV&0Qunh?9Y5MGw?&qM8|yATGodRf!SyUo~QHWdF2tNu%`hG0Vw%(oi5$aX#|Ua zPF3p-fo9{bGH46S5b*0Sl@YM841vnfvp|l3Q@|S{FJ>SO57gk5SaG@`6 z!E0S8&(i>L`%2Fz9VDK6s}btdkr&Cjkjd1NdLd(ZNiuE_sxGbF#)W3N!%l&}RNxcn W<23!t!^_qH0000vXJq*QpNWBKW?oFdHB!w$R!@=x z`j%|aW%~c`G|+&XK+M3v$e{L*>7PkaRLE5@pA3SD6g`ZL|C0YRGTg|I2{Zsfk6!z3c3@P ztSbbHfzj4&+d%Ytp!g1!Uw`wrub%%JB!CNJIDp~r?`ez-jKR1x0K=VQ!jkphz%F29 zV8@|Okl{Z=CWILcdM4bq$2`eGn0UHQ} zKmgF!gso>_81P&`b_fvbLqefIwgWoXK$?YwEC9*VBoTrP1sP5Z24zEB>c|cOTn56# zab-K0Jd7sW0ffo{4I;BCHV1&JJElK>|1z*JGs7H;LDQ}r_|JHmf$_h7K|==v4;Lqj z#e%vX3>@MrC}NaySg;t74-{K}>)r!FAdP~I<*gYc#8HGO=3o>=yO+;C`2RPPBEx@1 z2VfK36&VXiC?2BNMl=I}EoU?lJlu&Vf3600&G5Ex@Ms516_~N3*oiF)zzVSujQ<&X hft`(2*yP9+0{}*8kE9M?(j))?002ovPDHLkV1lLAJtP1C literal 0 HcmV?d00001 diff --git a/RouteEditData/icons/save.png b/RouteEditData/icons/save.png new file mode 100644 index 0000000000000000000000000000000000000000..bb71556567afd40e9d2f1e3161bee96e22d1ffb9 GIT binary patch literal 443 zcmV;s0Yv_ZP)Z zU^9R!4%oDD1H-dt&%x$rWM+bCm;*p;e2$=s17=K{#&GZ6U9iPnJ-rwXz~>0MIRNGe z-hHP9k)wjH4gguqv-cE=ox{Tc;6(WJ86**AWr1nYo0l2%*UZC6_(X@mp4BR|K z1uH>CUp_N@4R8S~Bi;dfP7*Yi6a`;h%*k`Wmv7%0?mUL%aZ-VbtP}$e7bjR7*$(*o z?;peWA3sU88?2s_gPnnyiHQmh`1y-G2e7kI?tlT00Kj>}-^!0k8vJz9!EB;-Zv0fGnG-5d~~ng$&HfYE-fR@7-Grzozw*RuECeW-Nvf lEQ)DmF)njQyJiC(0s!z`DliN%d3yi=002ovPDHLkV1jDDyhs25 literal 0 HcmV?d00001 diff --git a/RouteWidget.py b/RouteWidget.py new file mode 100644 index 0000000..66ec3b6 --- /dev/null +++ b/RouteWidget.py @@ -0,0 +1,176 @@ +import re +from PyQt5 import QtCore, QtGui, QtWidgets +Qt = QtCore.Qt + +class routeEditorWidget(QtWidgets.QWidget): + def __init__(self, arc, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + + self.arc = arc + self.fileLoaded = False + self.currentLoadedFile = "" + self.selectedFile = "" + self.layout = QtWidgets.QVBoxLayout(self) + + # Create Widgets + self.fileSelector = QtWidgets.QComboBox() + self.scrollArea = QtWidgets.QScrollArea() + self.routeEntries = routeEntryContainer() + self.header = header() + + # Setup Scroll Area + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setWidget(self.routeEntries) + + # Setup file selector + for file in self.arc: + self.fileSelector.addItem(str(file.name)[5:-4]) + + self.fileSelector.currentIndexChanged.connect(self.fileIndexChanged) + + # add widgets to layout + self.layout.addWidget(self.fileSelector) + self.layout.addWidget(self.header) + self.layout.addWidget(self.scrollArea) + + # load initial file + self.fileIndexChanged() + + + def fileIndexChanged(self): + # store the currently selected file's name + self.selectedFile = "route" + self.fileSelector.currentText() + ".csv" + + # check if a file is already open + if self.fileLoaded: + # if a file is already open, store the changes made and close the file + self.storeChanges() + self.routeEntries.reset() + self.loadDataFromFile() + else: + self.loadDataFromFile() + + + def loadDataFromFile(self): + + dataArray = [] + + # load the data for the file the user selected + for file in self.arc: + if file.name == self.selectedFile: + data = file.data + data = data.decode('shiftjis') + + # split data by row + rows = str(data).split() + + # split rows elements but don't break substrings + for row in rows: + row = re.split(''',(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', row) + dataArray.append(row) + + # create a route entry container + self.routeEntries.populate(dataArray) + + self.fileLoaded = True + self.currentLoadedFile = self.selectedFile + + + def storeChanges(self): + data = self.routeEntries.routesToString() + data = data.encode('shiftjis') + + for file in self.arc: + if str(file.name) == self.currentLoadedFile: + file.data = data + + def getArc(self): + self.storeChanges() + return self.arc + +class routeEntryContainer(QtWidgets.QWidget): + def __init__(self, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + self.layout = QtWidgets.QVBoxLayout(self) + + self.routeEntries = [] + + def populate(self, dataArray): + i = 0 + while i < len(dataArray): + route = routeEntry(dataArray[i]) + self.layout.addWidget(route) + self.routeEntries.append(route) + i += 1 + + def routesToString(self): + temp = [] + for route in self.routeEntries: + temp.append(route.valuesToString()) + outString = "\r\n".join(temp) + outString = outString + "\r\n" + return outString + + def reset(self): + self.clearLayout(self.layout) + self.routeEntries = [] # f + + def clearLayout(self, layout): + while layout.count(): + child = layout.takeAt(0) + if child.widget() is not None: + child.widget().deleteLater() + elif child.layout() is not None: + self.clearLayout(child.layout()) + +class routeEntry(QtWidgets.QWidget): + def __init__(self, data, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + self.layout = QtWidgets.QHBoxLayout(self) + + self.pathName = QtWidgets.QLineEdit(data[0]) + self.movementType = QtWidgets.QLineEdit(data[1]) + self.soundEffect = QtWidgets.QLineEdit(data[2]) + + self.setMaximumHeight(75) + + self.layout.addWidget(self.pathName) + self.layout.addWidget(self.movementType) + self.layout.addWidget(self.soundEffect) + + # set background colour + pal = QtGui.QPalette() + pal.setColor(QtGui.QPalette.Background, QtGui.QColor(249, 249, 249)) + self.setAutoFillBackground(True) + self.setPalette(pal) + + def valuesToString(self): + temp = [] + temp.append(self.pathName.text()) + temp.append(self.movementType.text()) + temp.append(self.soundEffect.text()) + output = ','.join(temp) + output.encode('shiftjis') + + return output + +class header(QtWidgets.QWidget): + def __init__(self, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + self.layout = QtWidgets.QHBoxLayout(self) + + pathName = QtWidgets.QLabel('Path') + movementType = QtWidgets.QLabel('Movement Type') + soundEffect = QtWidgets.QLabel('Sound Effect') + + self.setMaximumHeight(75) + + self.layout.addWidget(pathName) + self.layout.addWidget(movementType) + self.layout.addWidget(soundEffect) + + # set background colour + pal = QtGui.QPalette() + pal.setColor(QtGui.QPalette.Background, QtGui.QColor(249, 249, 249)) + self.setAutoFillBackground(True) + self.setPalette(pal) diff --git a/SARC.py b/SARC.py new file mode 100644 index 0000000..6d31146 --- /dev/null +++ b/SARC.py @@ -0,0 +1,577 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Miyamoto! Level Editor - New Super Mario Bros. U Level Editor +# Copyright (C) 2009-2017 Treeki, Tempus, angelsl, JasonP27, Kinnay, +# MalStar1000, RoadrunnerWMC, MrRean, Grop, AboodXD, Gota7, John10v10 + +# This file is part of Miyamoto!. + +# Miyamoto! 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. + +# Miyamoto! 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 Miyamoto!. If not, see . + +# SARC.py +# A library for opening and saving the Nintendo SARC archive format + + +################################################################ +################################################################ + +############ Imports ############ + +import struct +from bytes import bytes_to_string + +################################# + + +class File: + """ + Class that represents a file of unknown format + """ + + def __init__(self, name='', data=b''): + self.name = name + self.data = data + + +class Folder: + """ + Class that represents a folder + """ + + def __init__(self, name='', contents=None): + self.name = name + + if contents is not None: + self.contents = contents + else: + self.contents = set() + + def addFile(self, file): + self.contents.add(file) + + def removeFile(self, file): + self.contents.remove(file) + + def addFolder(self, folder): + self.contents.add(folder) + + def removeFolder(self, folder): + self.contents.remove(folder) + + +class FileArchive: + """ + Class that represents any Nintendo file archive + """ + + def __init__(self): + self.contents = set() + self.endianness = '<' + + def clear(self): + self.contents = set() + + def __str__(self): + """ + Returns a string representation of this archive + """ + s = '' + + def addFolderStructure(folder, indent): + """ + Adds a folder structure to the repr with an indent level set to indent + """ + nonlocal s + + folders = set() + files = set() + + for thing in folder: + if isinstance(thing, File): + files.add(thing) + + else: + folders.add(thing) + + folders = sorted(folders, key=lambda entry: entry.name) + files = sorted(files, key=lambda entry: entry.name) + + for folder in folders: + s += '\n' + (' ' * indent) + folder.name + '/' + addFolderStructure(folder.contents, indent + 2) + + for file in files: + s += '\n' + (' ' * indent) + file.name + + addFolderStructure(self.contents, 0) + return s[1:] # Remove the leading \n + + def __getitem__(self, key): + """ + Returns the file requested when one indexes this archive + """ + currentPlaceToLook = self.contents + folderStructure = key.replace('\\', '/').split('/') + + for folderName in folderStructure[:-1]: + for lookObj in currentPlaceToLook: + if isinstance(lookObj, Folder) and lookObj.name == folderName: + currentPlaceToLook = lookObj.contents + break + + else: + raise KeyError('File/Folder not found') + + for file in currentPlaceToLook: + if file.name == folderStructure[-1]: + return file + + raise KeyError('File/Folder not found') + + def __setitem__(self, key, val): + """ + Handles the request to set a value to an index of the archive + """ + if not isinstance(val, (Folder, File)): + raise TypeError('New value is not a file or folder!') + + currentPlaceToLook = self.contents + folderStructure = key.replace('\\', '/').split('/') + + File.name = folderStructure[-1] + + for folderName in folderStructure[1:]: + for lookObj in currentPlaceToLook: + if isinstance(lookObj, Folder) and lookObj.name == folderName: + currentPlaceToLook = lookObj.contents + break + + else: + newFolder = Folder(folderName) + currentPlaceToLook.add(newFolder) + currentPlaceToLook = newFolder.contents + + for file in currentPlaceToLook: + if file.name == key: + currentPlaceToLook.remove(file) + + currentPlaceToLook.add(val) + + def __delitem__(self, key): + """ + Handles the request to delete an index of the archive + """ + currentPlaceToLook = self.contents + folderStructure = key.replace('\\', '/').split('/') + + for folderName in folderStructure[1:]: + for lookObj in currentPlaceToLook: + if isinstance(lookObj, Folder) and lookObj.name == folderName: + currentPlaceToLook = lookObj.contents + break + + else: + raise KeyError('File/Folder not found') + + for file in currentPlaceToLook: + if file.name == key: + currentPlaceToLook.remove(file) + return + + raise KeyError('File/Folder not found') + + def addFile(self, file): + self.contents.add(file) + + def removeFile(self, file): + self.contents.remove(file) + + def addFolder(self, folder): + self.contents.add(folder) + + def removeFolder(self, folder): + self.contents.remove(folder) + + +class SARC_Archive(FileArchive): + """ + Class that represents a 3DS SARC Archive + """ + + def __init__(self, data=None, endianness='<', hashMultiplier=0x65): + super().__init__() + + self.endianness = endianness + self.hashMultiplier = hashMultiplier + + if data is not None: + self.load(data) + + def load(self, data): + """ + Loads a SARC file from data + """ + + result = self._load(data) + if result is not True: + raise ValueError('This is not a valid SARC file! Error code: ' + str(result)) + + def _load(self, data): + + # SARC Header ----------------------------------------- + + # File magic (0x00 - 0x03) + if not data.startswith(b'SARC'): + return 1 + + # Come back to header length later, when we have endianness + + # Endianness/BOM (0x06 - 0x07) + bom = bytes(data[0x06:0x08]) + endians = {b'\xFE\xFF': '>', b'\xFF\xFE': '<'} + if bom not in endians: + return 2 + + self.endianness = endians[bom] + + # Header length (0x04 - 0x05) + headLen = struct.unpack(self.endianness + 'H', data[0x04:0x06])[0] + if headLen != 0x14: + return 3 + + # File Length (0x08 - 0x0B) + filelen = struct.unpack(self.endianness + 'I', data[0x08:0x0C])[0] + if len(data) != filelen: + return 4 + + # Beginning Of Data offset (0x0C - 0x0F) + begOfDat = struct.unpack(self.endianness + 'I', data[0x0C:0x10])[0] + + # SFAT Header ----------------------------------------- + + # Sanity check (0x14 - 0x17) + if data[0x14:0x18] != b'SFAT': + return 5 + + # Header length (0x18 - 0x19) + headLen = struct.unpack(self.endianness + 'H', data[0x18:0x1A])[0] + if headLen != 0x0C: + return 6 + + # Node count (0x1A - 0x1C) + nodeCount = struct.unpack(self.endianness + 'H', data[0x1A:0x1C])[0] + + # Hash multiplier (0x1D - 0x1F) + self.hashMultiplier = struct.unpack(self.endianness + 'I', data[0x1C:0x20])[0] + + # SFAT Nodes (0x20 - 0x20+(0x10*nodeCount)) + SFATNodes = [] + + SFATNodeOffset = 0x20 + for nodeNum in range(nodeCount): + if self.endianness == '>': + unkFlagOffset = SFATNodeOffset + 4 + fileNameTableEntryOffsetOffset = SFATNodeOffset + 5 + + else: + unkFlagOffset = SFATNodeOffset + 7 + fileNameTableEntryOffsetOffset = SFATNodeOffset + 4 + + # Unknown flag: Could function as a file/folder flag. + unkFlag = data[unkFlagOffset] + + # File Name Table Entry offset + if self.endianness == '>': + fileNameTableEntryOffsetData = (b'\x00' + + data[fileNameTableEntryOffsetOffset: + fileNameTableEntryOffsetOffset + 3]) + + else: + fileNameTableEntryOffsetData = \ + data[fileNameTableEntryOffsetOffset: + fileNameTableEntryOffsetOffset + 3] + b'\x00' + + fileNameTableEntryOffset = struct.unpack( + self.endianness + 'I', + fileNameTableEntryOffsetData)[0] + + # Beginning of Node File Data + fileDataStart = struct.unpack( + self.endianness + 'I', data[SFATNodeOffset + 8:SFATNodeOffset + 0x0C])[0] + + # End of Node File Data + fileDataEnd = struct.unpack( + self.endianness + 'I', data[SFATNodeOffset + 0x0C:SFATNodeOffset + 0x10])[0] + + # Calculate file data length + fileDataLength = fileDataEnd - fileDataStart + + # Add an entry to the node list + SFATNodes.append( + (unkFlag, fileNameTableEntryOffset, fileDataStart, fileDataLength)) + + # Increment the offset counter + SFATNodeOffset += 0x10 + + # SFNT Header ----------------------------------------- + + # From now on we need to keep track of an offset variable + offset = 0x20 + (0x10 * nodeCount) + + # Sanity check (offset - offset+0x03) + if data[offset:offset + 0x04] != b'SFNT': + return 7 + + # Header length (offset+0x04 - offset+0x05) + headLen = struct.unpack(self.endianness + 'H', data[offset + 0x04:offset + 0x06])[0] + if headLen != 0x08: + return 8 + + # Increment the offset + offset += 0x08 + + # Add the files to the self.contents set -------------- + self.clear() + for unkFlag, fileNameTableEntryOffset, fileDataStart, fileDataLength in SFATNodes: + + # Get the file name (search for the first null byte manually) + nameOffset = offset + (fileNameTableEntryOffset * 4) + name = bytes_to_string(data, nameOffset) + + # Get the file data + fileData = data[begOfDat + fileDataStart: + begOfDat + fileDataStart + fileDataLength] + + # Split it into its folders + folderStructure = name.split('/') + + # Handle it differently if the file is not in a folder + if len(folderStructure) == 1: + self.contents.add(File(name, fileData)) + + else: + + # Get the first folder, or make one if needed + folderName = folderStructure[0] + for foundFolder in self.contents: + if not isinstance(foundFolder, Folder): + continue + + if foundFolder.name == folderName: + break + + else: + foundFolder = Folder(folderName) + self.contents.add(foundFolder) + + # Now find/make the rest of them + outerFolder = foundFolder + for folderName in folderStructure[1:-1]: + for foundFolder in outerFolder.contents: + if not isinstance(foundFolder, Folder): + continue + + if foundFolder.name == folderName: + break + + else: + foundFolder = Folder(folderName) + outerFolder.addFolder(foundFolder) + + outerFolder = foundFolder + + # Now make a new file and add it to self.contents + outerFolder.addFile(File(folderStructure[-1], fileData)) + + # We're done! Return True so no exception will be thrown. + return True + + @staticmethod + def filenameHash(filename, endianness, multiplier): + """ + Returns the hash that should be used by an SFAT node. + """ + result = 0 + for char in filename: + result = result * multiplier + ord(char) + result &= 0xFFFFFFFF + + return struct.pack(endianness + 'I', result) + + def save(self, padding=4, dataStartOffset=None): + """ + Returns a bytes object that can be saved to a file. + """ + if dataStartOffset is None: + dataStartOffset = padding + + # Flatten the file list + flatList = [] + + def addToFlatList(folder, path): + nonlocal flatList + nonlocal checkObj + + for checkObj in folder.contents: + if isinstance(checkObj, File): + flatList.append((path + '/' + checkObj.name, checkObj)) + + else: + addToFlatList(checkObj, path + '/' + checkObj.name) + + for checkObj in self.contents: + if isinstance(checkObj, File): + flatList.append((checkObj.name, checkObj)) + + else: + addToFlatList(checkObj, checkObj.name) + + # Sort the files + flatList.sort( + key=lambda filetuple: + struct.unpack( + self.endianness + 'I', + self.filenameHash(filetuple[0], self.endianness, self.hashMultiplier), + ), + ) + + # Put each file object into a list + files = [[file, ] for file in flatList] + + # Create the File Names table + fileNamesTable = b'' + for i, filetuplelist in enumerate(files): + filepath = filetuplelist[0][0] + files[i] = [filetuplelist[0][1], ] + file = files[i] + + # Add the name offset, this will be used later + file.append(len(fileNamesTable)) + + # Add the name to the table + fileNamesTable += filepath.encode('utf-8') + + # Pad to 0x04 + fileNamesTable += b'\x00' * (0x04 - (len(fileNamesTable) % 0x04)) + + # Determine the length of the SFAT Nodes table + SFATNodesTableLen = 0x10 * len(files) + + # Determine the Beginning Of Data offset + begOfDat = max( + 0x20 + SFATNodesTableLen + 0x08 + len(fileNamesTable), dataStartOffset) + + # Create the File Data table + fileDataTable = b'' + for file in files: + + # Pad it to 0x04, relative to file start (if we're not at the very start) + totalFileLen = len(fileDataTable) + begOfDat + if totalFileLen % padding != 0: + fileDataTable += b'\x00' * (padding - (totalFileLen % padding)) + + assert (begOfDat + len(fileDataTable)) % padding == 0 + + # Add the data offset, this will be used later + file.append(len(fileDataTable)) + + # Add the data to the table + fileDataTable += file[0].data + + # Calculate total file length + totalFileLen = begOfDat + len(fileDataTable) + + # SARC Header ----------------------------------------- + + # File magic + sarcHead = b'SARC' + + # Header length (always 0x14) + sarcHead += struct.pack(self.endianness + 'H', 0x14) + + # BOM + sarcHead += b'\xFE\xFF' if self.endianness == '>' else b'\xFF\xFE' + + # File Length + sarcHead += struct.pack(self.endianness + 'I', totalFileLen) + + # Beginning Of Data offset + sarcHead += struct.pack(self.endianness + 'I', begOfDat) + + # Unknown value + if self.endianness == '>': + sarcHead += b'\1\0\0\0' + + else: + sarcHead += b'\0\1\0\0' + + # SFAT Header ----------------------------------------- + + # File magic + sfatHead = b'SFAT' + + # Header length (always 0x0C) + sfatHead += struct.pack(self.endianness + 'H', 0x0C) + + # Number of files + sfatHead += struct.pack(self.endianness + 'H', len(files)) + + # Hash multiplier + sfatHead += struct.pack(self.endianness + 'I', self.hashMultiplier) + + # SFAT Nodes + sfat = b'' + for (_, filenameoffset, filedataoffset), (filepath, file) in zip(files, flatList): + # File ID + sfat += self.filenameHash(filepath, self.endianness, self.hashMultiplier) + # Filename Offset (4 bytes + a constant?) + sfat += struct.pack(self.endianness + 'I', (filenameoffset // 4) | 0x1000000) + # Filedata Offset + sfat += struct.pack(self.endianness + 'I', filedataoffset) + # Filedata Length + Filedata Offset + sfat += struct.pack(self.endianness + 'I', filedataoffset + len(file.data)) + + # SFNT Header ----------------------------------------- + + # File magic + sfntHead = b'SFNT' + + # Header length (always 0x08) + if self.endianness == '>': + sfntHead += b'\x00\x08' + + else: + sfntHead += b'\x08\x00' + + # 2-byte padding + sfntHead += b'\x00\x00' + + # File Data Table Padding + headerSize = len(sarcHead + sfatHead + sfat + sfntHead + fileNamesTable) + fileDataTablePadding = b'' + + if begOfDat > headerSize: + fileDataTablePadding = b'\0' * (begOfDat - headerSize) + + # Put It All Together --------------------------------- + + fileData = b''.join([ + sarcHead, sfatHead, sfat, + sfntHead + fileNamesTable, + fileDataTablePadding + fileDataTable, + ]) + + # Return the data + return fileData diff --git a/bossPathWidget.py b/bossPathWidget.py new file mode 100644 index 0000000..c514708 --- /dev/null +++ b/bossPathWidget.py @@ -0,0 +1,266 @@ +import re +from PyQt5 import QtCore, QtGui, QtWidgets +Qt = QtCore.Qt + +class bossPathEditorWidget(QtWidgets.QWidget): + def __init__(self, arc, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + + self.arc = arc + self.fileLoaded = False + self.currentLoadedFile = "" + self.selectedFile = "" + self.layout = QtWidgets.QVBoxLayout(self) + + # Create Widgets + self.fileSelector = QtWidgets.QComboBox() + self.scrollArea = QtWidgets.QScrollArea() + self.BossPathEntries = bossPathEntryContainer() + + # Setup Scroll Area + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setWidget(self.BossPathEntries) + + # Setup file selector + for file in self.arc: + if not str(file.name).find('worldIn'): + self.fileSelector.addItem(str(file.name)[7:-4]) + + self.fileSelector.currentIndexChanged.connect(self.fileIndexChanged) + + # add widgets to layout + self.layout.addWidget(self.fileSelector) + self.layout.addWidget(self.scrollArea) + + # load initial file + self.fileIndexChanged() + + def fileIndexChanged(self): + + # store the currently selected file's name + self.selectedFile = self.fileSelector.currentText() + ".csv" + + # check if a file is already open + if self.fileLoaded: + # if a file is already open, store the changes made and close the file + self.storeChanges() + self.BossPathEntries.reset() + self.loadDataFromFile() + else: + self.loadDataFromFile() + + + def loadDataFromFile(self): + + files = [] + + # load the data for the file the user selected + for file in self.arc: + if file.name == "worldIn" + self.selectedFile: + files.append(file) + + elif file.name == "toCastle" + self.selectedFile: + files.append(file) + + self.BossPathEntries.populate(files) + + self.fileLoaded = True + self.currentLoadedFile = self.selectedFile + + + def storeChanges(self): + data = self.BossPathEntries.bossPathToArray() + + i = 0 + + for entry in data: + if str(entry) == "worldIn": + for file in self.arc: + if str(file.name) == "worldIn" + self.currentLoadedFile: + d = str(data[i+1]) + d = d.encode('shiftjis') + file.data = d + + elif str(entry) == "toCastle": + for file in self.arc: + if str(file.name) == "toCastle" + self.currentLoadedFile: + d = str(data[i+1]) + d = d.encode('shiftjis') + file.data = d + + i += 1 + + def getArc(self): + self.storeChanges() + return self.arc + +class bossPathEntryContainer(QtWidgets.QWidget): + def __init__(self, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + self.layout = QtWidgets.QVBoxLayout(self) + + self.worldInEntries = [] + self.toCastleEntries = [] + + def populate(self, files): + + for file in files: + if not str(file.name).find("worldIn"): + data = file.data + data = data.decode('shiftjis') + worldIn = worldInEntry(data) + self.layout.addWidget(worldIn) + self.worldInEntries.append(worldIn) + + if not str(file.name).find("toCastle"): + data = file.data + data = data.decode('shiftjis') + toCaslte = toCastleEntry(data) + self.layout.addWidget(toCaslte) + self.toCastleEntries.append(toCaslte) + + def bossPathToArray(self): + temp = [] + + if self.worldInEntries != []: + for worldIn in self.worldInEntries: + temp.append('worldIn') + temp.append(worldIn.valuesToString()) + + if self.toCastleEntries != []: + for toCastle in self.toCastleEntries: + temp.append('toCastle') + temp.append(toCastle.valuesToString()) + + return temp + + def reset(self): + self.clearLayout(self.layout) + self.worldInEntries = [] + self.toCastleEntries = [] + + def clearLayout(self, layout): + while layout.count(): + child = layout.takeAt(0) + if child.widget() is not None: + child.widget().deleteLater() + elif child.layout() is not None: + self.clearLayout(child.layout()) + +class worldInEntry(QtWidgets.QWidget): + def __init__(self, data, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + self.layout = QtWidgets.QHBoxLayout(self) + + self.entries = [] + + worldInLabel = QtWidgets.QLabel("WorldIn") + self.layout.addWidget(worldInLabel) + + addEntryBtn = QtWidgets.QPushButton("+") + addEntryBtn.pressed.connect(self.addNewEntry) + + removeEntryBtn = QtWidgets.QPushButton("-") + removeEntryBtn.pressed.connect(self.removeEntry) + + buttonLayout = QtWidgets.QVBoxLayout() + buttonLayout.addWidget(addEntryBtn) + buttonLayout.addWidget(removeEntryBtn) + + self.layout.addLayout(buttonLayout) + + data = str(data).split(',') + + for i in data: + lineEdit = QtWidgets.QLineEdit(i) + self.entries.append(lineEdit) + self.layout.addWidget(lineEdit) + + self.setMaximumHeight(75) + + # set background colour + pal = QtGui.QPalette() + pal.setColor(QtGui.QPalette.Background, QtGui.QColor(249, 249, 249)) + self.setAutoFillBackground(True) + self.setPalette(pal) + + def valuesToString(self): + temp = [] + + for lineEdit in self.entries: + temp.append(lineEdit.text()) + + output = ','.join(temp) + output.encode('shiftjis') + + return output + + def addNewEntry(self): + lineEdit = QtWidgets.QLineEdit() + self.entries.append(lineEdit) + self.layout.addWidget(lineEdit) + + def removeEntry(self): + if len(self.entries) >= 2: + if self.entries[-1] is not None: + self.entries[-1].deleteLater() + self.entries = self.entries[:-1] + +class toCastleEntry(QtWidgets.QWidget): + def __init__(self, data, parent=None): + QtWidgets.QWidget.__init__(self, parent=parent) + self.layout = QtWidgets.QHBoxLayout(self) + + self.entries = [] + + toCastleLabel = QtWidgets.QLabel("toCastle") + self.layout.addWidget(toCastleLabel) + + addEntryBtn = QtWidgets.QPushButton("+") + addEntryBtn.pressed.connect(self.addNewEntry) + + removeEntryBtn = QtWidgets.QPushButton("-") + removeEntryBtn.pressed.connect(self.removeEntry) + + buttonLayout = QtWidgets.QVBoxLayout() + buttonLayout.addWidget(addEntryBtn) + buttonLayout.addWidget(removeEntryBtn) + + self.layout.addLayout(buttonLayout) + + data = str(data).split(',') + + for i in data: + lineEdit = QtWidgets.QLineEdit(i) + self.entries.append(lineEdit) + self.layout.addWidget(lineEdit) + + self.setMaximumHeight(75) + + # set background colour + pal = QtGui.QPalette() + pal.setColor(QtGui.QPalette.Background, QtGui.QColor(249, 249, 249)) + self.setAutoFillBackground(True) + self.setPalette(pal) + + def valuesToString(self): + temp = [] + + for lineEdit in self.entries: + temp.append(lineEdit.text()) + + output = ','.join(temp) + output.encode('shiftjis') + + return output + + def addNewEntry(self): + lineEdit = QtWidgets.QLineEdit() + self.entries.append(lineEdit) + self.layout.addWidget(lineEdit) + + def removeEntry(self): + if len(self.entries) >= 2: + if self.entries[-1] is not None: + self.entries[-1].deleteLater() + self.entries = self.entries[:-1] diff --git a/bytes.py b/bytes.py new file mode 100644 index 0000000..9eb1027 --- /dev/null +++ b/bytes.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Miyamoto! Level Editor - New Super Mario Bros. U Level Editor +# Copyright (C) 2009-2017 Treeki, Tempus, angelsl, JasonP27, Kinnay, +# MalStar1000, RoadrunnerWMC, MrRean, Grop, AboodXD, Gota7, John10v10 + +# This file is part of Miyamoto!. + +# Miyamoto! 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. + +# Miyamoto! 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 Miyamoto!. If not, see . + + +################################################################ +################################################################ + +def bytes_to_string(data, offset=0, charWidth=1, encoding='utf-8'): + # Thanks RoadrunnerWMC + end = data.find(b'\0' * charWidth, offset) + if end == -1: + return data[offset:].decode(encoding) + + return data[offset:end].decode(encoding) + + +def to_bytes(inp, length=1, endianness='big'): + if isinstance(inp, bytearray): + return bytes(inp) + + elif isinstance(inp, int): + return inp.to_bytes(length, endianness) + + elif isinstance(inp, str): + return inp.encode('utf-8').ljust(length, b'\0')