diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..db0986c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing to esptool.py + +## Reporting Issues + +Please report bugs in esptool.py if you find them. + +However, before reporting a bug please check through the following: + +* [Troubleshooting Section](https://github.com/espressif/esptool/#troubleshooting) - common problems and known issues + +* [Existing Open Issues](https://github.com/espressif/esptool/issues) - someone might have already encountered this. + +If you don't find anything, please [open a new issue](https://github.com/espressif/esptool/issues/new). + +## Sending Feature Requests + +Feel free to post feature requests. It's helpful if you can explain exactly why the feature would be useful. + +There are usually some outstanding feature requests in the [existing issues list](https://github.com/espressif/esptool/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement), feel free to add comments to them. + +## Sending Pull Requests + +Pull Requests with changes and fixes are also welcome! + +### Code Style & Static Analysis + +esptool.py complies with Flake 8 and is valid Python 2 & Python 3 code (in the same source file.) + +When you submit a Pull Request, the Travis automated build system will run automated checks for this, using the [flake8 tool](http://flake8.readthedocs.io/en/latest/). If you want to check your code locally before submitting, you can install flake8 and run `python setup.py flake8` to test it. + +### Automated Integration Tests + +The test directory contains an integration suite with some integration tests for esptool.py: + +* `test_imagegen.py` tests the elf2image command and is run automatically by Travis for each Pull Request. You can run this command locally to check for regressions in the elf2image functionality. + +* `test_esptool.py` is a [Python unittest](https://docs.python.org/3/library/unittest.html) file that contains integration tests to be run against real ESP8266 or ESP32 hardware. These tests need real hardware so are not run automatically by Travis, they need to be run locally: + +`test_esptool.py` takes a command line with the following format: + +`./test_esptool.py [optional test name(s)]` + +For example, to run all tests on an ESP32 board connected to /dev/ttyUSB0, at 230400bps: + +`./test_esptool.py /dev/ttyUSB0 esp32 230400` + +Or to run the TestFlashing suite only on an ESP8266 board connected to /dev/ttyUSB2` at 460800bps: + +`./test_esptool.py /dev/ttyUSB2 esp8266 460800 TestFlashing` + +(Note that some tests will fail at higher baud rates on some hardware.) diff --git a/LICENSE b/LICENSE index f288702..d159169 100644 --- a/LICENSE +++ b/LICENSE @@ -1,622 +1,281 @@ GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 + Version 2, June 1991 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble - The 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 + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for -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. +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. - To protect your rights, we need to 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. + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether -gratis or for a fee, you must 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. +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - 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 + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to this License. - 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 + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot 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 +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. - Each version is given a distinguishing version number. If the -Program specifies 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. +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS @@ -628,15 +287,15 @@ 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 +convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) - This program is free software: you can redistribute it and/or modify + 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 + the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, @@ -644,31 +303,37 @@ the "copyright" line and a pointer to where the full notice is found. 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 . + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: +If the program is interactive, 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'. + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, 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 -. +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..724438d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,14 @@ +include README.md +include LICENSE +# sdist includes test/test*.py by default, but esptool.py tests +# are so far only intended to run from the git repo itself +prune test +prune ecdsa +prune pyaes +prune flasher_stub +prune .github +exclude .git* +exclude .travis* +exclude MANIFEST* + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad2543f --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# DoayeeESP32DFU + +A GUI for ESP32 flashing tool esptool. +**Note:** Currently using esptool v2.6 + +![gui](esp32bta/dfu/osxgui.PNG "Description goes here") + + +## Installing + +You can download prebuilt executable applications for both Windows and MacOS from ?. These are self-contained applications and have no prerequisites on your system. They have been tested with Windows 10 and macOS Mojave. + +## Usage + +If you compile your project using make, the App and partition table binaries will be put in your /build directory. The bootloader binary is under /build/bootloader.bin + +If the partition table has not been changed, it only needs to be reflashed when the ESP32 has been fully erased. Likewise the bootloader binary will not change between edits to your personal app code. This means only the App needs to be flashed each time + +## Running From Source + +**Note:** Currently using esptool v2.6 + +1. Install the project dependencies using your python3 package manager +2. Run the doayee_dfu.py script in python3 diff --git a/doayee_dfu.py b/doayee_dfu.py new file mode 100644 index 0000000..dcbd8c7 --- /dev/null +++ b/doayee_dfu.py @@ -0,0 +1,416 @@ +import wx +import sys +import threading +import serial.tools.list_ports +import os +import esptool +from serial import SerialException +from esptool import FatalError +import argparse + +# this class credit marcelstoer +# See discussion at http://stackoverflow.com/q/41101897/131929 +class RedirectText: + def __init__(self, text_ctrl): + self.out = text_ctrl + self.pending_backspaces = 0 + + def write(self, string): + new_string = "" + number_of_backspaces = 0 + for c in string: + if c == "\b": + number_of_backspaces += 1 + else: + new_string += c + + if self.pending_backspaces > 0: + # current value minus pending backspaces plus new string + new_value = self.out.GetValue()[:-1 * self.pending_backspaces] + new_string + wx.CallAfter(self.out.SetValue, new_value) + else: + wx.CallAfter(self.out.AppendText, new_string) + + self.pending_backspaces = number_of_backspaces + + def flush(self): + None + +class dfuTool(wx.Frame): + + ################################################################ + # INIT TASKS # + ################################################################ + def __init__(self, parent, title): + super(dfuTool, self).__init__(parent, title=title) + + self.baudrates = ['9600', '57600', '74880', '115200', '230400', '460800', '921600'] + self.SetSize(800,550) + self.SetMinSize(wx.Size(800,500)) + self.Centre() + self.initUI() + self.initFlags() + print('Doayee ESP32 Firmware Flasher') + print('--------------------------------------------') + + def initUI(self): + '''Runs on application start to build the GUI''' + + self.mainPanel = wx.Panel(self) + vbox = wx.BoxSizer(wx.VERTICAL) + ################################################################ + # BEGIN SERIAL OPTIONS GUI # + ################################################################ + self.serialPanel = wx.Panel(self.mainPanel) + serialhbox = wx.BoxSizer(wx.HORIZONTAL) + + self.serialtext = wx.StaticText(self.serialPanel,label = "Serial Port:", style = wx.ALIGN_CENTRE) + serialhbox.Add(self.serialtext,0.5,wx.ALL|wx.ALIGN_CENTER_VERTICAL,20) + + devices = self.list_serial_devices() + self.serialChoice = wx.Choice(self.serialPanel, choices=devices) + self.serialChoice.Bind(wx.EVT_CHOICE, self.on_serial_list_select) + serialhbox.Add(self.serialChoice,3,wx.ALL|wx.ALIGN_CENTER_VERTICAL,20) + + self.scanButton = wx.Button(parent=self.serialPanel, label='Rescan Ports') + self.scanButton.Bind(wx.EVT_BUTTON, self.on_serial_scan_request) + serialhbox.Add(self.scanButton,2,wx.ALL|wx.ALIGN_CENTER_VERTICAL,20) + + self.serialAutoCheckbox = wx.CheckBox(parent=self.serialPanel,label="Auto-detect (slow)") + self.serialAutoCheckbox.Bind(wx.EVT_CHECKBOX,self.on_serial_autodetect_check) + serialhbox.Add(self.serialAutoCheckbox,2,wx.ALL|wx.ALIGN_CENTER_VERTICAL,20) + + vbox.Add(self.serialPanel,1, wx.LEFT|wx.RIGHT|wx.EXPAND, 20) + ################################################################ + # BEGIN BAUD RATE GUI # + ################################################################ + self.baudPanel = wx.Panel(self.mainPanel) + baudhbox = wx.BoxSizer(wx.HORIZONTAL) + + self.baudtext = wx.StaticText(self.baudPanel,label = "Baud Rate:", style = wx.ALIGN_CENTRE) + baudhbox.Add(self.baudtext,0.5,wx.ALL,20) + + # create a button for each baud rate + for index, baud in enumerate(self.baudrates): + # use the first button to initialise the group + style = wx.RB_GROUP if index == 0 else 0 + + baudChoice = wx.RadioButton(self.baudPanel,style=style,label=baud, name=baud) + baudChoice.Bind(wx.EVT_RADIOBUTTON, self.on_baud_selected) + baudChoice.baudrate = baud + baudhbox.Add(baudChoice, 1, wx.TOP | wx.BOTTOM |wx.EXPAND, 20) + + # set the default up + if index == len(self.baudrates) - 1: + baudChoice.SetValue(True) + self.ESPTOOLARG_BAUD = baudChoice.baudrate + + vbox.Add(self.baudPanel,1, wx.LEFT|wx.RIGHT|wx.EXPAND, 20) + ################################################################ + # BEGIN ERASE BUTTON GUI # + ################################################################ + self.eraseButton = wx.Button(parent=self.mainPanel, label='Erase ESP') + self.eraseButton.Bind(wx.EVT_BUTTON, self.on_erase_button) + + self.eraseWarning= wx.StaticText(self.mainPanel,label = "WARNING: Erasing is not mandatory to flash a new app, but if you do, you must reflash ALL 3 files.", style = wx.ALIGN_LEFT) + + vbox.Add(self.eraseButton,1, wx.LEFT|wx.RIGHT|wx.EXPAND, 20) + vbox.Add(self.eraseWarning,0.1,wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.EXPAND, 20 ) + ################################################################ + # BEGIN APP DFU FILE GUI # + ################################################################ + self.appDFUpanel = wx.Panel(self.mainPanel) + self.appDFUpanel.SetBackgroundColour('white') + hbox = wx.BoxSizer(wx.HORIZONTAL) + + self.appDFUCheckbox = wx.CheckBox(parent=self.appDFUpanel,label="Flash App at 0x10000 ") + self.appDFUCheckbox.Bind(wx.EVT_CHECKBOX,self.on_appFlash_check) + self.appDFUCheckbox.SetValue(True) + self.appDFUCheckbox.Disable() + hbox.Add(self.appDFUCheckbox,1,wx.ALL|wx.ALIGN_CENTER_VERTICAL,10) + + self.app_pathtext = wx.StaticText(self.appDFUpanel,label = "No File Selected", style = wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) + hbox.Add(self.app_pathtext,5,wx.ALL|wx.ALIGN_CENTER_VERTICAL,10) + + self.browseButton = wx.Button(parent=self.appDFUpanel, label='Browse...') + self.browseButton.Bind(wx.EVT_BUTTON, self.on_app_browse_button) + hbox.Add(self.browseButton, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 10) + + vbox.Add(self.appDFUpanel,1,wx.LEFT|wx.RIGHT|wx.EXPAND, 20) + ################################################################ + # BEGIN PARTITIONS DFU FILE GUI # + ################################################################ + self.partitionDFUpanel = wx.Panel(self.mainPanel) + self.partitionDFUpanel.SetBackgroundColour('white') + partitionhbox = wx.BoxSizer(wx.HORIZONTAL) + + self.partitionDFUCheckbox = wx.CheckBox(parent=self.partitionDFUpanel,label="Flash Partition Table at 0x8000") + self.partitionDFUCheckbox.Bind(wx.EVT_CHECKBOX,self.on_partitionFlash_check) + partitionhbox.Add(self.partitionDFUCheckbox,1,wx.ALL|wx.ALIGN_CENTER_VERTICAL,10) + + self.partition_pathtext = wx.StaticText(self.partitionDFUpanel,label = "No File Selected", style = wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) + partitionhbox.Add(self.partition_pathtext,5,wx.ALL|wx.ALIGN_CENTER_VERTICAL,10) + + self.browseButton = wx.Button(parent=self.partitionDFUpanel, label='Browse...') + self.browseButton.Bind(wx.EVT_BUTTON, self.on_partition_browse_button) + partitionhbox.Add(self.browseButton, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 10) + + vbox.Add(self.partitionDFUpanel,1,wx.LEFT|wx.RIGHT|wx.EXPAND, 20) + ################################################################ + # BEGIN BOOTLOADER DFU FILE GUI # + ################################################################ + self.bootloaderDFUpanel = wx.Panel(self.mainPanel) + self.bootloaderDFUpanel.SetBackgroundColour('white') + bootloaderhbox = wx.BoxSizer(wx.HORIZONTAL) + + self.bootloaderDFUCheckbox = wx.CheckBox(parent=self.bootloaderDFUpanel,label="Flash Bootloader at 0x1000 ") + self.bootloaderDFUCheckbox.Bind(wx.EVT_CHECKBOX,self.on_bootloaderFlash_check) + bootloaderhbox.Add(self.bootloaderDFUCheckbox,1,wx.ALL|wx.ALIGN_CENTER_VERTICAL,10) + + self.bootloader_pathtext = wx.StaticText(self.bootloaderDFUpanel,label = "No File Selected", style = wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) + bootloaderhbox.Add(self.bootloader_pathtext,5,wx.ALL|wx.ALIGN_CENTER_VERTICAL,10) + + self.browseButton = wx.Button(parent=self.bootloaderDFUpanel, label='Browse...') + self.browseButton.Bind(wx.EVT_BUTTON, self.on_bootloader_browse_button) + bootloaderhbox.Add(self.browseButton, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 10) + + vbox.Add(self.bootloaderDFUpanel,1,wx.LEFT|wx.RIGHT|wx.EXPAND, 20) + ################################################################ + # BEGIN FLASH BUTTON GUI # + ################################################################ + self.flashButton = wx.Button(parent=self.mainPanel, label='Flash') + self.flashButton.Bind(wx.EVT_BUTTON, self.on_flash_button) + + vbox.Add(self.flashButton,1, wx.LEFT|wx.RIGHT|wx.EXPAND, 20) + ################################################################ + # BEGIN CONSOLE OUTPUT GUI # + ################################################################ + self.consolePanel = wx.TextCtrl(self.mainPanel, style=wx.TE_MULTILINE|wx.TE_READONLY) + sys.stdout = RedirectText(self.consolePanel) + + vbox.Add(self.consolePanel,5, wx.ALL|wx.EXPAND, 20) + ################################################################ + # ASSOCIATE PANELS TO SIZERS # + ################################################################ + self.appDFUpanel.SetSizer(hbox) + self.partitionDFUpanel.SetSizer(partitionhbox) + self.bootloaderDFUpanel.SetSizer(bootloaderhbox) + self.serialPanel.SetSizer(serialhbox) + self.baudPanel.SetSizer(baudhbox) + self.mainPanel.SetSizer(vbox) + + def initFlags(self): + '''Initialises the flags used to control the program flow''' + self.ESPTOOL_BUSY = False + + self.ESPTOOLARG_AUTOSERIAL = False + self.ESPTOOLARG_SERIALPORT = self.serialChoice.GetString(self.serialChoice.GetSelection()) + self.ESPTOOLARG_BAUD = self.ESPTOOLARG_BAUD # this default is regrettably loaded as part of the initUI process + self.ESPTOOLARG_APPPATH = None + self.ESPTOOLARG_PARTITIONPATH = None + self.ESPTOOLARG_BOOTLOADERPATH = None + self.ESPTOOLARG_APPFLASH = True + self.ESPTOOLARG_PARTITIONFLASH = False + self.ESPTOOLARG_BOOTLOADERFLASH = False + + self.APPFILE_SELECTED = False + self.PARTITIONFILE_SELECTED = False + self.BOOTLOADERFILE_SELECTED = False + + self.ESPTOOLMODE_ERASE = False + self.ESPTOOLMODE_FLASH = False + + self.ESPTOOL_ERASE_USED = False + + ################################################################ + # UI EVENT HANDLERS # + ################################################################ + def on_serial_scan_request(self, event): + # disallow if automatic serial port is chosen + if self.ESPTOOLARG_AUTOSERIAL: + print('disable automatic mode first') + return + + # repopulate the serial port choices and update the selected port + print('rescanning serial ports...') + devices = self.list_serial_devices() + self.serialChoice.Clear() + for device in devices: + self.serialChoice.Append(device) + self.ESPTOOLARG_SERIALPORT = self.serialChoice.GetString(self.serialChoice.GetSelection()) + print('serial choices updated') + + def on_serial_list_select(self,event): + port = self.serialChoice.GetString(self.serialChoice.GetSelection()) + self.ESPTOOLARG_SERIALPORT = self.serialChoice.GetString(self.serialChoice.GetSelection()) + print('you chose '+port) + + def on_serial_autodetect_check(self,event): + self.ESPTOOLARG_AUTOSERIAL = self.serialAutoCheckbox.GetValue() + + if self.ESPTOOLARG_AUTOSERIAL: + self.serialChoice.Clear() + self.serialChoice.Append('Automatic') + else: + self.on_serial_scan_request(event) + + def on_baud_selected(self,event): + selection = event.GetEventObject() + self.ESPTOOLARG_BAUD = selection.baudrate + print('baud set to '+selection.baudrate) + + def on_erase_button(self, event): + if self.ESPTOOL_BUSY: + print('currently busy') + return + self.ESPTOOLMODE_ERASE = True + self.ESPTOOL_ERASE_USED = True + t = threading.Thread(target=self.esptoolRunner, daemon=True) + t.start() + + def on_appFlash_check(self, event): + self.ESPTOOLARG_APPFLASH = self.appDFUCheckbox.GetValue() + + def on_partitionFlash_check(self, event): + self.ESPTOOLARG_PARTITIONFLASH = self.partitionDFUCheckbox.GetValue() + + def on_bootloaderFlash_check(self, event): + self.ESPTOOLARG_BOOTLOADERFLASH = self.bootloaderDFUCheckbox.GetValue() + + def on_app_browse_button(self, event): + with wx.FileDialog(self, "Open", "", "","*.bin", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog: + + if fileDialog.ShowModal() == wx.ID_CANCEL: + return + + path = fileDialog.GetPath() + self.APPFILE_SELECTED = True + + self.app_pathtext.SetLabel(os.path.abspath(path)) + self.ESPTOOLARG_APPPATH=os.path.abspath(path) + + def on_partition_browse_button(self, event): + with wx.FileDialog(self, "Open", "", "","*.bin", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog: + + if fileDialog.ShowModal() == wx.ID_CANCEL: + return + + path = fileDialog.GetPath() + self.PARTITIONFILE_SELECTED = True + + self.partition_pathtext.SetLabel(os.path.abspath(path)) + self.ESPTOOLARG_PARTITIONPATH=os.path.abspath(path) + + def on_bootloader_browse_button(self, event): + with wx.FileDialog(self, "Open", "", "","*.bin", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog: + + if fileDialog.ShowModal() == wx.ID_CANCEL: + return + + path = fileDialog.GetPath() + self.BOOTLOADERFILE_SELECTED = True + + self.bootloader_pathtext.SetLabel(os.path.abspath(path)) + self.ESPTOOLARG_BOOTLOADERPATH=os.path.abspath(path) + + def on_flash_button(self, event): + if self.ESPTOOL_BUSY: + print('currently busy') + return + # handle cases where a flash has been requested but no file provided + elif self.ESPTOOLARG_APPFLASH & ~self.APPFILE_SELECTED: + print('no app selected for flash') + return + elif self.ESPTOOLARG_PARTITIONFLASH & ~self.PARTITIONFILE_SELECTED: + print('no partition table selected for flash') + return + elif self.ESPTOOLARG_BOOTLOADERFLASH & ~self.BOOTLOADERFILE_SELECTED: + print('no bootloader selected for flash') + return + else: + # if the erase_flash has been used but we have not elected to upload all the required files + if self.ESPTOOL_ERASE_USED & (~self.ESPTOOLARG_APPFLASH | ~self.ESPTOOLARG_PARTITIONFLASH | ~self.ESPTOOLARG_BOOTLOADERFLASH): + dialog = wx.MessageDialog(self.mainPanel, 'DoayeeESP32DFU detected use of \"Erase ESP\", which means you should reflash all files. Are you sure you want to continue? ','Warning',wx.YES_NO|wx.ICON_EXCLAMATION) + ret = dialog.ShowModal() + + if ret == wx.ID_NO: + return + + # if we're uploading everything, clear the fact that erase_flash has been used + if self.ESPTOOLARG_APPFLASH & self.ESPTOOLARG_PARTITIONFLASH & self.ESPTOOLARG_BOOTLOADERFLASH: + self.ESPTOOL_ERASE_USED = False + + self.ESPTOOLMODE_FLASH = True + t = threading.Thread(target=self.esptoolRunner, daemon=True) + t.start() + + ################################################################ + # MISC FUNCTIONS # + ################################################################ + def list_serial_devices(self): + ports = serial.tools.list_ports.comports() + ports.sort() + devices = [] + for port in ports: + devices.append(port.device) + return devices + + ################################################################ + # ESPTOOL FUNCTIONS # + ################################################################ + def esptool_cmd_builder(self): + '''Build the command that we would give esptool on the CLI''' + cmd = ['--baud',self.ESPTOOLARG_BAUD] + + if self.ESPTOOLARG_AUTOSERIAL == False: + cmd = cmd + ['--port',self.ESPTOOLARG_SERIALPORT] + + if self.ESPTOOLMODE_ERASE: + cmd.append('erase_flash') + elif self.ESPTOOLMODE_FLASH: + cmd.append('write_flash') + if self.ESPTOOLARG_BOOTLOADERFLASH: + cmd.append('0x1000') + cmd.append(self.ESPTOOLARG_BOOTLOADERPATH) + if self.ESPTOOLARG_APPFLASH: + cmd.append('0x10000') + cmd.append(self.ESPTOOLARG_APPPATH) + if self.ESPTOOLARG_PARTITIONFLASH: + cmd.append('0x8000') + cmd.append(self.ESPTOOLARG_PARTITIONPATH) + + return cmd + + def esptoolRunner(self): + '''Handles the interaction with esptool''' + self.ESPTOOL_BUSY = True + + cmd = self.esptool_cmd_builder() + try: + esptool.main(cmd) + print('esptool execution completed') + except esptool.FatalError as e: + print(e) + pass + except serial.SerialException as e: + print(e) + pass + except: + print('unexpected error, maybe you chose invalid files, or files which overlap') + pass + + self.ESPTOOL_BUSY = False + self.ESPTOOLMODE_ERASE = False + self.ESPTOOLMODE_FLASH = False + + +def main(): + + app = wx.App() + window = dfuTool(None, title='Doayee ESP32 DFU Tool') + window.Show() + + app.MainLoop() + +if __name__ == '__main__': + main() diff --git a/doayee_dfu.spec b/doayee_dfu.spec new file mode 100644 index 0000000..dd4678f --- /dev/null +++ b/doayee_dfu.spec @@ -0,0 +1,39 @@ +# -*- mode: python -*- + +block_cipher = None + + +a = Analysis(['doayee_dfu.py'], + pathex=['/Users/Tom/Documents/GitRepos/Doayee-dev/esp32bta/dfu'], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='DoayeeESP32DFU', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + runtime_tmpdir=None, + console=False , icon='logo.ico') +app = BUNDLE(exe, + name='DoayeeESP32DFU.app', + icon='logo.png.icns', + bundle_identifier='com.doayee.esp32dfu', + info_plist={ + 'NSHighResolutionCapable': 'True' + },) diff --git a/doayee_logo.py b/doayee_logo.py new file mode 100644 index 0000000..443c380 --- /dev/null +++ b/doayee_logo.py @@ -0,0 +1,2496 @@ +#---------------------------------------------------------------------- +# This file was generated by C:\Users\Tom\AppData\Local\Programs\Python\Python37-32\Scripts\img2py +# +from wx.lib.embeddedimage import PyEmbeddedImage + +logo = PyEmbeddedImage( + b'iVBORw0KGgoAAAANSUhEUgAAEEcAABBHCAYAAACcmAIaAAAACXBIWXMAAC4jAAAuIwF4pT92' + b'AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAACDGNJREFUeNrs3c9tI0ma' + b'xuGowhigtaDHk5mD/JgyYQFh77ovAqAHzfKAugcwKQ+SFkzSgg1aoA12UqX6p25JRUmR/J4H' + b'+JAJSrq8Bx5/+nB3d5cAAAAAAAAAAAAAAAAAAAAAevXRBAAAAAAAAAAAAAAAAAAAAEDPxBEA' + b'AAAAAAAAAAAAAAAAAACArokjAAAAAAAAAAAAAAAAAAAAAF0TRwAAAAAAAAAAAAAAAAAAAAC6' + b'Jo4AAAAAAAAAAAAAAAAAAAAAdE0cAQAAAAAAAAAAAAAAAAAAAOiaOAIAAAAAAAAAAAAAAAAA' + b'AADQNXEEAAAAAAAAAAAAAAAAAAAAoGviCAAAAAAAAAAAAAAAAAAAAEDXxBEAAAAAAAAAAAAA' + b'AAAAAACArokjAAAAAAAAAAAAAAAAAAAAAF0TRwAAAAAAAAAAAAAAAAAAAAC6Jo4AAAAAAAAA' + b'AAAAAAAAAAAAdE0cAQAAAAAAAAAAAAAAAAAAAOiaOAIAAAAAAAAAAAAAAAAAAADQNXEEAAAA' + b'AAAAAAAAAAAAAAAAoGviCAAAAAAAAAAAAAAAAAAAAEDXxBEAAAAAAAAAAAAAAAAAAACArokj' + b'AAAAAAAAAAAAAAAAAAAAAF0TRwAAAAAAAAAAAAAAAAAAAAC6Jo4AAAAAAAAAAAAAAAAAAAAA' + b'dE0cAQAAAAAAAAAAAAAAAAAAAOiaOAIAAAAAAAAAAAAAAAAAAADQNXEEAAAAAAAAAAAAAAAA' + b'AAAAoGviCAAAAAAAAAAAAAAAAAAAAEDXxBEAAAAAAAAAAAAAAAAAAACArokjAAAAAAAAAAAA' + b'AAAAAAAAAF0TRwAAAAAAAAAAAAAAAAAAAAC6Jo4AAAAAAAAAAAAAAAAAAAAAdE0cAQAAAAAA' + b'AAAAAAAAAAAAAOiaOAIAAAAAAAAAAAAAAAAAAADQNXEEAAAAAAAAAAAAAAAAAAAAoGviCAAA' + b'AAAAAAAAAAAAAAAAAEDXxBEAAAAAAAAAAAAAAAAAAACArokjAAAAAAAAAAAAAAAAAAAAAF0T' + b'RwAAAAAAAAAAAAAAAAAAAAC6Jo4AAAAAAAAAAAAAAAAAAAAAdE0cAQAAAAAAAAAAAAAAAAAA' + b'AOiaOAIAAAAAAAAAAAAAAAAAAADQNXEEAAAAAAAAAAAAAAAAAAAAoGviCAAAAAAAAAAAAAAA' + b'AAAAAEDXxBEAAAAAAAAAAAAAAAAAAACArokjAAAAAAAAAAAAAAAAAAAAAF0TRwAAAAAAAAAA' + b'AAAAAAAAAAC6Jo4AAAAAAAAAAAAAAAAAAAAAdE0cAQAAAAAAAAAAAAAAAAAAAOiaOAIAAAAA' + b'AAAAAAAAAAAAAADQNXEEAAAAAAAAAAAAAAAAAAAAoGviCAAAAAAAAAAAAAAAAAAAAEDXxBEA' + b'AAAAAAAAAAAAAAAAAACArokjAAAAAAAAAAAAAAAAAAAAAF0TRwAAAAAAAAAAAAAAAAAAAAC6' + b'Jo4AAAAAAAAAAAAAAAAAAAAAdE0cAQAAAAAAAAAAAAAAAAAAAOiaOAIAAAAAAAAAAAAAAAAA' + b'AADQNXEEAAAAAAAAAAAAAAAAAAAAoGviCAAAAAAAAAAAAAAAAAAAAEDXxBEAAAAAAAAAAAAA' + b'AAAAAACArokjAAAAAAAAAAAAAAAAAAAAAF0TRwAAAAAAAAAAAAAAAAAAAAC6Jo4AAAAAAAAA' + b'AAAAAAAAAAAAdE0cAQAAAAAAAAAAAAAAAAAAAOiaOAIAAAAAAAAAAAAAAAAAAADQNXEEAAAA' + b'AAAAAAAAAAAAAAAAoGviCAAAAAAAAAAAAAAAAAAAAEDXxBEAAAAAAAAAAAAAAAAAAACArokj' + b'AAAAAAAAAAAAAAAAAAAAAF0TRwAAAAAAAAAAAAAAAAAAAAC6Jo4AAAAAAAAAAAAAAAAAAAAA' + b'dE0cAQAAAAAAAAAAAAAAAAAAAOiaOAIAAAAAAAAAAAAAAAAAAADQNXEEAAAAAAAAAAAAAAAA' + b'AAAAoGviCAAAAAAAAAAAAAAAAAAAAEDXxBEAAAAAAAAAAAAAAAAAAACArokjAAAAAAAAAAAA' + b'AAAAAAAAAF0TRwAAAAAAAAAAAAAAAAAAAAC6Jo4AAAAAAAAAAAAAAAAAAAAAdE0cAQAAAAAA' + b'AAAAAAAAAAAAAOiaOAIAAAAAAAAAAAAAAAAAAADQNXEEAAAAAAAAAAAAAAAAAAAAoGviCAAA' + b'AAAAAAAAAAAAAAAAAEDXxBEAAAAAAAAAAAAAAAAAAACArokjAAAAAAAAAAAAAAAAAAAAAF37' + b'mwmAiC4urwYrAAAAAMAiDb/wt7Xd+ORfLnkwNwAAAAAAAAAAAPThw93dnRWAcC4ur3z5AQAA' + b'AACnsE1zdOEx45/8fDreD4QZAAAAAAAAAAAA4FviCEBI4ggAAAAAwEI9FmMYfvLZz8IMYy25' + b'mhEAAAAAAAAAAIClEUcAQhJHAAAAAAD44vvgwuF9/O53vg8tTLXkyXQAAAAAAAAAAAC8FXEE' + b'ICRxBAAAAACAk9qnb4MK3wcWpuPdG2vJ1WwAAAAAAAAAAAA8lTgCEJI4AgAAAABAV26/ep/S' + b'tyGF4at3UQUAAAAAAAAAAICgxBGAkMQRAAAAAAAWb5ceIgqHYML41c+G+5da8mAqAAAAAAAA' + b'AACA5RNHAEISRwAAAAAACOn2q/fhZ+9iCgAAAAAAAAAAAH0SRwBCEkcAAAAAAOAv7NuNx/fp' + b'eOn4WT1cLXk0EwAAAAAAAAAAwNsQRwBCEkcAAAAAAOCEdukhnjAcn4eAwn08YawlVzMBAAAA' + b'AAAAAAC8nDgCEJI4AgAAAAAA72CfvgompDmgMB0v1ZIHEwEAAAAAAAAAAPycOAIQkjgCAAAA' + b'AAAd26Y5nHC4+5jCcHyOteRqIgAAAAAAAAAAIBpxBCAkcQQAAAAAABZu125KPwko1JIH8wAA' + b'AAAAAAAAAOdGHAEISRwBAAAAAIAAtmmOJ0zfXy15Mg8AAAAAAAAAALAk4ghASOIIAAAAAACQ' + b'9u3GNAcUvnnWkkfzAAAAAAAAAAAAPRFHAEISRwAAAAAAgL/0WDxhqiVP5gEAAAAAAAAAAN6S' + b'OAIQkjgCAAAAAAD8sl276av7ElCoJVfzAAAAAAAAAAAApySOAIQkjgAAAAAAAK/uNh1jCV89' + b'p1ryZBoAAAAAAAAAAOC5xBGAkMQRAAAAAADgXW3THEwYknACAAAAAAAAAADwBOIIQEjiCAAA' + b'AAAA0C3hBAAAAAAAAAAA4AfiCEBI4ggAAAAAALBIh3DClI7BhPv3WnI1DQAAAAAAAAAAnDdx' + b'BCAkcQQAAAAAADgr+/RtMGFoV2vJo2kAAAAAAAAAAOA8iCMAIYkjAAAAAABAGNs0BxO+xBNq' + b'yYNZAAAAAAAAAABgWcQRgJDEEQAAAAAAILxdmmMJQ7ua5njCWEuupgEAAAAAAAAAgP6IIwAh' + b'iSMAAAAAAACP2KdjKCHN8QTRBAAAAAAAAAAA6IA4AhCSOAIAAAAAAPBMogkAAAAAAAAAAPCO' + b'xBGAkMQRAAAAAACAE9mlOZYwHJ9TLXkwCwAAAAAAAAAAnJY4AhCSOAIAAAAAAPDKtmmOJYz3' + b'V0uezAIAAAAAAAAAAC8jjgCEJI4AAAAAAAC8k9s0xxKmNAcTBpMAAAAAAAAAAMBfE0cAQhJH' + b'AAAAAAAAOrJLczDhy9WSJ7MAAAAAAAAAAMADcQQgJHEEAAAAAACgc/v0YzBhNAsAAAAAAAAA' + b'AFGJIwAhiSMAAAAAAAALtU3fBhMGkwAAAAAAAAAAEIE4AhCSOAIAAAAAAHBGDsGEKc3BhCHN' + b'0YRqFgAAAAAAAAAAzok4AhCSOAIAAAAAAHDmdmmOJQgmAAAAAAAAAABwFsQRgJDEEQAAAAAA' + b'gIAEEwAAAAAAAAAAWCxxBCAkcQQAAAAAAIA/3AcThjTHEgaTAAAAAAAAAADQI3EEICRxBAAA' + b'AAAAgEdt0xxM+COaUEseTQIAAAAAAAAAwHsTRwBCEkcAAAAAAAB4ltv0bTBhMgkAAAAAAAAA' + b'AG9JHAEISRwBAAAAAADgl+zTMZRwvLGWXM0CAAAAAAAAAMBrEUcAQhJHAAAAAAAAOLltmoMJ' + b'f0QTasmjSQAAAAAAAAAAOBVxBCAkcQQAAAAAAIA3cdtuSA/BhGoSAAAAAAAAAABeQhwBCEkc' + b'AQAAAAAA4F3s0hxLONxYSx5NAgAAAAAAAADAU4gjACGJIwAAAAAAAHRh3+4QSBgOV0seTAIA' + b'AAAAAAAAwM+IIwAhiSMAAAAAAAB0a5uOsYR2Yy15MgkAAAAAAAAAAOIIQEjiCAAAAAAAAIux' + b'S9/GEkaTAAAAAAAAAADEI44AhCSOAAAAAAAAsFj79BBLGMQSAAAAAAAAAABiEEcAQhJHAAAA' + b'AAAAOCu36SGWMJgDAAAAAAAAAOD8iCMAIYkjAAAAAAAAnDWxBAAAAAAAAACAMyOOAIQkjgAA' + b'AAAAABDKfSxhTHMwoZoEAAAAAAAAAGBZxBGAkMQRAAAAAAAAQtumOZZwCCVszAEAAAAAAAAA' + b'0D9xBCAkcQQAAAAAAAC+cpseYgmDOQAAAAAAAAAA+iOOAIQkjgAAAAAAAMCfEEsAAAAAAAAA' + b'AOiMOAIQkjgCAAAAAAAAz3CIJWzSHEsYzQEAAAAAAAAA8PbEEYCQxBEAAAAAAAB4oX274Xib' + b'WvJkEgAAAAAAAACA1yeOAIQkjgAAAAAAAMCJ7NJDLGEQSwAAAAAAAAAAeB3iCEBI4ggAAAAA' + b'AAC8km16CCVszAEAAAAAAAAAcBriCEBI4ggAAAAAAAC8kdt2h0jCIZYwmgMAAAAAAAAA4GXE' + b'EYCQxBEAAAAAAAB4B/t0DCWkOZYwmQQAAAAAAAAA4GnEEYCQxBEAAAAAAADowDY9hBI25gAA' + b'AAAAAAAAeJw4AhCSOAIAAAAAAAAduklzLGFTS57MAQAAAAAAAADwQBwBCEkcAQAAAAAAgM7t' + b'2m3aDbXkjTkAAAAAAAAAgOjEEYCQxBEAAAAAAABYmJt2Q7tNLXkyBwAAAAAAAAAQjTgCEJI4' + b'AgAAAAAAAAu2a7dpN9SSN+YAAAAAAAAAACIQRwBCEkcAAAAAAADgjNy0G9ptasmTOQAAAAAA' + b'AACAcySOAIQkjgAAAAAAAMCZ2qaHUMJgDgAAAAAAAADgXIgjACGJIwAAAAAAABDAvt0mPcQS' + b'qkkAAAAAAAAAgKUSRwBCEkcAAAAAAAAgoNs0xxIOoYTJHAAAAAAAAADAkogjACGJIwAAAAAA' + b'ABDcLs2hhHUteTQHAAAAAAAAANA7cQQgJHEEAAAAAAAA+GKf5lDC4YZacjUJAAAAAAAAANAb' + b'cQQgJHEEAAAAAAAAeNRNOsYShBIAAAAAAAAAgF6IIwAhiSMAAAAAAADAk2zbrdMcSpjMAQAA' + b'AAAAAAC8F3EEICRxBAAAAAAAAHi2QyhhaLeuJY/mAAAAAAAAAADekjgCEJI4AgAAAAAAAPyS' + b'XbvN4WrJgzkAAAAAAAAAgNcmjgCEJI4AAAAAAAAAJ7NPD6GEjTkAAAAAAAAAgNcgjgCEJI4A' + b'AAAAAAAAr0IoAQAAAAAAAAB4FeIIQEjiCAAAAAAAAPAmbtJDLKGaAwAAAAAAAAB4KXEEICRx' + b'BAAAAAAAAHhzQgkAAAAAAAAAwIuJIwAhiSMAAAAAAADAuxJKAAAAAAAAAACeRRwBCEkcAQAA' + b'AAAAALohlAAAAAAAAAAA/CVxBCAkcQQAAAAAAADoklACAAAAAAAAAPBT4ghASOIIAAAAAAAA' + b'0D2hBAAAAAAAAADgC3EEICRxBAAAAAAAAFiUQyhhXUvemAIAAAAAAAAAYhJHAEISRwAAAAAA' + b'AIBF2rc7BBI2QgkAAAAAAAAAEIs4AhCSOAIAAAAAAAAs3n0oYV1LHswBAAAAAAAAAOdNHAEI' + b'SRwBAAAAAAAAzsouPYQSRnMAAAAAAAAAwPkRRwBCEkcAAAAAAACAs3UIJazTHEqYzAEAAAAA' + b'AAAA50EcAQhJHAEAAAAAAABC2KaHUEI1BwAAAAAAAAAslzgCEJI4AgAAAAAAAIRz025zOKEE' + b'AAAAAAAAAFgecQQgJHEEAAAAAAAACGufHiIJG3MAAAAAAAAAwDKIIwAhiSMAAAAAAAAAzS7N' + b'oYR1LXk0BwAAAAAAAAD0SxwBCEkcAQAAAAAAAPjOtt263aaWPJkDAAAAAAAAAPoijgCEJI4A' + b'AAAAAAAA/ImbNEcS1qYAAAAAAAAAgD6IIwAhiSMAAAAAAAAAT7Bvt2m3riUP5gAAAAAAAACA' + b'9yOOAIQkjgAAAAAAAAA8067dOs2hhMkcAAAAAAAAAPC2xBGAkMQRAAAAAAAAgF9wm+ZQwqaW' + b'XM0BAAAAAAAAAK9PHAEISRwBAAAAAAAAOIF9u027VS15NAcAAAAAAAAAvB5xBCAkcQQAAAAA' + b'AADgxHbtVu02teTJHAAAAAAAAABwWuIIQEjiCAAAAAAAAMArumm3riVvTAEAAAAAAAAApyGO' + b'AIQkjgAAAAAAAAC8gX27dbtVLXkyBwAAAAAAAAC8nDgCEJI4AgAAAAAAAPDGbtMcStjUkqs5' + b'AAAAAAAAAOB5xBGAkMQRAAAAAAAAgHeyb7dpt6olj+YAAAAAAAAAgKcRRwBCEkcAAAAAAAAA' + b'OrBtt2q3qSVXcwAAAAAAAADA48QRgJDEEQAAAAAAAICO7Ntt2q1qyaM5AAAAAAAAAOBH4ghA' + b'SOIIAAAAAAAAQKe27VbtNrXkag4AAAAAAAAAmIkjACGJIwAAAAAAAACd27fbtFvVkkdzAAAA' + b'AAAAABCdOAIQkjgCAAAAAAAAsCDbdqt2m1pyNQcAAAAAAAAAEYkjACGJIwAAAAAAAAALtG+3' + b'abeqJY/mAAAAAAAAACAScQQgJHEEAAAAAAAAYOFu261ryWtTAAAAAAAAABCBOAIQkjgCAAAA' + b'AAAAcCb27dbtVrXkyRwAAAAAAAAAnCtxBCAkcQQAAAAAAADgDN20W9eSN6YAAAAAAAAA4NyI' + b'IwAhiSMAAAAAAAAAZ2zXbpXmUEI1BwAAAAAAAADnQBwBCEkcAQAAAAAAAAjic7tVLXk0BQAA' + b'AAAAAABLJo4AhCSOAAAAAAAAAARz225dS16bAgAAAAAAAIAlEkcAQhJHAAAAAAAAAILat1ul' + b'OZQwmQMAAAAAAACApRBHAEISRwAAAAAAAABIn9McSRhMAQAAAAAAAEDvxBGAkMQRAAAAAAAA' + b'AL7YtlvVktemAAAAAAAAAKBX4ghASOIIAAAAAAAAAD/Yt1ulOZRQzQEAAAAAAABAT8QRgJDE' + b'EQAAAAAAAAD+1Oc0RxJGUwAAAAAAAADQA3EEICRxBAAAAAAAAIAnuW23riWvTQEAAAAAAADA' + b'exJHAEISRwAAAAAAAAB4ll27VZpDCdUcAAAAAAAAALw1cQQgJHEEAAAAAAAAgBfZt1u3W9WS' + b'J3MAAAAAAAAA8FbEEYCQxBEAAAAAAAAAftnnduta8mAKAAAAAAAAAF6bOAIQkjgCAAAAAAAA' + b'wMncpjmSsDYFAAAAAAAAAK9FHAEISRwBAAAAAAAA4OR27a7bbWrJ1RwAAAAAAAAAnJI4AhCS' + b'OAIAAAAAAADAq9m3Wx1OJAEAAAAAAACAUxFHAEISRwAAAAAAAAB4E5/bXdeSJ1MAAAAAAAAA' + b'8CvEEYCQxBEAAAAAAAAA3tRNu1UteTAFAAAAAAAAAC8hjgCEJI4AAAAAAAAA8C5u0xxJ2JgC' + b'AAAAAAAAgOcQRwBCEkcAAAAAAAAAeFe7dte15LUpAAAAAAAAAHgKcQQgJHEEAAAAAAAAgC4c' + b'IgnrdqtacjUHAAAAAAAAAI8RRwBCEkcAAAAAAAAA6Mq+3SqJJAAAAAAAAADwCHEEICRxBAAA' + b'AAAAAIBufW53XUueTAEAAAAAAADAvY8mAAAAAAAAAACgI/9q95+Ly6t1u7+bAwAAAAAAAICD' + b'D3d3/nk6EM/F5ZUvPwAAAAAAAIBluGm3qiUPpgAAAAAAAACISxwBCEkcAQAAAAAAAGBxbttd' + b'iyQAAAAAAAAAxPTRBAAAAAAAAAAALMA/2v374vJqaPdPcwAAAAAAAADEIo4AAAAAAAAAAMCS' + b'3EcSpnafzAEAAAAAAAAQgzgCAAAAAAAAAABL9Fu730USAAAAAAAAAGIQRwAAAAAAAAAAYMlE' + b'EgAAAAAAAAACEEcAAAAAAAAAAOAciCQAAAAAAAAAnDFxBAAAAAAAAAAAzolIAgAAAAAAAMAZ' + b'EkcAAAAAAAAAAOAciSQAAAAAAAAAnJEPd3d3VgDCubi88uUHAAAAAAAAEMuu3XW7TS25mgMA' + b'AAAAAABgWT6aAAAAAAAAAACAAH5r93u76eLy6tocAAAAAAAAAMvy4e7OP08H4rm4vPLlBwAA' + b'AAAAABDbrt11LXltCgAAAAAAAID+fTQBAAAAAAAAAAAB/dbu94vLq6ndJ3MAAAAAAAAA9E0c' + b'AQAAAAAAAACAyEQSAAAAAAAAABbgw93dnRWAcC4ur3z5AQAAAAAAAPAzu3bXteS1KQAAAACA' + b'7/3X5f98ao9PluAdjf9X/ve/zQBARH8zAQAAAAAAAAAAfPFbu98vLq+uk0gCAAAAAPCjv7f7' + b'hxkAAN7eRxMAAAAAAAAAAMAP7iMJU7tP5gAAAAAAAAB4X+IIAAAAAAAAAADwuPtIwtjun+YA' + b'AAAAAAAAeB/iCP/Pzr3cNpZdYRiVDc/vzkDKgMyAGpx5KQMyAxVwAiATMEoZkBlIARjQdQRi' + b'Bs0Q7Ajam60utKu7VKUHH/exFvAnsAdncAYfAAAAAAAAAAD83CT3GKW2IgkAAAAAAAAApyeO' + b'AAAAAAAAAAAArze7EEkAAAAAAAAAODlxBAAAAAAAAAAAeLuvkYSH3JVzAAAAAAAAAByXOAIA' + b'AAAAAAAAALzfp9wvUepGJAEAAAAAAADgeMQRAAAAAAAAAADg4+YXf0QSwjkAAAAAAAAADksc' + b'AQAAAAAAAAAADmcfSdhFqSuRBAAAAAAAAIDDEUcAAAAAAAAAAIDDanLLi98jCc4BAAAAAAAA' + b'8HHiCAAAAAAAAAAAcBy/RRKi1H0kYeEcAAAAAAAAAO8njgAAAAAAAAAAAMd1mVv/Hkm4dg4A' + b'AAAAAACAtxNHAAAAAAAAAACA09hHEh6j1FYkAQAAAAAAAOBtxBEAAAAAAAAAAOC0ZhfPkYRN' + b'7so5AAAAAAAAAH5OHAEAAAAAAAAAAM5jnvslSr3LhXMAAAAAAAAAvEwcAQAAAAAAAAAAzus2' + b't4tSVyIJAAAAAAAAAN8njgAAAAAAAAAAAOfX5Ja5bZS6cA4AAAAAAACAb4kjAAAAAAAAAABA' + b'd1zm1lHqPpJw7RwAAAAAAAAAz8QRAAAAAAAAAACgeya5xyi1zV05BwAAAAAAADB24ggAAAAA' + b'AAAAANBds9wvUeomF84BAAAAAAAAjJU4AgAAAAAAAAAAdN88t4tSV04BAAAAAAAAjJE4AgAA' + b'AAAAAAAA9EOTW0ap+0jCwjkAAAAAAACAMRFHAAAAAAAAAACAfrnMraPUNnftHAAAAAAAAMAY' + b'iCMAAAAAAAAAAEA/zXKPUeomd+UcAAAAAAAAwJCJIwAAAAAAAAAAQL/Nc9sodZUL5wAAAAAA' + b'AACGSBwBAAAAAAAAAAD6r8ktL54jCQvnAAAAAAAAAIZGHAEAAAAAAAAAAIbjMreOUtvctXMA' + b'AAAAAAAAQyGOAAAAAAAAAAAAwzPLPUapm1w4BwAAAAAAANB34ggAAAAAAAAAADBc89wuSl05' + b'BQAAAAAAANBn4ggAAAAAAAAAADBsTW4Zpe4jCdfOAQAAAAAAAPSROAIAAAAAAAAAAIzDZe4x' + b'Sm1zV84BAAAAAAAA9Ik4AgAAAAAAAAAAjMss90uUusqFcwAAAAAAAAB9II4AAAAAAAAAAADj' + b'tMxto9SFUwAAAAAAAABdJ44AAAAAAAAAAADjdZlbR6ltbuocAAAAAAAAQFeJIwAAAAAAAAAA' + b'ALPcU5R6lwvnAAAAAAAAALpGHAEAAAAAAAAAAPjqNreLUhdOAQAAAAAAAHSJOAIAAAAAAAAA' + b'APD/mtw6Sm1zU+cAAAAAAAAAukAcAQAAAAAAAAAA+J5Z7ilKvcuFcwAAAAAAAADnJI4AAAAA' + b'AAAAAAD8yG1uF6UunAIAAAAAAAA4F3EEAAAAAAAAAADgZ5rcOkptc1PnAAAAAAAAAE5NHAEA' + b'AAAAAAAAAHitWe4pSl3lwjkAAAAAAACAUxFHAAAAAAAAAAAA3mqZ20apN04BAAAAAAAAnII4' + b'AgAAAAAAAAAA8B6Xufso9SF35RwAAAAAAADAMYkjAAAAAAAAAAAAH/Ept41SV04BAAAAAAAA' + b'HIs4AgAAAAAAAAAA8FFNbhml7iMJ184BAAAAAAAAHJo4AgAAAAAAAAAAcCiT3GOUepcL5wAA' + b'AAAAAAAORRwBAAAAAAAAAAA4tNvcLkq9cQoAAAAAAADgEMQRAAAAAAAAAACAY2hy91Fqm7ty' + b'DgAAAAAAAOAjxBEAAAAAAAAAAIBjmuW2UerKKQAAAAAAAID3EkcAAAAAAAAAAACOrckto9R9' + b'JGHqHAAAAAAAAMBbiSMAAAAAAAAAAACnMsk9Ral3uXAOAAAAAAAA4LXEEQAAAAAAAAAAgFO7' + b'zW2j1GunAAAAAAAAAF5DHAEAAAAAAAAAADiHy9xjlPqQC+cAAAAAAAAAfkQcAQAAAAAAAAAA' + b'OKdPuV2UunAKAAAAAAAA4CXiCAAAAAAAAAAAwLk1uXWU2uaunAMAAAAAAAD4M3EEAAAAAAAA' + b'AACgK2a5bZT62SkAAAAAAACA/yeOAAAAAAAAAAAAdEmT+xKltrmpcwAAAAAAAAB74ggAAAAA' + b'AAAAAEAXzXJPUerKKQAAAAAAAABxBAAAAAAAAAAAoMuWUeo2N3UKAAAAAAAAGC9xBAAAAAAA' + b'AAAAoOsmuacodeUUAAAAAAAAME7iCAAAAAAAAAAAQF8so9RtbuoUAAAAAAAAMC7iCAAAAAAA' + b'AAAAQJ9Mck9R6sopAAAAAAAAYDzEEQAAAAAAAAAAgD5aRqm73LVTAAAAAAAAwPCJIwAAAAAA' + b'AAAAAH11mXuMUu9y4RwAAAAAAAAwXOIIAAAAAAAAAABA393mtlHq1CkAAAAAAABgmMQRAAAA' + b'AAAAAACAIbjMPUWpK6cAAAAAAACA4RFHAAAAAAAAAAAAhmQZpW5zU6cAAAAAAACA4RBHAAAA' + b'AAAAAAAAhmaSe4pSV04BAAAAAAAAwyCOAAAAAAAAAAAADNUySt3mpk4BAAAAAAAA/SaOAAAA' + b'AAAAAAAADNkk9xSlrpwCAAAAAAAA+kscAQAAAAAAAAAAGINllLrNXTkFAAAAAAAA9I84AgAA' + b'AAAAAAAAMBaT3D6Q8NkpAAAAAAAAoF/EEQAAAAAAAAAAgDFpcl+i1DZ35RwAAAAAAADQD+II' + b'AAAAAAAAAADAGM1y2yj1xikAAAAAAACg+8QRAAAAAAAAAACAsWpy91HqQy6cAwAAAAAAALpL' + b'HAEAAAAAAAAAABi7T7ldlHrtFAAAAAAAANBN4ggAAAAAAAAAAAAXF03uMUq9y4VzAAAAAAAA' + b'QLeIIwAAAAAAAAAAAPzhNtdGqVOnAAAAAAAAgO4QRwAAAAAAAAAAAPjWJPcUpa6cAgAAAAAA' + b'ALpBHAEAAAAAAAAAAOD7llFqm7tyCgAAAAAAADgvcQQAAAAAAAAAAICXzXLbKPXGKQAAAAAA' + b'AOB8xBEAAAAAAAAAAAB+rMndR6mbXDgHAAAAAAAAnJ44AgAAAAAAAAAAwOvMc9sodeoUAAAA' + b'AAAAcFriCAAAAAAAAAAAAK93mXuKUldOAQAAAAAAAKcjjgAAAAAAAAAAAPB2yyi1zV05BQAA' + b'AAAAAByfOAIAAAAAAAAAAMD7zHLbKPXGKQAAAAAAAOC4xBEAAAAAAAAAAADer8ndR6mbXDgH' + b'AAAAAAAAHIc4AgAAAAAAAAAAwMfNc22UOnUKAAAAAAAAODxxBAAAAAAAAAAAgMOY5J6i1M9O' + b'AQAAAAAAAIcljgAAAAAAAAAAAHBYX6LUh1w4BQAAAAAAAByGOAIAAAAAAAAAAMDhfcrtotRr' + b'pwAAAAAAAICPE0cAAAAAAAAAAAA4jib3GKWunAIAAAAAAAA+RhwBAAAAAAAAAADguJZRapsL' + b'pwAAAAAAAID3EUcAAAAAAAAAAAA4vlluF6XeOAUAAAAAAAC8nTgCAAAAAAAAAADAaTS5+yj1' + b'zikAAAAAAADgbcQRAAAAAAAAAAAATus2St3mrpwCAAAAAAAAXkccAQAAAAAAAAAA4PQmuX0g' + b'4cYpAAAAAAAA4OfEEQAAAAAAAAAAAM6jyd1HqXdOAQAAAAAAAD8mjgAAAAAAAAAAAHBet1Hq' + b'NnflFAAAAAAAAPB94ggAAAAAAAAAAADnN8ntAwk3TgEAAAAAAAB/JY4AAAAAAAAAAADQDU3u' + b'Pkq9cwoAAAAAAAD4ljgCAAAAAAAAAABAt9xGqW0unAIAAAAAAACeiSMAAAAAAAAAAAB0zyy3' + b'i1KvnQIAAAAAAADEEQAAAAAAAAAAALqqyT1GqSunAAAAAAAAYOzEEQAAAAAAAAAAALptGaW2' + b'uXAKAAAAAAAAxkocAQAAAAAAAAAAoPtmuW2UOnUKAAAAAAAAxkgcAQAAAAAAAAAAoB8uc09R' + b'6sIpAAAAAAAAGBtxBAAAAAAAAAAAgH5ZR6mbXDgFAAAAAAAAYyGOAAAAAAAAAAAA0D/zXBul' + b'Tp0CAAAAAACAMRBHAAAAAAAAAAAA6KfJxXMg4cYpAAAAAAAAGDpxBAAAAAAAAAAAgP5qcvdR' + b'6sopAAAAAAAAGDJxBAAAAAAAAAAAgP5bRqltLpwCAAAAAACAIRJHAAAAAAAAAAAAGIZZbhul' + b'Tp0CAAAAAACAoRFHAAAAAAAAAAAAGI7LXBulLpwCAAAAAACAIRFHAAAAAAAAAAAAGJYmt45S' + b'N04BAAAAAADAUIgjAAAAAAAAAAAADNM8St3mwikAAAAAAADoO3EEAAAAAAAAAACA4ZrkdlHq' + b'1CkAAAAAAADoM3EEAAAAAAAAAACAYWtyT1HqwikAAAAAAADoK3EEAAAAAAAAAACAcVhHqRtn' + b'AAAAAAAAoI/EEQAAAAAAAAAAAMZjHqVuc+EUAAAAAAAA9Ik4AgAAAAAAAAAAwLhMcrsodeoU' + b'AAAAAAAA9IU4AgAAAAAAAAAAwPg0uTZKXTgFAAAAAAAAfSCOAAAAAAAAAAAAME77QMI6Sr1z' + b'CgAAAAAAALpOHAEAAAAAAAAAAGDcbqPUNhdOAQAAAAAAQFeJIwAAAAAAAAAAADDL7QMJU6cA' + b'AAAAAACgi8QRAAAAAAAAAAAA2JtcPAcSbpwCAAAAAACArhFHAAAAAAAAAAAA4Ksmdx+lfnYK' + b'AAAAAAAAukQcAQAAAAAAAAAAgD/7EqVunAEAAAAAAICuEEcAAAAAAAAAAADge+ZR6jYXTgEA' + b'AAAAAMC5iSMAAAAAAAAAAADwkkluH0iYOgUAAAAAAADnJI4AAAAAAAAAAADAj1zm2ij1xikA' + b'AAAAAAA4F3EEAAAAAAAAAAAAfqbJ3Uepn50CAAAAAACAcxBHAAAAAAAAAAAA4LW+RKkbZwAA' + b'AAAAAODUxBEAAAAAAAAAAAB4i3mUus2FUwAAAAAAAHAq4ggAAAAAAAAAAAC81SS3DyRMnQIA' + b'AAAAAIBTEEcAAAAAAAAAAADgPS5zbZR67RQAAAAAAAAcmzgCAAAAAAAAAAAA79XkHqPUhVMA' + b'AAAAAABwTOIIAAAAAAAAAAAAfNQ6St04AwAAAAAAAMcijgAAAAAAAAAAAMAhzKPUh1w4BQAA' + b'AAAAAIcmjgAAAAAAAAAAAMChfMq1AgkAAAAAAAAcmjgCAAAAAAAAAAAAhzTJ7aLUqVMAAAAA' + b'AABwKOIIAAAAAAAAAAAAHFqTa6PUG6cAAAAAAADgEMQRAAAAAAAAAAAAOIZ9IOE+Sl04BQAA' + b'AAAAAB8ljgAAAAAAAAAAAMAxraPUO2cAAAAAAADgI8QRAAAAAAAAAAAAOLbbKHWTC6cAAAAA' + b'AADgPcQRAAAAAAAAAAAAOIV5rhVIAAAAAAAA4D3EEQAAAAAAAAAAADiVycVzIGHqFAAAAAAA' + b'ALyFOAIAAAAAAAAAAACnJJAAAAAAAADAm4kjAAAAAAAAAAAAcGrNxXMg4cYpAAAAAAAAeA1x' + b'BAAAAAAAAAAAAM5hH0i4j1IXTgEAAAAAAMDPiCMAAAAAAAAAAABwTuso9c4ZAAAAAAAA+BFx' + b'BAAAAAAAAAAAAM7tNkrdOAMAAAAAAAAvEUcAAAAAAAAAAACgC+ZRapsLpwAAAAAAAODPxBEA' + b'AAAAAAAAAADoillOIAEAAAAAAIC/EEcAAAAAAAAAAACgSyYXz4GEqVMAAAAAAADwlTgCAAAA' + b'AAAAAAAAXSOQAAAAAAAAwDfEEQAAAAAAAAAAAOii5uI5kHDtFAAAAAAAAIgjAAAAAAAAAAAA' + b'0FX7QMJjlLpwCgAAAAAAgHETRwAAAAAAAAAAAKDr1gIJAAAAAAAA4yaOAAAAAAAAAAAAQB/s' + b'AwkbZwAAAAAAABgncQQAAAAAAAAAAAD6Yi6QAAAAAAAAME7iCAAAAAAAAAAAAPTJb4GEXDgF' + b'AAAAAADAeIgjAAAAAAAAAAAA0DfzXCuQAAAAAAAAMB7iCAAAAAAAAAAAAPTR5EIgAQAAAAAA' + b'YDTEEQAAAAAAAAAAAOirr4GEqVMAAAAAAAAMmzgCAAAAAAAAAAAAfSaQAAAAAAAAMALiCAAA' + b'AAAAAAAAAPRdcyGQAAAAAAAAMGjiCAAAAAAAAAAAAAzB10DCtVMAAAAAAAAMjzgCAAAAAAAA' + b'AAAAQ7EPJDxGqQunAAAAAAAAGBZxBAAAAAAAAAAAAIZmLZAAAAAAAAAwLOIIAAAAAAAAAAAA' + b'DJFAAgAAAAAAwICIIwAAAAAAAAAAADBU+0DCyhkAAAAAAAD6TxwBAAAAAAAAAACAIVtGqRtn' + b'AAAAAAAA6DdxBAAAAAAAAAAAAIZuLpAAAAAAAADQb+IIAAAAAAAAAAAAjIFAAgAAAAAAQI+J' + b'IwAAAAAAAAAAADAWAgkAAAAAAAA9JY4AAAAAAAAAAADAmOwDCW0unAIAAAAAAKA/xBEAAAAA' + b'AAAAAAAYm1lOIAEAAAAAAKBHxBEAAAAAAAAAAAAYo8mFQAIAAAAAAEBviCMAAAAAAAAAAAAw' + b'VgIJAAAAAAAAPSGOAAAAAAAAAAAAwJgJJAAAAAAAAPSAOAIAAAAAAAAAAABjJ5AAAAAAAADQ' + b'ceIIAAAAAAAAAAAAIJAAAAAAAADQaeIIAAAAAAAAAAAA8GwfSNhFqVOnAAAAAAAA6BZxBAAA' + b'AAAAAAAAAPhDk2sFEgAAAAAAALpFHAEAAAAAAAAAAAC+JZAAAAAAAADQMeIIAAAAAAAAAAAA' + b'8FcCCQAAAAAAAB0ijgAAAAAAAAAAAADfJ5AAAAAAAADQEeIIAAAAAAAAAAAA8DKBBAAAAAAA' + b'gA4QRwAAAAAAAAAAAIAfE0gAAAAAAAA4M3EEAAAAAAAAAAAA+DmBBAAAAAAAgDMSRwAAAAAA' + b'AAAAAIDXEUgAAAAAAAA4E3EEAAAAAAAAAAAAeD2BBAAAAAAAgDMQRwAAAAAAAAAAAIC3EUgA' + b'AAAAAAA4MXEEAAAAAAAAAAAAeDuBBAAAAAAAgBMSRwAAAAAAAAAAAID3EUgAAAAAAAA4EXEE' + b'AAAAAAAAAAAAeD+BBAAAAAAAgBMQRwAAAAAAAAAAAICPEUgAAAAAAAA4MnEEAAAAAAAAAAAA' + b'+DiBBAAAAAAAgCMSRwAAAAAAAAAAAIDDEEgAAAAAAAA4EnEEAAAAAAAAAAAAOJyvgYQrpwAA' + b'AAAAADgccQQAAAAAAAAAAAA4rH0g4SFKDacAAAAAAAA4DHEEAAAAAAAAAAAAOLxJrhVIAAAA' + b'AAAAOAxxBAAAAAAAAAAAADgOgQQAAAAAAIADEUcAAAAAAAAAAACA4xFIAAAAAAAAOABxBAAA' + b'AAAAAAAAADgugQQAAAAAAIAPEkcAAAAAAAAAAACA4xNIAAAAAAAA+ABxBAAAAAAAAAAAADgN' + b'gQQAAAAAAIB3EkcAAAAAAAAAAACA09kHEu6cAQAAAAAA4G3EEQAAAAAAAAAAAOC05lHqxhkA' + b'AAAAAABeTxwBAAAAAAAAAAAATk8gAQAAAAAA4A3EEQAAAAAAAAAAAOA8BBIAAAAAAABeSRwB' + b'AAAAAAAAAAAAzmcfSLhzBgAAAAAAgB8TRwAAAAAAAAAAAIDzuo1SF84AAAAAAADwMnEEAAAA' + b'AAAAAAAAOL+1QAIAAAAAAMDLxBEAAAAAAAAAAACgGwQSAAAAAAAAXiCOAAAAAAAAAAAAAN2x' + b'DyTcOAMAAAAAAMC3xBEAAAAAAAAAAACgWzZR6tQZAAAAAAAA/vAPJwAAAAAAAAAAAIBOaXJt' + b'lHr9n3/9c+scAAAA0Cm73L+dgTPyXwTAaP3t119/dQVgdKJUjx8AAAAAAAAAAF3335xAAgAA' + b'AAAAQPq7EwAAAAAAAAAAAEAnNbmHKDWcAgAAAAAAGDtxBAAAAAAAAAAAAOiuy1wrkAAAAAAA' + b'AIydOAIAAAAAAAAAAAB02+RCIAEAAAAAABg5cQQAAAAAAAAAAADovn0gYeMMAAAAAADAWIkj' + b'AAAAAAAAAAAAQD98ilI3zgAAAAAAAIyROAIAAAAAAAAAAAD0x1wgAQAAAAAAGCNxBAAAAAAA' + b'AAAAAOiXfSBh4QwAAAAAAMCYiCMAAAAAAAAAAABA/6wFEgAAAAAAgDERRwAAAAAAAAAAAIB+' + b'2gcSbpwBAAAAAAAYA3EEAAAAAAAAAAAA6K9NlDp1BgAAAAAAYOjEEQAAAAAAAAAAAKC/mlwr' + b'kAAAAAAAAAydOAIAAAAAAAAAAAD02z6QsIlSwykAAAAAAIChEkcAAAAAAAAAAACA/pvkWoEE' + b'AAAAAABgqMQRAAAAAAAAAAAAYBj2gYQHZwAAAAAAAIZIHAEAAAAAAAAAAPgfO3dQ3DYYRWG0' + b'DPQYtAwsBt68fcPAZpDFA2BDMAMLgiEYWpVFZjqdtonjsSz9OmfmErgAPqAd28ga3AAAAAAA' + b'ALRGHAEAAAAAAAAAAADasousoxsAAAAAAICWiCMAAAAAAAAAAABAew6RtXcDAAAAAADQCnEE' + b'AAAAAAAAAAAAaNM5sl7cAAAAAAAAtEAcAQAAAAAAAAAAANo1RFbvBgAAAAAAYOnEEQAAAAAA' + b'AAAAAKBd3bhrZP1wBQAAAAAAsGTiCAAAAAAAAAAAANC2t0DCJbLCFQAAAAAAwFKJIwAAAAAA' + b'AAAAAED7NuMubgAAAAAAAJZKHAEAAAAAAAAAAADWYRtZgxsAAAAAAIAlEkcAAAAAAAAAAACA' + b'9dhF1tENAAAAAADA0ogjAAAAAAAAAAAAwLocImvvBgAAAAAAYEnEEQAAAAAAAAAAAGB9zpHV' + b'uwEAAAAAAFgKcQQAAAAAAAAAAABYp6tAAgAAAAAAsBTiCAAAAAAAAAAAALBO3bghssIVAAAA' + b'AADA3IkjAAAAAAAAAAAAwHptxl3dAAAAAAAAzJ04AgAAAAAAAAAAAKzbJrIGNwAAAAAAAHMm' + b'jgAAAAAAAAAAAADsIuvoBgAAAAAAYK7EEQAAAAAAAAAAAIA3h8jauwEAAAAAAJgjcQQAAAAA' + b'AAAAAADg3SmyejcAAAAAAABzI44AAAAAAAAAAAAAvOvGXSMrXAEAAAAAAMyJOAIAAAAAAAAA' + b'AADwO4EEAAAAAABgdsQRAAAAAAAAAAAAgD9txg1uAAAAAAAA5kIcAQAAAAAAAAAAAPibn5F1' + b'cgMAAAAAADAH4ggAAAAAAAAAAADAv7xG1t4NAAAAAADAs4kjAAAAAAAAAAAAAP9zjqzeDQAA' + b'AAAAwDOJIwAAAAAAAAAAAAAfuUZWuAEAAAAAAHgWcQQAAAAAAAAAAADgI903gQQAAAAAAOCJ' + b'xBEAAAAAAAAAAACAz9iMO7kBAAAAAAB4BnEEAAAAAAAAAAAA4LN2kXV0AwAAAAAAMDVxBAAA' + b'AAAAAAAAAOAWh8h6cQMAAAAAADAlcQQAAAAAAAAAAADgVkNk9W4AAAAAAACmIo4AAAAAAAAA' + b'AAAA3Kobd4mscAUAAAAAADAFcQQAAAAAAAAAAADgK76Pu7gBAAAAAACYgjgCAAAAAAAAAAAA' + b'8FXbyDq5AQAAAAAAeDRxBAAAAAAAAAAAAOAer5G1dwMAAAAAAPBI4ggAAAAAAAAAAADAvU6R' + b'1bsBAAAAAAB4FHEEAAAAAAAAAAAA4F7duEtkhSsAAAAAAIBH+CUAe3dz21aW5nGYMLznuRGQ' + b'GZATAdnAu7cmAnEiEIGDu/bttSAUJ4LiRNDqPYFRRVCeDDwRSM5gDluusatsy/ogxfvxPMCL' + b'wwZ69VuU6IX+Mo4AAAAAAAAAAAAAHMKk3LUMAAAAAADAMRhHAAAAAAAAAAAAAA5lkSJvZAAA' + b'AAAAAA7NOAIAAAAAAAAAAABwSBcp8koGAAAAAADgkIwjAAAAAAAAAAAAAIe2SZHnMgAAAAAA' + b'AIdiHAEAAAAAAAAAAAA4tHG56xQ5SQEAAAAAAByCcQQAAAAAAAAAAADgGCblrmUAAAAAAAAO' + b'wTgCAAAAAAAAAAAAcCyLFLmRAQAAAAAAeCnjCAAAAAAAAAAAAMAxvU+Rz2QAAAAAAABewjgC' + b'AAAAAAAAAAAAcGzbFHkuAwAAAAAA8FzGEQAAAAAAAAAAAIBjG4/uBxKSFAAAAAAAwHMYRwAA' + b'AAAAAAAAAABew6zcRgYAAAAAAOA5jCMAAAAAAAAAAAAAr+U8RV7LAAAAAAAAPJVxBAAAAAAA' + b'AAAAAOA1/ZIiz2UAAAAAAACewjgCAAAAAAAAAAAA8NquU+QkAwAAAAAA8FhvJQAAAAAAAAAA' + b'AABe2aTcdbmlFAAAwLFVUX/v3x4P/XvEv1Xa6cPt7nItAwDAcBlHAAAAAAAAAAAAAE5hkSI3' + b'd7urRgoAAOAxqqin5Zl+/p8/+vyvf2+oBQAA/WMcAQAAAAAAAAAAADiV9ynyzd3u6kYKAAAY' + b'tirqeXlSuT/e6ejL4IGxAwAAwDgCAAAAAAAAAAAAcFLXKfL8bnf1UQoAAOivKurp6H7s4K8j' + b'CPt3rBAAAPAzxhEAAAAAAAAAAACAU9r/EtT16P4XogAAgI6rol6O7kcQpp+/5+/fmTIAAMBL' + b'GUcAAAAAAAAAAAAATm2WIm/udldrKQAAoBuqqP8YPti/y8+fJ8oAAADHYhwBAAAAAAAAAAAA' + b'aIOLFPnmbnd1LQUAALRLFfVydD+CMP38LlQBAABem3EEAAAAAAAAAAAAoC22KfL8bnf1UQoA' + b'ADiNKur9+MEftyw3UwUAAGgD4wgAAAAAAAAAAABAW4zLXY/ufwkLAAB4BVXUy9H9CMLy83fx' + b'sSoAAEAbGUcAAAAAAAAAAAAA2mSWIm/udldrKQAA4LCqqNPoyxDC/maqAAAAXWEcAQAAAAAA' + b'AAAAAGibixT55m53dS0FAAC8TBX12cgYAgAA0APGEQAAAAAAAAAAAIA22qbI87vd1UcpAADg' + b'8aqo56P7IYT9KMJCEQAAoC+MIwAAAAAAAAAAAABtNC53XW4uBQAA/FgVdRrdDyEsP79jVQAA' + b'gD4yjgAAAAAAAAAAAAC01SxF3tztrtZSAADAF1XU09H9EML+FooAAABDYBwBAAAAAAAAAAAA' + b'aLOLFPnmbnd1LQUAAENWRT0vz6rcstxMEQAAYGiMIwAAAAAAAAAAAABtt02Rp3e7qzspAAAY' + b'kq8GEc7KTRQBAACGzDgCAAAAAAAAAAAA0Hbjctej+7+QCwAAvWYQAQAA4PuMIwAAAAAAAAAA' + b'AABdsEiRm7vdVSMFAAB9U0U9Hd2PIazKzRQBAAD4lnEEAAAAAAAAAAAAoCvep8g3d7urGykA' + b'AOi6Kuo0+jKIsFAEAADgYcYRAAAAAAAAAAAAgC7Zpsjzu93VnRQAAHRRFfW8POvR/TDCWBEA' + b'AIDHMY4AAAAAAAAAAAAAdMmk3HZ0/4tkAADQCVXUqTyr0f0owkQRAACAp3sjAQAAAAAAAAAA' + b'ANAx71LktQwAALRdFfW83LZ8vC33y8gwAgAAwLMZRwAAAAAAAAAAAAC6qEmR5zIAANBGVdSr' + b'ch/Kx9/LnSsCAADwcm8lAAAAAAAAAAAAADpoXG6bIi/vdld3cgAAcGpV1NPyrMqtP39fBQAA' + b'4ICMIwAAAAAAAAAAAABdNSvXjO5/+QwAAE6iinr++TvpuRoAAADH80YCAAAAAAAAAAAAoMMu' + b'UuQzGQAAeG1V1MtyN+Xj7yPDCAAAAEdnHAEAAAAAAAAAAADoum2KnGQAAOA1VFGvyn0oH/+7' + b'3EIRAACA1/FWAgAAAAAAAAAAAKDjxuWuyy2lAADgWPajCOVpyk3UAAAAeH1vJAAAAAAAAAAA' + b'AAB6YJEir2UAAODQ9qMI5T6Wj7+ODCMAAACcjHEEAAAAAAAAAAAAoC9+SZHnMgAAcAhGEQAA' + b'ANrlrQQAAAAAAAAAAABAj2zLGUgAAODZ9qMI5WlGBhEAAABa5Y0EAAAAAAAAAAAAQI/MUuSN' + b'DAAAPFUV9bLcTfn468gwAgAAQOu8lQAAAAAAAAAAAADomYsU+fpud3UjBQAAP1NFPS/PfmBr' + b'oQZwhP++JCU4sLvb3eUHGQAYIuMIAAAAAAAAAAAAQB9dp8jTu93VnRQAAHxPFfW0PE25czWA' + b'IzG8wjH8Vm4pAwBDZBwBAAAAAAAAAAAA6KNxuW25MykAAPhaFfX+r7ivP99YEQAAgG54IwEA' + b'AAAAAAAAAADQU+9S5JUMAAD8oYp6//3wQ7n3I8MIAAAAnWIcAQAAAAAAAAAAAOizTYo8lQEA' + b'YNiqqOflbsrHX8tNFAEAAOietxIAAAAAAAAAAAAAPbb/a8DbckspAACGp4o6lacpd6EGAABA' + b't72RAAAAAAAAAAAAAOi5RYq8lgEAYFiqqM/K83FkGAEAAKAXjCMAAAAAAAAAAAAAQ/BLijyX' + b'AQCg/6qop+Vuysd/lBsrAgAA0A/GEQAAAAAAAAAAAICh2EoAANBvVdTr8nwot1ADAACgX4wj' + b'AAAAAAAAAAAAAEMxS5EbGQAA+qeKel7upnz8pdxYEQAAgP4xjgAAAAAAAAAAAAAMyfsUeS4D' + b'AEB/VFE35fm93EINAACA/jKOAAAAAAAAAAAAAAzNdYqcZAAA6LYq6nm5D+XjezUAAAD6zzgC' + b'AAAAAAAAAAAAMDSTco0MAADdVUW9Ls9NuZkaAAAAw2AcAQAAAAAAAAAAABiiixR5KQMAQLdU' + b'UadyN+XjL+XGigAAAAyHcQQAAAAAAAAAAABgqLYpcpIBAKAbqqjPyvOx3EINAACA4XkrAQAA' + b'AAAAAAAAADBQk3JNubUUAADtVUWdPn9vu1ADAABguN5IAAAAAAAAAAAAAAzYRYq8lAEAoJ2q' + b'qOfluRkZRgAAABg84wgAAAAAAAAAAADA0G1T5CQDAEC7VFGvRvfDCDM1AAAAeCsBAAAAAAAA' + b'AAAAMHCTck25tRQAAKdXRb0frtqUO1cDAACAP7yRAAAAAAAAAAAAAGB0kSIvZQAAOK0q6nl5' + b'bkaGEQAAAPgL4wgAAAAAAAAAAAAA97YpcpIBAOA0qqjPRvfDCDM1AAAA+CvjCAAAAAAAAAAA' + b'AAD3JuUaGQAAXl8V9f572D/KjdUAAADge4wjAAAAAAAAAAAAAHxxkSIvZQAAeB1V1Kncdfn4' + b'Xg0AAAAeYhwBAAAAAAAAAAAA4M+2KXKSAQDguKqop+W5KfdODQAAAH7GOAIAAAAAAAAAAADA' + b'n03KNTIAABxPFfW8PB/KzdQAAADgMYwjAAAAAAAAAAAAAHzrIkVeygAAcHhV1Kvy/F5urAYA' + b'AACPZRwBAAAAAAAAAAAA4Pu2KXKSAQDgcKqoN+X5VQkAAACeyjgCAAAAAAAAAAAAwPdNyjUy' + b'AAAcRhX1tjwXSgAAAPAcxhEAAAAAAAAAAAAAfuwiRV7KAADwfFXUqdyH8vFcDQAAAJ7LOAIA' + b'AAAAAAAAAADAwzYSAAA8z34YoTw35WZqAAAA8BLGEQAAAAAAAAAAAAAeNkuRGxkAAJ6minpe' + b'ng8jwwgAAAAcgHEEAAAAAAAAAAAAgJ97nyLPZQAAeJzPwwg35SZqAAAAcAjGEQAAAAAAAAAA' + b'AAAeZysBAMDPfTWMMFYDAACAQzGOAAAAAAAAAAAAAPA4sxR5LQMAwI8ZRgAAAOBYjCMAAAAA' + b'AAAAAAAAPF6TIk9lAAD4lmEEAAAAjsk4AgAAAAAAAAAAAMDj7X/RbysDAMCfGUYAAADg2Iwj' + b'AAAAAAAAAAAAADzNIkU+kwEA4J5hBAAAAF6DcQQAAAAAAAAAAACAp9umyEkGAGDoDCMAAADw' + b'WowjAAAAAAAAAAAAADzd/pf/NjIAAENmGAEAAIDXZBwBAAAAAAAAAAAA4HnOU+SlDADAEBlG' + b'AAAA4LUZRwAAAAAAAAAAAAB4vq0EAMDQGEYAAADgFIwjAAAAAAAAAAAAADzfJEVuZAAAhsIw' + b'AgAAAKdiHAEAAAAAAAAAAADgZd6nyHMZAIC+q6JO5dmODCMAAABwAsYRAAAAAAAAAAAAAF5u' + b'IwEA0GefhxFuys3UAAAA4BSMIwAAAAAAAAAAAAC83CJFXskAAPTYzcgwAgAAACdkHAEAAAAA' + b'AAAAAADgMDYpcpIBAOibKurtyDACAAAAJ2YcAQAAAAAAAAAAAOAwxuU2MgAAfVJFvf9+c64E' + b'AAAAp2YcAQAAAAAAAAAAAOBwzlPkpQwAQB9UUa/Kc6EEAAAAbWAcAQAAAAAAAAAAAOCwthIA' + b'AF1XRb0sz69KAAAA0BbGEQAAAAAAAAAAAAAOa5IiNzIAAF1VRT0vz7USAAAAtIlxBAAAAAAA' + b'AAAAAIDDW6fIUxkAgK6pok7l2ZYbqwEAAECbGEcAAAAAAAAAAAAAOLz9LxNuZQAAOui63EwG' + b'AAAA2sY4AgAAAAAAAAAAAMBxLFLkMxkAgK6oot7sv8MoAQAAQBsZRwAAAAAAAAAAAAA4nk2K' + b'nGQAANquinpVngslAAAAaCvjCAAAAAAAAAAAAADHMym3lgEAaLMq6nl5NkoAAADQZsYRAAAA' + b'AAAAAAAAAI7rfYo8lQEAaKMq6lSebbmxGgAAALSZcQQAAAAAAAAAAACA49tKAAC01KbcTAYA' + b'AADazjgCAAAAAAAAAAAAwPEtUuQzGQCANqmiXpfnXAkAAAC6wDgCAAAAAAAAAAAAwOvYpMhJ' + b'BgCgDaqo5+X5RQkAAAC6wjgCAAAAAAAAAAAAwOuYlFvLAACcWhX1frBpqwQAAABdYhwBAAAA' + b'AAAAAAAA4PW8T5GnMgAAJ7YpN5MBAACALjGOAAAAAAAAAAAAAPC6thIAAKdSRX1WnnMlAAAA' + b'6BrjCAAAAAAAAAAAAACva5Ein8kAALy2KurpyFATAAAAHWUcAQAAAAAAAAAAAOD1bSQAAE5g' + b'W24sAwAAAF1kHAEAAAAAAAAAAADg9U1S5EYGAOC1VFGvy7NQAgAAgK4yjgAAAAAAAAAAAABw' + b'GusUeSoDAHBsVdTz8jRKAAAA0GXGEQAAAAAAAAAAAABOY1xuIwMA8Aq2n797AAAAQGcZRwAA' + b'AAAAAAAAAAA4nXcp8lIGAOBYqqib8syUAAAAoOuMIwAAAAAAAAAAAACc1kYCAOAYqqin5Vkr' + b'AQAAQB8YRwAAAAAAAAAAAAA4rVmK7JcWAYBj2JYbywAAAEAfGEcAAAAAAAAAAAAAOL0mRU4y' + b'AACHUkW9H19aKAEAAEBfGEcAAAAAAAAAAAAAOL39X3RuZAAADqGKOvluAQAAQN8YRwAAAAAA' + b'AAAAAABoh4sUeSoDAHAA29H9+BIAAAD0hnEEAAAAAAAAAAAAgPbYSgAAvEQV9bI875QAAACg' + b'b4wjAAAAAAAAAAAAALTHIkVeygAAvMBWAgAAAPrIOAIAAAAAAAAAAABAu2wlAACeo4q6Kc9E' + b'CQAAAPrIOAIAAAAAAAAAAABAu0xS5LUMAMBTVFFPy+M7BAAAAL1lHAEAAAAAAAAAAACgfZoU' + b'OckAADzBptxYBgAAAPrKOAIAAAAAAAAAAABA++x/sbGRAQB4jCrqZXneKQEAAECfGUcAAAAA' + b'AAAAAAAAaKeLFHkqAwDwCBsJAAAA6DvjCAAAAAAAAAAAAADt5RcdAYAHVVGvyjNTAgAAgL4z' + b'jgAAAAAAAAAAAADQXu9S5KUMAMD3VFGn8jRKAAAAMATGEQAAAAAAAAAAAADabSMBAPAD63IT' + b'GQAAABgC4wgAAAAAAAAAAAAA7TZLkVcyAABfq6JOo/txBAAAABgE4wgAAAAAAAAAAAAA7dek' + b'yEkGAOAr+2GEsQwAAAAMhXEEAAAAAAAAAAAAgPabjPxlaADgsyrqaXneKwEAAMCQGEcAAAAA' + b'AAAAAAAA6IZ1ipxkAACKRgIAAACGxjgCAAAAAAAAAAAAQDeMy21kAIBhq6KeludcCQAAAIbG' + b'OAIAAAAAAAAAAABAd5ynyFMZAGDQGgkAAAAYIuMIAAAAAAAAAAAAAN2ykQAAhqmKelqecyUA' + b'AAAYIuMIAAAAAAAAAAAAAN3yLkVeygAAg9RIAAAAwFAZRwAAAAAAAAAAAADonkYCABiWKupp' + b'ec6VAAAAYKiMIwAAAAAAAAAAAAB0zyJFPpMBAAalkQAAAIAhM44AAAAAAAAAAAAA0E0bCQBg' + b'GKqop+U5VwIAAIAhM44AAAAAAAAAAAAA0E2TFHklAwAMgp/5AAAADJ5xBAAAAAAAAAAAAIDu' + b'2qTISQYA6K8q6v3P+rUSAAAADJ1xBAAAAAAAAAAAAIDuGo/8siQA9N368898AAAAGDTjCAAA' + b'AAAAAAAAAADdtk6RkwwA0FsrCQAAAMA4AgAAAAAAAAAAAEDX7f+SdCMDAPRPFfWqPBMlAAAA' + b'wDgCAAAAAAAAAAAAQB9cpMhTGQCgd9YSAAAAwD3jCAAAAAAAAAAAAAD90EgAAP1RRb0sz0wJ' + b'AAAAuGccAQAAAAAAAAAAAKAfzlPkqQwA0BtrCQAAAOAL4wgAAAAAAAAAAAAA/bGRAAC6r4p6' + b'Wp53SgAAAMAXxhEAAAAAAAAAAAAA+uNdiryUAQA6byUBAAAA/JlxBAAAAAAAAAAAAIB+aSQA' + b'gM5bSwAAAAB/ZhwBAAAAAAAAAAAAoF8WKfJSBgDopirqVXnGSgAAAMCfGUcAAAAAAAAAAAAA' + b'6J9GAgDorJUEAAAA8C3jCAAAAAAAAAAAAAD9s0iRlzIAQLdUUU/3P8eVAAAAgG8ZRwAAAAAA' + b'AAAAAADop60EANA5awkAAADg+4wjAAAAAAAAAAAAAPTTJEVeyQAAnXImAQAAAHyfcQQAAAAA' + b'AAAAAACA/mokAIBuqKLeDyNMlAAAAIDvM44AAAAAAAAAAAAA0F+TFHklAwB0gp/ZAAAA8ADj' + b'CAAAAAAAAAAAAAD91kgAAO1WRZ3K804JAAAA+DHjCAAAAAAAAAAAAAD9NkmRVzIAQKudSQAA' + b'AAAPM44AAAAAAAAAAAAA0H+NBADQamsJAAAA4GHGEQAAAAAAAAAAAAD6b5Iir2QAgPapop6W' + b'Z6YEAAAAPMw4AgAAAAAAAAAAAMAwNBIAQCudSQAAAAA/ZxwBAAAAAAAAAAAAYBgmKfJaBgBo' + b'nZUEAAAA8HPGEQAAAAAAAAAAAACGo0mRkwwA0A5V1PPyzJQAAACAnzOOAAAAAAAAAAAAADAc' + b'43JrGQCgNVYSAAAAwOMYRwAAAAAAAAAAAAAYlnWKnGQAgFY4kwAAAAAexzgCAAAAAAAAAAAA' + b'wLCMy61lAIDTqqKelmeiBAAAADyOcQQAAAAAAAAAAACA4VmnyEkGADipMwkAAADg8YwjAAAA' + b'AAAAAAAAAAzPuNxaBgA4qZUEAAAA8HjGEQAAAAAAAAAAAACGaZ0iJxkA4PVVUU/LM1MCAAAA' + b'Hs84AgAAAAAAAAAAAMAwjUf+YjUAnMqZBAAAAPA0xhEAAAAAAAAAAAAAhmstAQCcxFICAAAA' + b'eBrjCAAAAAAAAAAAAADDNUmRVzIAwOupok7leacEAAAAPI1xBAAAAAAAAAAAAIBhayQAgFe1' + b'lAAAAACezjgCAAAAAAAAAAAAwLBNUuSVDADwas4kAAAAgKczjgAAAAAAAAAAAABAIwEAvJql' + b'BAAAAPB0xhEAAAAAAAAAAAAAmKTIKxkA4LiqqOf7n7tKAAAAwNMZRwAAAAAAAAAAAABgr5EA' + b'AI5uKQEAAAA8j3EEAAAAAAAAAAAAAPYmKfJKBgA4qjMJAAAA4HmMIwAAAAAAAAAAAADwh0YC' + b'ADiqhQQAAADwPMYRAAAAAAAAAAAAAPjDJEX2F60B4AiqqP2MBQAAgBcwjgAAAAAAAAAAAADA' + b'19YSAMBRLCUAAACA5zOOAAAAAAAAAAAAAMDXFinyUgYAODg/XwEAAOAFjCMAAAAAAAAAAAAA' + b'8FeNBABwOFXUqTwzJQAAAOD5jCMAAAAAAAAAAAAA8FeLFHkpAwAcjJ+rAAAA8ELGEQAAAAAA' + b'AAAAAAD4nrUEAHAwSwkAAADgZYwjAAAAAAAAAAAAAPA971LkqQwAcBBLCQAAAOBljCMAAAAA' + b'AAAAAAAA8CONBABwEDMJAAAA4GWMIwAAAAAAAAAAAADwI+cp8lQGAHi+KuqlCgAAAPByxhEA' + b'AAAAAAAAAAAAeEgjAQC8yFICAAAAeDnjCAAAAAAAAAAAAAA85CxFTjIAwLMtJQAAAICXM44A' + b'AAAAAAAAAAAAwEPG5dYyAMCzzSUAAACAlzOOAAAAAAAAAAAAAMDPrFPkJAMAPE0V9X4YYawE' + b'AAAAvNxbCQAAAAAAAAAAAAD4if0vdZ6V20oBAE8ylwAO5lO5D+U+fr69m3KrcufyAABA/xlH' + b'AAAAAAAAAAAAAOAxmpFxBAB4KuMI8HS/jb4MIOzHEO5ud5c3P/o/V1EvJQMAgGEwjgAAAAAA' + b'AAAAAADAY0xS5NXd7morBQA82lIC+KGvRxBu9u/t7vKjLAAAwI8YRwAAAAAAAAAAAADgsdbl' + b'tjIAwKPNJAAjCAAAwGEYRwAAAAAAAAAAAADgsWYp8vJud3UjBQA8rIp6qQID87/lPnx1+xGE' + b'D7IAAACHYhwBAAAAAAAAAAAAgKdoyi1lAICfmktAT30afTuCcCMLAABwbMYRAAAAAAAAAAAA' + b'AHiKRYo8vdtdfZQCAB40lYCe+a9y17e7y2spAACAUzCOAAAAAAAAAAAAAMBTNeVWMgDAg+YS' + b'0AOfym32d7u7vJMDAAA4pTcSAAAAAAAAAAAAAPBE5ynyVAYAeNBCAjpsP4rw93LT291lYxgB' + b'AABog7cSAAAAAAAAAAAAAPAMq3KNDADwrSrquQp02H/uv+cZRAAAANrmjQQAAAAAAAAAAAAA' + b'PMM6RU4yAMB3TSWgg34r92+3u8u1YQQAAKCN3koAAAAAAAAAAAAAwDOMy52V20oBAN+YS0CH' + b'fCq3H0TwvQ4AAGi1NxIAAAAAAAAAAAAA8EyNBADwXUsJ6Ih/lpsaRgAAALrAOAIAAAAAAAAA' + b'AAAAzzVJkVcyAMA3phLQcp/K/fvt7vKs3J0cAABAFxhHAAAAAAAAAAAAAOAlVhIAwDcmEtBi' + b'/yw3vd1dXksBAAB0iXEEAAAAAAAAAAAAAF5ikSLPZQCAe1XUSxVoqU/l/uN2d3lW7k4OAACg' + b'a4wjAAAAAAAAAAAAAPBSawkA4P8lCWih/yk3v91dbqUAAAC6yjgCAAAAAAAAAAAAAC91niJP' + b'ZQCAf5lLQMv8/XZ3uR9G+CgFAADQZcYRAAAAAAAAAAAAADiElQQA8C/GEWiLT+X+dru7bKQA' + b'AAD6wDgCAAAAAAAAAAAAAIewTpGTDAAw8vOQNvit3PR2d3kjBQAA0BdvJQAAAAAAAAAAAADg' + b'AMblzsptpQBg4BYScGJ/v91dNjIAAAB980YCAAAAAAAAAAAAAA6kkQCAIauiTipwQp/K/c0w' + b'AgAA0FfGEQAAAAAAAAAAAAA4lEmKvJQBgAGbS8CJ/FZueru7vJECAADoq7cSAAAAAAAAAP/H' + b'3t3cNo4lahgWBndvKgIpAysDc8F9KYPWzcAXhNatWRODUUdwVRmo9wRGjuDKGdgRUM7gHo3V' + b'XV1Tdsk/PBR/ngc4OD2zmMULq6lZ8BMAAADU6DacnQwADNRUAi7gt6osbmUAAAD67m8SAAAA' + b'AAAAAAAAAFCjL0mWT2UAYKA8A2nSUzj/bRgBAAAYCuMIAAAAAAAAAAAAANTNC3oADNVMAhq0' + b'qMpiIwMAADAUxhEAAAAAAAAAAAAAqNsiyfJEBgAGyPOPpvxWlcVWBgAAYEiMIwAAAAAAAAAA' + b'AABQt6tw5jIAMEAzCWjAYzgrGQAAgKExjgAAAAAAAAAAAABADCsJABigKwlowKIqi4MMAADA' + b'0BhHAAAAAAAAAAAAACCGSZLlqQwADMU4W85UoAG/V2WxkwEAABgi4wgAAAAAAAAAAAAAxHIr' + b'AQADkkhAZE/hLGQAAACGyjgCAAAAAAAAAAAAALF8SbJ8KgMAA+GZR2zrqiwOMgAAAENlHAEA' + b'AAAAAAAAAACAmBYSADAQUwmI6LEqi5UMAADAkBlHAAAAAAAAAAAAACCmWwkAGIhEAiJaSQAA' + b'AAydcQQAAAAAAAAAAAAAYrpKsnwhAwADMJOASO6qstjIAAAADJ1xBAAAAAAAAAAAAABiW0gA' + b'APBhKwkAAACMIwAAAAAAAAAAAAAQ302S5X5NG4C+86wjhvuqLHYyAAAAGEcAAAAAAAAAAAAA' + b'oBm3EgDQc1cSEMFaAgAAgGfGEQAAAAAAAAAAAABowjzJ8kQGAIA3e6zKYiMDAADAM+MIAAAA' + b'AAAAAAAAADTh+GvaCxkA6KNxtkxVIIK1BAAAAN8YRwAAAAAAAAAAAACgKbcSAAC82UYCAACA' + b'b4wjAAAAAAAAAAAAANCUSZLlqQwA9FAiATX7WpXFQQYAAIBvjCMAAAAAAAAAAAAA0KSFBAD0' + b'0EwCaraRAAAA4HvGEQAAAAAAAAAAAABo0i9Jlvt1bQCA1z1WZbGTAQAA4HvGEQAAAAAAAAAA' + b'AABo2kICAIBXbSUAAAD4kXEEAAAAAAAAAAAAAJp2KwEAPZNKQI02EgAAAPzIOAIAAAAAAAAA' + b'AAAATZskWT6XAQDgB49VWexlAAAA+JFxBAAAAAAAAAAAAAAuYSEBAMAPthIAAAC8zDgCAAAA' + b'AAAAAAAAAJfwJcnyqQwA9IRnGnUxjgAAAPAK4wgAAAAAAAAAAAAAXMpCAgB6YiIBdajKYqcC' + b'AADAy4wjAAAAAAAAAAAAAHApCwkAAP50JwEAAMDrjCMAAAAAAAAAAAAAcCmTJMtTGQAA/m0n' + b'AQAAwOuMIwAAAAAAAAAAAABwSQsJAOiycbZMVKAmOwkAAABeZxwBAAAAAAAAAAAAgEv6Jcly' + b'L5UC0GUzCahDVRY7FQAAAF5nHAEAAAAAAAAAAACAS1tIAAAM3L0EAAAAP2ccAQAAAAAAAAAA' + b'AIBLu5UAABi4vQQAAAA/ZxwBAAAAAAAAAAAAgEubJFmeygAADJhxBAAAgDOMIwAAAAAAAAAA' + b'AADQBgsJAIABM44AAABwhnEEAAAAAAAAAAAAANpgnmR5IgMAHZRKQA2MIwAAAJxhHAEAAAAA' + b'AAAAAACANrgKZy4DADBEVVkcVAAAAPg54wgAAAAAAAAAAAAAtMWtBADAAN1JAAAAcJ5xBAAA' + b'AAAAAAAAAADa4jrJ8qkMAAAAAAD8J+MIAAAAAAAAAAAAALTJrQQAwMDsJQAAADjPOAIAAAAA' + b'AAAAAAAAbbKQAAAYmIMEAAAA5xlHAAAAAAAAAAAAAKBNrpIsn8sAAAAAAMBfGUcAAAAAAAAA' + b'AAAAoG0WEgAAA7KTAAAA4DzjCAAAAAAAAAAAAAC0zZckyxMZAOiImQQAAAAQn3EEAAAAAAAA' + b'AAAAANpoIQEAHWHQBwAAABpgHAEAAAAAAAAAAACANlpIAAAAAADAH4wjAAAAAAAAAAAAANBG' + b'10mWz2QAAAbgIAEAAMB5xhEAAAAAAAAAAAAAaKuFBABA31VlsVcBAADgPOMIAAAAAAAAAAAA' + b'ALTVQgIAAAAAAI6MIwAAAAAAAAAAAADQVldJls9lAAAAAADAOAIAAAAAAAAAAAAAbbaQAAAA' + b'AAAA4wgAAAAAAAAAAAAAtNmXJMsTGQCAvhpny6kKAAAA5xlHAAAAAAAAAAAAAKDtFhIAAD02' + b'lQAAAOA84wgAAAAAAAAAAAAAtN1CAgAAAACAYTOOAAAAAAAAAAAAAEDbXSdZPpUBAAAAAGC4' + b'jCMAAAAAAAAAAAAA0AW3EgAAAAAADJdxBAAAAAAAAAAAAAC6YC4BAAAAAMBwGUcAAAAAAAAA' + b'AAAAoAsmSZanMgAAPeQ7DgAAwBsYRwAAAAAAAAAAAACgKxYSAAAAAAAMk3EEAAAAAAAAAAAA' + b'ALpiLgEA0EOJBAAAAOcZRwAAAAAAAAAAAACgK66SLDeQAAD0zUwCAACA84wjAAAAAAAAAAAA' + b'ANAlCwkAaJm9BAAAABCfcQQAAAAAAAAAAAAAuuRLkuWJDAC0yEECPulGAgAAgPOMIwAAAAAA' + b'AAAAAADQNXMJAIA+GWdL408AAABnGEcAAAAAAAAAAAAAoGsWEgAAPTOTAAAA4OeMIwAAAAAA' + b'AAAAAADQNTdJlk9lAAB6xHcbAACAM4wjAAAAAAAAAAAAANBFcwkAgB6ZSQAAAPBzxhEAAAAA' + b'AAAAAAAA6KKFBABAjxhHAAAAOMM4AgAAAAAAAAAAAABddJ1k+VQGAKAnbiQAAAD4OeMIAAAA' + b'AAAAAAAAAHTVQgIAWmAvAXUYZ8uZCgAAAK8zjgAAAAAAAAAAAABAVy0kAKAFDhJQE+MIAAAA' + b'P2EcAQAAAAAAAAAAAICumiRZ7iVCAKAvUgkAAABeZxwBAAAAAAAAAAAAgC5bSAAA9EQqAfSO' + b'MTcAgBoZRwAAAAAAAAAAAACgy+YSAAA9MRlnSy9SQ79cSQAAUB/jCAAAAAAAAAAAAAB02STJ' + b'ci8RAnBJewmoUSoBAADAy4wjAAAAAAAAAAAAANB1CwkAuJSqLA4q4HsN8J/G2XKqAgBAvYwj' + b'AAAAAAAAAAAAANB1cwkAgJ64HmfLRAbohakEAAD1Mo4AAAAAAAAAAAAAQNdNkiyfyQAA9MRC' + b'AuiFqQQAAPUyjgAAAAAAAAAAAABAHywkAOCC7iXA9xrgP0wlAACol3EEAAAAAAAAAAAAAPpg' + b'LgEAF3SQgBpdj7PlVAbovJkEAAD1Mo4AAAAAAAAAAAAAQB9Mkiz38hEA0Be3EkDnTSUAAKiX' + b'cQQAAAAAAAAAAAAA+mIhAQAXcpCAur/XjLNlIgN02rUEAAD1Mo4AAAAAAAAAAAAAQF/MJQDg' + b'QvYSULMr322gu8bZMlUBAKB+xhEAAAAAAAAAAAAA6ItJkuUzGQCAnlhJAJ2VSgAAUD/jCAAA' + b'AAAAAAAAAAD0yUICAKAnJuNs6bsNdFMqAQBA/YwjAAAAAAAAAAAAANAncwkAuICdBESykgC6' + b'ZZwtk3DdKAEAUD/jCAAAAAAAAAAAAAD0ySTJ8pkMAEBfvtuMs+VCBugUg20AAJEYRwAAAAAA' + b'AAAAAACgbxYSANCwgwREtD79Ej3QDcYRAAAiMY4AAAAAAAAAAAAAQN94GQmARlVlsVeBiK7C' + b'uZUB2u80ZPJFCQCAOIwjAAAAAAAAAAAAANA3kyTLZzIAAD3y6zhb+n4D7WeoDQAgIuMIAAAA' + b'AAAAAAAAAPTRQgIAGnYvAZFtJIDWW0kAABCPcQQAAAAAAAAAAAAA+sgvtgLQtIMERHY9zpYr' + b'GaCdwuczDddECQCAeIwjAAAAAAAAAAAAANBHkyTLZzIA0KAHCWjAr+Ns6TsOtNNKAgCAuIwj' + b'AAAAAAAAAAAAANBXCwkAaNCDBDRkO86WiQzQHuEzmYbrRgkAgLiMIwAAAAAAAAAAAADQV6kE' + b'ADToIAENmYSzlQFaZSMBAEB8xhEAAAAAAAAAAAAA6KvrJMunMgDQkL0ENOhmnC03MsDlhc/i' + b'7eh5tAQAgMiMIwAAAAAAAAAAAADQZ3MJAICe+mWcLRcywOWEz+A0XCslAACaYRwBAAAAAAAA' + b'AAAAgD5bSABAE6qy2KnABfyvgQS4qE04VzIAADTDOAIAAAAAAAAAAAAAfXadZPlUBgCgxwwk' + b'wAWEz90qXDdKAAA0xzgCAAAAAAAAAAAAAH2XSgBAQ+4k4EIMJECDTp+3X5UAAGiWcQQAAAAA' + b'AAAAAAAA+m4uAQANOUjABRlIgAaEz9ksXGslAACaZxwBAAAAAAAAAAAAgL77kmR5IgMADdhL' + b'wIUdBxI2MkAcp2GEXThXagAANM84AgAAAAAAAAAAAABDMJcAgAYcJKAFfhlny104xqGgRoYR' + b'AAAuzzgCAAAAAAAAAAAAAEOQSgBAA/YS0BI3x7/H08vcwCeFz9Lx/0/sRoYRAAAuyjgCAAAA' + b'AAAAAAAAAEMwlwCABjxIQItMwvm/cba8lQI+7vQZ+tfIMAIAwMUZRwAAAAAAAAAAAABgCK6S' + b'LDeQAEBUVVk8qEAL/XOcLXfhTKWAtwufmSSc7fEzpAYAQDsYRwAAAAAAAAAAAABgKIwjANCE' + b'OwlooZtw9uNsuZICzguflfT4mQnnixoAAO3xXxIAAAAAAAAAAAAAMBCpBAA04CABLXUVzq/j' + b'bLkI96Iqi50k8L3w+ZiGaz0yigAA0Ep/kwAAAAAAAAAAAACAgZgkWT6TAYDI9hLQ9u9E4fxr' + b'nC134aRywL9HEZJwVqd/hxtGAABoqf+SAAAAAAAAAAAAAIABmY+8tApAXJ4zdMXN6Hkk4S7c' + b'q6osdpIwNMdRhHDdns6VIgAA7WYcAQAAAAAAAAAAAIAhOY4jrGQAIKIHCeiYv44krKuy2EpC' + b'34W/9zRci3B+UQMAoDuMIwAAAAAAAAAAAAAwJNdJlk8P5T8epAAghqos9uNsKQRddBxJuAl/' + b'v4/hXoezCX/PB1noi/C3PRs9DyIcB9MmigAAdI9xBAAAAAAAAAAAAACGJg1nIwMAEd2Hcy0D' + b'HXV8afyfxzPOll/Dva3KYisLXRP+fpPTd//jMYgAANADxhEAAAAAAAAAAAAAGJrji1EbGQCI' + b'6GFkHIF++OV4xtnyKdzHgQRDCbRS+BtdjJ5HEGb+/QsA0F/GEQAAAAAAAAAAAAAYmi8SABDZ' + b'3vOGnrkafRtKOP7n38PZjZ7HEh7koSGz8Pe3e+G/n4YzkQcAoP+MIwAAAAAAAAAAAAAwOEmW' + b'zw/lP/zqMQCx7ML5VQZ67Mvp/HOcLR9Pf/PHUZBdVRZ7eYjkONJxIwMAwHAZRwAAAAAAAAAA' + b'AABgiObhGEcAIJYHCRiQSTi/nM5onC2P193oeSzh4XTvq7I4SAUAAHyGcQQAAAAAAAAAAAAA' + b'hiiVAIBYqrJ4GGfLp9Hzr5zDEN2czp9On4k/BhP+eg7hM7OXDAAAOMc4AgAAAAAAAAAAAABD' + b'NEmyfHYo/+FFPABiOT5jbmSAP12NXhhNOBpnyz/+8XH0PJjw18/R4cz/biotAAAMg3EEAAAA' + b'AAAAAAAAAIZqPnp+4Q4AYtiNjCPAe01O5w8+QwAAwJ/+JgEAAAAAAAAAAAAAAzWXAICIDPAA' + b'AABAjYwjAAAAAAAAAAAAADBU10mWJzIAEIlxBAAAAKiRcQQAAAAAAAAAAAAAhmwuAQAxVGXx' + b'EK4nJQAAAKAexhEAAAAAAAAAAAAAGLJUAgAi2ksAAAAA9TCOAAAAAAAAAAAAAMCQzSUAIKKd' + b'BAAAAFAP4wgAAAAAAAAAAAAADNlVkuUzGQCIZCcBAOD7BQDUwzgCAAAAAAAAAAAAAEM3lwCA' + b'SPYSAAAAQD2MIwAAAAAAAAAAAAAwdMYRAIiiKotDuO6VAAAAgM8zjgAAAAAAAAAAAADA0F0n' + b'WZ7IAEAkewkAAADg84wjAAAAAAAAAAAAAMBoNJcAgEh2EgAANTpIAMBQGUcAAAAAAAAAAAAA' + b'gNEolQCASHYSAAA12ksAwFAZRwAAAAAAAAAAAACA0WguAQAxVGXxEK5HJQAAAOBzjCMAAAAA' + b'AAAAAAAAwGh0lWT5TAYAItlJAAAAAJ9jHAEAAAAAAAAAAAAAns0lACCSnQQAQE0eJABgqIwj' + b'AAAAAAAAAAAAAMCzVAIAItlJAADUoSqLBxUAGCrjCAAAAAAAAAAAAADw7CbJ8kQGAOp2eonx' + b'UQkAAAD4OOMIAAAAAAAAAAAAAPBNKgEAkewkAAA+ydgSAINmHAEAAAAAAAAAAAAAvplLAEAk' + b'OwkAgE96kACAITOOAAAAAAAAAAAAAADfpBIAEMlWAgAAAPg44wgAAAAAAAAAAAAA8M0kyfKp' + b'DADUrSqLQ7julQAAPmEvAQBDZhwBAAAAAAAAAAAAAL43lwCASHYSAACfcJAAgCEzjgAAAAAA' + b'AAAAAAAA30slACCSrQQAwCc8SADAkBlHAAAAAAAAAAAAAIDvpRIAEENVFrtwPSkBAHzQgwQA' + b'DJlxBAAAAAAAAAAAAAD43lWS5akMAESykwAA+KCDBAAMmXEEAAAAAAAAAAAAAPhRKgEAkWwl' + b'AAA+oiqLvQoADJlxBAAAAAAAAAAAAAD4USoBAJEYRwAAPuJJAgCGzjgCAAAAAAAAAAAAAPzo' + b'JsnyRAYA6laVxSFc90oAAO+0lwCAoTOOAAAAAAAAAAAAAAAvSyUAIJKNBADAOx0kAGDojCMA' + b'AAAAAAAAAAAAwMtSCQCIZCsBAPBOewkAGDrjCAAAAAAAAAAAAADwslQCAGKoyuIhXPdKAADv' + b'8CABAENnHAEAAAAAAAAAAAAAXnadZHkiAwCR7CQAAN7hQQIAhs44AgAAAAAAAAAAAAC8bi4B' + b'AJFsJAAA3mEvAQBDZxwBAAAAAAAAAAAAAF6XSgBADFVZHF9wfFQCAHiDp/Dd4SADAENnHAEA' + b'AAAAAAAAAAAAXpdKAEBEWwkAgDfYSwAAxhEAAAAAAAAAAAAA4GcmSZZPZQAgko0EAMAbGEcA' + b'gJFxBAAAAAAAAAAAAAA4J5UAgBiqsji+6PioBABwxoMEAGAcAQAAAAAAAAAAAADOSSUAIKK1' + b'BADAGXsJAMA4AgAAAAAAAAAAAACck0oAQERbCQCAM4wjAMDIOAIAAAAAAAAAAAAAnDNJsnwq' + b'AwAxVGXxEK57JQCAVzyG7wsHGQDAOAIAAAAAAAAAAAAAvEUqAQARrSUAAF6xlwAAnhlHAAAA' + b'AAAAAAAAAIDzUgkAiGgrAQDwCuMIAHBiHAEAAAAAAAAAAAAAzptLAEAsVVkcwvVVCQDgBTsJ' + b'AOCZcQQAAAAAAAAAAAAAOO8qyfKZDABEtJUAAHjBXgIAeGYcAQAAAAAAAAAAAADeJpUAgFiq' + b'sjiOIzwqAQD8xWP4jnCQAQCeGUcAAAAAAAAAAAAAgLeZSQBAZBsJAIC/2EsAAN8YRwAAAAAA' + b'AAAAAACAt0klACCyjQQAwF/sJACAb4wjAAAAAAAAAAAAAMDbTJIsn8oAQCxVWTyE63clAICT' + b'nQQA8I1xBAAAAAAAAAAAAAB4u1QCACLbSAAAHFVlsVcBAL4xjgAAAAAAAAAAAAAAb5dKAEBM' + b'VVlsw/WoBAAM3p0EAPA94wgAAAAAAAAAAAAA8HapBAA0YCMBAAzeTgIA+J5xBAAAAAAAAAAA' + b'AAB4u0mS5VMZAIhsLQEADN5OAgD4nnEEAAAAAAAAAAAAAHifVAIAYqrK4hCur0oAwKC/D+xU' + b'AIDvGUcAAAAAAAAAAAAAgPdJJQCgAWsJAGCw7iQAgB8ZRwAAAAAAAAAAAACA90klACC2qiz2' + b'Iy9GAsBQbSUAgB8ZRwAAAAAAAAAAAACA95kkWZ7IAEADNhIAwCDtJACAHxlHAAAAAAAAAAAA' + b'AID3SyUAILaqLDbhelQCAAblKXwH2MsAAD8yjgAAAAAAAAAAAAAA75dKAEBDVhIAwKBsJQCA' + b'lxlHAAAAAAAAAAAAAID3SyUAoCHHFySfZACAwdhJAAAvM44AAAAAAAAAAAAAAO93nWR5IgMA' + b'sVVlcQjXWgkAGIytBADwMuMIAAAAAAAAAAAAAPAxMwkAaMhxHOFJBgDovbvTMBIA8ALjCAAA' + b'AAAAAAAAAADwMakEADTh9JLkWgkA6L2tBADwOuMIAAAAAAAAAAAAAPAxqQQANMg4AgD0n3EE' + b'APgJ4wgAAAAAAAAAAAAA8DE3EgDQlKosDuH6qgQA9NZ9eN4/yAAArzOOAAAAAAAAAAAAAAAf' + b'lGT5TAUAGrSSAAB6ayMBAPyccQQAAAAAAAAAAAAA+LhUAgCacvo16a9KAEAvbSUAgJ8zjgAA' + b'AAAAAAAAAAAAH5dKAEDDVuE8yQAAvXJ/GkECAH7COAIAAAAAAAAAAAAAfNxMAgCadHpxcq0E' + b'APTKRgIAOM84AgAAAAAAAAAAAAB83CTJ8qkMADTsOI7wJAMA9MZWAgA4zzgCAAAAAAAAAAAA' + b'AHzOTAIAmlSVxWH0PJAAAHTfXXi2P8gAAOcZRwAAAAAAAAAAAACAz0klAOACjuMITzIAQOdt' + b'JACAtzGOAAAAAAAAAAAAAACfk0oAQNOqsjiEa6UEAHTacehoKwMAvI1xBAAAAAAAAAAAAAD4' + b'nGsJALiEqizW4XpUAgA6a3saPAIA3sA4AgAAAAAAAAAAAAB8UpLlqQoAXMitBADQWRsJAODt' + b'jCMAAAAAAAAAAAAAwOelEgBwCVVZbMN1pwQAdM5jeI7vZACAtzOOAAAAAAAAAAAAAACfN5MA' + b'gAtaSQAAnbOWAADexzgCAAAAAAAAAAAAAHyecQQALub0q9NflQCATtlIAADvYxwBAAAAAAAA' + b'AAAAAD5vkmT5VAYALug2nCcZAKATvlZlcZABAN7HOAIAAAAAAAAAAAAA1GMmAQCXcnrBcq0E' + b'AHSCZzYAfIBxBAAAAAAAAAAAAACoRyoBAJdUlcUqXI9KAECr3YVn9l4GAHg/4wgAAAAAAAAA' + b'AAAAUI+ZBAC0wEICAGi1jQQA8DHGEQAAAAAAAAAAAACgHjcSAHBpVVnswvW7EgDQSo/hWb2R' + b'AQA+xjgCAAAAAAAAAAAAANQkyfKZCgC0wCKcJxkAoHXWEgDAxxlHAAAAAAAAAAAAAID6pBIA' + b'cGlVWRzCtVICAFrlOFy0kQEAPs44AgAAAAAAAAAAAADUZyYBAG1QlcXxl6nvlACA1lifBowA' + b'gA8yjgAAAAAAAAAAAAAA9TGOAECb3EoAAK3wFM5aBgD4HOMIAAAAAAAAAAAAAFCf6yTLExkA' + b'aIOqLPbh+rsSAHBx6/BcPsgAAJ9jHAEAAAAAAAAAAAAA6jWTAIC2qMpiFa57JQDgYp7CWcsA' + b'AJ9nHAEAAAAAAAAAAAAA6pVKAEDLLCQAgItZV2VxkAEAPs84AgAAAAAAAAAAAADUayYBAG1S' + b'lcU+XH9XAgAa9xTOWgYAqIdxBAAAAAAAAAAAAACol3EEAFqnKotVuO6VAIBGrcMz+CADANTD' + b'OAIAAAAAAAAAAAAA1GuSZHkiAwAttJAAABrzFM5aBgCoj3EEAAAAAAAAAAAAAKjfTAIA2qYq' + b'i324/kcJAGjEOjx7DzIAQH2MIwAAAAAAAAAAAABA/VIJAGijqiyOv2B9pwQARPUYnrkrGQCg' + b'XsYRAAAAAAAAAAAAAKB+qQQAtNginCcZACCalQQAUD/jCAAAAAAAAAAAAABQv5kEALRVVRYP' + b'o+eBBACgfnfhWbuRAQDqZxwBAAAAAAAAAAAAAOp3lWT5VAYA2qoqi224flMCAGq3kgAA4jCO' + b'AAAAAAAAAAAAAABxzCQAoOVW4dzLAAC1+VqVxU4GAIjDOAIAAAAAAAAAAAAAxGEcAYBWq8ri' + b'EK5FOE9qAMCnHZ+ntzIAQDzGEQAAAAAAAAAAAAAgjlQCANquKov9yIucAFCH1Wl4CACIxDgC' + b'AAAAAAAAAAAAAMQxkwCALqjKYhOur0oAwIfdh+fpWgYAiMs4AgAAAAAAAAAAAADEcZVk+VQG' + b'ALqgKotFuO6VAIAPWUgAAPEZRwAAAAAAAAAAAACAeKYSANAh83CeZACAd/mtKou9DAAQn3EE' + b'AAAAAAAAAAAAAIgnlQDg/9m7m5w21iwMwE6UecwKUncFMStIIXne9A7MDows5jAuRQ07wDsI' + b'8xo4O7B3ACsoewXcU3Hd2+m++SEE1+/zSEfH43fwHTGoF7qiyLP70b4gAQB4moeYSzEAQD2U' + b'IwAAAAAAAAAAAADA4UxEAECXFHm2inUuCQB4klnczq0YAKAeyhEAAAAAAAAAAAAA4HCUIwDQ' + b'OUWeXcdaSgIAfuimKhUCAGqiHAEAAAAAAAAAAAAADufdeLoYiwGADprHbMQAAN/0EHMpBgCo' + b'l3IEAAAAAAAAAAAAADisiQgA6Joiz7ax0pidNADgH2bVrQQAaqQcAQAAAAAAAAAAAAAOKxUB' + b'AF2kIAEAvukqbuRKDABQP+UIAAAAAAAAAAAAAHBYExEA0FVFnq1jzSQBAF9s4jZeigEAmqEc' + b'AQAAAAAAAAAAAAAOKxEBAF1W5NmnWGeSAGDgdiOFQQDQKOUIAAAAAAAAAAAAAHBY70UAQNcV' + b'eXYbaykJAAZsHvdwLQYAaI5yBAAAAAAAAAAAAAA4sPF0kUoBgK4r8mw2UpAAwDAtq6IgAKBB' + b'yhEAAAAAAAAAAAAA4PAmIgCgJ+YxGzEAMCCb6v4BAA1TjgAAAAAAAAAAAAAAh5eIAIA+KPJs' + b'GysdKUgAYBh2MbPq/gEADVOOAAAAAAAAAAAAAACHNxEBAH3xVUHCgzQA6LmyGGEtBgBoB+UI' + b'AAAAAAAAAAAAAHB4H0QAQJ9UBQmno/1/1AaAPrqJe/dJDADQHsoRAAAAAAAAAAAAAKAG4+ki' + b'kQIAfVL9J+10pCABgP65izs3FwMAtItyBAAAAAAAAAAAAACox0QEAPSNggQAemgTMxMDALSP' + b'cgQAAAAAAAAAAAAAqIdyBAB6SUECAD1S3rJZ3LatKACgfZQjAAAAAAAAAAAAAEA9lCMA0FsK' + b'EgDoibS6aQBACylHAAAAAAAAAAAAAIB6KEcAoNeqj0lPJQFAR50pRgCAdlOOAAAAAAAAAAAA' + b'AAD1eCcCAPquyLNVrDNJANAxV3HDbsUAAO2mHAEAAAAAAAAAAAAAajKeLlIpANB31celxzE7' + b'aQDQAcu4XZdiAID2U44AAAAAAAAAAAAAAPWZiACAISjybB0rHSlIAKDdymKEmRgAoBuUIwAA' + b'AAAAAAAAAABAfRIRADAUChIAaLmNYgQA6BblCAAAAAAAAAAAAABQn4kIABgSBQkAtNSmuk8A' + b'QIcoRwAAAAAAAAAAAACA+ihHAGBwvipIeJAGAC3wpRgh7tNWFADQLcoRAAAAAAAAAAAAAKA+' + b'b8fTRSIGAIamKkgoS4I20gCgQYoRAKDDlCMAAAAAAAAAAAAAQL0SEQAwRNWHqGnMZ2kA0ADF' + b'CADQccoRAAAAAAAAAAAAAKBeqQgAGKryg9SY8hYupQFAjRQjAEAPKEcAAAAAAAAAAAAAgHol' + b'IgBg6Io8m8W6kgQANVCMAAA9oRwBAAAAAAAAAAAAAOqViAAAvhQkXMY6i9lJA4ADUYwAAD2i' + b'HAEAAAAAAAAAAAAA6vVBBACwV+TZbax0pCABgJenGAEAekY5AgAAAAAAAAAAAADUbDxdJFIA' + b'gL0iz9axytu4kQYAL0QxAgD0kHIEAAAAAAAAAAAAAKhfIgIA+K/q49U0ZikNAH7TMu7KRDEC' + b'APSPcgQAAAAAAAAAAAAAqF8qAgD4X+VHrDGz+HkuDQCeaVndEgCgh5QjAAAAAAAAAAAAAED9' + b'EhEAwLcVeXYd6zhmJw0AfsG5YgQA6DflCAAAAAAAAAAAAABQv0QEAPB9RZ6tq3v5WRoAPMFZ' + b'Va4DAPSYcgQAAAAAAAAAAAAAqN8HEQDAjxV5to1J4+eVNAD4jl3McdyLW1EAQP8pRwAAAAAA' + b'AAAAAACABoyni0QKAPBzRZ5dxjoZ7T+ABYC/bGLSuBNrUQDAMChHAAAAAAAAAAAAAIBmJCIA' + b'gKcp8mxV3c7P0gAg3I0UIwDA4LwRAQAAAAAAAAAAAAA0Io1ZiQEAnqbIs215P4+mF/PY/5EI' + b'wGDdxE2YiwEAhue1CAAAAAAAAAAAAACgEYkIAODXFXl2Hes4ZiMNgEHZxZwpRgCA4VKOAAAA' + b'AAAAAAAAAADNSEQAAM9T5Nk6VhpzIw2AQSgLcdJ4/29FAQDDpRwBAAAAAAAAAAAAAJrxQQQA' + b'8HxFnm2r/x5+EvMgEYDeuhvtixHWogCAYVOOAAAAAAAAAAAAAAANGU8XYykAwO8p8mwVaxJz' + b'Iw2A3jmPd/60LMQRBQCgHAEAAAAAAAAAAAAAmjMRAQD8vvKj2Zh5/DyJeZAIQOeVb/lxvO3X' + b'ogAA/qIcAQAAAAAAAAAAAACaoxwBAF5QkWer6r7eSAOgs5blWx5v+loUAMDX3ogAAAAAAAAA' + b'AAAAABqTiAAAXlaRZ9tY86PpxW3sct5LBaATduX7He/4rSgAgG95LQIAAAAAAAAAAAAAaMxE' + b'BABwGOV/HI8pb+35aP/BLQDt9bn8+0gxAgDwI8oRAAAAAAAAAAAAAKA5iQgA4LCKPLse7QuJ' + b'7qQB0Ern8VanMfeiAAB+RDkCAAAAAAAAAAAAADTnnQgA4PDKD25jTuPnScyDRABaYRNzXJXY' + b'AAD81KvHx0cpAIMzni48fgAAAAAAAAAAALTFyTb/uBIDANTnaHpxGWse81YaAI24KvLsUgwA' + b'wK94LQIAAAAAAAAAAAAAaNRYBABQr+qD3CRmKQ2AWn2O+UMxAgDwHMoRAAAAAAAAAAAAAKBZ' + b'ExEAQP2KPNvGzOLn8Wj/sS4Ah7OLOY93N425FwcA8BzKEQAAAAAAAAAAAACgWcoRAKBBRZ6t' + b'y4914+e/Yx4kAvDi7mKSeGuvRQEA/I43IgAAAAAAAAAAAACARo1FAADNK/LsU6xPR9OLWezy' + b'A963UgH4LWXhzCze15UoAICX8FoEAAAAAAAAAAAAANCoDyIAgPYo8uw2VhJzFbOTCMAvK9/O' + b'q3hPE8UIAMBLevX4+CgFYHDG04XHDwAAAAAAAAAAgDY52uYft2IAgJYd6OnFONa8mrcSAfip' + b'u/LNLPLsXhQAwEt7LQIAAAAAAAAAAAAAaNxEBADQPkWebWMu42cSczXa/zd0AP5pE3MSb+ap' + b'YgQA4FCUIwAAAAAAAAAAAABA85QjAECLKUkA+K6HmLN4IycxK3EAAIekHAEAAAAAAAAAAAAA' + b'mjcWAQC0n5IEgL/tqnewLEW4FQcAUIc3IgAAAAAAAAAAAACAxqUiAIDuKEsSYl0eTS+uY8+r' + b'eSsZYCBuyjewegsBAGqjHAEAAAAAAAAAAAAAmjcWAQB0z/+VJJyWv2PeSQboqeVoX4pwLwoA' + b'oAmvHh8fpQAMzni68PgBAAAAAAAAAADQKtv84yspAED3HU0vZrHmMe+lAfSEUgQAoBWUIwCD' + b'pBwBAAAAAAAAAACAFvpjm3+8FwMA9MPR9CId7UsS/iUNoKOUIgAArfKnAOzdTW4bZ7YG4OrA' + b'c1WtwOwViL0CMcA3j+8KLK8gCogatzI2Cq1eQZgVtDMvoOkVRF7BlVbgTyvIPdWk2rqKbMuS' + b'SNXP8wAH55PkzuCNxbDJ4lsvRAAAAAAAAAAAAAAAvTCLuRADAIzDx/btOta6SnX33/iuJOE4' + b'5kAywAAoRQAAeuk7EQAAAAAAAAAAAABAL8xFAADj0324OKYrR5jFvIm5lArQU10pwl/jMetY' + b'MQIA0EcvRAAAAAAAAAAAAAAAvVCKAADG62P7NsdadVOlehH7OOa1ZIBndhVz1j02KUQAAPpO' + b'OQIAAAAAAAAAAAAA9MNCBAAwDR/bt+tY6yrVJ8WmJKHbLyUD7NFlsSlsOduWtwAA9J5yBAAA' + b'AAAAAAAAAADoh1IEADAt2w8kd3dsP6tSvSg2RQmvYg6kA+zIh2JTiLASBQAwNMoRAAAAAAAA' + b'AAAAAKAfDkUAANP1sX27jrWuUt0VJnUFCccxR5IBnsivMavtYw0AwCD95Y8//pACMDllWnrw' + b'AwAAAAAAAAAAoI+q3DZZDADAf54YpHpWbEoSunkpEeAbXcWcFZtShAtxAABDpxwBmCTlCAAA' + b'AAAAAAAAAPTU97lt1mIAAG6rUj0vPhUlHEgE+IL3xaYQYSUKAGBMXogAAAAAAAAAAAAAAHpj' + b'JgIA4C4f27fnsU66qVL9Kvb1KEoAOlcxq5izeLy4EAcAMEbKEQAAAAAAAAAAAACgP2YiAAC+' + b'5mP79l2sbgpFCTB5v3WPB/G4sBIFADB2yhEAAAAAAAAAAAAAoD/mIgAAvoWiBJiky5izYlOK' + b'cCEOAGAqlCMAAAAAAAAAAAAAQH+UIgAAHupWUUJXunRcbIoSXkoHBu9q+/t9Fr/r5+IAAKbo' + b'L3/88YcUgMkp09KDHwAAAAAAAAAAAH10ldtGQQIA8KSqVM+KTUlCN0cSgeH8/4NiU4jwblt+' + b'AgAwacoRgElSjgAAAAAAAAAAAEBf5bb5ixQAgF2pUt0VMXUlCYvtPpAK9M5vxadShCwOAIAN' + b'5QjAJClHAAAAAAAAAAAAoMe+z22zFgMAsA9VqufFp6KEI4nAs1GIAADwFS9EAAAAAAAAAAAA' + b'AAAAANP0sX17Hqubs+7rKtVdScJiO4cSgp25KjZlCOtCIQIAwL0oRwAAAAAAAAAAAACAflkU' + b'mw9IAQDs3cf27X/uXN+dq1SXxaeihG6UJcDjXG5/v9bb3zUAAL6BcgQAAAAAAAAAAAAAAADg' + b'T7Z3sleWAI/zW7EpP3sXv1MX4gAAeDjlCAAAAAAAAAAAAADQLwsRAAB99JmyhHnxqSzhSEpQ' + b'vC82ZQjr+J1ZiwMA4OkoRwAAAAAA9uHn3DanYgC+pEzLxRd+vLjH97qLrg4kCQAAAAAAAPux' + b'LUtYb+c/qlRflyXMt3MoKUZOGQIAwJ4oRwAAAAAAAHoht836Cz9ef8s/q0zL6zvUXFvcce7+' + b'jAuxAAAAAOgjd1wGAAbrY/v2PNb59ddVqq/fu1sUnwoTXkqKgbrc/v1eF5syhHORAADsz1/+' + b'+OMPKQCTU6alBz8AAADYr59z25yKAeirMi27C7C6i7Jm27lZruBCdAAAAACeQ5XbJosBABjl' + b'E50/FybMCsXm9M9VcaMIoTt/bN96jg4A8IxeiAAAAAAAAJi63DZfvZtHmZaL7bHb1xdrzQp3' + b'tQEAAABgN7rXn9ZiAADGaPsB8/Xt5ztVqhfF5j24+Y05kBh78CHmovhUhqAIAQCgh5QjAAAA' + b'AAAA3ENum/X2uL79szItu4uyusKERfGpOMGFWgAAAAA8RikCAGBqPrZv17e/V6X65vtvs8J7' + b'cTzOzRKEbi7i7925WAAAhkE5AgAAAAAAwCPltrm+WGZ98/tlWt51odaRxAAAAAC4h+61pHdi' + b'AACm7mP7Nheb9+HWN79/ozRhtp1FsSmYOpTa5HUFCN3fm/Pt7v7udCUIF6IBABg25QgAAAAA' + b'AAA7ktvmzgu1yrScFZ9KExbF5mKtlxIDAAAAAACA+7lRmvAnVapnxafy8q4wYbH9kSLzcXi/' + b'3dflBxfXowABAGDclCMAAAAAAADsWW6bi2Jzcc5/7/xXpuX1nW0W2+nOB9ICAAAAmKyFCAAA' + b'Hmb7Aflu1nf9vEr14tZzruutPOH5XBWbsoOi+FR2UBSfChBy/Hs9FxMAwLQpRwAAAAAAAOiB' + b'3DbXd7ZZX3+vTMtZ8akooduHkgIAAAAAAIDH+di+XW+P67t+XqW6e3+uKzc/Ljbv1Xmf7n7e' + b'3/q6ew/0dqHBzcwvtkUWAABwL8oRAAAAAAAAeiq3zUWs1c3vlWm5KDZFCd24cw0AAADAeHnt' + b'BwDgmXxs315/oH99/b0q1bNYs1t/9K7vDc1dBQZf/PM38gEAgL1SjgAAAAAAADAguW3WxY2L' + b'sJQlAAAAAAAAwO59bN9exLqQBAAAPB/lCAAAAAAAAAP2mbKEV8WmLOFQQgAAAADD1b3Ws339' + b'BwAAAABg8pQjAAAAAAAAjMjNsoQyLcviU1FCtw8kBAAAAAAAAAAAwBApRwAAAAAAABip3DY5' + b'1mo7XVnCPNZxsSlLOJQQAAAAQO91r+esxQAAAAAAoBwBAAAAAABgMnLbnMc66c5lWs5ivSo2' + b'RQk/SAcAAACgl0oRAAAAAABsKEcAAAAAAACYoNw2F7HOuinTsrvI/tV2FCUAAAAA9MdcBAAA' + b'AAAAG8oRAAAAAAAAJi63TY616kZRAgAAAECvlCIAAAAAANhQjgAAAAAAAMB/faYo4TjmSDoA' + b'AAAAezcTAQAAAADAhnIEAAAAAAAA7nSrKGFWbIoSTmJeSgcAAABgL7wOAwAAAACw9Z0IAAAA' + b'AAAA+JrcNhcxZzGz+PJvMb/GXEkGAAAAYLfKtCylAAAAAABQFC9EAAAAAAAAwLfIbXMe67g7' + b'l2l5vD0fSQYAAABgJ+YxazEAAAAAAFP3nQgAAAAAAAB4qNw2q5hFHP8a88+YK6kAAAAAPKlS' + b'BAAAAAAAyhEAAAAAAAB4ArltLmJOYrqL9d/EfJAKAAAAwJOYiwAAAAAAQDkCAAAAAAAATyy3' + b'zSqmu2j/+5hfJQIAAAAAAAAAAMBjKUcAAAAAAABgJ3LbrGOO4/jXmJ9jrqQCAAAA8M3mIgAA' + b'AAAAUI4AAAAAAADAjuW2uYg5jeMs5qeYS6kAAAAA3FspAgAAAAAA5QgAAAAAAADsSW6bHHMW' + b'M4sv3xRKEgAAAADuYyYCAAAAAADlCAAAAAAAADyD3DarbUnC/8S8lwgAAADAZ70UAQAAAACA' + b'cgQAAAAAAACeUW6bdzGLOH5fKEkAAAAAuFOZlqUUAAAAAICpU44AAAAAAADAs8tts1aSAAAA' + b'APBZcxEAAAAAAFOnHAEAAAAAAIDeuFWScCkRAAAAAAAAAAAAOsoRAAAAAAAA6J1tScIsjm8K' + b'JQkAAAAAcxEAAAAAAFOnHAEAAAAAAIDeym2z2pYk/BRzJREAAABgokoRAAAAAABTpxwBAAAA' + b'AACA3sttcxZrFvOzNAAAAIAJmokAAAAAAJg65QgAAAAAAAAMQm6bHHMax7/G/CYRAAAAYEJm' + b'IgAAAAAApk45AgAAAAAAAIOS2+Yi5lUcv4+5lAgAAAAAAAAAAMD4KUcAAAAAAABgkHLbrGNm' + b'cfwp5koiAAAAwIgdiQAAAAAAmDrlCAAAAAAAAAxabpuzWLOY36QBAAAAAAAAAAAwTsoRAAAA' + b'AAAAGLzcNjnmVRy/j7mUCAAAADA2ZVrOpAAAAAAATJlyBAAAAAAAAEYjt8061jzmZ2kAAAAA' + b'IzMTAQAAAAAwZcoRAAAAAAAAGJXcNjnmNI5/i/kgEQAAAAAAAAAAgOFTjgAAAAAAAMAo5bY5' + b'j5nH8WdpAAAAACOwEAEAAAAAMGXKEQAAAAAAABi13Dansf4W80EaAAAAAAAAAAAAw6QcAQAA' + b'AAAAgNHLbXMeM4/jz9IAAAAABqoUAQAAAAAwZcoRAAAAAAAAmIzcNqexvo+5lAYAAAAwMHMR' + b'AAAAAABTphwBAAAAAACAScltsy42Hyb4VRoAAAAAAAAAAADDoBwBAAAAAACAycltk2OO4/gm' + b'5koiAAAAwADMRQAAAAAATJlyBAAAAAAAACYrt82q2Hyw4IM0AAAAgJ47EAEAAAAAMGXKEQAA' + b'AAAAAJi03DYXMV1Bwj+lAQAAAAAAAAAA0E/KEQAAAAAAAKD4T0nCSaw3MVfSAAAAAPqoTMuF' + b'FAAAAACAqVKOAAAAAAAAAFu5bVaxFjEfpAEAAAAAAAAAANAfyhEAAAAAAADghtw258WmIOE3' + b'aQAAAAA9U4oAAAAAAJgq5QgAAAAAAABwS26bHPMqjj9LAwAAAOiRuQgAAAAAgKlSjgAAAAAA' + b'AACfkdvmNNabmCtpAAAAAAAAAAAAPB/lCAAAAAAAAPAFuW1WsRaFggQAAADg+c1EAAAAAABM' + b'lXIEAAAAAAAA+IrcNufF5sMHH6QBAAAAPKOZCAAAAACAqVKOAAAAAAAAAPeQ2ybHWsT8Jg0A' + b'AAAAAAAAAID9Uo4AAAAAAAAA99QVJMS8iuOv0gAAAACewUwEAAAAAMBUKUcAAAAAAACAb5Tb' + b'5jjWT5IAAAAA9uylCAAAAACAqVKOAAAAAAAAAA+Q2+Ys1htJAAAAAAAAAAAA7J5yBAAAAAAA' + b'AHig3DarQkECAAAAsEdlWs6kAAAAAABM0QsRAAAAAAAAwMN1BQllWp7HcR1zIBEAAABgx2Yx' + b'F2IAALi/KtWLO769+ML/ZCG1Xjr/2L49EQMAwHQpRwAAAAAAAIBHym1zXqblolCQAAAAAAAA' + b'sDNVqmfFpiyq2O67zp0jaQEAwPgoRwAAAAAAAIAnoCABAAAA2JOZCACAMapSPY9Vxlzv2Y3n' + b'PsoOAAAA5QgAAAAAAADwVBQkAAAAAHswEwEAMERVqmfb5zK3SxC67X0VAADgq5QjAAAAAAAA' + b'wBNSkAAAAAAAAExZlepFsSlB6Ga+3YeSAQAAHks5AgAAAAAAADwxBQkAAADADpUiAAD6oEr1' + b'dfFBtxfb80vJAAAAu6IcAQAAAAAAAHZAQQIAAACwI3MRAAD7VqV6sX0eMtvuI6kAAAD7phwB' + b'AAAAAAAAdkRBAgAAAAAAMDRVqrvyg+tZxBxKBQAA6APlCAAAAAAAALBDChIAAACAJ1aKAAB4' + b'SlWqF8WmBKGbrhDB+xkAAEAvKUcAAAAAAACAHdsWJLyK47+lAQAAADySOzcDAA9WpborWlrc' + b'GM8tAACAwVCOAAAAAAAAAHuQ22ZdpuWbOP4iDQAAAAAAYF+qVHcFzotCGQIAADBwyhEAAAAA' + b'AABgT3LbrMq07I4KEgAAAAAAgJ2oUj0vNkUIXSnCkUQAAICxUI4AAAAAAAAAe7QtSOguSvxR' + b'GgAAAMBDdK8t5LY5lwQA0KlSXRabIoTFdh9IBQAAGCPlCAAAAAAAALBnuW1OyrTsLlR8LQ0A' + b'AADgAUoRAMC0VameFZsihG6OJAIAAEyBcgQAAAAAAAB4Brltjsu0nBUuWAQAAAAAAO6hSvU8' + b'1nHMIuZQIgAAwNQoRwAAAAAAAIDn093NaV24gBEAAAD4NjMRAMA03ChE6N5TeCkRAABgypQj' + b'AAAAAAAAwDPJbZPLtOwuZjyPOZAIAAAAcE8zEQDAeClEAAAAuJtyBAAAAAAAAHhGuW0uyrRc' + b'xPF3aQAAAAAAwDRVqZ4VmzKE45hDiQAAAPyZcgQAAAAAAAB4Zrltzsu0fBPHX6QBAAAAAADT' + b'UKW6LD4VIhxJBAAA4MuUIwAAAAAAAEAP5LZZlWm5iONraQAAAABfsRABAAxXlep5rJNiU4xw' + b'IBEAAID7UY4AAAAAAAAAPZHb5rhMy1nh7lAAAAAAADAqVarLWMfFphThpUQAAAC+nXIEAAAA' + b'AAAA6JfuLlEXhTtFAQAAAADA4FWpnhebQoTX0gAAAHic70QAAAAAAAAA/ZHbJsdaSAIAAAD4' + b'glIEANBvVaqPY87j+HuhGAEAAOBJvBABAAAAAAAA9Etum/MyLX+K4z+kAQAAANzhUAQA0D9V' + b'qmexjmNOYg4kAgAA8LSUIwAAAAAAAEAP5bY5K9NyEccfpAEAAAAAAP1VpXpebAoRXksDAABg' + b'd5QjAAAAAAAAQH8dx5zHvBQFAAAAAAD0S5XqRazTmCNpAAAA7N53IgAAAAAAAIB+ym2TY72S' + b'BAAAAHBbmZYzKQDA86hSfRzTlRv/u1CMAAAAsDfKEQAAAAAAAKDHctt0F1f+JAkAAADglpkI' + b'AGC/tqUIF3H8JeZQIgAAAPulHAEAAAAAAAB6LrfNWaz3kgAAAAAAgP27VYrwUiIAAADP44UI' + b'AAAAAAAAYBBexVzEHIgCAAAAAAB2rytFiHVaKEQAAADohe9EAAAAAAAAAP2X2ybHOpYEAAAA' + b'sDUTAQDsRleKEHMRx18KxQgAAAC9oRwBAAAAAAAABiK3zbtY/5QEAAAAUChHAIAnV6V6EbMu' + b'lCIAAAD00gsRAAAAAAAAwKCcxrwqXJQJAAAAAABPokr1PNZZzJE0gB08vpSS4Inlj+3bczEA' + b'MEXKEQAAAAAAAGBActvkMi2P4/hvaQAAAAAAwMNVqZ4Vm1Li19IAdkTxCrvwPmYhBgCm6DsR' + b'AAAAAAAAwLDktlnH+qckAAAAYNLcfRYAHqhKdRlzGsfurtuKEQAAAAZCOQIAAAAAAAAM02nM' + b'pRgAAABgsuYiAIBvV6X6uNiUIvw95kAiAAAAw6EcAQAAAAAAAAYot02OdSwJAAAAAAD4uirV' + b'85h1HH+JeSkRAACA4VGOAAAAAAAAAAOV22Yd61dJAAAAAADA3apUlzFncfw95kgiAAAAw6Uc' + b'AQAAAAAAAIbtJOZKDAAAAAAA8P9VqX4V6yLmR2kAAAAMn3IEAAAAAAAAGLDcNrnYFCQAAAAA' + b'0+LO1wDwGVWqZzHrOP4r5kAiAAAA46AcAQAAAAAAAAYut80q1ntJAAAAAAAwdVWqu0Lh80KR' + b'EAAAwOi8EAEAAAAAAACMwnHM/4oBAAAAAIApqlI9j3VWKEUAAAAYre9EAAAAAAAAAMOX2+Yi' + b'1s+SAAAAAABgaqpUn8b6vVCMAAAAMGrKEQAAAAAAAGA8ujtiXYoBAAAApqFMy5kUAJiyKtXz' + b'mPM4/l0aAAAA46ccAQAAAAAAAEYit02OdSoJAAAAmIyZCACYqirVJ7HWMYfSAAAAmAblCAAA' + b'AAAAADAiuW1Wsd5LAgAAAACAMapSXcas4/iPmAOJAAAATIdyBAAAAAAAABifUxEAAAAAADA2' + b'VapfxbqIOZIGAADA9ChHAAAAAAAAgJHJbbOO9askAAAAAAAYgyrVZcxZHP8VcyARAACAaVKO' + b'AAAAAAAAAON0KgIAAAAYvZkIABi7KtXzWOuYH6UBAAAwbcoRAAAAAAAAYIRy21zE+lkSAAAA' + b'MGozEQAwZlWqj4tNMcKhNAAAAHghAgAAAAAAABits5iTmANRAAAAAAAwFFWqy2LzGvdraQAA' + b'AHDtOxEAAAAAAADAOOW2ycXm4lEAAAAAABiEKtXzWOtCMQIAAAC3KEcAAAAAAACAEcttcxrr' + b'UhIAAAAAAPRdlepXxaYY4VAaAAAA3KYcAQAAAAAAAMbvVAQAAAAwSqUIABiLKtWnsf4VcyAN' + b'AAAA7qIcAQAAAAAAAEYut80q1qUkAAAAYHTmIgBg6KpUlzHv4vh3aQAAAPAlyhEAAAAAAABg' + b'Gk5FAAAAAABAn1SpnsVax/wgDQAAAL5GOQIAAAAAAABMQG6bVaxLSQAAAAAA0AdVquexzmMO' + b'pQEAAMB9KEcAAAAAAACA6TgVAQAAAAAAz61K9XGs32MOpAEAAMB9KUcAAAAAAACAichts4p1' + b'KQkAAAAAAJ5LleqzWL9IAgAAgG+lHAEAAAAAAACm5VQEAAAAMBpzEQAwJFWqV7F+lAQAAAAP' + b'oRwBAAAAAAAAJiS3zSrWpSQAAABgFA5EAMAQVKkuY87j+FoaAAAAPJRyBAAAAAAAAJieUxEA' + b'AAAAALAPXTFCrHXMoTQAAAB4DOUIAAAAAAAAMD3vYq7EAAAAAADALlWpnsc6LxQjAAAA8ASU' + b'IwAAAAAAAMDE5LbJsc4kAQAAAADArmyLEdYxL6UBAADAU1COAAAAAAAAANPUlSNciQEAAAAA' + b'gKd2oxjhQBoAAAA8FeUIAAAAAAAAMEG5bXKsd5IAAACAYSvTciYFAPpEMQIAAAC7ohwBAAAA' + b'AAAAputUBAAAADB4MxEA0BeKEQAAANgl5QgAAAAAAAAwUbltLmL9JgkAAAAAAB5LMQIAAAC7' + b'phwBAAAAAAAApu1MBAAAAAAAPIZiBAAAAPZBOQIAAAAAAABMWG6bdaxLSQAAAAAA8BCKEQAA' + b'ANgX5QgAAAAAAADAqQgAAAAAAPhWihEAAADYJ+UIAAAAAAAAwLuYKzEAAAAAAHBfihEAAADY' + b'N+UIAAAAAAAAMHG5bXKslSQAAABgkOYiAGDfFCMAAADwHJQjAAAAAAAAAJ0zEQAAAMAglSIA' + b'YJ8UIwAAAPBclCMAAAAAAAAARW6bi1jvJQEAAAAAwOdUqe5KeVaFYgQAAACegXIEAAAAAAAA' + b'4NpKBAAAAAAA3GVbjLCOOZQGAAAAz0E5AgAAAAAAAPAfuW1Wsa4kAQAAAADAHdaFYgQAAACe' + b'kXIEAAAAAAAA4KaVCAAAAAAAuKlK9apQjAAAAMAzU44AAAAAAAAA3LQSAQAAAAAA16pUn8V6' + b'LQkAAACem3IEAAAAAAAA4L9y25zH+iAJAAAAAACqVB/H+lESAAAA9IFyBAAAAAAAAOC2lQgA' + b'AABgMOYiAGAXqlQvYv0iCQAAAPpCOQIAAAAAAABw20oEAAAAMBilCAB4alWqu/Kdd5IAAACg' + b'T5QjAAAAAAAAAP9Pbpsc6zdJAAAAAABMT5XqrnhnFXMgDQAAAPpEOQIAAAAAAABwF3cEAwAA' + b'AACYpu714UMxAAAA0DfKEQAAAAAAAIA/yW2zinUlCQAAAACA6ahSfRbrSBIAAAD0kXIEAAAA' + b'AAAA4HPeiQAAAAAAYBqqVB/H+lESAAAA9JVyBAAAAAAAAOBzlCMAAAAAAExAlep5rDNJAAAA' + b'0GfKEQAAAAAAAIA75bbpyhGuJAEAAAAAMF5VqstYq5gDaQAAANBnyhEAAAAAAACAL3knAgAA' + b'AACAUTuLORQDAAAAfaccAQAAAAAAAPgS5QgAAADQb6UIAHioKtUnsV5LAgAAgCFQjgAAAAAA' + b'AAB8Vm6brhzhShIAAADQW+70DcCDVKmex/qHJAAAABgK5QgAAAAAAADA17wTAQAAAADAeFSp' + b'LmOtJAEAAMCQKEcAAAAAAAAAvkY5AgAAAADAuJzFHIoBAACAIVGOAAAAAAAAAHxRbpuuHOFK' + b'EgAAAAAAw1el+lWs15IAAABgaJQjAAAAAAAAAPexFgEAAAAAwLBVqZ7FWkkCAACAIVKOAAAA' + b'AAAAANzHOxEAAAAAAAzeKuZADAAAAAyRcgQAAAAAAADgPpQjAAAAAAAMWJXqk1hHkgAAAGCo' + b'lCMAAAAAAAAAX5XbJsf6IAkAAAAAgOGpUj2PdSoJAAAAhkw5AgAAAAAAAHBfKxEAAAAAAAzS' + b'KuZADAAAAAyZcgQAAAAAAADgvtYiAAAAAAAYlirVp7EOJQEAAMDQKUcAAAAAAAAA7iW3zXms' + b'S0kAAAAAAAxDlepZrBNJAAAAMAbKEQAAAAAAAIBvsRYBAAAAAMBgrGIOxAAAAMAYKEcAAAAA' + b'AAAAvsU7EQAAAEC/lGk5kwIAt1WpPol1JAkAAADGQjkCAAAAAAAA8C3WIgAAAIDemYkAgJuq' + b'VJexTiUBAADAmChHAAAAAAAAAO4tt02O9UESAAAAAAC9too5EAMAAABjohwBAAAAAAAA+Fbv' + b'RAAAAAAA0E9VqhexfpAEAAAAY6McAQAAAAAAAPhWaxEAAAAAAPTWSgQAAACMkXIEAAAAAAAA' + b'4JvktllLAQAAAACgf6pUn8Z6KQkAAADGSDkCAAAAAAAA8BDvRQAAAAAA0B9VqmexTiQBAADA' + b'WClHAAAAAAAAAB7inQgAAAAAAHrlLOZADAAAAIyVcgQAAAAAAADgIdYiAAAAAADohyrVi1g/' + b'SAIAAIAxU44AAAAAAAAAfLPcNudSAAAAAADojTMRAAAAMHbKEQAAAAAAAICHei8CAAAAAIDn' + b'VaX6ONahJAAAABg75QgAAAAAAADAQ61FAAAAAADwfKpUl7FOJQEAAMAUKEcAAAAAAAAAHmot' + b'AgAAAACAZ3US81IMAAAATIFyBAAAAAAAAOBBctuspQAAAAAA8DyqVJfFphwBAAAAJkE5AgAA' + b'AAAAAPAY70UAAAAAAPAsumKEAzEAAAAwFcoRAAAAAAAAgMc4FwEAAAAAwH5VqZ7F+rskAAAA' + b'mBLlCAAAAAAAAMBjrEUAAAAAALB3pyIAAABgapQjAAAAAAAAAI9xLgIAAAAAgP2pUj2L9VoS' + b'AAAATI1yBAAAAAAAAODBcttcxLqUBAAAAADA3pyKAAAAgClSjgAAAAAAAAA81rkIAAAAAAB2' + b'r0r1LNZrSQAAADBFyhEAAAAAAACAx1KOAAAAAACwH6ciAAAAYKqUIwAAAAAAAACPtRYBAAAA' + b'AMBuVamexXotCQAAAKZKOQIAAAAAAADwWOciAAAAAADYuVMRAAAAMGXKEQAAAAAAAIBHyW2T' + b'Y11KAgAAAABgN6pUz2K9lgQAAABTphwBAAAAAAAAeArnIgAAAAAA2JljEQAAADB1yhEAAAAA' + b'AACAp6AcAQAAAABgB6pUl7FOJAEAAMDUKUcAAAAAAAAAnsJaBAAAAAAAO9EVIxyIAQAAgKlT' + b'jgAAAAAAAAA8hXMRAAAAAADsxLEIAAAAQDkCAAAAAAAA8ARy2+RYV5IAAAAAAHg6VaqPY72U' + b'BAAAAPwfe3eQ27iVrmFYCDK3uAJrB9YOrAHn5R2UlmDgguO4x0QhlRVc9w6cOYGmV3DpHcgr' + b'IL2De9R2UilU2bLFc0RKfB7gx+kOGhm8MIMM2l8ZRwAAAAAAAADiaSQAAAAAAIjqWgIAAAB4' + b'ZhwBAAAAAAAAiKWWAAAAAAAgjiwvVuG5UAIAAACeGUcAAAAAAAAAYtlIAAAAAAAQzbUEAAAA' + b'8I1xBAAAAAAAACCWjQQAAAAAAP1lebEIzyclAAAA4BvjCAAAAAAAAEAUXfWlVgEAAAAAIIq1' + b'BAAAAPA94wgAAAAAAABATI8SAAAAAAD0di0BAAAAfM84AgAAAAAAABDTRgIAAAAAgP1lebEO' + b'z5kSAAAA8D3jCAAAAAAAAEBMtQQAAAAAAL2sJQAAAIAfGUcAAAAAAAAAYtpIAAAAAACwnywv' + b'FuG5VAIAAAB+ZBwBAAAAAAAAiGkjAQAAAADA3q4lAAAAgJ8zjgAAAAAAAADE1EgAAAAAALC3' + b'KwkAAADg54wjAAAAAAAAANF01ZcuPE9KAAAAAAB8TJYX22GEcyUAAADg54wjAAAAAAAAALE1' + b'EgAAAAAAfNhaAgAAAHidcQQAAAAAAAAgto0EAAAAAADvl+XFPDyflAAAAIDXGUcAAAAAAAAA' + b'YttIAAAAAADwIVcSAAAAwNuMIwAAAAAAAACxNRIAAAAAAHzItQQAAADwNuMIAAAAAAAAQGyd' + b'BAAAAAAA75PlxSI8F0oAAADA24wjAAAAAAAAAFF11ZdaBQAAAACAd7uSAAAAAHYzjgAAAAAA' + b'AAAAAAAAADCctQQAAACwm3EEAAAAAAAAIIV7CQAAAAAA3pblxTI8F0oAAADAbsYRAAAAAAAA' + b'AAAAAAAAhrGWAAAAAN7HOAIAAAAAAACQQi0BAAAAAMBOVxIAAADA+xhHAAAAAAAAAAAAAAAA' + b'OLAsLxbhOVcCAAAA3sc4AgAAAAAAAJBCLQEAAAAAwJuuJAAAAID3M44AAAAAAAAAAAAAAABw' + b'eGsJAAAA4P2MIwAAAAAAAAApNBIAAAAAAPxclheL8FwoAQAAAO9nHAEAAAAAAACIrqu+dCoA' + b'AAAAALzqSgIAAAD4GOMIAAAAAAAAQCqPEgAAAAAA/NRKAgAAAPgY4wgAAAAAAABAKhsJAAAA' + b'AAC+l+XFPDyflAAAAICPMY4AAAAAAAAAAAAAAABwOCsJAAAA4OOMIwAAAAAAAACp1BIAAAAA' + b'APzgSgIAAAD4OOMIAAAAAAAAAAAAAAAAh7OSAAAAAD7OOAIAAAAAAACQykYCAAAAAIBvsrxY' + b'hudcCQAAAPg44wgAAAAAAABAKhsJAAAAAAC+s5IAAAAA9mMcAQAAAAAAAAAAAAAA4DCuJAAA' + b'AID9GEcAAAAAAAAAkuiqL7UKAAAAAADfuZQAAAAA9mMcAQAAAAAAAAAAAAAAILEsL65UAAAA' + b'gP0ZRwAAAAAAAAAAAAAAAEhvJQEAAADszzgCAAAAAAAAkNK9BAAAAAAA/7WSAAAAAPZnHAEA' + b'AAAAAAAAAAAAACChLC/m4blQAgAAAPZnHAEAAAAAAAAAAAAAACCtlQQAAADQj3EEAAAAAAAA' + b'IKVGAgAAAAAA4wgAAADQl3EEAAAAAAAAIKVOAgAAAAAA4wgAAADQl3EEAAAAAAAAAAAAAACA' + b'tC4kAAAAgH6MIwAAAAAAAAApdRIAAAAAAFOW5cVKBQAAAOjPOAIAAAAAAACQUiMBAAAAADBx' + b'KwkAAACgP+MIAAAAAAAAAAAAAAAA6awkAAAAgP6MIwAAAAAAAAAAAAAAAKSzlAAAAAD6M44A' + b'AAAAAAAApNRJAAAAAABMVZYX22GEMyUAAACgv18lAAAAAAAAAFLpqi/NPP8fIQAAAACAqVpK' + b'ANE8hWvCbV5uqw63DvdZHgAAOH3GEQAAAAAAAAAAAAAAANIwjgAfdz/7NoCwHUPo2qqsX/sf' + b'Z3mxkgwAAKbBOAIAAAAAAAAAAAAAAEAaKwngVf8cQai3b1uVG1kAAIDXGEcAAAAAAAAAAAAA' + b'AABI40ICMIIAAADEYRwBAAAAAAAASO1h5v8ADAAAAABMTJYXKxWYmMdwzT9uO4LQyAIAAMRi' + b'HAEAAAAAAABIrZMAAAAAAJigpQScqKfZjyMItSwAAEBqxhEAAAAAAAAAAAAAAADiW0jAifl3' + b'uLu2Ku+kAAAAhmAcAQAAAAAAAAAAAAAAIL6lBJyAp3Bft9dWZScHAAAwJOMIAAAAAAAAAAAA' + b'AAAA8V1KwBEzigAAAIyOcQQAAAAAAAAAAAAAAICIsrxYqsAR+yPcjVEEAABgbIwjAAAAAAAA' + b'AKnVM39CGgAAAAAwLQsJOEL34a7bqmykAAAAxsg4AgAAAAAAAAAAAAAAQFxLCTgiT7PnUYRb' + b'KQAAgDEzjgAAAAAAAAAAAAAAABDXSgKOxJ/h1m1VdlIAAABjZxwBAAAAAAAAAAAAAAAgroUE' + b'jNzT7HkU4U4KAADgWPwiAQAAAAAAAAAAAAAAQFTnEjBif4ZbGEYAAACOza8SAAAAAAAAAAAA' + b'AAAAxJHlxUoFRuop3HVblbdSAAAAx8g4AgAAAAAAAAAAAAAAQDxzCRihh3BXbVVupAAAAI6V' + b'cQQAAAAAAAAAAAAAAIB4lhIwMv9qq/JGBgAA4NgZRwAAAAAAAABS6yQAAAAAACbEOAJj8RTu' + b'qq3KWgoAAOAU/CIBAAAAAAAAkFgjAQAAAAAwIXMJGIH7cAvDCAAAwCn5VQIAAAAAAAAAAAAA' + b'AIBoLiVgYP9qq/JGBgAA4NQYRwAAAAAAAAAAAAAAAIggy4u5CgzoKdxVW5W1FAAAwCn6RQIA' + b'AAAAAAAAAAAAAIAolhIwkPtwC8MIAADAKftVAgAAAAAAAAAAAAAAgCgWEjCAP9qqvJYBAAA4' + b'dcYRAAAAAAAAAAAAAAAA4lhIwAE9hbtuq/JWCgAAYAqMIwAAAAAAAAAAAAAAAMSxlIADWrdV' + b'eScDAAAwFb9IAAAAAAAAAAAAAAAAEMVcAg7kD8MIAADA1BhHAAAAAAAAAAAAAAAAiGMpAQfw' + b'GO5GBgAAYGqMIwAAAAAAAAAAAAAAAMRxJgEHsG6rspMBAACYGuMIAAAAAAAAAAAAAAAAPWV5' + b'sVSBA/izrcpaBgAAYIqMIwAAAAAAAAAAAAAAAPQ3l4DEnsKtZQAAAKbKOAIAAAAAAAAAAAAA' + b'AEB/CwlI7GtblZ0MAADAVBlHAAAAAAAAAAAAAAAA6G8hAQk9tlV5IwMAADBlxhEAAAAAAAAA' + b'AAAAAAD6m0tAQjcSAAAAU2ccAQAAAAAAAAAAAAAAoL+lBCRy31blrQwAAMDUGUcAAAAAAAAA' + b'AAAAAACA8bqRAAAAwDgCAAAAAAAAAAAAAABADEsJSOChrcpaBgAAAOMIAAAAAAAAAAAAAAAA' + b'MZxJQAJfJQAAAHhmHAEAAAAAAAAAAAAAAADG57GtylsZAAAAnhlHAAAAAAAAAAAAAAAA6CHL' + b'i5UKJPBVAgAAgG+MIwAAAAAAAAAAAAAAAMD43EoAAADwjXEEAAAAAAAAAAAAAACAfuYSENm/' + b'26rsZAAAAPjGOAIAAAAAAAAAAAAAAEA/SwmI7FYCAACA7xlHAAAAAAAAAAAAAAAAgPF4bKuy' + b'lgEAAOB7xhEAAAAAAAAAAAAAAABgPO4kAAAA+JFxBAAAAAAAAAAAAAAAgH5WEhDRrQQAAAA/' + b'Mo4AAAAAAAAAAAAAAAAA4/DYVmUjAwAAwI+MIwAAAAAAAAAAAAAAAMA43EkAAADwc8YRAAAA' + b'AAAAAAAAAAAA+llIQCTGEQAAAF5hHAEAAAAAAAAAAAAAAKCfcwmIoa3KWgUAAICfM44AAAAA' + b'AAAAAAAAAAAAw7uXAAAA4HXGEQAAAAAAAAAAAAAAAGB4tQQAAACvM44AAAAAAAAAAAAAAACw' + b'pywv5ioQSS0BAADA64wjAAAAAAAAAAAAAAAA7G8pATG0VVmrAAAA8DrjCAAAAAAAAAAAAAAA' + b'ADCsBwkAAADeZhwBAAAAAAAAAAAAAAAAhtVIAAAA8DbjCAAAAAAAAAAAAAAAADAs4wgAAAA7' + b'GEcAAAAAAAAAAAAAAACAYRlHAAAA2ME4AgAAAAAAAAAAAAAAwP5WEhCBcQQAAIAdjCMAAAAA' + b'AAAAAAAAAADAgNqq7FQAAAB4m3EEAAAAAAAAAAAAAAAAGM69BAAAALsZRwAAAAAAAAAAAAAA' + b'AAAAAABGzTgCAAAAAAAAAAAAAAAADKeRAAAAYDfjCAAAAAAAAAAAAAAAADCcTgIAAIDdjCMA' + b'AAAAAAAAAAAAAAAAAAAAo2YcAQAAAAAAAAAAAAAAAIZTSwAAALCbcQQAAAAAAAAAAAAAAID9' + b'LSUAAACA9IwjAAAAAAAAAAAAAAAA7G8uAQAAAKRnHAEAAAAAAAAAAAAAAAAAAAAYNeMIAAAA' + b'AAAAAAAAAAAAMJxOAgAAgN2MIwAAAAAAAAAAAAAAAMBA2qpsVAAAANjNOAIAAAAAAAAAAAAA' + b'AAAAAAAwasYRAAAAAAAAAAAAAAAAAAAAgFEzjgAAAAAAAAAAAAAAAAAAAACMmnEEAAAAAAAA' + b'AAAAAAAAGEiWFwsVAAAAdjOOAAAAAAAAAAAAAAAAAMNZSAAAALCbcQQAAAAAAAAAAAAAAAAA' + b'AABg1IwjAAAAAAAAAAAAAAAAAAAAAKNmHAEAAAAAAAAAAAAAAAAAAAAYNeMIAAAAAAAAAAAA' + b'AAAAAAAAwKgZRwAAAAAAAAAAAAAAAIDhrCQAAADYzTgCAAAAAAAAAAAAAAAAAAAAMGrGEQAA' + b'AAAAAAAAAAAAAGA4cwkAAAB2M44AAAAAAAAAAAAAAAAAw1lKAAAAsJtxBAAAAAAAAAAAAAAA' + b'gP01EgAAAEB6xhEAAAAAAAAAAAAAAAD210lAT5cSAAAA7GYcAQAAAAAAAAAAAAAAAAaU5cVc' + b'BQAAgLcZRwAAAAAAAAAAAAAAAIBhLSUAAAB4m3EEAAAAAAAAAAAAAAAAGNZCAgAAgLcZRwAA' + b'AAAAAAAAAAAAAIBhLSUAAAB4m3EEAAAAAAAAAAAAAAAAGJZxBAAAgB2MIwAAAAAAAAAAAAAA' + b'AMCwLiUAAAB4m3EEAAAAAAAAAAAAAACA/TUSEEOWF0sVAAAAXmccAQAAAAAAAAAAAAAAYH+d' + b'BERiHAEAAOANxhEAAAAAAAAAAAAAAABgeCsJAAAAXmccAQAAAAAAAAAAAAAAAIa3kgBOzlIC' + b'AIB4jCMAAAAAAAAAAAAAAADA8M6zvPCL1HBaziQAAIjHOAIAAAAAAAAAAAAAAMD+GgmIaCUB' + b'AADAzxlHAAAAAAAAAAAAAAAA2FNblZ0KRLSWAE5DlhcLFQAA4jKOAAAAAAAAAAAAAAAAAONw' + b'keXFXAY4CQsJAADiMo4AAAAAAAAAAAAAAAAA47GWAE7CQgIAgLiMIwAAAAAAAAAAAAAAAPTz' + b'IAERrSWAk7CQAAAgLuMIAAAAAAAAAAAAAAAA/XQSENFFlhcLGeDoLSUAAIjLOAIAAAAAAAAA' + b'AAAAAACMy7UEcPQWEgAAxGUcAQAAAAAAAAAAAAAAoJ9OAiJbZ3kxlwGO2oUEAABxGUcAAAAA' + b'AAAAAAAAAADop5GAyM7CXckAxynLi5UKAADxGUcAAAAAAAAAAAAAAACA8bmRAI7WSgIAgPiM' + b'IwAAAAAAAAAAAAAAAMD4nGd5sZYBjtJKAgCA+IwjAAAAAAAAAAAAAAAA9FNLQCI3EsBxyfJi' + b'Hp5LJQAA4jOOAAAAAAAAAAAAAAAAAON0nuXFWgY4KlcSAACkYRwBAAAAAAAAAAAAAACgn04C' + b'Evr68ifRA8fBOAIAQCLGEQAAAAAAAAAAAAAAAHpoq7JRgYTOwl3LAOP3MmTySQkAgDSMIwAA' + b'AAAAAAAAAAAAAMC4/ZblxVIGGL0rCQAA0jGOAAAAAAAAAAAAAAAA0N+DBCR2KwGM3o0EAADp' + b'GEcAAAAAAAAAAAAAAADor5OAxC6yvLiRAcYpfJ+r8JwrAQCQjnEEAAAAAAAAAAAAAACA/jYS' + b'cAC/ZXmxlAFG6UYCAIC0jCMAAAAAAAAAAAAAAAD0t5GAA7nL8mIuA4xH+CZX4blUAgAgLeMI' + b'AAAAAAAAAAAAAAAA/XUScCDn4e5kgFG5lQAAID3jCAAAAAAAAAAAAAAAAP01EnBAl1le3MoA' + b'wwvf4vXsebQEAIDEjCMAAAAAAAAAAAAAAADA8fmc5cVaBhhO+AYX4blRAgDgMIwjAAAAAAAA' + b'AAAAAAAA9NRWZa0CA/hfAwkwqNtwZzIAAByGcQQAAAAAAAAAAAAAAAA4XgYSYADhu7sJz6US' + b'AACHYxwBAAAAAAAAAAAAAAAgjnsJGIiBBDigl+/tNyUAAA7LOAIAAAAAAAAAAAAAAEAcnQQM' + b'yEACHED4zpbh+aoEAMDhGUcAAAAAAAAAAAAAAACIo5GAgW0HEm5lgDRehhHqcGdqAAAcnnEE' + b'AAAAAAAAAAAAAACAODoJGIHPWV7U4eZSQDyGEQAAhmccAQAAAAAAAAAAAAAAII5GAkbicvvz' + b'+PLL3EBP4VtazQwjAAAMzjgCAAAAAAAAAAAAAABAHBsJGJHzcP+X5cW1FLC/l2/oPzPDCAAA' + b'gzOOAAAAAAAAAAAAAAAAEEFblRsVGKHfs7yowy2kgPcL38w83N32G1IDAGAcjCMAAAAAAAAA' + b'AAAAAADEcy8BI3QZrsny4kYK2C18K6vtNxPukxoAAOPxqwQAAAAAAAAAAAAAAADRdBIwUmfh' + b'fsvyYh3edVuVtSTwvfB9LMLzdWYUAQBglIwjAAAAAAAAAAAAAAAAxONPGmfszsP9J8uL+/De' + b'GEmA/44izMNz/XJnigAAjJNxBAAAAAAAAAAAAAAAgHgaCTgSlzMjCUycUQQAgONiHAEAAAAA' + b'AAAAAAAAACCejQQcmX+OJHxtq/JOEk5d+HlfhWcd7rMaAADHwzgCAAAAAAAAAAAAAABAJG1V' + b'NlleCMEx2o4kXIaf38fwfg13G36eO1k4FeFnezl7HkS4CneuCADA8TGOAAAAAAAAAAAAAAAA' + b'ENdDuAsZOFLbXxr/fXtZXvw7vHdtVd7JwrEJP7/z8KxeziACAMAJMI4AAAAAAAAAAAAAAAAQ' + b'12ZmHIHT8Hl7WV48hXc7kGAogVEKP6Pr2fMIwtI/fwEATpdxBAAAAAAAAAAAAAAAgLiacJ9k' + b'4ISczb4NJWz/+5/h6tnzWMJGHg5kGX7+6p/89UW4c3kAAE6fcQQAAAAAAAAAAAAAAIC46nC/' + b'ycAJ+/Ryv2d58fjyM78dBanbqmzkIZHtSMelDAAA02UcAQAAAAAAAAAAAAAAIK6NBEzIebjP' + b'LzfL8mL73M+exxI2L2/TVmUnFQAA0IdxBAAAAAAAAAAAAAAAgIjaqtxkefE0e/5TzmGKLl/u' + b'by/fxF+DCf+8LnwzjWQAAMAuxhEAAAAAAAAAAAAAAADi2/6y96UM8Lez2U9GE7ayvPjrPz7O' + b'ngcT/vkddTv+vitpAQBgGowjAAAAAAAAAAAAAAAAxFfPjCPAR52/3F98QwAAwN9+kQAAAAAA' + b'AAAAAAAAACC6RgIAAACIxzgCAAAAAAAAAAAAAABAfMYRAAAAICLjCAAAAAAAAAAAAAAAAJG1' + b'VbkJz5MSAAAAEIdxBAAAAAAAAAAAAAAAgDQaCQAAACAO4wgAAAAAAAAAAAAAAABp1BIAAABA' + b'HMYRAAAAAAAAAAAAAAAA0qglAAD8+wUAxGEcAQAAAAAAAAAAAAAAII1GAgAAAIjDOAIAAAAA' + b'AAAAAAAAAEACbVV24XlQAgAAAPozjgAAAAAAAAAAAAAAAJBOIwEAAAD0ZxwBAAAAAAAAAAAA' + b'AAAgnVoCACCiTgIApso4AgAAAAAAAAAAAAAAQDq1BABARI0EAEyVcQQAAAAAAAAAAAAAAIBE' + b'2qrchOdRCQAAAOjHOAIAAAAAAAAAAAAAAEBatQQAAADQj3EEAAAAAAAAAAAAAACAtGoJAIBI' + b'NhIAMFXGEQAAAAAAAAAAAAAAANKqJQAAYmircqMCAFNlHAEAAAAAAAAAAAAAACChl19ifFQC' + b'AAAA9mccAQAAAAAAAAAAAAAAIL1aAgCgJ2NLAEyacQQAAAAAAAAAAAAAAID0agkAgJ42EgAw' + b'ZcYRAAAAAAAAAAAAAAAA0ruTAAAAAPZnHAEAAAAAAAAAAAAAACCxtiq78DwoAQD00EgAwJQZ' + b'RwAAAAAAAAAAAAAAADiMWgIAoIdOAgCmzDgCAAAAAAAAAAAAAADAYdxJAAD0sJEAgCkzjgAA' + b'AAAAAAAAAAAAAHAAbVXW4XlSAgDY00YCAKbMOAIAAAAAAAAAAAAAAMDh1BIAAHvqJABgyowj' + b'AAAAAAAAAAAAAAAAHM6dBADAPtqqbFQAYMqMIwAAAAAAAAAAAAAAAByOcQQAYB9PEgAwdcYR' + b'AAAAAAAAAAAAAAAADqStyi48D0oAAB/USADA1BlHAAAAAAAAAAAAAAAAOKxbCQCAD+okAGDq' + b'jCMAAAAAAAAAAAAAAAAc1p0EAMAHNRIAMHXGEQAAAAAAAAAAAAAAAA6orcpNeB6UAAA+YCMB' + b'AFNnHAEAAAAAAAAAAAAAAODwagkAgA/YSADA1BlHAAAAAAAAAAAAAAAAOLxbCQCAD2gkAGDq' + b'jCMAAAAAAAAAAAAAAAAcWFuV219wfFQCAHiHp/DvDp0MAEydcQQAAAAAAAAAAAAAAIBh3EkA' + b'ALxDIwEAGEcAAAAAAAAAAAAAAAAYyq0EAMA7GEcAgJlxBAAAAAAAAAAAAAAAgEG0Vbn9RcdH' + b'JQCAHTYSAIBxBAAAAAAAAAAAAAAAgCF9lQAA2KGRAACMIwAAAAAAAAAAAAAAAAzpTgIAYAfj' + b'CAAwM44AAAAAAAAAAAAAAAAwmLYqN+F5UAIAeMX/s3c3x6nrABiGUyol5Hbgs9bGpwPTgc9e' + b'C+gAOggVyO7gisEzSSbkj0CQ7OeZ0Wj/bsTGH4f8e2GQAQCMIwAAAAAAAAAAAAAAANxbKwEA' + b'8I6dBABwYhwBAAAAAAAAAAAAAADgvnoJAIB3GEcAgIlxBAAAAAAAAAAAAAAAgDtKMQz5WisB' + b'AJyxkQAATowjAAAAAAAAAAAAAAAA3F8vAQBwxk4CADgxjgAAAAAAAAAAAAAAAHBnKYbjOMJB' + b'CQDghUP+jTDIAAAnxhEAAAAAAAAAAAAAAADK0EkAALywkwAAnhlHAAAAAAAAAAAAAAAAKEMn' + b'AQDwwkYCAHhmHAEAAAAAAAAAAAAAAKAAKYanfP1TAgCYbCQAgGfGEQAAAAAAAAAAAAAAAMrR' + b'SQAAHKUYdioAwDPjCAAAAAAAAAAAAAAAAIVIMfT5OigBAIu3lQAAXjOOAAAAAAAAAAAAAAAA' + b'UJZOAgBYvI0EAPCacQQAAAAAAAAAAAAAAICytBIAwOJtJACA14wjAAAAAAAAAAAAAAAAFCTF' + b'MORrrQQALPr3wEYFAHjNOAIAAAAAAAAAAAAAAEB5WgkAYLG2EgDAW8YRAAAAAAAAAAAAAAAA' + b'CpNi2D34MBIAlqqXAADeMo4AAAAAAAAAAAAAAABQpk4CAFikjQQA8JZxBAAAAAAAAAAAAAAA' + b'gAKlGLp8HZQAgEUZ82+AnQwA8JZxBAAAAAAAAAAAAAAAgHI1EgDAovQSAMB5xhEAAAAAAAAA' + b'AAAAAADKdfxAcpQBABZjIwEAnGccAQAAAAAAAAAAAAAAoFAphiFfrRIAsBi9BABwnnEEAAAA' + b'AAAAAAAAAACAsh3HEUYZAGD2ttMwEgBwhnEEAAAAAAAAAAAAAACAgk0fSbZKAMDs9RIAwPuM' + b'IwAAAAAAAAAAAAAAAJTPOAIAzJ9xBAD4gHEEAAAAAAAAAAAAAACAwqUYhnytlQCA2drn9/5J' + b'BgB4n3EEAAAAAAAAAAAAAACAOjQSAMBsdRIAwMeMIwAAAAAAAAAAAAAAAFRg+jfptRIAMEu9' + b'BADwMeMIAAAAAAAAAAAAAAAA9WjyGWUAgFnZTyNIAMAHjCMAAAAAAAAAAAAAAABUYvpwslUC' + b'AGalkwAAPmccAQAAAAAAAAAAAAAAoC7HcYRRBgCYjV4CAPiccQQAAAAAAAAAAAAAAICKpBiG' + b'h9NAAgBQv21+259kAIDPGUcAAAAAAAAAAAAAAACoz3EcYZQBAKrXSQAAX2McAQAAAAAAAAAA' + b'AAAAoDIphiFfjRIAULXj0FEvAwB8jXEEAAAAAAAAAAAAAACACqUY2nwdlACAavXT4BEA8AXG' + b'EQAAAAAAAAAAAAAAAOr1KAEAVKuTAAC+zjgCAAAAAAAAAAAAAABApVIMfb62SgBAdQ75Hd/I' + b'AABfZxwBAAAAAAAAAAAAAACgbo0EAFCdVgIA+B7jCAAAAAAAAAAAAAAAABWb/nV6rQQAVKWT' + b'AAC+xzgCAAAAAAAAAAAAAABA/R7zGWUAgCqsUwyDDADwPcYRAAAAAAAAAAAAAAAAKjd9YNkq' + b'AQBV8GYDwAWMIwAAAAAAAAAAAAAAAMxAiqHJ10EJACjaNr/ZOxkA4PuMIwAAAAAAAAAAAAAA' + b'AMzHSgIAKFonAQBcxjgCAAAAAAAAAAAAAADATKQYNvn6pwQAFOmQ3+pOBgC4jHEEAAAAAAAA' + b'AAAAAACAeVnlM8oAAMVpJQCAyxlHAAAAAAAAAAAAAAAAmJEUw5CvRgkAKMpxuKiTAQAuZxwB' + b'AAAAAAAAAAAAAABgZlIMx3+m3ioBAMVopwEjAOBCxhEAAAAAAAAAAAAAAADm6VECACjCmE8r' + b'AwD8jHEEAAAAAAAAAAAAAACAGUox7PL1RwkAuLs2v8uDDADwM8YRAAAAAAAAAAAAAAAAZirF' + b'0ORrrwQA3M2YTysDAPyccQQAAAAAAAAAAAAAAIB5W0kAAHfTphgGGQDg54wjAAAAAAAAAAAA' + b'AAAAzFiKYZevP0oAwK8b82llAIDrMI4AAAAAAAAAAAAAAAAwcymGJl97JQDgV7X5DR5kAIDr' + b'MI4AAAAAAAAAAAAAAACwDCsJAODXjPm0MgDA9RhHAAAAAAAAAAAAAAAAWIAUwy5f/ykBAL+i' + b'zW/vIAMAXI9xBAAAAAAAAAAAAAAAgIVIMRz/wXqrBADc1CG/uY0MAHBdxhEAAAAAAAAAAAAA' + b'AACWZZXPKAMA3EwjAQBcn3EEAAAAAAAAAAAAAACABUkxPD2cBhIAgOvb5re2kwEArs84AgAA' + b'AAAAAAAAAAAAwMKkGPp8/VUCAK6ukQAAbsM4AgAAAAAAAAAAAAAAwDI1+exlAICrWacYNjIA' + b'wG0YRwAAAAAAAAAAAAAAAFigFMOQr1U+oxoA8GPH9/RRBgC4HeMIAAAAAAAAAAAAAAAAC5Vi' + b'2D34kBMArqGZhocAgBsxjgAAAAAAAAAAAAAAALBgKYYuX2slAOBi+/yetjIAwG39LwB7d5DU' + b'xrl2AbiLYk56BXBXgLMCc6t6Dv8KICvAGfQ4zrgHKCugvYNm3gO8A7EDaQUt7eB/v3Q74jqO' + b'AwaklvQ8VadexfboBLVGOhhHAAAAAAAAAAAAAAAA2HNdW13FedAEAPyQKxUAwNszjgAAAAAA' + b'AAAAAAAAAEByEVmqAQCe5Y+uraZqAIC3ZxwBAAAAAAAAAAAAAACArGurWdYPJAAATzOPfFQD' + b'AKyHcQQAAAAAAAAAAAAAAAD+1LXVfZxfNQEAT3IVn50LNQDAehhHAAAAAAAAAAAAAAAA4C9d' + b'W03ifNIEAHzXH8OoEACwJsYRAAAAAAAAAAAAAAAA+NqHyIMaAOCb5pGPagCA9TKOAAAAAAAA' + b'AAAAAAAAwP/o2moR5yyy1AYA/M3V8FkJAKyRcQQAAAAAAAAAAAAAAAD+xkACAHzT7/EZea8G' + b'AFg/4wgAAAAAAAAAAAAAAAB8U9dW0zhXmgCAPz3EZ+NHNQDAZhhHAAAAAAAAAAAAAAAA4B91' + b'bdXE+UUTAOy5ZWYwCAA2yjgCAAAAAAAAAAAAAAAA39W1VR3nkyYA2GMf4vNwqgYA2BzjCAAA' + b'AAAAAAAAAAAAAPyrrq2uMgMJAOynT8NQEACwQcYRAAAAAAAAAAAAAAAAeKoPkQc1ALBHHobP' + b'PwBgw4wjAAAAAAAAAAAAAAAA8CRdWy3inGUGEgDYD8vI1fD5BwBsmHEEAAAAAAAAAAAAAAAA' + b'nuzRQMJcGwDsuDSMMFUDAIyDcQQAAAAAAAAAAAAAAACeZRhIuMj636gNALvoj/i8a9QAAONh' + b'HAEAAAAAAAAAAAAAAIBnG36T9llmIAGA3XMXn3Mf1AAA42IcAQAAAAAAAAAAAAAAgB9iIAGA' + b'HfQQuVIDAIyPcQQAAAAAAAAAAAAAAAB+mIEEAHZI+iy7is+2hSoAYHyMIwAAAAAAAAAAAAAA' + b'APAiBhIA2BFnw2caADBCxhEAAAAAAAAAAAAAAAB4seHLpBeaAGBL/WIYAQDGzTgCAAAAAAAA' + b'AAAAAAAAr6Jrq/s4v2gCgC3ze3yG1WoAgHEzjgAAAAAAAAAAAAAAAMCrGb5c+nNkqQ0AtsCn' + b'+Oz6qAYAGD/jCAAAAAAAAAAAAAAAALyqrq2mcc4yAwkAjFsaRrhSAwBsB+MIAAAAAAAAAAAA' + b'AAAAvDoDCQCM3INhBADYLsYRAAAAAAAAAAAAAAAAeBMGEgAYqYfh8wkA2CLGEQAAAAAAAAAA' + b'AAAAAHgzjwYS5toAYAT+HEaIz6eFKgBguxhHAAAAAAAAAAAAAAAA4E0NAwnvsv4LqQCwKYYR' + b'AGCLGUcAAAAAAAAAAAAAAADgzQ1fRD2LfNYGABtgGAEAtpxxBAAAAAAAAAAAAAAAANYifSE1' + b'chYvP2kDgDUyjAAAO8A4AgAAAAAAAAAAAAAAAGvVtdVVnN81AcAaGEYAgB1hHAEAAAAAAAAA' + b'AAAAAIC169rqY5xfIkttAPBGDCMAwA4xjgAAAAAAAAAAAAAAAMBGdG1VxznLDCQA8PoMIwDA' + b'jjGOAAAAAAAAAAAAAAAAwMZ0bTWNc5L1X2IFgNdgGAEAdpBxBAAAAAAAAAAAAAAAADZq+PLq' + b'WeSTNgB4oU/xufLOMAIA7B7jCAAAAAAAAAAAAAAAAGxc+hJr5Cpe/qoNAH7Qp+GzBADYQcYR' + b'AAAAAAAAAAAAAAAAGI2urSZxfo4stQHAM/xqGAEAdptxBAAAAAAAAAAAAAAAAEala6tpnJPI' + b'Z20A8AS/DOM6AMAOM44AAAAAAAAAAAAAAADA6HRttYicxcvftQHAP1hGfo7Pi1oVALD7jCMA' + b'AAAAAAAAAAAAAAAwWl1bfYzz36z/AiwAfPEQOYvPiakqAGA/GEcAAAAAAAAAAAAAAABg1Lq2' + b'uo9zEvmsDQDCXWYYAQD2zqEKAAAAAAAAAAAAAAAAGLuurRZxzvKi/BD3RiMAe+uP+Ez4oAYA' + b'2D8HKgAAAAAAAAAAAAAAAGBbdG01ifNz5EEbAHtlGfnFMAIA7C/jCAAAAAAAAAAAAAAAAGyV' + b'rq2mcc4if2gDYC+kQZyzeP7XqgCA/WUcAQAAAAAAAAAAAAAAgK3TtdVi+O3h/43MNQKws+6y' + b'fhhhqgoA2G/GEQAAAAAAAAAAAAAAANhaXVvdx3kX+UMbADvn13jOX6RBHFUAAMYRAAAAAAAA' + b'AAAAAAAA2GrpS7ORD/Hyv5G5RgC2XnqW/xzP9okqAIAvjCMAAAAAAAAAAAAAAACwE7q2uo/z' + b'LvKHNgC21qf0LI9n+lQVAMBjhyoAAAAAAAAAAAAAAABgV3RttYjzIS/KOm7KqVYAtsIyPb/j' + b'OV6rAgD4lgMVAAAAAAAAAAAAAAAAsGvSbxyPvIuXv2b9F24BGK/PkXeGEQCA7zGOAAAAAAAA' + b'AAAAAAAAwM7q2moSJ40k3GkDYJR+jWf1WWSmCgDge4wjAAAAAAAAAAAAAAAAsNPSF24jF/Hy' + b'v5G5RgBG4SHy8zBiAwDwrw5VAAAAAAAAAAAAAAAAwD7o2uo+zklelB/jfogcaQVgI36PZ/JH' + b'NQAAz3GgAgAAAAAAAAAAAAAAAPbJ8IXck8gnbQCs1efIfwwjAAA/wjgCAAAAAAAAAAAAAAAA' + b'e6drq0XkKl7+nPVf1gXg7Swjv8Zz9ywyUwcA8COMIwAAAAAAAAAAAAAAALC3uraapi/rxsv/' + b'i8w1AvDq7iIn8aydqAIAeIlDFQAAAAAAAAAAAAAAALDvurZq4jR5UV7FTV/gPdIKwIukwZmr' + b'eL7eqwIAeA0HKgAAAAAAAAAAAAAAAIBe11Z1nJPI75GlRgCeLT07f4/n6YlhBADgNRlHAAAA' + b'AAAAAAAAAAAAgEe6tlpEPmZGEgCe6y7ybniGAgC8qkMVAAAAAAAAAAAAAAAAwN+lkYQ4H/Oi' + b'nMT9MORIMwB/85CekfHcvFcFAPBWjCMAAAAAAAAAAAAAAADAdxhJAPhH8/R8jOdkrQoA4K0d' + b'qAAAAAAAAAAAAAAAAAD+XRpJiHyMlyeR3yNLrQB7ajk8B98ZRgAA1uVQBQAAAAAAAAAAAAAA' + b'APB0aSQhzse8KCdxPww50gywJ/5Iz8DhWQgAsDbGEQAAAAAAAAAAAAAAAOAHfDWScJFeR441' + b'A+yoT1k/ijBTBQCwCcYRAAAAAAAAAAAAAAAA4AWGkYQ6JS/Kq7gfIqeaAXaEUQQAYBSMIwAA' + b'AAAAAAAAAAAAAMAr6dqqzvqRhLOsH0k41wqwpYwiAACjYhwBAAAAAAAAAAAAAAAAXlnXVvdx' + b'7vOiPMn6kYSryJFmgC1gFAEAGKUDFQAAAAAAAAAAAAAAAMDbSF8ujqRxhJPIL5G5VoCRSqMI' + b'/4ln1pVhBABgjA5VAAAAAAAAAAAAAAAAAG+ra6tFnDolL8qzuFeRS80AG7aMTNKzySACADB2' + b'xhEAAAAAAAAAAAAAAABgjbq2uo9znxflh6wfSUj3WDPAGs2zfrBlMoy3AACMnnEEAAAAAAAA' + b'AAAAAAAA2IDhC8npN7ZP8qI8y/qhhIvIkXaAN/KQ9YMItSoAgG1jHAEAAAAAAAAAAAAAAAA2' + b'rGur+zj3eVH+lPUDCVeR95oBXsmnSD08awAAtpJxBAAAAAAAAAAAAAAAABiJrq0WceqUvChP' + b'sn4kIeVYO8AzLSOTrB9FmKkDANh2xhEAAAAAAAAAAAAAAABghIYvM39MyYvyXbYaSjjSDvAd' + b'n7N+EKFWBQCwS4wjAAAAAAAAAAAAAAAAwMh1bTWN8yElL8qLuF9iKAFIlpE6MhmGVQAAdo5x' + b'BAAAAAAAAAAAAAAAANgiXVs1cVIyQwmw9+7S8yCeC7UqAIBdZxwBAAAAAAAAAAAAAAAAtpSh' + b'BNhL88gk60cRZuoAAPaFcQQAAAAAAAAAAAAAAADYAV8NJbyLc5X1QwnH2oGttxze35N4r0/V' + b'AQDsI+MIAAAAAAAAAAAAAAAAsGOGL09/SMmL8iTrRxJS3msHtsaXQYRmGD8BANhrxhEAAAAA' + b'AAAAAAAAAABgh3VtNYszScmL8qesH0k4G+6RhmB07rLVKMJCHQAAPeMIAAAAAAAAAAAAAAAA' + b'sCeGL1rXQ7K8KN9lq6GE9xqCjTGIAADwL4wjAAAAAAAAAAAAAAAAwJ7q2moaJ2WS/jsvyjSS' + b'cDbkVEPwZpZZP4ZwnxlEAAB4EuMIAAAAAAAAAAAAAAAAwJ+6tvrzN9en13lR/pSthhJSjCXA' + b'y8yH99f98F4DAOAZjCMAAAAAAAAAAAAAAAAAfzP8JntjCfAyd5H79D6K99RMHQAAP844AgAA' + b'AAAAAAAAAAAAwBvKi7LO+i/F+i3hbLV/GEt4l63GEt5rCbLPWT+GcB/vmXt1AAC8HuMIAAAA' + b'AAAAAAAAAAAAb+syJS/KedZ/qbzu2mqqFrbdMJZwP+RP8XP+ZSzh3ZBTTbHjjCEAAKyJcQQA' + b'AAAAAAAAAAAAAID1OI5cpzwaSph0bTVTDbtiGP74a/wjftZ/yvqRhLNsNZhwrCm21Hz4+b7P' + b'+jEEQzcAAGtkHAEAAAAAAAAAAAAAAGD9Hg8lPMStI42hBHZN/EwvsuGL5F/+7BuDCSeRU20x' + b'Msvs0RBCej38PAMAsCHGEQAAAAAAAAAAAAAAADYrfSn8JiUvys/ZaijBl3DZSd8aTEji5/8s' + b'64cS3j3KkcZYgzRSM8tWYwiGEAAARsg4AgAAAAAAAAAAAAAAwHi8H3KbF+Vd3CYzlMCeiJ/z' + b'+6//LN4HP2WroYSTzGgCL/N4BCFlFj93U7UAAGwH4wgAAAAAAAAAAAAAAADjdD5kkhfll5GE' + b'Ri3sk2EY5H7IXx6NJpwMOYukPzvV2t5LAwjp52Y63PSzk0YQZqoBANhuxhEAAAAAAAAAAAAA' + b'AADG7ShymZIX5TJuGkiY+G3n7LNHowl/E++Tk6wfTEjjCWkw4Wz4q/ea2wmfh/tl/GD2JQYQ' + b'AAB2m3EEAAAAAAAAAAAAAACA7fF4KGGe9UMJtaEEWBm+IJ9y/62/j/fO2fDy62s8YXPS8MuX' + b'59iX/39ZthpAWHjOAQBgHAEAAAAAAAAAAAAAAGA7HUeuU4ahhEmk8ZvT4fviPXI/vLz/1t/H' + b'++ldnJ8iV5H0+lRrT/L5q/9OowZfDxo87nzmeQUAwHMYRwAAAAAAAAAAAAAAANh+aSjhJiUv' + b'yoe4dUrXVgvVwPPE++bLF/rvv/xZvK9O4px89U+/9Wfb5lsDBt/994/6AQCAtTKOAAAAAAAA' + b'AAAAAAAAsFvSb7n/MpRwF7dJMZQAPy7eP7M4M00AAMDmHKgAAAAAAAAAAAAAAABgZ51HbiNd' + b'XpRN5EIlAAAAbKNDFQAAAAAAAAAAAAAAAOyFNJRwnhflMm6T0rVVoxYAAAC2gXEEAAAAAAAA' + b'AAAAAACA/XIUuUzJi3Ke9UMJdddWU9UAAAAwVsYRAAAAAAAAAAAAAAAA9tdx5DplGEqos34o' + b'YaYaAAAAxsQ4AgAAAAAAAAAAAAAAAEkaSvgtJS/Kh6wfSmgMJQAAADAGxhEAAAAAAAAAAAAA' + b'AAD42mnkJiUvys/ZaihhoRoAAAA24UAFAAAAAAAAAAAAAAAAfMf7yG2ky4uyiVypBAAAgHU7' + b'VAEAAAAAAAAAAAAAAABPdJ6SF+UkbpPStVWjFgAAAN6acQQAAAAAAAAAAAAAAACe6yhymZIX' + b'5TLrhxImXVtNVQMAAMBbMI4AAAAAAAAAAAAAAADASzweSphn/VBCbSgBAACA12QcAQAAAAAA' + b'AAAAAAAAgNdyHLlOyYvyIW4dabq2mqkGAACAlzCOAAAAAAAAAAAAAAAAwFs4jdykPBpKqLu2' + b'WqgGAACA5zKOAAAAAAAAAAAAAAAAwFt7PJRwF7dJMZQAAADAUx2oAAAAAAAAAAAAAAAAgDU6' + b'j9xGurwo68iFSgAAAPg3hyoAAAAAAAAAAAAAAABgQy5T8qJcxm1SurZq1AIAAMDXjCMAAAAA' + b'AAAAAAAAAACwaUfZaihhnvVDCXXXVlPVAAAAkBhHAAAAAAAAAAAAAAAAYEyOI9cpw1BCnfVD' + b'CTPVAAAA7C/jCAAAAAAAAAAAAAAAAIxVGkr4LSUvyoesH0poDCUAAADsH+MIAAAAAAAAAAAA' + b'AAAAbIPTyE1KXpSfs9VQwkI1AAAAu+9ABQAAAAAAAAAAAAAAAGyZ95HbSJcXZRO5UgkAAMBu' + b'O1QBAAAAAAAAAAAAAAAAW+w8JS/KSdwmpWurRi0AAAC7xTgCAAAAAAAAAAAAAAAAu+AocpmS' + b'F+Uy64cSJl1bTVUDAACw/YwjAAAAAAAAAAAAAAAAsGseDyXMs34ooTaUAAAAsL2MIwAAAAAA' + b'AAAAAAAAALDLjiPXKXlRPsStI03XVjPVAAAAbA/jCAAAAAAAAAAAAAAAAOyL08hNyqOhhLpr' + b'q4VqAAAAxs04AgAAAAAAAAAAAAAAAPvo8VDCXdwmxVACAADAOB2oAAAAAAAAAAAAAAAAgD13' + b'HrmNdHlR1pELlQAAAIzLoQoAAAAAAAAAAAAAAADgL5cpeVEu4zYpXVs1agEAANgs4wgAAAAA' + b'AAAAAAAAAADwd0fZaihhnvVDCXXXVlPVAAAArJ9xBAAAAAAAAAAAAAAAAPi+48h1yjCUUGf9' + b'UMJMNQAAAOthHAEAAAAAAAAAAAAAAACeLg0l/JaSF+VD1g8lNIYSAAAA3pZxBAAAAAAAAAAA' + b'AAAAAPgxp5GblLwoP2eroYSFagAAAF7XgQoAAAAAAAAAAAAAAADgxd5HbiNdXpRN5EolAAAA' + b'r+dQBQAAAAAAAAAAAAAAAPCqzlPyopzEbVK6tmrUAgAA8OOMIwAAAAAAAAAAAAAAAMDbOIpc' + b'puRFuYxbp3RtNVUNAADA8xhHAAAAAAAAAAAAAAAAgLeXhhKuU/KinMdtMkMJAAAAT2YcAQAA' + b'AAAAAAAAAAAAANbrOFsNJTzErSNN11Yz1QAAAHybcQQAAAAAAAAAAAAAAADYnNPITcowlDDJ' + b'+qGEhWoAAABWjCMAAAAAAAAAAAAAAADAOKShhNuUvCjv4jaZoQQAAIA/HagAAAAAAAAAAAAA' + b'AAAARuc864cSurwo68iFSgAAgH12qAIAAAAAAAAAAAAAAAAYtcuUvCiXcZtI3bXVvVoAAIB9' + b'YhwBAAAAAAAAAAAAAAAAtsNRthpKmGeroYSpagAAgF1nHAEAAAAAAAAAAAAAAAC2z3HkOmUY' + b'SqizfihhphoAAGAXGUcAAAAAAAAAAAAAAACA7ZaGEn5LyYvyIeuHEhpDCQAAwC4xjgAAAAAA' + b'AAAAAAAAAAC74zRyk5IX5V3cJuuHEhaqAQAAttmBCgAAAAAAAAAAAAAAAGAnnUduI11elE3k' + b'SiUAAMC2OlQBAAAAAAAAAAAAAAAA7Lw0lHCeF+UkbpPStVWjFgAAYFsYRwAAAAAAAAAAAAAA' + b'AID9cRS5TMmLchm3TunaaqoaAABgzIwjAAAAAAAAAAAAAAAAwH5KQwnXKXlRzuM2maEEAABg' + b'pIwjAAAAAAAAAAAAAAAAAMfZaijhIW4dabq2mqkGAAAYA+MIAAAAAAAAAAAAAAAAwGOnkZuU' + b'YShhkvVDCQvVAAAAm2IcAQAAAAAAAAAAAAAAAPgnaSjhNiUvyru4TWYoAQAA2IADFQAAAAAA' + b'AAAAAAAAAABPcJ71QwldXpR15EIlAADAuhyqAAAAAAAAAAAAAAAAAHimy5S8KJdxm0jdtdW9' + b'WgAAgLdiHAEAAAAAAAAAAAAAAAD4UUfZaihhnq2GEqaqAQAAXpNxBAAAAAAAAAAAAAAAAOA1' + b'HEeuU4ahhDrrhxJmqgEAAF7KOAIAAAAAAAAAAAAAAADw2tJQwm8peVE+ZP1QQmMoAQAA+FHG' + b'EQAAAAAAAAAAAAAAAIC3dBq5ScmL8i5uk/VDCQvVAAAAT3WgAgAAAAAAAAAAAAAAAGBNziO3' + b'kS4vyiZypRIAAOApDlUAAAAAAAAAAAAAAAAAbEAaSjjPi3ISt0np2qpRCwAA8C3GEQAAAAAA' + b'AAAAAAAAAIBNOopcpuRFuYxbp3RtNVUNAADwhXEEAAAAAAAAAAAAAAAAYCzSUMJ1Sl6U87hN' + b'ZNK11Uw1AACw34wjAAAAAAAAAAAAAAAAAGN0nK2GEh7i1pHGUAIAAOwn4wgAAAAAAAAAAAAA' + b'AADA2J1GblKGoYRJ1g8lLFQDAAD7wTgCAAAAAAAAAAAAAAAAsE3SUMJtSl6Ud3GbzFACAADs' + b'vAMVAAAAAAAAAAAAAAAAAFvqPOuHEmZ5UdaRC5UAAMBuOlQBAAAAAAAAAAAAAAAAsOWOIpcp' + b'eVEu4zaRumure9UAAMBuMI4AAAAAAAAAAAAAAAAA7JLHQwnzbDWUMFUNAABsL+MIAAAAAAAA' + b'AAAAAAAAwK46jlynDEMJk0jTtdVMNQAAsF2MIwAAAAAAAAAAAAAAAAD7IA0l3KTkRfkQt84M' + b'JQAAwNYwjgAAAAAAAAAAAAAAAADsm9NsNZRwF7fJ+qGEhWoAAGCcDlQAAAAAAAAAAAAAAAAA' + b'7LHzyG2ky4uyiVyoBAAAxudQBQAAAAAAAAAAAAAAAAB/SkMJ53lRLuM2KV1bNWoBAIDNM44A' + b'AAAAAAAAAAAAAAAA8L+OIpcpw1BCndK11VQ1AACwGcYRAAAAAAAAAAAAAAAAAP5ZGkq4TsmL' + b'ch63iUy6tpqpBgAA1sc4AgAAAAAAAAAAAAAAAMDTHGeroYSHuHWkMZQAAABvzzgCAAAAAAAA' + b'AAAAAAAAwPOdRm5ShqGESdYPJSxUAwAAr884AgAAAAAAAAAAAAAAAMDLpKGE25S8KO/iNpmh' + b'BAAAeFUHKgAAAAAAAAAAAAAAAAB4NedZP5Qwy4uyjlyoBAAAXu5QBQAAAAAAAAAAAAAAAACv' + b'7ihymZIX5TJuE6m7trpXDQAAPJ9xBAAAAAAAAAAAAAAAAIC39XgoYZ6thhKmqgEAgKcxjgAA' + b'AAAAAAAAAAAAAACwPseR65RhKGESabq2mqkGAAD+mXEEAAAAAAAAAAAAAAAAgM1IQwk3KXlR' + b'PsStM0MJAADwTcYRAAAAAAAAAAAAAAAAADbvNFsNJdzFbbJ+KGGhGgAAyLIDFQAAAAAAAAAA' + b'AAAAAACMynnkNtLlRdlELlQCAMC+O1QBAAAAAAAAAAAAAAAAwGiloYTzvCiXcZuUrq0atQAA' + b'sG+MIwAAAAAAAAAAAAAAAACM31HkMmUYSqhTuraaqgYAgH1gHAEAAAAAAAAAAAAAAABgu6Sh' + b'hOuUvCjncZvIpGurmWoAANhVxhEAAAAAAAAAAAAAAAAAttdxthpKeIhbRxpDCQAA7BrjCAAA' + b'AAAAAAAAAAAAAAC74TRykzIMJUyyfihhoRoAALadcQQAAAAAAAAAAAAAAACA3ZOGEm5T8qK8' + b'i9tkhhIAANhiByoAAAAAAAAAAAAAAAAA2GnnWT+UMMuLso5cqAQAgG1zqAIAAAAAAAAAAAAA' + b'AACAvXAUuUzJi3IZt4lMuraaqgYAgLEzjgAAAAAAAAAAAAAAAACwfx4PJcyzfiihNpQAAMBY' + b'GUcAAAAAAAAAAAAAAAAA2G/HkeuUYShhEmm6tpqpBgCAsTCOAAAAAAAAAAAAAAAAAMAXaSjh' + b'JiUvyoe4dUrXVgvVAACwScYRAAAAAAAAAAAAAAAAAPiW02w1lHAXt0kxlAAAwCYcqAAAAAAA' + b'AAAAAAAAAACAf3EeuY10eVE2kQuVAACwTocqAAAAAAAAAAAAAAAAAOAZ0lDCeV6Uy7hNStdW' + b'jVoAAHhLxhEAAAAAAAAAAAAAAAAA+BFHkcuUvCjnWT+UUHdtNVUNAACvzTgCAAAAAAAAAAAA' + b'AAAAAC91HLlOeTSUMOnaaqYaAABeg3EEAAAAAAAAAAAAAAAAAF7T46GEh7h1pDGUAADASxhH' + b'AAAAAAAAAAAAAAAAAOCtnEZuUvKi/JythhIWqgEA4DmMIwAAAAAAAAAAAAAAAACwDu+H3OZF' + b'eRe3yQwlAADwRAcqAAAAAAAAAAAAAAAAAGDNziO3kVlelHXkQiUAAHzPoQoAAAAAAAAAAAAA' + b'AAAA2JCjyGVKXpTLuE1k0rXVVDUAADxmHAEAAAAAAAAAAAAAAACAMXg8lDDP+qGE2lACAACJ' + b'cQQAAAAAAAAAAAAAAAAAxuY4cp0yDCVMIk3XVjPVAADsJ+MIAAAAAAAAAAAAAAAAAIxZGkq4' + b'ScmL8iFundK11UI1AAD7wzgCAAAAAAAAAAAAAAAAANviNFsNJdzFbVIMJQAA7L4DFQAAAAAA' + b'AAAAAAAAAACwhc4jt5EuL8omcqESAIDddagCAAAAAAAAAAAAAAAAALZcGko4z4tyGbdJ6dqq' + b'UQsAwO4wjgAAAAAAAAAAAAAAAADArjiKXKbkRTnP+qGEumurqWoAALabcQQAAAAAAAAAAAAA' + b'AAAAdtFx5Drl0VDCpGurmWoAALaPcQQAAAAAAAAAAAAAAAAAdt3joYSHuHWkMZQAALA9jCMA' + b'AAAAAAAAAAAAAAAAsE9OIzcpeVF+zlZDCQvVAACM1/+zdz9HbezpHod/RbEXHYHIQNwI0KL3' + b'kAFMBPam12bWvbBuBGgimPa+qw4ngosjGDmCxhnc92c1Ix0O2IAl0J/nqfpWw/bddG/4II4A' + b'AAAAAAAAAAAAAAAAwL467XddlNWXeDZJKAEAYCMdOAEAAAAAAAAAAAAAAAAApLPYdWxWlNU0' + b'du4kAACb49AJAAAAAAAAAAAAAAAAAOC/BrGLvKKsvseziU26tr51GgCA9yOOAAAAAAAAAAAA' + b'AAAAAACPWw4lfEvzUMJUKAEA4O2JIwAAAAAAAAAAAAAAAADArw1jH/L6UMIk1nRtPXMaAID1' + b'E0cAAAAAAAAAAAAAAAAAgJfJoYTPeUVZfY3nNK9r6zunAQBYD3EEAAAAAAAAAAAAAAAAAHi9' + b'UVqEEr7Es8kTSgAAWK0DJwAAAAAAAAAAAAAAAACAlTiLXce6oqymsXMnAQBYjUMnAAAAAAAA' + b'AAAAAAAAAICVu8gryup7PJu8rq0bZwEAeB1xBAAAAAAAAAAAAAAAAABYn0FahBK+pXkoYdq1' + b'9a3TAAA8nzgCAAAAAAAAAAAAAAAAALyNYexDXh9KmKZ5KGHmNAAAPyeOAAAAAAAAAAAAAAAA' + b'AABvL4cSPuUVZfU1zUMJjVACAMDjxBEAAAAAAAAAAAAAAAAA4H2NYp/zirL6My1CCXdOAwAw' + b'd+AEAAAAAAAAAAAAAAAAALAxTmPXsa4oqyZ26SQAACkdOgEAAAAAAAAAAAAAAAAAbKSzvKKs' + b'JvFs8rq2bpwFANhH4ggAAAAAAAAAAAAAAAAAsNkGsYu8oqy+p3koYdK19a3TAAD7QhwBAAAA' + b'AAAAAAAAAAAAALbHcijhW5qHEqZCCQDArhNHAAAAAAAAAAAAAAAAAIDtNIx9yCvK6ms8p7Gm' + b'a+uZ0wAAu0YcAQAAAAAAAAAAAAAAAAC23yj2OW8plDDt2vrOaQCAXSCOAAAAAAAAAAAAAAAA' + b'AAC7ZTmU8CWeTZ5QAgCwzQ6cAAAAAAAAAAAAAAAAAAB21lnsOtYVZTWNnTsJALCNDp0AAAAA' + b'AAAAAAAAAAAAAPbCRV5RVt/j2eR1bd04CwCwDcQRAAAAAAAAAAAAAAAAAGC/DNIilPAtzUMJ' + b'066tb50GANhU4ggAAAAAAAAAAAAAAAAAsL+GsQ95fShhmuahhJnTAACbRBwBAAAAAAAAAAAA' + b'AAAAAMhyKOFTXlFWX9M8lNAIJQAAm0AcAQAAAAAAAAAAAAAAAAB4aBT7nFeU1Z9pEUq4cxoA' + b'4D0cOAEAAAAAAAAAAAAAAAAA8BOnsetYV5RVE7t0EgDgrR06AQAAAAAAAAAAAAAAAADwTGd5' + b'RVlN4tnkdW3dOAsAsG7iCAAAAAAAAAAAAAAAAADASw1iF3lFWX1P81DCpGvrW6cBANZBHAEA' + b'AAAAAAAAAAAAAAAA+B3LoYRvaR5KmAolAACrJI4AAAAAAAAAAAAAAAAAAKzKMPYhryirr/Gc' + b'xpqurWdOAwD8DnEEAAAAAAAAAAAAAAAAAGAdRrHPeUuhhGnX1ndOAwC8lDgCAAAAAAAAAAAA' + b'AAAAALBuy6GEL/Fs8oQSAIDnOnACAAAAAAAAAAAAAAAAAOANncWuY11RVtPYuZMAAL9y6AQA' + b'AAAAAAAAAAAAAAAAwDu5yCvK6ns8m9i0a+sbZwEAHhJHAAAAAAAAAAAAAAAAAADe2yAtQgnf' + b'0iKUcOs0AEAmjgAAAAAAAAAAAAAAAAAAbJJh7ENeH0qYpnkoYeY0ALC/xBEAAAAAAAAAAAAA' + b'AAAAgE2VQwmf8oqy+prmoYRGKAEA9o84AgAAAAAAAAAAAAAAAACwDUaxz3lFWX2JZ5PmoYQ7' + b'pwGA3XfgBAAAAAAAAAAAAAAAAADAljmLXce6oqya2KWTAMBuO3QCAAAAAAAAAAAAAAAAAGCL' + b'5VDCWVFWk3g2eV1bN84CALtFHAEAAAAAAAAAAAAAAAAA2AWD2EVeUVbf4znN69r61mkAYPuJ' + b'IwAAAAAAAAAAAAAAAAAAuyaHEj7kFWX1LZ5NEkoAgK0mjgAAAAAAAAAAAAAAAAAA7LJhWoQS' + b'vsZzGmu6tp45DQBsD3EEAAAAAAAAAAAAAAAAAGBfjGKf8/pQwiTNQwl3TgMAm00cAQAAAAAA' + b'AAAAAAAAAADYRzmUcJ1XlNWXeDZJKAEANtaBEwAAAAAAAAAAAAAAAAAAe+4szUMJXVFW09i5' + b'kwDAZjl0AgAAAAAAAAAAAAAAAACA/7rIK8rqezyb2LRr6xtnAYD3JY4AAAAAAAAAAAAAAAAA' + b'APB3g7QIJXxLi1DCrdMAwNsTRwAAAAAAAAAAAAAAAAAA+Llh7ENeH0qYpnkoYeY0APA2xBEA' + b'AAAAAAAAAAAAAAAAAJ4vhxI+5RVl9TXNQwmNUAIArJc4AgAAAAAAAAAAAAAAAADA64xin/OK' + b'svoSzybNQwl3TgMAq3XgBAAAAAAAAAAAAAAAAAAAv+0sdh3rirJqYpdOAgCrc+gEAAAAAAAA' + b'AAAAAAAAAAArlUMJZ0VZTeLZ5HVt3TgLALyeOAIAAAAAAAAAAAAAAAAAwHoMYhd5RVl9j+c0' + b'r2vrW6cBgJcRRwAAAAAAAAAAAAAAAAAAWL8cSviQV5TVt3g2SSgBAJ5NHAEAAAAAAAAAAAAA' + b'AAAA4G0N0yKU8DWe01jTtfXMaQDgceIIAAAAAAAAAAAAAAAAAADvZxT7nNeHEiZpHkq4cxoA' + b'WBBHAAAAAAAAAAAAAAAAAADYDDmUcJ1XlNWXeDZJKAEAfjhwAgAAAAAAAAAAAAAAAACAjXOW' + b'5qGEriiraezcSQDYZ4dOAAAAAAAAAAAAAAAAAACw0S7yirL6Hs8mNu3a+sZZANgn4ggAAAAA' + b'AAAAAAAAAAAAANthkBahhG9pEUq4dRoAdp04AgAAAAAAAAAAAAAAAADA9hnGPuT1oYRpmocS' + b'Zk4DwC4SRwAAAAAAAAAAAAAAAAAA2G45lPApryirr2keSmiEEgDYJeIIAAAAAAAAAAAAAAAA' + b'AAC7YxT7nFeU1Zd4NmkeSrhzGgC22YETAAAAAAAAAAAAAAAAAADspLPYdawryqqJnTsJANvq' + b'0AkAAAAAAAAAAAAAAAAAAHZeDiWcFWX1PZ5NXtfWjbMAsC3EEQAAAAAAAAAAAAAAAAAA9scg' + b'dpHXhxKmeV1b3zoNAJtMHAEAAAAAAAAAAAAAAAAAYD/lUMKHvKKsvsWziU26tp45DQCbRhwB' + b'AAAAAAAAAAAAAAAAAIBhWoQSvsZzGmuEEgDYFOIIAAAAAAAAAAAAAAAAAAAsG8U+5/WhhEma' + b'hxLunAaA9yKOAAAAAAAAAAAAAAAAAADAU3Io4TqvKKsv8WySUAIA7+DACQAAAAAAAAAAAAAA' + b'AAAAeIazNA8lzIqymsbOnQSAt3LoBAAAAAAAAAAAAAAAAAAAvMAgdpFXlNX3eDaxadfWN04D' + b'wLqIIwAAAAAAAAAAAAAAAAAA8FrLoYRvaRFKuHUaAFZJHAEAAAAAAAAAAAAAAAAAgFUYxj7k' + b'9aGESazp2nrmNAD8LnEEAAAAAAAAAAAAAAAAAABWLYcSPucVZfU1ntMklADAbxBHAAAAAAAA' + b'AAAAAAAAAABgnUZpEUr4Es8mzUMJd04DwHMdOAEAAAAAAAAAAAAAAAAAAG/kLHYd64qyamLn' + b'TgLAcxw6AQAAAAAAAAAAAAAAAAAA7yCHEs6Ksvoezyava+vGWQB4jDgCAAAAAAAAAAAAAAAA' + b'AADvaRC7yOtDCdO8rq1vnQaAe+IIAAAAAAAAAAAAAAAAAABsihxK+JBXlNW3eDZJKAGAJI4A' + b'AAAAAAAAAAAAAAAAAMBmGqZFKOFrPKexpmvrmdMA7B9xBAAAAAAAAAAAAAAAAAAANt0o9jmv' + b'KKsv8WzSPJRw5zQA++HACQAAAAAAAAAAAAAAAAAA2CJnsevYrCiraezESQB236ETAAAAAAAA' + b'AAAAAAAAAACwhQaxi7yirL7FcxKbdm195zQAu+fACQAAAAAAAAAAAAAAAAAA2HLD2OdYV5TV' + b'NDZ2EoDdcugEAAAAAAAAAAAAAAAAAADskIu8oqy+xnMSa7q2vnMWgO124AQAAAAAAAAAAAAA' + b'AAAAAOygUew6NivK6ip27CQA20scAQAAAAAAAAAAAAAAAACAXTaIfYr9pyiraWzsJADbRxwB' + b'AAAAAAAAAAAAAABgvb7EvjsDAMBGuIj9UZTVjUgCwHYRRwAAAAAAAAAAAAAAAFijrq3PY0fx' + b'4//E/hH7V+ybywAAvKvTNI8kzGKXzgGw+Q6dAAAAAAAAAAAAAAAAYP26tr6NR940/16U1XE8' + b'xksbuhIAwJvL32DX8W12Fc+r+GabOgnAZhJHAAAAAAAAAAAAAAAAeAddW8/SPJSQtxxLOOmf' + b'I1cCAHgzIgkAG04cAQAAAAAAAAAAAAAAYAMsxRJ+KMrqKM0jCfcTSwAAWD+RBIANJY4AAAAA' + b'AAAAAAAAAACwgbq2votH0+9hLOEkdupKAABrI5IAsGHEEQAAAAAAAAAAAAAAALbAw1hCVpTV' + b'OC2CCWIJAACrtxxJuIxvshsnAXgf4ggAAAAAAAAAAAAAAABbqv/jvJv738USAADWJkcS/ojv' + b'rT/jeSWSAPD2xBEAAAAAAAAAAAAAAAB2xBOxhJO0CCYMXAkA4LfkAFWOJPwrzSMJMycBeBvi' + b'CAAAAAAAAAAAAAAAADtqKZYwyb8XZbUcSsgTSwAAeJ2L2Hl8X+XvrEl8d905CcB6iSMAAAAA' + b'AAAAAAAAAADsia6tb+OR9zCWcP8cuhIAwLPl0NSn2GV8V32Mb63GSQDWRxwBAAAAAAAAAAAA' + b'AABgTy3FEn4oyuo4zSMJ9xNLAAD4tfzN9O/4lvoznpfxjTVzEoDVE0cAAAAAAAAAAAAAAADg' + b'h/4P+ab9HsYSTmIjVwIAeNJp7D/xDfXPeE7i2+rOSQBWRxwBAAAAAAAAAAAAAACARz0SSzhK' + b'i1hCnlgCAMDffYpdxrfTZXxP3TgHwGqIIwAAAAAAAAAAAAAAAPAs/X8/bvqJJQAAPG0Y+yO+' + b'l77E87L/jgLgN4gjAAAAAAAAAAAAAAAA8CoPYwlZUVbjtIglnLoSALDnzmKz+EbKgYTGOQBe' + b'TxwBAAAAAAAAAAAAAACAlena+iYeN/e/iyUAAKRB7N/xXfQlnpd9YAqAFxJHAAAAAAAAAAAA' + b'AAAAYG2eiCWcpEUwYeBKAMCeOIvN4nsoBxIa5wB4GXEEAAAAAAAAAAAAAAAA3sxSLGGSfy/K' + b'ajmUkCeWAADssvyt8+/4BvpXPD/Gt9GdkwA8jzgCAAAAAAAAAAAAAAAA76Zr69t45D2MJdw/' + b'h64EAOygi/ytE98+l308CoBfEEcAAAAAAAAAAAAAAABgYyzFEn4oyuo4zSMJ9xNLAAB2Rf6u' + b'+SO+d/4Z30BXzgHwc+IIAAAAAAAAAAAAAAAAbKyurWfxmPZ7GEs4iY1cCQDYcp/iGyd/25zH' + b't8+dcwA8ThwBAAAAAAAAAAAAAACArfFILOEoLWIJeWIJAMA2Oo3N4tsmBxJunAPg78QRAAAA' + b'AAAAAAAAAAAA2Fr9f1du+oklAADbbBD7I75n/hnfOFfOAfBX4ggAAAAAAAAAAAAAAADsjIex' + b'hKwoq3FaxBJOXQkA2HCf+u+X8/7bBoAkjgAAAAAAAAAAAAAAAMCO69r6Jh4397+LJQAAWyB/' + b'o9zGd0sOJNw6B4A4AgAAAAAAAAAAAAAAAHvmiVjCSVoEEwauBABsgGH+ZolvlY/x/TJ1DmDf' + b'iSMAAAAAAAAAAAAAAACw15ZiCZP8e1FWy6GEPLEEAOC95O+Q6/g+OY5vlivnAPaZOAIAAAAA' + b'AAAAAAAAAAAs6dr6Nh55D2MJ98+hKwEAb+xTDiTE82N8q9w5B7CPxBEAAAAAAAAAAAAAAADg' + b'J5ZiCT/0f5g4XppYAgDwFi5iJ/EtMhZIAPaROAIAAAAAAAAAAAAAAAC8QNfWs3hM+z2MJZzE' + b'Rq4EAKxJ/s64je+P8z7gBLA3xBEAAAAAAAAAAAAAAADgNzwSSzhKi1hCnlgCALBKw9hNfHOM' + b'BRKAfSKOAAAAAAAAAAAAAAAAACvUtfVdPJp+YgkAwDoMYv8X3xn/iG+PqXMA+0AcAQAAAAAA' + b'AAAAAAAAANboYSwhy//pOS1iCaeuBAC80nV8VySBBGAfiCMAAAAAAAAAAAAAAADAG+va+iYe' + b'N/e/iyUAAL9BIAHYC+IIAAAAAAAAAAAAAAAA8M6eiCWcpEUwYeBKAMBP5EDCOL4pLp0C2FXi' + b'CAAAAAAAAAAAAAAAALBhlmIJk/x7UVbLoYQ8sQQA4KGL+GZIAgnArhJHAAAAAAAAAAAAAAAA' + b'gA3XtfVtPPIexhLun0NXAgCSQAKww8QRAAAAAAAAAAAAAAAAYMssxRJ+KMrqOM0jCfcTSwCA' + b'/SWQAOwkcQQAAAAAAAAAAAAAAADYcl1bz+Ix7fcwlnASG7kSAOwVgQRg54gjAAAAAAAAAAAA' + b'AAAAwI55JJZwlBaxhDyxBADYfQIJwE4RRwAAAAAAAAAAAAAAAIAd17X1XTyafmIJALA/ciDh' + b'Lr4FPjoFsO3EEQAAAAAAAAAAAAAAAGDPPIwlZEVZjdMilnDqSgCwMz7Ee/423v9TpwC2mTgC' + b'AAAAAAAAAAAAAAAAkIMJN/G4uf9dLAEAdsp1vNuTQAKwzcQRAAAAAAAAAAAAAAAAgL95IpZw' + b'khbBhIErAcBWEUgAtpo4AgAAAAAAAAAAAAAAAPBLS7GESf69KKvlUEKeWAIAbL5JvMNv471+' + b'6xTAthFHAAAAAAAAAAAAAAAAAF6s/6PKvIexhPvn0JUAYOPkmNFNvLfHAgnAthFHAAAAAAAA' + b'AAAAAAAAAH7bUizhh6KsjtM8knA/sQQA2Aw5kDDtAwl3zgFsC3EEAAAAAAAAAAAAAAAAYOW6' + b'tp7FY9pPLAEANssodhM7cQpgW4gjAAAAAAAAAAAAAAAAAGv3SCzhKP01ljByJQB4U6N4H0/j' + b'HX3pFMA2EEcAAAAAAAAAAAAAAAAA3lzX1nfxaPqJJQDA+7iId/BtvJcnTgFsOnEEAAAAAAAA' + b'AAAAAAAA4N09EUs4SYtYwqkrAcBafI737izexY1TAJtMHAEAAAAAAAAAAAAAAADYOH0s4abf' + b'D0VZjZNYAgCswzTesyfx/p05BbCpxBEAAAAAAAAAAAAAAACArdC19U16PJZw0j8HrgQAr5Lf' + b'oU1+t/aBIoCNI44AAAAAAAAAAAAAAAAAbKVHYgn3kYT7iSUAwPONYpPYpVMAm0gcAQAAAAAA' + b'AAAAAAAAANgJXVvfxiMv/2Hnw1hC/nnoSgDwUxfx/ryJd+rUKYBNI44AAAAAAAAAAAAAAAAA' + b'7KRHYgnHaRFLyBNLAIC/u4535m3/HgXYGOIIAAAAAAAAAAAAAAAAwF7o2noWj2k/sQQAeNo0' + b'3pPjeHfeOQWwKcQRAAAAAAAAAAAAAAAAgL30SCzhKP01ljByJQD2VH4HTmKXTgFsCnEEAAAA' + b'AAAAAAAAAAAAgPQjlpD/O3bTTywBgH13Ee/CJt6PjVMAm0AcAQAAAAAAAAAAAAAAAOART8QS' + b'TtIilnDqSgDsuGm8/07inThzCuC9iSMAAAAAAAAAAAAAAAAAPEMfS7jp90NRVuMklgDA7hrE' + b'pv17DuBdiSMAAAAAAAAAAAAAAAAAvFLX1jfp8VjCSf8cuBIAW+403m8f4503cQrgPYkjAAAA' + b'AAAAAAAAAAAAAKzII7GE+0jC/cQSANhGV/FOa+I9N3MK4L2IIwAAAAAAAAAAAAAAAACsSdfW' + b't/HI+/Hfth/EEvLPQ1cCYAvkuM+0f38BvAtxBAAAAAAAAAAAAAAAAIA38kgs4TgtYgl5YgkA' + b'bKrTeG99jHfZxCmA9yCOAAAAAAAAAAAAAAAAAPBOuraepfl/4s4TSwBg013Fu6rp318Ab0oc' + b'AQAAAAAAAAAAAAAAAGBDPBJLOEp/jSWMXAmAdzTo31FjpwDemjgCAAAAAAAAAAAAAAAAwIbq' + b'2vouHk0/sQQANsFpvI/O4x3VOAXwlsQRAAAAAAAAAAAAAAAAALbEE7GEk7SIJZy6EgBvYBrv' + b'oOP+vQTwJsQRAAAAAAAAAAAAAAAAALZU/0epN/1+KMpqnMQSAFivQewq9tEpgLcijgAAAAAA' + b'AAAAAAAAAACwQ7q2vkmPxxJO+ufAlQBYgQ/xjpnGe+fWKYC3II4AAAAAAAAAAAAAAAAAsMMe' + b'iSXcRxLuJ5YAwGtN+ncJwNqJIwAAAAAAAAAAAAAAAADskf4/fOflP2h9GEvIPw9dCYBnOo33' + b'yHm8WxqnANZNHAEAAAAAAAAAAAAAAABgjz0SSzhOi1hCnlgCAD+T3x/iCMDaiSMAAAAAAAAA' + b'AAAAAAAA8F9dW8/iMe0nlgDArwzjXXEV748rpwDWSRwBAAAAAAAAAAAAAAAAgCc9Eks4Sn+N' + b'JYxcCWDvfYz3wyTeGXdOAayLOAIAAAAAAAAAAAAAAAAAz9b/4WvTTywBgGwQu4p9dApgXcQR' + b'AAAAAAAAAAAAAAAAAHi1J2IJJ2kRSzh1JYC98CHeAZN4L8ycAlgHcQQAAAAAAAAAAAAAAAAA' + b'VqaPJdz0+6Eoq3ESSwDYB1exS2cA1uHACQAAAAAAAAAAAAAAAABYp66tb2JXaf5Hs//rIgA7' + b'66Ioq2NnANbh0AkAAAAAAAAAAAAAAAAAWIeirMbxuN+piwDshavYpTMAqyaOAAAAAAAAAAAA' + b'AAAAAMBKFGV1khYxhLyBqwDsnYt4H1x1bT1zCmCVxBEAAAAAAAAAAAAAAAAAeJWirI7TPIJw' + b'nsQQAFi4il06A7BK4ggAAAAAAAAAAAAAAAAAPMtSDOF+Q1cB4BEX8c646tp65hTAqogjAAAA' + b'AAAAAAAAAAAAAPCooqyO0iKEcJ7EEAB4vqvYpTMAqyKOAAAAAAAAAAAAAAAAAMB/FWWVIwjj' + b'fiMXAeCVLuKdctW19cwpgFUQRwAAAAAAAAAAAAAAAADYY0VZjdMihnDqIgCs0FXs0hmAVRBH' + b'AAAAAAAAAAAAAAAAANgjRVmdpEUM4cxFAFij83jvHHVtfecUwO8SRwAAAAAAAAAAAAAAAADY' + b'YUVZHcfjPC2CCANXAeCN5HfOx9iVUwC/SxwBAAAAAAAAAAAAAAAAYIf0MYTx0oauAsA7ukzi' + b'CMAKiCMAAAAAAAAAAAAAAAAAbLGirI7SPIJwnsQQANg8w3hXXXZtPXUK4HeIIwAAAAAAAAAA' + b'AAAAAABskaUYwv1GrgLAhvsYmzoD8DvEEQAAAAAAAAAAAAAAAAA2XFFW47SIIZy6CABbZpTf' + b'ZV1b3zgF8FriCAAAAAAAAAAAAAAAAAAbpiirkzQPIZwnMQQAdsNl7MYZgNcSRwAAAAAAAAAA' + b'AAAAAAB4Z0sxhPsNXAWAHXMR77uPXVvfOQXwGuIIAAAAAAAAAAAAAAAAAG+sKKvj9NcYwtBV' + b'ANgDl7GJMwCvIY4AAAAAAAAAAAAAAAAAsGZFWR2leQThPIkhALC/PiZxBOCVxBEAAAAAAAAA' + b'AAAAAAAAVmwphnC/kasAQBrGO3LctfWNUwAvJY4AAAAAAAAAAAAAAAAAsAL5jz3TPIRwnsQQ' + b'AOApl7EbZwBeShwBAAAAAAAAAAAAAAAA4BWKsjpJ8xDCOHbqIgDwLOfxDj3q2vrOKYCXEEcA' + b'AAAAAAAAAAAAAAAAeIY+hjBe2sBVAODF8vszx4WmTgG8hDgCAAAAAAAAAAAAAAAAwCOKsjpO' + b'ixBC/iNOMQQAWI3LJI4AvJA4AgAAAAAAAAAAAAAAAED6EUM4SvMIwrjf0FUAYC1Oc4Soa+uZ' + b'UwDPJY4AAAAAAAAAAAAAAAAA7KU+hjBe2shVAODN5CDRxBmA5xJHAAAAAAAAAAAAAAAAAPZG' + b'UVbjNP9jzPwUQwCA93OZxBGAFxBHAAAAAAAAAAAAAAAAAHZWH0O436mLAMDGGMV7+rhr65lT' + b'AM8hjgAAAAAAAAAAAAAAAADsjKKsTtIihpA3cBUA2FjnsYkzAM8hjgAAAAAAAAAAAAAAAABs' + b'rfwfp9M8gnCexBAAYNtcJnEE4JnEEQAAAAAAAAAAAAAAAICtsRRDuN/QVQBga43yu71r65lT' + b'AL8ijgAAAAAAAAAAAAAAAABsrKKsjtJfYwgjVwGAnXIemzgD8CviCAAAAAAAAAAAAAAAAMBG' + b'Kcoq/5HkOIkhAMA+uEziCMAziCMAAAAAAAAAAAAAAAAA76ooq3FaxBBOXQQA9soovgWOu7ae' + b'OQXwM+IIAAAAAAAAAAAAAAAAwJsqyuokLWIIZy4CAHsvfxNMnQH4GXEEAAAAAAAAAAAAAAAA' + b'YK3yf4OOx3laBBEGrgIALMnfCVNnAH5GHAEAAAAAAAAAAAAAAABYqT6GMF7a0FUAgJ84cwLg' + b'V8QRAAAAAAAAAAAAAAAAgN9SlNVRWoQQ8n9+FkMAAF76PXHetXXjEsBTxBEAAAAAAAAAAAAA' + b'AACAF3kQQ8gbuQoA8JvyN4U4AvAkcQQAAAAAAAAAAAAAAADgl4qyGqdFDOHURQCAFRs7AfAz' + b'4ggAAAAAAAAAAAAAAADA3xRldZLmf6R4nsQQAID1G8X3x3HX1jOnAB4jjgAAAAAAAAAAAAAA' + b'AAAsxxDuN3AVAOCN5W+QqTMAjxFHAAAAAAAAAAAAAAAAgD2U/zNz+msMYegqAMA7y98kU2cA' + b'HiOOAAAAAAAAAAAAAAD8P3t3kJxGdsdx/JXLe3gngBvADdSLtx9uMMoNksVbR1mzUU4Q5gTB' + b'exbkBJFvgE+AdIO8NmBaymjGsgV0059P1b9aG5fFDy/Y8DUA0AMx5WHYfeFwFsQQAIB2qkwA' + b'vEYcAQAAAAAAAAAAAAAAAK5QI4ZwuIlVAICWG5XPMOPtar4xBfCSOAIAAAAAAAAAAAAAAABc' + b'iZhyFXYhhFkQQwAAuqn+LLMwA/CSOAIAAAAAAAAAAAAAAAB0VEx5GnYhhKrcjUUAgCtQf65Z' + b'mAF4SRwBAAAAAAAAAAAAAAAAOmIfQ6gaN7AKAHBlKhMAv0ccAQAAAAAAAAAAAAAAAFoqpjwO' + b'xxDCLIghAADXb1R/Btqu5htTAE3iCAAAAAAAAAAAAAAAANASMeVh2EUQqv2NrAIA9NC03MYM' + b'QJM4AgAAAAAAAAAAAAAAAFzIPoZQNW5iFQCAr5+LlmYAmsQRAAAAAAAAAAAAAAAA4IxiylV5' + b'zIIYAgDAa6YmAF4SRwAAAAAAAAAAAAAAAIAT2scQDndjEQCAP+UzE/B/xBEAAAAAAAAAAAAA' + b'AADgHcWU6//puGrcwCoAAG/+TFVtV/O1JYADcQQAAAAAAAAAAAAAAAD4CTHlcdhFEGZBDAEA' + b'4L3Uwam1GYADcQQAAAAAAAAAAAAAAAB4g0YM4XAjqwAAvLupCYAmcQQAAAAAAAAAAAAAAAD4' + b'AzHlYXgeQ5hYBQDg5MQRgGfEEQAAAAAAAAAAAAAAAOCFmPIsiCEAAFySz2DAM+IIAAAAAAAA' + b'AAAAAAAA9F5MuQrHGMKNRQAA2vEZbbuary0B1MQRAAAAAAAAAAAAAAAA6J2Y8jQcYwi/WAQA' + b'oJXGJgAOxBEAAAAAAAAAAAAAAAC4ejHlcXnMwjGIMLAKAEDrTU0AHIgjAAAAAAAAAAAAAAAA' + b'cHX2MYSqcSOrAAB0jjgC8I04AgAAAAAAAAAAAAAAAJ0XUx6GYwhhFsQQAACugTgC8I04AgAA' + b'AAAAAAAAAAAAAJ3zIoZQ38QqAABXZ1B/7tuu5o+mAMQRAAAAAAAAAAAAAAAA6ISYchWOMYQb' + b'iwAA9MK03NoMgDgCAAAAAAAAAAAAAAAArRRTrr8IV5WbBTEEAIC+EkcAvhJHAAAAAAAAAAAA' + b'AAAAoBUaMYTDDawCANB7QxMANXEEAAAAAAAAAAAAAAAALiKmPA7PYwgjqwAA8EJlAqAmjgAA' + b'AAAAAAAAAAAAAMBZxJTr//W3KjcLYggAAHyfsQmAmjgCAAAAAAAAAAAAAAAAJ9GIIRxuYhUA' + b'AN5IUAv4ShwBAAAAAAAAAAAAAACAdxNTrsIuhDALYggAALzPZ8zpdjV/sAT0mzgCAAAAAAAA' + b'AAAAAAAAP6z+olrYhRCqcjcWAQDgBIYmAMQRAAAAAAAAAAAAAAAA+G77GELVuIFVAAA4sfoz' + b'6NoM0G/iCAAAAAAAAAAAAAAAALwqpjwOxxDCLIghAABwfkMTAOIIAAAAAAAAAAAAAAAAfBNT' + b'rr94VkcQqv2NrAIAwIVVJgDEEQAAAAAAAAAAAAAAAHpsH0OoGjexCgAAAG0jjgAAAAAAAAAA' + b'AAAAANAzMeWqPGZBDAEAgG6YmgAQRwAAAAAAAAAAAAAAALhy+xjC4W4sAgBAxwxMAIgjAAAA' + b'AAAAAAAAAAAAXJmYcv0/61aN82UyAAA6/xl3u5o/WAL6SxwBAAAAAAAAAAAAAACg42LK47CL' + b'IMyCGAIAANdpaALoN3EEAAAAAAAAAAAAAACAjmnEEA43sgoAAFdOHAF6ThwBAAAAAAAAAAAA' + b'AACg5WLK9RfBqsZNrAIAQM9Myy3NAP0ljgAAAAAAAAAAAAAAANBCMeWqPGZBDAEAAADEEQAA' + b'AAAAAAAAAAAAANpgH0M43I1FAADgmakJoN/EEQAAAAAAAAAAAAAAAC4gplx/uava3y8WAQCA' + b'PzQ0AfSbOAIAAAAAAAAAAAAAAMAZxJTH5TELxyDCwCoAAADwfcQRAAAAAAAAAAAAAAAATiim' + b'vAi7GMLIGgAA8MNuTAD9Jo4AAAAAAAAAAAAAAABwWr+aAAAAAH7OBxMAAAAAAAAAAAAAAAAA' + b'AAAAbSaOAAAAAAAAAAAAAAAAAAAAtF5MeWoF6C9xBAAAAAAAAAAAAAAAAAAAoAuGJoD+EkcA' + b'AAAAAAAAAAAAAAAAAAAAWk0cAQAAAAAAAAAAAAAAAAAAAGg1cQQAAAAAAAAAAAAAAAAAAKAL' + b'KhNAf4kjAAAAAAAAAAAAAAAAAAAAAK0mjgAAAAAAAAAAAAAAAAAAAAC0mjgCAAAAAAAAAAAA' + b'AAAAAAAA0GriCAAAAAAAAAAAAAAAAAAAQBcMTQD9JY4AAAAAAAAAAAAAAAAAAAB0wdQE0F/i' + b'CAAAAAAAAAAAAAAAAAAAAECriSMAAAAAAAAAAAAAAAAAAAAArSaOAAAAAAAAAAAAAAAAAAAA' + b'ALTaRxMAAAAAAAAAAAAAAAAAAHTKU7mHcpv9vVTtn9NyA3MBcA3EEQAAAAAAAAAAAAAAAAAA' + b'2u1zufX+Hrar+eZ7/2BMeRh2kYSq3KzcxJwAdJE4AgAAAAAAAAAAAAAAAABAuzyFXQhhWT/f' + b'EkN4qfzZx3AMK9ztYwl1JOG23I2pAegKcQQAAAAAAAAAAAAAAAAAgMurgwh1DGG5Xc2Xp/pL' + b'9rGERX0x5XF53pX71fx0hKAH9Jg4AgAAAAAAAAAAAAAAAADAZZwliPCa8nduyuM2pnwXRBIA' + b'aDlxBAAAAAAAAAAAAAAAAACA8/pUbnGJIMLvaUQSFuV5X27iLQKgbcQRAAAAAAAAAAAAAAAA' + b'AABO73O5RdhFER7b+AuW32tdHtOY8l15/t1bBkCbiCMAAAAAAAAAAAAAAAAAAJzGU7llufvt' + b'av7QlV+6/K53MeX6912UG3gbAWgDcQQAAAAAAAAAAAAAAAAAgPf1qdxyu5ovuvoCyu++jClX' + b'YRd3GHlLAbg0cQQAAAAAAAAAAAAAAAAAgJ/3pdx92EURNtfwgsrreIgpT8uP63ITbzEAlySO' + b'AAAAAAAAAAAAAAAAAADwY57KLcvd1yGBa3yB5XU9xpSrIJAAwIWJIwAAAAAAAAAAAAAAAAAA' + b'vM1/yi3KLet4wLW/2EYgYVNu4O0H4BLEEQAAAAAAAAAAAAAAAAAA/tyXsAsiLLar+aZvL74R' + b'SFgHgQQALkAcAQAAAAAAAAAAAAAAAADgdb+FXRBh3fchygYPMeXb8uO//bMA4Nw+mAAAAAAA' + b'AAAAAAAAAAAA4JnP5f5SLm5X81thhKOyxbI8/mkJAM7towkAAAAAAAAAAAAAAAAAAMKXcvUX' + b'/++3q/nGHK8r+/w1plyVHyfWAOBcxBEAAAAAAAAAAAAAAAAAgD77rdxyu5ovTfEmt+X+awYA' + b'zuWDCQAAAAAAAAAAAAAAAACAnvlc7m/l4nY1vxVGeLuy2UN5/MMSAJzLRxMAAAAAAAAAAAAA' + b'AAAAAD3wVG5R3/6L/fy8+3K35UamAODUxBEAAAAAAAAAAAAAAAAAgGv2KeyCCEtTvK+y6WNM' + b'+a78+C9rAHBq/xOAvfs3ctxa0ziMUskfMQJpI9BkMNegL2UgZSBdg778YywyWHQGh/6puuwM' + b'yAgWjOCQGezHwuiOVtLM7W7+A4jnqXoLLJkf2CWLv/nKCQAAAAAAAAAAAAAAAACAB7OP/TP2' + b'X7WkH4URridu28Vj5xIAXNvXTgAAAAAAAAAAAAAAAAAAPIBj7BRBaGtJW+e4qV9j/3IGAK5J' + b'HAEAAAAAAAAAAAAAAAAAmLJ1LNeSOqe4j7j9ZrFcPcfHD64BwLWIIwAAAAAAAAAAAAAAAAAA' + b'U7OPdafVknrnGIXT+xBHAOBqxBEAAAAAAAAAAAAAAAAAgCk4xnIzBBE2zjEu8U66xXL1W3z8' + b'1jUAuAZxBAAAAAAAAAAAAAAAAABgzJ5jXSzXkg7OMWpt7L+dAYBrEEcAAAAAAAAAAAAAAAAA' + b'AMZmH8uxtpbUO8dkdI04AgBXIo4AAAAAAAAAAAAAAAAAAIzFUyzXkrJTTE+8t8NiuTq9w59c' + b'A4BLE0cAAAAAAAAAAAAAAAAAAO5pF2ubIYpwcI7JO4UtxBEAuDhxBAAAAAAAAAAAAAAAAADg' + b'1o6xLtbWknrneBzxPvNiuTq933euAcAliSMAAAAAAAAAAAAAAAAAALeyjnWnH9A7xUPrYr84' + b'AwCX9JUTAAAAAAAAAAAAAAAAAABXtIv9M7aoJf0ojDAL3jFXs1iuvnEFmKevnQAAAAAAAAAA' + b'AAAAAAAAuLBjM/xAvq0lbZ1jXuKdbxbL1ek78M41uIL3sY0zwPyIIwAAAAAAAAAAAAAAAAAA' + b'l7KO5VpS5xSzd4pj/OQMAFyKOAIAAAAAAAAAAAAAAAAAcI59rG2GKELvHHy0acQRALggcQQA' + b'AAAAAAAAAAAAAAAA4LWOsRxra0lb5+BvnL4f/+MMAFyKOAIAAAAAAAAAAAAAAAAA8FLPsS6W' + b'a0kH5+BzTt+PxXK1i4/fuwYAlyCOAAAAAAAAAAAAAAAAAAB8yb4ZgghdLal3Dl5h04gjAHAh' + b'4ggAAAAAAAAAAAAAAAAAwN95aoYgwsYpeKPTd+cXZwDgEsQRAAAAAAAAAAAAAAAAAIDf7WJt' + b'LNeSDs7BmTZOAMCliCMAAAAAAAAAAAAAAAAAwLztYznW1pJ65+BSToGNxXJ1Cm587xoAnEsc' + b'AQAAAAAAAAAAAAAAAADm6SmWa0nZKbiibSOOAMAFiCMAAAAAAAAAAAAAAAAAwHzsYt1ptaSD' + b'c3ADpzjCT84AwLnEEQAAAAAAAAAAAAAAAADgsR2bT0GErXNwY75zAFyEOAIAAAAAAAAAAAAA' + b'AAAAPKZ1MwQRslNwL/H92yyWK4cA4GziCAAAAAAAAAAAAAAAAADwOPaxNpZrSb1zMBK72PfO' + b'AMA5xBEAAAAAAAAAAAAAAAAAYNqOsRxra0lb52CE+kYcAYAziSMAAAAAAAAAAAAAAAAAwDSt' + b'Y7mW1DkFI3eKdvzgDACcQxwBAAAAAAAAAAAAAAAAAKZjH+tOqyX1zsFEbJ0AgHOJIwAAAAAA' + b'AAAAAAAAAADAuB1juRmCCBvnYIIOTgDAucQRAAAAAAAAAAAAAAAAAGCcnmNdLNeS/LicyTpF' + b'PRbLlUMAcBZxBAAAAAAAAAAAAAAAAAAYj30sx9paUu8cPJBj7J0zAPBW4ggAAAAAAAAAAAAA' + b'AAAAcH9PsVxLyk7Bg9rGPjgDAG8ljgAAAAAAAAAAAAAAAAAA97GLtc0QRTg4BwDA54kjAAAA' + b'AAAAAAAAAAAAAMDtHGNdrK0l9c4BAPAy4ggAAAAAAAAAAAAAAAAAcH3rWFdLyk4BAPB64ggA' + b'AAAAAAAAAAAAAAAAcB27WNcMUYSDcwAAvJ04AgAAAAAAAAAAAAAAAABczjGWY20taesc8G99' + b'7IMzAPBW4ggAAAAAAAAAAAAAAAAAcL51LNeSOqeAv9U7AQDnEEcAAAAAAAAAAAAAAAAAgLfZ' + b'x9pmiCL0zgEAcD3iCAAAAAAAAAAAAAAAAADwcsdYjrW1pK1zwIv5ewHgLOIIAAAAAAAAAAAA' + b'AAAAAPCfPce6WK4lHZwDXs3fDQBnEUcAAAAAAAAAAAAAAAAAgL+3b4YgQldL6p0DzrJ1AgDO' + b'IY4AAAAAAAAAAAAAAAAAAP/fUzMEETZOAZcRf0+HxXLlEAC8mTgCAAAAAAAAAAAAAAAAADTN' + b'LtbG8ulH3M4BV/Ec++AMALyFOAIAAAAAAAAAAAAAAAAAc7WP5VhbS+qdA65OeASANxNHAAAA' + b'AAAAAAAAAAAAAGBunmK5lpSdAm5qG/vBGQB4C3EEAAAAAAAAAAAAAAAAAOZgF+tOqyX51+vh' + b'PrZOwAW8j22cAeZHHAEAAAAAAAAAAAAAAACAR3VsPgUR/Cgb7q93Ai7gGyeAeRJHAAAAAAAA' + b'AAAAAAAAAODRrJshiJCdAsbjFClZLFcOAcCbiCMAAAAAAAAAAAAAAAAA8Aj2sTaWa0m9cwAA' + b'PBZxBAAAAAAAAAAAAAAAAACm6hjLsfb0L9I7B0zCc+yDMwDwWuIIAAAAAAAAAAAAAAAAAEzN' + b'6cfVXS2pcwoAgHkQRwAAAAAAAAAAAAAAAABgCvaxrhmiCL1zAADMizgCAAAAAAAAAAAAAAAA' + b'AGN1jOVmCCJsnAMAYL7EEQAAAAAAAAAAAAAAAAAYm+dYF8u1pINzAAAgjgAAAAAAAAAAAAAA' + b'AADAGOxjOdbWknrnAADgj8QRAAAAAAAAAAAAAAAAALinp1iuJWWnAADgc8QRAAAAAAAAAAAA' + b'AAAAALi1XaxthijCwTkAAPhPxBEAAAAAAAAAAAAAAAAAuIVjrDutlrR1DgAAXkMcAQAAAAAA' + b'AAAAAAAAAIBrWjdDECE7BQAAbyWOAAAAAAAAAAAAAAAAAMCl7WJdM0QRDs4BAMC5xBEAAAAA' + b'AAAAAAAAAAAAuIRjLMfaWtLWOQAAuCRxBAAAAAAAAAAAAAAAAADOsY7lWlLnFAAAXIs4AgAA' + b'AAAAAAAAAAAAAACvtY+1zRBF6J0DAIBrE0cAAAAAAAAAAAAAAAAA4CWOsRzrakkb5wAA4JbE' + b'EQAAAAAAAAAAAAAAAAD4kudYF8u1pINzAABwD+IIAAAAAAAAAAAAAAAAAPzZvhmCCF0tqXcO' + b'AADuTRwBAAAAAAAAAAAAAAAAgN89NUMQYeMUAACMiTgCAAAAAAAAAAAAAAAAwLztYm0s15IO' + b'zgEAwBiJIwAAAAAAAAAAAAAAAADMzz6WY20tqXcOAADGThwBAAAAAAAAAAAAAAAAYD7Wsa6W' + b'lJ0CAIApEUcAAAAAAAAAAAAAAAAAeGy7WNcMUYSDcwAAMEXiCAAAAAAAAAAAAAAAAACP59h8' + b'CiJsnQMAgKkTRwAAAAAAAAAAAAAAAAB4HOtmCCJkpwAA4JGIIwAAAAAAAAAAAAAAAABM2z7W' + b'xnItqXcOAAAekTgCAAAAAAAAAAAAAAAAwPQcYznW1pK2zgEAwKMTRwAAAAAAAAAAAAAAAACY' + b'judYV0vqnAIAgDkRRwAAAAAAAAAAAAAAAAAYt32sa4YoQu8cAADMkTgCAAAAAAAAAAAAAAAA' + b'wPgcY7kZgggb5wAAYO7EEQAAAAAAAAAAAAAAAADG4znWxXIt6eAcAAAwEEcAAAAAAAAAAAAA' + b'AAAAuK99LMfaWlLvHAAA8FfiCAAAAAAAAAAAAAAAAAD38RTLtaTsFADwYt84AcyTOAIAAAAA' + b'AAAAAAAAAADA7exibTNEEQ7OAQCv9t4JYJ7EEQAAAAAAAAAAAAAAAACu6xjrTqslbZ0DAABe' + b'TxwBAAAAAAAAAAAAAAAA4DrWzRBEyE4BAADnEUcAAAAAAAAAAAAAAAAAuJxdrGuGKMLBOQAA' + b'4DLEEQAAAAAAAAAAAAAAAADOc4zlWFtL2joHAABcnjgCAAAAAAAAAAAAAAAAwNusY7mW1DkF' + b'AABclzgCAAAAAAAAAAAAAAAAwMvtY20zRBF65wAAgNsQRwAAAAAAAAAAAAAAAAD4smMsx7pa' + b'0sY5AADg9sQRAAAAAAAAAAAAAAAAAP7ec6yL5VrSwTkAAOB+xBEAAAAAAAAAAAAAAAAAPtk3' + b'QxChqyX1zgEAAOMgjgAAAAAAAAAAAAAAAADQNE/NEETYOAUAAIyPOAIAAAAAAAAAAAAAAAAw' + b'V7tYG8u1pINzAADAeIkjAAAAAAAAAAAAAAAAAHOyj+VYW0vqnQMAAKZBHAEAAAAAAAAAAAAA' + b'AACYg3WsqyVlpwAAgOkRRwAAAAAAAAAAAAAAAAAe1S7WNUMU4eAcAAAwXeIIAAAAAAAAAAAA' + b'AAAAwCM5Np+CCFvnAACAxyCOAAAAAAAAAAAAAAAAADyCdTMEEbJTAADA4xFHAAAAAAAAAAAA' + b'AAAAAKZqH2tjuZbUOwcAADwucQQAAAAAAAAAAAAAAABgSo6xHGtrSVvnAACAeRBHAAAAAAAA' + b'AAAAAAAAAKbgOdbVkjqnAACA+RFHAAAAAAAAAAAAAAAAAMZqH+uaIYrQOwcAAMyXOAIAAAAA' + b'AAAAAAAAAAAwJsdYboYgwsY5AACAE3EEAAAAAAAAAAAAAAAAYAyeY10s15IOzgEAAPyROAIA' + b'AAAAAAAAAAAAAABwL/tYjrW1pN45AACAzxFHAAAAAAAAAAAAAAAAAG7tKZZrSdkpAACAlxBH' + b'AAAAAAAAAAAAAAAAAG5hF2ubIYpwcA4AAOA1xBEAAAAAAAAAAAAAAACAaznGutNqSVvnAAAA' + b'3kocAQAAAAAAAAAAAAAAALi0dTMEEbJTAAAAlyCOAAAAAAAAAAAAAAAAAFzCLtY1QxTh4BwA' + b'AMAliSMAAAAAAAAAAAAAAAAAb3WM5VhbS9o6BwAAcC3iCAAAAAAAAAAAAAAAAMBrrWO5ltQ5' + b'BQAAcAviCAAAAAAAAAAAAAAAAMBL7GNtM0QReucAAABuSRwBAAAAAAAAAAAAAAAA+JxjLMe6' + b'WtLGOQAAgHsRRwAAAAAAAAAAAAAAAAD+7DnWxXIt6eAcAADAvYkjAAAAAAAAAAAAAAAAACf7' + b'ZggidLWk3jkAAIAxEUcAAAAAAAAAAAAAAACAeXuK5VpSdgoAYAI+OAHMkzgCAAAAAAAAAAAA' + b'AAAAzM8u1jZDFOHgHAAAwNiJIwAAAAAAAAAAAAAAAMA87GM51taSeucAAACmRBwBAAAAAAAA' + b'AAAAAAAAHts61tWSslMAAABTJY4AAAAAAAAAAAAAAAAAj2cX65ohinBwDgAAYOrEEQAAAAAA' + b'AAAAAAAAAOAxHJtPQYStcwAAAI9EHAEAAAAAAAAAAAAAAACmbR3LtaTOKQAAgEcljgAAAAAA' + b'AAAAAAAAAADTs4+1zRBF6J0DgAnpYhtnAOC1xBEAAAAAAAAAAAAAAABgGo6xHGtrSVvnAGCK' + b'4v9hnSsA8BbiCAAAAAAAAAAAAAAAADBuz7HOj0kBAIA5E0cAAAAAAAAAAAAAAACA8dnHumaI' + b'IvTOAQAAzJ04AgAAAAAAAAAAAAAAAIzDMZabIYiwcQ4AAIBPxBEAAAAAAAAAAAAAAADgvnax' + b'NpZrSQfnAAAA+CtxBAAAAAAAAAAAAAAAALi9fSzH2lpS7xwAAABfJo4AAAAAAAAAAAAAAAAA' + b't/MUy7Wk7BQAAAAvJ44AAAAAAAAAAAAAAAAA17WLtc0QRTg4BwAAwOuJIwAAAAAAAAAAAAAA' + b'AMDlHWPdabWkrXMAAACcRxwBAAAAAAAAAAAAAAAALmfdDEGE7BQAAACXI44AAAAAAAAAAAAA' + b'AAAA59nH2maIIhycAwAA4PLEEQAAAAAAAAAAAAAAAOD1jrEca2tJW+cAAAC4LnEEAAAAAAAA' + b'AAAAAAAAeLl1LNeSOqcAAAC4HXEEAAAAAAAAAAAAAAAA+LJ9rG2GKELvHAAAALcnjgAAAAAA' + b'AAAAAAAAAAB/dYzlWFdL2jgHAADAfYkjAAAAAAAAAAAAAAAAwCfPsS6Wa0kH5wAAABgHcQQA' + b'AAAAAAAAAAAAAADmbt8MQYSultQ7BwAAwPiIIwAAAAAAAAAAAAAAADBXT7FcS8pOAQAAMG7i' + b'CAAAAAAAAAAAAAAAAMzJLtY2QxTh4BwAAADTII4AAAAAAAAAAAAAAADAo9vHcqytJfXOAQAA' + b'MD3iCAAAAAAAAAAAAAAAADyqdayrJWWnAAAAmDZxBAAAAAAAAAAAAAAAAB7JLtY1QxTh4BwA' + b'AACPQRwBAAAAAAAAAAAAAACAqTs2n4IIW+cAAAB4POIIAAAAAAAAAAAAAAAATNU6lmtJnVMA' + b'AAA8NnEEAAAAAAAAAAAAAAAApmQfa5shitA7BwAAwDyIIwAAAAAAAAAAAAAAADB2x1iOtbWk' + b'rXMAAADMjzgCAAAAAAAAAAAAAAAAY/Uc62pJnVMAAADMmzgCAAAAAAAAAAAAAAAAY7KPdc0Q' + b'ReidAwAAgBNxBAAAAAAAAAAAAAAAAO7tGMvNEETYOAcAAAB/Jo4AAAAAAAAAAAAAAADAvexi' + b'bSzXkg7OAQAAwOeIIwAAAAAAAAAAAAAAAHBL+1iOtbWk3jkAAAB4CXEEAAAAAAAAAAAAAAAA' + b'buEplmtJ2SkAAAB4LXEEAAAAAAAAAAAAAAAArmUXa5shinBwDgAAAN5KHAEAAAAAAAAAAAAA' + b'AIBLOsa602pJW+cAAADgEsQRAAAAAAAAAAAAAAAAuIR1MwQRslMAAABwaeIIAAAAAAAAAAAA' + b'AAAAvNU+1jZDFOHgHAAAAFyLOAIAAAAAAAAAAAAAAACvcYzlWFtL2joHAAAAtyCOAAAAAAAA' + b'AAAAAAAAwEusY7mW1DkFAAAAtyaOAAAAAAAAAAAAAAAAwOfsY20zRBF65wAAAOBexBEAAAAA' + b'AAAAAAAAAAD4o2Msx7pa0sY5AAAAGANxBAAAAAAAAAAAAAAAAE6eY10s15IOzgEAAMCYiCMA' + b'AAAAAAAAAAAAAADM174ZgghdLal3DgAAAMZKHAEAAAAAAAAAAAAAAGB+nmK5lpSdAgAAgCkQ' + b'RwAAAAAAAAAAAAAAAJiHXaxthijCwTkAAACYEnEEAAAAAAAAAAAAAACAx7WP5VhbS+qdAwAA' + b'gKkSRwAAAAAAAAAAAAAAAHg861hXS8pOAQAAwCMQRwAAAAAAAAAAAAAAAHgMu1jXDFGEg3MA' + b'AADwSMQRAAAAAAAAAAAAAAAApuvYfAoibJ0DAACARyWOAAAAAAAAAAAAAAAAMD3rWK4ldU4B' + b'AADAHIgjAAAAAAAAAAAAAAAATMM+1jZDFKF3DgAAAOZEHAEAAAAAAAAAAAAAAGC8jrEca2tJ' + b'W+cAAABgrsQRAAAAAAAAAAAAAAAAxuc51tWSOqcAAAAAcQQAAAAAAAAAAAAAAICx2Me6Zogi' + b'9M4BAAAAn4gjAAAAAAAAAAAAAAAA3M8xlpshiLBxDgAAAPh74ggAAAAAAAAAAAAAAAC3t4u1' + b'sVxLOjgHAAAAfJk4AgAAAAAAAAAAAAAAwG3sYznW1pJ65wAAAICXE0cAAAAAAAAAAAAAAAC4' + b'rqdYriVlpwAAAIC3EUcAAAAAAAAAAAAAAAC4olrSz64AAAAA5/nKCQAAAAAAAAAAAAAAAAAA' + b'AIAxE0cAAAAAAAAAAAAAAAAAAAAARk0cAQAAAAAAAAAAAAAAAAAAABg1cQQAAAAAAAAAAAAA' + b'AAAAAABg1MQRAAAAAAAAAAAAAAAAAAAAgFETRwAAAAAAAAAAAAAAAAAAAABGTRwBAAAAAAAA' + b'AAAAAAAAAAAAGDVxBAAAAAAAAAAAAAAAAAAAAGDUxBEAAAAAAAAAAAAAAAAAAACAURNHAAAA' + b'AAAAAAAAAAAAAAAAAEZNHAEAAAAAAAAAAAAAAAAAAAAYNXEEAAAAAAAAAAAAAAAAAAAAYNTE' + b'EQAAAAAAAAAAAAAAAAAAAIBRE0cAAAAAAAAAAAAAAAAAAAAARk0cAQAAAAAAAAAAAAAAAAAA' + b'ABg1cQQAAAAAAAAAAAAAAAAAAABg1MQRAAAAAAAAAAAAAAAAAAAAgFETRwAAAAAAAAAAAAAA' + b'AAAAAABGTRwBAAAAAAAAAAAAAAAAAAAAGDVxBAAAAAAAAAAAAAAAAAAAAGDUvnYCAAAAAAAA' + b'AAAAAAAAOM9iufomHu//9J//4TIAXFhfS+qcAYA5EkcAAAAAAAAAAAAAAACAL1gsV//4+PHP' + b'z+9i37oQADf0HOucAYA5EkcAAAAAAAAAAAAAAACA5t8RhPfNED14/3HvXAYAAOD+xBEAAAAA' + b'AAAAAAAAAACYnT+EEE47ff7WVQAAAMZLHAEAAAAAAAAAAAAAAICH9zGG8Ps+uAgAAMC0iCMA' + b'AAAAAAAAAAAAAADwcBbL1Xfx+LEZYgg/uAgAAMC0iSMAAAAAAAAAAAAAAADwEBbL1ft4/NwM' + b'UYRvXQQAAOBxiCMAAAAAAAAAAAAAAAAwWYIIAAAA8yCOAAAAAAAAAAAAAAAAwKQslqvvmiGG' + b'8GsjiAAAADAL4ggAAAAAAAAAAAAAAABMwmK5OgURfo794BoAAADzIo4AAAAAAAAAAAAAAADA' + b'aC2Wq2+aIYjwa+xbFwEAAJgncQQAAAAAAAAAAAAAAABGZ7Fcfdd8iiK8cxEAAIB5E0cAAAAA' + b'AAAAAAAAAABgND5GEX6L/eQaAAAA/E4cAQAAAAAAAAAAAAAAgLsTRQAAAOBLxBEAAAAAAAAA' + b'AAAAAAC4m8Vy9U082kYUAQAAgC8QRwAAAAAAAAAAAAAAAODmPkYRfv24dy4CAADAl4gjAAAA' + b'AAAAAAAAAAAAcFOL5erHeLSxb10DAACAlxBHAAAAAAAAAAAAAAAA4CYWy9V38ehiH1wDAACA' + b'1/jKCQAAAAAAAAAAAAAAALi2xXL1Wzz+txFGAAAA4A2+dgIAAAAAAAAAAAAAAACuZbFc/SMe' + b'bex71wAAAOCtxBEAAAAAAAAAAAAAAAC4isVydYoi/OISAAAAnEscgf9j5w6O28buOI5rPL6L' + b'rwKxA7ID4fDuRgdWKljt4Z2jnHmIUkGUCiLfeaAriNwBtwLaHeQp1mazWZMEQQJ4ID+fGczf' + b's+M1MT/cvwAAAAAAAAAAAAAAACcVYprn85SfmTUAAAA4hXcmAAAAAAAAAAAAAAAA4FRCTPf5' + b'/OtKGAEAAIATem8CAAAAAAAAAAAAAAAAjhVimuTzlJ8P1gAAAODUxBEAAAAAAAAAAAAAAAA4' + b'Sohpns9zfm6sAQAAQBfemQAAAAAAAAAAAAAAAIC2Qkx3+ayuhBEAAADo0HsTAAAAAAAAAAAA' + b'AAAA0EaI6TGfnywBAABA18QRAAAAAAAAAAAAAAAAOEiIaZLPU34+WAMAAIA+iCMAAAAAAAAA' + b'AAAAAADQ2FsYYZWfmTUAAADoyzsTAAAAAAAAAAAAAAAA0ESIaZ7P+koYAQAAgJ6JIwAAAAAA' + b'AAAAAAAAALDXWxhhlZ9rawAAANA3cQQAAAAAAAAAAAAAAAB2CjHdXQkjAAAAMKD3JgAAAAAA' + b'AAAAAAAAAGCbtzDC3y0BAADAkN6ZAAAAAAAAAAAAAAAAgB8RRgAAAKAU4ggAAAAAAAAAAAAA' + b'AAD8gTACAAAAJRFHAAAAAAAAAAAAAAAA4HeEEQAAACiNOAIAAAAAAAAAAAAAAAD/JYwAAABA' + b'icQRAAAAAAAAAAAAAAAA+I8Q0zyfR0sAAABQGnEEAAAAAAAAAAAAAAAAfg0jrPJzbQ0AAABK' + b'I44AAAAAAAAAAAAAAABw4UJM0ythBAAAAAomjgAAAAAAAAAAAAAAAHDBQkyTfJ6vhBEAAAAo' + b'mDgCAAAAAAAAAAAAAADAZVvlZ2YGAAAASiaOAAAAAAAAAAAAAAAAcKFCTE9XwggAAACMgDgC' + b'AAAAAAAAAAAAAADABQox3efz0RIAAACMgTgCAAAAAAAAAAAAAADAhQkxVfn81RIAAACMhTgC' + b'AAAAAAAAAAAAAADABQkxTfN5tgQAAABjIo4AAAAAAAAAAAAAAABwWV7DCNdmAAAAYEzEEQAA' + b'AAAAAAAAAAAAAC5EiOkxn5klAAAAGBtxBAAAAAAAAAAAAAAAgAsQYqrz+ckSAAAAjJE4AgAA' + b'AAAAAAAAAAAAwJkLMU3zebIEAAAAYyWOAAAAAAAAAAAAAAAAcP6e83NtBgAAAMZKHAEAAAAA' + b'AAAAAAAAAOCMhZge8plZAgAAgDETRwAAAAAAAAAAAAAAADhTIaZ5Pn+2BAAAAGMnjgAAAAAA' + b'AAAAAAAAAHC+nkwAAADAORBHAAAAAAAAAAAAAAAAOEMhpod8ZpYAAADgHIgjAAAAAAAAAAAA' + b'AAAAnJkQ0zyfP1sCAACAcyGOAAAAAAAAAAAAAAAAcH6eTAAAAMA5EUcAAAAAAAAAAAAAAAA4' + b'IyGm+3xmlgAAAOCciCMAAAAAAAAAAAAAAACciRDTJJ8HSwAAAHBuxBEAAAAAAAAAAAAAAADO' + b'x2N+rs0AAADAuRFHAAAAAAAAAAAAAAAAOAMhpiqfj5YAAADgHIkjAAAAAAAAAAAAAAAAnIdH' + b'EwAAAHCuxBEAAAAAAAAAAAAAAABGLsR0l8/MEgAAAJwrcQQAAAAAAAAAAAAAAIARCzFN8nmw' + b'BAAAAOdMHAEAAAAAAAAAAAAAAGDc7vNzYwYAAADOmTgCAAAAAAAAAAAAAADASIWYJlff4wgA' + b'AABw1sQRAAAAAAAAAAAAAAAAxushP9dmAAAA4NyJIwAAAAAAAAAAAAAAAIxQiGmaz0+WAAAA' + b'4BKIIwAAAAAAAAAAAAAAAIzTgwkAAAC4FOIIAAAAAAAAAAAAAAAAIxNimubz0RIAAABcCnEE' + b'AAAAAAAAAAAAAACA8XkwAQAAAJdEHAEAAAAAAAAAAAAAAGBEQkzTfD5aAgAAgEsijgAAAAAA' + b'AAAAAAAAADAuDyYAAADg0ogjAAAAAAAAAAAAAAAAjESIaZLPR0sAAABwacQRAAAAAAAAAAAA' + b'AAAAxuPeBAAAAFwicQQAAAAAAAAAAAAAAIARCDFNrsQRAAAAuFDiCAAAAAAAAAAAAAAAAONw' + b'l59rMwAAAHCJxBEAAAAAAAAAAAAAAADG4d4EAAAAXCpxBAAAAAAAAAAAAAAAgMKFmOp8biwB' + b'AADApRJHAAAAAAAAAAAAAAAAKN+9CQAAALhk4ggAAAAAAAAAAAAAAAAFCzFN87m1BAAAAJdM' + b'HAEAAAAAAAAAAAAAAKBs9yYAAADg0okjAAAAAAAAAAAAAAAAlO3OBAAAAFw6cQQAAAAAAAAA' + b'AAAAAIBChZju8rm2BAAAAJdOHAEAAAAAAAAAAAAAAKBcdyYAAAAAcQQAAAAAAAAAAAAAAIAi' + b'hZim+dxaAgAAAMQRAAAAAAAAAAAAAAAASnVvAgAAAPhOHAEAAAAAAAAAAAAAAKBMtQkAAADg' + b'O3EEAAAAAAAAAAAAAACAwoSYqnxuLAEAAADfiSMAAAAAAAAAAAAAAACU584EAAAA8BtxBAAA' + b'AAAAAAAAAAAAgPLUJgAAAIDfiCMAAAAAAAAAAAAAAAAUJMT0Gka4tgQAAAD8RhwBAAAAAAAA' + b'AAAAAACgLLUJAAAA4PfEEQAAAAAAAAAAAAAAAMoijgAAAAD/RxwBAAAAAAAAAAAAAACgECGm' + b'1zDCtSUAAADg98QRAAAAAAAAAAAAAAAAylGbAAAAAP5IHAEAAAAAAAAAAAAAAKAc4ggAAADw' + b'A+IIAAAAAAAAAAAAAAAABQgxVflcWwIAAAD+SBwBAAAAAAAAAAAAAACgDLUJAAAA4MfEEQAA' + b'AAAAAAAAAAAAAMogjgAAAABbiCMAAAAAAAAAAAAAAAAMLMQ0zefGEgAAAPBj4ggAAAAAAAAA' + b'AAAAAADDq00AAAAA24kjAAAAAAAAAAAAAAAADK8yAQAAAGwnjgAAAAAAAAAAAAAAADC8DyYA' + b'AACA7cQRAAAAAAAAAAAAAAAABhRiqqwAAAAAu4kjAAAAAAAAAAAAAAAADKs2AQAAAOwmjgAA' + b'AAAAAAAAAAAAADCsygQAAACwmzgCAAAAAAAAAAAAAADAQEJMk3xmlgAAAIDdxBEAAAAAAAAA' + b'AAAAAACGU5kAAAAA9hNHAAAAAAAAAAAAAAAAGE5lAgAAANhPHAEAAAAAAAAAAAAAAGA4lQkA' + b'AABgP3EEAAAAAAAAAAAAAACA4cxMAAAAAPuJIwAAAAAAAAAAAAAAAAwgxFRZAQAAAJoRRwAA' + b'AAAAAAAAAAAAABhGZQIAAABoRhwBAAAAAAAAAAAAAABgGHMTAAAAQDPiCAAAAAAAAAAAAAAA' + b'AMOoTAAAAADNiCMAAAAAAAAAAAAAAAD0LMQ0zefaEgAAANCMOAIAAAAAAAAAAAAAAED/5iYA' + b'AACA5sQRAAAAAAAAAAAAAAAA+ieOAAAAAAcQRwAAAAAAAAAAAAAAAOhfZQIAAABoThwBAAAA' + b'AAAAAAAAAACgf3MTAAAAQHPiCAAAAAAAAAAAAAAAAD0KMU3zubYEAAAANCeOAAAAAAAAAAAA' + b'AAAA0K+5CQAAAOAw4ggAAAAAAAAAAAAAAAD9EkcAAACAA4kjAAAAAAAAAAAAAAAA9EscAQAA' + b'AA4kjgAAAAAAAAAAAAAAANCvqQkAAADgMOIIAAAAAAAAAAAAAAAA/ZqZAAAAAA4jjgAAAAAA' + b'AAAAAAAAANCTENPcCgAAAHA4cQQAAAAAAAAAAAAAAID+TE0AAAAAhxNHAAAAAAAAAAAAAAAA' + b'6M/cBAAAAHA4cQQAAAAAAAAAAAAAAID+iCMAAABAC+IIAAAAAAAAAAAAAAAA/ZmYAAAAAA4n' + b'jgAAAAAAAAAAAAAAANCfWxMAAADA4cQRAAAAAAAAAAAAAAAAehBimlgBAAAA2hFHAAAAAAAA' + b'AAAAAAAA6MfcBAAAANCOOAIAAAAAAAAAAAAAAEA/JiYAAACAdsQRAAAAAAAAAAAAAAAA+jE3' + b'AQAAALQjjgAAAAAAAAAAAAAAANCPqQkAAACgHXEEAAAAAAAAAAAAAACAfkxNAAAAAO2IIwAA' + b'AAAAAAAAAAAAAPRjYgIAAABoRxwBAAAAAAAAAAAAAACgHzMTAAAAQDviCAAAAAAAAAAAAAAA' + b'AAAAAEDRxBEAAAAAAAAAAAAAAAA6FmKqrAAAAADtiSMAAAAAAAAAAAAAAAAAAAAARRNHAAAA' + b'AAAAAAAAAAAA6N7cBAAAANCeOAIAAAAAAAAAAAAAAED3JiYAAACA9sQRAAAAAAAAAAAAAAAA' + b'AAAAgKKJIwAAAAAAAAAAAAAAAHSvMgEAAAC0J44AAAAAAAAAAAAAAAAAAAAAFE0cAQAAAAAA' + b'AAAAAAAAoHsTEwAAAEB74ggAAAAAAAAAAAAAAADdm5kAAAAA2hNHAAAAAAAAAAAAAAAAAAAA' + b'AIomjgAAAAAAAAAAAAAAAAAAAAAUTRwBAAAAAAAAAAAAAACgQyGmygoAAABwHHEEAAAAAAAA' + b'AAAAAAAAAAAAoGjiCAAAAAAAAAAAAAAAAAAAAEDRxBEAAAAAAAAAAAAAAAC6NTEBAAAAHEcc' + b'AQAAAAAAAAAAAAAAoFtzEwAAAMBxxBEAAAAAAAAAAAAAAAAAAACAookjAAAAAAAAAAAAAAAA' + b'AAAAAEUTRwAAAAAAAAAAAAAAAAAAAACKJo4AAAAAAAAAAAAAAADQrYkJAAAA4DjiCAAAAAAA' + b'AAAAAAAAAN2amwAAAACOI44AAAAAAAAAAAAAAAAAAAAAFE0cAQAAAAAAAAAAAAAAAAAAACia' + b'OAIAAAAAAAAAAAAAAAAAAABQNHEEAAAAAAAAAAAAAAAAAAAAoGjiCAAAAAAAAAAAAAAAAAAA' + b'AEDRxBEAAAAAAAAAAAAAAAC6NTEBAAAAHEccAQAAAAAAAAAAAAAAoFszEwAAAMBxxBEAAAAA' + b'AAAAAAAAAAAAAACAookjAAAAAAAAAAAAAAAAAAAAAEUTRwAAAAAAAAAAAAAAAAAAAACKJo4A' + b'AAAAAAAAAAAAAAAAAAAAFE0cAQAAAAAAAAAAAAAAAAAAACiaOAIAAAAAAAAAAAAAAAAAAABQ' + b'NHEEAAAAAAAAAAAAAAAAAAAAoGjiCAAAAAAAAAAAAAAAAAAAAEDRxBEAAAAAAAAAAAAAAAAA' + b'AACAookjAAAAAAAAAAAAAAAAAAAAAEUTRwAAAAAAAAAAAAAAAAAAAACKJo4AAAAAAAAAAAAA' + b'AAAAAAAAFE0cAQAAAAAAAAAAAAAAAAAAACiaOAIAAAAAAAAAAAAAAAAAAABQNHEEAAAAAAAA' + b'AAAAAAAAAAAAoGjiCAAAAAAAAAAAAAAAAAAAAEDRxBEAAAAAAAAAAAAAAAAAAACAookjAAAA' + b'AAAAAAAAAAAAAAAAAEUTRwAAAAAAAAAAAAAAAAAAAACKJo4AAAAAAAAAAAAAAAAAAAAAFO29' + b'CQAAAAAAAAAAAAAAAGCUvuTn6w/++zQ/N+YBAADOiTgCAAAAAAAAAAAAAAAAjMNrDOEpP6vN' + b'cvGy7y+HmKp86rdHLAEAABg1cQQAAAAAAAAAAAAAAAAo2+f8PGyWi9Uh/9Pb33997t9CCQ/5' + b'uTUnAAAwRuIIAAAAAAAAAAAAAAAAUKZvV9+jCI/H/kNvoYTqLZLw+u/NzAsAAIzJOxMAAAAA' + b'AAAAAAAAAABAcV7DCNUpwgj/6zWSkJ95/uPfTAwAAIyJOAIAAAAAAAAAAAAAAACU5dcwwktX' + b'P5D/7ft8/vT2WwAAAMUTRwAAAAAAAAAAAAAAAICydBpG+FX+jafX37oSSAAAAEbgvQkAAAAA' + b'AAAAAAAAAACgGD/3EUb41etvhZiq/MdVfq7NTwN/MUFjd/m5MQMAwGmIIwAAAAAAAAAAAAAA' + b'AEAZvmyWi8e+f/QtkHCX//hPn4AGnvsMeIzZW3hEHAEA4ETemQAAAAAAAAAAAAAAAACKcD/U' + b'D2+Wi+d8fvYJaKAyAQAAQxBHAAAAAAAAAAAAAAAAgOF92SwXqyFfIP/+Yz6ffAr2qE0AAMAQ' + b'xBEAAAAAAAAAAAAAAABgeI+FvMddfr75HOxwG2KamAEAgL6JIwAAAAAAAAAAAAAAAMDwnkt4' + b'ic1y8TWfe5+DPSoTAADQN3EEAAAAAAAAAAAAAAAAGNantyhBEfK7POXz2Wdhh9oEAAD0TRwB' + b'AAAAAAAAAAAAAAAAhrUq8J0efBZ2qEwAAEDfxBEAAAAAAAAAAAAAAABgWKvSXmizXLy+0z98' + b'Gra4CTHNzQAAQJ/EEQAAAAAAAAAAAAAAAGBAm+XipdBXe/B12KEyAQAAfRJHAAAAAAAAAAAA' + b'AAAAgOF8LvXFNsvFOp9PPhFb1CYAAKBP4ggAAAAAAAAAAAAAAAAwnHXh7/foE7HFbYhpYgYA' + b'APoijgAAAAAAAAAAAAAAAADDWZf8cpvlYpXPLz4TW1QmAACgL+IIAAAAAAAAAAAAAAAAMJz1' + b'CN7x0Wdii9oEAAD0RRwBAAAAAAAAAAAAAAAAhrMewTs++UxsUZkAAIC+iCMAAAAAAAAAAAAA' + b'AAAAW22Wi6/5fLIEP3ATYpqbAQCAPogjAAAAAAAAAAAAAAAAAPs8m4AtKhMAANAHcQQAAAAA' + b'AAAAAAAAAAAYznok77nyqdiiNgEAAH0QRwAAAAAAAAAAAAAAAICBbJaL9Yje8xdfjB+4DTFN' + b'zAAAQNfEEQAAAAAAAAAAAAAAAIAmViZgi8oEAAB0TRwBAAAAAAAAAAAAAAAAaOLFBGxRmwAA' + b'gK6JIwAAAAAAAAAAAAAAAABNiCOwTWUCAAC6Jo4AAAAAAAAAAAAAAAAA7LVZLlZWYIubENPc' + b'DAAAdEkcAQAAAAAAAAAAAAAAAGjqiwnYojIBAABdEkcAAAAAAAAAAAAAAAAAmlqbgC1qEwAA' + b'0CVxBAAAAAAAAAAAAAAAAKCpFxOwxW2IaWIGAAC6Io4AAAAAAAAAAAAAAAAANLUyATtUJgAA' + b'oCviCAAAAAAAAAAAAAAAAEBTaxOwQ20CAAC6Io4AAAAAAAAAAAAAAAAANLJZLtb5fLMEW1Qm' + b'AACgK+IIAAAAAAAAAAAAAAAAwCFeTMAWNyGmuRkAAOiCOAIAAAAAAAAAAAAAAABwCHEEdqlM' + b'AABAF8QRAAAAAAAAAAAAAAAAgEOsTcAOtQkAAOiCOAIAAAAAAAAAAAAAAABwiBcTsMNtiGli' + b'BgAATk0cAQAAAAAAAAAAAAAAADiEOAL7VCYAAODUxBEAAAAAAAAAAAAAAACAxjbLxdd8vlmC' + b'HWoTAABwauIIAAAAAAAAAAAAAAAAwKFeTMAOlQkAADg1cQQAAAAAAAAAAAAAAADgUOII7HIT' + b'YpqbAQCAUxJHAAAAAAAAAAAAAAAAAA61NgF7VCYAAOCUxBEAAAAAAAAAAAAAAACAQ72YgD1q' + b'EwAAcEriCAAAAAAAAAAAAAAAAMCh1iZgj1sTAABwSuIIAAAAAAAAAAAAAAAAwEE2y8XaCuwT' + b'YqqtAADAqYgjAAAAAAAAAAAAAAAAAG18NgF7VCYAAOBUxBEAAAAAAAAAAAAAAACANtYmYI/a' + b'BAAAnIo4AgAAAAAAAAAAAAAAANDG2gTscRNimpoBAIBTEEcAAAAAAAAAAAAAAAAA2ngxAQ3U' + b'JgAA4BTEEQAAAAAAAAAAAAAAAIA21iaggcoEAACcgjgCAAAAAAAAAAAAAAAAcLDNcvFiBRr4' + b'YAIAAE5BHAEAAAAAAAAAAAAAAABo6xcTsE+IqbYCAADHEkcAAAAAAAAAAAAAAAAA2lqbgAYq' + b'EwAAcCxxBAAAAAAAAAAAAAAAAKCtlQlooDYBAADHEkcAAAAAAAAAAAAAAAAA2vpqAhq4CTFN' + b'zQAAwDHEEQAAAAAAAAAAAAAAAIC2XkxAQ7UJAAA4hjgCAAAAAAAAAAAAAAAA0NbaBDRUmQAA' + b'gGOIIwAAAAAAAAAAAAAAAACtbJaLtRVo6IMJAAA4hjgCAAAAAAAAAAAAAAAAcIzPJqCJEFNt' + b'BQAA2hJHAAAAAAAAAAAAAAAAAKAPlQkAAGhLHAEAAAAAAAAAAAAAAAA4xtoENFSbAACAtsQR' + b'AAAAAAAAAAAAAAAAgGOsTUBDNyGmqRkAAGhDHAEAAAAAAAAAAAAAAACAvtQmAACgDXEEAAAA' + b'AAAAAAAAAAAA4BgvJuAAlQkAAGhDHAEAAAAAAAAAAAAAAAA4xlcTcIAPJgAAoA1xBADg3+zc' + b'O5GDUACFYQk7KAgOYuEW9LlmUIElCiSwDogCNg42KTKTyeTBfdB9X3P6X8ABAAAAAAAAAAAA' + b'AMi2jsOkAimaro8qAACQyjkCAAAAAAAAAAAAAAAAUOoiAQmCBAAApHKOAAAAAAAAAAAAAAAA' + b'AJSaJSBBlAAAgFTOEQAAAAAAAAAAAAAAAIBSiwQkODRd38oAAEAK5wgAAAAAAAAAAAAAAABA' + b'qUUCEkUJAABI4RwBAAAAAAAAAAAAAAAAKDVJQKIgAQAAKZwjAAAAAAAAAAAAAAAAAKUWCUh0' + b'kgAAgBTOEQAAAAAAAAAAAAAAAIAi6zgs17koQYqm66MKAABs5RwBAAAAAAAAAAAAAAAAqGGW' + b'gERBAgAAtnKOAAAAAAAAAAAAAAAAANTgHIFUUQIAALZyjgAAAAAAAAAAAAAAAADU4ByBVIem' + b'61sZAADYwjkCAAAAAAAAAAAAAAAAUMMiARmiBAAAbOEcAQAAAAAAAAAAAAAAACi2jsOkAhmC' + b'BAAAbOEcAQAAAAAAAAAAAAAAAKjlVwISnSQAAGAL5wgAAAAAAAAAAAAAAABALYsEpGq6PqoA' + b'AMA3zhEAAAAAAAAAAAAAAACAWmYJyBAkAADgG+cIAAAAAAAAAAAAAAAAQC2TBGSIEgAA8I1z' + b'BAAAAAAAAAAAAAAAAKCWWQIyHJqub2UAAOAT5wgAAAAAAAAAAAAAAABAFes4/F3nrAQZogQA' + b'AHziHAEAAAAAAAAAAAAAAACoaZaADEECAAA+cY4AAAAAAAAAAAAAAAAA1OQcgRwnCQAA+MQ5' + b'AgAAAAAAAAAAAAAAAFCTcwSyNF0fVQAA4B3nCAAAAAAAAAAAAAAAAEBNzhHIFSQAAOAd5wgA' + b'AAAAAAAAAAAAAABANes4LNe5KEGGIAEAAO84RwAAAAAAAAAAAAAAAABqmyUgw7Hp+lYGAABe' + b'cY4AAAAAAAAAAAAAAAAA1DZJQKYgAQAArzhHAAAAAAAAAAAAAAAAAGqbJSBTlAAAgFecIwAA' + b'AAAAAAAAAAAAAAC1OUcgV5AAAIBXnCMAAAAAAAAAAAAAAAAAVa3jsFznrAQZfpquDzIAAPDM' + b'OQIAAAAAAAAAAAAAAACwh1kCMkUJAAB45hwBAAAAAAAAAAAAAAAA2INzBHIFCQAAeOYcAQAA' + b'AAAAAAAAAAAAANjDJAGZjk3XtzIAAPDIOQIAAAAAAAAAAAAAAACwh1kCCgQJAAB45BwBAAAA' + b'AAAAAAAAAAAAqG4dh7/rnJUgU5QAAIBHzhEAAAAAAAAAAAAAAACAvcwSkClIAADAI+cIAAAA' + b'AAAAAAAAAAAAwF6cI5Drp+n6IAMAAHfOEQAAAAAAAAAAAAAAAIC9TBJQIEoAAMCdcwQAAAAA' + b'AAAAAAAAAABgF+s4TCpQIEgAAMCdcwQAAAAAAAAAAAAAAABgT78SkOnYdH0rAwAAN84RAAAA' + b'AAAAAAAAAAAAgD3NElAgSAAAwI1zBAAAAAAAAAAAAAAAAGBPzhEoESUAAODmXwD27iC3jWPB' + b'4zARZO/wBPY7QfROEAbgPrpB6BOMsuA69LoWo5zgUTeg9gVM6wbkCYY8QZE3mGLUmTi2ZFsi' + b'u9nd9X1AoYz3EFv4Uewd/zSOAAAAAAAAAAAAAAAAADSpkoATTCQAAODIOAIAAAAAAAAAAAAA' + b'AADQmBTDWgVO8GY8nU9kAADAOAIAAAAAAAAAAAAAAADQtAcJOMG1BAAAGEcAAAAAAAAAAAAA' + b'AAAAmraWgBNMJAAAwDgCAAAAAAAAAAAAAAAA0DTjCJzix/F0/k4GAICyGUcAAAAAAAAAAAAA' + b'AAAAmlZJwIkmEgAAlM04AgAAAAAAAAAAAAAAANCoFMM2XwclOMG1BAAAZTOOAAAAAAAAAAAA' + b'AAAAALRhLQEnmEgAAFA24wgAAAAAAAAAAAAAAABAGyoJOMGb8XQ+kQEAoFzGEQAAAAAAAAAA' + b'AAAAAIA2VBJwomsJAADKZRwBAAAAAAAAAAAAAAAAaMNaAk40kQAAoFzGEQAAAAAAAAAAAAAA' + b'AIDGpRj2+dopwQl+HE/n72QAACiTcQQAAAAAAAAAAAAAAACgLZUEnGgiAQBAmYwjAAAAAAAA' + b'AAAAAAAAAG1ZS8CJriUAACiTcQQAAAAAAAAAAAAAAACgLcYRONVEAgCAMhlHAAAAAAAAAAAA' + b'AAAAAFqRYqhU4ERvxtP5tQwAAOUxjgAAAAAAAAAAAAAAAAC06UECTmQcAQCgQMYRAAAAAAAA' + b'AAAAAAAAgDatJeBExhEAAApkHAEAAAAAAAAAAAAAAABok3EETvVmPJ0bSAAAKIxxBAAAAAAA' + b'AAAAAAAAAKBNlQScQR/GEd55mQAAzsc4AgAAAAAAAAAAAAAAANCaFMM2XwclOFEfxhHeepkA' + b'AM7HOAIAAAAAAAAAAAAAAADQtkoCTvRmPJ3PZAAAKIdxBAAAAAAAAAAAAAAAAKBtawk4g+uu' + b'/mDj6fzKywMAcF7GEQAAAAAAAAAAAAAAAIC2VRJwBr+Mp/N3Hf3ZfvDyAACcl3EEAAAAAAAA' + b'AAAAAAAAoG1rCTiTWUd/romXBgDgvIwjAAAAAAAAAAAAAAAAAK1KMezztVGCM7jp6M915aUB' + b'ADgv4wgAAAAAAAAAAAAAAADAJawl4AzejKfzWQd/romXBgDgvIwjAAAAAAAAAAAAAAAAAJdQ' + b'ScCZLLr0w4yn86t8vfGyAACcl3EEAAAAAAAAAAAAAAAA4BLWEnAmb8fT+axDP8/ES0KD9hIA' + b'UCrjCAAAAAAAAAAAAAAAAEDrUgzGETinRYd+lhsvBw3y7ASgWMYRAAAAAAAAAAAAAAAAgEt5' + b'kIAzeTuezmeX/iHyzzA5/ixeDgCA8zOOAAAAAAAAAAAAAAAAAFyKb0DnnBZ+BgCA4TKOAAAA' + b'AAAAAAAAAAAAAFyKcQTO6e14Ol9c6h/P//YkXz95GQAAmmEcAQAAAAAAAAAAAAAAALiUrQSc' + b'2e/j6fzdhf7tW/kBAJpjHAEAAAAAAAAAAAAAAAC4iBRDpQINWLX9D46n8+Mwwo/SAwA0xzgC' + b'AAAAAAAAAAAAAAAAcEkbCTizH8fT+aKtfyz/W7N8/ZfstKSSAIBSGUcAAAAAAAAAAAAAAAAA' + b'LmktAQ34vR4taFT9b/xHbgCA5hlHAAAAAAAAAAAAAAAAAC7JOAJNuR1P51dN/eWGEQAA2mUc' + b'AQAAAAAAAAAAAAAAALgk4wg05U0+1Xg6n5zzL81/3w/5rEaGEQAAWmUcAQAAAAAAAAAAAAAA' + b'ALiYFEOlAg06DiT8z3g6vzn1L6pHERb5j9t8fpEWAKBd30sAAAAAAAAAAAAAAAAAXNgun7cy' + b'0KD/Hk/n1/mepRi2L/kP8393dfzv6vNGSi7JoAwAJTOOAAAAAAAAAAAAAAAAAFzaemQcgeb9' + b'lM//jqfz+3yvjr93KYb1X/9n/t9/yNdxCOGv+3gmI4MIAACdYBwBAAAAAAAAAAAAAAAAuLTj' + b'B9R/kYGW/PLX79t4OlcDAKAnvpMAAAAAAAAAAAAAAAAAuLBKAgAA4EuMIwAAAAAAAAAAAAAA' + b'AACXtpYA4KseJACgZMYRAAAAAAAAAAAAAAAAgItKMezztVMCAAB4jnEEAAAAAAAAAAAAAAAA' + b'oAvWEgAAAM8xjgAAAAAAAAAAAAAAAAB0gXEEgC/bSgBAyYwjAAAAAAAAAAAAAAAAAF1QSQDw' + b'RVsJACiZcQQAAAAAAAAAAAAAAACgC9YSAAAAzzGOAAAAAAAAAAAAAAAAAFxcimGfr50SAM/a' + b'SwBAyYwjAAAAAAAAAAAAAAAAAF2xlgDAMxIAnmIcAQAAAAAAAAAAAAAAAOgKH/wFAACeZBwB' + b'AAAAAAAAAAAAAAAA6IpKAoBnbSUAoGTGEQAAAAAAAAAAAAAAAICuWEsA8LQUw1YFAEpmHAEA' + b'AAAAAAAAAAAAAADohBTDPl87JQAAgE8ZRwAAAAAAAAAAAAAAAAC6ZC0BwGc2EgBQOuMIAAAA' + b'AAAAAAAAAAAAQJcYRwD43F4CAEpnHAEAAAAAAAAAAAAAAADokkoCgM8YRwCgeMYRAAAAAAAA' + b'AAAAAAAAgC5ZSwDg2QgAnzKOAAAAAAAAAAAAAAAAAHRGiuH47eg7JQAAgI8ZRwAAAAAAAAAA' + b'AAAAAAC6xjekA/xTJQEApTOOAAAAAAAAAAAAAAAAAHSNcQQAAOAfjCMAAAAAAAAAAAAAAAAA' + b'XVNJAPAPRmMAKJ5xBAAAAAAAAAAAAAAAAKBrfAgY4CMphr0KAJTOOAIAAAAAAAAAAAAAAADQ' + b'KfWHgDdKAPxpJwEAGEcAAAAAAAAAAAAAAAAAumktAcCfthIAgHEEAAAAAAAAAAAAAAAAoJuM' + b'IwA82koAAMYRAAAAAAAAAAAAAAAAgG4yjgDwaCsBABhHAAAAAAAAAAAAAAAAADooxVCpAPAn' + b'YzEAMDKOAAAAAAAAAAAAAAAAAHTXgwQAo70EAGAcAQAAAAAAAAAAAAAAAOgu35YO4FkIAH8y' + b'jgAAAAAAAAAAAAAAAAB0lQ8EA8VLMexVAADjCAAAAAAAAAAAAAAAAEB3VRIAhXuQAAAeGUcA' + b'AAAAAAAAAAAAAAAAOinFsM3XQQmgYFsJAOCRcQQAAAAAAAAAAAAAAACgy9YSAAXbSgAAj4wj' + b'AAAAAAAAAAAAAAAAAF1WSQAUzEAMANSMIwAAAAAAAAAAAAAAAABdVkkAFGwrAQA8Mo4AAAAA' + b'AAAAAAAAAAAAdJlvTQeKlWLwDASAmnEEAAAAAAAAAAAAAAAAoLNSDPt8bZQACrSTAAD+ZhwB' + b'AAAAAAAAAAAAAAAA6DrfnA6UaCsBAPzNOAIAAAAAAAAAAAAAAADQdZUEgGcfAJTNOAIAAAAA' + b'AAAAAAAAAADQdWsJAM8+ACibcQQAAAAAAAAAAAAAAACg01IMxw8IH5QACrOVAAD+ZhwBAAAA' + b'AAAAAAAAAAAA6INKAqAk9TAMAFAzjgAAAAAAAAAAAAAAAAD0gQ8JAyXZSAAA/2QcAQAAAAAA' + b'AAAAAAAAAOiDSgKgIAZhAOATxhEAAAAAAAAAAAAAAACAzksxVCoABTGOAACfMI4AAAAAAAAA' + b'AAAAAAAA9MWDBEAhjCMAwCeMIwAAAAAAAAAAAAAAAAB9UUkAlCDF4HkHAJ8wjgAAAAAAAAAA' + b'AAAAAAD0hW9SB0qwkwAAPmccAQAAAAAAAAAAAAAAAOiLSgLAsw4AymQcAQAAAAAAAAAAAAAA' + b'AOiFFMM+XxslgIFbSwAAnzOOAAAAAAAAAAAAAAAAAPRJJQEwcMYRAOAJxhEAAAAAAAAAAAAA' + b'AACAPqkkAIYsxeA5BwBPMI4AAAAAAAAAAAAAAAAA9EaKYaUCMGAbCQDgacYRAAAAAAAAAAAA' + b'AAAAgL55kAAYqEoCAHiacQQAAAAAAAAAAAAAAACgbyoJgIFaSwAATzOOAAAAAAAAAAAAAAAA' + b'APTNSgJgoCoJAOBpxhEAAAAAAAAAAAAAAACAXkkxHL9Z/aAEMDC7/HzbygAATzOOAAAAAAAA' + b'AAAAAAAAAPTRSgJgYNYSAMDzjCMAAAAAAAAAAAAAAAAAfWQcARiaSgIAeJ5xBAAAAAAAAAAA' + b'AAAAAKCPKgkAzzUAKIdxBAAAAAAAAAAAAAAAAKB3Ugz7fN0rAQzEIT/X1jIAwPOMIwAAAAAA' + b'AAAAAAAAAAB9tZIAGIhKAgD4MuMIAAAAAAAAAAAAAAAAQC+lGJb5OigBDEAlAQB8mXEEAAAA' + b'AAAAAAAAAAAAoM+WEgADsJIAAL7MOAIAAAAAAAAAAAAAAADQZ0sJgJ7bpRi2MgDAlxlHAAAA' + b'AAAAAAAAAAAAAHorxbDO104JoMcqCQDg64wjAAAAAAAAAAAAAAAAAH23kgDosUoCAPg64wgA' + b'AAAAAAAAAAAAAABA3y0lAHrMwAsAfAPjCAAAAAAAAAAAAAAAAECvpRjW+dopAfTQJj/D9jIA' + b'wNcZRwAAAAAAAAAAAAAAAACGwDevA55dADBgxhEAAAAAAAAAAAAAAACAIbiVAOgh4wgA8I2M' + b'IwAAAAAAAAAAAAAAAAC9l2LY5mujBNAjh/zsWssAAN/GOAIAAAAAAAAAAAAAAAAwFEsJgB5Z' + b'SQAA3844AgAAAAAAAAAAAAAAADAUSwmAHjGOAAAvYBwBAAAAAAAAAAAAAAAAGIQUwz5f90oA' + b'PXlmGUcAgBcwjgAAAAAAAAAAAAAAAAAMyVICoAcMuQDACxlHAAAAAAAAAAAAAAAAAAaj/ib2' + b'gxJAx60kAICXMY4AAAAAAAAAAAAAAAAADM1SAqDjjCMAwAsZRwAAAAAAAAAAAAAAAACG5lYC' + b'oMPuUwx7GQDgZYwjAAAAAAAAAAAAAAAAAIOSYtjma6ME0FErCQDg5YwjAAAAAAAAAAAAAAAA' + b'AEN0KwHQUcYRAOAVjCMAAAAAAAAAAAAAAAAAQ3T88PFBBqBj7lMMexkA4OWMIwAAAAAAAAAA' + b'AAAAAACDU3/4eKkE0DErCQDgdYwjAAAAAAAAAAAAAAAAAEN1KwHQMcYRAOCVjCMAAAAAAAAA' + b'AAAAAAAAg5Ri2ObrQQmgI+7yc2kvAwC8jnEEAAAAAAAAAAAAAAAAYMiWEgAdsZIAAF7POAIA' + b'AAAAAAAAAAAAAAAwWCmGZb52SgAXdsjPI+MIAHAC4wgAAAAAAAAAAAAAAADA0C0lADyHAKDf' + b'jCMAAAAAAAAAAAAAAAAAQ3ebz0EG4IKWEgDAaYwjAAAAAAAAAAAAAAAAAIOWYtjna6UEcCGb' + b'/BxaywAApzGOAAAAAAAAAAAAAAAAAJRgIQFwIbcSAMDpjCMAAAAAAAAAAAAAAAAAg5di2Obr' + b'XgmgZYd8VjIAwOmMIwAAAAAAAAAAAAAAAACl8O3tQNtWKYa9DABwOuMIAAAAAAAAAAAAAAAA' + b'QBFSDFW+HpQAWmSUBQDOxDgCAAAAAAAAAAAAAAAAUJKlBEBLNimGtQwAcB7GEQAAAAAAAAAA' + b'AAAAAIBipBiW+dopAbTgVgIAOB/jCAAAAAAAAAAAAAAAAEBpFhIADTvUYywAwJkYRwAAAAAA' + b'AAAAAAAAAACKUn9geacE0KBbCQDgvIwjAAAAAAAAAAAAAAAAACVaSAA0yDgCAJyZcQQAAAAA' + b'AAAAAAAAAACgOCmGZb52SgANuMvPmL0MAHBexhEAAAAAAAAAAAAAAACAUi0kADxbAKAfjCMA' + b'AAAAAAAAAAAAAAAARUoxLPO1UwI4o4f8bNnKAADnZxwBAAAAAAAAAAAAAAAAKNlCAsAzBQC6' + b'zzgCAAAAAAAAAAAAAAAAUKwUwzJfOyWAM3jIz5RKBgBohnEEAAAAAAAAAAAAAAAAoHQLCQDP' + b'EgDoNuMIAAAAAAAAAAAAAAAAQNFSDMt8bZQATrDLz5JKBgBojnEEAAAAAAAAAAAAAAAAgNHo' + b'RgLgBAsJAKBZxhEAAAAAAAAAAAAAAACA4tXf+P6gBPAKu/wMWcoAAM0yjgAAAAAAAAAAAAAA' + b'AADw6EYC4BUWEgBA84wjAAAAAAAAAAAAAAAAAGQphnW+7pQAXmCTnx1LGQCgecYRAAAAAAAA' + b'AAAAAAAAAP62yOcgA/CNbiQAgHYYRwAAAAAAAAAAAAAAAACopRi2+bpVAvgGD/mZUckAAO0w' + b'jgAAAAAAAAAAAAAAAADwkRTDIl87JYCvWEgAAO0xjgAAAAAAAAAAAAAAAADwuRsJgC+4TzFU' + b'MgBAe4wjAAAAAAAAAAAAAAAAAHwixbDK14MSwDMMqABAy4wjAAAAAAAAAAAAAAAAADxtJgHw' + b'hD9SDFsZAKBdxhEAAAAAAAAAAAAAAAAAnlB/+PmDEsBHDvksZACA9hlHAAAAAAAAAAAAAAAA' + b'AHjebT47GYDaIsWwlwEA2mccAQAAAAAAAAAAAAAAAOAZ9YegZ0oA2SY/E25lAIDLMI4AAAAA' + b'AAAAAAAAAAAA8AUphipfd0pA8W4kAIDLMY4AAAAAAAAAAAAAAAAA8HXHD0UfZIBi3ddDKQDA' + b'hRhHAAAAAAAAAAAAAAAAAPiKFMM+XzMloEgH738AuDzjCAAAAAAAAAAAAAAAAADfIMWwyte9' + b'ElCcRT2QAgBckHEEAAAAAAAAAAAAAAAAgG83Gz1+izxQhocUw60MAHB5xhEAAAAAAAAAAAAA' + b'AAAAvlH97fEzJaAYNxIAQDcYRwAAAAAAAAAAAAAAAAB4gRTDKl93SsDgfcjv97UMANANxhEA' + b'AAAAAAAAAAAAAAAAXu74bfI7GWCwNimGhQwA0B3GEQAAAAAAAAAAAAAAAABeKMWwz9dMCRgs' + b'728A6BjjCAAAAAAAAAAAAAAAAACvkGKo8vVBCRic3/L7ey0DAHSLcQQAAAAAAAAAAAAAAACA' + b'V0oxLPL1oAQMxkN+X9/KAADdYxwBAAAAAAAAAAAAAAAA4DTX+RxkgN471O9nAKCDjCMAAAAA' + b'AAAAAAAAAAAAnCDFsB/5QDUMwax+PwMAHWQcAQAAAAAAAAAAAAAAAOBEKYYqXx+UgN76I7+P' + b'VzIAQHcZRwAAAAAAAAAAAAAAAAA4gxTDIl8PSkDvbPL790YGAOg24wgAAAAAAAAAAAAAAAAA' + b'53Odz04G6I1D/b4FADrOOAIAAAAAAAAAAAAAAADAmaQY9qPHD1of1IBeuM7v260MANB9xhEA' + b'AAAAAAAAAAAAAAAAzijFsM7XjRLQeb/l92slAwD0w/cSAAAAAAAAAAAAAAAANOqDBFCeFMNy' + b'PJ2/y3/8XQ3opLv8Pr2VAQD6wzgCAAAAAAAAAAAAAABAg1IMCxWg3Pd/PZDwqxrQKZt8bmQA' + b'gH75TgIAAAAAAAAAAAAAAACAxhw/gL2RATrjkM8kxbCXAgD6xTgCAAAAAAAAAAAAAAAAQEPq' + b'D2BPRgYSoAsMIwBAjxlHAAAAAAAAAAAAAAAAAGhQ/UHs2ejxg9nA5czy+3EtAwD0k3EEAAAA' + b'AAAAAAAAAAAAgIbVH8iejAwkwKW8z+/DlQwA0F/GEQAAAAAAAAAAAAAAAABaUA8kXCsBrfuQ' + b'339LGQCg34wjAAAAAAAAAAAAAAAAALQkxVDl670S0Jq7/L5byAAA/WccAQAAAAAAAAAAAAAA' + b'AKBF9TfYG0iA5h2HEWYyAMAwGEcAAAAAAAAAAAAAAAAAaJmBBGicYQQAGBjjCAAAAAAAAAAA' + b'AAAAAAAXYCABGmMYAQAGyDgCAAAAAAAAAAAAAAAAwIUYSICzM4wAAANlHAEAAAAAAAAAAAAA' + b'AADgggwkwNkYRgCAATOOAAAAAAAAAAAAAAAAAHBhBhLgZIYRAGDgjCMAAAAAAAAAAAAAAAAA' + b'dICBBHg1wwgAUADjCAAAAAAAAAAAAAAAAAAdUQ8k/DufgxrwTQwjAEAhjCMAAAAAAAAAAAAA' + b'AAAAdEiKYZ2vychAAnzNb4YRAKAcxhEAAAAAAAAAAAAAAAAAOqYeSLjKZ6MGPOl9fp/cygAA' + b'5TCOAAAAAAAAAAAAAAAAANBBKYZtviYjAwnwsUM+P+f3x1IKACiLcQQAAAAAAAAAAAAAAACA' + b'jkox7PO5yn+8UwNGu3wm+T1RSQEA5TGOAAAAAAAAAAAAAAAAANBxKYZZvn5TgoJt8rnK74W1' + b'FABQJuMIAAAAAAAAAAAAAAAAAD2QYrjN18/5HNSgMHf59/84jLCXAgDKZRwBAAAAAAAAAAAA' + b'AAAAoCdSDFW+rvLZqEEBjkMg7/Pv/UwKAMA4AgAAAAAAAAAAAAAAAECPpBi2+Zrkc6cGA7Y7' + b'/p7n3/elFADAkXEEAAAAAAAAAAAAAAAAgJ5JMezzmeU/vs/noAgDc5/PVf4dX0sBAPzFOAIA' + b'AAAAAAAAAAAAAABAT6UYlvma5LNRgwE4Dn28z7/X18cBEDkAgI8ZRwAAAAAAAAAAAAAAAADo' + b'sRTDOp+r/Mc/1KDHjgMfk3rwAwDgM8YRAAAAAAAAAAAAAAAAAAYgxXCTr5/z2alBz3w4Dnwc' + b'hz6kAACeYxwBAAAAAAAAAAAAAAAAYCBSDFW+rvL5Qw16YJPPv/Pv7UIKAOBrvpcAAAAAAAAA' + b'AAAAAAAAYDhSDPt83Yyn81W+l/m8VYUO+mAUAQB4ie8kAAAAAAAAAAAAAAAAABieFEOVr6t8' + b'PqhBhzzk8y/DCADAS30vAQAAAAAAAAAAAAAAAMAwpRj2+VqMp/NVvm/z+UkVLuSQz03+nVxK' + b'AQC8xncSAAAAAAAAAAAAAAAAAAxbimGdzyT/8X0+O0Vo2R/5vDOMAACcwjgCAAAAAAAAAAAA' + b'AAAAQCHqD6df5fMhn4MiNOwhn3/l37ubfPZyAACnMI4AAAAAAAAAAAAAAAAAUJDjh9TzWYwe' + b'RxLuFKEBx1GEn/Pv2SSfrRwAwDl8LwEAAAAAAAAAAAAAAABAeeoPrc/G0/ki38t8flKFE+3y' + b'WeTfraUUAMC5GUcAAAAAAAAAAAAAAAAAKFg9kjAZT+eTfC9GRhJ4OaMIAEDjjCMAAAAAAAAA' + b'AAAAAAAAcBxJqEZGEngZowgAQGuMIwAAAAAAAAAAAAAAAADw/z4aSXg3ehxJ+FUVPvEwehxF' + b'qKQAANpiHAEAAAAAAAAAAAAAAACAz6QYtvmajafzRb5vjn/O540yRbvL5zb/bqylAADaZhwB' + b'AAAAAAAAAAAAAAAAgGfVIwk39UjCdT7H+60yxdjlsxw9jiLs5QAALsU4AgAAAAAAAAAAAAAA' + b'AABfVX8wfnk84+l8ku9ZPr8qM1j3x9c6v+4rKQCALjCOAAAAAAAAAAAAAAAAAMCLpBiqfFXj' + b'6fwm39f5HO8flem9zagewKjHMAAAOsM4AgAAAAAAAAAAAAAAAACvUn+Afnk84+n8Xb5n9Xmr' + b'Tm/s8lnlc5tfz60cAEBXGUcAAAAAAAAAAAAAAAAA4GT1B+sXxzOezq9GjyMJ1yNDCV20yafK' + b'Z5lft7UcAEAfGEcAAAAAAAAAAAAAAAAA4KzqD9zfHM94On83ehxJOJ6f1LmY+9HjIMKqHrIA' + b'AOgV4wgAAAAAAAAAAAAAAAAANKb+IP7t8Yyn8x/yPRk9DiUc77cKNWYzehxDqPJrsJIDAOg7' + b'4wgAAAAAAAAAAAAAAAAA/B97d5Pbtrm2AZgIMhe5AmkH0g6swTu3dmDtIAYIjq25UMTfCqJv' + b'BXXnAqqsoOoO1BXI3cF5WdGN4zqObP2YP9cFPHjknBY4vRPzDWTx5llsl/P7uO6qSbJQDJJd' + b'ScLDKEt4u3/LEJJdIcK9SACANlGOAAAAAAAAAAAAAAAAAMC72C7nm7gW1ZRlCWmyK0kYPdo9' + b'Sf3HX3HWya4IYR1zXIkEAGg75QgAAAAAAAAAAAAAAAAA1MJ2Ob+P666af2ShGCS7koSHKb8e' + b'diSSv5NdCUI5m0QRAgDQYcoRAAAAAAAAAAAAAAAAAKit7XK+SXbFAHePfz0LRVmUkMYZV/vh' + b'66YVJ/xV/fc9nXVVFgEAQKIcAQAAAAAAAAAAAAAAAIAG2i7n6+rl6rn/PQvFuHo5qObp61JZ' + b'qNA78v+1h7KDB5tHXz9+rfwAAOAVlCMAAAAAAAAAAAAAAAAA0Drb5Xx1yL+fhSJNduUJT90/' + b'KmYAAOBMlCMAAAAAAAAAAAAAAAAAwBPb5fw+rpUkAADq4YMIAAAAAAAAAAAAAAAAAAAAgDpT' + b'jgAAAAAAAAAAAAAAAAAAAADUmnIEAAAAAAAAAAAAAAAAAAAAoNaUIwAAAAAAAAAAAAAAAAAA' + b'AAC1phwBAAAAAAAAOLWRCAAAAAAAAAAAgEMoRwAAAAAAAABOLRUBAAAAAAAAAABwCOUIAAAA' + b'AAAAAAAAAAAAAAAAQK0pRwAAAAAAAAAAAAAAAAAAAABqTTkCAAAAAAAAAAAAAAAAAAAAUGvK' + b'EQAAAAAAAAAAAAAAAAAAAIBaU44AAAAAAAAAAAAAAAAAAAAA1JpyBAAAAAAAAAAAAAAAAAAA' + b'AKDWlCMAAAAAAAAAp5aKAAAAAAAAAAAAOIRyBAAAAAAAAODURiIAAAAAAAAAAAAOoRwBAAAA' + b'AAAAAAAAAAAAAAAAqDXlCAAAAAAAAAAAAAAAAAAAAECtKUcAAAAAAAAAAAAAAAAAAAAAak05' + b'AgAAAAAAAAAAAAAAAAAAAFBryhEAAAAAAACAUxuIAAAAAAAAAAAAOIRyBAAAAAAAAODU+iIA' + b'AAAAAAAAAAAOoRwBAAAAAAAAAAAAAAAAAAAAqDXlCAAAAAAAAAAAAAAAAAAAAECtKUcAAAAA' + b'AAAAAAAAAAAAAAAAak05AgAAAAAAAHAyachHUgAAAAAAAAAAAA6lHAEAAAAAAAA4pVQEAAAA' + b'AAAAAADAoZQjAAAAAAAAAAAAAAAAAAAAALWmHAEAAAAAAAAAAAAAAAAAAACoNeUIAAAAAAAA' + b'wCmNRAAAAAAAAAAAABxKOQIAAAAAAABwSqkIAAAAAAAAAACAQylHAAAAAAAAAAAAAAAAAAAA' + b'AGpNOQIAAAAAAABwSqkIAAAAAAAAAACAQylHAAAAAAAAAE5pJAIAAAAAAAAAAOBQyhEAAAAA' + b'AAAAAAAAAAAAAACAWlOOAAAAAAAAAJxSKgIAAAAAAAAAAOBQyhEAAAAAAACAUxqKAAAAAAAA' + b'AAAAOJRyBAAAAAAAAAAAAAAAAAAAAKDWlCMAAAAAAAAAJ5GGPJUCAAAAAAAAAABwDMoRAAAA' + b'AAAAgFMZiQAAAAAAAAAAADiGjyIAAAAAAAAAAAAAAAAAeFkWikFcgxb8p9xvl/O131EAAJpG' + b'OQIAAAAAAABwKgMRAAAAAAAA5/JMecH4yT8yipM++bXy62EHs/rZP/J3nKcFCptqHqwevV5v' + b'l/N7fwoBADgl5QgAAAAAAADAqQxEAAAAAAAAHOJJ4cG42o9/rSw86Enq6MpML5782tOvb578' + b'Xj28/FrtTTVlaUJZtHC/Xc7XogUA4K2UIwAAAAAAAAAAAAAAAADvIgtFWW6QJt+KDx6+VnrQ' + b'XBdP9uPf73L9nezKEjaPZ7ucr0QHAMBLlCMAAAAAAAAApzIWAQAAAAAA8KQA4aH4YBCnL51O' + b'KksvLpIn5QlPihMeyhP+eb1dzu/FBgCAcgQAAAAAAAAAAAAAAADgYFUJwiDZlR88vB5Khlf4' + b'UXHCQ2nCKqlKE7bL+VpcAADdohwBAAAAAAAAOJWBCAAAAAAAoJ2yUIyT70sQLqTCCf2nNCH+' + b'GSzX12RXmvBPccJ2Od+ICgCgvZQjAAAAAAAAAKfSFwEAAAAAADTfkyKEcoZSoSaeFib8Hdcq' + b'+VaWsBIRAEB7KEcAAAAAAAAAji4NeSoFAAAAAABoniwU5Xv842rKIoQLqdAgvTiX1dzEP8/l' + b'r31NqrKEZFeYcC8mAIBmUo4AAAAAAAAAnMJIBAAAAAAAUH9ZKAbJtzKEcvpSoWUuqvlU/Zn/' + b'M/lWlHAnHgCA5lCOAAAAAAAAAJxCKgIAAAAAAKgfZQiQDKv5FL8fyq+/xilLEsqyhLV4AADq' + b'SzkCAAAAAAAAcAojEQAAAAAAwPvLQlEWGo+rmSTKEOCpi2rK75e/k6ooodzb5fxePAAA9aEc' + b'AQAAAAAAAAAAAAAAAFokC8Ug2RUhlHMhEdhbL85VNV/i99LXZFeWUBYlbMQDAPC+lCMAAAAA' + b'AAAApzAWAQAAAAAAnE8WinHyrRChLxE4iotqPsfvsT/jXsVZbJfztWgAAM5POQIAAAAAAABw' + b'CqkIAAAAAADgtLJQPJQhlNOTCJzUsJpP8Xvvr7jvEkUJAABnpRwBAAAAAAAAOIWhCAAAAAAA' + b'4LiyUJTlxA9lCONEIQK8l36cT4miBACAs1KOAAAAAAAAABxVGvJUCgAAAAAAcDxZKB4KEcpR' + b'iAD18rQoYZHsihI2ogEAOC7lCAAAAAAAAMCxjUQAAAAAAACHyUIxjmuaKESAJimLEm7Kid/D' + b'f8Z9G+duu5zfiwYA4HDKEQAAAAAAAIBjG4gAAAAAAABeLwvFINkVIpTTlwg02jDOl3Li9/Zv' + b'cS+2y/mdWAAA3k45AgAAAAAAAHBsAxEAAAAAAMB+slCkcU2SXSHChUSglS7Lid/vf8VdFiTc' + b'bpfzjVgAAF5HOQIAAAAAAABwbAMRAAAAAADAy7JQjOK6TnbFCD2JQCf043wqJ14Dvsa92C7n' + b'C7EAAOxHOQIAAAAAAABwbAMRAAAAAADA87JQTJNdKcJQGtBpF+XEa8Jt3Is4t9vlfCMWAIAf' + b'U44AAAAAAAAAHNuFCAAAAAAA4JssFIO4psmuFKEnEeCR8prwqZx4rfgt2ZUkrMQCAPBfyhEA' + b'AAAAAACAo0lDnkoBAAAAAAB2slCMk10pwpU0gD1clhOvHX/FPYtzt13O78UCALCjHAEAAAAA' + b'AAA4ppEIAAAAAADouiwU02RXinAhDeAN+nG+xLmN15NFubfL+UYsAEDXKUcAAAAAAAAAjmkg' + b'AgAAAAAAuigLRRrXJNk97b0vEeAIenE+lROvMf+f7EoS1mIBALpKOQIAAAAAAABwTAMRAAAA' + b'AADQJVUpwnU1PYkAJ3JVTrzmfI17tl3OVyIBALpGOQIAAAAAAABwTGMRAAAAAADQBUoRgHdy' + b'Eef3eA36M+7b7XK+EAkA0BXKEQAAAAAAAIBjGogAAAAAAIA2U4oA1MQwzpd4TZrFPVOSAAB0' + b'gXIEAAAAAAAA4Jj6IgAAAAAAoI2UIgA1Vf58TkkCANAJyhEAAAAAAACAo0hDPpYCAAAAAABt' + b'oxQBaAglCQBA630QAQAAAAAAAHAkAxEAAAAAANAm1U3Gmzg3iWIEoBkeShI2cabiAADaRDkC' + b'AAAAAAAAcCwjEQAAAAAA0AblDcXljcWJUgSguZQkAACtoxwBAAAAAAAAOBblCAAAAAAANFoW' + b'inGcdXz5JdndWAzQdEoSAIDW+CgCAAAAAAAA4EiUIwAAAAAA0EhZKAZx3ca5lAbQUg8lCdO4' + b'Z9vlfCUSAKBplCMAAAAAAAAAB0tDPoirJwkAAAAAAJokC0Ua13WcG2kAHXER5/d4/ftaXv+2' + b'y/laJABAU3wQAQAAAAAAAHAEAxEAAAAAANAk1dPTy5uCFSMAXVSWJPwRr4WLOANxAABN8FEE' + b'AAAAAAAAwBGMRQAAAAAAQBNkoRjFdZvsbgwG6LqrOJN4bSyvi7fb5fxeJABAXX0QAQAAAAAA' + b'AHAEIxEAAAAAAFBnWSjSOLP48o9EMQLAY704N3E28To5FQcAUFfKEQAAAAAAAIBjUI4AAAAA' + b'AEBtZaEYx7VOdjf/AvC8siThS7xmrqvrJgBArXwUAQAAAAAAAHCINORpXH1JAAAAAABQN1ko' + b'yvewF3EupQGwt2Gc3+M19Le4r7fL+UYkAEAdfBABAAAAAAAAcKCRCAAAAAAAqJssFJO4Noli' + b'BIC3Kq+f63g9nYkCAKiDjyIAAAAAAAAADjQWAQAAAAAAdZGFIo1rkShFADiGXpybeG2dxj3d' + b'LucrkQAA7+WDCAAAAAAAAIADjUUAAAAAAEAdZKGYxLVJFCMAHFs/zu/xOnsXZyAOAOA9KEcA' + b'AAAAAAAADjUSAQAAAAAA7ykLRVresBtf/prsnnIOwGmU5TPreM29FgUAcG7KEQAAAAAAAIA3' + b'S0M+SHzIFAAAAACAd5SFYhzXJtndsAvA6ZU/H/wcr79lSYIidQDgbD6KAAAAAAAAADjAWAQA' + b'AAAAALyHLBRpXLM4n6QB8C6Gcf6I1+P/K6/H2+X8XiQAwCl9EAEAAAAAAABwAE+CAQAAAADg' + b'7Konla8SxQgAdVBei9fx2jwWBQBwSsoRAAAAAAAAgEOMRQAAAAAAwDllobhOdsUIQ2kA1EY/' + b'zu/xGn0XJxUHAHAKyhEAAAAAAACAN0lDXn6oyQdPAQAAAAA4i/Jm2/Km2/jyc5yeRABq6TLO' + b'Jl6vJ6IAAI5NOQIAAAAAAADwViMRAAAAAABwDlkoyvek18nuplsA6q0ssPm1LLQpi23EAQAc' + b'i3IEAAAAAAAA4K3GIgAAAAAA4NSyUEzj+iNOXxoAjVIW2mzidXwiCgDgGD6KAAAAAAAAAHij' + b'sQgAAAAAADiV6mnjt3GupAHQWL04v8Zr+m9xT7fL+b1IAIC3+iACAAAAAAAA4I0uRAAAAAAA' + b'wClkoRjEtUoUIwC0xWWcTby+T0QBALyVcgQAAAAAAADg1dKQj6UAAAAAAMApZKEYx7WOM5QG' + b'QKv04vwar/O3cVJxAACvpRwBAAAAAAAAeIuxCAAAAAAAOLYsFNdx/Z7sbqAFoJ0+xVnHa/5I' + b'FADAayhHAAAAAAAAAN5iLAIAAAAAAI4pC8Uirs+SAOiEfpw/4rV/JgoAYF8fRQAAAAAAAAC8' + b'wYUIAAAAAAA4hiwUaVyrOENpAHTOTTwHJnFPtsv5RhwAwEs+iAAAAAAAAAB4jTTkYykAAAAA' + b'AHAMWShGiWIEgK4rz4B1VZIAAPBDyhEAAAAAAACA1/KhJAAAAAAADqYYAYBHenF+jWfDIk4q' + b'DgDgOcoRAAAAAAAAgNcaiwAAAAAAgENkoZjG9UeyuxkWAB5cxVlVBToAAN9RjgAAAAAAAADs' + b'LQ15+ZQWT/ACAAAAAODNslBcx/VFEgD8QPnzyFV1XgAA/Es5AgAAAAAAAPAaExEAAAAAAPBW' + b'WSgWcX2WBAA/0SvPi3hu3MVJxQEAlJQjAAAAAAAAAK8xFgEAAAAAAG9RFSNcSQKAV7iMs45n' + b'yEgUAIByBAAAAAAAAOA1JiIAAAAAAOA1yid+x1knihEAeJt+nFU8S6aiAIBuU44AAAAAAAAA' + b'7CUNefk0lp4kAAAAAADYV1mMENcqzlAaAByg/Dnll3iuLEQBAN2lHAEAAAAAAADY10QEAAAA' + b'AADsSzECACdwFc+XdXXGAAAdoxwBAAAAAAAA2JdyBAAAAAAA9qIYAYATKs+WTTxrRqIAgG5R' + b'jgAAAAAAAAD8VBryQeIDrAAAAAAA7EExAgBn0CvPmnjmTEUBAN2hHAEAAAAAAADYx1gEAAAA' + b'AAD8jGIEAM6oLEj4Es+ea1EAQDcoRwAAAAAAAAD2MREBAAAAAAAvUYwAwDv5HM+ghRgAoP2U' + b'IwAAAAAAAAAvSkNefpj1UhIAAAAAAPyIYgQA3tlVPIvuqvMIAGgp5QgAAAAAAADAz0xEAAAA' + b'AADAjyhGAKAmysL3lYIEAGgv5QgAAAAAAADAz4xFAAAAAADAcxQjAFAz5XmkIAEAWko5AgAA' + b'AAAAAPAzExEAAAAAAPADi0QxAgD1oiABAFpKOQIAAAAAAADwQ2nIy2KEniQAAAAAAHgqC8Ui' + b'rktJAFBDZUHCJp5VI1EAQHsoRwAAAAAAAABeMhEBAAAAAABPVcUIV5IAoMbKEviVggQAaA/l' + b'CAAAAAAAAMBLlCMAAABA/d2LAIBzykJxnShGAKAZFCQAQIsoRwAAAAAAAACelYa8LEboSQIA' + b'AADq7X75y1oKAJxLFoppXJ8lAUCDKEgAgJZQjgAAAAAAAAD8yFQEAAAAAAA8yEIxjuuLJABo' + b'oLIgYRHPslQUANBcyhEAAAAAAACA/0hDXn4o6FISAAAAAACUqqdt30kCgAYbxlkpSACA5lKO' + b'AAAAAAAAADxnIgIAAAAAAErVTaSrZPfUbQBoMgUJANBgyhEAAAAAAACA50xFAAAAAACAYgQA' + b'WmhYnW0AQMMoRwAAAAAAAAC+k4Z8ENeFJAAAAAAAiG6T3U2kANAmwywUCzEAQLMoRwAAAAAA' + b'AACemogAAAAAAIAsFLO4riQBQEtdKUgAgGZRjgAAAAAAAAA8dS0CAAAAAIBuy0IxjetGEgC0' + b'3FV15gEADaAcAQAAAAAAAPhXGvJRXH1JAAAAAAB0VxaK8r3iW0kA0BFf4tk3FgMA1J9yBAAA' + b'AAAAAOCxaxEAAAAAAHRXFoo0rrs4PWkA0CF3VTkQAFBjyhEAAAAAAACAf6QhLz/wOpEEAAAA' + b'AECnlcUIfTEA0DFlKdCiKgkCAGpKOQIAAAAAAADwoCxG8CQwAAAAAICOykIxi+tCEgB01DDZ' + b'lQQBADWlHAEAAAAAAAB4cC0CAAAAaJy/RADAMWShKAt0byQBQMddxDPxVgwAUE/KEQAAAAAA' + b'AIAkDfko2T0JBQAAAGiWjQgAOFQWikFcC0kAwD8+VaVBAEDNKEcAAAAAAAAAStciAAAAAADo' + b'rLs4PTEAwL8WWShGYgCAelGOAAAAAAAAAB2XhjyNy5NPAAAAAAA6KAvFbVxDSQDAd8rSoLIg' + b'IRUFANSHcgQAAAAAAABgmngiGAAAAABA52ShGMf1SRIA8KyyPGgmBgCoD+UIAAAAAAAAwLUI' + b'AAAAAAC6pXoS9p0kAOBFn+KZOREDANSDcgQAAAAAAADosDTk5Qd5+pIAAAAAAOicshihJwYA' + b'+KlFFoqBGADg/SlHAAAAAAAAgG67FgEAAAAAQLdkoSjfG76QBADspSwTWogBAN6fcgQAAAAA' + b'AADoqDTkg8SHXwEAAAAAOiULxSiumSQA4FUuqnIhAOAdKUcAAAAAAACA7pqJAAAAAACgcxbJ' + b'7gnYAMDrfK5KhgCAd6IcAQAAAAAAADooDfkgritJAAAAQOOtRADAvrJQzOIaSgIA3mwhAgB4' + b'P8oRAAAAAAAAoJumIgAAAAAA6I7qSdc3kgCAgwyrsiEA4B0oRwAAAAAAAICOSUOexnUtCQAA' + b'AACATlmIAACO4qYqHQIAzkw5AgAAAAAAAHRPWYzQEwMAAAAAQDdUT7geSgIAjmYhAgA4P+UI' + b'AAAAAAAA0D3XIgAAAAAA6IbqydY3kgCAoxrGM9bPXQHgzJQjAAAAAAAAQIekIZ/G1ZMEAAAA' + b'AEBnLEQAACcxy0IxEAMAnI9yBAAAAAAAAOiWmQgAAAAAALqheqL1UBIAcBJlKf2tGADgfJQj' + b'AAAAAAAAQEekIZ/G1ZcEAAAAAED7VU+ynkkCAE7qMp65YzEAwHkoRwAAAAAAAIDumIkAAAAA' + b'WmclAgB+oHySdU8MAHByCxEAwHkoRwAAAAAAAIAOSEM+jasvCQAAAACA9queYH0pCQA4i348' + b'e2diAIDTU44AAAAAAAAA3TATAQAAAABA+2WhSBNPsAaAc7uOZ/BADABwWsoRAAAAAAAAoOXS' + b'kM/i6ksCAAAAAKATrhPvCQPAufUShfUAcHLKEQAAAAAAAKDF0pCXTwi7lgQAAAAAQPtVT6z2' + b'njAAvI+reBaPxQAAp6McAQAAAAAAANqt/BBsTwwAAAAAAJ1wm3hPGADe00wEAHA6yhEAAAAA' + b'AACgpdKQp4knhAEAAEDb3YsAgFL1pOpLSQDAu7qIZ/JEDABwGsoRAAAAAAAAoL08IQwAAABa' + b'7n75y1oKAFRuRQAAzmQAaDPlCAAAAAAAANBCacgHcV1JAgAAAACg/bJQTOMaSgIAaqFfnc0A' + b'wJEpRwAAAAAAAIB2WogAAAAAAKD9slCkiSdUA0DdzEQAAMenHAEAAAAAAABaJg35OK4LSQAA' + b'AAAAdMJ1nJ4YAKBW+lkorsUAAMelHAEAAAAAAADaxxPCAAAAAAA6IAtFmuzKEQCA+plVZzUA' + b'cCTKEQAAAAAAAKBF0pCXH4IdSgIAAAAAoBPKstyeGACglsozWokRAByRcgQAAAAAAABoiTTk' + b'5VNHZpIAAACAzvgqAoDuykIxiOtKEgBQa9fxzE7FAADHoRwBAAAAAAAA2sMTwgAAAAAAumMm' + b'AgCovfLntxMxAMBxKEcAAAAAAACAFkhDPk48IQwAAAAAoBOyUAwS7wkDQFPMRAAAx6EcAQAA' + b'AAAAANrhVgQAAAAAAJ0xEwEANEY/C8VUDABwOOUIAAAAAAAA0HBpyK/jGkoCAAAAAKD9slAM' + b'4rqSBAA0ykwEAHA45QgAAAAAAADQYGnIB4kP0gAAAEBXbUQA0EkzEQBA4/SzUEzFAACHUY4A' + b'AAAAAAAAzXYbpycGAAAA6KSNCAC6JQvFIK4rSQBAI01FAACHUY4AAAAAAAAADZWGfBLXpSQA' + b'AAAAADpjJgIAaKyLLBRjMQDA2ylHAAAAAAAAgAZKQ57GtZAEAAAAAEA3ZKEYxHUlCQBotKkI' + b'AODtlCMAAAAAAABAMy3i9MQAAAAAANAZMxEAQONdVYVHAMAbKEcAAAAAAACAhklDPonrUhIA' + b'AADQefciAOiG6ibKK0kAQCtMRQAAb6McAQAAAAAAABokDXka10ISAAAAQLQWAUBnTEUAAK1x' + b'LQIAeBvlCAAAAAAAANAsizg9MQAAAAAAdEMWirI0102UANAevXi+T8UAAK+nHAEAAAAAAAAa' + b'Ig35NK5LSQAAAAAAdMo0UZoLAG2j+AgA3kA5AgAAAAAAADRAGvJBXLeSAAAAAADoHDdPAkD7' + b'DLNQjMQAAK+jHAEAAAAAAACa4S7xZDAAAADgexsRALRbFoppXH1JAEArKUACgFdSjgAAAAAA' + b'AAA1l4Z8FtdQEgAAAMBj98tfNlIAaL2pCACgtSYiAIDXUY4AAAAAAAAANZaGfBzXjSQAAAAA' + b'ALolC8UorgtJAEBr9eJ5PxUDAOxPOQIAAAAAAADUVBryNK47SQAAAAAAdNK1CACg9aYiAID9' + b'KUcAAAAAAACA+iqLEXpiAAAAAADoliwUZXnulSQAoPUu4rk/EAMA7Ec5AgAAAAAAANRQGvJZ' + b'XBeSAAAAAH7gTxEAtNpUBADg3AcAvqccAQAAAAAAAGomDfk4rhtJAAAAAC+4FwFAq12LAAA6' + b'YyoCANiPcgQAAAAAAACokTTkg7juJAEAAAAA0E1ZKMZx9SUBAJ3Rj+f/SAwA8HPKEQAAAAAA' + b'AKAm0pCnya4YoScNAAAAAIDOmooAAJz/AMB/KUcAAAAAAACA+riNMxQDAAAAsIeNCADaJwtF' + b'WaJ7JQkA6JypCADg55QjAAAAAAAAQA2kIb9OfOAVAAAA2N9GBACtNBUBAHRSLwvFRAwA8DLl' + b'CAAAAAAAAPDO0pCXH3L5LAkAAAAAgM67FgEAdJZyBAD4CeUIAAAAAAAA8I7SkI/iWkgCAAAA' + b'AKDbslCU7xf3JQEAnaUcAQB+QjkCAAAAAAAAvJM05Glcqzg9aQAAAACvdC8CgNaZigAAOq2X' + b'hUJBAgC8QDkCAAAAAAAAvAPFCAAAAMCB1iIAaJ2pCACg85QjAMALlCMAAAAAAADA+1jEGYoB' + b'AAAAAIDqKdHKdAEA5QgA8ALlCAAAAAAAAHBmacgXcV1KAgAAAACAihshAYBSLwvFSAwA8Dzl' + b'CAAAAAAAAHBGachncV1JAgAAADjQvQgA2iELRZp43xgA+GYqAgB4nnIEAAAAAAAAOJM05NO4' + b'biQBAAAAHOp++ctaCgCtMREBAODvBgDwc8oRAAAAAAAA4AyqYoQvkgAAAAAA4Ak3QAIAj/Wz' + b'UAzEAAD/pRwBAAAAAAAATkwxAgAAAAAAz8lCkcZ1KQkA4AnlSQDwDOUIAAAAAAAAcEJpyEeJ' + b'YgQAAADguP4SAUBruPERAPB3BADYk3IEAAAAAAAAOJGqGGElCQAAAODINiIAaA03PgIAz7nI' + b'QpGKAQC+pxwBAAAAAAAATuBRMUJPGgAAAAAAPFXd8HgpCQDgB8YiAIDvKUcAAAAAAACAI1OM' + b'AAAAAADAHiYiAAD8XQEA9qccAQAAAAAAAI5IMQIAAABwBisRALSCGx4BgJeMRQAA31OOAAAA' + b'AAAAAEeiGAEAAAAAgFe4FAEA8IJ+FoqBGADgG+UIAAAAAAAAcARpyKeJYgQAAAAAAPaQhWIi' + b'BQBgD/7OAACPKEcAAAAAAACAA1XFCF8SxQgAAADAedyLAKDx3OgIAOxjLAIA+EY5AgAAAAAA' + b'ABzgUTECAAAAwLmsRQDQeGMRAAD+zgAAr6McAQAAAAAAAN4oDfl1ohgBAAAAAIBXyEIxiqsv' + b'CQBgD73q7w4AQKIcAQAAAAAAAN4kDfkirs+SAAAAAADglSYiAABeYSwCANj5KAIAAAAAAADY' + b'XxryNK7bOFfSAAAAAN7D/fKXlRQAGk05AgDwGuNk9zNqAOg85QgAAAAAAACwp6oYYRVnKA0A' + b'AAAAAF4rC0X5PrP3mAGA1xiLAAB2PogAAAAAAAAAfi4N+SiudeJDqwAAAAAAvN1EBAD/Y+9u' + b'cttG0jAAE0H2Lp3AvIF1A3NR+/gGUZ+gPSC0bvdaCMZzgvHcwL0X0MoJRr6BcgLJN5iqFtPx' + b'pIN2YuuHJT4P8KEYw6u3SYVpsV4CP+hsFKdjMQCAcgQAAAAAAAB4Vohtk5ZFmnNpAAAAAEf2' + b'IAKAojUiAADcQwDAyyhHAAAAAAAAgL8RYjtJy+9pzqQBAAAA9MBGBABFa0QAALzAWAQAUFVv' + b'RQAAAAAAAADfFmJ7l5b3kgAAAAAA4LVGcZo3NZ5LAgB4gUYEAFBVb0QAAAAAAAAA/y/ENqRZ' + b'VooRAAAAgP5ZiQCgWI0IAIAXOh/FaS0GAIZOOQIAAAAAAAA8EWLbVNtNBhfSAAAAAHpoJQKA' + b'YjUiAABeYSwCAIZOOQIAAAAAAAB0Qmyv0/J7mjNpAAAAAACwY40IAIBXUI4AwOC9FQEAAAAA' + b'AABDF2Ib0nKX5p00AAAAgJ7biACgPKM4zZsZFfMCAK/RiACAoVOOAAAAAAAAwKCF2OYHUu/T' + b'nEsDAAAAKMBSBABFakQAALzSpQgAGLo3IgAAAAAAAGCoQmyv0/LfSjECAAAAAAD71YgAAHit' + b'UZyOpQDAkL0VAQAAAAAAAEMTYhvScl95swYAAABQno0IAIrUiAAA2IFcjrAUAwBD9UYEAAAA' + b'AAAADEmI7VVaVpViBAAAAKBAm/kHm2AACjOK0zotZ5IAAHZgLAIAhuytCAAAAAAAABiCENuQ' + b'lts076UBAAAAAMABNSIAAHZEOQIAg6YcAQAAAAAAgJMXYtuk5S7NuTQAAACAgj2KAKBIjQgA' + b'gB25FAEAQ/ZGBAAAAAAAAJyqENuQ5jYd/l4pRgAAAADKtxQBQJG84RkA2JlRnLq3AGCwlCMA' + b'AAAAAABwkkJsm2q7YeBnaQAAAAAAcAyjOA1puZAEALBDtQgAGKq3IgAAAAAAAOCUhNjmB03v' + b'0ryTBgAAAHBiViIAKI43OwMA+7i/uBcDAEP0RgQAAAAAAACcihDbSbXdJKAYAQAAADhFKxEA' + b'FKcRAQDg/gIAduOtCAAAAAAAAChdiG1+M8ZtmktpAAAAAADQI2MRAAA7VosAgKFSjgAAAAAA' + b'AECxQmxDWm7S/CwNAAAAYABWIgAoTiMCAGDHzkUAwFC9EQEAAAAAAAAlCrGdVNsNAYoRAAAA' + b'gKFYiQCgHKM4rdNyJgkAYA/3GY0UABiityIAAAAAAACgJCG2TVpu0lxKAwAAAACAHhuLAADY' + b'433GQgwADI1yBAAAAAAAAIoQYltX21KE99IAAAAABmopAoCiKEcAAPYliACAIVKOAAAAAAAA' + b'QK+F2OaHOq7T/CINAAAAYMg28w8bKQAURTkCALAvjQgAGCLlCAAAAAAAAPTSk1KEPGcSAQAA' + b'AACgMMoRoJ8e0izTrETx3cI3PtPqNOeigaOpRQDAEClHAAAAAAAAoHdCbCdpuak8UAUAAADw' + b'2UcRAJRjFKd5I7H/xw3H91h9KUJYpLlfz2cbsezlM+9zeUJdfdm03XTrpZRg59xnADBIyhEA' + b'AAAAAADoDaUIAAAAAACciLEI4KAe0uTSg0W1LULIs1SEcBhdzovnfm8Up01aPhcp1N0oToAX' + b'ytdUuv4WkgBgSJQjAAAAAAAAcHRKEQAAAACetRIBQFGUI8B+fKyelB/kdT2fLcVShiebuO+f' + b'/nwUp3X3uZmn6dYzicGzgggAGBrlCAAAAAAAAByNUgQAAACA77YSAUBRahHAqzxUXwoQFtW2' + b'BMH90Inq/tvm+bM0oStMaKovhQkXkoK/GFdflY0AwKlTjgAAAAAAAMBBhdjmt1dcd+ONLwAA' + b'AAAAnKKxCOC7PFZPChDy8Xo+W4qFrjDh7vOfR3Gav2Ns0lx1q/J1UMYEwAApRwAAAAAAAOAg' + b'QmzrtEwqpQgAAAAAL2GTIEBZLkUAf/G0CCGvy24DPDwrnSubtNx3k8sScglNU22/f7yQEANV' + b'iwCAoVGOAAAAAAAAwF6F2OYHk3IhwntpAAAAALzYRgQAZRjFaS0F+MPHShECe5LOp2V3bt12' + b'n7tXlaIEhmcsAgCGRjkCAAAAAAAAexFiO6m2DyB5OxYAAADA6ylHAChHLQIG6FO13ai+yNNt' + b'XIeD6Io3bqsvRQmTbs6lw4k7EwEAQ6McAQAAAAAAgJ0Jsa0rDxsBAAAA7Nxm/sEGQ4ByeIsz' + b'Q/CY5r76UoawEgl90J2LN3lGcXpVbb+3fCcZTlU6z8cKaQAYEuUIAAAAAAAAvFqIbVNtHyx6' + b'Lw0AAAAAAAauFgEn6mPVFSLYiEsJ0nmaz9f7UZzmz+Xravt95plkODFBBAAMiXIEAAAAAAAA' + b'XiTEtk5LfttKfpDoXCIAAAAAe/MgAoCijEXAiXisujKEvK7ns41IKFE6d1dpuR7F6U21/W4z' + b'j5IETkXTfU4DwCAoRwAAAAAAAOCHhNjmQoRJmnfSAAAAADgIGxEBylKLgML9luZuPZ/di4JT' + b'0hV83Izi9LZSkgAAUCTlCAAAAAAAADwrxDa/5So/HJSLETwgBAAAAHBYyhEAynIuAgr0KU3e' + b'MH7XbSCHk/VVSUKe91KhYI0IABgS5QgAAAAAAAB8U4htXX0pRPAgJwAAAMDxLEUAUIZRnI6l' + b'QGEe0tyu57M7UTA0XUnC5ElJwqVUAAD6TTkCAAAAAAAAf+oKEXIZwiTNhUQAAAAAAOCHBBFQ' + b'iE9pbpQiwB8lCbmMrBnF6aTaliScSYWCKGYCYFCUIwAAAAAAAAxciG1+WKKpFCIAAAAA9NVC' + b'BADFaERAzz2muVaKAH+Vr4tRnN6nw3x9vJMIhVDmAcCgKEcAAAAAAAAYoK4QYZLmKs25RAAA' + b'AAAAYCeCCOixf6W5Wc9nG1HAt3XXx9UoTq/z9VLZeE4B0vlap3N3JQkAhkA5AgAAAAAAwACE' + b'2OaHMZtqW4aQx0M8AAAAAOVYiQCgGGMR0EMPaSbr+WwpCvg+6Xq5HcXpIh3epbmQCD1X+3cj' + b'AEOhHAEAAAAAAOBEhdjmBzCbaluGcCkRAAAAgDJt5h9WUgAoRhABPfPrej67EQP8uFwoMorT' + b'ptoWJLyTCO4/AOD4lCMAAAAAAACciBDbutqWIeTJhQhnUgEAAAAo3qMIAIri7eL0xac0k/V8' + b'thAFvFy6hjZpuRrF6W1af5YIPZVfnHAvBgCGQDkCAAAAAABAob4qQ8hzLhUAAACAk7MUAUAZ' + b'RnHqrc30xcc0V92mbmAH0vV0nT7n8735v6UBAHA8yhEAAAAAAAAKoQwBAAAAAAB6bSwCeuBf' + b'eRO3GGD30rV1N4rTfKggAfcgAHAkyhEAAAAAAAB6KsS2qbYlCONuPZMKAAAAwOAsRAAAfKef' + b'8uZtMcD+KEigp4IIABgK5QgAAAAAAAA90BUhjJ/MhVQAAAAAAKAojQg4ksc0V+v5bCEK2D8F' + b'CQAAx6McAQAAAAAA4IBCbPMbG8aVIgQAAAAAvs9SBADA38jFCM16PnPPAAekIIGeuRQBAEOh' + b'HAEAAAAAAGBPQmybtNTdfD4+lwwAAAAAP2AjAoBijEXAgSlGgCPqChLqdPiLNAAADkM5AgAA' + b'AAAAwCuE2NbVtvQgP/AYKiUIAAAAAOzWSgQAxQgi4MAUI8CRpWvwpitIeC8NAID9U44AAAAA' + b'AADwjBDbpjt8uuYHHC+kAwAAAMA+beYfVlIAKIZyBA7pJ8UI0BvX1bZM3/fHHM0oTnNhzkIS' + b'AJw65QgAAAAAAMCgPSk+qLvJDy6Ou59dSggAAACAI3oUAUBRbIrlUP6xns/uxAD9kK7HzShO' + b'J+lwkeZMIgAA+6McAQAAAAAAOCkhtrnY4PObmepuvnV8Li0AAAAAes7boAGAr/22ns9uxQD9' + b'kq7L5ShOb9LhP6UBALA/yhEAAAAAAICDC7FtvuPXvvU7ufRg/NXP8p+9fQMAAAAAADiaUZzW' + b'UuAAHtJMxAD9lItL0t8HV+nwUhocQZNmIQYATp1yBAAAAADgEH4Jsf1FDAAAAAAA8EMWIgAo' + b'Ri0CDmCyns82YoB+X6dplpWCfwCAvXgjAgAAAAAAAAAAAAAAAOi1X9fz2VIM0G/pOl2l5VYS' + b'AAD7oRwBAAAAAAAAAAAAAPppIQKAYoxFwB49rOezGzFAGbrr9ZMkOLBaBAAMgXIEAAAAAAAA' + b'AAAAAACA1wkiYI+uRQCuW3hGLQIAhkA5AgAAAAAAAAAAAAD001IEADB4/1nPZwsxQFnSdXuf' + b'lo+SAADYrf8JwN7d5LaNJWoYFgo1j7UCewfxDuKB5p0dxL2C9h1o3K4xIbSzglZ2oMwP0PQK' + b'Wl5BUysgtYN7WFYhTuLYlkSKP+d5AIJpXNzJW6YpD84n4wgAAAAAAAAAAAAA0ENVWFQqAAzG' + b'hQS0YDvx7fMwZLcSAAA0yzgCAAAAAAAAAAAAAPTPgwQAg3IhAS24K0NmLAkGKj6/uc/1+CwC' + b'AM0yjgAAAAAAAAAAAAAA/eMgJACkbRuvOxlg8DzHnMq5BACkwDgCAAAAAAAAAAAAAPTPWgIA' + b'SNpdGTJjSTBw8TlexttGCQCAZhhHAAAAAAAAAAAAAID+cRgSYFg+SEDDlhKA5xkAgO8ZRwAA' + b'AAAAAAAAAACA/llLAADJ+lKGrJABRmMpAQBAM4wjAAAAAAAAAAAAAED/VBIAQLJWEsB47MZO' + b'HpSgbdPZ/FIFAMbOOAIAAAAAAAAAAAAA9M9aAgBI0qYMmXEEGJ+lBJzAmQQAjJ1xBAAAAAAA' + b'AAAAAADomSosKhUAhsG3NNMwwwjg2QYA4BeMIwAAAAAAAAAAAABAvzxIADAovqWZJi0lgPEp' + b'Q1b4nA8AcDzjCAAAAAAAAAAAAADQL5UEAJCkTRmytQwwWisJAACOYxwBAAAAAAAAAAAAAPrF' + b'oUgASFMuAXjGAQD4NeMIAAAAAAAAAAAAANAvlQQAkCTfKg8jVoYsV4GWXUoAwNgZRwAAAAAA' + b'AAAAAACAfllLADAoFxLQkFwCGL17CWjRmQQAjJ1xBAAAAAAAAAAAAADol0oCgEG5kIAGPJQh' + b'8xkAxi+XAADgcMYRAAAAAAAAAAAAAKBf1hIAgPc/4FkHAOB7xhEAAAAAAAAAAAAAoEeqsPCt' + b'0QCQHgemIQ2FBAAAhzOOAAAAAAAAAAAAAAD9cS8BACTJOAIkoAyZZx0A4AjGEQAAAAAAAAAA' + b'AAAAAKBDZchyFSAZDxIAABzGOAIAAAAAAAAAAAAA9EcuAQAkZyMBJKWSAADgMMYRAAAAAAAA' + b'AAAAAKA/HJQCgPQUEkBScgkAAA5jHAEAAAAAAAAAAAAA+mMtAcDgXEjAkQoJAAAAXmccAQAA' + b'AAAAAAAAAAD6o5AAYHAuJMD7H9hDLgEtuZQAgLEzjgAAAAAAAAAAAAAAPVGFRaECAAAABziT' + b'AICxM44AAAAAAAAAAAAAAP2wkQAAkpRLAAAA8DrjCAAAAAAAAAAAAADQD4UEAAAwbmXIchUA' + b'AA5jHAEAAAAAAAAAAAAA+mEtAQAAAADA84wjAAAAAAAAAAAAAEA/VBIAAAAAADzPOAIAAAAA' + b'AAAAAAAA9EMuAQAkyUASAADAGxhHAAAAAAAAAAAAAIB+cDASABJUhmytAgAAwOuMIwAAAAAA' + b'AAAAAABAD1Rh4WAkAAAAh7qYzubXMgAwZsYRAAAAAAAAAAAAAKB7GwkAAAA4wnm8rmUAYMyM' + b'IwAAAAAAAAAAAABA9woJACBN09n8TAUAAIDXGUcAAAAAAAAAAAAAgO6tJQCAZF1KAEBDcgkA' + b'GDPjCAAAAAAAAAAAAADQvUoCAAAAAIBfM44AAAAAAAAAAAAAAN3LJQAAgPGbzuYXKgAAHMY4' + b'AgAAAAAAAAAAAAB0r5AAAACScCEBAMBhjCMAAAAAAAAAAAAAQMeqsChUAIBkXUgAAADwOuMI' + b'AAAAAAAAAAAAANCtBwkAIGkXEkBSLiUAADiMcQQAAAAAAAAAAAAA6FYlAQAAJONMAgCAwxhH' + b'AAAAAAAAAAAAAIBu5RIA+D1O0q4kgKRcSgAAcBjjCAAAAAAAAAAAAADQrUICAABIxoUE+PsS' + b'AA5jHAEAAAAAAAAAAAAAulVIAABJ+yABJOW9BPj7EgAOYxwBAAAAAAAAAAAAALq1lgAA0jad' + b'zc9UgCSe9UsVAAAOZxwBAAAAAAAAAAAAALqzrcKikgEAkufANKThSgIAgMMZRwAAAAAAAAAA' + b'AACA7qwlAAAmDkxDKgyhAAAcwTgCAAAAAAAAAAAAAHSnkAAAmDgwDam4kgAA4HDGEQAAAAAA' + b'AAAAAACgO4UEAMDEOAKM3nQ2r5/zcyVo2VoCAMbMOAIAAAAAAAAAAAAAdCeXAGDwHEKkCefT' + b'2fxCBhi1KwloWxmySgUAxsw4AgAAAAAAAAAAAAB0p5AAYPAcQqQpVxLAqF1LAABwHOMIAAAA' + b'AAAAAAAAANCRKiwKFQCAnSsJYJyms/lFvL1XAgDgOMYRAAAAAAAAAAAAAKAbDxIAAE98lAA8' + b'33CErQQAjJ1xBAAAAAAAAAAAAADoRiEBAPDEu+lsfikDjNKNBJzAWgIAxs44AgAAAAAAAAAA' + b'AAB0w8EVgHEoJKBB1xLAuExn86t4O1cCAOB4xhEAAAAAAAAAAAAAoBvGEQBGoAxZoQIN+igB' + b'jM6NBAAAzTCOAAAAAAAAAAAAAADdKCQAAH5wvvuWeWAE4vN8EW9/UwJ/YwJAM4wjAAAAAAAA' + b'AAAAAEAHqrBYqwAAPONaAhiNWwk4oUICAMbOOAIAAAAAAAAAAAAAnN6DBACjspWABn2azuZn' + b'MsCwxef4on6elQAAaI5xBAAAAAAAAAAAAAA4vUICgFFZS0DDbiSAwbuTgBOrJABg7IwjAAAA' + b'AAAAAAAAAMDpOUQLALzkZjqbn8kAwxSf36t4+5sS+DsTAJplHAEAAAAAAAAAAAAATs+hFQDg' + b'Je/i9VEGGKylBAAAzTOOAAAAAAAAAAAAAACnV0gA4Pc6vOJuOpufyQDDEp/b23g7V4IOVBIA' + b'MHbGEQAAAAAAAAAAAADgxKqwWKsAMCqFBLTgXbxuZIDhmM7ml/H2TyXoQhkyf2cCMHrGEQAA' + b'AAAAAAAAAADgtB4kAADe6GY6m5/JAIOxlAAAoD3GEQAAAAAAAAAAAADgtAoJAEankoCWvIvX' + b'nQzQf9PZvH5W3ytBRzYSAJAC4wgAAAAAAAAAAAAAcFprCQD8boc9fJrO5lcyQH/FZ/Q63v6h' + b'BB0qJAAgBcYRAAAAAAAAAAAAAOC0HKAFAPa1nM7mZzJA/8Rn8zLe7pQAAGifcQQAAAAAAAAA' + b'AAAAOK1CAgC/22FP5xOHr6F3prP5Rbzl8XqnBh0zwgdAEowjAAAAAAAAAAAAAMAJVWHh0ArA' + b'yJQhK1TgBD5NZ/OPMkA/xOfxLN5WE8MI9ORPTQkASIFxBAAAAAAAAAAAAAA4nXsJAIAjLKez' + b'+aUM0K3dMEIer/dq0BOFBACkwDgCAAAAAAAAAAAAAJxOIQHAaD1IwAnU31C/3B3MBjpgGAF/' + b'awJAd4wjAAAAAAAAAAAAAMDpFBIAjFYlASdSH8heyQCnZxgBAKBbxhEAAAAAAAAAAAAA4HRy' + b'CQBGyzgCp/RhOpsvZYDTic/c5cQwAj1VhszfmgAkwTgCAAAAAAAAAAAAAJzOWgIAv+OhIZ8M' + b'JMBpGEYAAOgH4wgAAAAAAAAAAAAAcBrbKix8qzgA0CQDCdCy+Ixdx9t/4/VODXrqXgIAUmEc' + b'AQAAAAAAAAAAAABOwzeKA4xbLgEdMZAALYjP1dnu2fq3GgAA/WAcAQAAAAAAAAAAAABOI5cA' + b'AGiJgQRoUHyeLnef3z+pwQAY4gMgGcYRAAAAAAAAAAAAAOA0CgkARs3BRLpWDySs62+7lwIO' + b'F5+hm8njMMJ7NRiISgIAUmEcAQAAAAAAAAAAAABOw6FZgBErQ+ZgIn1QH+Ze7771HthDfG4u' + b'4pXHf/4rXu8UYUByCQBIhXEEAAAAAAAAAAAAADiBKiyMIwCM34ME9MB5vPLpbH4tBbxNfF5u' + b'J49jZh/UAADor98lAAAAAAAAAAAAAIDWOSwLkIZKAnqi/tb7f09n86t4vylD5mcTnrF7RpaT' + b'x1ERGKT4Oz5XAYBU/CYBAAAAAAAAAAAAALRuLQGA3/fQgU/1z+XuADiwE5+Ji3jl8Z//mRhG' + b'YNi2EgCQkt8lAAAAAAAAAAAAAIDWFRIAJKGSgB6qD37/Zzqbf4732zJkfk5JVj2KUD8Hk8fh' + b'EBgDw0wAJOU3CQAAAAAAAAAAAACgdbkEAH7fQ8f+Ea9iOptfS0Fq6lGEeC3jP/83MYzAuBQS' + b'AJCS3yUAAAAAAAAAAAAAgNb5Nk8AoA/exevfu4GE2zJkuSSMWT2KUP+sTwwiMF6FBACk5DcJ' + b'AAAAAAAAAAAAAKBVmyosKhkAxs9BcwbkQ7z+M53N83hdysHYxJ/rj/XPd/zn/yaGERg3Q3wA' + b'JOV3CQAAAAAAAAAAAACgVQ6rAKRlG693MjAQ9UjCf6ez+Zd4Xxr4YMjiz/FZvF3H6yZe54qQ' + b'CEN8ACTFOAIAAAAAAAAAAAAAtMs4AkB6v/c/yMDAfKqv6Wx+H+93ZchWkjAU8ef24+RxFOFv' + b'apAaozYApMY4AgAAAAAAAAAAAAC0yzgCQHq/940jMFT1z+6H6Wy+ife7eC3LkPlWcnon/oxe' + b'Th4HEerrnSIkaiMBAKkxjgAAAAAAAAAAAAAA7TKOAJAWB8kZg/N4/au+prP5l3hflSFbyUKX' + b'4s/iVbx93F3nisCkkACA1BhHAAAAAAAAAAAAAID2bKuwKGQASEoer3/KwIh8qq/pbF5/Q3k9' + b'kLAsQ2b8idbFn7mzeLuafBtEeKcK/PSZAwCSYhwBAAAAAAAAAAAAANrj4CBAegoJGKnzeP2j' + b'vp4MJazKkOXS0JT4s3U1eRxEqK8PioDPHADwlHEEAAAAAAAAAAAAAGhPLgFAWsqQFdPZXAjG' + b'7ulQwnb3maceS8jrZ0Ae3soYAhzF71sAkmMcAQAAAAAAAAAAAADas5YAIEn3E4d8Sce7eP1t' + b'd9WH3TeTx7GE+lqXIfN5iMnuZ+Mi3i4nj0MIl35PwnHi79dcBQBSYxwBAAAAAAAAAAAAANrj' + b'MCBAmoqJQ7+k6zxen3ZXfSB+u/tMlO/u9WBCIdO4xf/u9fhBfV1Mvo0hvFMGGrORAIAUGUcA' + b'AAAAAAAAAAAAgJZUYVGoAJAkv//hm/pA/IfJk8GQJ4MJ693z8ufdaMLwxP+WV/F2NnkcP/hr' + b'DOG9MtA6Q3wAJMk4AgAAAAAAAAAAAAC0414CgGTl8fqnDPBLPw0m1KazeX17iFe1e45q693/' + b'Np7Qgfjf5K/hg7/uF0+uc4WgM8YRAEiScQQAAAAAAAAAAAAAaIfDKgDeAcD+3u/uH378P+zG' + b'E2p/DSj89bxVPzx/1Y//v2XIcmlfFxv/NYRwtbs+qAI+awBAXxhHAAAAAAAAAAAAAIB2OKwC' + b'kKgyZNV0Nt/Gf75TA1rx/sm/33R4/8mwAoC/NwFgoH6TAAAAAAAAAAAAAABa4bAKgPcAAEDT' + b'tmXIChkASJFxBAAAAAAAAAAAAABo3rYKC4diAdKWSwAAtMDfmgAkyzgCAAAAAAAAAAAAADTP' + b'YRUAvAsAgDbkEgCQKuMIAAAAAAAAAAAAANC8XAKA5BlHAAB8xgCABhlHAAAAAAAAAAAAAIDm' + b'OawCkLgyZEW8bZUAAPy9CQDNMI4AAAAAAAAAAAAAAM1zWAUA7wMAoGnb3QATACTJOAIAAAAA' + b'AAAAAAAANGtbhUUhAwBRLgEA0CDDSwAkzTgCAAAAAAAAAAAAADTLYRUAvBMAgDbkEgCQMuMI' + b'AAAAAAAAAAAAANCsXAIAdowjAAD+3gSAhhhHAAAAAAAAAAAAAIBmOQgLwJ/KkBXxtlECAPD3' + b'JgAczzgCAAAAAAAAAAAAADQrlwCAJxxiBACa8FCGrJIBgJQZRwAAAAAAAAAAAACA5myqsHBY' + b'BYCncgkAgAYYXAIgecYRAAAAAAAAAAAAAKA5DqsA8KNcAgDAZwoAOJ5xBAAAAAAAAAAAAABo' + b'jnEEAL5Thsy7AQBoQi4BAKkzjgAAAAAAAAAAAAAAzcklAOAZ9xIAAEfYliErZAAgdcYRAAAA' + b'AAAAAAAAAKAhVVjkKgDwDO8HAMBnCQA4knEEAAAAAAAAAAAAAGjGgwQA/EIuAQDgswQAHMc4' + b'AgAAAAAAAAAAAAA0Yy0BAM8pQ5arAAAcwWcJAJgYRwAAAAAAAAAAAACApuQSAPCCewkAgANs' + b'y5AZ4wOAiXEEAAAAAAAAAAAAAGiKwyoAvCSXAADwGQIADmccAQAAAAAAAAAAAACOt63CwjgC' + b'AC9ZSQAA+AwBAIczjgAAAAAAAAAAAAAAxzOMAMCLypDV74qtEgDAnnIJAOCRcQQAAAAAAAAA' + b'AAAAOF4uAQDeFwBAwzZlyAoZAOCRcQQAAAAAAAAAAAAAOF4uAQBvsJIAAPC3JgAcxjgCAAAA' + b'AAAAAAAAABxvLQEAb5BLAADswbASADxhHAEAAAAAAAAAAAAAjvNQhUUlAwCvKUNWxNtGCQDg' + b'jXIJAOAb4wgAAAAAAAAAAAAAcJy1BADswTdAAwBvcV+GzBAfADxhHAEAAAAAAAAAAAAAjpNL' + b'AID3BgDQMINKAPAD4wgAAAAAAAAAAAAAcJy1BAC8VRmy+qDjVgkA4BW5BADwPeMIAAAAAAAA' + b'AAAAAHC4bRUWxhEA2FcuAQDwgk0ZMn9rAsAPjCMAAAAAAAAAAAAAwOEcVgHgECsJAACfFQBg' + b'P8YRAAAAAAAAAAAAAOBwuQQAHMCBRwDA35oAsCfjCAAAAAAAAAAAAABwuFwCAPZVhqyKt3sl' + b'AIBnbONnBUNKAPAM4wgAAAAAAAAAAAAAcKAqLHIVADiQQ48AgM8IALAH4wgAAAAAAAAAAAAA' + b'cJgHCQA4goOPAMBzcgkA4HnGEQAAAAAAAAAAAADgMLkEAByqDFkxMbQDAPzMgBIA/IJxBAAA' + b'AAAAAAAAAAA4TC4BAEdaSgAAPPG1DFklAwA8zzgCAAAAAAAAAAAAABwmlwCAI/lmaADAZwMA' + b'eCPjCAAAAAAAAAAAAACwv00VFr7NE4CjlCEr4u1BCQBgxzgCALzAOAIAAAAAAAAAAAAA7C+X' + b'AICGLCUAAKKvZciM8AHAC4wjAAAAAAAAAAAAAMD+cgkAaMhSAgAgWkkAAC8zjgAAAAAAAAAA' + b'AAAA+8slAKAJu2+I/qoEACRtOzGOAACvMo4AAAAAAAAAAAAAAPvZVmFRyABAgxyGBIDEPwvs' + b'BpMAgBcYRwAAAAAAAAAAAACA/eQSANCwehxhKwMAJP1ZAAB4hXEEAAAAAAAAAAAAANhPLgEA' + b'Tdp9U7RDkQCQpm38LOBzAAC8gXEEAAAAAAAAAAAAANhPLgEALVhKAAA+AwAAv2YcAQAAAAAA' + b'AAAAAADebluFxVoGAJpWhiyPt40SAJCcpQQA8DbGEQAAAAAAAAAAAADg7XIJAGjRnQQAkJRN' + b'GTIDfADwRsYRAAAAAAAAAAAAAODtcgkAaNFKAgBIimEkANiDcQQAAAAAAAAAAAAAeLtcAgDa' + b'UoasiLevSgBAMpYSAMDbGUcAAAAAAAAAAAAAgLfZVmGxlgGAli0lAIAkfC1DVskAAG9nHAEA' + b'AAAAAAAAAAAA3iaXAIC2lSFbxdtGCQAYvaUEALAf4wgAAAAAAAAAAAAA8Da5BACcyJ0EADBq' + b'm90gEgCwB+MIAAAAAAAAAAAAAPA2uQQAnMhSAgDwrgcAvmccAQAAAAAAAAAAAABet63CYi0D' + b'AKdQhqyKty9KAMBo3UkAAPszjgAAAAAAAAAAAAAAr8slAODEHJoEgHH6shtCAgD2ZBwBAAAA' + b'AAAAAAAAAF6XSwDAKZUhW8fbvRIAMDpLCQDgMMYRAAAAAAAAAAAAAOB1uQQAdOBOAgAYlYcy' + b'ZP6+BIADGUcAAAAAAAAAAAAAgJdtq7BYywDAqZUhW8XbRgkAGA3DRwBwBOMIAAAAAAAAAAAA' + b'APCyXAIAOnQrAQCMwrYM2VIGADiccQQAAAAAAAAAAAAAeFkuAQAdWsVrKwMADN6dBABwHOMI' + b'AAAAAAAAAAAAAPCyXAIAulKGrJo4TAkAY7CUAACOYxwBAAAAAAAAAAAAAH5tW4XFWgYAOlaP' + b'I2xlAIDB+lKGrJABAI5jHAEAAAAAAAAAAAAAfi2XAICulSGrJo8DCQDAMN1KAADHM44AAAAA' + b'AAAAAAAAAL+2kgCAnjCOAADD9LUMWSEDABzPOAIAAAAAAAAAAAAA/FouAQB9UIasircvSgDA' + b'4Bg4AoCGGEcAAAAAAAAAAAAAgOdtqrAoZACgR24lAIBBuS9DlssAAM0wjgAAAAAAAAAAAAAA' + b'z8slAKBPypAV8fZFCQAYjFsJAKA5xhEAAAAAAAAAAAAA4Hm5BAD00G28tjIAQO/dlyHzdyUA' + b'NMg4AgAAAAAAAAAAAAA8byUBAH1ThqyItzslAKD3biUAgGYZRwAAAAAAAAAAAACAnz1UYVHJ' + b'AEBP1eMIWxkAoLfuy5DlMgBAs4wjAAAAAAAAAAAAAMDPcgkA6KsyZPWAz50SANBbtxIAQPOM' + b'IwAAAAAAAAAAAADAz3IJAOi5ehxhKwMA9M59GTJ/UwJAC4wjAAAAAAAAAAAAAMAPqrBYqQBA' + b'n5Uhq+LtRgkA6J1bCQCgHcYRAAAAAAAAAAAAAOB79xIAMARlyJbxtlECAPrz92R8P+cyAEA7' + b'jCMAAAAAAAAAAAAAwPdyCQAYkBsJAKA3biUAgPYYRwAAAAAAAAAAAACA760kAGAoypDV7617' + b'JQCgc/fxvZzLAADtMY4AAAAAAAAAAAAAAN9sq7BYywDAwNxIAACdu5YAANplHAEAAAAAAAAA' + b'AAAAvllJAMDQlCGrh30+KwEAnfkS38eFDADQLuMIAAAAAAAAAAAAAPBNLgEAA3Ubr60MAHBy' + b'2917GABomXEEAAAAAAAAAAAAAPgmlwCAISpDVk0czASALtzF93AhAwC0zzgCAAAAAAAAAAAA' + b'ADx6qMKikAGAoSpDdle/z5QAgJPZxOtOBgA4DeMIAAAAAAAAAAAAAPAolwCAEbiWAABO5rYM' + b'WSUDAJyGcQQAAAAAAAAAAAAAeJRLAMDQlSFbx9tnJQCgdffxvbuUAQBOxzgCAAAAAAAAAAAA' + b'AERVWKxUAGAkbuO1kQEAWn/fAgAnZBwBAAAAAAAAAAAAACaTrxIAMBZlyKp4u1ECAFrzJb5v' + b'cxkA4LSMIwAAAAAAAAAAAADAZJJLAMCYlCFbTYz/AEAbthMjRADQCeMIAAAAAAAAAAAAADCZ' + b'rCQAYISuJ48HOAGA5tyWIatkAIDTM44AAAAAAAAAAAAAQOo2VVgUMgAwNruDm9dKAEBj7uP7' + b'9U4GAOiGcQQAAAAAAAAAAAAAUreSAICxKkNWv+e+KgEAjbiRAAC6YxwBAAAAAAAAAAAAgNTl' + b'EgAwctfx2soAAEf5owzZWgYA6I5xBAAAAAAAAAAAAACSVoXFSgUAxqwMWTV5HEgAAA6zided' + b'DADQLeMIAAAAAAAAAAAAAKTsqwQApKAM2cp7DwAOdr0bGwIAOmQcAQAAAAAAAAAAAICU5RIA' + b'kJDryeM3XwMAb/e5DJm/HQGgB4wjAAAAAAAAAAAAAJCylQQApGL3jdfXSgDAm9WjQrcyAEA/' + b'GEcAAAAAAAAAAAAAIFWbKiwKGQBIye6br/9QAgDe5Ho3LgQA9IBxBAAAAAAAAAAAAABStZIA' + b'gBSVIbuNt3slAOBFn3ejQgBATxhHAAAAAAAAAAAAACBVxhEASNnHeG1lAIBnbeJ1KwMA9Itx' + b'BAAAAAAAAAAAAABStK3CIpcBgFSVIasmjwMJAMDPPu7elQBAjxhHAAAAAAAAAAAAACBFuQQA' + b'pK4MWf0+/EMJAPjOH/EduZYBAPrHOAIAAAAAAAAAAAAAKVpJAAB/DiTcxtu9EgDwp4fduxEA' + b'6CHjCAAAAAAAAAAAAACkyDgCAHzzMV4bGQBI3Hb3TgQAeso4AgAAAAAAAAAAAACpeajCopIB' + b'AB6VIavfi/Vh0K0aACTsOr4TCxkAoL+MIwAAAAAAAAAAAACQmqUEAPC9MmTreLtRAoBEfYnv' + b'wpUMANBvxhEAAAAAAAAAAAAASE0uAQD8rAzZMt4+KwFAYh4mBoIAYBCMIwAAAAAAAAAAAACQ' + b'kk0VFmsZAOB5Zcjqw6H3SgCQiG28ruP7r5ICAPrPOAIAAAAAAAAAAAAAKVlJAACv+jh5/BZt' + b'ABi7mzJkBvQAYCCMIwAAAAAAAAAAAACQklwCAHjZ7tuz64GErRoAjNjn+M5bygAAw2EcAQAA' + b'AAAAAAAAAIBUbKuwWMkAAK8rQ1bE29XEQAIA4/QQ33U3MgDAsBhHAAAAAAAAAAAAACAVhhEA' + b'YA9lyNbx5uAoAGNTD/9cyQAAw2McAQAAAAAAAAAAAIBU5BIAwH7KkC3j7e9KADAiV/H9VskA' + b'AMNjHAEAAAAAAAAAAACAVKwkAID97QYSvigBwAj8Pb7X1jIAwDAZRwAAAAAAAAAAAAAgBV+r' + b'sPDNoABwoDJk1xMDCQAM2+fd4A8AMFDGEQAAAAAAAAAAAABIQS4BABzHQAIAA/Y1vsduZACA' + b'YTOOAAAAAAAAAAAAAEAKVhIAQCPqg6UPMgAwIPV761oGABg+4wgAAAAAAAAAAAAAjN1DFRaF' + b'DABwvDJkVbxdTQwkADAM23h93L2/AICBM44AAAAAAAAAAAAAwNgtJQCA5hhIAGAg6mGEq/je' + b'KqQAgHEwjgAAAAAAAAAAAADA2OUSAECzDCQAMADX8X21lgEAxsM4AgAAAAAAAAAAAABjtqnC' + b'wmEYAGiBgQQAeuzv8T21kgEAxsU4AgAAAAAAAAAAAABj5jAMALTIQAIAPfR/8f20lAEAxsc4' + b'AgAAAAAAAAAAAABjtpQAANplIAGAHvkS30t3MgDAOBlHAAAAAAAAAAAAAGCsNlVYrGUA4P/Z' + b'u7fctrG0XcB04HuLI7B6BPEegV0A78v/CKIeQfuHweso1wLRqhGUPIJ27hfQ8ghaHsGWRyBq' + b'BNqLkbzjSufggw48PA/w4VOcunrLWSIv+JLdU5AAQA1UxQgDMQBAeylHAAAAAAAAAAAAAKCt' + b'bkUAAPujIAGAA1KMAAAdoBwBAAAAAAAAAAAAgLaaigAA9utJQcKdNADYk6qU50oMANB+R6vV' + b'SgpA5/Sya4cfAAAAAAAAAABAuy3LUPTEAACHk2b5JK4PkgBgh6pihItNOQ8A0HLvRAAAAAAA' + b'AAAAAABAC92KAAAOaxFGg7huJAHAjihGAICOUY4AAAAAAAAAAAAAQBspRwCAGtgUJHySBABb' + b'phgBADpIOQIAAAAAAAAAAAAAbbMsQ6EcAQBqYhFGw7j+LgkAtkQxAgB0lHIEAAAAAAAAAAAA' + b'ANpGMQIA1MwijCZx/RZnKQ0A3kAxAgB0mHIEAAAAAAAAAAAAANpGOQIA1NAijKZxXcR5kAYA' + b'r/A5UYwAAJ12tFqtpAB0Ti+7dvgBAAAAAAAAAAC007IMRU8MAFBfaZZX39XTOO+lAcAz3SzC' + b'aCAGAOi2dyIAAAAAAAAAAAAAoEVuRQAA9bZ54/dFnBtpAPAMihEAgC+UIwAAAAAAAAAAAADQ' + b'JsoRAKABqoKEzYOu/ysNAH7i74oRAIBHyhEAAAAAAAAAAAAAaItlGQrlCADQIIswGsf1P9X3' + b'uDQAeHp/l6yLESaiAAAeKUcAAAAAAAAAAAAAoC0UIwBAAy3CqPoOv4hzLw0AknUxwoViBADg' + b'W8oRAAAAAAAAAAAAAGgL5QgA0FCLMJol64KEG2kAdFpVlHO2+V4AAPiLo9VqJQWgc3rZtcMP' + b'AAAAAAAAAACgXZZlKHpiAIDmS7P8Kq5/SgKgcz7HGSzCqBQFAPA970QAAAAAAAAAAAAAQAvc' + b'igAA2mERRuO4/k+cB2kAdMYf8fy/VIwAAPyMcgQAAAAAAAAAAAAA2kA5AgC0yCKMZnGdJeu3' + b'iAPQbn+P5/6VGACAXzlarVZSADqnl107/AAAAAAAAAAAANpjWYaiJwYAaKc0y6sHZodxTqQB' + b'0K57uTgXm0IcAIBfeicCAAAAAAAAAAAAABruVgQA0F6LMBrHdRHnXhoArVGd6X3FCADASyhH' + b'AAAAAAAAAAAAAKDplCMAQMttHp69iPOHNAAa7494rp/FKUUBALzE0Wq1kgLQOb3s2uEHAAAA' + b'AAAAAADQDssyFD0xAEB3pFl+Edckzqk0AJp1/xZnsAgjBXcAwKu8EwEAAAAAAAAAAAAADeah' + b'GgDomEUYTeM6i/NZGgCNcR/nQjECAPAWyhEAAAAAAAAAAAAAaDIP1gBABy3CqIxzGT/+T7J+' + b'EzkA9XWTrIsRZqIAAN7iaLVaSQHonF527fADAAAAAAAAAABovmUZip4YAKDb0iyvrgcmcX6X' + b'BkC97tniDBZhpNQOANiKdyIAAAAAAAAAAAAAoKEmIgAAFmFUxrmMH3+L8yARgFq4j3OmGAEA' + b'2CblCAAAAAAAAAAAAAA01UQEAMCjRRhN4zqL84c0AA7qUzyTq2KEuSgAgG06Wq1WUgA6p5dd' + b'O/wAAAAAAAAAAACa7aEMRV8MAMD3pFlelSRM4ryXBsD+7tPiDDZlNQAAW/dOBAAAAAAAAAAA' + b'AAA00K0IAIAfWYTRrHprefz4v3GWEgHYuT/inClGAAB2STkCAAAAAAAAAAAAAE00EQEA8CuL' + b'MBrH1Y9zIw2AnagKaH6L5+1VnFIcAMAuKUcAAAAAAAAAAAAAoGnuy1DMxAAAPEf1sG6cQfz4' + b'W3UdIRGArfkcpx/P2KkoAIB9OBYBAAAAAAAAAAAAAA0zEQEA8FKbh3fP0iwfxD2OcyIVgFd5' + b'iDNQigAA7Ns7EQAAAAAAAAAAAADQMLciAABeaxFGk7j6cT5JA+DF/ohzphgBADiEo9VqJQWg' + b'c3rZtcMPAAAAAAAAAACgme7LUJyJAQDYhjTL+3EN43yQBsDP78XiXClFAAAO6VgEAAAAAAAA' + b'AAAAADTIWAQAwLYswmge1yDN8vHmOuNcKgB/sazOx3heDkUBABzaOxEAAAAAAAAAAAAA0CC3' + b'IgAAtm0RRrM4F/Hjb3HuJALwxec4Z4oRAIC6OFqtVlIAOqeXXTv8AAAAAAAAAAAAmudzGYpL' + b'MQAAu5Zm+UVckzin0gA66CHOYBFGU1EAAHVyLAIAAAAAAAAAAAAAGmIiAgBgHzYPBPfTLB/E' + b'PUyUJADdsIwzjmfgUBQAQB0drVZeng50Ty+7dvgBAAAAAAAAAAA0y7IMRU8MAMAhKEkAOuAm' + b'ztUijEpRAAB1dSwCAAAAAAAAAAAAABrgVgQAwKEswmgS12RTknAV571UgJa4S9alCDNRAAB1' + b'pxwBAAAAAAAAAAAAgCaYiAAAOLQnJQkXcQ/jnEsFaKiHOIN4rk1FAQA0xdFqtZIC0Dm97Nrh' + b'BwAAAAAAAAAA0BwPZSj6YgAA6mZTknAV53dpAE25v4oz3JS9AAA0yrEIAAAAAAAAAAAAAKi5' + b'iQgAgDravHF9mmZ5P+5hnMs4J5IBamiZrEsRxqIAAJrqnQgAAAAAAAAAAAAAqLmJCACAOluE' + b'0TzOIH7sx/mUrN/MDlAHy8251FeMAAA03dFqtZIC0Dm97NrhBwAAAAAAAAAA0Az3ZSjOxAAA' + b'NE2a5YO4qjmXBnAAVSlCVYYwXoRRKQ4AoA2ORQAAAAAAAAAAAABAjXmzKQDQSIswmsQ1SbO8' + b'Knq6inMZ50QywI4pRQAAWks5AgAAAAAAAAAAAAB1disCAKDJFmE0i2uQZnmv2sm6KOFUMsCW' + b'PcQZVvdQShEAgLY6Wq1WUgA6p5ddO/wAAAAAAAAAAADq76YMxUAMAEDbpFl+kayLEj5IA3ij' + b'L6UIizCaiAIAaLtjEQAAAAAAAAAAAABQU7ciAADaaBFG07imaZZfxX0Zp9rvJQO8wF2yLkWY' + b'igIA6Iqj1crL04Hu6WXXDj8AAAAAAAAAAIB6eyhD0RcDANAVaZZX1z6PZQmnEgF+4CZZlyLM' + b'RQEAdM2xCAAAAAAAAAAAAACooVsRAABdsnnQuSpHuEqzvCpIeJwT6UDnPcSZxBnHs6IUBwDQ' + b'VUerlZenA93Ty64dfgAAAAAAAAAAAPX2tzIUczEAAF2XZvkgWZck/C4N6JzPcSaLMFIeBwCQ' + b'KEcAOko5AgAAAAAAAAAAQK3dl6E4EwMAwFdplveSdUmCogRot4c4k2RdijAXBwDAV8ciAAAA' + b'AAAAAAAAAKBmxiIAAPirRRiVyeaBaUUJ0Eqfk3Uhwq0oAAC+72i18vJ0oHt62bXDDwAAAAAA' + b'AAAAoL7SMhSlGAAAnnHh9LUo4WKzT6QCjXGfbEpPNgUoAAD8hHIEoJOUIwAAAAAAAAAAANTW' + b'TRmKgRgAAF4nzfKnRQmnEoHaeYhzm6wLEWbiAAB4PuUIQCcpRwAAAAAAAAAAAKit38pQTMUA' + b'APB2aZafJV+LEs4lAgezjDNJFCIAALyJcgSgk5QjAAAAAAAAAAAA1NJDGYq+GAAAti/N8l7y' + b'tSih2qdSgd3e38SZxrldhNGtOAAA3u5YBAAAAAAAAAAAAADUxEQEAAC7sQijMq7bzVRlCf3k' + b'a1FCNSdSgjd72Pwbm8R/czNxAABs19Fq5eXpQPf0smuHHwAAAAAAAAAAQP38rQzFXAwAAPuX' + b'ZvlZ8rUs4Vwi8Gz3ybrobaoQAQBgt5QjAJ2kHAEAAAAAAAAAAKB2PpehuBQDAEA9pFl+kayL' + b'Es42+0Qq8MUyzm2cabUXYVSKBABgP45FAAAAAAAAAAAAAEAN3IoAAKA+FmE0TdYPf3+RZvlj' + b'ScLjPpUSHXK3uWeZxn8bM3EAABzG0Wrl5elA9/Sya4cfAAAAAAAAAABAfSzLUPTEAADQHGmW' + b'95N1UcJjWcK5VGiRqgxhmqzLEKbiAACoh2MRAAAAAAAAAAAAAHBgExEAADTLIozmcVVz+/iz' + b'NMsfyxIeR2ECTbCMM0uUIQAA1J5yBAAAAAAAAAAAAAAObSwCAIDmW4RR9YD57OnPFCZQQ/eb' + b'39MvhQib31sAABrgaLVaSQHonF527fADAAAAAAAAAACoh7syFBdiAADojjTL+8lfCxOqP7+X' + b'DDvwkDwpQqj2IoxKsQAANNOxCAAAAAAAAAAAAAA4oIkIAAC6ZRFG87iquX368zTLH4sSlCbw' + b'Gneb36svZQjx92wqEgCAdjlarbw8HeieXnbt8AMAAAAAAAAAADi8ZRmKnhgAAPiZNMv7yboo' + b'4SJOdf34WJxwKp3u3UMk6/KD+Wam1d4UbgAA0HLHIgAAAAAAAAAAAADgQCYiAADgVzYPvlcz' + b'/fbv0iyvihKqwoSLzY8e97nkGuvbAoTHmcXfhVI8AADdpRwBAAAAAAAAAAAAgEMZiwAAgLdY' + b'hNFs83H6vb9Ps/xi8/FxP5Yp9OOcSnDvHosPks2uyg7mmymf/P8EAID/crRaraQAdE4vu3b4' + b'AQAAAAAAAAAAHNZdGYoLMQAAcGhPChT6cS6TdZHCiWSe5T5ZFxw8mj75PHvyd7NFGJXiAgDg' + b'LY5FAAAAAAAAAAAAAMABTEQAAEAdLMJo+u11aprl/WRdllA5i9PrUCTTH/y8jFnN/MYAAHAo' + b'R6uVl6cD3dPLrh1+AAAAAAAAAAAAh7MsQ9ETAwAAAAAAz/VOBAAAAAAAAAAAAADs2UQEAAAA' + b'AAC8hHIEAAAAAAAAAAAAAPZtLAIAAAAAAF5COQIAAAAAAAAAAAAA+3RXhmIuBgAAAAAAXkI5' + b'AgAAAAAAAAAAAAD7NBEBAAAAAAAvpRwBAAAAAAAAAAAAgH1ZlqGYiAEAAAAAgJdSjgAAAAAA' + b'AAAAAADAvoxFAAAAAADAayhHAAAAAAAAAAAAAGBfJiIAAAAAAOA1lCMAAAAAAAAAAAAAsA+f' + b'y1DMxQAAAAAAwGsoRwAAAAAAAAAAAABgHyYiAAAAAADgtZQjAAAAAAAAAAAAALBrD2UobsUA' + b'AAAAAMBrKUcAAAAAAAAAAAAAYNfGIgAAAAAA4C2UIwAAAAAAAAAAAACwaxMRAAAAAADwFsoR' + b'AAAAAAAAAAAAANilmzIUpRgAAAAAAHgL5QgAAAAAAAAAAAAA7NJYBAAAAAAAvJVyBAAAAAAA' + b'AAAAAAB25a4MxUwMAAAAAAC8lXIEAAAAAAAAAAAAAHZlIgIAAAAAALZBOQIAAAAAAAAAAAAA' + b'u7AsQzERAwAAAAAA26AcAQAAAAAAAAAAAIBdGIsAAAAAAIBtUY4AAAAAAAAAAAAAwC5MRAAA' + b'AAAAwLYoRwAAAAAAAAAAAABg227KUMzFAAAAAADAtihHAAAAAAAAAAAAAGDbJiIAAAAAAGCb' + b'lCMAAAAAAAAAAAAAsE33ZSimYgAAAAAAYJuUIwAAAAAAAAAAAACwTWMRAAAAAACwbcoRAAAA' + b'AAAAAAAAANiWZRmKiRgAAAAAANg25QgAAAAAAAAAAAAAbMtYBAAAAAAA7IJyBAAAAAAAAAAA' + b'AAC2RTkCAAAAAAA7oRwBAAAAAAAAAAAAgG24KUNRigEAAAAAgF1QjgAAAAAAAAAAAADANoxF' + b'AAAAAADArihHAAAAAAAAAAAAAOCt7spQzMQAAAAAAMCuKEcAAAAAAAAAAAAA4K3GIgAAAAAA' + b'YJeUIwAAAAAAAAAAAADwFg9lKG7FAAAAAADALilHAAAAAAAAAAAAAOAthiIAAAAAAGDXlCMA' + b'AAAAAAAAAAAA8FrLOLdiAAAAAABg15QjAAAAAAAAAAAAAPBa4zIUpRgAAAAAANg15QgAAAAA' + b'AAAAAAAAvNZEBAAAAAAA7INyBAAAAAAAAAAAAABe46YMxVwMAAAAAADsg3IEAAAAAAAAAAAA' + b'AF5jLAIAAAAAAPZFOQIAAAAAAAAAAAAAL3VXhmImBgAAAAAA9kU5AgAAAAAAAAAAAAAvNRQB' + b'AAAAAAD7pBwBAAAAAAAAAAAAgJd4KEMxFQMAAAAAAPukHAEAAAAAAAAAAACAlxiKAAAAAACA' + b'fVOOAAAAAAAAAAAAAMBzPZShmIgBAAAAAIB9U44AAAAAAAAAAAAAwHNNRAAAAAAAwCEoRwAA' + b'AAAAAAAAAADgOZZxxmIAAAAAAOAQlCMAAAAAAAAAAAAA8BzjMhSlGAAAAAAAOATlCAAAAAAA' + b'AAAAAAA8x0QEAAAAAAAcinIEAAAAAAAAAAAAAH7lpgzFXAwAAAAAAByKcgQAAAAAAAAAAAAA' + b'fmUoAgAAAAAADkk5AgAAAAAAAAAAAAA/c1OGYi4GAAAAAAAOSTkCAAAAAAAAAAAAAD8zEQEA' + b'AAAAAIemHAEAAAAAAAAAAACAH7krQzEVAwAAAAAAh6YcAQAAAAAAAAAAAIAfGYoAAAAAAIA6' + b'UI4AAAAAAAAAAAAAwPfclaGYigEAAAAAgDpQjgAAAAAAAAAAAADA90xEAAAAAABAXShHAAAA' + b'AAAAAAAAAOBbD2UoJmIAAAAAAKAulCMAAAAAAAAAAAAA8K2hCAAAAAAAqBPlCAAAAAAAAAAA' + b'AAA89VCGYiIGAAAAAADqRDkCAAAAAAAAAAAAAE8NRQAAAAAAQN0oRwAAAAAAAAAAAADg0bIM' + b'xUQMAAAAAADUjXIEAAAAAAAAAAAAAB6NRQAAAAAAQB0pRwAAAAAAAAAAAACgskyUIwAAAAAA' + b'UFPHIgAAAAAAAAAAAAAgGpehKMUAANuXZvlZXD1JAHzfIoymUgAA4FeUIwAAAAAAAAAAAACw' + b'jDMWAwD8WJrlVbnB2eaP/c18+znZ/DcnEgN40Rn7vR/fPflcFbnNvvN5tggjJW8AAB1xtFqt' + b'pAB0Ti+7dvgBAAAAAAAAAAB89akMxVAMAHRZmuVVqcFjAcLT3Y9zKiGAWqsK3x4LE6ZPdrkI' + b'o5l4AADaQTkC0EnKEQAAAAAAAAAAAP6/6iGifhkKb1sFoPXSLH8sPXgsPrhIlB8AdMFDnHmy' + b'LlB43LNFGLkPAgBokGMRAAAAAAAAAAAAAHTaWDECAG2UZnlVgNBP1kUIF4kSBIAuO93M+Tff' + b'FVVZ3OzpLMJoJi4AgHo6Wq28PB3onl527fADAAAAAAAAAABIkupBoL5yBACaLs3yfrIuQXgs' + b'QjiXCgBvcJd8LUyYLsJoLhIAgMM7FgEAAAAAAAAAAABAZ40VIwDQRGmWP5YgPO5TqQCwRefJ' + b'k6Kd+L1TFctNH2cRRjMRAQDs39Fq5eXpQPf0smuHHwAAAAAAAAAA0HXVwz195QgANMGTMoTH' + b'OZEKAAf2OVGWAACwV8oRgE5SjgAAAAAAAAAAAJD8UYbiSgwA1FGa5b1kXYJwudmnUgGgxqry' + b'udvka1nCXCQAANunHAHoJOUIAAAAAAAAAAAAyd/KUMzFAEBdpFneT9ZlCNWcSwSABrtP1mUJ' + b't4swmokDAGA7lCMAnaQcAQAAAAAAAAAA6LibMhQDMQBwaGmWn8VVfSdVhQinEgGghR6SdVHC' + b'dBFGt+IAAHg95QhAJylHAAAAAAAAAAAAOu5vZSjmYgDgEBQiANBhy2RdlHCrKAEA4OWUIwCd' + b'pBwBAAAAAAAAAADosJsyFAMxALBPaZb3k3UhQjUKEQAgSR6SdVHCZBFGM3EAAPyacgSgk5Qj' + b'AAAAAAAAAAAAHVW9pbRfhqIUBQC7lmZ5L67LOFdx3ksEAH6oKkoYx7ldhNFcHAAA36ccAegk' + b'5QgAAAAAAAAAAEBHfSpDMRQDALuUZvlFXIM4H6QBAC/2Oc5kEUa3ogAA+CvlCEAnKUcAAAAA' + b'AAAAAAA6aBmnX4aiFAUA25ZmeS9ZFyJcxTmVCAC82UOcSbIuSpiLAwBAOQLQUcoRAAAAAAAA' + b'AACADvpUhmIoBgC2Kc3yflzV98tlnBOJAMBO3CTrkoSpKACALlOOAHSScgQAAAAAAAAAAKBj' + b'lnH6ZShKUQCwDWmWXyTrUoRzaQDA3tzHGS/CaCIKAKCLlCMAnaQcAQAAAAAAAAAA6JhPZSiG' + b'YgDgrdIsHyTrUoRTaQDAwTzEGceZLMJICR4A0BnKEYBOUo4AAAAAAAAAAAB0yDJOvwyFB2YA' + b'eJU0y3txXSZKEQCgjvd7VUnCWEkCANAFyhGATlKOAAAAAAAAAAAAdMinMhRDMQDwUptShKvN' + b'nEgEAGpLSQIA0AnKEYBOUo4AAAAAAAAAAAB0RPWATL8MhYdjAHg2pQgA0Oh7QCUJAEBrvRMB' + b'AAAAAAAAAAAAQGuNFSMA8BJplg/imsf5mChGAICmOdl8h8/jd/pwU3gEANAayhEAAAAAAAAA' + b'AAAA2unxjaEA8EtVKUKcefz4Z6IUAQCa7mlJwpU4AIC2OFqtVlIAOqeXXTv8AAAAAAAAAACA' + b'tvt7GYqJGAD4mTTLL+IaxjmXBgC01kP1fb8II/eIAECjKUcAOkk5AgAAAAAAAAAA0HIPZSj6' + b'YgDgR9Isr74nxnF+lwYAdMZdsi5JmIoCAGiiYxEAAAAAAAAAAAAAtM5QBAB8T5rlvbiu4nyU' + b'BgB0znmcf8frgZvqemARRqVIAIAmeScCAAAAAAAAAAAAgFZ5KEMxEQMA30qz/DKuWaIYAQC6' + b'7kOcebw2uBIFANAkxyIAAAAAAAAAAAAAaJWhCAB4Ks3yflyTZP22aACAykmcf8brhEHcV4sw' + b'mooEAKi7dyIAAAAAAAAAAAAAaI27MhQTMQDwKM3yYVz/N1GMAAB83/s4/47XDOM4PXEAAHWm' + b'HAEAAAAAAAAAAACgPYYiAKCSZvlZnFn8+FEaAMAz/CPOLF4/XIoCAKiro9VqJQWgc3rZtcMP' + b'AAAAAAAAAABom7syFBdiAOi2zRufh8n6AUcAgNf4HGewCKNSFABAnbwTAQAAAAAAAAAAAEAr' + b'DEUA0G1plp/FNUsUIwAAb/N7nHm8trgUBQBQJ8oRAAAAAAAAAAAAAJrvcxmKqRgAuivN8mFc' + b'/4lzKg0AYAtO4vwrXmNM4vTEAQDUgXIEAAAAAAAAAAAAgOa7EgFAN6VZfhZnFj9+lAYAsAMf' + b'4szi9caFKACAQ1OOAAAAAAAAAAAAANBsN2Uo5mIA6J40ywdxTeO8lwYAsEOncf4drz2GogAA' + b'DulYBAAAAAAAAAAAAACNNhQBQLekWd6LaxLnd2kAAHv0MV6HXMQ9WITRXBwAwL69EwEAAAAA' + b'AAAAAABAY30qQzEXA0B3pFl+FtcsUYwAABzGeXUtEq9JLkUBAOybcgQAAAAAAAAAAACAZlrG' + b'GYsBoDvSLL+K6z9xTqUBABzQSZx/xWsT96QAwF4diwAAAAAAAAAAAACgkcZlKEoxALRfmuW9' + b'ZF2I80EaAECN/CNep5zFfbkII/enAMDOvRMBAAAAAAAAAAAAQOM8lKEYigGg/dIs78c1TRQj' + b'AAD1dB5nvilJAADYKeUIAAAAAAAAAAAAAM0zFAFA+6VZfhHXLM57aQAANXYS5z/x2mUgCgBg' + b'l5QjAAAAAAAAAAAAADTLfRmKiRgA2i3N8qu4/p2sHzYEAGiCP+M1jPtVAGBnlCMAAAAAAAAA' + b'AAAANMuVCADabfNQ4T8lAQA00Id4LTON0xMFALBtR6vVSgpA5/Sya4cfAAAAAAAAAADQRHdl' + b'KC7EANBOm4cIb+OcSwMAaLj7OJeLMJqLAgDYlnciAAAAAAAAAAAAAGiMKxEAtFOa5f24poli' + b'BACgHd7HmcVrnDNRAADbohwBAAAAAAAAAAAAoBluylDMxADQPpuHBqsz/r00AIAWOYkzjdc6' + b'A1EAANugHAEAAAAAAAAAAACg/pZxhmIAaJ80yy/jmibrhwcBANqmusb5U0ECALANyhEAAAAA' + b'AAAAAAAA6m9chmIuBoB22Twk+K9EMQIA0H5VQcJQDADAWyhHAAAAAAAAAAAAAKi3ZZyxGADa' + b'ZVOM8KckAIAO+RivgSZiAABeSzkCAAAAAAAAAAAAQL1dlaEoxQDQHpu3JitGAAC66IOCBADg' + b'tZQjAAAAAAAAAAAAANTXfRmKiRgA2mPzMOBHSQAAHaYgAQB4FeUIAAAAAAAAAAAAAPV1JQKA' + b'9tg8BPhBEgAAXwoSpnF6ogAAnks5AgAAAAAAAAAAAEA93ZWhmIoBoB0UIwAA/JfzOAoSAIBn' + b'U44AAAAAAAAAAAAAUE8DEQC0g2IEAIAfep8oSAAAnkk5AgAAAAAAAAAAAED9/FGGYi4GgOZT' + b'jAAA8EsKEgCAZ1GOAAAAAAAAAAAAAFAvyzhDMQA0n2IEAIBn+1KQIAYA4GeUIwAAAAAAAAAA' + b'AADUy7AMRSkGgGZTjAAA8GLvN9dQAADfpRwBAAAAAAAAAAAAoD4eylCMxQDQbGmWV2e5YgQA' + b'gJf7oCABAPgR5QgAAAAAAAAAAAAA9TEQAUCzpVleneX/kAQAwKspSAAAvks5AgAAAAAAAAAA' + b'AEA93JWhmIoBoLk2xQh/SgIA4M0UJAAA/0U5AgAAAAAAAAAAAEA9DEQA0FyKEQAAtu7D5hoL' + b'AOAL5QgAAAAAAAAAAAAAh/epDMVcDADNlGb5WVxjSQAAbN2fChIAgEfKEQAAAAAAAAAAAAAO' + b'a5l4oBagsTbFCNM4J9IAANiJPzfXXABAxylHAAAAAAAAAAAAADisqzIUpRgAmifN8l5ct4li' + b'BACAXZsqSAAAlCMAAAAAAAAAAAAAHM59GYqJGACaZ1OMMI1zKg0AgJ2ryqhuN9dgAEBHKUcA' + b'AAAAAAAAAAAAOJwrEQA01jjOezEAAOxNVUo1VZAAAN2lHAEAAAAAAAAAAADgMG7KUEzFANA8' + b'aZYP4/ogCQCAvavKqcZiAIBuUo4AAAAAAAAAAAAAsH/LOFdiAGieNMsHcX2UBADAwXzYlFUB' + b'AB2jHAEAAAAAAAAAAABg/8ZlKEoxADRLmuVniTcVAwDUwcdNaRUA0CHKEQAAAAAAAAAAAAD2' + b'66EMxVAMAM2SZnkvrmmcE2kAANTCeFNeBQB0hHIEAAAAAAAAAAAAgP0aiACgkaaJYgQAgDqp' + b'rs1uNyVWAEAHKEcAAAAAAAAAAAAA2J/PZSimYgBoljTLx3G9lwQAQO2cxrkVAwB0g3IEAAAA' + b'AAAAAAAAgP1YxrkSA0CzpFk+iOsfkgAAqK3zeM02FAMAtJ9yBAAAAAAAAAAAAID9GJehmIsB' + b'oDnSLD+rzm9JAADU3sd47XYpBgBoN+UIAAAAAAAAAAAAALv3UIZiKAaA5kizvBfXJM6JNAAA' + b'GmESr+H6YgCA9lKOAAAAAAAAAMD/Y+9ujtvIETAMMwMKEZAZSBmIB9zFDNQZmBectx0AaukM' + b'tBnI9z5YGdgZSBmAGSxUUtXWzI5n/MM/gM9ThcL9PXQ1D/0RAAA4vEECgOZs67mWAQCgGa+j' + b'Vo8yAEC/jCMAAAAAAAAAAAAAHNbnMuUvMgC0I8Q01OteCQCA5lzXd7mtDADQJ+MIAAAAAAAA' + b'AAAAAIezq2cjA0A7Qkw39fJBHQBAuz7Ud7q1DADQH+MIAAAAAAAAAAAAAIezLVN+lgGgDSGm' + b'q3o91DNXAwCgaQ/v73YAQEeMIwAAAAAAAAAAAAAcxrcy5VEGgKa8PrevZQAAaN7r2NWjDADQ' + b'F+MIAAAAAAAAAAAAAIexkQCgHSGmdb0+KAEA0I3b+o7ntzkAdMQ4AgAAAAAAAAAAAMD+/adM' + b'+YsMAG0IMV3V60EJAIDu/Lu+693IAAB9MI4AAAAAAAAAAAAAsF+7evwzJUBbHuqZywAA0O27' + b'HgDQAeMIAAAAAAAAAAAAAPs1likXGQDaEGIa6nWnBABAt67rO99WBgBon3EEAAAAAAAAAAAA' + b'gP15KlP2wQVAI0JMy3p5bgMA9O9DffdbyQAAbTOOAAAAAAAAAAAAALA/GwkAmvJQz1wGAIDL' + b'ePcLMV3JAADtMo4AAAAAAAAAAAAAsB8fy5S/ygDQhhDT66DNrRIAABdjUc8oAwC0yzgCAAAA' + b'AAAAAAAAwO97qWcrA0AbQkzLmQ/jAAAu0Yf6LriSAQDaZBwBAAAAAAAAAAAA4PcNZcpFBoBm' + b'vA7azGUAALhIDyGmKxkAoD3GEQAAAAAAAAAAAAB+z+cy5S8yALQhxLSu150SAAAXa1HPRgYA' + b'aI9xBAAAAAAAAAAAAIBft6tnkAGgDe//EPygBADAxftXfTe8kQEA2mIcAQAAAAAAAAAAAODX' + b'jWXKRQaAdp7b9cxlAABgZjQLAJpjHAEAAAAAAAAAAADg1zyVKW9lAGhDiGlVrw9KAADw7rq+' + b'I25kAIB2GEcAAAAAAAAAAAAA+DU+oABoi0EbAAD+bAwxLWUAgDYYRwAAAAAAAAAAAAD4eR/L' + b'lL/KANCGENNYr2slAAD4k/nMiBYANMM4AgAAAAAAAAAAAMDPeSlTHmUAaMP7PwFvlAAA4Dvu' + b'6jvjSgYAOH/GEQAAAAAAAAAAAAB+ziABQFPG2ds/AgMAwPc8SAAA5884AgAAAAAAAAAAAMCP' + b'+1Sm/EUGgDa8/wPwvRIAAPyDRX13HGUAgPNmHAEAAAAAAAAAAADgx7zM3v59HIB2bCUAAOAH' + b'bUJMVzIAwPkyjgAAAAAAAAAAAADwYzZlykUGgDaEmIZ6XSsBAMAPms+MawHAWTOOAAAAAAAA' + b'AAAAAPDPPpcpP8oA0Ib3f/z1YRsAAD/rvr5LrmQAgPNkHAEAAAAAAAAAAADg7+3qGWQAaMpm' + b'9vbPvwAA8LNGCQDgPBlHAAAAAAAAAAAAAPh7Q5lykQGgDSGm5extHAEAAH7FbX2nHGQAgPNj' + b'HAEAAAAAAAAAAADg+57KlB9lAGjKWM9cBgAAfvOdEgA4M8YRAAAAAAAAAAAAAP7arp5BBoB2' + b'hJhu6nWvBAAAv2lR3y1HGQDgvBhHAAAAAAAAAAAAAPhrY5nyswwATdlKAADAnmxCTFcyAMD5' + b'MI4AAAAAAAAAAAAA8P+eypR9YAvQkBDTql63SgAAsCfzejYyAMD5MI4AAAAAAAAAAAAA8Ee7' + b'egYZAJozSgAAwJ5tQkxLGQDgPBhHAAAAAAAAAAAAAPijsUz5WQaAdoSYVvW6VQIAgD2bz4xw' + b'AcDZMI4AAAAAAAAAAAAA8D9PZcpbGQCa49kNAMCh3IeYljIAwOkZRwAAAAAAAAAAAAB4s6tn' + b'kAGgLSGm12f3tRIAABzQKAEAnJ5xBAAAAAAAAAAAAIA3Y5nyswwA7T2/JQAA4MDuQ0xLGQDg' + b'tIwjAAAAAAAAAAAAAMxmT2XKWxkA2hJiGuq1UAIAgCMYJQCA0zKOAAAAAAAAAAAAAFy6XT2D' + b'DABNGiUAAOBI7kNMSxkA4HSMIwAAAAAAAAAAAACXbixTfpYBoC0hplW9FkoAAHBEowQAcDrG' + b'EQAAAAAAAAAAAIBL9lSmvJUBoEmjBAAAHNl9iGkpAwCchnEEAAAAAAAAAAAA4FLt6hlkAGhP' + b'iGlVr1slAAA4gVECADgN4wgAAAAAAAAAAADApRrLlJ9lAGjzGS4BAAAnch9iWsoAAMdnHAEA' + b'AAAAAAAAAAC4RE9lylsZANoTYlrV61YJAABOaJQAAI7POAIAAAAAAAAAAABwaXb1DDIANMsz' + b'HACAU1uHmK5kAIDjMo4AAAAAAAAAAAAAXJqhTPlZBoD2hJiW9bpXAgCAE5vXs5EBAI7LOAIA' + b'AAAAAAAAAABwST6XKT/KANCsUQIAAM7EJsR0JQMAHI9xBAAAAAAAAAAAAOBS7OoZZABo0/uH' + b'Z2slAAA4E3PvpwBwXMYRAAAAAAAAAAAAgEsxlCkXGQCatZm9fYAGAADnYpQAAI7HOAIAAAAA' + b'AAAAAABwCT6VKT/KANC0QQIAAM7MIsS0lgEAjsM4AgAAAAAAAAAAANC7l5l/cgRoWohpqNdC' + b'CQAAztBGAgA4DuMIAAAAAAAAAAAAQO+GMuUiA0Dbz3IJAAA4U7chphsZAODwjCMAAAAAAAAA' + b'AAAAPftYpvxFBoB2vX9odqsEAABnbCMBAByecQQAAAAAAAAAAACgV9/KlEcZAJrnQzMAAM7d' + b'fYjpSgYAOCzjCAAAAAAAAAAAAECPdvUMMgC07f0Ds7USAAA0wKgXAByYcQQAAAAAAAAAAACg' + b'R2OZ8lcZAJr3OowwlwEAgAYMEgDAYRlHAAAAAAAAAAAAAHrzVKa8lQGgC6MEAAA0YhFiWssA' + b'AIdjHAEAAAAAAAAAAADoyW729i/jADQuxLSq10IJAAAaMkgAAIdjHAEAAAAAAAAAAADoyVCm' + b'XGQA6OOZLgEAAI25CzEtZQCAwzCOAAAAAAAAAAAAAPTiU5nyowwA7QsxXdVrrQQAAA0aJACA' + b'wzCOAAAAAAAAAAAAAPTgpZ5RBoBuvA4jzGUAAKBBgwQAcBjGEQAAAAAAAAAAAIAerMuUiwwA' + b'3dhIAABAoxYhprUMALB/xhEAAAAAAAAAAACA1n0sU/4qA0AfQkzLel0rAQBAwwYJAGD/jCMA' + b'AAAAAAAAAAAALXsqUx5lAOjKRgIAABp3F2K6kgEA9ss4AgAAAAAAAAAAANCqXT1rGQC649kO' + b'AEAPBgkAYL+MIwAAAAAAAAAAAACtGsqUiwwA/QgxvQ4jLJQAAKADgwQAsF/GEQAAAAAAAAAA' + b'AIAWfSpTfpQBoDtrCQAA6MR1iOlGBgDYH+MIAAAAAAAAAAAAQGu+1TPKANAl4wgAAPRkkAAA' + b'9sc4AgAAAAAAAAAAANCSXT1DmXKRAqAvIaahXnMlAADoiPEvANgj4wgAAAAAAAAAAABAS8Yy' + b'5a8yAHTJh2MAAPRmEWJayQAA+2EcAQAAAAAAAAAAAGjF5zLlrQwA/QkxXdXrTgkAADo0SAAA' + b'+2EcAQAAAAAAAAAAAGjBy8zHBAA9W0sAAIB3XQDg7xhHAAAAAAAAAAAAAFqwLlMuMgD0+5yX' + b'AACATs1DTN53AWAP/isAe3dz28gRoGGYS/AuMYJhBuZG4F6AdzODpTKQsOB5NWehAE0GUgbS' + b'vQ5SBEtmIEWgYgTeoqXx/GkkUvzr7noeoNC2jy/gdo2B/mgcAQAAAAAAAAAAAKi7sxTDTAaA' + b'duqPpsf58ZcSAAC0mHEEANgC4wgAAAAAAAAAAABAnd2nGC5lAGg1H4oBAODOCwC8yzgCAAAA' + b'AAAAAAAAUFeLjo8HAErgXQ8AQNsd9UdT914A2JBxBAAAAAAAAAAAAKCuximGJANAe/VH0+P8' + b'+EsJAAAKYBwBADZkHAEAAAAAAAAAAACoo88phjsZAFrPB2IAALj7AgArMY4AAAAAAAAAAAAA' + b'1M19iuFcBoAiVBIAAFCIo/5oaiABADZgHAEAAAAAAAAAAACok0XHLykClMQ7HwAA918AYCXG' + b'EQAAAAAAAAAAAIA6GacYkgwA7ffyq7lHSgAAUBDjCACwAeMIAAAAAAAAAAAAQF18TjHcyQBQ' + b'jEoCAAAKc9QfTYcyAMDHGEcAAAAAAAAAAAAA6uA+xXAuA0BR/GouAAAlmkgAAB9jHAEAAAAA' + b'AAAAAAA4tEXHB7IARXn5tdxPSgAAUCD/DwQAPsg4AgAAAAAAAAAAAHBoVYohyQBQ1rtfAgAA' + b'CvWpP5oOZACA9RlHAAAAAAAAAAAAAA7pLMUwkwGgOH4tFwAA92EAYC3GEQAAAAAAAAAAAIBD' + b'uU0xXMoAUJb+aHqcH38qAQBAwSoJAGB9xhEAAAAAAAAAAACAQ3jMZyIDQJEqCQAAKNxfEgDA' + b'+owjAAAAAAAAAAAAAPu2yGecYkhSABRpLAEAAKXrj6buxQCwJuMIAAAAAAAAAAAAwL6dphhm' + b'MgAUq5IAAADciwFgXcYRAAAAAAAAAAAAgH26TjFcyQBQpv5oOsiPT0oAAEBnLAEArMc4AgAA' + b'AAAAAAAAALAv8xTDRAaAolUSAADAPz69jIcBACsyjgAAAAAAAAAAAADsw6LjFxEB8N8CAAD4' + b'XiUBAKzOOAIAAAAAAAAAAACwD+MUw4MMAMWrJAAAgH8ZDwOANRhHAAAAAAAAAAAAAHbtc4rh' + b'TgaAsvVH02F+HCkBAAD/qiQAgNUZRwAAAAAAAAAAAAB26TbFcC4DAB0ffgEAwM+OXkbEAIAV' + b'GEcAAAAAAAAAAAAAdmWez0QGAF5UEgAAgHsyAHyUcQQAAAAAAAAAAABgFxb5TFIMSQoAXlQS' + b'AACAezIAfJRxBAAAAAAAAAAAAGAXTlMMMxkAWOqPpsP8OFICAAB+UUkAAKsxjgAAAAAAAAAA' + b'AABs25cUw5UMAHynkgAAAF519DImBgC8wzgCAAAAAAAAAAAAsE33KYZTGQD4SSUBAAC4LwPA' + b'JowjAAAAAAAAAAAAANvymM9YBgBeUUkAAADuywCwCeMIAAAAAAAAAAAAwDYs8hmnGJIUAHyv' + b'P5oO8uNICQAA+K1KAgB4n3EEAAAAAAAAAAAAYBtOUwwzGQB4RSUBAAC86ag/mg5lAIC3GUcA' + b'AAAAAAAAAAAANvUlxXAlAwC/4SMvAAB4XyUBALzNOAIAAAAAAAAAAACwifsUw6kMALyhkgAA' + b'AN5lVAwA3mEcAQAAAAAAAAAAAPiox3zGMgDwjj8kAACAd1USAMDbjCMAAAAAAAAAAAAAH7HI' + b'Z5xiSFIA8Dv90bRSAQAAVvIp35+PZQCA3zOOAAAAAAAAAAAAAHzEJMUwkwGAd1QSAACA+zMA' + b'bINxBAAAAAAAAAAAAGBdn1MMNzIAsIKhBAAA4P4MANtgHAEAAAAAAAAAAABYx3WK4VwGAFbk' + b'4y4AAFhdJQEA/J5xBAAAAAAAAAAAAGBV83xOZQBgFf3R9Dg/PikBAAArMy4GAG8wjgAAAAAA' + b'AAAAAACsYpHPOMWQpABgRT7sAgCA9Rz1R1P3aAD4DeMIAAAAAAAAAAAAwCqqFMODDACs898O' + b'CQAAYG3GEQDgN4wjAAAAAAAAAAAAAO85STHMZABgTT7qAgAA92gA2BrjCAAAAAAAAAAAAMBb' + b'vqQYrmQA4AN81AUAAO7RALA1xhEAAAAAAAAAAACA37lNMZzKAMC6+qPpcX58UgIAANb2pwQA' + b'8DrjCAAAAAAAAAAAAMBr5vlMZADgg/zaLQAAfFB/NHWfBoBXGEcAAAAAAAAAAAAAfrbIZ5xi' + b'SFIA8EGVBAAA8GHGEQDgFcYRAAAAAAAAAAAAgJ9VKYYHGQDYwEACAABwnwaAbTKOAAAAAAAA' + b'AAAAAHzvJMUwkwGADfmlWwAA+LhKAgD4lXEEAAAAAAAAAAAA4KsvKYYrGQDYgj8kAACADzM2' + b'BgCvMI4AAAAAAAAAAAAALF2nGE5lAGBT/dHUh1wAALCZo3yvPpYBAH5kHAEAAAAAAAAAAACY' + b'52MYAYBtGUgAAAAbMzoGAD8xjgAAAAAAAAAAAABlW+RTpRiSFABsiY+4AABgc5UEAPAj4wgA' + b'AAAAAAAAAABQLsMIAOyCcQQAANjcQAIA+JFxBAAAAAAAAAAAACjXJMUwkwGALRtIAAAA7tUA' + b'sG3GEQAAAAAAAAAAAKBMJymGGxkA2IE/JAAAgI39KQEA/Mg4AgAAAAAAAAAAAJTnOsVwJQMA' + b'29YfTYcqAADA1u7XAxUA4BvjCAAAAAAAAAAAAFCW5TDCRAYAdmQgAQAAuF8DwC4YRwAAAAAA' + b'AAAAAIByzPM5lQGAHRpKAAAAW1NJAADfGEcAAAAAAAAAAACAMjzmU6UYkhQA7NBAAgAA2Jpj' + b'CQDgG+MIAAAAAAAAAAAA0H6LfMaGEQDYg4EEAACwNUMJAOAb4wgAAAAAAAAAAADQflWKYSYD' + b'AHvg4y0AAHC/BoCdMI4AAAAAAAAAAAAA7XZiGAGAPTqSAAAA3K8BYBeMIwAAAAAAAAAAAEB7' + b'naUYrmQAYB/6o2mlAgAAuGcDwK4YRwAAAAAAAAAAAIB2uk4xXMoAwB4dSwAAAO7ZALArxhEA' + b'AAAAAAAAAACgfW5TDBMZANizoQQAAOCeDQC7YhwBAAAAAAAAAAAA2mWez0QGAA5gIAEAALhn' + b'A8CuGEcAAAAAAAAAAACA9lgOI1QphiQFAAcwkAAAANyzAWBXjCMAAAAAAAAAAABAOyzymRhG' + b'AOCABhIAAIB7NgDsinEEAAAAAAAAAAAAaL7lMEKVYphJAcABfZIAAADcswFgV4wjAAAAAAAA' + b'AAAAQPNNDCMAcEj90XSgAgAAuG8DwC4ZRwAAAAAAAAAAAIBmO0kx3MgAwIENJAAAAPdtANgl' + b'4wgAAAAAAAAAAADQXGcphisZAKiBgQQAALAzQwkAoNPpSQAAAAAAAAAAAACNdJ1iuJQBgJoY' + b'SAA7s8hnlk96eS49vByAfTnufPs4e/Dd+SQN7O3fQQAonnEEAAAAAAAAAAAAaJ7lMMJEBgBq' + b'xMdasB2P+dx1nkcQ/jlP8SLJAtTEzWv/sD+aVp3noYTleMLyr/+QCrZuIAEAGEcAAAAAAAAA' + b'AACAprk3jABADQ0lgA9ZdJ4/Nr5bPg0hAE2U31133/99fzRdjiZVL2eczyeVYGMDCQDAOAIA' + b'AAAAAAAAAAA0ybzz/GEJAADN9XUQYTmGcCMH0DYvQy83L+e0P5ouR5QmHUMJsImBBABgHAEA' + b'AAAAAAAAAACaYjmMUKUY/JowAHX0pwTwrsd8zjvPowjudEAx8jtvlh+nneehhPHLX7s7wHoM' + b'iwBAxzgCAAAAAAAAAAAANMHy14XHhhEAABrpPp/Lp3hxIwVQupd34U1/NB12nkcS/lsVAABW' + b'9R9///23CkBxjkf/4+UHAAAAAAAAAEBTLIcRqhTDTAoA6ujl48b/UwJ+8ZjPqVEEgHfvEZf5' + b'/KkGvOu/8r3iTgYAStaTAAAAAAAAAAAAAGrLMAIATXAsAfzi81O8OJcB4G35Xbn8827VH03H' + b'+XmVz5EqAAD8TlcCAAAAAAAAAAAAqK2JYQQAGsA4Anwzz+c/DSMArCe/N2/yY5DPrRrwW0MJ' + b'AChdTwIAAAAAAAAAAACopZMUw40MADSAj7Tg2ZeneHEqA8DH5Hdoyo9xfzSd5OdlPkeqwA+M' + b'kgFQvK4EAAAAAAAAAAAAUDvLYYQrGQAAGmGxvL8ZRgDYjvw+Xf55uMrnUQ34gXEEAIpnHAEA' + b'AAAAAAAAAADq5cwwAgANM5CAgi2HEaqXD3kB2JL8Xp3lxzCfuRrwr6EEAJTOOAIAAAAAAAAA' + b'AADUx3WK4VIGABpmIAGF+jqMMJMCYPvy+zUt37MdAwkAALwwjgAAAAAAAAAAAAD1sBxGmMgA' + b'ANAIhhEA9sBAAvxgIAEApTOOAAAAAAAAAAAAAIdnGAGAJhtKQGEMIwDskYEE+NcnCQAonXEE' + b'AAAAAAAAAAAAOKxbwwgANNyRBBRmYhgBYL9eBhLGneeBGgAACmUcAQAAAAAAAAAAAA5n+auX' + b'ExkAABrj7Cle3MgAsH/5/fuQH5USlKw/mg5VAKBkxhEAAAAAAAAAAADgMJbDCFWKIUkBQFP5' + b'OIvC3D7Fi0sZAA4nv4dn+XGmBAU7lgCAkhlHAAAAAAAAAAAAgP0zjABAW/g4i1Is8pnIAHB4' + b'L0M190oAAJTHOAIAAAAAAAAAAADsl2EEAIDmmTzFC/c3gBq9lzvPwzVQmoEEAJTMOAIAAAAA' + b'AAAAAADsz2PHMAIA7TKQgALcPsWLGxkA6iO/lx/y41wJ3L8BoCzGEQAAAAAAAAAAAGA/lr9o' + b'OTaMAEDLDCSgAKcSANTPU7y47DyPEAIAUAjjCAAAAAAAAAAAALB7y2GEKsUwkwIAoFE+v/w6' + b'OQD1NJGAwhxLAEDJjCMAAAAAAAAAAADAbhlGAABo7j3uUgaA+nqKF3f5ca8EBRlKAEDJjCMA' + b'AAAAAAAAAADA7hhGAKDtfJxFm10+xYskA0DtnUsAAFAG4wgAAAAAAAAAAACwG4YRACjBsQS0' + b'+C53KQNA/T3Fi7v8uFcCAKD9jCMAAAAAAAAAAADA9hlGAABotpuneJFkAGiMKwkoxEACAEpm' + b'HAEAAAAAAAAAAAC2yzACAEDznUsA0BxP8eIqPx6VoACfJACgZMYRAAAAAAAAAAAAYLvGhhEA' + b'KMhQAlpo/hQvHmQAaJwbCQAA2s04AgAAAAAAAAAAAGzPSYrhTgYACnIkAS10KQGA9zcAAPVj' + b'HAEAAAAAAAAAAAC2YzmMcCUDAEDj+eVxgAZ6ihcP+TFXAgCgvYwjAAAAAAAAAAAAwOYMIwAA' + b'tMPtU7xIMgA0lj+b03r90bRSAYBSGUcAAAAAAAAAAACAzRhGAABojxsJALzHAQCoJ+MIAAAA' + b'AAAAAAAA8HGGEQAoll+spaXuJABorqd48ZAfj0oAALSTcQQAAAAAAAAAAAD4GMMIAADtMn/5' + b'qBaAZruRAACgnYwjAAAAAAAAAAAAwPoMIwAAtM+dBADe5wAA1JdxBAAAAAAAAAAAAFiPYQQA' + b'gHa6kwDA+xwaoJIAgFIZRwAAAAAAAAAAAIDVGUYAAGivmQQAzfcUL1J+PCoBANA+xhEAAAAA' + b'AAAAAABgNYYRAADa6/EpXjzIANAadxIAALSPcQQAAAAAAAAAAAB4n2EEAPhVJQEtMpMAwHsd' + b'AIB6M44AAAAAAAAAAAAAbzOMAADQfj6iBfBeBwCg5owjAAAAAAAAAAAAwO8ZRgAAKIOPaAG8' + b'1wEAqDnjCAAAAAAAAAAAAPA6wwgAAOV4kACgPZ7iRcqPhRIAAO1iHAEAAAAAAAAAAAB+ZRgB' + b'AKAgT/HCL4wDtI93O21VSQBAqXoSAAAAAAAAAAAAwL+Wvyo5TjHcSQEAUNQdEID2SRIAALRL' + b'VwIAAAAAAAAAAAD4x/KjuMowAgBAcfyyOID3OwAADWAcAQAAAAAAAAAAAL4NI/hwAgCgPH5Z' + b'HMD7HQCABjCOAAAAAAAAAAAAQOkMIwAAlM09EMD7HQCABjCOAAAAAAAAAAAAQMkMIwDAx1US' + b'AAAAALAvxhEAAAAAAAAAAAAolWEEAACWHiQA8H4HAKD+jCMAAAAAAAAAAABQIsMIAAB89SAB' + b'QPs8xQvvdwCAljGOAAAAAAAAAAAAQGnmHcMIAAAAAAAAjdKTAAAAAAAAAAAAgIJ8HUZIUgAA' + b'AAAAADRHVwIAAAAAAAAAAAAKYRgBAIDXuB8CtNdCAgCA9jCOAAAAAAAAAAAAQAkMIwAA8Kqn' + b'eDFTAaC1vOMBAFrEOAIAAAAAAAAAAABtZxgBAAAAAACg4YwjAAAAAAAAAAAA0GbXHcMIAAAA' + b'AAAAjdeTAAAAAAAAAAAAgJa6TjFMZAAAAAAAAGi+rgQAAAAAAAAAAAC0kGEEAAAAAACAFjGO' + b'AAAAAAAAAAAAQNsYRgAAAAAAAGgZ4wgAAAAAAAAAAAC0yZlhBAAAAAAAgPbpSQAAAAAAAAAA' + b'AEBLnKQYrmQAAAAAAABon64EAAAAAAAAAAAAtIBhBAAAAAAAgBbrSQAAAAAAAAAAAECDLfKZ' + b'pBhupAAAAAAAAGgv4wgAAAAAAAAAAAA01XIYoUoxzKQAAAAAAABot64EAAAAAAAAAAAANJBh' + b'BAAAAAAAgIIYRwAAAAAAAAAAAKBp5vkMDSMAAAAAAACUoycBAAAAAAAAAAAADbIcRqhSDEkK' + b'AAAAAACAcnQlAAAAAAAAAAAAoCEMIwAAAAAAABSqJwEAAAAAAAAAAAANcJ1imMgAAAAAAABQ' + b'pq4EAAAAAAAAAAAA1JxhBAAAAAAAgMIZRwAAAAAAAAAAAKDOzgwjAAAAAAAA0JMAAAAAAAAA' + b'AACAmjpJMVzJAAAAAAAAgHEEAAAAAAAAAAAA6maRzyTFcCMFAAAAAAAAS8YRAAAAAAAAAAAA' + b'qJPlMEKVYphJAQAAAAAAwFddCQAAAAAAAAAAAKiJx45hBAAAAAAAAF7RkwAAAAAAAAAAAIAa' + b'mHeehxGSFAAAAAAAAPysKwEAAAAAAAAAAAAHdt8xjAAAAAAAAMAbehIAAAAAAAAAAABwQNcp' + b'hokMAAAAAAAAvKUrAQAAAAAAAAAAAAfy2TACAAAAAAAAq+hJAAAAAAAAAAAAwAGcpBiuZAAA' + b'AAAAAGAVxhEAAAAAAAAAAADYp0U+kxTDjRQAAAAAAACsyjgCAAAAAAAAAAAA+7IcRqhSDDMp' + b'AAAAAAAAWEdXAgAAAAAAAAAAAPZgns/QMAIAAAAAAAAfYRwBAAAAAAAAAACAXbvPp0oxPEgB' + b'AAAAAADAR/QkAAAAAAAAAAAAYIeuUwwTGQAAAAAAANhEVwIAAAAAAAAAAAB25LNhBAAAAAAA' + b'ALahJwEAAAAAAAAAAAA7cJJiuJIBAAAAAACAbTCOAAAAAAAAAAAAwDYt8qlSDDMpAAAAAAAA' + b'2JauBAAAAAAAAAAAAGzJvGMYAQAAAAAAgB3oSQAAAAAAAAAAAMAWfB1GSFIAAAAAAACwbcYR' + b'AAAAAAAAAAAA2NR1imEiAwAAAAAAALvSlQAAAAAAAAAAAIANnBlGAAAAAAAAYNd6EgAAAAAA' + b'AAAAAPABi3xOUwxXUgAAAAAAALBrxhEAAAAAAAAAAABY13IYoUoxzKQAAAAAAABgH7oSAAAA' + b'AAAAAAAAsIZ5PgPDCAAAAAAAAOyTcQQAAAAAAAAAAABWdZtPlWJIUgAAAAAAALBPPQkAAAAA' + b'AAAAAABYwZcUw6kMAAAAAAAAHIJxBAAAAAAAAAAAAN5zkmK4kgEAAAAAAIBDMY4AAAAAAAAA' + b'AADA7yzyqVIMMykAAAAAAAA4pK4EAAAAAAAAAAAAvGKez9AwAgAAAAAAAHVgHAEAAAAAAAAA' + b'AICfXedTpRgepAAAAAAAAKAOehIAAAAAAAAAAADwnc8phnMZAAAAAAAAqBPjCAAAAAAAAAAA' + b'ACwt8jlNMVxJAQAAAAAAQN0YRwAAAAAAAAAAAOAxn3GKYSYFAAAAAAAAdWQcAQAAAAAAAAAA' + b'oGzzfKoUQ5ICAAAAAACAujKOAAAAAAAAAAAAUK7rFMNEBgAAAAAAAOquKwEAAAAAAAAAAECR' + b'TgwjAAAAAAAA0BQ9CQAAAAAAAAAAAIqyyGecYriTAgAAAAAAgKboSgAAAAAAAAAAAFCMeT5D' + b'wwgAAAAAAAA0jXEEAAAAAAAAAACAMlznU6UYHqQAAAAAAACgaXoSAAAAAAAAAAAAtN5ZiuFS' + b'BgAAAAAAAJrKOAIAAAAAAAAAAEB7LfIZpxjupAAAAAAAAKDJuhIAAAAAAAAAAAC00jyfoWEE' + b'AAAAAAAA2qAnAQAAAAAAAAAAQOtc53OaYkhSAADA2/qj6bkKAK01kAAAoD2MIwAAAAAAAAAA' + b'ALTLWYrhUgYAAFjZ/0oAAAAA9WccAQAAAAAAAAAAoB0W+YxTDHdSAAAAAAAA0DZdCQAAAAAA' + b'AAAAABpvns/QMAIAAAAAAABt1ZMAAAAAAAAAAACg0a5TDBMZAAAAAAAAaLOuBAAAAAAAAAAA' + b'AI11YhgBAAAAAACAEvQkAAAAAAAAAAAAaJzHfMYphpkUAAAAAAAAlKArAQAAAAAAAAAAQKPc' + b'5zM0jAAAAAAAAEBJehIAAAAAAAAAAAA0xucUw7kMAAAAAAAAlMY4AgAAAAAAAAAAQP0t8pmk' + b'GG6kAAAAAAAAoETGEQAAAAAAAAAAAOptns84xfAgBQAAAAAAAKUyjgAAAAAAAAAAAFBf1ymG' + b'iQwAAAAAAACUrisBAAAAAAAAAABA7SzyOTGMAAAAAAAAAM96EgAAAAAAAAAAANTKPJ9JimEm' + b'BQAAAAAAADzrSgAAAAAAAAAAAFAb1/lUhhEAAAAAAADgRz0JAAAAAAAAAAAAauEsxXApAwAA' + b'AAAAAPzKOAIAAAAAAAAAAMBhPeYzTjHMpAAAAAAAAIDXdSUAAAAAAAAAAAA4mNt8hoYRAAAA' + b'AAAA4G09CQAAAAAAAAAAAA7iLMVwKQMAAAAAAAC8zzgCAAAAAAAAAADAfj3mM04xzKQAAAAA' + b'AACA1XQlAAAAAAAAAAAA2JvbfIaGEQAAAAAAAGA9PQkAAAAAAAAAAAD24izFcCkDAAAAAAAA' + b'rM84AgAAAAAAAAAAwG495jNOMcykAAAAAAAAgI/pSgAAAAAAAAAAALAzt/kMDSMAAAAAAADA' + b'ZnoSAAAAAAAAAAAAbN0in/MUw6UUAAAAAAAAsDnjCAAAAAAAAAAAANs1z2eSYphJAQAAAAAA' + b'ANvRlQAAAAAAAAAAAGBrrvOpDCMAAAAAAADAdvUkAAAAAAAAAAAA2Ngin0mK4UYKAAAAAAAA' + b'2D7jCAAAAAAAAAAAAJuZ5zNOMTxIAQAAAAAAALthHAEAAAAAAAAAAODjPqcYzmUAAAAAAACA' + b'3TKOAAAAAAAAAAAAsL5FPuMUw50UAAAAAAAAsHtdCQAAAAAAAAAAANZym8/AMAIAAAAAAADs' + b'T08CAAAAAAAAAACAlSzyOU8xXEoBAAAAAAAA+2UcAQAAAAAAAAAA4H3zfCYphpkUAAAAAAAA' + b'sH/GEQAAAAAAAAAAAN72JcVwKgMAAAAAAAAcjnEEAAAAAAAAAACA1y3yGacY7qQAAAAAAACA' + b'w+pKAAAAAAAAAAAA8IvbfAaGEQAAAAAAAKAeehIAAAAAAAAAAAD8a5HPeYrhUgoAAAAAAACo' + b'D+MIAAAAAAAAAAAAz+b5TFIMMykAAAAAAACgXowjAAAAAPw/O3d228aShmG4RlAAk8E4BAcx' + b'gSgEAZMAHUABVAZUBtR9XTQzaGbQnUF1BJ7q09SR5CNbG5dengf4UdQCX3wGfGPgBQAAAAAI' + b'4UdOcWUGAAAAAAAAGCdxBAAAAAAAAAAAYMnacjc5xcoUAAAAAAAAMF5XJgAAAAAAAAAAABbq' + b'vtx3YQQAAAAAAAAYv2sTAAAAAAAAAAAAC9OVu8kpbk0BAAAAAAAA03BlAgAAAAAAAAAAYEEe' + b'yn0TRgAAAAAAAIBpuTYBAAAAAAAAAACwAF25VU5xbQoAAAAAAACYHnEEAAAAAAAAAABg7nbl' + b'bnKKjSkAAAAAAABgmsQRAAAAAAAAAACAOfuRU1yZAQAAAAAAAKZNHAEAAAAAAAAAAJijfbmb' + b'nGJtCgAAAAAAAJg+cQQAAAAAAAAAAGBufuQUV2YAAAAAAACA+RBHAAAAAAAAAAAA5mJf7ian' + b'WJsCAAAAAAAA5kUcAQAAAAAAAAAAmIO7nOKtGQAAAAAAAGCexBEAAAAAAAAAAIApa8vd5BQr' + b'UwAAAAAAAMB8XZkAAAAAAAAAAACYqLty34URAAAAAAAAYP6uTQAAAAAAAAAAAExMW+5GFAEA' + b'AAAAAACW48oEAAAAAAAAAADAhNyV+y6MAAAAAAAAAMtybQIAAAAAAAAAAGAC2nI3oggAAAAA' + b'AACwTFcmAAAAAAAAAAAARu6u3HdhBAAAAAAAAFiuaxMAAAAAAAAAAAAj1Za7EUUAAAAAAAAA' + b'rkwAAAAAAAAAAACM0F2578IIAAAAAAAAQO/aBAAAAAAAAAAAwIjsy93kFGtTAAAAAAAAAI/E' + b'EQAAAAAAAAAAgLH4kVNcmQEAAAAAAAD4lTgCAAAAAAAAAABwaftyNznF2hQAAAAAAADAa8QR' + b'AAAAAAAAAACAS+nKrXKKa1MAAAAAAAAAfyKOAAAAAAAAAAAAXMKu3E1OsTEFAAAAAAAA8BZx' + b'BAAAAAAAAAAA4Jy6cquc4toUAAAAAAAAwHuJIwAAAAAAAAAAAOfyUO4mp5hNAQAAAAAAAHyE' + b'OAIAAAAAAAAAAHBqbbnbnOLWFAAAAAAAAMBnXJkAAAAAAAAAAAA4obty34URAAAAAAAAgK+4' + b'NgEAAAAAAAAAAHAC+3K3OcXKFAAAAAAAAMBXiSMAAAAAAAAAAADH9iOnuDIDAAAAAAAAcCzi' + b'CAAAAAAAAAAAwLHsyt3kFBtTAAAAAAAAAMckjgAAAAAAAAAAAHxVV+42p7gxBQAAAAAAAHAK' + b'VyYAAAAAAAAAAAC+4L7cN2EEAAAAAAAA4JSuTQAAAAAAAAAAAHxCW+4mp1iZAgAAAAAAADg1' + b'cQQAAAAAAAAAAOCjfuQUV2YAAAAAAAAAzkUcAQAAAAAAAAAAeK9duZucYmMKAAAAAAAA4JzE' + b'EQAAAAAAAAAAgLd0YYgibE0BAAAAAAAAXMKVCQAAAAAAAAAAgD+4K/dNGAEAAAAAAAC4pGsT' + b'AAAAAAAAAAAAr9iVu80p1qYAAAAAAAAALk0cAQAAAAAAAAAAeK4LQxRhYwoAAAAAAABgLK5M' + b'AAAAAAAAAAAAHNyX+yaMAAAAAAAAAIzNtQkAAAAAAAAAAGDx9uVuc4qVKQAAAAAAAIAxEkcA' + b'AAAAAAAAAIDl6sqtcoprUwAAAAAAAABjJo4AAAAAAAAAAADLdF/uNqeYTQEAAAAAAACMnTgC' + b'AAAAAAAAAAAsyz4MUYTKFAAAAAAAAMBUiCMAAAAAAAAAAMAydGGIImxMAQAAAAAAAEzNlQkA' + b'AAAAAAAAAGD27sp9E0YAAAAAAAAApuraBAAAAAAAAAAAMFu7crc5xdoUAAAAAAAAwJSJIwAA' + b'AAAAAAAAwPy0YYgibE0BAAAAAAAAzMGVCQAAAAAAAAAAYFZ+lPsujAAAAAAAAADMybUJAAAA' + b'AAAAAABgFh7K3eYUG1MAAAAAAAAAcyOOAAAAAAAAAAAA07YPQxShMgUAAAAAAAAwV+IIAAAA' + b'AAAAAAAwTV25VU5xbQoAAAAAAABg7q5MAAAAAAAAAAAAk3NX7pswAgAAAAAAALAU1yYAAAAA' + b'AAAAAIDJ2JW7ySk2pgAAAAAAAACWRBwBAAAAAAAAAADGrw1DFKEyBQAAAAAAALBE4ggAAAAA' + b'AAAAADBeXblVTnFtCgAAAAAAAGDJxBEAAAAAAAAAAGCcfpRb5xSzKQAAAAAAAIClE0cAAAAA' + b'AAAAAIBxeSh3m1NsTAEAAAAAAAAwEEcAAAAAAAAAAIBx2JVb5RQrUwAAAAAAAAC8JI4AAAAA' + b'AAAAAACX1YYhirAxBQAAAAAAAMDrxBEAAAAAAAAAAOAyunLrnOLKFAAAAAAAAAB/Jo4AAAAA' + b'AAAAAADnd1dulVPMpgAAAAAAAAB4mzgCAAAAAAAAAACcz0O525xiYwoAAAAAAACA9xNHAAAA' + b'AAAAAACA09uVW+UUK1MAAAAAAAAAfJw4AgAAAAAAAAAAnE5b7januDUFAAAAAAAAwOeJIwAA' + b'AAAAAAAAwPF1YYgibEwBAAAAAAAA8HXiCAAAAAAAAAAAcDx9FGHdX04xmwMAAAAAAADgOMQR' + b'AAAAAAAAAADgOO7KrUQRAAAAAAAAAI5PHAEAAAAAAAAAAL7mPgxRhMYUAAAAAAAAAKchjgAA' + b'AAAAAAAAAJ+zK3cjigAAAAAAAABweuIIAAAAAAAAAADwMX0UYZVTrEwBAAAAAAAAcB7iCAAA' + b'AAAAAAAA8D5tuRtRBAAAAAAAAIDzE0cAAAAAAAAAAIA/66MIq5zixhQAAAAAAAAAlyGOAAAA' + b'AAAAAAAArxNFAAAAAAAAABgJcQQAAAAAAAAAAHipK7fOKa5MAQAAAAAAADAO4ggAAAAAAAAA' + b'ADD4K4oQhjBCNgcAAAAAAADAeIgjAAAAAAAAAACwdKIIAAAAAAAAACMnjgAAAAAAAAAAwFKJ' + b'IgAAAAAAAABMhDgCAAAAAAAAAABLdF9ulVNsTAEAAAAAAAAwfuIIAAAAAAAAAAAsiSgCAAAA' + b'AAAAwASJIwAAAAAAAAAAsASiCAAAAAAAAAATJo4AAAAAAAAAAMCciSIAAAAAAAAAzIA4AgAA' + b'AAAAAAAAcySKAAAAAAAAADAj4ggAAAAAAAAAAMyJKAIAAAAAAADADIkjAAAAAAAAAAAwB6II' + b'AAAAAAAAADMmjgAAAAAAAAAAwJSJIgAAAAAAAAAsgDgCAAAAAAAAAABTJIoAAAAAAAAAsCDi' + b'CAAAAAAAAAAATIkoAgAAAAAAAMACiSMAAAAAAAAAADB2Xbl1uY0oAgAAAAAAAMAyiSMAAAAA' + b'AAAAADBWj1GEdU4xmwMAAAAAAABgucQRAAAAAAAAAAAYG1EEAAAAAAAAAF4QRwAAAAAAAAAA' + b'YCxEEQAAAAAAAAB4lTgCAAAAAAAAAACX1pZb5RQ3pgAAAAAAAADgNeIIAAAAAAAAAABciigC' + b'AAAAAAAAAO8ijgAAAAAAAAAAwLntyq1ziltTAAAAAAAAAPAe4ggAAAAAAAAAAJxLH0VY5RQr' + b'UwAAAAAAAADwEeIIAAAAAAAAAACc2n25dU6xNgUAAAAAAAAAnyGOAAAAAAAAAADAqfRRhFVO' + b'sTEFAAAAAAAAAF8hjgAAAAAAAAAAwDF15db95RSzOQAAAAAAAAA4BnEEAAAAAAAAAACOoQ1D' + b'FGEjigAAAAAAAADAsYkjAAAAAAAAAADwFX0UYZVT3JgCAAAAAAAAgFMRRwAAAAAAAAAA4DN2' + b'YYgiVKYAAAAAAAAA4NTEEQAAAAAAAAAA+Ij7cuucYm0KAAAAAAAAAM5FHAEAAAAAAAAAgLd0' + b'5TZhiCI05gAAAAAAAADg3MQRAAAAAAAAAAD4nbbcutwmp5jNAQAAAAAAAMCliCMAAAAAAAAA' + b'APCrXRiCCBtTAAAAAAAAADAG4ggAAAAAAAAAADy6D0MUoTIFAAAAAAAAAGMijgAAAAAAAAAA' + b'sGxduU25dU6xMQcAAAAAAAAAYySOAAAAAAAAAACwTG25dblNTjGbAwAAAAAAAIAxE0cAAAAA' + b'AAAAAFiWXbl1TnFrCgAAAAAAAACmQhwBAAAAAAAAAGAZ7sutcoqNKQAAAAAAAACYGnEEAAAA' + b'AAAAAID5asttyq1zitkcAAAAAAAAAEyVOAIAAAAAAAAAwPzsym1yihtTAAAAAAAAADAH4ggA' + b'AAAAAAAAAPNxX26dU6xNAQAAAAAAAMCciCMAAAAAAAAAAExbW25dbpNTzOYAAAAAAAAAYI7E' + b'EQAAAAAAAAAApmlXbp1T3JoCAAAAAAAAgLkTRwAAAAAAAAAAmI6u3CYMUYTGHAAAAAAAAAAs' + b'hTgCAAAAAAAAAMD47cuty21zitkcAAAAAAAAACyNOAIAAAAAAAAAwHjdl9vkFCtTAAAAAAAA' + b'ALBk4ggAAAAAAAAAAOPSlluHIYqQzQEAAAAAAAAA4ggAAAAAAAAAAGPxEIYgwtYUAAAAAAAA' + b'APCSOAIAAAAAAAAAwOW05TZhiCI05gAAAAAAAACA14kjAAAAAAAAAACc30MYgghbUwAAAAAA' + b'AADA28QRAAAAAAAAAADOoy23CUMUoTEHAAAAAAAAALyfOAIAAAAAAAAAwGk9hCGIsDUFAAAA' + b'AAAAAHyOOAIAAAAAAAAAwPG15TZhiCI05gAAAAAAAACArxFHAAAAAAAAAAA4nocwBBG2pgAA' + b'AAAAAACA4xFHAAAAAAAAAAD4mrbcOgxRhGwOAAAAAAAAADg+cQQAAAAAAAAAgI/rym3DEESo' + b'zAEAAAAAAAAApyWOAAAAAAAAAADwfvty63LbnGI2BwAAAAAAAACchzgCAAAAAAAAAMCfdeU2' + b'5dY5xcYcAAAAAAAAAHB+4ggAAAAAAAAAAK97KLfJKW5NAQAAAAAAAACXJY4AAAAAAAAAAPBk' + b'X24ThihCNgcAAAAAAAAAjIM4AgAAAAAAAACwdF14CiLU5gAAAAAAAACA8RFHAAAAAAAAAACW' + b'6iEMQYStKQAAAAAAAABg3MQRAAAAAAAAAIAl2ZfbhCGKkM0BAAAAAAAAANMgjgAAAAAAAAAA' + b'zF1bbltunVNszAEAAAAAAAAA0yOOAAAAAAAAAADMUReGIMI2p7g1BwAAAAAAAABMmzgCAAAA' + b'AAAAADAnD+EpipDNAQAAAAAAAADzII4AAAAAAAAAAEzdvtymP0EEAAAAAAAAAJgncQQAAAAA' + b'AAAAYIractty65xiYw4AAAAAAAAAmDdxBAAAAAAAAABgKrpym/5yirU5AAAAAAAAAGA5xBEA' + b'AAAAAAAAgDHrgwjb/nKKW3MAAAAAAAAAwDKJIwAAAAAAAAAAY/RQbiOIAAAAAAAAAAD0xBEA' + b'AAAAAAAAgLHogwh9DGGbU8zmAAAAAAAAAAAeiSMAAAAAAAAAAJckiAAAAAAAAAAAvEkcAQAA' + b'AAAAAAA4t325TRiCCI05AAAAAAAAAIC3iCMAAAAAAAAAAOcgiAAAAAAAAAAAfJo4AgAAAAAA' + b'AABwKoIIAAAAAAAAAMBRiCMAAAAAAAAAAMckiAAAAAAAAAAAHJ04AgAAAAAAAADwVYIIAAAA' + b'AAAAAMBJiSMAAAAAAAAAAJ+xK7cNgggAAAAAAAAAwBmIIwAAAAAAAAAA7/UQnoII2RwAAAAA' + b'AAAAwLmIIwAAAAAAAAAAfyKIAAAAAAAAAABcnDgCAAAAAAAAAPBcFw4xhHKVIAIAAAAAAAAA' + b'MAbiCAAAAAAAAABAW64qt80pbs0BAAAAAAAAAIyNOAIAAAAAAAAALFMfROhDCJucYm0OAAAA' + b'AAAAAGDMxBEAAAAAAAAAYDn25TbltjnFxhwAAAAAAAAAwFSIIwAAAAAAAADAvD2U25arBBEA' + b'AAAAAAAAgKkSRwAAAAAAAACAeenCEEPY5hS35gAAAAAAAAAA5kAcAQAAAAAAAACmb1+uKrfJ' + b'KdbmAAAAAAAAmK1vJgBgqcQRAAAAAAAAAGCaHsIQRNjmFBtzAAAAAAAALMJ/TADAUokjAAAA' + b'AAAAAMA0tOEQQ+jfnGI2CQAAAAAAAACwFOIIAAAAAAAAADBe+zDEELY5xdocAAAAAAAAAMBS' + b'iSMAAAAAAAAAwHh0YYghVGEIImSTAAAAAAAAAACIIwAAAAAAAADApe3DUwyhMgcAAAAAAAAA' + b'wD+JIwAAAAAAAADAeXXhEEPo35xiYxIAAAAAAAAAgD8TRwAAAAAAAACA09uHIYawzSnW5gAA' + b'AAAAAAAA+BhxBAAAAAAAAAA4vrZcdbg+iJBNAgAAAAAAAADweeIIAAAAAAAAAHAcu3LbclVO' + b'sTYHAAAAAAAAAMDxiCMAAAAAAAAAwOfsy1VhiCFszQEAAAAAAAAAcDriCAAAAAAAAADwPm04' + b'xBDCEERoTAIAAAAAAAAAcB7iCAAAAAAAAADwui68jCHUJgEAAAAAAAAAuAxxBAAAAAAAAAB4' + b'sgtPMYTKHAAAAAAAAAAA4yCOAAAAAAAAAMCS7cNTDGFrDgAAAAAAAACAcRJHAAAAAAAAAGBJ' + b'/o4hhCGIkE0CAAAAAAAAADB+4ggAAAAAAAAAzFkbXsYQGpMAAAAAAAAAAEyPOAIAAAAAAAAA' + b'cyKGAAAAAAAAAAAwQ+IIAAAAAAAAAEyZGAIAAAAAAAAAwAKIIwAAAAAAAAAwJftydRBDAAAA' + b'AAAAAABYFHEEAAAAAAAAAMasjyFU4SmGkE0CAAAAAAAAALA84ggAAAAAAAAAjMkuDCGEOogh' + b'AAAAwNjdlvu3GQAA4Kz8/xkAi/Wvnz9/WgFYnH//93/+8QMAAAAAALi8LrwMIVQmAQAAAAAA' + b'AADgNdcmAAAAAAAAAOBM2vAyhlCbBAAAAAAAAACA9xBHAAAAAAAAAOBU9mGIIfRX5xQbkwAA' + b'AAAAAAAA8BniCAAAAAAAAAAcQxcOEYT+zSlWJgEAAAAAAAAA4FjEEQAAAAAAAAD4jH14GUNo' + b'TAIAAAAAAAAAwKmIIwAAAAAAAADwli68DCFUJgEAAAAAAAAA4JzEEQAAAAAAAAD41S4MIYTH' + b'GEJjEgAAAAAAAAAALkkcAQAAAAAAAGDZ9uEphFDnFCuTAAAAAAAAAAAwNuIIAAAAAAAAAMvR' + b'hqcQQhWGGEI2CwAAAAAAAAAAYyeOAAAAAAAAADBPXXgWQQhDCKExCwAAAAAAAAAAUySOAAAA' + b'AAAAADB9QggAAAAAAAAAAMyaOAIAAAAAAADAtAghAAAAAAAAAACwOOIIAAAAAAAAAOMlhAAA' + b'AAAAAAAAAEEcAQAAAAAAAGAs2nAIIIQhhtAIIQAAAAAAAAAAwEAcAQAAAAAAAOD89uEphPDX' + b'5RSzWQAAAAAAAAAA4HXiCAAAAAAAAACn04V/RhBqswAAAAAAAAAAwMeIIwAAAAAAAAAcx75c' + b'E4YIQtV/zik2ZgEAAAAAAAAAgK8TRwAAAAAAAAD4mDYMEYTq8NY5xdosAAAAAAAAAABwOuII' + b'AAAAAAAAAK/rytWHax4/5xSzaQAAAAAAAAAA4LzEEQAAAAAAAABC2IUhgNBf1b85xcYsAAAA' + b'AAAAAAAwDuIIAAAAAAAAwJKIIAAAAAAAAAAAwASJIwAAAAAAAABz05WrD9c8viIIAAAAAAAA' + b'AAAwXeIIAAAAAAAAwFS1YYgfVIe3vzqnmE0DAAAAAAAAAADzIo4AAAAAAAAAjN0uPIsf9G9O' + b'sTYLAAAAAAAAAAAshzgCAAAAAAAAMAb7crlcFZ6FEHKK2TQAAAAAAAAAAIA4AgAAAAAAAHAu' + b'zwMI/VuXa3KKjWkAAAAAAAAAAIA/EUcAAAAAAAAAjkkAAQAAAAAAAAAAODpxBAAAAAAAAOCj' + b'duFZ+ODxBBAAAAAAAAAAAIBTEUcAAAAAAAAAfrUPT/GDv9+cYmUaAAAAAAAAAADgEsQRAAAA' + b'AAAAYHnacs1rl1NszAMAAAAAAAAAAIyNOAIAAAAAAADMz75cLlcf3iaIHwAAAAAAAAAAABMm' + b'jgAAAAAAAADT0oUhetCrfnnrnGI2EQAAAAAAAAAAMDfiCAAAAAAAADAuu8PbBxD60EHzeDnF' + b'xjwAAAAAAAAAAMASiSMAAAAAAADAebRhiBz0qsP7dwBB+AAAAAAAAAAAAOD3xBEAAAAAAADg' + b'a7owRA5CeIod5OffyylmMwEAAAAAAAAAAHyeOAIAAAAAAAC8bh+GyEGvOrzN4XqiBwAAAAAA' + b'AAAAAGcijgAAAAAAAMCS7J59rg5vHzioD5+bnGJjJgAAAAAAAAAAgHERRwAAAAAAAGDKnscO' + b'+sBBfu1zTjGbCgAAAAAAAAAAYLrEEQAAAAAAABiL56GD5nC9PmxQP/4gp1iZCgAAAAAAAAAA' + b'YFnEEQAAAAAAADimNjxFDXp91CA/+7p69rnJKTYmAwAAAAAAAAAA4C3iCAAAAAAAADy3Dy9j' + b'Bk14GTvof1Y//zqnWJsNAAAAAAAAAACAUxJHAAAAAAAAmL7dK9+r3vE9YQMAAAAAAAAAAAAm' + b'QRwBAAAAAADgtPbl8m9+Vv/mZ83h/vH9nGJjUgAAAAAAAAAAAJZGHAEAAAAAAJi6P8UHfqf/' + b'/fqdv1u99WflFGt/DQAAAAAAAAAAAHA6//r586cVAAAAAAAAAAAAAAAAAAAAgNG6MgEAAAAA' + b'AAAAAAAAAAAAAAAwZuIIAAAA8H927lgAAAAAYJC/9TR2FEgAAAAAAAAAAAAAAACsyREAAAAA' + b'AAAAAAAAAAAAAACANTkCAAAAAAAAAAAAAAAAAAAAsCZHAAAAAAAAAAAAAAAAAAAAANbkCAAA' + b'AAAAAAAAAAAAAAAAAMCaHAEAAAAAAAAAAAAAAAAAAABYkyMAAAAAAAAAAAAAAAAAAAAAa3IE' + b'AAAAAAAAAAAAAAAAAAAAYE2OAAAAAAAAAAAAAAAAAAAAAKzJEQAAAAAAAAAAAAAAAAAAAIA1' + b'OQIAAAAAAAAAAAAAAAAAAACwJkcAAAAAAAAAAAAAAAAAAAAA1uQIAAAAAAAAAAAAAAAAAAAA' + b'wJocAQAAAAAAAAAAAAAAAAAAAFiTIwAAAAAAAAAAAAAAAAAAAABrcgQAAAAAAAAAAAAAAAAA' + b'AABgTY4AAAAAAAAAAAAAAAAAAAAArMkRAAAAAAAAAAAAAAAAAAAAgDU5AgAAAAAAAAAAAAAA' + b'AAAAALAmRwAAAAAAAAAAAAAAAAAAAADW5AgAAAAAAAAAAAAAAAAAAADAmhwBAAAAAAAAAAAA' + b'AAAAAAAAWJMjAAAAAAAAAAAAAAAAAAAAAGtyBAAAAAAAAAAAAAAAAAAAAGBNjgAAAAAAAAAA' + b'AAAAAAAAAACsyREAAAAAAAAAAAAAAAAAAACANTkCAAAAAAAAAAAAAAAAAAAAsCZHAAAAAAAA' + b'AAAAAAAAAAAAANbkCAAAAAAAAAAAAAAAAAAAAMCaHAEAAAAAAAAAAAAAAAAAAABYkyMAAAAA' + b'AAAAAAAAAAAAAAAAa3IEAAAAAAAAAAAAAAAAAAAAYE2OAAAAAAAAAAAAAAAAAAAAAKzJEQAA' + b'AAAAAAAAAAAAAAAAAIA1OQIAAAAAAAAAAAAAAAAAAACwJkcAAAAAAAAAAAAAAAAAAAAA1uQI' + b'AAAAAAAAAAAAAAAAAAAAwJocAQAAAAAAAAAAAAAAAAAAAFiTIwAAAAAAAAAAAAAAAAAAAABr' + b'cgQAAAAAAAAAAAAAAAAAAABgTY4AAAAAAAAAAAAAAAAAAAAArMkRAAAAAAAAAAAAAAAAAAAA' + b'gDU5AgAAAAAAAAAAAAAAAAAAALAmRwAAAAAAAAAAAAAAAAAAAADW5AgAAAAAAAAAAAAAAAAA' + b'AADAmhwBAAAAAAAAAAAAAAAAAAAAWJMjAAAAAAAAAAAAAAAAAAAAAGtyBAAAAAAAAAAAAAAA' + b'AAAAAGAtAdq5YwEAAACAQf7W09hRIMkRAAAAAAAAAAAAAAAAAAAAgDU5AgAAAAAAAAAAAAAA' + b'AAAAALAmRwAAAAAAAAAAAAAAAAAAAADW5AgAAAAAAAAAAAAAAAAAAADAmhwBAAAAAAAAAAAA' + b'AAAAAAAAWJMjAAAAAAAAAAAAAAAAAAAAAGtyBAAAAAAAAAAAAAAAAAAAAGBNjgAAAAAAAAAA' + b'AAAAAAAAAACsyREAAAAAAAAAAAAAAAAAAACANTkCAAAAAAAAAAAAAAAAAAAAsCZHAAAAAAAA' + b'AAAAAAAAAAAAANbkCAAAAAAAAAAAAAAAAAAAAMCaHAEAAAAAAAAAAAAAAAAAAABYkyMAAAAA' + b'AAAAAAAAAAAAAAAAa3IEAAAAAAAAAAAAAAAAAAAAYE2OAAAAAAAAAAAAAAAAAAAAAKzJEQAA' + b'AAAAAAAAAAAAAAAAAIA1OQIAAAAAAAAAAAAAAAAAAACwJkcAAAAAAAAAAAAAAAAAAAAA1uQI' + b'AAAAAAAAAAAAAAAAAAAAwJocAQAAAAAAAAAAAAAAAAAAAFiTIwAAAAAAAAAAAAAAAAAAAABr' + b'cgQAAAAAAAAAAAAAAAAAAABgTY4AAAAAAAAAAAAAAAAAAAAArMkRAAAAAAAAAAAAAAAAAAAA' + b'gDU5AgAAAAAAAAAAAAAAAAAAALAmRwAAAAAAAAAAAAAAAAAAAADW5AgAAAAAAAAAAAAAAAAA' + b'AADAmhwBAAAAAAAAAAAAAAAAAAAAWJMjAAAAAAAAAAAAAAAAAAAAAGtyBAAAAAAAAAAAAAAA' + b'AAAAAGBNjgAAAAAAAAAAAAAAAAAAAACsyREAAAAAAAAAAAAAAAAAAACANTkCAAAAAAAAAAAA' + b'AAAAAAAAsCZHAAAAAAAAAAAAAAAAAAAAANbkCAAAAAAAAAAAAAAAAAAAAMCaHAEAAAAAAAAA' + b'AAAAAAAAAABYkyMAAAAAAAAAAAAAAAAAAAAAa3IEAAAAAAAAAAAAAAAAAAAAYE2OAAAAAAAA' + b'AAAAAAAAAAAAAKzJEQAAAAAAAAAAAAAAAAAAAIA1OQIAAAAAAAAAAAAAAAAAAACwJkcAAAAA' + b'AAAAAAAAAAAAAAAA1uQIAAAAAAAAAAAAAAAAAAAAwJocAQAAAAAAAAAAAAAAAAAAAFiTIwAA' + b'AAAAAAAAAAAAAAAAAABrcgQAAAAAAAAAAAAAAAAAAABgTY4AAAAAAAAAAAAAAAAAAAAArMkR' + b'AAAAAAAAAAAAAAAAAAAAgDU5AgAAAAAAAAAAAAAAAAAAALAmRwAAAAAAAAAAAAAAAAAAAADW' + b'5AgAAAAAAAAAAAAAAAAAAADAmhwBAAAAAAAAAAAAAAAAAAAAWJMjAAAAAAAAAAAAAAAAAAAA' + b'AGtyBAAAAAAAAAAAAAAAAAAAAGBNjgAAAAAAAAAAAAAAAAAAAACsyREAAAAAAAAAAAAAAAAA' + b'AACANTkCAAAAAAAAAAAAAAAAAAAAsCZHAAAAAAAAAAAAAAAAAAAAANbkCAAAAAAAAAAAAAAA' + b'AAAAAMCaHAEAAAAAAAAAAAAAAAAAAABYkyMAAAAAAAAAAAAAAAAAAAAAa3IEAAAAAAAAAAAA' + b'AAAAAAAAYE2OAAAAAAAAAAAAAAAAAAAAAKzJEQAAAAAAAAAAAAAAAAAAAIA1OQIAAAAAAAAA' + b'AAAAAAAAAACwJkcAAAAAAAAAAAAAAAAAAAAA1uQIAAAAAAAAAAAAAAAAAAAAwJocAQAAAAAA' + b'AAAAAAAAAAAAAFiTIwAAAAAAAAAAAAAAAAAAAABrcgQAAAAAAAAAAAAAAAAAAABgTY4AAAAA' + b'AAAAAAAAAAAAAAAArMkRAAAAAAAAAAAAAAAAAAAAgDU5AgAAAAAAAAAAAAAAAAAAALAmRwAA' + b'AAAAAAAAAAAAAAAAAADW5AgAAAAAAAAAAAAAAAAAAADAmhwBAAAAAAAAAAAAAAAAAAAAWJMj' + b'AAAAAAAAAAAAAAAAAAAAAGtyBAAAAAAAAAAAAAAAAAAAAGBNjgAAAAAAAAAAAAAAAAAAAACs' + b'yREAAAAAAAAAAAAAAAAAAACANTkCAAAAAAAAAAAAAAAAAAAAsCZHAAAAAAAAAAAAAAAAAAAA' + b'ANbkCAAAAAAAAAAAAAAAAAAAAMCaHAEAAAAAAAAAAAAAAAAAAABYkyMAAAAAAAAAAAAAAAAA' + b'AAAAa3IEAAAAAAAAAAAAAAAAAAAAYE2OAAAAAAAAAAAAAAAAAAAAAKzJEQAAAAAAAAAAAAAA' + b'AAAAAIA1OQIAAAAAAAAAAAAAAAAAAACwJkcAAAAAAAAAAAAAAAAAAAAA1uQIAAAAAAAAAAAA' + b'AAAAAAAAwJocAQAAAAAAAAAAAAAAAAAAAFiTIwAAAAAAAAAAAAAAAAAAAABrcgQAAAAAAAAA' + b'AAAAAAAAAABgTY4AAAAAAAAAAAAAAAAAAAAArMkRAAAAAAAAAAAAAAAAAAAAgDU5AgAAAAAA' + b'AAAAAAAAAAAAALAmRwAAAAAAAAAAAAAAAAAAAADW5AgAAAAAAAAAAAAAAAAAAADAmhwBAAAA' + b'AAAAAAAAAAAAAAAAWJMjAAAAAAAAAAAAAAAAAAAAAGtyBAAAAAAAAAAAAAAAAAAAAGBNjgAA' + b'AAAAAAAAAAAAAAAAAACsyREAAAAAAAAAAAAAAAAAAACANTkCAAAAAAAAAAAAAAAAAAAAsCZH' + b'AAAAAAAAAAAAAAAAAAAAANbkCAAAAAAAAAAAAAAAAAAAAMCaHAEAAAAAAAAAAAAAAAAAAABY' + b'kyMAAAAAAAAAAAAAAAAAAAAAa3IEAAAAAAAAAAAAAAAAAAAAYE2OAAAAAAAAAAAAAAAAAAAA' + b'AKzJEQAAAAAAAAAAAAAAAAAAAIA1OQIAAAAAAAAAAAAAAAAAAACwJkcAAAAAAAAAAAAAAAAA' + b'AAAA1uQIAAAAAAAAAAAAAAAAAAAAwJocAQAAAAAAAAAAAAAAAAAAAFiTIwAAAAAAAAAAAAAA' + b'AAAAAABrcgQAAAAAAAAAAAAAAAAAAABgTY4AAAAAAAAAAAAAAAAAAAAArMkRAAAAAAAAAAAA' + b'AAAAAAAAgDU5AgAAAAAAAAAAAAAAAAAAALAmRwAAAAAAAAAAAAAAAAAAAADW5AgAAAAAAAAA' + b'AAAAAAAAAADAmhwBAAAAAAAAAAAAAAAAAAAAWJMjAAAAAAAAAAAAAAAAAAAAAGtyBAAAAAAA' + b'AAAAAAAAAAAAAGBNjgAAAAAAAAAAAAAAAAAAAACsyREAAAAAAAAAAAAAAAAAAACANTkCAAAA' + b'AAAAAAAAAAAAAAAAsCZHAAAAAAAAAAAAAAAAAAAAANbkCAAAAAAAAAAAAAAAAAAAAMCaHAEA' + b'AAAAAAAAAAAAAAAAAABYkyMAAAAAAAAAAAAAAAAAAAAAa3IEAAAAAAAAAAAAAAAAAAAAYE2O' + b'AAAAAAAAAAAAAAAAAAAAAKzJEQAAAAAAAAAAAAAAAAAAAIA1OQIAAAAAAAAAAAAAAAAAAACw' + b'JkcAAAAAAAAAAAAAAAAAAAAA1uQIAAAAAAAAAAAAAAAAAAAAwJocAQAAAAAAAAAAAAAAAAAA' + b'AFiTIwAAAAAAAAAAAAAAAAAAAABrcgQAAAAAAAAAAAAAAAAAAABgLa0ywJNRfB25AAAAAElF' + b'TkSuQmCC') + diff --git a/ecdsa/__init__.py b/ecdsa/__init__.py new file mode 100644 index 0000000..e834b3a --- /dev/null +++ b/ecdsa/__init__.py @@ -0,0 +1,14 @@ +__all__ = ["curves", "der", "ecdsa", "ellipticcurve", "keys", "numbertheory", + "test_pyecdsa", "util", "six"] +from .keys import SigningKey, VerifyingKey, BadSignatureError, BadDigestError +from .curves import NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1 + +_hush_pyflakes = [SigningKey, VerifyingKey, BadSignatureError, BadDigestError, + NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1] +del _hush_pyflakes + +# This code comes from http://github.com/warner/python-ecdsa + +from ._version import get_versions +__version__ = get_versions()['version'] +del get_versions diff --git a/ecdsa/_version.py b/ecdsa/_version.py new file mode 100644 index 0000000..3e833e6 --- /dev/null +++ b/ecdsa/_version.py @@ -0,0 +1,183 @@ + +# This file helps to compute a version number in source trees obtained from +# git-archive tarball (such as those provided by githubs download-from-tag +# feature). Distribution tarballs (built by setup.py sdist) and build +# directories (produced by setup.py build) will contain a much shorter file +# that just contains the computed version number. + +# This file is released into the public domain. Generated by +# versioneer-0.12 (https://github.com/warner/python-versioneer) + +# these strings will be replaced by git during git-archive +git_refnames = " (tag: python-ecdsa-0.13)" +git_full = "5a6fc047222cf21ad89f6cbf8782d0f1e3ddacda" + +# these strings are filled in when 'setup.py versioneer' creates _version.py +tag_prefix = "python-ecdsa-" +parentdir_prefix = "ecdsa-" +versionfile_source = "ecdsa/_version.py" + +import os, sys, re, subprocess, errno + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + assert isinstance(commands, list) + p = None + for c in commands: + try: + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %s" % args[0]) + print(e) + return None + else: + if verbose: + print("unable to find command, tried %s" % (commands,)) + return None + stdout = p.communicate()[0].strip() + if sys.version >= '3': + stdout = stdout.decode() + if p.returncode != 0: + if verbose: + print("unable to run %s (error)" % args[0]) + return None + return stdout + + +def versions_from_parentdir(parentdir_prefix, root, verbose=False): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % + (root, dirname, parentdir_prefix)) + return None + return {"version": dirname[len(parentdir_prefix):], "full": ""} + +def git_get_keywords(versionfile_abs): + # the code embedded in _version.py can just fetch the value of these + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} + try: + f = open(versionfile_abs,"r") + for line in f.readlines(): + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + f.close() + except EnvironmentError: + pass + return keywords + +def git_versions_from_keywords(keywords, tag_prefix, verbose=False): + if not keywords: + return {} # keyword-finding function failed to find keywords + refnames = keywords["refnames"].strip() + if refnames.startswith("$Format"): + if verbose: + print("keywords are unexpanded, not using") + return {} # unexpanded, so not in an unpacked git-archive tarball + refs = set([r.strip() for r in refnames.strip("()").split(",")]) + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%s', no digits" % ",".join(refs-tags)) + if verbose: + print("likely tags: %s" % ",".join(sorted(tags))) + for ref in sorted(tags): + # sorting will prefer e.g. "2.0" over "2.0rc1" + if ref.startswith(tag_prefix): + r = ref[len(tag_prefix):] + if verbose: + print("picking %s" % r) + return { "version": r, + "full": keywords["full"].strip() } + # no suitable tags, so we use the full revision id + if verbose: + print("no suitable tags, using full revision id") + return { "version": keywords["full"].strip(), + "full": keywords["full"].strip() } + + +def git_versions_from_vcs(tag_prefix, root, verbose=False): + # this runs 'git' from the root of the source tree. This only gets called + # if the git-archive 'subst' keywords were *not* expanded, and + # _version.py hasn't already been rewritten with a short version string, + # meaning we're inside a checked out source tree. + + if not os.path.exists(os.path.join(root, ".git")): + if verbose: + print("no .git in %s" % root) + return {} + + GITS = ["git"] + if sys.platform == "win32": + GITS = ["git.cmd", "git.exe"] + stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], + cwd=root) + if stdout is None: + return {} + if not stdout.startswith(tag_prefix): + if verbose: + print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix)) + return {} + tag = stdout[len(tag_prefix):] + stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if stdout is None: + return {} + full = stdout.strip() + if tag.endswith("-dirty"): + full += "-dirty" + return {"version": tag, "full": full} + + +def get_versions(default={"version": "unknown", "full": ""}, verbose=False): + # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have + # __file__, we can work backwards from there to the root. Some + # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which + # case we can only use expanded keywords. + + keywords = { "refnames": git_refnames, "full": git_full } + ver = git_versions_from_keywords(keywords, tag_prefix, verbose) + if ver: + return ver + + try: + root = os.path.abspath(__file__) + # versionfile_source is the relative path from the top of the source + # tree (where the .git directory might live) to this file. Invert + # this to find the root from __file__. + for i in range(len(versionfile_source.split(os.sep))): + root = os.path.dirname(root) + except NameError: + return default + + return (git_versions_from_vcs(tag_prefix, root, verbose) + or versions_from_parentdir(parentdir_prefix, root, verbose) + or default) diff --git a/ecdsa/curves.py b/ecdsa/curves.py new file mode 100644 index 0000000..a361da7 --- /dev/null +++ b/ecdsa/curves.py @@ -0,0 +1,46 @@ +from __future__ import division + +from . import der, ecdsa + +class UnknownCurveError(Exception): + pass + +def orderlen(order): + return (1+len("%x"%order))//2 # bytes + +# the NIST curves +class Curve: + def __init__(self, name, curve, generator, oid, openssl_name=None): + self.name = name + self.openssl_name = openssl_name # maybe None + self.curve = curve + self.generator = generator + self.order = generator.order() + self.baselen = orderlen(self.order) + self.verifying_key_length = 2*self.baselen + self.signature_length = 2*self.baselen + self.oid = oid + self.encoded_oid = der.encode_oid(*oid) + +NIST192p = Curve("NIST192p", ecdsa.curve_192, ecdsa.generator_192, + (1, 2, 840, 10045, 3, 1, 1), "prime192v1") +NIST224p = Curve("NIST224p", ecdsa.curve_224, ecdsa.generator_224, + (1, 3, 132, 0, 33), "secp224r1") +NIST256p = Curve("NIST256p", ecdsa.curve_256, ecdsa.generator_256, + (1, 2, 840, 10045, 3, 1, 7), "prime256v1") +NIST384p = Curve("NIST384p", ecdsa.curve_384, ecdsa.generator_384, + (1, 3, 132, 0, 34), "secp384r1") +NIST521p = Curve("NIST521p", ecdsa.curve_521, ecdsa.generator_521, + (1, 3, 132, 0, 35), "secp521r1") +SECP256k1 = Curve("SECP256k1", ecdsa.curve_secp256k1, ecdsa.generator_secp256k1, + (1, 3, 132, 0, 10), "secp256k1") + +curves = [NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1] + +def find_curve(oid_curve): + for c in curves: + if c.oid == oid_curve: + return c + raise UnknownCurveError("I don't know about the curve with oid %s." + "I only know about these: %s" % + (oid_curve, [c.name for c in curves])) diff --git a/ecdsa/der.py b/ecdsa/der.py new file mode 100644 index 0000000..b045952 --- /dev/null +++ b/ecdsa/der.py @@ -0,0 +1,199 @@ +from __future__ import division + +import binascii +import base64 +from .six import int2byte, b, integer_types, text_type + +class UnexpectedDER(Exception): + pass + +def encode_constructed(tag, value): + return int2byte(0xa0+tag) + encode_length(len(value)) + value +def encode_integer(r): + assert r >= 0 # can't support negative numbers yet + h = ("%x" % r).encode() + if len(h) % 2: + h = b("0") + h + s = binascii.unhexlify(h) + num = s[0] if isinstance(s[0], integer_types) else ord(s[0]) + if num <= 0x7f: + return b("\x02") + int2byte(len(s)) + s + else: + # DER integers are two's complement, so if the first byte is + # 0x80-0xff then we need an extra 0x00 byte to prevent it from + # looking negative. + return b("\x02") + int2byte(len(s)+1) + b("\x00") + s + +def encode_bitstring(s): + return b("\x03") + encode_length(len(s)) + s +def encode_octet_string(s): + return b("\x04") + encode_length(len(s)) + s +def encode_oid(first, second, *pieces): + assert first <= 2 + assert second <= 39 + encoded_pieces = [int2byte(40*first+second)] + [encode_number(p) + for p in pieces] + body = b('').join(encoded_pieces) + return b('\x06') + encode_length(len(body)) + body +def encode_sequence(*encoded_pieces): + total_len = sum([len(p) for p in encoded_pieces]) + return b('\x30') + encode_length(total_len) + b('').join(encoded_pieces) +def encode_number(n): + b128_digits = [] + while n: + b128_digits.insert(0, (n & 0x7f) | 0x80) + n = n >> 7 + if not b128_digits: + b128_digits.append(0) + b128_digits[-1] &= 0x7f + return b('').join([int2byte(d) for d in b128_digits]) + +def remove_constructed(string): + s0 = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + if (s0 & 0xe0) != 0xa0: + raise UnexpectedDER("wanted constructed tag (0xa0-0xbf), got 0x%02x" + % s0) + tag = s0 & 0x1f + length, llen = read_length(string[1:]) + body = string[1+llen:1+llen+length] + rest = string[1+llen+length:] + return tag, body, rest + +def remove_sequence(string): + if not string.startswith(b("\x30")): + n = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + raise UnexpectedDER("wanted sequence (0x30), got 0x%02x" % n) + length, lengthlength = read_length(string[1:]) + endseq = 1+lengthlength+length + return string[1+lengthlength:endseq], string[endseq:] + +def remove_octet_string(string): + if not string.startswith(b("\x04")): + n = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + raise UnexpectedDER("wanted octetstring (0x04), got 0x%02x" % n) + length, llen = read_length(string[1:]) + body = string[1+llen:1+llen+length] + rest = string[1+llen+length:] + return body, rest + +def remove_object(string): + if not string.startswith(b("\x06")): + n = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + raise UnexpectedDER("wanted object (0x06), got 0x%02x" % n) + length, lengthlength = read_length(string[1:]) + body = string[1+lengthlength:1+lengthlength+length] + rest = string[1+lengthlength+length:] + numbers = [] + while body: + n, ll = read_number(body) + numbers.append(n) + body = body[ll:] + n0 = numbers.pop(0) + first = n0//40 + second = n0-(40*first) + numbers.insert(0, first) + numbers.insert(1, second) + return tuple(numbers), rest + +def remove_integer(string): + if not string.startswith(b("\x02")): + n = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + raise UnexpectedDER("wanted integer (0x02), got 0x%02x" % n) + length, llen = read_length(string[1:]) + numberbytes = string[1+llen:1+llen+length] + rest = string[1+llen+length:] + nbytes = numberbytes[0] if isinstance(numberbytes[0], integer_types) else ord(numberbytes[0]) + assert nbytes < 0x80 # can't support negative numbers yet + return int(binascii.hexlify(numberbytes), 16), rest + +def read_number(string): + number = 0 + llen = 0 + # base-128 big endian, with b7 set in all but the last byte + while True: + if llen > len(string): + raise UnexpectedDER("ran out of length bytes") + number = number << 7 + d = string[llen] if isinstance(string[llen], integer_types) else ord(string[llen]) + number += (d & 0x7f) + llen += 1 + if not d & 0x80: + break + return number, llen + +def encode_length(l): + assert l >= 0 + if l < 0x80: + return int2byte(l) + s = ("%x" % l).encode() + if len(s)%2: + s = b("0")+s + s = binascii.unhexlify(s) + llen = len(s) + return int2byte(0x80|llen) + s + +def read_length(string): + num = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + if not (num & 0x80): + # short form + return (num & 0x7f), 1 + # else long-form: b0&0x7f is number of additional base256 length bytes, + # big-endian + llen = num & 0x7f + if llen > len(string)-1: + raise UnexpectedDER("ran out of length bytes") + return int(binascii.hexlify(string[1:1+llen]), 16), 1+llen + +def remove_bitstring(string): + num = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + if not string.startswith(b("\x03")): + raise UnexpectedDER("wanted bitstring (0x03), got 0x%02x" % num) + length, llen = read_length(string[1:]) + body = string[1+llen:1+llen+length] + rest = string[1+llen+length:] + return body, rest + +# SEQUENCE([1, STRING(secexp), cont[0], OBJECT(curvename), cont[1], BINTSTRING) + + +# signatures: (from RFC3279) +# ansi-X9-62 OBJECT IDENTIFIER ::= { +# iso(1) member-body(2) us(840) 10045 } +# +# id-ecSigType OBJECT IDENTIFIER ::= { +# ansi-X9-62 signatures(4) } +# ecdsa-with-SHA1 OBJECT IDENTIFIER ::= { +# id-ecSigType 1 } +## so 1,2,840,10045,4,1 +## so 0x42, .. .. + +# Ecdsa-Sig-Value ::= SEQUENCE { +# r INTEGER, +# s INTEGER } + +# id-public-key-type OBJECT IDENTIFIER ::= { ansi-X9.62 2 } +# +# id-ecPublicKey OBJECT IDENTIFIER ::= { id-publicKeyType 1 } + +# I think the secp224r1 identifier is (t=06,l=05,v=2b81040021) +# secp224r1 OBJECT IDENTIFIER ::= { +# iso(1) identified-organization(3) certicom(132) curve(0) 33 } +# and the secp384r1 is (t=06,l=05,v=2b81040022) +# secp384r1 OBJECT IDENTIFIER ::= { +# iso(1) identified-organization(3) certicom(132) curve(0) 34 } + +def unpem(pem): + if isinstance(pem, text_type): + pem = pem.encode() + + d = b("").join([l.strip() for l in pem.split(b("\n")) + if l and not l.startswith(b("-----"))]) + return base64.b64decode(d) +def topem(der, name): + b64 = base64.b64encode(der) + lines = [("-----BEGIN %s-----\n" % name).encode()] + lines.extend([b64[start:start+64]+b("\n") + for start in range(0, len(b64), 64)]) + lines.append(("-----END %s-----\n" % name).encode()) + return b("").join(lines) + diff --git a/ecdsa/ecdsa.py b/ecdsa/ecdsa.py new file mode 100644 index 0000000..4eb2d39 --- /dev/null +++ b/ecdsa/ecdsa.py @@ -0,0 +1,576 @@ +#! /usr/bin/env python + +""" +Implementation of Elliptic-Curve Digital Signatures. + +Classes and methods for elliptic-curve signatures: +private keys, public keys, signatures, +NIST prime-modulus curves with modulus lengths of +192, 224, 256, 384, and 521 bits. + +Example: + + # (In real-life applications, you would probably want to + # protect against defects in SystemRandom.) + from random import SystemRandom + randrange = SystemRandom().randrange + + # Generate a public/private key pair using the NIST Curve P-192: + + g = generator_192 + n = g.order() + secret = randrange( 1, n ) + pubkey = Public_key( g, g * secret ) + privkey = Private_key( pubkey, secret ) + + # Signing a hash value: + + hash = randrange( 1, n ) + signature = privkey.sign( hash, randrange( 1, n ) ) + + # Verifying a signature for a hash value: + + if pubkey.verifies( hash, signature ): + print_("Demo verification succeeded.") + else: + print_("*** Demo verification failed.") + + # Verification fails if the hash value is modified: + + if pubkey.verifies( hash-1, signature ): + print_("**** Demo verification failed to reject tampered hash.") + else: + print_("Demo verification correctly rejected tampered hash.") + +Version of 2009.05.16. + +Revision history: + 2005.12.31 - Initial version. + 2008.11.25 - Substantial revisions introducing new classes. + 2009.05.16 - Warn against using random.randrange in real applications. + 2009.05.17 - Use random.SystemRandom by default. + +Written in 2005 by Peter Pearson and placed in the public domain. +""" + +from .six import int2byte, b, print_ +from . import ellipticcurve +from . import numbertheory +import random + + + +class Signature( object ): + """ECDSA signature. + """ + def __init__( self, r, s ): + self.r = r + self.s = s + + + +class Public_key( object ): + """Public key for ECDSA. + """ + + def __init__( self, generator, point ): + """generator is the Point that generates the group, + point is the Point that defines the public key. + """ + + self.curve = generator.curve() + self.generator = generator + self.point = point + n = generator.order() + if not n: + raise RuntimeError("Generator point must have order.") + if not n * point == ellipticcurve.INFINITY: + raise RuntimeError("Generator point order is bad.") + if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y(): + raise RuntimeError("Generator point has x or y out of range.") + + + def verifies( self, hash, signature ): + """Verify that signature is a valid signature of hash. + Return True if the signature is valid. + """ + + # From X9.62 J.3.1. + + G = self.generator + n = G.order() + r = signature.r + s = signature.s + if r < 1 or r > n-1: return False + if s < 1 or s > n-1: return False + c = numbertheory.inverse_mod( s, n ) + u1 = ( hash * c ) % n + u2 = ( r * c ) % n + xy = u1 * G + u2 * self.point + v = xy.x() % n + return v == r + + + +class Private_key( object ): + """Private key for ECDSA. + """ + + def __init__( self, public_key, secret_multiplier ): + """public_key is of class Public_key; + secret_multiplier is a large integer. + """ + + self.public_key = public_key + self.secret_multiplier = secret_multiplier + + def sign( self, hash, random_k ): + """Return a signature for the provided hash, using the provided + random nonce. It is absolutely vital that random_k be an unpredictable + number in the range [1, self.public_key.point.order()-1]. If + an attacker can guess random_k, he can compute our private key from a + single signature. Also, if an attacker knows a few high-order + bits (or a few low-order bits) of random_k, he can compute our private + key from many signatures. The generation of nonces with adequate + cryptographic strength is very difficult and far beyond the scope + of this comment. + + May raise RuntimeError, in which case retrying with a new + random value k is in order. + """ + + G = self.public_key.generator + n = G.order() + k = random_k % n + p1 = k * G + r = p1.x() + if r == 0: raise RuntimeError("amazingly unlucky random number r") + s = ( numbertheory.inverse_mod( k, n ) * \ + ( hash + ( self.secret_multiplier * r ) % n ) ) % n + if s == 0: raise RuntimeError("amazingly unlucky random number s") + return Signature( r, s ) + + + +def int_to_string( x ): + """Convert integer x into a string of bytes, as per X9.62.""" + assert x >= 0 + if x == 0: return b('\0') + result = [] + while x: + ordinal = x & 0xFF + result.append(int2byte(ordinal)) + x >>= 8 + + result.reverse() + return b('').join(result) + + +def string_to_int( s ): + """Convert a string of bytes into an integer, as per X9.62.""" + result = 0 + for c in s: + if not isinstance(c, int): c = ord( c ) + result = 256 * result + c + return result + + +def digest_integer( m ): + """Convert an integer into a string of bytes, compute + its SHA-1 hash, and convert the result to an integer.""" + # + # I don't expect this function to be used much. I wrote + # it in order to be able to duplicate the examples + # in ECDSAVS. + # + from hashlib import sha1 + return string_to_int( sha1( int_to_string( m ) ).digest() ) + + +def point_is_valid( generator, x, y ): + """Is (x,y) a valid public key based on the specified generator?""" + + # These are the tests specified in X9.62. + + n = generator.order() + curve = generator.curve() + if x < 0 or n <= x or y < 0 or n <= y: + return False + if not curve.contains_point( x, y ): + return False + if not n*ellipticcurve.Point( curve, x, y ) == \ + ellipticcurve.INFINITY: + return False + return True + + + +# NIST Curve P-192: +_p = 6277101735386680763835789423207666416083908700390324961279 +_r = 6277101735386680763835789423176059013767194773182842284081 +# s = 0x3045ae6fc8422f64ed579528d38120eae12196d5L +# c = 0x3099d2bbbfcb2538542dcd5fb078b6ef5f3d6fe2c745de65L +_b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1 +_Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 +_Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 + +curve_192 = ellipticcurve.CurveFp( _p, -3, _b ) +generator_192 = ellipticcurve.Point( curve_192, _Gx, _Gy, _r ) + + +# NIST Curve P-224: +_p = 26959946667150639794667015087019630673557916260026308143510066298881 +_r = 26959946667150639794667015087019625940457807714424391721682722368061 +# s = 0xbd71344799d5c7fcdc45b59fa3b9ab8f6a948bc5L +# c = 0x5b056c7e11dd68f40469ee7f3c7a7d74f7d121116506d031218291fbL +_b = 0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4 +_Gx =0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21 +_Gy = 0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34 + +curve_224 = ellipticcurve.CurveFp( _p, -3, _b ) +generator_224 = ellipticcurve.Point( curve_224, _Gx, _Gy, _r ) + +# NIST Curve P-256: +_p = 115792089210356248762697446949407573530086143415290314195533631308867097853951 +_r = 115792089210356248762697446949407573529996955224135760342422259061068512044369 +# s = 0xc49d360886e704936a6678e1139d26b7819f7e90L +# c = 0x7efba1662985be9403cb055c75d4f7e0ce8d84a9c5114abcaf3177680104fa0dL +_b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b +_Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296 +_Gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5 + +curve_256 = ellipticcurve.CurveFp( _p, -3, _b ) +generator_256 = ellipticcurve.Point( curve_256, _Gx, _Gy, _r ) + +# NIST Curve P-384: +_p = 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319 +_r = 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643 +# s = 0xa335926aa319a27a1d00896a6773a4827acdac73L +# c = 0x79d1e655f868f02fff48dcdee14151ddb80643c1406d0ca10dfe6fc52009540a495e8042ea5f744f6e184667cc722483L +_b = 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef +_Gx = 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7 +_Gy = 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f + +curve_384 = ellipticcurve.CurveFp( _p, -3, _b ) +generator_384 = ellipticcurve.Point( curve_384, _Gx, _Gy, _r ) + +# NIST Curve P-521: +_p = 6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151 +_r = 6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449 +# s = 0xd09e8800291cb85396cc6717393284aaa0da64baL +# c = 0x0b48bfa5f420a34949539d2bdfc264eeeeb077688e44fbf0ad8f6d0edb37bd6b533281000518e19f1b9ffbe0fe9ed8a3c2200b8f875e523868c70c1e5bf55bad637L +_b = 0x051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00 +_Gx = 0xc6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66 +_Gy = 0x11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650 + +curve_521 = ellipticcurve.CurveFp( _p, -3, _b ) +generator_521 = ellipticcurve.Point( curve_521, _Gx, _Gy, _r ) + +# Certicom secp256-k1 +_a = 0x0000000000000000000000000000000000000000000000000000000000000000 +_b = 0x0000000000000000000000000000000000000000000000000000000000000007 +_p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f +_Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 +_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 +_r = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 + +curve_secp256k1 = ellipticcurve.CurveFp( _p, _a, _b) +generator_secp256k1 = ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r) + + + +def __main__(): + class TestFailure(Exception): pass + + def test_point_validity( generator, x, y, expected ): + """generator defines the curve; is (x,y) a point on + this curve? "expected" is True if the right answer is Yes.""" + if point_is_valid( generator, x, y ) == expected: + print_("Point validity tested as expected.") + else: + raise TestFailure("*** Point validity test gave wrong result.") + + def test_signature_validity( Msg, Qx, Qy, R, S, expected ): + """Msg = message, Qx and Qy represent the base point on + elliptic curve c192, R and S are the signature, and + "expected" is True iff the signature is expected to be valid.""" + pubk = Public_key( generator_192, + ellipticcurve.Point( curve_192, Qx, Qy ) ) + got = pubk.verifies( digest_integer( Msg ), Signature( R, S ) ) + if got == expected: + print_("Signature tested as expected: got %s, expected %s." % \ + ( got, expected )) + else: + raise TestFailure("*** Signature test failed: got %s, expected %s." % \ + ( got, expected )) + + print_("NIST Curve P-192:") + + p192 = generator_192 + + # From X9.62: + + d = 651056770906015076056810763456358567190100156695615665659 + Q = d * p192 + if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: + raise TestFailure("*** p192 * d came out wrong.") + else: + print_("p192 * d came out right.") + + k = 6140507067065001063065065565667405560006161556565665656654 + R = k * p192 + if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + raise TestFailure("*** k * p192 came out wrong.") + else: + print_("k * p192 came out right.") + + u1 = 2563697409189434185194736134579731015366492496392189760599 + u2 = 6266643813348617967186477710235785849136406323338782220568 + temp = u1 * p192 + u2 * Q + if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + raise TestFailure("*** u1 * p192 + u2 * Q came out wrong.") + else: + print_("u1 * p192 + u2 * Q came out right.") + + e = 968236873715988614170569073515315707566766479517 + pubk = Public_key( generator_192, generator_192 * d ) + privk = Private_key( pubk, d ) + sig = privk.sign( e, k ) + r, s = sig.r, sig.s + if r != 3342403536405981729393488334694600415596881826869351677613 \ + or s != 5735822328888155254683894997897571951568553642892029982342: + raise TestFailure("*** r or s came out wrong.") + else: + print_("r and s came out right.") + + valid = pubk.verifies( e, sig ) + if valid: print_("Signature verified OK.") + else: raise TestFailure("*** Signature failed verification.") + + valid = pubk.verifies( e-1, sig ) + if not valid: print_("Forgery was correctly rejected.") + else: raise TestFailure("*** Forgery was erroneously accepted.") + + print_("Testing point validity, as per ECDSAVS.pdf B.2.2:") + + test_point_validity( \ + p192, \ + 0xcd6d0f029a023e9aaca429615b8f577abee685d8257cc83a, \ + 0x00019c410987680e9fb6c0b6ecc01d9a2647c8bae27721bacdfc, \ + False ) + + test_point_validity( + p192, \ + 0x00017f2fce203639e9eaf9fb50b81fc32776b30e3b02af16c73b, \ + 0x95da95c5e72dd48e229d4748d4eee658a9a54111b23b2adb, \ + False ) + + test_point_validity( + p192, \ + 0x4f77f8bc7fccbadd5760f4938746d5f253ee2168c1cf2792, \ + 0x000147156ff824d131629739817edb197717c41aab5c2a70f0f6, \ + False ) + + test_point_validity( + p192, \ + 0xc58d61f88d905293bcd4cd0080bcb1b7f811f2ffa41979f6, \ + 0x8804dc7a7c4c7f8b5d437f5156f3312ca7d6de8a0e11867f, \ + True ) + + test_point_validity( + p192, \ + 0xcdf56c1aa3d8afc53c521adf3ffb96734a6a630a4a5b5a70, \ + 0x97c1c44a5fb229007b5ec5d25f7413d170068ffd023caa4e, \ + True ) + + test_point_validity( + p192, \ + 0x89009c0dc361c81e99280c8e91df578df88cdf4b0cdedced, \ + 0x27be44a529b7513e727251f128b34262a0fd4d8ec82377b9, \ + True ) + + test_point_validity( + p192, \ + 0x6a223d00bd22c52833409a163e057e5b5da1def2a197dd15, \ + 0x7b482604199367f1f303f9ef627f922f97023e90eae08abf, \ + True ) + + test_point_validity( + p192, \ + 0x6dccbde75c0948c98dab32ea0bc59fe125cf0fb1a3798eda, \ + 0x0001171a3e0fa60cf3096f4e116b556198de430e1fbd330c8835, \ + False ) + + test_point_validity( + p192, \ + 0xd266b39e1f491fc4acbbbc7d098430931cfa66d55015af12, \ + 0x193782eb909e391a3148b7764e6b234aa94e48d30a16dbb2, \ + False ) + + test_point_validity( + p192, \ + 0x9d6ddbcd439baa0c6b80a654091680e462a7d1d3f1ffeb43, \ + 0x6ad8efc4d133ccf167c44eb4691c80abffb9f82b932b8caa, \ + False ) + + test_point_validity( + p192, \ + 0x146479d944e6bda87e5b35818aa666a4c998a71f4e95edbc, \ + 0xa86d6fe62bc8fbd88139693f842635f687f132255858e7f6, \ + False ) + + test_point_validity( + p192, \ + 0xe594d4a598046f3598243f50fd2c7bd7d380edb055802253, \ + 0x509014c0c4d6b536e3ca750ec09066af39b4c8616a53a923, \ + False ) + + print_("Trying signature-verification tests from ECDSAVS.pdf B.2.4:") + print_("P-192:") + Msg = 0x84ce72aa8699df436059f052ac51b6398d2511e49631bcb7e71f89c499b9ee425dfbc13a5f6d408471b054f2655617cbbaf7937b7c80cd8865cf02c8487d30d2b0fbd8b2c4e102e16d828374bbc47b93852f212d5043c3ea720f086178ff798cc4f63f787b9c2e419efa033e7644ea7936f54462dc21a6c4580725f7f0e7d158 + Qx = 0xd9dbfb332aa8e5ff091e8ce535857c37c73f6250ffb2e7ac + Qy = 0x282102e364feded3ad15ddf968f88d8321aa268dd483ebc4 + R = 0x64dca58a20787c488d11d6dd96313f1b766f2d8efe122916 + S = 0x1ecba28141e84ab4ecad92f56720e2cc83eb3d22dec72479 + test_signature_validity( Msg, Qx, Qy, R, S, True ) + + Msg = 0x94bb5bacd5f8ea765810024db87f4224ad71362a3c28284b2b9f39fab86db12e8beb94aae899768229be8fdb6c4f12f28912bb604703a79ccff769c1607f5a91450f30ba0460d359d9126cbd6296be6d9c4bb96c0ee74cbb44197c207f6db326ab6f5a659113a9034e54be7b041ced9dcf6458d7fb9cbfb2744d999f7dfd63f4 + Qx = 0x3e53ef8d3112af3285c0e74842090712cd324832d4277ae7 + Qy = 0xcc75f8952d30aec2cbb719fc6aa9934590b5d0ff5a83adb7 + R = 0x8285261607283ba18f335026130bab31840dcfd9c3e555af + S = 0x356d89e1b04541afc9704a45e9c535ce4a50929e33d7e06c + test_signature_validity( Msg, Qx, Qy, R, S, True ) + + Msg = 0xf6227a8eeb34afed1621dcc89a91d72ea212cb2f476839d9b4243c66877911b37b4ad6f4448792a7bbba76c63bdd63414b6facab7dc71c3396a73bd7ee14cdd41a659c61c99b779cecf07bc51ab391aa3252386242b9853ea7da67fd768d303f1b9b513d401565b6f1eb722dfdb96b519fe4f9bd5de67ae131e64b40e78c42dd + Qx = 0x16335dbe95f8e8254a4e04575d736befb258b8657f773cb7 + Qy = 0x421b13379c59bc9dce38a1099ca79bbd06d647c7f6242336 + R = 0x4141bd5d64ea36c5b0bd21ef28c02da216ed9d04522b1e91 + S = 0x159a6aa852bcc579e821b7bb0994c0861fb08280c38daa09 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x16b5f93afd0d02246f662761ed8e0dd9504681ed02a253006eb36736b563097ba39f81c8e1bce7a16c1339e345efabbc6baa3efb0612948ae51103382a8ee8bc448e3ef71e9f6f7a9676694831d7f5dd0db5446f179bcb737d4a526367a447bfe2c857521c7f40b6d7d7e01a180d92431fb0bbd29c04a0c420a57b3ed26ccd8a + Qx = 0xfd14cdf1607f5efb7b1793037b15bdf4baa6f7c16341ab0b + Qy = 0x83fa0795cc6c4795b9016dac928fd6bac32f3229a96312c4 + R = 0x8dfdb832951e0167c5d762a473c0416c5c15bc1195667dc1 + S = 0x1720288a2dc13fa1ec78f763f8fe2ff7354a7e6fdde44520 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x08a2024b61b79d260e3bb43ef15659aec89e5b560199bc82cf7c65c77d39192e03b9a895d766655105edd9188242b91fbde4167f7862d4ddd61e5d4ab55196683d4f13ceb90d87aea6e07eb50a874e33086c4a7cb0273a8e1c4408f4b846bceae1ebaac1b2b2ea851a9b09de322efe34cebe601653efd6ddc876ce8c2f2072fb + Qx = 0x674f941dc1a1f8b763c9334d726172d527b90ca324db8828 + Qy = 0x65adfa32e8b236cb33a3e84cf59bfb9417ae7e8ede57a7ff + R = 0x9508b9fdd7daf0d8126f9e2bc5a35e4c6d800b5b804d7796 + S = 0x36f2bf6b21b987c77b53bb801b3435a577e3d493744bfab0 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x1843aba74b0789d4ac6b0b8923848023a644a7b70afa23b1191829bbe4397ce15b629bf21a8838298653ed0c19222b95fa4f7390d1b4c844d96e645537e0aae98afb5c0ac3bd0e4c37f8daaff25556c64e98c319c52687c904c4de7240a1cc55cd9756b7edaef184e6e23b385726e9ffcba8001b8f574987c1a3fedaaa83ca6d + Qx = 0x10ecca1aad7220b56a62008b35170bfd5e35885c4014a19f + Qy = 0x04eb61984c6c12ade3bc47f3c629ece7aa0a033b9948d686 + R = 0x82bfa4e82c0dfe9274169b86694e76ce993fd83b5c60f325 + S = 0xa97685676c59a65dbde002fe9d613431fb183e8006d05633 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x5a478f4084ddd1a7fea038aa9732a822106385797d02311aeef4d0264f824f698df7a48cfb6b578cf3da416bc0799425bb491be5b5ecc37995b85b03420a98f2c4dc5c31a69a379e9e322fbe706bbcaf0f77175e05cbb4fa162e0da82010a278461e3e974d137bc746d1880d6eb02aa95216014b37480d84b87f717bb13f76e1 + Qx = 0x6636653cb5b894ca65c448277b29da3ad101c4c2300f7c04 + Qy = 0xfdf1cbb3fc3fd6a4f890b59e554544175fa77dbdbeb656c1 + R = 0xeac2ddecddfb79931a9c3d49c08de0645c783a24cb365e1c + S = 0x3549fee3cfa7e5f93bc47d92d8ba100e881a2a93c22f8d50 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0xc598774259a058fa65212ac57eaa4f52240e629ef4c310722088292d1d4af6c39b49ce06ba77e4247b20637174d0bd67c9723feb57b5ead232b47ea452d5d7a089f17c00b8b6767e434a5e16c231ba0efa718a340bf41d67ea2d295812ff1b9277daacb8bc27b50ea5e6443bcf95ef4e9f5468fe78485236313d53d1c68f6ba2 + Qx = 0xa82bd718d01d354001148cd5f69b9ebf38ff6f21898f8aaa + Qy = 0xe67ceede07fc2ebfafd62462a51e4b6c6b3d5b537b7caf3e + R = 0x4d292486c620c3de20856e57d3bb72fcde4a73ad26376955 + S = 0xa85289591a6081d5728825520e62ff1c64f94235c04c7f95 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0xca98ed9db081a07b7557f24ced6c7b9891269a95d2026747add9e9eb80638a961cf9c71a1b9f2c29744180bd4c3d3db60f2243c5c0b7cc8a8d40a3f9a7fc910250f2187136ee6413ffc67f1a25e1c4c204fa9635312252ac0e0481d89b6d53808f0c496ba87631803f6c572c1f61fa049737fdacce4adff757afed4f05beb658 + Qx = 0x7d3b016b57758b160c4fca73d48df07ae3b6b30225126c2f + Qy = 0x4af3790d9775742bde46f8da876711be1b65244b2b39e7ec + R = 0x95f778f5f656511a5ab49a5d69ddd0929563c29cbc3a9e62 + S = 0x75c87fc358c251b4c83d2dd979faad496b539f9f2ee7a289 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x31dd9a54c8338bea06b87eca813d555ad1850fac9742ef0bbe40dad400e10288acc9c11ea7dac79eb16378ebea9490e09536099f1b993e2653cd50240014c90a9c987f64545abc6a536b9bd2435eb5e911fdfde2f13be96ea36ad38df4ae9ea387b29cced599af777338af2794820c9cce43b51d2112380a35802ab7e396c97a + Qx = 0x9362f28c4ef96453d8a2f849f21e881cd7566887da8beb4a + Qy = 0xe64d26d8d74c48a024ae85d982ee74cd16046f4ee5333905 + R = 0xf3923476a296c88287e8de914b0b324ad5a963319a4fe73b + S = 0xf0baeed7624ed00d15244d8ba2aede085517dbdec8ac65f5 + test_signature_validity( Msg, Qx, Qy, R, S, True ) + + Msg = 0xb2b94e4432267c92f9fdb9dc6040c95ffa477652761290d3c7de312283f6450d89cc4aabe748554dfb6056b2d8e99c7aeaad9cdddebdee9dbc099839562d9064e68e7bb5f3a6bba0749ca9a538181fc785553a4000785d73cc207922f63e8ce1112768cb1de7b673aed83a1e4a74592f1268d8e2a4e9e63d414b5d442bd0456d + Qx = 0xcc6fc032a846aaac25533eb033522824f94e670fa997ecef + Qy = 0xe25463ef77a029eccda8b294fd63dd694e38d223d30862f1 + R = 0x066b1d07f3a40e679b620eda7f550842a35c18b80c5ebe06 + S = 0xa0b0fb201e8f2df65e2c4508ef303bdc90d934016f16b2dc + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x4366fcadf10d30d086911de30143da6f579527036937007b337f7282460eae5678b15cccda853193ea5fc4bc0a6b9d7a31128f27e1214988592827520b214eed5052f7775b750b0c6b15f145453ba3fee24a085d65287e10509eb5d5f602c440341376b95c24e5c4727d4b859bfe1483d20538acdd92c7997fa9c614f0f839d7 + Qx = 0x955c908fe900a996f7e2089bee2f6376830f76a19135e753 + Qy = 0xba0c42a91d3847de4a592a46dc3fdaf45a7cc709b90de520 + R = 0x1f58ad77fc04c782815a1405b0925e72095d906cbf52a668 + S = 0xf2e93758b3af75edf784f05a6761c9b9a6043c66b845b599 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x543f8af57d750e33aa8565e0cae92bfa7a1ff78833093421c2942cadf9986670a5ff3244c02a8225e790fbf30ea84c74720abf99cfd10d02d34377c3d3b41269bea763384f372bb786b5846f58932defa68023136cd571863b304886e95e52e7877f445b9364b3f06f3c28da12707673fecb4b8071de06b6e0a3c87da160cef3 + Qx = 0x31f7fa05576d78a949b24812d4383107a9a45bb5fccdd835 + Qy = 0x8dc0eb65994a90f02b5e19bd18b32d61150746c09107e76b + R = 0xbe26d59e4e883dde7c286614a767b31e49ad88789d3a78ff + S = 0x8762ca831c1ce42df77893c9b03119428e7a9b819b619068 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0xd2e8454143ce281e609a9d748014dcebb9d0bc53adb02443a6aac2ffe6cb009f387c346ecb051791404f79e902ee333ad65e5c8cb38dc0d1d39a8dc90add5023572720e5b94b190d43dd0d7873397504c0c7aef2727e628eb6a74411f2e400c65670716cb4a815dc91cbbfeb7cfe8c929e93184c938af2c078584da045e8f8d1 + Qx = 0x66aa8edbbdb5cf8e28ceb51b5bda891cae2df84819fe25c0 + Qy = 0x0c6bc2f69030a7ce58d4a00e3b3349844784a13b8936f8da + R = 0xa4661e69b1734f4a71b788410a464b71e7ffe42334484f23 + S = 0x738421cf5e049159d69c57a915143e226cac8355e149afe9 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x6660717144040f3e2f95a4e25b08a7079c702a8b29babad5a19a87654bc5c5afa261512a11b998a4fb36b5d8fe8bd942792ff0324b108120de86d63f65855e5461184fc96a0a8ffd2ce6d5dfb0230cbbdd98f8543e361b3205f5da3d500fdc8bac6db377d75ebef3cb8f4d1ff738071ad0938917889250b41dd1d98896ca06fb + Qx = 0xbcfacf45139b6f5f690a4c35a5fffa498794136a2353fc77 + Qy = 0x6f4a6c906316a6afc6d98fe1f0399d056f128fe0270b0f22 + R = 0x9db679a3dafe48f7ccad122933acfe9da0970b71c94c21c1 + S = 0x984c2db99827576c0a41a5da41e07d8cc768bc82f18c9da9 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + + + print_("Testing the example code:") + + # Building a public/private key pair from the NIST Curve P-192: + + g = generator_192 + n = g.order() + + # (random.SystemRandom is supposed to provide + # crypto-quality random numbers, but as Debian recently + # illustrated, a systems programmer can accidentally + # demolish this security, so in serious applications + # further precautions are appropriate.) + + randrange = random.SystemRandom().randrange + + secret = randrange( 1, n ) + pubkey = Public_key( g, g * secret ) + privkey = Private_key( pubkey, secret ) + + # Signing a hash value: + + hash = randrange( 1, n ) + signature = privkey.sign( hash, randrange( 1, n ) ) + + # Verifying a signature for a hash value: + + if pubkey.verifies( hash, signature ): + print_("Demo verification succeeded.") + else: + raise TestFailure("*** Demo verification failed.") + + if pubkey.verifies( hash-1, signature ): + raise TestFailure( "**** Demo verification failed to reject tampered hash.") + else: + print_("Demo verification correctly rejected tampered hash.") + +if __name__ == "__main__": + __main__() diff --git a/ecdsa/ellipticcurve.py b/ecdsa/ellipticcurve.py new file mode 100644 index 0000000..390bb35 --- /dev/null +++ b/ecdsa/ellipticcurve.py @@ -0,0 +1,293 @@ +#! /usr/bin/env python +# +# Implementation of elliptic curves, for cryptographic applications. +# +# This module doesn't provide any way to choose a random elliptic +# curve, nor to verify that an elliptic curve was chosen randomly, +# because one can simply use NIST's standard curves. +# +# Notes from X9.62-1998 (draft): +# Nomenclature: +# - Q is a public key. +# The "Elliptic Curve Domain Parameters" include: +# - q is the "field size", which in our case equals p. +# - p is a big prime. +# - G is a point of prime order (5.1.1.1). +# - n is the order of G (5.1.1.1). +# Public-key validation (5.2.2): +# - Verify that Q is not the point at infinity. +# - Verify that X_Q and Y_Q are in [0,p-1]. +# - Verify that Q is on the curve. +# - Verify that nQ is the point at infinity. +# Signature generation (5.3): +# - Pick random k from [1,n-1]. +# Signature checking (5.4.2): +# - Verify that r and s are in [1,n-1]. +# +# Version of 2008.11.25. +# +# Revision history: +# 2005.12.31 - Initial version. +# 2008.11.25 - Change CurveFp.is_on to contains_point. +# +# Written in 2005 by Peter Pearson and placed in the public domain. + +from __future__ import division + +from .six import print_ +from . import numbertheory + +class CurveFp( object ): + """Elliptic Curve over the field of integers modulo a prime.""" + def __init__( self, p, a, b ): + """The curve of points satisfying y^2 = x^3 + a*x + b (mod p).""" + self.__p = p + self.__a = a + self.__b = b + + def p( self ): + return self.__p + + def a( self ): + return self.__a + + def b( self ): + return self.__b + + def contains_point( self, x, y ): + """Is the point (x,y) on this curve?""" + return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0 + + + +class Point( object ): + """A point on an elliptic curve. Altering x and y is forbidding, + but they can be read by the x() and y() methods.""" + def __init__( self, curve, x, y, order = None ): + """curve, x, y, order; order (optional) is the order of this point.""" + self.__curve = curve + self.__x = x + self.__y = y + self.__order = order + # self.curve is allowed to be None only for INFINITY: + if self.__curve: assert self.__curve.contains_point( x, y ) + if order: assert self * order == INFINITY + + def __eq__( self, other ): + """Return True if the points are identical, False otherwise.""" + if self.__curve == other.__curve \ + and self.__x == other.__x \ + and self.__y == other.__y: + return True + else: + return False + + def __add__( self, other ): + """Add one point to another point.""" + + # X9.62 B.3: + + if other == INFINITY: return self + if self == INFINITY: return other + assert self.__curve == other.__curve + if self.__x == other.__x: + if ( self.__y + other.__y ) % self.__curve.p() == 0: + return INFINITY + else: + return self.double() + + p = self.__curve.p() + + l = ( ( other.__y - self.__y ) * \ + numbertheory.inverse_mod( other.__x - self.__x, p ) ) % p + + x3 = ( l * l - self.__x - other.__x ) % p + y3 = ( l * ( self.__x - x3 ) - self.__y ) % p + + return Point( self.__curve, x3, y3 ) + + def __mul__( self, other ): + """Multiply a point by an integer.""" + + def leftmost_bit( x ): + assert x > 0 + result = 1 + while result <= x: result = 2 * result + return result // 2 + + e = other + if self.__order: e = e % self.__order + if e == 0: return INFINITY + if self == INFINITY: return INFINITY + assert e > 0 + + # From X9.62 D.3.2: + + e3 = 3 * e + negative_self = Point( self.__curve, self.__x, -self.__y, self.__order ) + i = leftmost_bit( e3 ) // 2 + result = self + # print_("Multiplying %s by %d (e3 = %d):" % ( self, other, e3 )) + while i > 1: + result = result.double() + if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self + if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self + # print_(". . . i = %d, result = %s" % ( i, result )) + i = i // 2 + + return result + + def __rmul__( self, other ): + """Multiply a point by an integer.""" + + return self * other + + def __str__( self ): + if self == INFINITY: return "infinity" + return "(%d,%d)" % ( self.__x, self.__y ) + + def double( self ): + """Return a new point that is twice the old.""" + + if self == INFINITY: + return INFINITY + + # X9.62 B.3: + + p = self.__curve.p() + a = self.__curve.a() + + l = ( ( 3 * self.__x * self.__x + a ) * \ + numbertheory.inverse_mod( 2 * self.__y, p ) ) % p + + x3 = ( l * l - 2 * self.__x ) % p + y3 = ( l * ( self.__x - x3 ) - self.__y ) % p + + return Point( self.__curve, x3, y3 ) + + def x( self ): + return self.__x + + def y( self ): + return self.__y + + def curve( self ): + return self.__curve + + def order( self ): + return self.__order + + +# This one point is the Point At Infinity for all purposes: +INFINITY = Point( None, None, None ) + +def __main__(): + + class FailedTest(Exception): pass + def test_add( c, x1, y1, x2, y2, x3, y3 ): + """We expect that on curve c, (x1,y1) + (x2, y2 ) = (x3, y3).""" + p1 = Point( c, x1, y1 ) + p2 = Point( c, x2, y2 ) + p3 = p1 + p2 + print_("%s + %s = %s" % ( p1, p2, p3 ), end=' ') + if p3.x() != x3 or p3.y() != y3: + raise FailedTest("Failure: should give (%d,%d)." % ( x3, y3 )) + else: + print_(" Good.") + + def test_double( c, x1, y1, x3, y3 ): + """We expect that on curve c, 2*(x1,y1) = (x3, y3).""" + p1 = Point( c, x1, y1 ) + p3 = p1.double() + print_("%s doubled = %s" % ( p1, p3 ), end=' ') + if p3.x() != x3 or p3.y() != y3: + raise FailedTest("Failure: should give (%d,%d)." % ( x3, y3 )) + else: + print_(" Good.") + + def test_double_infinity( c ): + """We expect that on curve c, 2*INFINITY = INFINITY.""" + p1 = INFINITY + p3 = p1.double() + print_("%s doubled = %s" % ( p1, p3 ), end=' ') + if p3.x() != INFINITY.x() or p3.y() != INFINITY.y(): + raise FailedTest("Failure: should give (%d,%d)." % ( INFINITY.x(), INFINITY.y() )) + else: + print_(" Good.") + + def test_multiply( c, x1, y1, m, x3, y3 ): + """We expect that on curve c, m*(x1,y1) = (x3,y3).""" + p1 = Point( c, x1, y1 ) + p3 = p1 * m + print_("%s * %d = %s" % ( p1, m, p3 ), end=' ') + if p3.x() != x3 or p3.y() != y3: + raise FailedTest("Failure: should give (%d,%d)." % ( x3, y3 )) + else: + print_(" Good.") + + + # A few tests from X9.62 B.3: + + c = CurveFp( 23, 1, 1 ) + test_add( c, 3, 10, 9, 7, 17, 20 ) + test_double( c, 3, 10, 7, 12 ) + test_add( c, 3, 10, 3, 10, 7, 12 ) # (Should just invoke double.) + test_multiply( c, 3, 10, 2, 7, 12 ) + + test_double_infinity(c) + + # From X9.62 I.1 (p. 96): + + g = Point( c, 13, 7, 7 ) + + check = INFINITY + for i in range( 7 + 1 ): + p = ( i % 7 ) * g + print_("%s * %d = %s, expected %s . . ." % ( g, i, p, check ), end=' ') + if p == check: + print_(" Good.") + else: + raise FailedTest("Bad.") + check = check + g + + # NIST Curve P-192: + p = 6277101735386680763835789423207666416083908700390324961279 + r = 6277101735386680763835789423176059013767194773182842284081 + #s = 0x3045ae6fc8422f64ed579528d38120eae12196d5L + c = 0x3099d2bbbfcb2538542dcd5fb078b6ef5f3d6fe2c745de65 + b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1 + Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 + Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 + + c192 = CurveFp( p, -3, b ) + p192 = Point( c192, Gx, Gy, r ) + + # Checking against some sample computations presented + # in X9.62: + + d = 651056770906015076056810763456358567190100156695615665659 + Q = d * p192 + if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: + raise FailedTest("p192 * d came out wrong.") + else: + print_("p192 * d came out right.") + + k = 6140507067065001063065065565667405560006161556565665656654 + R = k * p192 + if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + raise FailedTest("k * p192 came out wrong.") + else: + print_("k * p192 came out right.") + + u1 = 2563697409189434185194736134579731015366492496392189760599 + u2 = 6266643813348617967186477710235785849136406323338782220568 + temp = u1 * p192 + u2 * Q + if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + raise FailedTest("u1 * p192 + u2 * Q came out wrong.") + else: + print_("u1 * p192 + u2 * Q came out right.") + +if __name__ == "__main__": + __main__() diff --git a/ecdsa/keys.py b/ecdsa/keys.py new file mode 100644 index 0000000..5b52125 --- /dev/null +++ b/ecdsa/keys.py @@ -0,0 +1,283 @@ +import binascii + +from . import ecdsa +from . import der +from . import rfc6979 +from .curves import NIST192p, find_curve +from .util import string_to_number, number_to_string, randrange +from .util import sigencode_string, sigdecode_string +from .util import oid_ecPublicKey, encoded_oid_ecPublicKey +from .six import PY3, b +from hashlib import sha1 + +class BadSignatureError(Exception): + pass +class BadDigestError(Exception): + pass + +class VerifyingKey: + def __init__(self, _error__please_use_generate=None): + if not _error__please_use_generate: + raise TypeError("Please use SigningKey.generate() to construct me") + + @classmethod + def from_public_point(klass, point, curve=NIST192p, hashfunc=sha1): + self = klass(_error__please_use_generate=True) + self.curve = curve + self.default_hashfunc = hashfunc + self.pubkey = ecdsa.Public_key(curve.generator, point) + self.pubkey.order = curve.order + return self + + @classmethod + def from_string(klass, string, curve=NIST192p, hashfunc=sha1, + validate_point=True): + order = curve.order + assert len(string) == curve.verifying_key_length, \ + (len(string), curve.verifying_key_length) + xs = string[:curve.baselen] + ys = string[curve.baselen:] + assert len(xs) == curve.baselen, (len(xs), curve.baselen) + assert len(ys) == curve.baselen, (len(ys), curve.baselen) + x = string_to_number(xs) + y = string_to_number(ys) + if validate_point: + assert ecdsa.point_is_valid(curve.generator, x, y) + from . import ellipticcurve + point = ellipticcurve.Point(curve.curve, x, y, order) + return klass.from_public_point(point, curve, hashfunc) + + @classmethod + def from_pem(klass, string): + return klass.from_der(der.unpem(string)) + + @classmethod + def from_der(klass, string): + # [[oid_ecPublicKey,oid_curve], point_str_bitstring] + s1,empty = der.remove_sequence(string) + if empty != b(""): + raise der.UnexpectedDER("trailing junk after DER pubkey: %s" % + binascii.hexlify(empty)) + s2,point_str_bitstring = der.remove_sequence(s1) + # s2 = oid_ecPublicKey,oid_curve + oid_pk, rest = der.remove_object(s2) + oid_curve, empty = der.remove_object(rest) + if empty != b(""): + raise der.UnexpectedDER("trailing junk after DER pubkey objects: %s" % + binascii.hexlify(empty)) + assert oid_pk == oid_ecPublicKey, (oid_pk, oid_ecPublicKey) + curve = find_curve(oid_curve) + point_str, empty = der.remove_bitstring(point_str_bitstring) + if empty != b(""): + raise der.UnexpectedDER("trailing junk after pubkey pointstring: %s" % + binascii.hexlify(empty)) + assert point_str.startswith(b("\x00\x04")) + return klass.from_string(point_str[2:], curve) + + def to_string(self): + # VerifyingKey.from_string(vk.to_string()) == vk as long as the + # curves are the same: the curve itself is not included in the + # serialized form + order = self.pubkey.order + x_str = number_to_string(self.pubkey.point.x(), order) + y_str = number_to_string(self.pubkey.point.y(), order) + return x_str + y_str + + def to_pem(self): + return der.topem(self.to_der(), "PUBLIC KEY") + + def to_der(self): + order = self.pubkey.order + x_str = number_to_string(self.pubkey.point.x(), order) + y_str = number_to_string(self.pubkey.point.y(), order) + point_str = b("\x00\x04") + x_str + y_str + return der.encode_sequence(der.encode_sequence(encoded_oid_ecPublicKey, + self.curve.encoded_oid), + der.encode_bitstring(point_str)) + + def verify(self, signature, data, hashfunc=None, sigdecode=sigdecode_string): + hashfunc = hashfunc or self.default_hashfunc + digest = hashfunc(data).digest() + return self.verify_digest(signature, digest, sigdecode) + + def verify_digest(self, signature, digest, sigdecode=sigdecode_string): + if len(digest) > self.curve.baselen: + raise BadDigestError("this curve (%s) is too short " + "for your digest (%d)" % (self.curve.name, + 8*len(digest))) + number = string_to_number(digest) + r, s = sigdecode(signature, self.pubkey.order) + sig = ecdsa.Signature(r, s) + if self.pubkey.verifies(number, sig): + return True + raise BadSignatureError + +class SigningKey: + def __init__(self, _error__please_use_generate=None): + if not _error__please_use_generate: + raise TypeError("Please use SigningKey.generate() to construct me") + + @classmethod + def generate(klass, curve=NIST192p, entropy=None, hashfunc=sha1): + secexp = randrange(curve.order, entropy) + return klass.from_secret_exponent(secexp, curve, hashfunc) + + # to create a signing key from a short (arbitrary-length) seed, convert + # that seed into an integer with something like + # secexp=util.randrange_from_seed__X(seed, curve.order), and then pass + # that integer into SigningKey.from_secret_exponent(secexp, curve) + + @classmethod + def from_secret_exponent(klass, secexp, curve=NIST192p, hashfunc=sha1): + self = klass(_error__please_use_generate=True) + self.curve = curve + self.default_hashfunc = hashfunc + self.baselen = curve.baselen + n = curve.order + assert 1 <= secexp < n + pubkey_point = curve.generator*secexp + pubkey = ecdsa.Public_key(curve.generator, pubkey_point) + pubkey.order = n + self.verifying_key = VerifyingKey.from_public_point(pubkey_point, curve, + hashfunc) + self.privkey = ecdsa.Private_key(pubkey, secexp) + self.privkey.order = n + return self + + @classmethod + def from_string(klass, string, curve=NIST192p, hashfunc=sha1): + assert len(string) == curve.baselen, (len(string), curve.baselen) + secexp = string_to_number(string) + return klass.from_secret_exponent(secexp, curve, hashfunc) + + @classmethod + def from_pem(klass, string, hashfunc=sha1): + # the privkey pem file has two sections: "EC PARAMETERS" and "EC + # PRIVATE KEY". The first is redundant. + if PY3 and isinstance(string, str): + string = string.encode() + privkey_pem = string[string.index(b("-----BEGIN EC PRIVATE KEY-----")):] + return klass.from_der(der.unpem(privkey_pem), hashfunc) + @classmethod + def from_der(klass, string, hashfunc=sha1): + # SEQ([int(1), octetstring(privkey),cont[0], oid(secp224r1), + # cont[1],bitstring]) + s, empty = der.remove_sequence(string) + if empty != b(""): + raise der.UnexpectedDER("trailing junk after DER privkey: %s" % + binascii.hexlify(empty)) + one, s = der.remove_integer(s) + if one != 1: + raise der.UnexpectedDER("expected '1' at start of DER privkey," + " got %d" % one) + privkey_str, s = der.remove_octet_string(s) + tag, curve_oid_str, s = der.remove_constructed(s) + if tag != 0: + raise der.UnexpectedDER("expected tag 0 in DER privkey," + " got %d" % tag) + curve_oid, empty = der.remove_object(curve_oid_str) + if empty != b(""): + raise der.UnexpectedDER("trailing junk after DER privkey " + "curve_oid: %s" % binascii.hexlify(empty)) + curve = find_curve(curve_oid) + + # we don't actually care about the following fields + # + #tag, pubkey_bitstring, s = der.remove_constructed(s) + #if tag != 1: + # raise der.UnexpectedDER("expected tag 1 in DER privkey, got %d" + # % tag) + #pubkey_str = der.remove_bitstring(pubkey_bitstring) + #if empty != "": + # raise der.UnexpectedDER("trailing junk after DER privkey " + # "pubkeystr: %s" % binascii.hexlify(empty)) + + # our from_string method likes fixed-length privkey strings + if len(privkey_str) < curve.baselen: + privkey_str = b("\x00")*(curve.baselen-len(privkey_str)) + privkey_str + return klass.from_string(privkey_str, curve, hashfunc) + + def to_string(self): + secexp = self.privkey.secret_multiplier + s = number_to_string(secexp, self.privkey.order) + return s + + def to_pem(self): + # TODO: "BEGIN ECPARAMETERS" + return der.topem(self.to_der(), "EC PRIVATE KEY") + + def to_der(self): + # SEQ([int(1), octetstring(privkey),cont[0], oid(secp224r1), + # cont[1],bitstring]) + encoded_vk = b("\x00\x04") + self.get_verifying_key().to_string() + return der.encode_sequence(der.encode_integer(1), + der.encode_octet_string(self.to_string()), + der.encode_constructed(0, self.curve.encoded_oid), + der.encode_constructed(1, der.encode_bitstring(encoded_vk)), + ) + + def get_verifying_key(self): + return self.verifying_key + + def sign_deterministic(self, data, hashfunc=None, sigencode=sigencode_string): + hashfunc = hashfunc or self.default_hashfunc + digest = hashfunc(data).digest() + + return self.sign_digest_deterministic(digest, hashfunc=hashfunc, sigencode=sigencode) + + def sign_digest_deterministic(self, digest, hashfunc=None, sigencode=sigencode_string): + """ + Calculates 'k' from data itself, removing the need for strong + random generator and producing deterministic (reproducible) signatures. + See RFC 6979 for more details. + """ + secexp = self.privkey.secret_multiplier + k = rfc6979.generate_k( + self.curve.generator.order(), secexp, hashfunc, digest) + + return self.sign_digest(digest, sigencode=sigencode, k=k) + + def sign(self, data, entropy=None, hashfunc=None, sigencode=sigencode_string, k=None): + """ + hashfunc= should behave like hashlib.sha1 . The output length of the + hash (in bytes) must not be longer than the length of the curve order + (rounded up to the nearest byte), so using SHA256 with nist256p is + ok, but SHA256 with nist192p is not. (In the 2**-96ish unlikely event + of a hash output larger than the curve order, the hash will + effectively be wrapped mod n). + + Use hashfunc=hashlib.sha1 to match openssl's -ecdsa-with-SHA1 mode, + or hashfunc=hashlib.sha256 for openssl-1.0.0's -ecdsa-with-SHA256. + """ + + hashfunc = hashfunc or self.default_hashfunc + h = hashfunc(data).digest() + return self.sign_digest(h, entropy, sigencode, k) + + def sign_digest(self, digest, entropy=None, sigencode=sigencode_string, k=None): + if len(digest) > self.curve.baselen: + raise BadDigestError("this curve (%s) is too short " + "for your digest (%d)" % (self.curve.name, + 8*len(digest))) + number = string_to_number(digest) + r, s = self.sign_number(number, entropy, k) + return sigencode(r, s, self.privkey.order) + + def sign_number(self, number, entropy=None, k=None): + # returns a pair of numbers + order = self.privkey.order + # privkey.sign() may raise RuntimeError in the amazingly unlikely + # (2**-192) event that r=0 or s=0, because that would leak the key. + # We could re-try with a different 'k', but we couldn't test that + # code, so I choose to allow the signature to fail instead. + + # If k is set, it is used directly. In other cases + # it is generated using entropy function + if k is not None: + _k = k + else: + _k = randrange(order, entropy) + + assert 1 <= _k < order + sig = self.privkey.sign(number, _k) + return sig.r, sig.s diff --git a/ecdsa/numbertheory.py b/ecdsa/numbertheory.py new file mode 100644 index 0000000..7ace0dc --- /dev/null +++ b/ecdsa/numbertheory.py @@ -0,0 +1,613 @@ +#! /usr/bin/env python +# +# Provide some simple capabilities from number theory. +# +# Version of 2008.11.14. +# +# Written in 2005 and 2006 by Peter Pearson and placed in the public domain. +# Revision history: +# 2008.11.14: Use pow( base, exponent, modulus ) for modular_exp. +# Make gcd and lcm accept arbitrarly many arguments. + +from __future__ import division + +from .six import print_, integer_types +from .six.moves import reduce + +import math + + +class Error( Exception ): + """Base class for exceptions in this module.""" + pass + +class SquareRootError( Error ): + pass + +class NegativeExponentError( Error ): + pass + + +def modular_exp( base, exponent, modulus ): + "Raise base to exponent, reducing by modulus" + if exponent < 0: + raise NegativeExponentError( "Negative exponents (%d) not allowed" \ + % exponent ) + return pow( base, exponent, modulus ) +# result = 1L +# x = exponent +# b = base + 0L +# while x > 0: +# if x % 2 > 0: result = (result * b) % modulus +# x = x // 2 +# b = ( b * b ) % modulus +# return result + + +def polynomial_reduce_mod( poly, polymod, p ): + """Reduce poly by polymod, integer arithmetic modulo p. + + Polynomials are represented as lists of coefficients + of increasing powers of x.""" + + # This module has been tested only by extensive use + # in calculating modular square roots. + + # Just to make this easy, require a monic polynomial: + assert polymod[-1] == 1 + + assert len( polymod ) > 1 + + while len( poly ) >= len( polymod ): + if poly[-1] != 0: + for i in range( 2, len( polymod ) + 1 ): + poly[-i] = ( poly[-i] - poly[-1] * polymod[-i] ) % p + poly = poly[0:-1] + + return poly + + + +def polynomial_multiply_mod( m1, m2, polymod, p ): + """Polynomial multiplication modulo a polynomial over ints mod p. + + Polynomials are represented as lists of coefficients + of increasing powers of x.""" + + # This is just a seat-of-the-pants implementation. + + # This module has been tested only by extensive use + # in calculating modular square roots. + + # Initialize the product to zero: + + prod = ( len( m1 ) + len( m2 ) - 1 ) * [0] + + # Add together all the cross-terms: + + for i in range( len( m1 ) ): + for j in range( len( m2 ) ): + prod[i+j] = ( prod[i+j] + m1[i] * m2[j] ) % p + + return polynomial_reduce_mod( prod, polymod, p ) + + +def polynomial_exp_mod( base, exponent, polymod, p ): + """Polynomial exponentiation modulo a polynomial over ints mod p. + + Polynomials are represented as lists of coefficients + of increasing powers of x.""" + + # Based on the Handbook of Applied Cryptography, algorithm 2.227. + + # This module has been tested only by extensive use + # in calculating modular square roots. + + assert exponent < p + + if exponent == 0: return [ 1 ] + + G = base + k = exponent + if k%2 == 1: s = G + else: s = [ 1 ] + + while k > 1: + k = k // 2 + G = polynomial_multiply_mod( G, G, polymod, p ) + if k%2 == 1: s = polynomial_multiply_mod( G, s, polymod, p ) + + return s + + + +def jacobi( a, n ): + """Jacobi symbol""" + + # Based on the Handbook of Applied Cryptography (HAC), algorithm 2.149. + + # This function has been tested by comparison with a small + # table printed in HAC, and by extensive use in calculating + # modular square roots. + + assert n >= 3 + assert n%2 == 1 + a = a % n + if a == 0: return 0 + if a == 1: return 1 + a1, e = a, 0 + while a1%2 == 0: + a1, e = a1//2, e+1 + if e%2 == 0 or n%8 == 1 or n%8 == 7: s = 1 + else: s = -1 + if a1 == 1: return s + if n%4 == 3 and a1%4 == 3: s = -s + return s * jacobi( n % a1, a1 ) + + + +def square_root_mod_prime( a, p ): + """Modular square root of a, mod p, p prime.""" + + # Based on the Handbook of Applied Cryptography, algorithms 3.34 to 3.39. + + # This module has been tested for all values in [0,p-1] for + # every prime p from 3 to 1229. + + assert 0 <= a < p + assert 1 < p + + if a == 0: return 0 + if p == 2: return a + + jac = jacobi( a, p ) + if jac == -1: raise SquareRootError( "%d has no square root modulo %d" \ + % ( a, p ) ) + + if p % 4 == 3: return modular_exp( a, (p+1)//4, p ) + + if p % 8 == 5: + d = modular_exp( a, (p-1)//4, p ) + if d == 1: return modular_exp( a, (p+3)//8, p ) + if d == p-1: return ( 2 * a * modular_exp( 4*a, (p-5)//8, p ) ) % p + raise RuntimeError("Shouldn't get here.") + + for b in range( 2, p ): + if jacobi( b*b-4*a, p ) == -1: + f = ( a, -b, 1 ) + ff = polynomial_exp_mod( ( 0, 1 ), (p+1)//2, f, p ) + assert ff[1] == 0 + return ff[0] + raise RuntimeError("No b found.") + + + +def inverse_mod( a, m ): + """Inverse of a mod m.""" + + if a < 0 or m <= a: a = a % m + + # From Ferguson and Schneier, roughly: + + c, d = a, m + uc, vc, ud, vd = 1, 0, 0, 1 + while c != 0: + q, c, d = divmod( d, c ) + ( c, ) + uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc + + # At this point, d is the GCD, and ud*a+vd*m = d. + # If d == 1, this means that ud is a inverse. + + assert d == 1 + if ud > 0: return ud + else: return ud + m + + +def gcd2(a, b): + """Greatest common divisor using Euclid's algorithm.""" + while a: + a, b = b%a, a + return b + + +def gcd( *a ): + """Greatest common divisor. + + Usage: gcd( [ 2, 4, 6 ] ) + or: gcd( 2, 4, 6 ) + """ + + if len( a ) > 1: return reduce( gcd2, a ) + if hasattr( a[0], "__iter__" ): return reduce( gcd2, a[0] ) + return a[0] + + +def lcm2(a,b): + """Least common multiple of two integers.""" + + return (a*b)//gcd(a,b) + + +def lcm( *a ): + """Least common multiple. + + Usage: lcm( [ 3, 4, 5 ] ) + or: lcm( 3, 4, 5 ) + """ + + if len( a ) > 1: return reduce( lcm2, a ) + if hasattr( a[0], "__iter__" ): return reduce( lcm2, a[0] ) + return a[0] + + + +def factorization( n ): + """Decompose n into a list of (prime,exponent) pairs.""" + + assert isinstance( n, integer_types ) + + if n < 2: return [] + + result = [] + d = 2 + + # Test the small primes: + + for d in smallprimes: + if d > n: break + q, r = divmod( n, d ) + if r == 0: + count = 1 + while d <= n: + n = q + q, r = divmod( n, d ) + if r != 0: break + count = count + 1 + result.append( ( d, count ) ) + + # If n is still greater than the last of our small primes, + # it may require further work: + + if n > smallprimes[-1]: + if is_prime( n ): # If what's left is prime, it's easy: + result.append( ( n, 1 ) ) + else: # Ugh. Search stupidly for a divisor: + d = smallprimes[-1] + while 1: + d = d + 2 # Try the next divisor. + q, r = divmod( n, d ) + if q < d: break # n < d*d means we're done, n = 1 or prime. + if r == 0: # d divides n. How many times? + count = 1 + n = q + while d <= n: # As long as d might still divide n, + q, r = divmod( n, d ) # see if it does. + if r != 0: break + n = q # It does. Reduce n, increase count. + count = count + 1 + result.append( ( d, count ) ) + if n > 1: result.append( ( n, 1 ) ) + + return result + + + +def phi( n ): + """Return the Euler totient function of n.""" + + assert isinstance( n, integer_types ) + + if n < 3: return 1 + + result = 1 + ff = factorization( n ) + for f in ff: + e = f[1] + if e > 1: + result = result * f[0] ** (e-1) * ( f[0] - 1 ) + else: + result = result * ( f[0] - 1 ) + return result + + +def carmichael( n ): + """Return Carmichael function of n. + + Carmichael(n) is the smallest integer x such that + m**x = 1 mod n for all m relatively prime to n. + """ + + return carmichael_of_factorized( factorization( n ) ) + + +def carmichael_of_factorized( f_list ): + """Return the Carmichael function of a number that is + represented as a list of (prime,exponent) pairs. + """ + + if len( f_list ) < 1: return 1 + + result = carmichael_of_ppower( f_list[0] ) + for i in range( 1, len( f_list ) ): + result = lcm( result, carmichael_of_ppower( f_list[i] ) ) + + return result + +def carmichael_of_ppower( pp ): + """Carmichael function of the given power of the given prime. + """ + + p, a = pp + if p == 2 and a > 2: return 2**(a-2) + else: return (p-1) * p**(a-1) + + + +def order_mod( x, m ): + """Return the order of x in the multiplicative group mod m. + """ + + # Warning: this implementation is not very clever, and will + # take a long time if m is very large. + + if m <= 1: return 0 + + assert gcd( x, m ) == 1 + + z = x + result = 1 + while z != 1: + z = ( z * x ) % m + result = result + 1 + return result + + +def largest_factor_relatively_prime( a, b ): + """Return the largest factor of a relatively prime to b. + """ + + while 1: + d = gcd( a, b ) + if d <= 1: break + b = d + while 1: + q, r = divmod( a, d ) + if r > 0: + break + a = q + return a + + +def kinda_order_mod( x, m ): + """Return the order of x in the multiplicative group mod m', + where m' is the largest factor of m relatively prime to x. + """ + + return order_mod( x, largest_factor_relatively_prime( m, x ) ) + + +def is_prime( n ): + """Return True if x is prime, False otherwise. + + We use the Miller-Rabin test, as given in Menezes et al. p. 138. + This test is not exact: there are composite values n for which + it returns True. + + In testing the odd numbers from 10000001 to 19999999, + about 66 composites got past the first test, + 5 got past the second test, and none got past the third. + Since factors of 2, 3, 5, 7, and 11 were detected during + preliminary screening, the number of numbers tested by + Miller-Rabin was (19999999 - 10000001)*(2/3)*(4/5)*(6/7) + = 4.57 million. + """ + + # (This is used to study the risk of false positives:) + global miller_rabin_test_count + + miller_rabin_test_count = 0 + + if n <= smallprimes[-1]: + if n in smallprimes: return True + else: return False + + if gcd( n, 2*3*5*7*11 ) != 1: return False + + # Choose a number of iterations sufficient to reduce the + # probability of accepting a composite below 2**-80 + # (from Menezes et al. Table 4.4): + + t = 40 + n_bits = 1 + int( math.log( n, 2 ) ) + for k, tt in ( ( 100, 27 ), + ( 150, 18 ), + ( 200, 15 ), + ( 250, 12 ), + ( 300, 9 ), + ( 350, 8 ), + ( 400, 7 ), + ( 450, 6 ), + ( 550, 5 ), + ( 650, 4 ), + ( 850, 3 ), + ( 1300, 2 ), + ): + if n_bits < k: break + t = tt + + # Run the test t times: + + s = 0 + r = n - 1 + while ( r % 2 ) == 0: + s = s + 1 + r = r // 2 + for i in range( t ): + a = smallprimes[ i ] + y = modular_exp( a, r, n ) + if y != 1 and y != n-1: + j = 1 + while j <= s - 1 and y != n - 1: + y = modular_exp( y, 2, n ) + if y == 1: + miller_rabin_test_count = i + 1 + return False + j = j + 1 + if y != n-1: + miller_rabin_test_count = i + 1 + return False + return True + + +def next_prime( starting_value ): + "Return the smallest prime larger than the starting value." + + if starting_value < 2: return 2 + result = ( starting_value + 1 ) | 1 + while not is_prime( result ): result = result + 2 + return result + + +smallprimes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, + 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, + 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, + 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, + 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, + 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, + 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, + 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, + 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, + 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, + 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, + 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, + 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, + 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, + 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, + 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, + 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, + 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, + 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, + 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229] + +miller_rabin_test_count = 0 + +def __main__(): + + # Making sure locally defined exceptions work: + # p = modular_exp( 2, -2, 3 ) + # p = square_root_mod_prime( 2, 3 ) + + + print_("Testing gcd...") + assert gcd( 3*5*7, 3*5*11, 3*5*13 ) == 3*5 + assert gcd( [ 3*5*7, 3*5*11, 3*5*13 ] ) == 3*5 + assert gcd( 3 ) == 3 + + print_("Testing lcm...") + assert lcm( 3, 5*3, 7*3 ) == 3*5*7 + assert lcm( [ 3, 5*3, 7*3 ] ) == 3*5*7 + assert lcm( 3 ) == 3 + + print_("Testing next_prime...") + bigprimes = ( 999671, + 999683, + 999721, + 999727, + 999749, + 999763, + 999769, + 999773, + 999809, + 999853, + 999863, + 999883, + 999907, + 999917, + 999931, + 999953, + 999959, + 999961, + 999979, + 999983 ) + + for i in range( len( bigprimes ) - 1 ): + assert next_prime( bigprimes[i] ) == bigprimes[ i+1 ] + + error_tally = 0 + + # Test the square_root_mod_prime function: + + for p in smallprimes: + print_("Testing square_root_mod_prime for modulus p = %d." % p) + squares = [] + + for root in range( 0, 1+p//2 ): + sq = ( root * root ) % p + squares.append( sq ) + calculated = square_root_mod_prime( sq, p ) + if ( calculated * calculated ) % p != sq: + error_tally = error_tally + 1 + print_("Failed to find %d as sqrt( %d ) mod %d. Said %d." % \ + ( root, sq, p, calculated )) + + for nonsquare in range( 0, p ): + if nonsquare not in squares: + try: + calculated = square_root_mod_prime( nonsquare, p ) + except SquareRootError: + pass + else: + error_tally = error_tally + 1 + print_("Failed to report no root for sqrt( %d ) mod %d." % \ + ( nonsquare, p )) + + # Test the jacobi function: + for m in range( 3, 400, 2 ): + print_("Testing jacobi for modulus m = %d." % m) + if is_prime( m ): + squares = [] + for root in range( 1, m ): + if jacobi( root * root, m ) != 1: + error_tally = error_tally + 1 + print_("jacobi( %d * %d, %d ) != 1" % ( root, root, m )) + squares.append( root * root % m ) + for i in range( 1, m ): + if not i in squares: + if jacobi( i, m ) != -1: + error_tally = error_tally + 1 + print_("jacobi( %d, %d ) != -1" % ( i, m )) + else: # m is not prime. + f = factorization( m ) + for a in range( 1, m ): + c = 1 + for i in f: + c = c * jacobi( a, i[0] ) ** i[1] + if c != jacobi( a, m ): + error_tally = error_tally + 1 + print_("%d != jacobi( %d, %d )" % ( c, a, m )) + + +# Test the inverse_mod function: + print_("Testing inverse_mod . . .") + import random + n_tests = 0 + for i in range( 100 ): + m = random.randint( 20, 10000 ) + for j in range( 100 ): + a = random.randint( 1, m-1 ) + if gcd( a, m ) == 1: + n_tests = n_tests + 1 + inv = inverse_mod( a, m ) + if inv <= 0 or inv >= m or ( a * inv ) % m != 1: + error_tally = error_tally + 1 + print_("%d = inverse_mod( %d, %d ) is wrong." % ( inv, a, m )) + assert n_tests > 1000 + print_(n_tests, " tests of inverse_mod completed.") + + class FailedTest(Exception): pass + print_(error_tally, "errors detected.") + if error_tally != 0: + raise FailedTest("%d errors detected" % error_tally) + +if __name__ == '__main__': + __main__() diff --git a/ecdsa/rfc6979.py b/ecdsa/rfc6979.py new file mode 100644 index 0000000..6190ebd --- /dev/null +++ b/ecdsa/rfc6979.py @@ -0,0 +1,103 @@ +''' +RFC 6979: + Deterministic Usage of the Digital Signature Algorithm (DSA) and + Elliptic Curve Digital Signature Algorithm (ECDSA) + + http://tools.ietf.org/html/rfc6979 + +Many thanks to Coda Hale for his implementation in Go language: + https://github.com/codahale/rfc6979 +''' + +import hmac +from binascii import hexlify +from .util import number_to_string, number_to_string_crop +from .six import b + +try: + bin(0) +except NameError: + binmap = {"0": "0000", "1": "0001", "2": "0010", "3": "0011", + "4": "0100", "5": "0101", "6": "0110", "7": "0111", + "8": "1000", "9": "1001", "a": "1010", "b": "1011", + "c": "1100", "d": "1101", "e": "1110", "f": "1111"} + def bin(value): # for python2.5 + v = "".join(binmap[x] for x in "%x"%abs(value)).lstrip("0") + if value < 0: + return "-0b" + v + return "0b" + v + +def bit_length(num): + # http://docs.python.org/dev/library/stdtypes.html#int.bit_length + s = bin(num) # binary representation: bin(-37) --> '-0b100101' + s = s.lstrip('-0b') # remove leading zeros and minus sign + return len(s) # len('100101') --> 6 + +def bits2int(data, qlen): + x = int(hexlify(data), 16) + l = len(data) * 8 + + if l > qlen: + return x >> (l-qlen) + return x + +def bits2octets(data, order): + z1 = bits2int(data, bit_length(order)) + z2 = z1 - order + + if z2 < 0: + z2 = z1 + + return number_to_string_crop(z2, order) + +# https://tools.ietf.org/html/rfc6979#section-3.2 +def generate_k(order, secexp, hash_func, data): + ''' + order - order of the DSA generator used in the signature + secexp - secure exponent (private key) in numeric form + hash_func - reference to the same hash function used for generating hash + data - hash in binary form of the signing data + ''' + + qlen = bit_length(order) + holen = hash_func().digest_size + rolen = (qlen + 7) / 8 + bx = number_to_string(secexp, order) + bits2octets(data, order) + + # Step B + v = b('\x01') * holen + + # Step C + k = b('\x00') * holen + + # Step D + + k = hmac.new(k, v+b('\x00')+bx, hash_func).digest() + + # Step E + v = hmac.new(k, v, hash_func).digest() + + # Step F + k = hmac.new(k, v+b('\x01')+bx, hash_func).digest() + + # Step G + v = hmac.new(k, v, hash_func).digest() + + # Step H + while True: + # Step H1 + t = b('') + + # Step H2 + while len(t) < rolen: + v = hmac.new(k, v, hash_func).digest() + t += v + + # Step H3 + secret = bits2int(t, qlen) + + if secret >= 1 and secret < order: + return secret + + k = hmac.new(k, v+b('\x00'), hash_func).digest() + v = hmac.new(k, v, hash_func).digest() diff --git a/ecdsa/six.py b/ecdsa/six.py new file mode 100644 index 0000000..3003f8f --- /dev/null +++ b/ecdsa/six.py @@ -0,0 +1,394 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +# Copyright (c) 2010-2012 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.2.0" + + +# True if we are running on Python 3. +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) + # This is a bit ugly, but it avoids running this again. + delattr(tp, self.name) + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + + +class _MovedItems(types.ModuleType): + """Lazy loading of moved objects""" + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("reload_module", "__builtin__", "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("winreg", "_winreg"), +] +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) +del attr + +moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_code = "__code__" + _func_defaults = "__defaults__" + + _iterkeys = "keys" + _itervalues = "values" + _iteritems = "items" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_code = "func_code" + _func_defaults = "func_defaults" + + _iterkeys = "iterkeys" + _itervalues = "itervalues" + _iteritems = "iteritems" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) + + +def iterkeys(d): + """Return an iterator over the keys of a dictionary.""" + return iter(getattr(d, _iterkeys)()) + +def itervalues(d): + """Return an iterator over the values of a dictionary.""" + return iter(getattr(d, _itervalues)()) + +def iteritems(d): + """Return an iterator over the (key, value) pairs of a dictionary.""" + return iter(getattr(d, _iteritems)()) + + +if PY3: + def b(s): + return s.encode("latin-1") + def u(s): + return s + if sys.version_info[1] <= 1: + def int2byte(i): + return bytes((i,)) + else: + # This is about 2x faster than the implementation above on 3.2+ + int2byte = operator.methodcaller("to_bytes", 1, "big") + import io + StringIO = io.StringIO + BytesIO = io.BytesIO +else: + def b(s): + return s + def u(s): + if isinstance(s, unicode): + return s + return unicode(s, "unicode_escape") + int2byte = chr + import StringIO + StringIO = BytesIO = StringIO.StringIO +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +if PY3: + import builtins + exec_ = getattr(builtins, "exec") + + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + + print_ = getattr(builtins, "print") + del builtins + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + + def print_(*args, **kwargs): + """The new-style print function.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + def write(data): + if not isinstance(data, basestring): + data = str(data) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + +_add_doc(reraise, """Reraise an exception.""") + + +def with_metaclass(meta, base=object): + """Create a base class with a metaclass.""" + return meta("NewBase", (base,), {}) diff --git a/ecdsa/test_pyecdsa.py b/ecdsa/test_pyecdsa.py new file mode 100644 index 0000000..326cac4 --- /dev/null +++ b/ecdsa/test_pyecdsa.py @@ -0,0 +1,663 @@ +from __future__ import with_statement, division + +import unittest +import os +import time +import shutil +import subprocess +from binascii import hexlify, unhexlify +from hashlib import sha1, sha256, sha512 + +from .six import b, print_, binary_type +from .keys import SigningKey, VerifyingKey +from .keys import BadSignatureError +from . import util +from .util import sigencode_der, sigencode_strings +from .util import sigdecode_der, sigdecode_strings +from .curves import Curve, UnknownCurveError +from .curves import NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1 +from .ellipticcurve import Point +from . import der +from . import rfc6979 + +class SubprocessError(Exception): + pass + +def run_openssl(cmd): + OPENSSL = "openssl" + p = subprocess.Popen([OPENSSL] + cmd.split(), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + stdout, ignored = p.communicate() + if p.returncode != 0: + raise SubprocessError("cmd '%s %s' failed: rc=%s, stdout/err was %s" % + (OPENSSL, cmd, p.returncode, stdout)) + return stdout.decode() + +BENCH = False + +class ECDSA(unittest.TestCase): + def test_basic(self): + priv = SigningKey.generate() + pub = priv.get_verifying_key() + + data = b("blahblah") + sig = priv.sign(data) + + self.assertTrue(pub.verify(sig, data)) + self.assertRaises(BadSignatureError, pub.verify, sig, data+b("bad")) + + pub2 = VerifyingKey.from_string(pub.to_string()) + self.assertTrue(pub2.verify(sig, data)) + + def test_deterministic(self): + data = b("blahblah") + secexp = int("9d0219792467d7d37b4d43298a7d0c05", 16) + + priv = SigningKey.from_secret_exponent(secexp, SECP256k1, sha256) + pub = priv.get_verifying_key() + + k = rfc6979.generate_k( + SECP256k1.generator.order(), secexp, sha256, sha256(data).digest()) + + sig1 = priv.sign(data, k=k) + self.assertTrue(pub.verify(sig1, data)) + + sig2 = priv.sign(data, k=k) + self.assertTrue(pub.verify(sig2, data)) + + sig3 = priv.sign_deterministic(data, sha256) + self.assertTrue(pub.verify(sig3, data)) + + self.assertEqual(sig1, sig2) + self.assertEqual(sig1, sig3) + + def test_bad_usage(self): + # sk=SigningKey() is wrong + self.assertRaises(TypeError, SigningKey) + self.assertRaises(TypeError, VerifyingKey) + + def test_lengths(self): + default = NIST192p + priv = SigningKey.generate() + pub = priv.get_verifying_key() + self.assertEqual(len(pub.to_string()), default.verifying_key_length) + sig = priv.sign(b("data")) + self.assertEqual(len(sig), default.signature_length) + if BENCH: + print_() + for curve in (NIST192p, NIST224p, NIST256p, NIST384p, NIST521p): + start = time.time() + priv = SigningKey.generate(curve=curve) + pub1 = priv.get_verifying_key() + keygen_time = time.time() - start + pub2 = VerifyingKey.from_string(pub1.to_string(), curve) + self.assertEqual(pub1.to_string(), pub2.to_string()) + self.assertEqual(len(pub1.to_string()), + curve.verifying_key_length) + start = time.time() + sig = priv.sign(b("data")) + sign_time = time.time() - start + self.assertEqual(len(sig), curve.signature_length) + if BENCH: + start = time.time() + pub1.verify(sig, b("data")) + verify_time = time.time() - start + print_("%s: siglen=%d, keygen=%0.3fs, sign=%0.3f, verify=%0.3f" \ + % (curve.name, curve.signature_length, + keygen_time, sign_time, verify_time)) + + def test_serialize(self): + seed = b("secret") + curve = NIST192p + secexp1 = util.randrange_from_seed__trytryagain(seed, curve.order) + secexp2 = util.randrange_from_seed__trytryagain(seed, curve.order) + self.assertEqual(secexp1, secexp2) + priv1 = SigningKey.from_secret_exponent(secexp1, curve) + priv2 = SigningKey.from_secret_exponent(secexp2, curve) + self.assertEqual(hexlify(priv1.to_string()), + hexlify(priv2.to_string())) + self.assertEqual(priv1.to_pem(), priv2.to_pem()) + pub1 = priv1.get_verifying_key() + pub2 = priv2.get_verifying_key() + data = b("data") + sig1 = priv1.sign(data) + sig2 = priv2.sign(data) + self.assertTrue(pub1.verify(sig1, data)) + self.assertTrue(pub2.verify(sig1, data)) + self.assertTrue(pub1.verify(sig2, data)) + self.assertTrue(pub2.verify(sig2, data)) + self.assertEqual(hexlify(pub1.to_string()), + hexlify(pub2.to_string())) + + def test_nonrandom(self): + s = b("all the entropy in the entire world, compressed into one line") + def not_much_entropy(numbytes): + return s[:numbytes] + # we control the entropy source, these two keys should be identical: + priv1 = SigningKey.generate(entropy=not_much_entropy) + priv2 = SigningKey.generate(entropy=not_much_entropy) + self.assertEqual(hexlify(priv1.get_verifying_key().to_string()), + hexlify(priv2.get_verifying_key().to_string())) + # likewise, signatures should be identical. Obviously you'd never + # want to do this with keys you care about, because the secrecy of + # the private key depends upon using different random numbers for + # each signature + sig1 = priv1.sign(b("data"), entropy=not_much_entropy) + sig2 = priv2.sign(b("data"), entropy=not_much_entropy) + self.assertEqual(hexlify(sig1), hexlify(sig2)) + + def assertTruePrivkeysEqual(self, priv1, priv2): + self.assertEqual(priv1.privkey.secret_multiplier, + priv2.privkey.secret_multiplier) + self.assertEqual(priv1.privkey.public_key.generator, + priv2.privkey.public_key.generator) + + def failIfPrivkeysEqual(self, priv1, priv2): + self.failIfEqual(priv1.privkey.secret_multiplier, + priv2.privkey.secret_multiplier) + + def test_privkey_creation(self): + s = b("all the entropy in the entire world, compressed into one line") + def not_much_entropy(numbytes): + return s[:numbytes] + priv1 = SigningKey.generate() + self.assertEqual(priv1.baselen, NIST192p.baselen) + + priv1 = SigningKey.generate(curve=NIST224p) + self.assertEqual(priv1.baselen, NIST224p.baselen) + + priv1 = SigningKey.generate(entropy=not_much_entropy) + self.assertEqual(priv1.baselen, NIST192p.baselen) + priv2 = SigningKey.generate(entropy=not_much_entropy) + self.assertEqual(priv2.baselen, NIST192p.baselen) + self.assertTruePrivkeysEqual(priv1, priv2) + + priv1 = SigningKey.from_secret_exponent(secexp=3) + self.assertEqual(priv1.baselen, NIST192p.baselen) + priv2 = SigningKey.from_secret_exponent(secexp=3) + self.assertTruePrivkeysEqual(priv1, priv2) + + priv1 = SigningKey.from_secret_exponent(secexp=4, curve=NIST224p) + self.assertEqual(priv1.baselen, NIST224p.baselen) + + def test_privkey_strings(self): + priv1 = SigningKey.generate() + s1 = priv1.to_string() + self.assertEqual(type(s1), binary_type) + self.assertEqual(len(s1), NIST192p.baselen) + priv2 = SigningKey.from_string(s1) + self.assertTruePrivkeysEqual(priv1, priv2) + + s1 = priv1.to_pem() + self.assertEqual(type(s1), binary_type) + self.assertTrue(s1.startswith(b("-----BEGIN EC PRIVATE KEY-----"))) + self.assertTrue(s1.strip().endswith(b("-----END EC PRIVATE KEY-----"))) + priv2 = SigningKey.from_pem(s1) + self.assertTruePrivkeysEqual(priv1, priv2) + + s1 = priv1.to_der() + self.assertEqual(type(s1), binary_type) + priv2 = SigningKey.from_der(s1) + self.assertTruePrivkeysEqual(priv1, priv2) + + priv1 = SigningKey.generate(curve=NIST256p) + s1 = priv1.to_pem() + self.assertEqual(type(s1), binary_type) + self.assertTrue(s1.startswith(b("-----BEGIN EC PRIVATE KEY-----"))) + self.assertTrue(s1.strip().endswith(b("-----END EC PRIVATE KEY-----"))) + priv2 = SigningKey.from_pem(s1) + self.assertTruePrivkeysEqual(priv1, priv2) + + s1 = priv1.to_der() + self.assertEqual(type(s1), binary_type) + priv2 = SigningKey.from_der(s1) + self.assertTruePrivkeysEqual(priv1, priv2) + + def assertTruePubkeysEqual(self, pub1, pub2): + self.assertEqual(pub1.pubkey.point, pub2.pubkey.point) + self.assertEqual(pub1.pubkey.generator, pub2.pubkey.generator) + self.assertEqual(pub1.curve, pub2.curve) + + def test_pubkey_strings(self): + priv1 = SigningKey.generate() + pub1 = priv1.get_verifying_key() + s1 = pub1.to_string() + self.assertEqual(type(s1), binary_type) + self.assertEqual(len(s1), NIST192p.verifying_key_length) + pub2 = VerifyingKey.from_string(s1) + self.assertTruePubkeysEqual(pub1, pub2) + + priv1 = SigningKey.generate(curve=NIST256p) + pub1 = priv1.get_verifying_key() + s1 = pub1.to_string() + self.assertEqual(type(s1), binary_type) + self.assertEqual(len(s1), NIST256p.verifying_key_length) + pub2 = VerifyingKey.from_string(s1, curve=NIST256p) + self.assertTruePubkeysEqual(pub1, pub2) + + pub1_der = pub1.to_der() + self.assertEqual(type(pub1_der), binary_type) + pub2 = VerifyingKey.from_der(pub1_der) + self.assertTruePubkeysEqual(pub1, pub2) + + self.assertRaises(der.UnexpectedDER, + VerifyingKey.from_der, pub1_der+b("junk")) + badpub = VerifyingKey.from_der(pub1_der) + class FakeGenerator: + def order(self): return 123456789 + badcurve = Curve("unknown", None, FakeGenerator(), (1,2,3,4,5,6), None) + badpub.curve = badcurve + badder = badpub.to_der() + self.assertRaises(UnknownCurveError, VerifyingKey.from_der, badder) + + pem = pub1.to_pem() + self.assertEqual(type(pem), binary_type) + self.assertTrue(pem.startswith(b("-----BEGIN PUBLIC KEY-----")), pem) + self.assertTrue(pem.strip().endswith(b("-----END PUBLIC KEY-----")), pem) + pub2 = VerifyingKey.from_pem(pem) + self.assertTruePubkeysEqual(pub1, pub2) + + def test_signature_strings(self): + priv1 = SigningKey.generate() + pub1 = priv1.get_verifying_key() + data = b("data") + + sig = priv1.sign(data) + self.assertEqual(type(sig), binary_type) + self.assertEqual(len(sig), NIST192p.signature_length) + self.assertTrue(pub1.verify(sig, data)) + + sig = priv1.sign(data, sigencode=sigencode_strings) + self.assertEqual(type(sig), tuple) + self.assertEqual(len(sig), 2) + self.assertEqual(type(sig[0]), binary_type) + self.assertEqual(type(sig[1]), binary_type) + self.assertEqual(len(sig[0]), NIST192p.baselen) + self.assertEqual(len(sig[1]), NIST192p.baselen) + self.assertTrue(pub1.verify(sig, data, sigdecode=sigdecode_strings)) + + sig_der = priv1.sign(data, sigencode=sigencode_der) + self.assertEqual(type(sig_der), binary_type) + self.assertTrue(pub1.verify(sig_der, data, sigdecode=sigdecode_der)) + + def test_hashfunc(self): + sk = SigningKey.generate(curve=NIST256p, hashfunc=sha256) + data = b("security level is 128 bits") + sig = sk.sign(data) + vk = VerifyingKey.from_string(sk.get_verifying_key().to_string(), + curve=NIST256p, hashfunc=sha256) + self.assertTrue(vk.verify(sig, data)) + + sk2 = SigningKey.generate(curve=NIST256p) + sig2 = sk2.sign(data, hashfunc=sha256) + vk2 = VerifyingKey.from_string(sk2.get_verifying_key().to_string(), + curve=NIST256p, hashfunc=sha256) + self.assertTrue(vk2.verify(sig2, data)) + + vk3 = VerifyingKey.from_string(sk.get_verifying_key().to_string(), + curve=NIST256p) + self.assertTrue(vk3.verify(sig, data, hashfunc=sha256)) + + +class OpenSSL(unittest.TestCase): + # test interoperability with OpenSSL tools. Note that openssl's ECDSA + # sign/verify arguments changed between 0.9.8 and 1.0.0: the early + # versions require "-ecdsa-with-SHA1", the later versions want just + # "-SHA1" (or to leave out that argument entirely, which means the + # signature will use some default digest algorithm, probably determined + # by the key, probably always SHA1). + # + # openssl ecparam -name secp224r1 -genkey -out privkey.pem + # openssl ec -in privkey.pem -text -noout # get the priv/pub keys + # openssl dgst -ecdsa-with-SHA1 -sign privkey.pem -out data.sig data.txt + # openssl asn1parse -in data.sig -inform DER + # data.sig is 64 bytes, probably 56b plus ASN1 overhead + # openssl dgst -ecdsa-with-SHA1 -prverify privkey.pem -signature data.sig data.txt ; echo $? + # openssl ec -in privkey.pem -pubout -out pubkey.pem + # openssl ec -in privkey.pem -pubout -outform DER -out pubkey.der + + def get_openssl_messagedigest_arg(self): + v = run_openssl("version") + # e.g. "OpenSSL 1.0.0 29 Mar 2010", or "OpenSSL 1.0.0a 1 Jun 2010", + # or "OpenSSL 0.9.8o 01 Jun 2010" + vs = v.split()[1].split(".") + if vs >= ["1","0","0"]: + return "-SHA1" + else: + return "-ecdsa-with-SHA1" + + # sk: 1:OpenSSL->python 2:python->OpenSSL + # vk: 3:OpenSSL->python 4:python->OpenSSL + # sig: 5:OpenSSL->python 6:python->OpenSSL + + def test_from_openssl_nist192p(self): + return self.do_test_from_openssl(NIST192p) + def test_from_openssl_nist224p(self): + return self.do_test_from_openssl(NIST224p) + def test_from_openssl_nist256p(self): + return self.do_test_from_openssl(NIST256p) + def test_from_openssl_nist384p(self): + return self.do_test_from_openssl(NIST384p) + def test_from_openssl_nist521p(self): + return self.do_test_from_openssl(NIST521p) + def test_from_openssl_secp256k1(self): + return self.do_test_from_openssl(SECP256k1) + + def do_test_from_openssl(self, curve): + curvename = curve.openssl_name + assert curvename + # OpenSSL: create sk, vk, sign. + # Python: read vk(3), checksig(5), read sk(1), sign, check + mdarg = self.get_openssl_messagedigest_arg() + if os.path.isdir("t"): + shutil.rmtree("t") + os.mkdir("t") + run_openssl("ecparam -name %s -genkey -out t/privkey.pem" % curvename) + run_openssl("ec -in t/privkey.pem -pubout -out t/pubkey.pem") + data = b("data") + with open("t/data.txt","wb") as e: e.write(data) + run_openssl("dgst %s -sign t/privkey.pem -out t/data.sig t/data.txt" % mdarg) + run_openssl("dgst %s -verify t/pubkey.pem -signature t/data.sig t/data.txt" % mdarg) + with open("t/pubkey.pem","rb") as e: pubkey_pem = e.read() + vk = VerifyingKey.from_pem(pubkey_pem) # 3 + with open("t/data.sig","rb") as e: sig_der = e.read() + self.assertTrue(vk.verify(sig_der, data, # 5 + hashfunc=sha1, sigdecode=sigdecode_der)) + + with open("t/privkey.pem") as e: fp = e.read() + sk = SigningKey.from_pem(fp) # 1 + sig = sk.sign(data) + self.assertTrue(vk.verify(sig, data)) + + def test_to_openssl_nist192p(self): + self.do_test_to_openssl(NIST192p) + def test_to_openssl_nist224p(self): + self.do_test_to_openssl(NIST224p) + def test_to_openssl_nist256p(self): + self.do_test_to_openssl(NIST256p) + def test_to_openssl_nist384p(self): + self.do_test_to_openssl(NIST384p) + def test_to_openssl_nist521p(self): + self.do_test_to_openssl(NIST521p) + def test_to_openssl_secp256k1(self): + self.do_test_to_openssl(SECP256k1) + + def do_test_to_openssl(self, curve): + curvename = curve.openssl_name + assert curvename + # Python: create sk, vk, sign. + # OpenSSL: read vk(4), checksig(6), read sk(2), sign, check + mdarg = self.get_openssl_messagedigest_arg() + if os.path.isdir("t"): + shutil.rmtree("t") + os.mkdir("t") + sk = SigningKey.generate(curve=curve) + vk = sk.get_verifying_key() + data = b("data") + with open("t/pubkey.der","wb") as e: e.write(vk.to_der()) # 4 + with open("t/pubkey.pem","wb") as e: e.write(vk.to_pem()) # 4 + sig_der = sk.sign(data, hashfunc=sha1, sigencode=sigencode_der) + + with open("t/data.sig","wb") as e: e.write(sig_der) # 6 + with open("t/data.txt","wb") as e: e.write(data) + with open("t/baddata.txt","wb") as e: e.write(data+b("corrupt")) + + self.assertRaises(SubprocessError, run_openssl, + "dgst %s -verify t/pubkey.der -keyform DER -signature t/data.sig t/baddata.txt" % mdarg) + run_openssl("dgst %s -verify t/pubkey.der -keyform DER -signature t/data.sig t/data.txt" % mdarg) + + with open("t/privkey.pem","wb") as e: e.write(sk.to_pem()) # 2 + run_openssl("dgst %s -sign t/privkey.pem -out t/data.sig2 t/data.txt" % mdarg) + run_openssl("dgst %s -verify t/pubkey.pem -signature t/data.sig2 t/data.txt" % mdarg) + +class DER(unittest.TestCase): + def test_oids(self): + oid_ecPublicKey = der.encode_oid(1, 2, 840, 10045, 2, 1) + self.assertEqual(hexlify(oid_ecPublicKey), b("06072a8648ce3d0201")) + self.assertEqual(hexlify(NIST224p.encoded_oid), b("06052b81040021")) + self.assertEqual(hexlify(NIST256p.encoded_oid), + b("06082a8648ce3d030107")) + x = oid_ecPublicKey + b("more") + x1, rest = der.remove_object(x) + self.assertEqual(x1, (1, 2, 840, 10045, 2, 1)) + self.assertEqual(rest, b("more")) + + def test_integer(self): + self.assertEqual(der.encode_integer(0), b("\x02\x01\x00")) + self.assertEqual(der.encode_integer(1), b("\x02\x01\x01")) + self.assertEqual(der.encode_integer(127), b("\x02\x01\x7f")) + self.assertEqual(der.encode_integer(128), b("\x02\x02\x00\x80")) + self.assertEqual(der.encode_integer(256), b("\x02\x02\x01\x00")) + #self.assertEqual(der.encode_integer(-1), b("\x02\x01\xff")) + + def s(n): return der.remove_integer(der.encode_integer(n) + b("junk")) + self.assertEqual(s(0), (0, b("junk"))) + self.assertEqual(s(1), (1, b("junk"))) + self.assertEqual(s(127), (127, b("junk"))) + self.assertEqual(s(128), (128, b("junk"))) + self.assertEqual(s(256), (256, b("junk"))) + self.assertEqual(s(1234567890123456789012345678901234567890), + (1234567890123456789012345678901234567890,b("junk"))) + + def test_number(self): + self.assertEqual(der.encode_number(0), b("\x00")) + self.assertEqual(der.encode_number(127), b("\x7f")) + self.assertEqual(der.encode_number(128), b("\x81\x00")) + self.assertEqual(der.encode_number(3*128+7), b("\x83\x07")) + #self.assertEqual(der.read_number("\x81\x9b"+"more"), (155, 2)) + #self.assertEqual(der.encode_number(155), b("\x81\x9b")) + for n in (0, 1, 2, 127, 128, 3*128+7, 840, 10045): #, 155): + x = der.encode_number(n) + b("more") + n1, llen = der.read_number(x) + self.assertEqual(n1, n) + self.assertEqual(x[llen:], b("more")) + + def test_length(self): + self.assertEqual(der.encode_length(0), b("\x00")) + self.assertEqual(der.encode_length(127), b("\x7f")) + self.assertEqual(der.encode_length(128), b("\x81\x80")) + self.assertEqual(der.encode_length(255), b("\x81\xff")) + self.assertEqual(der.encode_length(256), b("\x82\x01\x00")) + self.assertEqual(der.encode_length(3*256+7), b("\x82\x03\x07")) + self.assertEqual(der.read_length(b("\x81\x9b")+b("more")), (155, 2)) + self.assertEqual(der.encode_length(155), b("\x81\x9b")) + for n in (0, 1, 2, 127, 128, 255, 256, 3*256+7, 155): + x = der.encode_length(n) + b("more") + n1, llen = der.read_length(x) + self.assertEqual(n1, n) + self.assertEqual(x[llen:], b("more")) + + def test_sequence(self): + x = der.encode_sequence(b("ABC"), b("DEF")) + b("GHI") + self.assertEqual(x, b("\x30\x06ABCDEFGHI")) + x1, rest = der.remove_sequence(x) + self.assertEqual(x1, b("ABCDEF")) + self.assertEqual(rest, b("GHI")) + + def test_constructed(self): + x = der.encode_constructed(0, NIST224p.encoded_oid) + self.assertEqual(hexlify(x), b("a007") + b("06052b81040021")) + x = der.encode_constructed(1, unhexlify(b("0102030a0b0c"))) + self.assertEqual(hexlify(x), b("a106") + b("0102030a0b0c")) + +class Util(unittest.TestCase): + def test_trytryagain(self): + tta = util.randrange_from_seed__trytryagain + for i in range(1000): + seed = "seed-%d" % i + for order in (2**8-2, 2**8-1, 2**8, 2**8+1, 2**8+2, + 2**16-1, 2**16+1): + n = tta(seed, order) + self.assertTrue(1 <= n < order, (1, n, order)) + # this trytryagain *does* provide long-term stability + self.assertEqual(("%x"%(tta("seed", NIST224p.order))).encode(), + b("6fa59d73bf0446ae8743cf748fc5ac11d5585a90356417e97155c3bc")) + + def test_randrange(self): + # util.randrange does not provide long-term stability: we might + # change the algorithm in the future. + for i in range(1000): + entropy = util.PRNG("seed-%d" % i) + for order in (2**8-2, 2**8-1, 2**8, + 2**16-1, 2**16+1, + ): + # that oddball 2**16+1 takes half our runtime + n = util.randrange(order, entropy=entropy) + self.assertTrue(1 <= n < order, (1, n, order)) + + def OFF_test_prove_uniformity(self): + order = 2**8-2 + counts = dict([(i, 0) for i in range(1, order)]) + assert 0 not in counts + assert order not in counts + for i in range(1000000): + seed = "seed-%d" % i + n = util.randrange_from_seed__trytryagain(seed, order) + counts[n] += 1 + # this technique should use the full range + self.assertTrue(counts[order-1]) + for i in range(1, order): + print_("%3d: %s" % (i, "*"*(counts[i]//100))) + +class RFC6979(unittest.TestCase): + # https://tools.ietf.org/html/rfc6979#appendix-A.1 + def _do(self, generator, secexp, hsh, hash_func, expected): + actual = rfc6979.generate_k(generator.order(), secexp, hash_func, hsh) + self.assertEqual(expected, actual) + + def test_SECP256k1(self): + '''RFC doesn't contain test vectors for SECP256k1 used in bitcoin. + This vector has been computed by Golang reference implementation instead.''' + self._do( + generator = SECP256k1.generator, + secexp = int("9d0219792467d7d37b4d43298a7d0c05", 16), + hsh = sha256(b("sample")).digest(), + hash_func = sha256, + expected = int("8fa1f95d514760e498f28957b824ee6ec39ed64826ff4fecc2b5739ec45b91cd", 16)) + + def test_SECP256k1_2(self): + self._do( + generator=SECP256k1.generator, + secexp=int("cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", 16), + hsh=sha256(b("sample")).digest(), + hash_func=sha256, + expected=int("2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3", 16)) + + def test_SECP256k1_3(self): + self._do( + generator=SECP256k1.generator, + secexp=0x1, + hsh=sha256(b("Satoshi Nakamoto")).digest(), + hash_func=sha256, + expected=0x8F8A276C19F4149656B280621E358CCE24F5F52542772691EE69063B74F15D15) + + def test_SECP256k1_4(self): + self._do( + generator=SECP256k1.generator, + secexp=0x1, + hsh=sha256(b("All those moments will be lost in time, like tears in rain. Time to die...")).digest(), + hash_func=sha256, + expected=0x38AA22D72376B4DBC472E06C3BA403EE0A394DA63FC58D88686C611ABA98D6B3) + + def test_SECP256k1_5(self): + self._do( + generator=SECP256k1.generator, + secexp=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140, + hsh=sha256(b("Satoshi Nakamoto")).digest(), + hash_func=sha256, + expected=0x33A19B60E25FB6F4435AF53A3D42D493644827367E6453928554F43E49AA6F90) + + def test_SECP256k1_6(self): + self._do( + generator=SECP256k1.generator, + secexp=0xf8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181, + hsh=sha256(b("Alan Turing")).digest(), + hash_func=sha256, + expected=0x525A82B70E67874398067543FD84C83D30C175FDC45FDEEE082FE13B1D7CFDF1) + + def test_1(self): + # Basic example of the RFC, it also tests 'try-try-again' from Step H of rfc6979 + self._do( + generator = Point(None, 0, 0, int("4000000000000000000020108A2E0CC0D99F8A5EF", 16)), + secexp = int("09A4D6792295A7F730FC3F2B49CBC0F62E862272F", 16), + hsh = unhexlify(b("AF2BDBE1AA9B6EC1E2ADE1D694F41FC71A831D0268E9891562113D8A62ADD1BF")), + hash_func = sha256, + expected = int("23AF4074C90A02B3FE61D286D5C87F425E6BDD81B", 16)) + + def test_2(self): + self._do( + generator=NIST192p.generator, + secexp = int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), + hsh = sha1(b("sample")).digest(), + hash_func = sha1, + expected = int("37D7CA00D2C7B0E5E412AC03BD44BA837FDD5B28CD3B0021", 16)) + + def test_3(self): + self._do( + generator=NIST192p.generator, + secexp = int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), + hsh = sha256(b("sample")).digest(), + hash_func = sha256, + expected = int("32B1B6D7D42A05CB449065727A84804FB1A3E34D8F261496", 16)) + + def test_4(self): + self._do( + generator=NIST192p.generator, + secexp = int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), + hsh = sha512(b("sample")).digest(), + hash_func = sha512, + expected = int("A2AC7AB055E4F20692D49209544C203A7D1F2C0BFBC75DB1", 16)) + + def test_5(self): + self._do( + generator=NIST192p.generator, + secexp = int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), + hsh = sha1(b("test")).digest(), + hash_func = sha1, + expected = int("D9CF9C3D3297D3260773A1DA7418DB5537AB8DD93DE7FA25", 16)) + + def test_6(self): + self._do( + generator=NIST192p.generator, + secexp = int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), + hsh = sha256(b("test")).digest(), + hash_func = sha256, + expected = int("5C4CE89CF56D9E7C77C8585339B006B97B5F0680B4306C6C", 16)) + + def test_7(self): + self._do( + generator=NIST192p.generator, + secexp = int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), + hsh = sha512(b("test")).digest(), + hash_func = sha512, + expected = int("0758753A5254759C7CFBAD2E2D9B0792EEE44136C9480527", 16)) + + def test_8(self): + self._do( + generator=NIST521p.generator, + secexp = int("0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538", 16), + hsh = sha1(b("sample")).digest(), + hash_func = sha1, + expected = int("089C071B419E1C2820962321787258469511958E80582E95D8378E0C2CCDB3CB42BEDE42F50E3FA3C71F5A76724281D31D9C89F0F91FC1BE4918DB1C03A5838D0F9", 16)) + + def test_9(self): + self._do( + generator=NIST521p.generator, + secexp = int("0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538", 16), + hsh = sha256(b("sample")).digest(), + hash_func = sha256, + expected = int("0EDF38AFCAAECAB4383358B34D67C9F2216C8382AAEA44A3DAD5FDC9C32575761793FEF24EB0FC276DFC4F6E3EC476752F043CF01415387470BCBD8678ED2C7E1A0", 16)) + + def test_10(self): + self._do( + generator=NIST521p.generator, + secexp = int("0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538", 16), + hsh = sha512(b("test")).digest(), + hash_func = sha512, + expected = int("16200813020EC986863BEDFC1B121F605C1215645018AEA1A7B215A564DE9EB1B38A67AA1128B80CE391C4FB71187654AAA3431027BFC7F395766CA988C964DC56D", 16)) + +def __main__(): + unittest.main() +if __name__ == "__main__": + __main__() diff --git a/ecdsa/util.py b/ecdsa/util.py new file mode 100644 index 0000000..c51ed3d --- /dev/null +++ b/ecdsa/util.py @@ -0,0 +1,247 @@ +from __future__ import division + +import os +import math +import binascii +from hashlib import sha256 +from . import der +from .curves import orderlen +from .six import PY3, int2byte, b, next + +# RFC5480: +# The "unrestricted" algorithm identifier is: +# id-ecPublicKey OBJECT IDENTIFIER ::= { +# iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 } + +oid_ecPublicKey = (1, 2, 840, 10045, 2, 1) +encoded_oid_ecPublicKey = der.encode_oid(*oid_ecPublicKey) + +def randrange(order, entropy=None): + """Return a random integer k such that 1 <= k < order, uniformly + distributed across that range. For simplicity, this only behaves well if + 'order' is fairly close (but below) a power of 256. The try-try-again + algorithm we use takes longer and longer time (on average) to complete as + 'order' falls, rising to a maximum of avg=512 loops for the worst-case + (256**k)+1 . All of the standard curves behave well. There is a cutoff at + 10k loops (which raises RuntimeError) to prevent an infinite loop when + something is really broken like the entropy function not working. + + Note that this function is not declared to be forwards-compatible: we may + change the behavior in future releases. The entropy= argument (which + should get a callable that behaves like os.urandom) can be used to + achieve stability within a given release (for repeatable unit tests), but + should not be used as a long-term-compatible key generation algorithm. + """ + # we could handle arbitrary orders (even 256**k+1) better if we created + # candidates bit-wise instead of byte-wise, which would reduce the + # worst-case behavior to avg=2 loops, but that would be more complex. The + # change would be to round the order up to a power of 256, subtract one + # (to get 0xffff..), use that to get a byte-long mask for the top byte, + # generate the len-1 entropy bytes, generate one extra byte and mask off + # the top bits, then combine it with the rest. Requires jumping back and + # forth between strings and integers a lot. + + if entropy is None: + entropy = os.urandom + assert order > 1 + bytes = orderlen(order) + dont_try_forever = 10000 # gives about 2**-60 failures for worst case + while dont_try_forever > 0: + dont_try_forever -= 1 + candidate = string_to_number(entropy(bytes)) + 1 + if 1 <= candidate < order: + return candidate + continue + raise RuntimeError("randrange() tried hard but gave up, either something" + " is very wrong or you got realllly unlucky. Order was" + " %x" % order) + +class PRNG: + # this returns a callable which, when invoked with an integer N, will + # return N pseudorandom bytes. Note: this is a short-term PRNG, meant + # primarily for the needs of randrange_from_seed__trytryagain(), which + # only needs to run it a few times per seed. It does not provide + # protection against state compromise (forward security). + def __init__(self, seed): + self.generator = self.block_generator(seed) + + def __call__(self, numbytes): + a = [next(self.generator) for i in range(numbytes)] + + if PY3: + return bytes(a) + else: + return "".join(a) + + + def block_generator(self, seed): + counter = 0 + while True: + for byte in sha256(("prng-%d-%s" % (counter, seed)).encode()).digest(): + yield byte + counter += 1 + +def randrange_from_seed__overshoot_modulo(seed, order): + # hash the data, then turn the digest into a number in [1,order). + # + # We use David-Sarah Hopwood's suggestion: turn it into a number that's + # sufficiently larger than the group order, then modulo it down to fit. + # This should give adequate (but not perfect) uniformity, and simple + # code. There are other choices: try-try-again is the main one. + base = PRNG(seed)(2*orderlen(order)) + number = (int(binascii.hexlify(base), 16) % (order-1)) + 1 + assert 1 <= number < order, (1, number, order) + return number + +def lsb_of_ones(numbits): + return (1 << numbits) - 1 +def bits_and_bytes(order): + bits = int(math.log(order-1, 2)+1) + bytes = bits // 8 + extrabits = bits % 8 + return bits, bytes, extrabits + +# the following randrange_from_seed__METHOD() functions take an +# arbitrarily-sized secret seed and turn it into a number that obeys the same +# range limits as randrange() above. They are meant for deriving consistent +# signing keys from a secret rather than generating them randomly, for +# example a protocol in which three signing keys are derived from a master +# secret. You should use a uniformly-distributed unguessable seed with about +# curve.baselen bytes of entropy. To use one, do this: +# seed = os.urandom(curve.baselen) # or other starting point +# secexp = ecdsa.util.randrange_from_seed__trytryagain(sed, curve.order) +# sk = SigningKey.from_secret_exponent(secexp, curve) + +def randrange_from_seed__truncate_bytes(seed, order, hashmod=sha256): + # hash the seed, then turn the digest into a number in [1,order), but + # don't worry about trying to uniformly fill the range. This will lose, + # on average, four bits of entropy. + bits, bytes, extrabits = bits_and_bytes(order) + if extrabits: + bytes += 1 + base = hashmod(seed).digest()[:bytes] + base = "\x00"*(bytes-len(base)) + base + number = 1+int(binascii.hexlify(base), 16) + assert 1 <= number < order + return number + +def randrange_from_seed__truncate_bits(seed, order, hashmod=sha256): + # like string_to_randrange_truncate_bytes, but only lose an average of + # half a bit + bits = int(math.log(order-1, 2)+1) + maxbytes = (bits+7) // 8 + base = hashmod(seed).digest()[:maxbytes] + base = "\x00"*(maxbytes-len(base)) + base + topbits = 8*maxbytes - bits + if topbits: + base = int2byte(ord(base[0]) & lsb_of_ones(topbits)) + base[1:] + number = 1+int(binascii.hexlify(base), 16) + assert 1 <= number < order + return number + +def randrange_from_seed__trytryagain(seed, order): + # figure out exactly how many bits we need (rounded up to the nearest + # bit), so we can reduce the chance of looping to less than 0.5 . This is + # specified to feed from a byte-oriented PRNG, and discards the + # high-order bits of the first byte as necessary to get the right number + # of bits. The average number of loops will range from 1.0 (when + # order=2**k-1) to 2.0 (when order=2**k+1). + assert order > 1 + bits, bytes, extrabits = bits_and_bytes(order) + generate = PRNG(seed) + while True: + extrabyte = b("") + if extrabits: + extrabyte = int2byte(ord(generate(1)) & lsb_of_ones(extrabits)) + guess = string_to_number(extrabyte + generate(bytes)) + 1 + if 1 <= guess < order: + return guess + + +def number_to_string(num, order): + l = orderlen(order) + fmt_str = "%0" + str(2*l) + "x" + string = binascii.unhexlify((fmt_str % num).encode()) + assert len(string) == l, (len(string), l) + return string + +def number_to_string_crop(num, order): + l = orderlen(order) + fmt_str = "%0" + str(2*l) + "x" + string = binascii.unhexlify((fmt_str % num).encode()) + return string[:l] + +def string_to_number(string): + return int(binascii.hexlify(string), 16) + +def string_to_number_fixedlen(string, order): + l = orderlen(order) + assert len(string) == l, (len(string), l) + return int(binascii.hexlify(string), 16) + +# these methods are useful for the sigencode= argument to SK.sign() and the +# sigdecode= argument to VK.verify(), and control how the signature is packed +# or unpacked. + +def sigencode_strings(r, s, order): + r_str = number_to_string(r, order) + s_str = number_to_string(s, order) + return (r_str, s_str) + +def sigencode_string(r, s, order): + # for any given curve, the size of the signature numbers is + # fixed, so just use simple concatenation + r_str, s_str = sigencode_strings(r, s, order) + return r_str + s_str + +def sigencode_der(r, s, order): + return der.encode_sequence(der.encode_integer(r), der.encode_integer(s)) + +# canonical versions of sigencode methods +# these enforce low S values, by negating the value (modulo the order) if above order/2 +# see CECKey::Sign() https://github.com/bitcoin/bitcoin/blob/master/src/key.cpp#L214 +def sigencode_strings_canonize(r, s, order): + if s > order / 2: + s = order - s + return sigencode_strings(r, s, order) + +def sigencode_string_canonize(r, s, order): + if s > order / 2: + s = order - s + return sigencode_string(r, s, order) + +def sigencode_der_canonize(r, s, order): + if s > order / 2: + s = order - s + return sigencode_der(r, s, order) + + +def sigdecode_string(signature, order): + l = orderlen(order) + assert len(signature) == 2*l, (len(signature), 2*l) + r = string_to_number_fixedlen(signature[:l], order) + s = string_to_number_fixedlen(signature[l:], order) + return r, s + +def sigdecode_strings(rs_strings, order): + (r_str, s_str) = rs_strings + l = orderlen(order) + assert len(r_str) == l, (len(r_str), l) + assert len(s_str) == l, (len(s_str), l) + r = string_to_number_fixedlen(r_str, order) + s = string_to_number_fixedlen(s_str, order) + return r, s + +def sigdecode_der(sig_der, order): + #return der.encode_sequence(der.encode_integer(r), der.encode_integer(s)) + rs_strings, empty = der.remove_sequence(sig_der) + if empty != b(""): + raise der.UnexpectedDER("trailing junk after DER sig: %s" % + binascii.hexlify(empty)) + r, rest = der.remove_integer(rs_strings) + s, empty = der.remove_integer(rest) + if empty != b(""): + raise der.UnexpectedDER("trailing junk after DER numbers: %s" % + binascii.hexlify(empty)) + return r, s + diff --git a/espefuse.py b/espefuse.py new file mode 100644 index 0000000..b0e1085 --- /dev/null +++ b/espefuse.py @@ -0,0 +1,894 @@ +#!/usr/bin/env python +# ESP32 efuse get/set utility +# https://github.com/themadinventor/esptool +# +# Copyright (C) 2016 Espressif Systems (Shanghai) PTE LTD +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +# Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import division, print_function + +import argparse +import os +import struct +import sys +import time + +import esptool + +# Table of efuse values - (category, block, word in block, mask, write disable bit, read disable bit, type, description) +# Match values in efuse_reg.h & Efuse technical reference chapter +EFUSES = [ + ('WR_DIS', "efuse", 0, 0, 0x0000FFFF, 1, None, "int", "Efuse write disable mask"), + ('RD_DIS', "efuse", 0, 0, 0x000F0000, 0, None, "int", "Efuse read disablemask"), + ('FLASH_CRYPT_CNT', "security", 0, 0, 0x0FF00000, 2, None, "bitcount", "Flash encryption mode counter"), + ('MAC', "identity", 0, 1, 0xFFFFFFFF, 3, None, "mac", "Factory MAC Address"), + ('XPD_SDIO_FORCE', "config", 0, 4, 1 << 16, 5, None, "flag", "Ignore MTDI pin (GPIO12) for VDD_SDIO on reset"), + ('XPD_SDIO_REG', "config", 0, 4, 1 << 14, 5, None, "flag", "If XPD_SDIO_FORCE, enable VDD_SDIO reg on reset"), + ('XPD_SDIO_TIEH', "config", 0, 4, 1 << 15, 5, None, "flag", "If XPD_SDIO_FORCE & XPD_SDIO_REG, 1=3.3V 0=1.8V"), + ('SPI_PAD_CONFIG_CLK', "config", 0, 5, 0x1F << 0, 6, None, "spipin", "Override SD_CLK pad (GPIO6/SPICLK)"), + ('SPI_PAD_CONFIG_Q', "config", 0, 5, 0x1F << 5, 6, None, "spipin", "Override SD_DATA_0 pad (GPIO7/SPIQ)"), + ('SPI_PAD_CONFIG_D', "config", 0, 5, 0x1F << 10, 6, None, "spipin", "Override SD_DATA_1 pad (GPIO8/SPID)"), + ('SPI_PAD_CONFIG_HD', "config", 0, 3, 0x1F << 4, 6, None, "spipin", "Override SD_DATA_2 pad (GPIO9/SPIHD)"), + ('SPI_PAD_CONFIG_CS0', "config", 0, 5, 0x1F << 15, 6, None, "spipin", "Override SD_CMD pad (GPIO11/SPICS0)"), + ('FLASH_CRYPT_CONFIG', "security", 0, 5, 0x0F << 28, 10, 3, "int", "Flash encryption config (key tweak bits)"), + ('CHIP_VER_REV1', "identity", 0, 3, 1 << 15, 3, None, "flag", "Silicon Revision 1"), + ('BLK3_PART_RESERVE', "calibration", 0, 3, 1 << 14, 10, 3, "flag", "BLOCK3 partially served for ADC calibration data"), + ('CHIP_VERSION', "identity", 0, 3, 0x03 << 12, 3, None, "int", "Reserved for future chip versions"), + ('CHIP_PACKAGE', "identity", 0, 3, 0x07 << 9, 3, None, "int", "Chip package identifier"), + ('CODING_SCHEME', "efuse", 0, 6, 0x3, 10, 3, "int", "Efuse variable block length scheme"), + ('CONSOLE_DEBUG_DISABLE',"security", 0, 6, 1 << 2, 15, None, "flag", "Disable ROM BASIC interpreter fallback"), + ('DISABLE_SDIO_HOST', "config", 0, 6, 1 << 3, None, None, "flag", "Disable SDIO host"), + ('ABS_DONE_0', "security", 0, 6, 1 << 4, 12, None, "flag", "secure boot enabled for bootloader"), + ('ABS_DONE_1', "security", 0, 6, 1 << 5, 13, None, "flag", "secure boot abstract 1 locked"), + ('JTAG_DISABLE', "security", 0, 6, 1 << 6, 14, None, "flag", "Disable JTAG"), + ('DISABLE_DL_ENCRYPT', "security", 0, 6, 1 << 7, 15, None, "flag", "Disable flash encryption in UART bootloader"), + ('DISABLE_DL_DECRYPT', "security", 0, 6, 1 << 8, 15, None, "flag", "Disable flash decryption in UART bootloader"), + ('DISABLE_DL_CACHE', "security", 0, 6, 1 << 9, 15, None, "flag", "Disable flash cache in UART bootloader"), + ('KEY_STATUS', "efuse", 0, 6, 1 << 10, 10, 3, "flag", "Usage of efuse block 3 (reserved)"), + ('ADC_VREF', "calibration", 0, 4,0x1F << 8,0, None, "vref", "Voltage reference calibration"), + ('BLK1', "security", 1, 0, 0xFFFFFFFF, 7, 0, "keyblock", "Flash encryption key"), + ('BLK2', "security", 2, 0, 0xFFFFFFFF, 8, 1, "keyblock", "Secure boot key"), + ('BLK3', "security", 3, 0, 0xFFFFFFFF, 9, 2, "keyblock", "Variable Block 3"), +] + +# if BLK3_PART_RESERVE is set, these efuse fields are in BLK3: +BLK3_PART_EFUSES = [ + ('ADC1_TP_LOW', "calibration", 3, 3, 0x7F << 0, 9, 2, "adc_tp", "ADC1 150mV reading"), + ('ADC1_TP_HIGH', "calibration", 3, 3, 0x1FF << 7, 9, 2, "adc_tp", "ADC1 850mV reading"), + ('ADC2_TP_LOW', "calibration", 3, 3, 0x7F << 16, 9, 2, "adc_tp", "ADC2 150mV reading"), + ('ADC2_TP_HIGH', "calibration", 3, 3, 0x1FF << 23, 9, 2, "adc_tp", "ADC2 850mV reading"), +] + +# BLK3 can contain arbitrary information, and it is not possible to know whether the Custom MAC Address is present +# there. Therefore, the following efuses are not shown in the efuse summary, and are accessed only by get_custom_mac +# and burn_custom_mac commands. +CUST_MAC_VER_EFUSE = ('CUST_MAC_VER', "identity", 3, 5, 0xFF << 24, None, None, "int", "Version of Custom MAC Address") +CUST_MAC_EFUSE = ('CUST_MAC', "identity", 3, 0, 0xFFFFFFFF, None, None, "cmac", "Custom MAC Address") + +# Offsets and lengths of each of the 4 efuse blocks in register space +# +# These offsets/lens are for esptool.read_efuse(X) which takes +# a word offset (into registers) not a byte offset. +EFUSE_BLOCK_OFFS = [0, 14, 22, 30] +EFUSE_BLOCK_LEN = [7, 8, 8, 8] + +# EFUSE registers & command/conf values +EFUSE_REG_CONF = 0x3FF5A0FC +EFUSE_CONF_WRITE = 0x5A5A +EFUSE_CONF_READ = 0x5AA5 +EFUSE_REG_CMD = 0x3FF5A104 +EFUSE_CMD_WRITE = 0x2 +EFUSE_CMD_READ = 0x1 +# address of first word of write registers for each efuse +EFUSE_REG_WRITE = [0x3FF5A01C, 0x3FF5A098, 0x3FF5A0B8, 0x3FF5A0D8] +# 3/4 Coding scheme warnings registers +EFUSE_REG_DEC_STATUS = 0x3FF5A11C +EFUSE_REG_DEC_STATUS_MASK = 0xFFF + +EFUSE_BURN_TIMEOUT = 0.250 # seconds + + +# Coding Scheme values +CODING_SCHEME_NONE = 0 +CODING_SCHEME_34 = 1 + + +def confirm(action, args): + print("%s%sThis is an irreversible operation." % (action, "" if action.endswith("\n") else ". ")) + if not args.do_not_confirm: + print("Type 'BURN' (all capitals) to continue.") + sys.stdout.flush() # required for Pythons which disable line buffering, ie mingw in mintty + try: + yes = raw_input() # raw_input renamed to input in Python 3 + except NameError: + yes = input() + if yes != "BURN": + print("Aborting.") + sys.exit(0) + + +def efuse_write_reg_addr(block, word): + """ + Return the physical address of the efuse write data register + block X word X. + """ + return EFUSE_REG_WRITE[block] + (4 * word) + + +class EspEfuses(object): + """ + Wrapper object to manage the efuse fields in a connected ESP bootloader + """ + def __init__(self, esp): + self._esp = esp + self._efuses = [EfuseField.from_tuple(self, efuse) for efuse in EFUSES] + if self["BLK3_PART_RESERVE"].get(): + # add these BLK3 efuses, if the BLK3_PART_RESERVE flag is set... + self._efuses += [EfuseField.from_tuple(self, efuse) for efuse in BLK3_PART_EFUSES] + + self.coding_scheme = self["CODING_SCHEME"].get() + + def __getitem__(self, efuse_name): + """ Return the efuse field with the given name """ + for e in self._efuses: + if efuse_name == e.register_name: + return e + raise KeyError + + def __iter__(self): + return self._efuses.__iter__() + + def write_efuses(self): + """ Write the values in the efuse write registers to + the efuse hardware, then refresh the efuse read registers. + """ + self._esp.write_reg(EFUSE_REG_CONF, EFUSE_CONF_WRITE) + self._esp.write_reg(EFUSE_REG_CMD, EFUSE_CMD_WRITE) + + def wait_idle(): + deadline = time.time() + EFUSE_BURN_TIMEOUT + while time.time() < deadline: + if self._esp.read_reg(EFUSE_REG_CMD) == 0: + return + raise esptool.FatalError("Timed out waiting for Efuse controller command to complete") + wait_idle() + self._esp.write_reg(EFUSE_REG_CONF, EFUSE_CONF_READ) + self._esp.write_reg(EFUSE_REG_CMD, EFUSE_CMD_READ) + wait_idle() + + def read_efuse(self, addr): + return self._esp.read_efuse(addr) + + def read_reg(self, addr): + return self._esp.read_reg(addr) + + def write_reg(self, addr, value): + return self._esp.write_reg(addr, value) + + def get_coding_scheme_warnings(self): + """ Check if the coding scheme has detected any errors. + Meaningless for default coding scheme (0) + """ + return self.read_reg(EFUSE_REG_DEC_STATUS) & EFUSE_REG_DEC_STATUS_MASK + + def get_block_len(self): + """ Return the length of BLK1, BLK2, BLK3 in bytes """ + return 24 if self.coding_scheme == CODING_SCHEME_34 else 32 + + +class EfuseField(object): + @staticmethod + def from_tuple(parent, efuse_tuple): + category = efuse_tuple[7] + return { + "mac": EfuseMacField, + "cmac": EfuseCustMacField, + "keyblock": EfuseKeyblockField, + "spipin": EfuseSpiPinField, + "vref": EfuseVRefField, + "adc_tp": EfuseAdcPointCalibration, + }.get(category, EfuseField)(parent, *efuse_tuple) + + def __init__(self, parent, register_name, category, block, word, mask, write_disable_bit, read_disable_bit, efuse_type, description): + self.category = category + self.parent = parent + self.block = block + self.word = word + self.data_reg_offs = EFUSE_BLOCK_OFFS[self.block] + self.word + self.mask = mask + self.shift = 0 + # self.shift is the number of the least significant bit in the mask + while mask & 0x1 == 0: + self.shift += 1 + mask >>= 1 + self.write_disable_bit = write_disable_bit + self.read_disable_bit = read_disable_bit + self.register_name = register_name + self.efuse_type = efuse_type + self.description = description + + def get_raw(self): + """ Return the raw (unformatted) numeric value of the efuse bits + + Returns a simple integer or (for some subclasses) a bitstring. + """ + value = self.parent.read_efuse(self.data_reg_offs) + return (value & self.mask) >> self.shift + + def get(self): + """ Get a formatted version of the efuse value, suitable for display """ + return self.get_raw() + + def is_readable(self): + """ Return true if the efuse is readable by software """ + if self.read_disable_bit is None: + return True # read cannot be disabled + value = (self.parent.read_efuse(0) >> 16) & 0xF # RD_DIS values + return (value & (1 << self.read_disable_bit)) == 0 + + def disable_read(self): + if self.read_disable_bit is None: + raise esptool.FatalError("This efuse cannot be read-disabled") + rddis_reg_addr = efuse_write_reg_addr(0, 0) + self.parent.write_reg(rddis_reg_addr, 1 << (16 + self.read_disable_bit)) + self.parent.write_efuses() + return self.get() + + def is_writeable(self): + if self.write_disable_bit is None: + return True # write cannot be disabled + value = self.parent.read_efuse(0) & 0xFFFF # WR_DIS values + return (value & (1 << self.write_disable_bit)) == 0 + + def disable_write(self): + wrdis_reg_addr = efuse_write_reg_addr(0, 0) + self.parent.write_reg(wrdis_reg_addr, 1 << self.write_disable_bit) + self.parent.write_efuses() + return self.get() + + def burn(self, new_value): + raw_value = (new_value << self.shift) & self.mask + # don't both reading old value as we can only set bits 0->1 + write_reg_addr = efuse_write_reg_addr(self.block, self.word) + self.parent.write_reg(write_reg_addr, raw_value) + self.parent.write_efuses() + return self.get() + + +class EfuseMacField(EfuseField): + def get_raw(self): + # MAC values are high half of second efuse word, then first efuse word + words = [self.parent.read_efuse(self.data_reg_offs + word) for word in [1,0]] + # endian-swap into a bitstring + bitstring = struct.pack(">II", *words) + return bitstring[2:] # trim 2 byte CRC from the beginning + + def get(self): + stored_crc = self.get_stored_crc() + calc_crc = self.calc_crc(self.get_raw()) + if calc_crc == stored_crc: + valid_msg = "(CRC %02x OK)" % stored_crc + else: + valid_msg = "(CRC %02x invalid - calculated 0x%02x)" % (stored_crc, calc_crc) + return "%s %s" % (hexify(self.get_raw(), ":"), valid_msg) + + def burn(self, new_value): + # Writing the BLK0 default MAC is not sensible, as it's written in the factory. + raise esptool.FatalError("Writing Factory MAC address is not supported") + + def get_stored_crc(self): + return (self.parent.read_efuse(self.data_reg_offs + 1) >> 16) & 0xFF + + @staticmethod + def calc_crc(raw_mac): + """ + This algorithm is the equivalent of esp_crc8() in ESP32 ROM code + + This is CRC-8 w/ inverted polynomial value 0x8C & initial value 0x00. + """ + result = 0x00 + for b in struct.unpack("B" * 6, raw_mac): + result ^= b + for _ in range(8): + lsb = result & 1 + result >>= 1 + if lsb != 0: + result ^= 0x8c + return result + + +class EfuseCustMacField(EfuseMacField): + """ + The custom MAC field uses the formatting according to the specification for version 1 + """ + def get_raw(self): + words = [self.parent.read_efuse(self.data_reg_offs + word) for word in [0,1]] + bitstring = struct.pack("> 24 + write_reg_addr = efuse_write_reg_addr(self.block, self.word + 0) + self.parent.write_reg(write_reg_addr, word0) + write_reg_addr = efuse_write_reg_addr(self.block, self.word + 1) + self.parent.write_reg(write_reg_addr, word1) + self.parent.write_efuses() + return self.get() + + def get_stored_crc(self): + return self.parent.read_efuse(self.data_reg_offs) & 0xFF + + +class EfuseKeyblockField(EfuseField): + def get_raw(self): + words = self.get_words() + return struct.pack("<" + ("I" * len(words)), *words) + + def get_key(self): + # Keys are stored in reverse byte order + result = self.get_raw() + result = result[::-1] + return result + + def get_words(self): + num_words = self.parent.get_block_len() // 4 + return [self.parent.read_efuse(self.data_reg_offs + word) for word in range(num_words)] + + def get(self): + return hexify(self.get_raw(), " ") + + def apply_34_encoding(self, inbits): + """ Takes 24 byte sequence to be represented in 3/4 encoding, + returns 8 words suitable for writing "encoded" to an efuse block + """ + def popcnt(b): + """ Return number of "1" bits set in 'b' """ + return len([x for x in bin(b) if x == "1"]) + + outbits = b"" + while len(inbits) > 0: # process in chunks of 6 bytes + bits = inbits[0:6] + inbits = inbits[6:] + xor_res = 0 + mul_res = 0 + index = 1 + for b in struct.unpack("B" * 6, bits): + xor_res ^= b + mul_res += index * popcnt(b) + index += 1 + outbits += bits + outbits += struct.pack("BB", xor_res, mul_res) + return struct.unpack("<" + "I" * (len(outbits) // 4), outbits) + + def burn_key(self, new_value): + new_value = new_value[::-1] # AES keys are stored in reverse order in efuse + return self.burn(new_value) + + def burn(self, new_value): + key_len = self.parent.get_block_len() + if len(new_value) != key_len: + raise RuntimeError("Invalid new value length for key block (%d), %d is required" % len(new_value), key_len) + + if self.parent.coding_scheme == CODING_SCHEME_34: + words = self.apply_34_encoding(new_value) + else: + words = struct.unpack("<" + ("I" * 8), new_value) + return self.burn_words(words) + + def burn_words(self, words, word_offset=0): + write_reg_addr = efuse_write_reg_addr(self.block, self.word + word_offset) + for word in words: + self.parent.write_reg(write_reg_addr, word) + write_reg_addr += 4 + warnings_before = self.parent.get_coding_scheme_warnings() + self.parent.write_efuses() + warnings_after = self.parent.get_coding_scheme_warnings() + if warnings_after & ~warnings_before != 0: + print("WARNING: Burning efuse block added coding scheme warnings 0x%x -> 0x%x. Encoding bug?" % (warnings_before, warnings_after)) + return self.get() + + +class EfuseSpiPinField(EfuseField): + def get(self): + val = self.get_raw() + if val >= 30: + val += 2 # values 30,31 map to 32, 33 + return val + + def burn(self, new_value): + if new_value in [30, 31]: + raise esptool.FatalError("IO pins 30 & 31 cannot be set for SPI flash. 0-29, 32 & 33 only.") + if new_value > 33: + raise esptool.FatalError("IO pin %d cannot be set for SPI flash. 0-29, 32 & 33 only." % new_value) + if new_value > 30: + new_value -= 2 # values 32,33 map to 30, 31 + return super(EfuseSpiPinField, self).burn(new_value) + + +class EfuseVRefField(EfuseField): + VREF_OFFSET = 1100 # ideal efuse value in mV + VREF_STEP_SIZE = 7 # 1 count in efuse == 7mV + VREF_SIGN_BIT = 0x10 + VREF_MAG_BITS = 0x0F + + def get(self): + val = self.get_raw() + # sign-magnitude format + if (val & self.VREF_SIGN_BIT): + val = -(val & self.VREF_MAG_BITS) + else: + val = (val & self.VREF_MAG_BITS) + val *= self.VREF_STEP_SIZE + return self.VREF_OFFSET + val + + def burn(self, new_value): + raise RuntimeError("Writing to VRef is not supported.") + + +class EfuseAdcPointCalibration(EfuseField): + TP_OFFSET = { # See TP_xxxx_OFFSET in esp_adc_cal.c in ESP-IDF + "ADC1_TP_LOW": 278, + "ADC2_TP_LOW": 421, + "ADC1_TP_HIGH": 3265, + "ADC2_TP_HIGH": 3406, + } + SIGN_BIT = (0x40, 0x100) # LOW, HIGH (2s complement format) + STEP_SIZE = 4 + + def get(self): + idx = 0 if self.register_name.endswith("LOW") else 1 + sign_bit = self.SIGN_BIT[idx] + offset = self.TP_OFFSET[self.register_name] + raw = self.get_raw() + delta = (raw & (sign_bit - 1)) - (raw & sign_bit) + return offset + (delta * self.STEP_SIZE) + + +def dump(esp, _efuses, args): + """ Dump raw efuse data registers """ + for block in range(len(EFUSE_BLOCK_OFFS)): + print("EFUSE block %d:" % block) + offsets = [x + EFUSE_BLOCK_OFFS[block] for x in range(EFUSE_BLOCK_LEN[block])] + print(" ".join(["%08x" % esp.read_efuse(offs) for offs in offsets])) + + +def summary(esp, efuses, args): + """ Print a human-readable summary of efuse contents """ + ROW_FORMAT = "%-22s %-50s%s= %s %s %s" + print(ROW_FORMAT.replace("-50", "-12") % ("EFUSE_NAME", "Description", "", "[Meaningful Value]", "[Readable/Writeable]", "(Hex Value)")) + print("-" * 88) + for category in set(e.category for e in efuses): + print("%s fuses:" % category.title()) + for e in (e for e in efuses if e.category == category): + raw = e.get_raw() + try: + raw = "(0x%x)" % raw + except TypeError: + raw = "" + (readable, writeable) = (e.is_readable(), e.is_writeable()) + if readable and writeable: + perms = "R/W" + elif readable: + perms = "R/-" + elif writeable: + perms = "-/W" + else: + perms = "-/-" + value = str(e.get()) + if not readable: + value = value.replace("0", "?") + print(ROW_FORMAT % (e.register_name, e.description, "\n " if len(value) > 20 else "", value, perms, raw)) + print("") + sdio_force = efuses["XPD_SDIO_FORCE"] + sdio_tieh = efuses["XPD_SDIO_TIEH"] + sdio_reg = efuses["XPD_SDIO_REG"] + if sdio_force.get() == 0: + print("Flash voltage (VDD_SDIO) determined by GPIO12 on reset (High for 1.8V, Low/NC for 3.3V).") + elif sdio_reg.get() == 0: + print("Flash voltage (VDD_SDIO) internal regulator disabled by efuse.") + elif sdio_tieh.get() == 0: + print("Flash voltage (VDD_SDIO) set to 1.8V by efuse.") + else: + print("Flash voltage (VDD_SDIO) set to 3.3V by efuse.") + warnings = efuses.get_coding_scheme_warnings() + if warnings: + print("WARNING: Coding scheme has encoding bit error warnings (0x%x)" % warnings) + + +def burn_efuse(esp, efuses, args): + efuse = efuses[args.efuse_name] + old_value = efuse.get() + if efuse.efuse_type == "flag": + if args.new_value not in [None, 1]: + raise esptool.FatalError("Efuse %s is type 'flag'. New value is not accepted for this efuse (will always burn 0->1)" % efuse.register_name) + args.new_value = 1 + if old_value: + print("Efuse %s is already burned." % efuse.register_name) + return + elif efuse.efuse_type == "int": + if args.new_value is None: + raise esptool.FatalError("New value required for efuse %s" % efuse.register_name) + elif efuse.efuse_type == "spipin": + if args.new_value is None or args.new_value == 0: + raise esptool.FatalError("New value required for efuse %s" % efuse.register_name) + elif efuse.efuse_type == "bitcount": + if args.new_value is None: # find the first unset bit and set it + args.new_value = old_value + bit = 1 + while args.new_value == old_value: + args.new_value = bit | old_value + bit <<= 1 + + if args.new_value & (efuse.mask >> efuse.shift) != args.new_value: + raise esptool.FatalError("Value mask for efuse %s is 0x%x. Value 0x%x is too large." % (efuse.register_name, efuse.mask >> efuse.shift, args.new_value)) + if args.new_value | old_value != args.new_value: + print("WARNING: New value contains some bits that cannot be cleared (value will be 0x%x)" % (old_value | args.new_value)) + + confirm("Burning efuse %s (%s) 0x%x -> 0x%x" % (efuse.register_name, efuse.description, old_value, args.new_value | old_value), args) + burned_value = efuse.burn(args.new_value) + if burned_value == old_value: + raise esptool.FatalError("Efuse %s failed to burn. Protected?" % efuse.register_name) + + +def read_protect_efuse(esp, efuses, args): + efuse = efuses[args.efuse_name] + if not efuse.is_readable(): + print("Efuse %s is already read protected" % efuse.register_name) + else: + # make full list of which efuses will be disabled (ie share a read disable bit) + all_disabling = [e for e in efuses if e.read_disable_bit == efuse.read_disable_bit] + names = ", ".join(e.register_name for e in all_disabling) + confirm("Permanently read-disabling efuse%s %s" % ("s" if len(all_disabling) > 1 else "",names), args) + efuse.disable_read() + + +def write_protect_efuse(esp, efuses, args): + efuse = efuses[args.efuse_name] + if not efuse.is_writeable(): + print("]fuse %s is already write protected" % efuse.register_name) + else: + # make full list of which efuses will be disabled (ie share a write disable bit) + all_disabling = [e for e in efuses if e.write_disable_bit == efuse.write_disable_bit] + names = ", ".join(e.register_name for e in all_disabling) + confirm("Permanently write-disabling efuse%s %s" % ("s" if len(all_disabling) > 1 else "",names), args) + efuse.disable_write() + + +def burn_key(esp, efuses, args): + # check block choice + if args.block in ["flash_encryption", "BLK1"]: + block_num = 1 + elif args.block in ["secure_boot", "BLK2"]: + block_num = 2 + elif args.block == "BLK3": + block_num = 3 + else: + raise RuntimeError("args.block argument not in list!") + + num_bytes = efuses.get_block_len() + + # check keyfile + keyfile = args.keyfile + keyfile.seek(0,2) # seek t oend + size = keyfile.tell() + keyfile.seek(0) + if size != num_bytes: + raise esptool.FatalError("Incorrect key file size %d. Key file must be %d bytes (%d bits) of raw binary key data." % + (size, num_bytes, num_bytes * 8)) + + # check existing data + efuse = [e for e in efuses if e.register_name == "BLK%d" % block_num][0] + original = efuse.get_raw() + EMPTY_KEY = b'\x00' * num_bytes + if original != EMPTY_KEY: + if not args.force_write_always: + raise esptool.FatalError("Key block already has value %s." % efuse.get()) + else: + print("WARNING: Key appears to have a value already. Trying anyhow, due to --force-write-always (result will be bitwise OR of new and old values.)") + if not efuse.is_writeable(): + if not args.force_write_always: + raise esptool.FatalError("The efuse block has already been write protected.") + else: + print("WARNING: Key appears to be write protected. Trying anyhow, due to --force-write-always") + msg = "Write key in efuse block %d. " % block_num + if args.no_protect_key: + msg += "The key block will left readable and writeable (due to --no-protect-key)" + else: + msg += "The key block will be read and write protected (no further changes or readback)" + confirm(msg, args) + + new_value = keyfile.read(num_bytes) + new = efuse.burn_key(new_value) + print("Burned key data. New value: %s" % (new,)) + if not args.no_protect_key: + print("Disabling read/write to key efuse block...") + efuse.disable_write() + efuse.disable_read() + if efuse.is_readable(): + print("WARNING: Key does not appear to have been read protected. Perhaps read disable efuse is write protected?") + if efuse.is_writeable(): + print("WARNING: Key does not appear to have been write protected. Perhaps write disable efuse is write protected?") + else: + print("Key is left unprotected as per --no-protect-key argument.") + + +def burn_block_data(esp, efuses, args): + num_bytes = efuses.get_block_len() + offset = args.offset + data = args.datafile.read() + + if offset >= num_bytes: + raise RuntimeError("Invalid offset: Key block only holds %d bytes." % num_bytes) + if len(data) > num_bytes - offset: + raise RuntimeError("Data will not fit: Key block size %d bytes, data file is %d bytes" % (num_bytes, len(data))) + if efuses.coding_scheme == CODING_SCHEME_34: + if offset % 6 != 0: + raise RuntimeError("Device has 3/4 Coding Scheme. Can only write at offsets which are a multiple of 6.") + if len(data) % 6 != 0: + raise RuntimeError("Device has 3/4 Coding Scheme. Can only write data lengths which are a multiple of 6 (data is %d bytes)" % len(data)) + + efuse = [e for e in efuses if e.register_name == args.block.upper()][0] + + if not args.force_write_always and \ + efuse.get_raw() != b'\x00' * num_bytes: + raise esptool.FatalError("Efuse block already has values written.") + + if efuses.coding_scheme == CODING_SCHEME_NONE: + pad = offset % 4 + if pad != 0: # left-pad to a word boundary + data = (b'\x00' * pad) + data + offset -= pad + pad = len(data) % 4 + if pad != 0: # right-pad to a word boundary + data += (b'\x00' * (4 - pad)) + words = struct.unpack("<" + "I" * (len(data) // 4), data) + word_offset = offset // 4 + else: # CODING_SCHEME_34 + words = efuse.apply_34_encoding(data) + word_offset = (offset // 6) * 2 + + confirm("Burning efuse %s (%s) with %d bytes of data at offset %d in the block" % (efuse.register_name, efuse.description, len(data), offset), args) + efuse.burn_words(words, word_offset) + + +def set_flash_voltage(esp, efuses, args): + sdio_force = efuses["XPD_SDIO_FORCE"] + sdio_tieh = efuses["XPD_SDIO_TIEH"] + sdio_reg = efuses["XPD_SDIO_REG"] + + # check efuses aren't burned in a way which makes this impossible + if args.voltage == 'OFF' and sdio_reg.get() != 0: + raise esptool.FatalError("Can't set flash regulator to OFF as XPD_SDIO_REG efuse is already burned") + + if args.voltage == '1.8V' and sdio_tieh.get() != 0: + raise esptool.FatalError("Can't set regulator to 1.8V is XPD_SDIO_TIEH efuse is already burned") + + if args.voltage == 'OFF': + msg = """ +Disable internal flash voltage regulator (VDD_SDIO). SPI flash will need to be powered from an external source. +The following efuse is burned: XPD_SDIO_FORCE. +It is possible to later re-enable the internal regulator (%s) by burning an additional efuse +""" % ("to 3.3V" if sdio_tieh.get() != 0 else "to 1.8V or 3.3V") + elif args.voltage == '1.8V': + msg = """ +Set internal flash voltage regulator (VDD_SDIO) to 1.8V. +The following efuses are burned: XPD_SDIO_FORCE, XPD_SDIO_REG. +It is possible to later increase the voltage to 3.3V (permanently) by burning additional efuse XPD_SDIO_TIEH +""" + elif args.voltage == '3.3V': + msg = """ +Enable internal flash voltage regulator (VDD_SDIO) to 3.3V. +The following efuses are burned: XPD_SDIO_FORCE, XPD_SDIO_REG, XPD_SDIO_TIEH. +""" + + confirm(msg, args) + + sdio_force.burn(1) # Disable GPIO12 + if args.voltage != 'OFF': + sdio_reg.burn(1) # Enable internal regulator + if args.voltage == '3.3V': + sdio_tieh.burn(1) + print("VDD_SDIO setting complete.") + + +def adc_info(esp, efuses, args): + adc_vref = efuses["ADC_VREF"] + blk3_reserve = efuses["BLK3_PART_RESERVE"] + + vref_raw = adc_vref.get_raw() + if vref_raw == 0: + print("ADC VRef calibration: None (1100mV nominal)") + else: + print("ADC VRef calibration: %dmV" % adc_vref.get()) + + if blk3_reserve.get(): + print("ADC readings stored in efuse BLK3:") + print(" ADC1 Low reading (150mV): %d" % efuses["ADC1_TP_LOW"].get()) + print(" ADC1 High reading (850mV): %d" % efuses["ADC1_TP_HIGH"].get()) + print(" ADC2 Low reading (150mV): %d" % efuses["ADC2_TP_LOW"].get()) + print(" ADC2 High reading (850mV): %d" % efuses["ADC2_TP_HIGH"].get()) + + +def burn_custom_mac(esp, efuses, args): + ver_fuse = EfuseField.from_tuple(efuses, CUST_MAC_VER_EFUSE) + ver = ver_fuse.get() + new_ver = ver | 1 # Only version 1 MAC Addresses are supported yet + if ver not in [0, new_ver]: + raise esptool.FatalError("The version of the custom MAC Address is already burned ({})!".format(ver)) + mac_fuse = EfuseField.from_tuple(efuses, CUST_MAC_EFUSE) + old_mac = mac_fuse.get_raw() + new_mac = struct.pack(">Q", args.mac)[2:] # Q has 8 bytes -> removing two MSBs to form a valid 6-byte MAC Address + # It doesn't make sense ORing together the old_mac and new_mac because probably the old and the new CRC won't be + # OR-able together. + if old_mac != b'\x00' * 6: + raise esptool.FatalError("Custom MAC Address was previously burned ({})!".format(hexify(old_mac, ":"))) + old_crc = mac_fuse.get_stored_crc() + if old_crc != 0: + raise esptool.FatalError("The CRC of the custom MAC Address was previously burned ({})!".format(old_crc)) + if ver != new_ver: + confirm("Burning efuse %s (%s) 0x%x -> 0x%x" % (ver_fuse.register_name, ver_fuse.description, ver, new_ver), args) + ver_fuse.burn(new_ver) + confirm("Burning efuse %s (%s) %s -> %s" % (mac_fuse.register_name, mac_fuse.description, hexify(old_mac, ":"), hexify(new_mac, ":")), args) + mac_fuse.burn(new_mac) + + +def get_custom_mac(esp, efuses, args): + cust_mac_ver = EfuseField.from_tuple(efuses, CUST_MAC_VER_EFUSE).get() + + if cust_mac_ver > 0: + cust_mac = EfuseField.from_tuple(efuses, CUST_MAC_EFUSE).get() + print("Custom MAC Address version {}: {}".format(cust_mac_ver, cust_mac)) + else: + print("Custom MAC Address is not set in the device.") + + +def hexify(bitstring, separator=""): + try: + as_bytes = tuple(ord(b) for b in bitstring) + except TypeError: # python 3, items in bitstring already ints + as_bytes = tuple(b for b in bitstring) + return separator.join(("%02x" % b) for b in as_bytes) + + +def arg_auto_int(x): + return int(x, 0) + + +def mac_int(string): + if string.count(":") != 5: + raise argparse.ArgumentTypeError("MAC Address needs to be a 6-byte hexadecimal format separated by colons (:)!") + hexad = string.replace(":", "") + if len(hexad) != 12: + raise argparse.ArgumentTypeError("MAC Address needs to be a 6-byte hexadecimal number (12 hexadecimal characters)!") + return int(hexad, 16) + + +def main(): + parser = argparse.ArgumentParser(description='espefuse.py v%s - ESP32 efuse get/set tool' % esptool.__version__, prog='espefuse') + + parser.add_argument( + '--baud', '-b', + help='Serial port baud rate used when flashing/reading', + type=arg_auto_int, + default=os.environ.get('ESPTOOL_BAUD', esptool.ESPLoader.ESP_ROM_BAUD)) + + parser.add_argument( + '--port', '-p', + help='Serial port device', + default=os.environ.get('ESPTOOL_PORT', esptool.ESPLoader.DEFAULT_PORT)) + + parser.add_argument( + '--before', + help='What to do before connecting to the chip', + choices=['default_reset', 'no_reset', 'esp32r1', 'no_reset_no_sync'], + default='default_reset') + + parser.add_argument('--do-not-confirm', + help='Do not pause for confirmation before permanently writing efuses. Use with caution.', action='store_true') + + def add_force_write_always(p): + p.add_argument('--force-write-always', help="Write the efuse even if it looks like it's already been written, or is write protected. " + + "Note that this option can't disable write protection, or clear any bit which has already been set.", action='store_true') + + subparsers = parser.add_subparsers( + dest='operation', + help='Run espefuse.py {command} -h for additional help') + + subparsers.add_parser('dump', help='Dump raw hex values of all efuses') + subparsers.add_parser('summary', + help='Print human-readable summary of efuse values') + + p = subparsers.add_parser('burn_efuse', + help='Burn the efuse with the specified name') + p.add_argument('efuse_name', help='Name of efuse register to burn', + choices=[efuse[0] for efuse in EFUSES]) + p.add_argument('new_value', help='New value to burn (not needed for flag-type efuses', nargs='?', type=esptool.arg_auto_int) + + p = subparsers.add_parser('read_protect_efuse', + help='Disable readback for the efuse with the specified name') + p.add_argument('efuse_name', help='Name of efuse register to burn', + choices=[efuse[0] for efuse in EFUSES if efuse[6] is not None]) # only allow if read_disable_bit is not None + + p = subparsers.add_parser('write_protect_efuse', + help='Disable writing to the efuse with the specified name') + p.add_argument('efuse_name', help='Name of efuse register to burn', + choices=[efuse[0] for efuse in EFUSES]) + + p = subparsers.add_parser('burn_key', + help='Burn a 256-bit AES key to EFUSE BLK1,BLK2 or BLK3 (flash_encryption, secure_boot).') + p.add_argument('--no-protect-key', help='Disable default read- and write-protecting of the key. ' + + 'If this option is not set, once the key is flashed it cannot be read back or changed.', action='store_true') + add_force_write_always(p) + p.add_argument('block', help='Key block to burn. "flash_encryption" is an alias for BLK1, ' + + '"secure_boot" is an alias for BLK2.', choices=["secure_boot", "flash_encryption","BLK1","BLK2","BLK3"]) + p.add_argument('keyfile', help='File containing 256 bits of binary key data', type=argparse.FileType('rb')) + + p = subparsers.add_parser('burn_block_data', + help="Burn non-key data to EFUSE BLK1, BLK2 or BLK3. " + + " Don't use this command to burn key data for Flash Encryption or Secure Boot, " + + "as the byte order of keys is swapped (use burn_key).") + p.add_argument('--offset', '-o', help='Byte offset in the efuse block', type=int, default=0) + add_force_write_always(p) + p.add_argument('block', help='Efuse block to burn.', choices=["BLK1","BLK2","BLK3"]) + p.add_argument('datafile', help='File containing data to burn into the efuse block', type=argparse.FileType('rb')) + + p = subparsers.add_parser('set_flash_voltage', + help='Permanently set the internal flash voltage regulator to either 1.8V, 3.3V or OFF. ' + + 'This means GPIO12 can be high or low at reset without changing the flash voltage.') + p.add_argument('voltage', help='Voltage selection', + choices=['1.8V', '3.3V', 'OFF']) + + p = subparsers.add_parser('adc_info', + help='Display information about ADC calibration data stored in efuse.') + + p = subparsers.add_parser('burn_custom_mac', + help='Burn a 48-bit Custom MAC Address to EFUSE BLK3.') + p.add_argument('mac', help='Custom MAC Address to burn given in hexadecimal format with bytes separated by colons' + + ' (e.g. AB:CD:EF:01:02:03).', type=mac_int) + + p = subparsers.add_parser('get_custom_mac', + help='Prints the Custom MAC Address.') + + args = parser.parse_args() + print('espefuse.py v%s' % esptool.__version__) + if args.operation is None: + parser.print_help() + parser.exit(1) + + # each 'operation' is a module-level function of the same name + operation_func = globals()[args.operation] + + esp = esptool.ESP32ROM(args.port, baud=args.baud) + esp.connect(args.before) + + # dict mapping register name to its efuse object + efuses = EspEfuses(esp) + operation_func(esp, efuses, args) + + +def _main(): + try: + main() + except esptool.FatalError as e: + print('\nA fatal error occurred: %s' % e) + sys.exit(2) + + +if __name__ == '__main__': + _main() diff --git a/espsecure.py b/espsecure.py new file mode 100644 index 0000000..af79ac2 --- /dev/null +++ b/espsecure.py @@ -0,0 +1,439 @@ +#!/usr/bin/env python +# ESP32 secure boot utility +# https://github.com/themadinventor/esptool +# +# Copyright (C) 2016 Espressif Systems (Shanghai) PTE LTD +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +# Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import division, print_function + +import argparse +import hashlib +import os +import struct +import sys + +import ecdsa +import esptool +import pyaes + + +def get_chunks(source, chunk_len): + """ Returns an iterator over 'chunk_len' chunks of 'source' """ + return (source[i: i + chunk_len] for i in range(0, len(source), chunk_len)) + + +def endian_swap_words(source): + """ Endian-swap each word in 'source' bitstring """ + assert len(source) % 4 == 0 + words = "I" * (len(source) // 4) + return struct.pack("<" + words, *struct.unpack(">" + words, source)) + + +def swap_word_order(source): + """ Swap the order of the words in 'source' bitstring """ + assert len(source) % 4 == 0 + words = "I" * (len(source) // 4) + return struct.pack(words, *reversed(struct.unpack(words, source))) + + +def _load_hardware_key(keyfile): + """ Load a 256-bit key, similar to stored in efuse, from a file + + 192-bit keys will be extended to 256-bit using the same algorithm used + by hardware if 3/4 Coding Scheme is set. + """ + key = keyfile.read() + if len(key) not in [24, 32]: + raise esptool.FatalError("Key file contains wrong length (%d bytes), 24 or 32 expected." % len(key)) + if len(key) == 24: + key = key + key[8:16] + print("Using 192-bit key (extended)") + else: + print("Using 256-bit key") + + assert len(key) == 32 + return key + + +def digest_secure_bootloader(args): + """ Calculate the digest of a bootloader image, in the same way the hardware + secure boot engine would do so. Can be used with a pre-loaded key to update a + secure bootloader. """ + if args.iv is not None: + print("WARNING: --iv argument is for TESTING PURPOSES ONLY") + iv = args.iv.read(128) + else: + iv = os.urandom(128) + plaintext_image = args.image.read() + args.image.seek(0) + + # secure boot engine reads in 128 byte blocks (ie SHA512 block + # size), but also doesn't look for any appended SHA-256 digest + fw_image = esptool.ESP32FirmwareImage(args.image) + if fw_image.append_digest: + if len(plaintext_image) % 128 <= 32: + # ROM bootloader will read to the end of the 128 byte block, but not + # to the end of the SHA-256 digest at the end + new_len = len(plaintext_image) - (len(plaintext_image) % 128) + plaintext_image = plaintext_image[:new_len] + + # if image isn't 128 byte multiple then pad with 0xFF (ie unwritten flash) + # as this is what the secure boot engine will see + if len(plaintext_image) % 128 != 0: + plaintext_image += b"\xFF" * (128 - (len(plaintext_image) % 128)) + + plaintext = iv + plaintext_image + + # Secure Boot digest algorithm in hardware uses AES256 ECB to + # produce a ciphertext, then feeds output through SHA-512 to + # produce the digest. Each block in/out of ECB is reordered + # (due to hardware quirks not for security.) + + key = _load_hardware_key(args.keyfile) + aes = pyaes.AESModeOfOperationECB(key) + digest = hashlib.sha512() + + for block in get_chunks(plaintext, 16): + block = block[::-1] # reverse each input block + + cipher_block = aes.encrypt(block) + # reverse and then byte swap each word in the output block + cipher_block = cipher_block[::-1] + for block in get_chunks(cipher_block, 4): + # Python hashlib can build each SHA block internally + digest.update(block[::-1]) + + if args.output is None: + args.output = os.path.splitext(args.image.name)[0] + "-digest-0x0000.bin" + with open(args.output, "wb") as f: + f.write(iv) + digest = digest.digest() + for word in get_chunks(digest, 4): + f.write(word[::-1]) # swap word order in the result + f.write(b'\xFF' * (0x1000 - f.tell())) # pad to 0x1000 + f.write(plaintext_image) + print("digest+image written to %s" % args.output) + + +def generate_signing_key(args): + """ Generate an ECDSA signing key for signing secure boot images (post-bootloader) """ + if os.path.exists(args.keyfile): + raise esptool.FatalError("ERROR: Key file %s already exists" % args.keyfile) + sk = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p) + with open(args.keyfile, "wb") as f: + f.write(sk.to_pem()) + print("ECDSA NIST256p private key in PEM format written to %s" % args.keyfile) + + +def _load_ecdsa_signing_key(args): + sk = ecdsa.SigningKey.from_pem(args.keyfile.read()) + if sk.curve != ecdsa.NIST256p: + raise esptool.FatalError("Signing key uses incorrect curve. ESP32 Secure Boot only supports NIST256p (openssl calls this curve 'prime256v1") + return sk + + +def sign_data(args): + """ Sign a data file with a ECDSA private key, append binary signature to file contents """ + sk = _load_ecdsa_signing_key(args) + + # calculate signature of binary data + binary_content = args.datafile.read() + signature = sk.sign_deterministic(binary_content, hashlib.sha256) + + # back-verify signature + vk = sk.get_verifying_key() + vk.verify(signature, binary_content, hashlib.sha256) # throws exception on failure + + if args.output is None or os.path.abspath(args.output) == os.path.abspath(args.datafile.name): # append signature to input file + args.datafile.close() + outfile = open(args.datafile.name, "ab") + else: # write file & signature to new file + outfile = open(args.output, "wb") + outfile.write(binary_content) + outfile.write(struct.pack("I", 0)) # Version indicator, allow for different curves/formats later + outfile.write(signature) + outfile.close() + print("Signed %d bytes of data from %s with key %s" % (len(binary_content), args.datafile.name, args.keyfile.name)) + + +def verify_signature(args): + """ Verify a previously signed binary image, using the ECDSA public key """ + key_data = args.keyfile.read() + if b"-BEGIN EC PRIVATE KEY" in key_data: + sk = ecdsa.SigningKey.from_pem(key_data) + vk = sk.get_verifying_key() + elif b"-BEGIN PUBLIC KEY" in key_data: + vk = ecdsa.VerifyingKey.from_pem(key_data) + elif len(key_data) == 64: + vk = ecdsa.VerifyingKey.from_string(key_data, + curve=ecdsa.NIST256p) + else: + raise esptool.FatalError("Verification key does not appear to be an EC key in PEM format or binary EC public key data. Unsupported") + + if vk.curve != ecdsa.NIST256p: + raise esptool.FatalError("Public key uses incorrect curve. ESP32 Secure Boot only supports NIST256p (openssl calls this curve 'prime256v1") + + binary_content = args.datafile.read() + data = binary_content[0:-68] + sig_version, signature = struct.unpack("I64s", binary_content[-68:]) + if sig_version != 0: + raise esptool.FatalError("Signature block has version %d. This version of espsecure only supports version 0." % sig_version) + print("Verifying %d bytes of data" % len(data)) + try: + if vk.verify(signature, data, hashlib.sha256): + print("Signature is valid") + else: + raise esptool.FatalError("Signature is not valid") + except ecdsa.keys.BadSignatureError: + raise esptool.FatalError("Signature is not valid") + + +def extract_public_key(args): + """ Load an ECDSA private key and extract the embedded public key as raw binary data. """ + sk = _load_ecdsa_signing_key(args) + vk = sk.get_verifying_key() + args.public_keyfile.write(vk.to_string()) + print("%s public key extracted to %s" % (args.keyfile.name, args.public_keyfile.name)) + + +def digest_private_key(args): + sk = _load_ecdsa_signing_key(args) + repr(sk.to_string()) + digest = hashlib.sha256() + digest.update(sk.to_string()) + result = digest.digest() + if args.keylen == '192': + result = result[0:24] + args.digest_file.write(result) + print("SHA-256 digest of private key %s%s written to %s" % (args.keyfile.name, + "" if args.keylen == '256' + else " (truncated to 192 bits)", + args.digest_file.name)) + + +# flash encryption key tweaking pattern: the nth bit of the key is +# flipped if the kth bit in the flash offset is set, where mapping +# from n to k is provided by this list of 'n' bit offsets (range k) +_FLASH_ENCRYPTION_TWEAK_PATTERN = [ + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 12, 11, 10, 9, 8, 7, 6, 5, + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 10, 9, 8, 7, 6, 5, + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, + 8, 7, 6, 5 +] +assert len(_FLASH_ENCRYPTION_TWEAK_PATTERN) == 256 + + +def _flash_encryption_tweak_range(flash_crypt_config=0xF): + """ Return a list of the bit indexes that the "key tweak" applies to, + as determined by the FLASH_CRYPT_CONFIG 4 bit efuse value. + """ + tweak_range = [] + if (flash_crypt_config & 1) != 0: + tweak_range += range(67) + if (flash_crypt_config & 2) != 0: + tweak_range += range(67, 132) + if (flash_crypt_config & 4) != 0: + tweak_range += range(132, 195) + if (flash_crypt_config & 8) != 0: + tweak_range += range(195, 256) + return tweak_range + + +def _flash_encryption_tweak_key(key, offset, tweak_range): + """Apply XOR "tweak" values to the key, derived from flash offset + 'offset'. This matches the ESP32 hardware flash encryption. + + tweak_range is a list of bit indexes to apply the tweak to, as + generated by _flash_encryption_tweak_range() from the + FLASH_CRYPT_CONFIG efuse value. + + Return tweaked key + """ + if esptool.PYTHON2: + key = [ord(k) for k in key] + else: + key = list(key) + assert len(key) == 32 + + offset_bits = [(offset & (1 << x)) != 0 for x in range(24)] + + for bit in tweak_range: + if offset_bits[_FLASH_ENCRYPTION_TWEAK_PATTERN[bit]]: + # note that each byte has a backwards bit order, compared + # to how it is looked up in the tweak pattern table + key[bit // 8] ^= 1 << (7 - (bit % 8)) + + if esptool.PYTHON2: + return b"".join(chr(k) for k in key) + else: + return bytes(key) + + +def generate_flash_encryption_key(args): + args.key_file.write(os.urandom(32)) + + +def _flash_encryption_operation(output_file, input_file, flash_address, keyfile, flash_crypt_conf, do_decrypt): + key = _load_hardware_key(keyfile) + + if flash_address % 16 != 0: + raise esptool.FatalError("Starting flash address 0x%x must be a multiple of 16" % flash_address) + + if flash_crypt_conf == 0: + print("WARNING: Setting FLASH_CRYPT_CONF to zero is not recommended") + tweak_range = _flash_encryption_tweak_range(flash_crypt_conf) + + aes = None + while True: + block_offs = flash_address + input_file.tell() + block = input_file.read(16) + if len(block) == 0: + break + elif len(block) < 16: + if do_decrypt: + raise esptool.FatalError("Data length is not a multiple of 16 bytes") + pad = 16 - len(block) + block = block + os.urandom(pad) + print("Note: Padding with %d bytes of random data (encrypted data must be multiple of 16 bytes long)" % pad) + + if (block_offs % 32 == 0) or aes is None: + # each bit of the flash encryption key is XORed with tweak bits derived from the offset of 32 byte block of flash + block_key = _flash_encryption_tweak_key(key, block_offs, tweak_range) + aes = pyaes.AESModeOfOperationECB(block_key) + + block = block[::-1] # reverse input block byte order + + # note AES is used inverted for flash encryption, so + # "decrypting" flash uses AES encrypt algorithm and vice + # versa. (This does not weaken AES.) + if do_decrypt: + block = aes.encrypt(block) + else: + block = aes.decrypt(block) + + block = block[::-1] # reverse output block byte order + output_file.write(block) + + +def decrypt_flash_data(args): + return _flash_encryption_operation(args.output, args.encrypted_file, args.address, args.keyfile, args.flash_crypt_conf, True) + + +def encrypt_flash_data(args): + return _flash_encryption_operation(args.output, args.plaintext_file, args.address, args.keyfile, args.flash_crypt_conf, False) + + +def main(): + parser = argparse.ArgumentParser(description='espsecure.py v%s - ESP32 Secure Boot & Flash Encryption tool' % esptool.__version__, prog='espsecure') + + subparsers = parser.add_subparsers( + dest='operation', + help='Run espsecure.py {command} -h for additional help') + + p = subparsers.add_parser('digest_secure_bootloader', + help='Take a bootloader binary image and a secure boot key, and output a combined digest+binary ' + + 'suitable for flashing along with the precalculated secure boot key.') + p.add_argument('--keyfile', '-k', help="256 bit key for secure boot digest.", type=argparse.FileType('rb'), required=True) + p.add_argument('--output', '-o', help="Output file for signed digest image.") + p.add_argument('--iv', help="128 byte IV file. Supply a file for testing purposes only, if not supplied an IV will be randomly generated.", + type=argparse.FileType('rb')) + p.add_argument('image', help="Bootloader image file to calculate digest from", type=argparse.FileType('rb')) + + p = subparsers.add_parser('generate_signing_key', + help='Generate a private key for signing secure boot images. Key file is generated in PEM format, ' + + 'and contains a ECDSA NIST256p private key and matching public key.') + p.add_argument('keyfile', help="Filename for private key file (embedded public key)") + + p = subparsers.add_parser('sign_data', + help='Sign a data file for use with secure boot. Signing algorithm is determinsitic ECDSA w/ SHA-512.') + p.add_argument('--keyfile', '-k', help="Private key file for signing. Key is in PEM format, ECDSA NIST256p curve. " + + "generate_signing_key command can be used to generate a suitable signing key.", type=argparse.FileType('rb'), required=True) + p.add_argument('--output', '-o', help="Output file for signed digest image. Default is to append signature to existing file.") + p.add_argument('datafile', help="Data file to sign.", type=argparse.FileType('rb')) + + p = subparsers.add_parser('verify_signature', + help='Verify a data file previously signed by "sign_data", using the public key.') + p.add_argument('--keyfile', '-k', help="Public key file for verification. Can be private or public key in PEM format, " + + "or a binary public key produced by extract_public_key command.", + type=argparse.FileType('rb'), required=True) + p.add_argument('datafile', help="Signed data file to verify signature.", type=argparse.FileType('rb')) + + p = subparsers.add_parser('extract_public_key', + help='Extract the public verification key for signatures, save it as a raw binary file.') + p.add_argument('--keyfile', '-k', help="Private key file (PEM format) to extract the public verification key from.", type=argparse.FileType('rb'), + required=True) + p.add_argument('public_keyfile', help="File to save new public key into", type=argparse.FileType('wb')) + + p = subparsers.add_parser('digest_private_key', help='Generate an SHA-256 digest of the private signing key. ' + + 'This can be used as a reproducible secure bootloader or flash encryption key.') + p.add_argument('--keyfile', '-k', help="Private key file (PEM format) to generate a digest from.", type=argparse.FileType('rb'), + required=True) + p.add_argument('--keylen', '-l', help="Length of private key digest file to generate (in bits).", + choices=['192','256'], default='256') + p.add_argument('digest_file', help="File to write 32 byte digest into", type=argparse.FileType('wb')) + + p = subparsers.add_parser('generate_flash_encryption_key', help='Generate a development-use 32 byte flash encryption key with random data.') + p.add_argument('key_file', help="File to write 32 byte digest into", type=argparse.FileType('wb')) + + p = subparsers.add_parser('decrypt_flash_data', help='Decrypt some data read from encrypted flash (using known key)') + p.add_argument('encrypted_file', help="File with encrypted flash contents", type=argparse.FileType('rb')) + p.add_argument('--keyfile', '-k', help="File with flash encryption key", type=argparse.FileType('rb'), + required=True) + p.add_argument('--output', '-o', help="Output file for plaintext data.", type=argparse.FileType('wb'), + required=True) + p.add_argument('--address', '-a', help="Address offset in flash that file was read from.", required=True, type=esptool.arg_auto_int) + p.add_argument('--flash_crypt_conf', help="Override FLASH_CRYPT_CONF efuse value (default is 0XF).", required=False, default=0xF, type=esptool.arg_auto_int) + + p = subparsers.add_parser('encrypt_flash_data', help='Encrypt some data suitable for encrypted flash (using known key)') + p.add_argument('--keyfile', '-k', help="File with flash encryption key", type=argparse.FileType('rb'), + required=True) + p.add_argument('--output', '-o', help="Output file for encrypted data.", type=argparse.FileType('wb'), + required=True) + p.add_argument('--address', '-a', help="Address offset in flash where file will be flashed.", required=True, type=esptool.arg_auto_int) + p.add_argument('--flash_crypt_conf', help="Override FLASH_CRYPT_CONF efuse value (default is 0XF).", required=False, default=0xF, type=esptool.arg_auto_int) + p.add_argument('plaintext_file', help="File with plaintext content for encrypting", type=argparse.FileType('rb')) + + args = parser.parse_args() + print('espsecure.py v%s' % esptool.__version__) + if args.operation is None: + parser.print_help() + parser.exit(1) + + # each 'operation' is a module-level function of the same name + operation_func = globals()[args.operation] + operation_func(args) + + +def _main(): + try: + main() + except esptool.FatalError as e: + print('\nA fatal error occurred: %s' % e) + sys.exit(2) + + +if __name__ == '__main__': + _main() diff --git a/esptool.py b/esptool.py new file mode 100644 index 0000000..6bfa1f7 --- /dev/null +++ b/esptool.py @@ -0,0 +1,2959 @@ +#!/usr/bin/env python +# +# ESP8266 & ESP32 ROM Bootloader Utility +# Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) PTE LTD, other contributors as noted. +# https://github.com/espressif/esptool +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +# Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from __future__ import division, print_function + +import argparse +import base64 +import binascii +import copy +import hashlib +import inspect +import io +import os +import shlex +import struct +import sys +import time +import zlib +import string + +try: + import serial +except ImportError: + print("Pyserial is not installed for %s. Check the README for installation instructions." % (sys.executable)) + raise + +# check 'serial' is 'pyserial' and not 'serial' https://github.com/espressif/esptool/issues/269 +try: + if "serialization" in serial.__doc__ and "deserialization" in serial.__doc__: + raise ImportError(""" +esptool.py depends on pyserial, but there is a conflict with a currently installed package named 'serial'. + +You may be able to work around this by 'pip uninstall serial; pip install pyserial' \ +but this may break other installed Python software that depends on 'serial'. + +There is no good fix for this right now, apart from configuring virtualenvs. \ +See https://github.com/espressif/esptool/issues/269#issuecomment-385298196 for discussion of the underlying issue(s).""") +except TypeError: + pass # __doc__ returns None for pyserial + +try: + import serial.tools.list_ports as list_ports +except ImportError: + print("The installed version (%s) of pyserial appears to be too old for esptool.py (Python interpreter %s). " + "Check the README for installation instructions." % (sys.VERSION, sys.executable)) + raise + +__version__ = "2.6" + +MAX_UINT32 = 0xffffffff +MAX_UINT24 = 0xffffff + +DEFAULT_TIMEOUT = 3 # timeout for most flash operations +START_FLASH_TIMEOUT = 20 # timeout for starting flash (may perform erase) +CHIP_ERASE_TIMEOUT = 120 # timeout for full chip erase +MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2 # longest any command can run +SYNC_TIMEOUT = 0.1 # timeout for syncing with bootloader +MD5_TIMEOUT_PER_MB = 8 # timeout (per megabyte) for calculating md5sum +ERASE_REGION_TIMEOUT_PER_MB = 30 # timeout (per megabyte) for erasing a region +MEM_END_ROM_TIMEOUT = 0.05 # special short timeout for ESP_MEM_END, as it may never respond +DEFAULT_SERIAL_WRITE_TIMEOUT = 10 # timeout for serial port write + + +def timeout_per_mb(seconds_per_mb, size_bytes): + """ Scales timeouts which are size-specific """ + result = seconds_per_mb * (size_bytes / 1e6) + if result < DEFAULT_TIMEOUT: + return DEFAULT_TIMEOUT + return result + + +DETECTED_FLASH_SIZES = {0x12: '256KB', 0x13: '512KB', 0x14: '1MB', + 0x15: '2MB', 0x16: '4MB', 0x17: '8MB', 0x18: '16MB'} + + +def check_supported_function(func, check_func): + """ + Decorator implementation that wraps a check around an ESPLoader + bootloader function to check if it's supported. + + This is used to capture the multidimensional differences in + functionality between the ESP8266 & ESP32 ROM loaders, and the + software stub that runs on both. Not possible to do this cleanly + via inheritance alone. + """ + def inner(*args, **kwargs): + obj = args[0] + if check_func(obj): + return func(*args, **kwargs) + else: + raise NotImplementedInROMError(obj, func) + return inner + + +def stub_function_only(func): + """ Attribute for a function only supported in the software stub loader """ + return check_supported_function(func, lambda o: o.IS_STUB) + + +def stub_and_esp32_function_only(func): + """ Attribute for a function only supported by software stubs or ESP32 ROM """ + return check_supported_function(func, lambda o: o.IS_STUB or o.CHIP_NAME == "ESP32") + + +PYTHON2 = sys.version_info[0] < 3 # True if on pre-Python 3 + +# Function to return nth byte of a bitstring +# Different behaviour on Python 2 vs 3 +if PYTHON2: + def byte(bitstr, index): + return ord(bitstr[index]) +else: + def byte(bitstr, index): + return bitstr[index] + +# Provide a 'basestring' class on Python 3 +try: + basestring +except NameError: + basestring = str + + +def esp8266_function_only(func): + """ Attribute for a function only supported on ESP8266 """ + return check_supported_function(func, lambda o: o.CHIP_NAME == "ESP8266") + + +class ESPLoader(object): + """ Base class providing access to ESP ROM & software stub bootloaders. + Subclasses provide ESP8266 & ESP32 specific functionality. + + Don't instantiate this base class directly, either instantiate a subclass or + call ESPLoader.detect_chip() which will interrogate the chip and return the + appropriate subclass instance. + + """ + CHIP_NAME = "Espressif device" + IS_STUB = False + + DEFAULT_PORT = "/dev/ttyUSB0" + + # Commands supported by ESP8266 ROM bootloader + ESP_FLASH_BEGIN = 0x02 + ESP_FLASH_DATA = 0x03 + ESP_FLASH_END = 0x04 + ESP_MEM_BEGIN = 0x05 + ESP_MEM_END = 0x06 + ESP_MEM_DATA = 0x07 + ESP_SYNC = 0x08 + ESP_WRITE_REG = 0x09 + ESP_READ_REG = 0x0a + + # Some comands supported by ESP32 ROM bootloader (or -8266 w/ stub) + ESP_SPI_SET_PARAMS = 0x0B + ESP_SPI_ATTACH = 0x0D + ESP_CHANGE_BAUDRATE = 0x0F + ESP_FLASH_DEFL_BEGIN = 0x10 + ESP_FLASH_DEFL_DATA = 0x11 + ESP_FLASH_DEFL_END = 0x12 + ESP_SPI_FLASH_MD5 = 0x13 + + # Some commands supported by stub only + ESP_ERASE_FLASH = 0xD0 + ESP_ERASE_REGION = 0xD1 + ESP_READ_FLASH = 0xD2 + ESP_RUN_USER_CODE = 0xD3 + + # Maximum block sized for RAM and Flash writes, respectively. + ESP_RAM_BLOCK = 0x1800 + + FLASH_WRITE_SIZE = 0x400 + + # Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want. + ESP_ROM_BAUD = 115200 + + # First byte of the application image + ESP_IMAGE_MAGIC = 0xe9 + + # Initial state for the checksum routine + ESP_CHECKSUM_MAGIC = 0xef + + # Flash sector size, minimum unit of erase. + FLASH_SECTOR_SIZE = 0x1000 + + UART_DATA_REG_ADDR = 0x60000078 + + # Memory addresses + IROM_MAP_START = 0x40200000 + IROM_MAP_END = 0x40300000 + + # The number of bytes in the UART response that signify command status + STATUS_BYTES_LENGTH = 2 + + def __init__(self, port=DEFAULT_PORT, baud=ESP_ROM_BAUD, trace_enabled=False): + """Base constructor for ESPLoader bootloader interaction + + Don't call this constructor, either instantiate ESP8266ROM + or ESP32ROM, or use ESPLoader.detect_chip(). + + This base class has all of the instance methods for bootloader + functionality supported across various chips & stub + loaders. Subclasses replace the functions they don't support + with ones which throw NotImplementedInROMError(). + + """ + if isinstance(port, basestring): + self._port = serial.serial_for_url(port) + else: + self._port = port + self._slip_reader = slip_reader(self._port, self.trace) + # setting baud rate in a separate step is a workaround for + # CH341 driver on some Linux versions (this opens at 9600 then + # sets), shouldn't matter for other platforms/drivers. See + # https://github.com/espressif/esptool/issues/44#issuecomment-107094446 + self._set_port_baudrate(baud) + self._trace_enabled = trace_enabled + # set write timeout, to prevent esptool blocked at write forever. + try: + self._port.write_timeout = DEFAULT_SERIAL_WRITE_TIMEOUT + except NotImplementedError: + # no write timeout for RFC2217 ports + # need to set the property back to None or it will continue to fail + self._port.write_timeout = None + + def _set_port_baudrate(self, baud): + try: + self._port.baudrate = baud + except IOError: + raise FatalError("Failed to set baud rate %d. The driver may not support this rate." % baud) + + @staticmethod + def detect_chip(port=DEFAULT_PORT, baud=ESP_ROM_BAUD, connect_mode='default_reset', trace_enabled=False): + """ Use serial access to detect the chip type. + + We use the UART's datecode register for this, it's mapped at + the same address on ESP8266 & ESP32 so we can use one + memory read and compare to the datecode register for each chip + type. + + This routine automatically performs ESPLoader.connect() (passing + connect_mode parameter) as part of querying the chip. + """ + detect_port = ESPLoader(port, baud, trace_enabled=trace_enabled) + detect_port.connect(connect_mode) + try: + print('Detecting chip type...', end='') + sys.stdout.flush() + date_reg = detect_port.read_reg(ESPLoader.UART_DATA_REG_ADDR) + + for cls in [ESP8266ROM, ESP32ROM]: + if date_reg == cls.DATE_REG_VALUE: + # don't connect a second time + inst = cls(detect_port._port, baud, trace_enabled=trace_enabled) + print(' %s' % inst.CHIP_NAME, end='') + return inst + finally: + print('') # end line + raise FatalError("Unexpected UART datecode value 0x%08x. Failed to autodetect chip type." % date_reg) + + """ Read a SLIP packet from the serial port """ + def read(self): + return next(self._slip_reader) + + """ Write bytes to the serial port while performing SLIP escaping """ + def write(self, packet): + buf = b'\xc0' \ + + (packet.replace(b'\xdb',b'\xdb\xdd').replace(b'\xc0',b'\xdb\xdc')) \ + + b'\xc0' + self.trace("Write %d bytes: %s", len(buf), HexFormatter(buf)) + self._port.write(buf) + + def trace(self, message, *format_args): + if self._trace_enabled: + now = time.time() + try: + + delta = now - self._last_trace + except AttributeError: + delta = 0.0 + self._last_trace = now + prefix = "TRACE +%.3f " % delta + print(prefix + (message % format_args)) + + """ Calculate checksum of a blob, as it is defined by the ROM """ + @staticmethod + def checksum(data, state=ESP_CHECKSUM_MAGIC): + for b in data: + if type(b) is int: # python 2/3 compat + state ^= b + else: + state ^= ord(b) + + return state + + """ Send a request and read the response """ + def command(self, op=None, data=b"", chk=0, wait_response=True, timeout=DEFAULT_TIMEOUT): + saved_timeout = self._port.timeout + new_timeout = min(timeout, MAX_TIMEOUT) + if new_timeout != saved_timeout: + self._port.timeout = new_timeout + + try: + if op is not None: + self.trace("command op=0x%02x data len=%s wait_response=%d timeout=%.3f data=%s", + op, len(data), 1 if wait_response else 0, timeout, HexFormatter(data)) + pkt = struct.pack(b' self.STATUS_BYTES_LENGTH: + return data[:-self.STATUS_BYTES_LENGTH] + else: # otherwise, just return the 'val' field which comes from the reply header (this is used by read_reg) + return val + + def flush_input(self): + self._port.flushInput() + self._slip_reader = slip_reader(self._port, self.trace) + + def sync(self): + self.command(self.ESP_SYNC, b'\x07\x07\x12\x20' + 32 * b'\x55', + timeout=SYNC_TIMEOUT) + for i in range(7): + self.command() + + def _setDTR(self, state): + self._port.setDTR(state) + + def _setRTS(self, state): + self._port.setRTS(state) + # Work-around for adapters on Windows using the usbser.sys driver: + # generate a dummy change to DTR so that the set-control-line-state + # request is sent with the updated RTS state and the same DTR state + self._port.setDTR(self._port.dtr) + + def _connect_attempt(self, mode='default_reset', esp32r0_delay=False): + """ A single connection attempt, with esp32r0 workaround options """ + # esp32r0_delay is a workaround for bugs with the most common auto reset + # circuit and Windows, if the EN pin on the dev board does not have + # enough capacitance. + # + # Newer dev boards shouldn't have this problem (higher value capacitor + # on the EN pin), and ESP32 revision 1 can't use this workaround as it + # relies on a silicon bug. + # + # Details: https://github.com/espressif/esptool/issues/136 + last_error = None + + # If we're doing no_sync, we're likely communicating as a pass through + # with an intermediate device to the ESP32 + if mode == "no_reset_no_sync": + return last_error + + # issue reset-to-bootloader: + # RTS = either CH_PD/EN or nRESET (both active low = chip in reset + # DTR = GPIO0 (active low = boot to flasher) + # + # DTR & RTS are active low signals, + # ie True = pin @ 0V, False = pin @ VCC. + if mode != 'no_reset': + self._setDTR(False) # IO0=HIGH + self._setRTS(True) # EN=LOW, chip in reset + time.sleep(0.1) + if esp32r0_delay: + # Some chips are more likely to trigger the esp32r0 + # watchdog reset silicon bug if they're held with EN=LOW + # for a longer period + time.sleep(1.2) + self._setDTR(True) # IO0=LOW + self._setRTS(False) # EN=HIGH, chip out of reset + if esp32r0_delay: + # Sleep longer after reset. + # This workaround only works on revision 0 ESP32 chips, + # it exploits a silicon bug spurious watchdog reset. + time.sleep(0.4) # allow watchdog reset to occur + time.sleep(0.05) + self._setDTR(False) # IO0=HIGH, done + + for _ in range(5): + try: + self.flush_input() + self._port.flushOutput() + self.sync() + return None + except FatalError as e: + if esp32r0_delay: + print('_', end='') + else: + print('.', end='') + sys.stdout.flush() + time.sleep(0.05) + last_error = e + return last_error + + def connect(self, mode='default_reset'): + """ Try connecting repeatedly until successful, or giving up """ + print('Connecting...', end='') + sys.stdout.flush() + last_error = None + + try: + for _ in range(7): + last_error = self._connect_attempt(mode=mode, esp32r0_delay=False) + if last_error is None: + return + last_error = self._connect_attempt(mode=mode, esp32r0_delay=True) + if last_error is None: + return + finally: + print('') # end 'Connecting...' line + raise FatalError('Failed to connect to %s: %s' % (self.CHIP_NAME, last_error)) + + """ Read memory address in target """ + def read_reg(self, addr): + # we don't call check_command here because read_reg() function is called + # when detecting chip type, and the way we check for success (STATUS_BYTES_LENGTH) is different + # for different chip types (!) + val, data = self.command(self.ESP_READ_REG, struct.pack(' start: + raise FatalError(("Software loader is resident at 0x%08x-0x%08x. " + + "Can't load binary at overlapping address range 0x%08x-0x%08x. " + + "Either change binary loading address, or use the --no-stub " + + "option to disable the software loader.") % (start, end, load_start, load_end)) + + return self.check_command("enter RAM download mode", self.ESP_MEM_BEGIN, + struct.pack(' length: + raise FatalError('Read more than expected') + digest_frame = self.read() + if len(digest_frame) != 16: + raise FatalError('Expected digest, got: %s' % hexify(digest_frame)) + expected_digest = hexify(digest_frame).upper() + digest = hashlib.md5(data).hexdigest().upper() + if digest != expected_digest: + raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest)) + return data + + def flash_spi_attach(self, hspi_arg): + """Send SPI attach command to enable the SPI flash pins + + ESP8266 ROM does this when you send flash_begin, ESP32 ROM + has it as a SPI command. + """ + # last 3 bytes in ESP_SPI_ATTACH argument are reserved values + arg = struct.pack(' 0: + self.write_reg(SPI_MOSI_DLEN_REG, mosi_bits - 1) + if miso_bits > 0: + self.write_reg(SPI_MISO_DLEN_REG, miso_bits - 1) + else: + + def set_data_lengths(mosi_bits, miso_bits): + SPI_DATA_LEN_REG = SPI_USR1_REG + SPI_MOSI_BITLEN_S = 17 + SPI_MISO_BITLEN_S = 8 + mosi_mask = 0 if (mosi_bits == 0) else (mosi_bits - 1) + miso_mask = 0 if (miso_bits == 0) else (miso_bits - 1) + self.write_reg(SPI_DATA_LEN_REG, + (miso_mask << SPI_MISO_BITLEN_S) | ( + mosi_mask << SPI_MOSI_BITLEN_S)) + + # SPI peripheral "command" bitmasks for SPI_CMD_REG + SPI_CMD_USR = (1 << 18) + + # shift values + SPI_USR2_DLEN_SHIFT = 28 + + if read_bits > 32: + raise FatalError("Reading more than 32 bits back from a SPI flash operation is unsupported") + if len(data) > 64: + raise FatalError("Writing more than 64 bytes of data with one SPI command is unsupported") + + data_bits = len(data) * 8 + old_spi_usr = self.read_reg(SPI_USR_REG) + old_spi_usr2 = self.read_reg(SPI_USR2_REG) + flags = SPI_USR_COMMAND + if read_bits > 0: + flags |= SPI_USR_MISO + if data_bits > 0: + flags |= SPI_USR_MOSI + set_data_lengths(data_bits, read_bits) + self.write_reg(SPI_USR_REG, flags) + self.write_reg(SPI_USR2_REG, + (7 << SPI_USR2_DLEN_SHIFT) | spiflash_command) + if data_bits == 0: + self.write_reg(SPI_W0_REG, 0) # clear data register before we read it + else: + data = pad_to(data, 4, b'\00') # pad to 32-bit multiple + words = struct.unpack("I" * (len(data) // 4), data) + next_reg = SPI_W0_REG + for word in words: + self.write_reg(next_reg, word) + next_reg += 4 + self.write_reg(SPI_CMD_REG, SPI_CMD_USR) + + def wait_done(): + for _ in range(10): + if (self.read_reg(SPI_CMD_REG) & SPI_CMD_USR) == 0: + return + raise FatalError("SPI command did not complete in time") + wait_done() + + status = self.read_reg(SPI_W0_REG) + # restore some SPI controller registers + self.write_reg(SPI_USR_REG, old_spi_usr) + self.write_reg(SPI_USR2_REG, old_spi_usr2) + return status + + def read_status(self, num_bytes=2): + """Read up to 24 bits (num_bytes) of SPI flash status register contents + via RDSR, RDSR2, RDSR3 commands + + Not all SPI flash supports all three commands. The upper 1 or 2 + bytes may be 0xFF. + """ + SPIFLASH_RDSR = 0x05 + SPIFLASH_RDSR2 = 0x35 + SPIFLASH_RDSR3 = 0x15 + + status = 0 + shift = 0 + for cmd in [SPIFLASH_RDSR, SPIFLASH_RDSR2, SPIFLASH_RDSR3][0:num_bytes]: + status += self.run_spiflash_command(cmd, read_bits=8) << shift + shift += 8 + return status + + def write_status(self, new_status, num_bytes=2, set_non_volatile=False): + """Write up to 24 bits (num_bytes) of new status register + + num_bytes can be 1, 2 or 3. + + Not all flash supports the additional commands to write the + second and third byte of the status register. When writing 2 + bytes, esptool also sends a 16-byte WRSR command (as some + flash types use this instead of WRSR2.) + + If the set_non_volatile flag is set, non-volatile bits will + be set as well as volatile ones (WREN used instead of WEVSR). + + """ + SPIFLASH_WRSR = 0x01 + SPIFLASH_WRSR2 = 0x31 + SPIFLASH_WRSR3 = 0x11 + SPIFLASH_WEVSR = 0x50 + SPIFLASH_WREN = 0x06 + SPIFLASH_WRDI = 0x04 + + enable_cmd = SPIFLASH_WREN if set_non_volatile else SPIFLASH_WEVSR + + # try using a 16-bit WRSR (not supported by all chips) + # this may be redundant, but shouldn't hurt + if num_bytes == 2: + self.run_spiflash_command(enable_cmd) + self.run_spiflash_command(SPIFLASH_WRSR, struct.pack(">= 8 + + self.run_spiflash_command(SPIFLASH_WRDI) + + def hard_reset(self): + self._setRTS(True) # EN->LOW + time.sleep(0.1) + self._setRTS(False) + + def soft_reset(self, stay_in_bootloader): + if not self.IS_STUB: + if stay_in_bootloader: + return # ROM bootloader is already in bootloader! + else: + # 'run user code' is as close to a soft reset as we can do + self.flash_begin(0, 0) + self.flash_finish(False) + else: + if stay_in_bootloader: + # soft resetting from the stub loader + # will re-load the ROM bootloader + self.flash_begin(0, 0) + self.flash_finish(True) + elif self.CHIP_NAME != "ESP8266": + raise FatalError("Soft resetting is currently only supported on ESP8266") + else: + # running user code from stub loader requires some hacks + # in the stub loader + self.command(self.ESP_RUN_USER_CODE, wait_response=False) + + +class ESP8266ROM(ESPLoader): + """ Access class for ESP8266 ROM bootloader + """ + CHIP_NAME = "ESP8266" + IS_STUB = False + + DATE_REG_VALUE = 0x00062000 + + # OTP ROM addresses + ESP_OTP_MAC0 = 0x3ff00050 + ESP_OTP_MAC1 = 0x3ff00054 + ESP_OTP_MAC3 = 0x3ff0005c + + SPI_REG_BASE = 0x60000200 + SPI_W0_OFFS = 0x40 + SPI_HAS_MOSI_DLEN_REG = False + + FLASH_SIZES = { + '512KB':0x00, + '256KB':0x10, + '1MB':0x20, + '2MB':0x30, + '4MB':0x40, + '2MB-c1': 0x50, + '4MB-c1':0x60, + '8MB':0x80, + '16MB':0x90, + } + + BOOTLOADER_FLASH_OFFSET = 0 + + def get_efuses(self): + # Return the 128 bits of ESP8266 efuse as a single Python integer + return (self.read_reg(0x3ff0005c) << 96 | + self.read_reg(0x3ff00058) << 64 | + self.read_reg(0x3ff00054) << 32 | + self.read_reg(0x3ff00050)) + + def get_chip_description(self): + efuses = self.get_efuses() + is_8285 = (efuses & ((1 << 4) | 1 << 80)) != 0 # One or the other efuse bit is set for ESP8285 + return "ESP8285" if is_8285 else "ESP8266EX" + + def get_chip_features(self): + features = ["WiFi"] + if self.get_chip_description() == "ESP8285": + features += ["Embedded Flash"] + return features + + def flash_spi_attach(self, hspi_arg): + if self.IS_STUB: + super(ESP8266ROM, self).flash_spi_attach(hspi_arg) + else: + # ESP8266 ROM has no flash_spi_attach command in serial protocol, + # but flash_begin will do it + self.flash_begin(0, 0) + + def flash_set_parameters(self, size): + # not implemented in ROM, but OK to silently skip for ROM + if self.IS_STUB: + super(ESP8266ROM, self).flash_set_parameters(size) + + def chip_id(self): + """ Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() function """ + id0 = self.read_reg(self.ESP_OTP_MAC0) + id1 = self.read_reg(self.ESP_OTP_MAC1) + return (id0 >> 24) | ((id1 & MAX_UINT24) << 8) + + def read_mac(self): + """ Read MAC from OTP ROM """ + mac0 = self.read_reg(self.ESP_OTP_MAC0) + mac1 = self.read_reg(self.ESP_OTP_MAC1) + mac3 = self.read_reg(self.ESP_OTP_MAC3) + if (mac3 != 0): + oui = ((mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff) + elif ((mac1 >> 16) & 0xff) == 0: + oui = (0x18, 0xfe, 0x34) + elif ((mac1 >> 16) & 0xff) == 1: + oui = (0xac, 0xd0, 0x74) + else: + raise FatalError("Unknown OUI") + return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff) + + def get_erase_size(self, offset, size): + """ Calculate an erase size given a specific size in bytes. + + Provides a workaround for the bootloader erase bug.""" + + sectors_per_block = 16 + sector_size = self.FLASH_SECTOR_SIZE + num_sectors = (size + sector_size - 1) // sector_size + start_sector = offset // sector_size + + head_sectors = sectors_per_block - (start_sector % sectors_per_block) + if num_sectors < head_sectors: + head_sectors = num_sectors + + if num_sectors < 2 * head_sectors: + return (num_sectors + 1) // 2 * sector_size + else: + return (num_sectors - head_sectors) * sector_size + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError("Overriding VDDSDIO setting only applies to ESP32") + + +class ESP8266StubLoader(ESP8266ROM): + """ Access class for ESP8266 stub loader, runs on top of ROM. + """ + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + IS_STUB = True + + def __init__(self, rom_loader): + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.flush_input() # resets _slip_reader + + def get_erase_size(self, offset, size): + return size # stub doesn't have same size bug as ROM loader + + +ESP8266ROM.STUB_CLASS = ESP8266StubLoader + + +class ESP32ROM(ESPLoader): + """Access class for ESP32 ROM bootloader + + """ + CHIP_NAME = "ESP32" + IS_STUB = False + + DATE_REG_VALUE = 0x15122500 + + IROM_MAP_START = 0x400d0000 + IROM_MAP_END = 0x40400000 + DROM_MAP_START = 0x3F400000 + DROM_MAP_END = 0x3F800000 + + # ESP32 uses a 4 byte status reply + STATUS_BYTES_LENGTH = 4 + + SPI_REG_BASE = 0x60002000 + EFUSE_REG_BASE = 0x6001a000 + + SPI_W0_OFFS = 0x80 + SPI_HAS_MOSI_DLEN_REG = True + + FLASH_SIZES = { + '1MB':0x00, + '2MB':0x10, + '4MB':0x20, + '8MB':0x30, + '16MB':0x40 + } + + BOOTLOADER_FLASH_OFFSET = 0x1000 + + OVERRIDE_VDDSDIO_CHOICES = ["1.8V", "1.9V", "OFF"] + + def get_chip_description(self): + word3 = self.read_efuse(3) + chip_ver_rev1 = (word3 >> 15) & 0x1 + pkg_version = (word3 >> 9) & 0x07 + + chip_name = { + 0: "ESP32D0WDQ6", + 1: "ESP32D0WDQ5", + 2: "ESP32D2WDQ5", + 5: "ESP32-PICO-D4", + }.get(pkg_version, "unknown ESP32") + + return "%s (revision %d)" % (chip_name, chip_ver_rev1) + + def get_chip_features(self): + features = ["WiFi"] + word3 = self.read_efuse(3) + + # names of variables in this section are lowercase + # versions of EFUSE names as documented in TRM and + # ESP-IDF efuse_reg.h + + chip_ver_dis_bt = word3 & (1 << 1) + if chip_ver_dis_bt == 0: + features += ["BT"] + + chip_ver_dis_app_cpu = word3 & (1 << 0) + if chip_ver_dis_app_cpu: + features += ["Single Core"] + else: + features += ["Dual Core"] + + chip_cpu_freq_rated = word3 & (1 << 13) + if chip_cpu_freq_rated: + chip_cpu_freq_low = word3 & (1 << 12) + if chip_cpu_freq_low: + features += ["160MHz"] + else: + features += ["240MHz"] + + pkg_version = (word3 >> 9) & 0x07 + if pkg_version in [2, 4, 5]: + features += ["Embedded Flash"] + + word4 = self.read_efuse(4) + adc_vref = (word4 >> 8) & 0x1F + if adc_vref: + features += ["VRef calibration in efuse"] + + blk3_part_res = word3 >> 14 & 0x1 + if blk3_part_res: + features += ["BLK3 partially reserved"] + + word6 = self.read_efuse(6) + coding_scheme = word6 & 0x3 + features += ["Coding Scheme %s" % { + 0: "None", + 1: "3/4", + 2: "Repeat (UNSUPPORTED)", + 3: "Invalid"}[coding_scheme]] + + return features + + def read_efuse(self, n): + """ Read the nth word of the ESP3x EFUSE region. """ + return self.read_reg(self.EFUSE_REG_BASE + (4 * n)) + + def chip_id(self): + raise NotSupportedError(self, "chip_id") + + def read_mac(self): + """ Read MAC from EFUSE region """ + words = [self.read_efuse(2), self.read_efuse(1)] + bitstring = struct.pack(">II", *words) + bitstring = bitstring[2:8] # trim the 2 byte CRC + try: + return tuple(ord(b) for b in bitstring) + except TypeError: # Python 3, bitstring elements are already bytes + return tuple(bitstring) + + def get_erase_size(self, offset, size): + return size + + def override_vddsdio(self, new_voltage): + new_voltage = new_voltage.upper() + if new_voltage not in self.OVERRIDE_VDDSDIO_CHOICES: + raise FatalError("The only accepted VDDSDIO overrides are '1.8V', '1.9V' and 'OFF'") + RTC_CNTL_SDIO_CONF_REG = 0x3ff48074 + RTC_CNTL_XPD_SDIO_REG = (1 << 31) + RTC_CNTL_DREFH_SDIO_M = (3 << 29) + RTC_CNTL_DREFM_SDIO_M = (3 << 27) + RTC_CNTL_DREFL_SDIO_M = (3 << 25) + # RTC_CNTL_SDIO_TIEH = (1 << 23) # not used here, setting TIEH=1 would set 3.3V output, not safe for esptool.py to do + RTC_CNTL_SDIO_FORCE = (1 << 22) + RTC_CNTL_SDIO_PD_EN = (1 << 21) + + reg_val = RTC_CNTL_SDIO_FORCE # override efuse setting + reg_val |= RTC_CNTL_SDIO_PD_EN + if new_voltage != "OFF": + reg_val |= RTC_CNTL_XPD_SDIO_REG # enable internal LDO + if new_voltage == "1.9V": + reg_val |= (RTC_CNTL_DREFH_SDIO_M | RTC_CNTL_DREFM_SDIO_M | RTC_CNTL_DREFL_SDIO_M) # boost voltage + self.write_reg(RTC_CNTL_SDIO_CONF_REG, reg_val) + print("VDDSDIO regulator set to %s" % new_voltage) + + +class ESP32StubLoader(ESP32ROM): + """ Access class for ESP32 stub loader, runs on top of ROM. + """ + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.flush_input() # resets _slip_reader + + +ESP32ROM.STUB_CLASS = ESP32StubLoader + + +class ESPBOOTLOADER(object): + """ These are constants related to software ESP bootloader, working with 'v2' image files """ + + # First byte of the "v2" application image + IMAGE_V2_MAGIC = 0xea + + # First 'segment' value in a "v2" application image, appears to be a constant version value? + IMAGE_V2_SEGMENT = 4 + + +def LoadFirmwareImage(chip, filename): + """ Load a firmware image. Can be for ESP8266 or ESP32. ESP8266 images will be examined to determine if they are + original ROM firmware images (ESP8266ROMFirmwareImage) or "v2" OTA bootloader images. + + Returns a BaseFirmwareImage subclass, either ESP8266ROMFirmwareImage (v1) or ESP8266V2FirmwareImage (v2). + """ + with open(filename, 'rb') as f: + if chip.lower() == 'esp32': + return ESP32FirmwareImage(f) + else: # Otherwise, ESP8266 so look at magic to determine the image type + magic = ord(f.read(1)) + f.seek(0) + if magic == ESPLoader.ESP_IMAGE_MAGIC: + return ESP8266ROMFirmwareImage(f) + elif magic == ESPBOOTLOADER.IMAGE_V2_MAGIC: + return ESP8266V2FirmwareImage(f) + else: + raise FatalError("Invalid image magic number: %d" % magic) + + +class ImageSegment(object): + """ Wrapper class for a segment in an ESP image + (very similar to a section in an ELFImage also) """ + def __init__(self, addr, data, file_offs=None): + self.addr = addr + self.data = data + self.file_offs = file_offs + self.include_in_checksum = True + if self.addr != 0: + self.pad_to_alignment(4) # pad all "real" ImageSegments 4 byte aligned length + + def copy_with_new_addr(self, new_addr): + """ Return a new ImageSegment with same data, but mapped at + a new address. """ + return ImageSegment(new_addr, self.data, 0) + + def split_image(self, split_len): + """ Return a new ImageSegment which splits "split_len" bytes + from the beginning of the data. Remaining bytes are kept in + this segment object (and the start address is adjusted to match.) """ + result = copy.copy(self) + result.data = self.data[:split_len] + self.data = self.data[split_len:] + self.addr += split_len + self.file_offs = None + result.file_offs = None + return result + + def __repr__(self): + r = "len 0x%05x load 0x%08x" % (len(self.data), self.addr) + if self.file_offs is not None: + r += " file_offs 0x%08x" % (self.file_offs) + return r + + def pad_to_alignment(self, alignment): + self.data = pad_to(self.data, alignment, b'\x00') + + +class ELFSection(ImageSegment): + """ Wrapper class for a section in an ELF image, has a section + name as well as the common properties of an ImageSegment. """ + def __init__(self, name, addr, data): + super(ELFSection, self).__init__(addr, data) + self.name = name.decode("utf-8") + + def __repr__(self): + return "%s %s" % (self.name, super(ELFSection, self).__repr__()) + + +class BaseFirmwareImage(object): + SEG_HEADER_LEN = 8 + SHA256_DIGEST_LEN = 32 + + """ Base class with common firmware image functions """ + def __init__(self): + self.segments = [] + self.entrypoint = 0 + self.elf_sha256 = None + self.elf_sha256_offset = 0 + + def load_common_header(self, load_file, expected_magic): + (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack(' 16: + raise FatalError('Invalid segment count %d (max 16). Usually this indicates a linker script problem.' % len(self.segments)) + + def load_segment(self, f, is_irom_segment=False): + """ Load the next segment from the image file """ + file_offs = f.tell() + (offset, size) = struct.unpack(' 0x40200000 or offset < 0x3ffe0000 or size > 65536: + print('WARNING: Suspicious segment 0x%x, length %d' % (offset, size)) + + def maybe_patch_segment_data(self, f, segment_data): + """If SHA256 digest of the ELF file needs to be inserted into this segment, do so. Returns segment data.""" + segment_len = len(segment_data) + file_pos = f.tell() + if self.elf_sha256_offset >= file_pos and self.elf_sha256_offset < file_pos + segment_len: + # SHA256 digest needs to be patched into this segment, + # calculate offset of the digest inside the segment. + patch_offset = self.elf_sha256_offset - file_pos + # Sanity checks + if patch_offset < self.SEG_HEADER_LEN or patch_offset + self.SHA256_DIGEST_LEN > segment_len: + raise FatalError('Can not place SHA256 digest on segment boundary' + + '(elf_sha256_offset=%d, file_pos=%d, segment_size=%d)' % + (self.elf_sha256_offset, file_pos, segment_len)) + assert(len(self.elf_sha256) == self.SHA256_DIGEST_LEN) + # offset relative to the data part + patch_offset -= self.SEG_HEADER_LEN + segment_data = segment_data[0:patch_offset] + self.elf_sha256 + \ + segment_data[patch_offset + self.SHA256_DIGEST_LEN:] + return segment_data + + def save_segment(self, f, segment, checksum=None): + """ Save the next segment to the image file, return next checksum value if provided """ + segment_data = self.maybe_patch_segment_data(f, segment.data) + f.write(struct.pack(' 0: + if len(irom_segments) != 1: + raise FatalError('Found %d segments that could be irom0. Bad ELF file?' % len(irom_segments)) + return irom_segments[0] + return None + + def get_non_irom_segments(self): + irom_segment = self.get_irom_segment() + return [s for s in self.segments if s != irom_segment] + + +class ESP8266ROMFirmwareImage(BaseFirmwareImage): + """ 'Version 1' firmware image, segments loaded directly by the ROM bootloader. """ + + ROM_LOADER = ESP8266ROM + + def __init__(self, load_file=None): + super(ESP8266ROMFirmwareImage, self).__init__() + self.flash_mode = 0 + self.flash_size_freq = 0 + self.version = 1 + + if load_file is not None: + segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC) + + for _ in range(segments): + self.load_segment(load_file) + self.checksum = self.read_checksum(load_file) + + self.verify() + + def default_output_name(self, input_file): + """ Derive a default output name from the ELF name. """ + return input_file + '-' + + def save(self, basename): + """ Save a set of V1 images for flashing. Parameter is a base filename. """ + # IROM data goes in its own plain binary file + irom_segment = self.get_irom_segment() + if irom_segment is not None: + with open("%s0x%05x.bin" % (basename, irom_segment.addr - ESP8266ROM.IROM_MAP_START), "wb") as f: + f.write(irom_segment.data) + + # everything but IROM goes at 0x00000 in an image file + normal_segments = self.get_non_irom_segments() + with open("%s0x00000.bin" % basename, 'wb') as f: + self.write_common_header(f, normal_segments) + checksum = ESPLoader.ESP_CHECKSUM_MAGIC + for segment in normal_segments: + checksum = self.save_segment(f, segment, checksum) + self.append_checksum(f, checksum) + + +class ESP8266V2FirmwareImage(BaseFirmwareImage): + """ 'Version 2' firmware image, segments loaded by software bootloader stub + (ie Espressif bootloader or rboot) + """ + + ROM_LOADER = ESP8266ROM + + def __init__(self, load_file=None): + super(ESP8266V2FirmwareImage, self).__init__() + self.version = 2 + if load_file is not None: + segments = self.load_common_header(load_file, ESPBOOTLOADER.IMAGE_V2_MAGIC) + if segments != ESPBOOTLOADER.IMAGE_V2_SEGMENT: + # segment count is not really segment count here, but we expect to see '4' + print('Warning: V2 header has unexpected "segment" count %d (usually 4)' % segments) + + # irom segment comes before the second header + # + # the file is saved in the image with a zero load address + # in the header, so we need to calculate a load address + irom_segment = self.load_segment(load_file, True) + irom_segment.addr = 0 # for actual mapped addr, add ESP8266ROM.IROM_MAP_START + flashing_addr + 8 + irom_segment.include_in_checksum = False + + first_flash_mode = self.flash_mode + first_flash_size_freq = self.flash_size_freq + first_entrypoint = self.entrypoint + # load the second header + + segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC) + + if first_flash_mode != self.flash_mode: + print('WARNING: Flash mode value in first header (0x%02x) disagrees with second (0x%02x). Using second value.' + % (first_flash_mode, self.flash_mode)) + if first_flash_size_freq != self.flash_size_freq: + print('WARNING: Flash size/freq value in first header (0x%02x) disagrees with second (0x%02x). Using second value.' + % (first_flash_size_freq, self.flash_size_freq)) + if first_entrypoint != self.entrypoint: + print('WARNING: Entrypoint address in first header (0x%08x) disagrees with second header (0x%08x). Using second value.' + % (first_entrypoint, self.entrypoint)) + + # load all the usual segments + for _ in range(segments): + self.load_segment(load_file) + self.checksum = self.read_checksum(load_file) + + self.verify() + + def default_output_name(self, input_file): + """ Derive a default output name from the ELF name. """ + irom_segment = self.get_irom_segment() + if irom_segment is not None: + irom_offs = irom_segment.addr - ESP8266ROM.IROM_MAP_START + else: + irom_offs = 0 + return "%s-0x%05x.bin" % (os.path.splitext(input_file)[0], + irom_offs & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)) + + def save(self, filename): + with open(filename, 'wb') as f: + # Save first header for irom0 segment + f.write(struct.pack(b' 0: + last_addr = flash_segments[0].addr + for segment in flash_segments[1:]: + if segment.addr // self.IROM_ALIGN == last_addr // self.IROM_ALIGN: + raise FatalError(("Segment loaded at 0x%08x lands in same 64KB flash mapping as segment loaded at 0x%08x. " + + "Can't generate binary. Suggest changing linker script or ELF to merge sections.") % + (segment.addr, last_addr)) + last_addr = segment.addr + + def get_alignment_data_needed(segment): + # Actual alignment (in data bytes) required for a segment header: positioned so that + # after we write the next 8 byte header, file_offs % IROM_ALIGN == segment.addr % IROM_ALIGN + # + # (this is because the segment's vaddr may not be IROM_ALIGNed, more likely is aligned + # IROM_ALIGN+0x18 to account for the binary file header + align_past = (segment.addr % self.IROM_ALIGN) - self.SEG_HEADER_LEN + pad_len = (self.IROM_ALIGN - (f.tell() % self.IROM_ALIGN)) + align_past + if pad_len == 0 or pad_len == self.IROM_ALIGN: + return 0 # already aligned + + # subtract SEG_HEADER_LEN a second time, as the padding block has a header as well + pad_len -= self.SEG_HEADER_LEN + if pad_len < 0: + pad_len += self.IROM_ALIGN + return pad_len + + # try to fit each flash segment on a 64kB aligned boundary + # by padding with parts of the non-flash segments... + while len(flash_segments) > 0: + segment = flash_segments[0] + pad_len = get_alignment_data_needed(segment) + if pad_len > 0: # need to pad + if len(ram_segments) > 0 and pad_len > self.SEG_HEADER_LEN: + pad_segment = ram_segments[0].split_image(pad_len) + if len(ram_segments[0].data) == 0: + ram_segments.pop(0) + else: + pad_segment = ImageSegment(0, b'\x00' * pad_len, f.tell()) + checksum = self.save_segment(f, pad_segment, checksum) + total_segments += 1 + else: + # write the flash segment + assert (f.tell() + 8) % self.IROM_ALIGN == segment.addr % self.IROM_ALIGN + checksum = self.save_flash_segment(f, segment, checksum) + flash_segments.pop(0) + total_segments += 1 + + # flash segments all written, so write any remaining RAM segments + for segment in ram_segments: + checksum = self.save_segment(f, segment, checksum) + total_segments += 1 + + if self.secure_pad: + # pad the image so that after signing it will end on a a 64KB boundary. + # This ensures all mapped flash content will be verified. + if not self.append_digest: + raise FatalError("secure_pad only applies if a SHA-256 digest is also appended to the image") + align_past = (f.tell() + self.SEG_HEADER_LEN) % self.IROM_ALIGN + # 16 byte aligned checksum (force the alignment to simplify calculations) + checksum_space = 16 + # after checksum: SHA-256 digest + (to be added by signing process) version, signature + 12 trailing bytes due to alignment + space_after_checksum = 32 + 4 + 64 + 12 + pad_len = (self.IROM_ALIGN - align_past - checksum_space - space_after_checksum) % self.IROM_ALIGN + pad_segment = ImageSegment(0, b'\x00' * pad_len, f.tell()) + + checksum = self.save_segment(f, pad_segment, checksum) + total_segments += 1 + + # done writing segments + self.append_checksum(f, checksum) + image_length = f.tell() + + if self.secure_pad: + assert ((image_length + space_after_checksum) % self.IROM_ALIGN) == 0 + + # kinda hacky: go back to the initial header and write the new segment count + # that includes padding segments. This header is not checksummed + f.seek(1) + try: + f.write(chr(total_segments)) + except TypeError: # Python 3 + f.write(bytes([total_segments])) + + if self.append_digest: + # calculate the SHA256 of the whole file and append it + f.seek(0) + digest = hashlib.sha256() + digest.update(f.read(image_length)) + f.write(digest.digest()) + + with open(filename, 'wb') as real_file: + real_file.write(f.getvalue()) + + def save_flash_segment(self, f, segment, checksum=None): + """ Save the next segment to the image file, return next checksum value if provided """ + segment_end_pos = f.tell() + len(segment.data) + self.SEG_HEADER_LEN + segment_len_remainder = segment_end_pos % self.IROM_ALIGN + if segment_len_remainder < 0x24: + # Work around a bug in ESP-IDF 2nd stage bootloader, that it didn't map the + # last MMU page, if an IROM/DROM segment was < 0x24 bytes over the page boundary. + segment.data += b'\x00' * (0x24 - segment_len_remainder) + return self.save_segment(f, segment, checksum) + + def load_extended_header(self, load_file): + def split_byte(n): + return (n & 0x0F, (n >> 4) & 0x0F) + + fields = list(struct.unpack(self.EXTENDED_HEADER_STRUCT_FMT, load_file.read(16))) + + self.wp_pin = fields[0] + + # SPI pin drive stengths are two per byte + self.clk_drv, self.q_drv = split_byte(fields[1]) + self.d_drv, self.cs_drv = split_byte(fields[2]) + self.hd_drv, self.wp_drv = split_byte(fields[3]) + + if fields[15] in [0, 1]: + self.append_digest = (fields[15] == 1) + else: + raise RuntimeError("Invalid value for append_digest field (0x%02x). Should be 0 or 1.", fields[15]) + + # remaining fields in the middle should all be zero + if any(f for f in fields[4:15] if f != 0): + print("Warning: some reserved header fields have non-zero values. This image may be from a newer esptool.py?") + + def save_extended_header(self, save_file): + def join_byte(ln,hn): + return (ln & 0x0F) + ((hn & 0x0F) << 4) + + append_digest = 1 if self.append_digest else 0 + + fields = [self.wp_pin, + join_byte(self.clk_drv, self.q_drv), + join_byte(self.d_drv, self.cs_drv), + join_byte(self.hd_drv, self.wp_drv)] + fields += [0] * 11 + fields += [append_digest] + + packed = struct.pack(self.EXTENDED_HEADER_STRUCT_FMT, *fields) + save_file.write(packed) + + +class ELFFile(object): + SEC_TYPE_PROGBITS = 0x01 + SEC_TYPE_STRTAB = 0x03 + + LEN_SEC_HEADER = 0x28 + + def __init__(self, name): + # Load sections from the ELF file + self.name = name + with open(self.name, 'rb') as f: + self._read_elf_file(f) + + def get_section(self, section_name): + for s in self.sections: + if s.name == section_name: + return s + raise ValueError("No section %s in ELF file" % section_name) + + def _read_elf_file(self, f): + # read the ELF file header + LEN_FILE_HEADER = 0x34 + try: + (ident,_type,machine,_version, + self.entrypoint,_phoff,shoff,_flags, + _ehsize, _phentsize,_phnum, shentsize, + shnum,shstrndx) = struct.unpack("<16sHHLLLLLHHHHHH", f.read(LEN_FILE_HEADER)) + except struct.error as e: + raise FatalError("Failed to read a valid ELF header from %s: %s" % (self.name, e)) + + if byte(ident, 0) != 0x7f or ident[1:4] != b'ELF': + raise FatalError("%s has invalid ELF magic header" % self.name) + if machine != 0x5e: + raise FatalError("%s does not appear to be an Xtensa ELF file. e_machine=%04x" % (self.name, machine)) + if shentsize != self.LEN_SEC_HEADER: + raise FatalError("%s has unexpected section header entry size 0x%x (not 0x28)" % (self.name, shentsize, self.LEN_SEC_HEADER)) + if shnum == 0: + raise FatalError("%s has 0 section headers" % (self.name)) + self._read_sections(f, shoff, shnum, shstrndx) + + def _read_sections(self, f, section_header_offs, section_header_count, shstrndx): + f.seek(section_header_offs) + len_bytes = section_header_count * self.LEN_SEC_HEADER + section_header = f.read(len_bytes) + if len(section_header) == 0: + raise FatalError("No section header found at offset %04x in ELF file." % section_header_offs) + if len(section_header) != (len_bytes): + raise FatalError("Only read 0x%x bytes from section header (expected 0x%x.) Truncated ELF file?" % (len(section_header), len_bytes)) + + # walk through the section header and extract all sections + section_header_offsets = range(0, len(section_header), self.LEN_SEC_HEADER) + + def read_section_header(offs): + name_offs,sec_type,_flags,lma,sec_offs,size = struct.unpack_from(" 0] + self.sections = prog_sections + + def sha256(self): + # return SHA256 hash of the input ELF file + sha256 = hashlib.sha256() + with open(self.name, 'rb') as f: + sha256.update(f.read()) + return sha256.digest() + + +def slip_reader(port, trace_function): + """Generator to read SLIP packets from a serial port. + Yields one full SLIP packet at a time, raises exception on timeout or invalid data. + + Designed to avoid too many calls to serial.read(1), which can bog + down on slow systems. + """ + partial_packet = None + in_escape = False + while True: + waiting = port.inWaiting() + read_bytes = port.read(1 if waiting == 0 else waiting) + if read_bytes == b'': + waiting_for = "header" if partial_packet is None else "content" + trace_function("Timed out waiting for packet %s", waiting_for) + raise FatalError("Timed out waiting for packet %s" % waiting_for) + trace_function("Read %d bytes: %s", len(read_bytes), HexFormatter(read_bytes)) + for b in read_bytes: + if type(b) is int: + b = bytes([b]) # python 2/3 compat + + if partial_packet is None: # waiting for packet header + if b == b'\xc0': + partial_packet = b"" + else: + trace_function("Read invalid data: %s", HexFormatter(read_bytes)) + trace_function("Remaining data in serial buffer: %s", HexFormatter(port.read(port.inWaiting()))) + raise FatalError('Invalid head of packet (0x%s)' % hexify(b)) + elif in_escape: # part-way through escape sequence + in_escape = False + if b == b'\xdc': + partial_packet += b'\xc0' + elif b == b'\xdd': + partial_packet += b'\xdb' + else: + trace_function("Read invalid data: %s", HexFormatter(read_bytes)) + trace_function("Remaining data in serial buffer: %s", HexFormatter(port.read(port.inWaiting()))) + raise FatalError('Invalid SLIP escape (0xdb, 0x%s)' % (hexify(b))) + elif b == b'\xdb': # start of escape sequence + in_escape = True + elif b == b'\xc0': # end of packet + trace_function("Received full packet: %s", HexFormatter(partial_packet)) + yield partial_packet + partial_packet = None + else: # normal byte in packet + partial_packet += b + + +def arg_auto_int(x): + return int(x, 0) + + +def div_roundup(a, b): + """ Return a/b rounded up to nearest integer, + equivalent result to int(math.ceil(float(int(a)) / float(int(b))), only + without possible floating point accuracy errors. + """ + return (int(a) + int(b) - 1) // int(b) + + +def align_file_position(f, size): + """ Align the position in the file to the next block of specified size """ + align = (size - 1) - (f.tell() % size) + f.seek(align, 1) + + +def flash_size_bytes(size): + """ Given a flash size of the type passed in args.flash_size + (ie 512KB or 1MB) then return the size in bytes. + """ + if "MB" in size: + return int(size[:size.index("MB")]) * 1024 * 1024 + elif "KB" in size: + return int(size[:size.index("KB")]) * 1024 + else: + raise FatalError("Unknown size %s" % size) + + +def hexify(s, uppercase=True): + format_str = '%02X' if uppercase else '%02x' + if not PYTHON2: + return ''.join(format_str % c for c in s) + else: + return ''.join(format_str % ord(c) for c in s) + + +class HexFormatter(object): + """ + Wrapper class which takes binary data in its constructor + and returns a hex string as it's __str__ method. + + This is intended for "lazy formatting" of trace() output + in hex format. Avoids overhead (significant on slow computers) + of generating long hex strings even if tracing is disabled. + + Note that this doesn't save any overhead if passed as an + argument to "%", only when passed to trace() + + If auto_split is set (default), any long line (> 16 bytes) will be + printed as separately indented lines, with ASCII decoding at the end + of each line. + """ + def __init__(self, binary_string, auto_split=True): + self._s = binary_string + self._auto_split = auto_split + + def __str__(self): + if self._auto_split and len(self._s) > 16: + result = "" + s = self._s + while len(s) > 0: + line = s[:16] + ascii_line = "".join(c if (c == ' ' or (c in string.printable and c not in string.whitespace)) + else '.' for c in line.decode('ascii', 'replace')) + s = s[16:] + result += "\n %-16s %-16s | %s" % (hexify(line[:8], False), hexify(line[8:], False), ascii_line) + return result + else: + return hexify(self._s, False) + + +def pad_to(data, alignment, pad_character=b'\xFF'): + """ Pad to the next alignment boundary """ + pad_mod = len(data) % alignment + if pad_mod != 0: + data += pad_character * (alignment - pad_mod) + return data + + +class FatalError(RuntimeError): + """ + Wrapper class for runtime errors that aren't caused by internal bugs, but by + ESP8266 responses or input content. + """ + def __init__(self, message): + RuntimeError.__init__(self, message) + + @staticmethod + def WithResult(message, result): + """ + Return a fatal error object that appends the hex values of + 'result' as a string formatted argument. + """ + message += " (result was %s)" % hexify(result) + return FatalError(message) + + +class NotImplementedInROMError(FatalError): + """ + Wrapper class for the error thrown when a particular ESP bootloader function + is not implemented in the ROM bootloader. + """ + def __init__(self, bootloader, func): + FatalError.__init__(self, "%s ROM does not support function %s." % (bootloader.CHIP_NAME, func.__name__)) + + +class NotSupportedError(FatalError): + def __init__(self, esp, function_name): + FatalError.__init__(self, "Function %s is not supported for %s." % (function_name, esp.CHIP_NAME)) + +# "Operation" commands, executable at command line. One function each +# +# Each function takes either two args (, ) or a single +# argument. + + +def load_ram(esp, args): + image = LoadFirmwareImage(esp.CHIP_NAME, args.filename) + + print('RAM boot...') + for seg in image.segments: + size = len(seg.data) + print('Downloading %d bytes at %08x...' % (size, seg.addr), end=' ') + sys.stdout.flush() + esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, seg.addr) + + seq = 0 + while len(seg.data) > 0: + esp.mem_block(seg.data[0:esp.ESP_RAM_BLOCK], seq) + seg.data = seg.data[esp.ESP_RAM_BLOCK:] + seq += 1 + print('done!') + + print('All segments done, executing at %08x' % image.entrypoint) + esp.mem_finish(image.entrypoint) + + +def read_mem(esp, args): + print('0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address))) + + +def write_mem(esp, args): + esp.write_reg(args.address, args.value, args.mask, 0) + print('Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address)) + + +def dump_mem(esp, args): + with open(args.filename, 'wb') as f: + for i in range(args.size // 4): + d = esp.read_reg(args.address + (i * 4)) + f.write(struct.pack(b'> 16 + args.flash_size = DETECTED_FLASH_SIZES.get(size_id) + if args.flash_size is None: + print('Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x), defaulting to 4MB' % (flash_id, size_id)) + args.flash_size = '4MB' + else: + print('Auto-detected Flash size:', args.flash_size) + + +def _update_image_flash_params(esp, address, args, image): + """ Modify the flash mode & size bytes if this looks like an executable bootloader image """ + if len(image) < 8: + return image # not long enough to be a bootloader image + + # unpack the (potential) image header + magic, _, flash_mode, flash_size_freq = struct.unpack("BBBB", image[:4]) + if address != esp.BOOTLOADER_FLASH_OFFSET or magic != esp.ESP_IMAGE_MAGIC: + return image # not flashing a bootloader, so don't modify this + + if args.flash_mode != 'keep': + flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] + + flash_freq = flash_size_freq & 0x0F + if args.flash_freq != 'keep': + flash_freq = {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] + + flash_size = flash_size_freq & 0xF0 + if args.flash_size != 'keep': + flash_size = esp.parse_flash_size_arg(args.flash_size) + + flash_params = struct.pack(b'BB', flash_mode, flash_size + flash_freq) + if flash_params != image[2:4]: + print('Flash params set to 0x%04x' % struct.unpack(">H", flash_params)) + image = image[0:2] + flash_params + image[4:] + return image + + +def write_flash(esp, args): + # set args.compress based on default behaviour: + # -> if either --compress or --no-compress is set, honour that + # -> otherwise, set --compress unless --no-stub is set + if args.compress is None and not args.no_compress: + args.compress = not args.no_stub + + # verify file sizes fit in flash + flash_end = flash_size_bytes(args.flash_size) + for address, argfile in args.addr_filename: + argfile.seek(0,2) # seek to end + if address + argfile.tell() > flash_end: + raise FatalError(("File %s (length %d) at offset %d will not fit in %d bytes of flash. " + + "Use --flash-size argument, or change flashing address.") + % (argfile.name, argfile.tell(), address, flash_end)) + argfile.seek(0) + + if args.erase_all: + erase_flash(esp, args) + + for address, argfile in args.addr_filename: + if args.no_stub: + print('Erasing flash...') + image = pad_to(argfile.read(), 4) + if len(image) == 0: + print('WARNING: File %s is empty' % argfile.name) + continue + image = _update_image_flash_params(esp, address, args, image) + calcmd5 = hashlib.md5(image).hexdigest() + uncsize = len(image) + if args.compress: + uncimage = image + image = zlib.compress(uncimage, 9) + ratio = uncsize / len(image) + blocks = esp.flash_defl_begin(uncsize, len(image), address) + else: + ratio = 1.0 + blocks = esp.flash_begin(uncsize, address) + argfile.seek(0) # in case we need it again + seq = 0 + written = 0 + t = time.time() + while len(image) > 0: + print('\rWriting at 0x%08x... (%d %%)' % (address + seq * esp.FLASH_WRITE_SIZE, 100 * (seq + 1) // blocks), end='') + sys.stdout.flush() + block = image[0:esp.FLASH_WRITE_SIZE] + if args.compress: + esp.flash_defl_block(block, seq, timeout=DEFAULT_TIMEOUT * ratio * 2) + else: + # Pad the last block + block = block + b'\xff' * (esp.FLASH_WRITE_SIZE - len(block)) + esp.flash_block(block, seq) + image = image[esp.FLASH_WRITE_SIZE:] + seq += 1 + written += len(block) + t = time.time() - t + speed_msg = "" + if args.compress: + if t > 0.0: + speed_msg = " (effective %.1f kbit/s)" % (uncsize / t * 8 / 1000) + print('\rWrote %d bytes (%d compressed) at 0x%08x in %.1f seconds%s...' % (uncsize, written, address, t, speed_msg)) + else: + if t > 0.0: + speed_msg = " (%.1f kbit/s)" % (written / t * 8 / 1000) + print('\rWrote %d bytes at 0x%08x in %.1f seconds%s...' % (written, address, t, speed_msg)) + try: + res = esp.flash_md5sum(address, uncsize) + if res != calcmd5: + print('File md5: %s' % calcmd5) + print('Flash md5: %s' % res) + print('MD5 of 0xFF is %s' % (hashlib.md5(b'\xFF' * uncsize).hexdigest())) + raise FatalError("MD5 of file does not match data in flash!") + else: + print('Hash of data verified.') + except NotImplementedInROMError: + pass + + print('\nLeaving...') + + if esp.IS_STUB: + # skip sending flash_finish to ROM loader here, + # as it causes the loader to exit and run user code + esp.flash_begin(0, 0) + if args.compress: + esp.flash_defl_finish(False) + else: + esp.flash_finish(False) + + if args.verify: + print('Verifying just-written flash...') + print('(This option is deprecated, flash contents are now always read back after flashing.)') + verify_flash(esp, args) + + +def image_info(args): + image = LoadFirmwareImage(args.chip, args.filename) + print('Image version: %d' % image.version) + print('Entry point: %08x' % image.entrypoint if image.entrypoint != 0 else 'Entry point not set') + print('%d segments' % len(image.segments)) + print + idx = 0 + for seg in image.segments: + idx += 1 + print('Segment %d: %r' % (idx, seg)) + calc_checksum = image.calculate_checksum() + print('Checksum: %02x (%s)' % (image.checksum, + 'valid' if image.checksum == calc_checksum else 'invalid - calculated %02x' % calc_checksum)) + try: + digest_msg = 'Not appended' + if image.append_digest: + is_valid = image.stored_digest == image.calc_digest + digest_msg = "%s (%s)" % (hexify(image.calc_digest).lower(), + "valid" if is_valid else "invalid") + print('Validation Hash: %s' % digest_msg) + except AttributeError: + pass # ESP8266 image has no append_digest field + + +def make_image(args): + image = ESP8266ROMFirmwareImage() + if len(args.segfile) == 0: + raise FatalError('No segments specified') + if len(args.segfile) != len(args.segaddr): + raise FatalError('Number of specified files does not match number of specified addresses') + for (seg, addr) in zip(args.segfile, args.segaddr): + with open(seg, 'rb') as f: + data = f.read() + image.segments.append(ImageSegment(addr, data)) + image.entrypoint = args.entrypoint + image.save(args.output) + + +def elf2image(args): + e = ELFFile(args.input) + if args.chip == 'auto': # Default to ESP8266 for backwards compatibility + print("Creating image for ESP8266...") + args.chip = 'esp8266' + + if args.chip == 'esp32': + image = ESP32FirmwareImage() + image.secure_pad = args.secure_pad + elif args.version == '1': # ESP8266 + image = ESP8266ROMFirmwareImage() + else: + image = ESP8266V2FirmwareImage() + image.entrypoint = e.entrypoint + image.segments = e.sections # ELFSection is a subclass of ImageSegment + image.flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] + image.flash_size_freq = image.ROM_LOADER.FLASH_SIZES[args.flash_size] + image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] + + if args.elf_sha256_offset: + image.elf_sha256 = e.sha256() + image.elf_sha256_offset = args.elf_sha256_offset + + image.verify() + + if args.output is None: + args.output = image.default_output_name(args.input) + image.save(args.output) + + +def read_mac(esp, args): + mac = esp.read_mac() + + def print_mac(label, mac): + print('%s: %s' % (label, ':'.join(map(lambda x: '%02x' % x, mac)))) + print_mac("MAC", mac) + + +def chip_id(esp, args): + try: + chipid = esp.chip_id() + print('Chip ID: 0x%08x' % chipid) + except NotSupportedError: + print('Warning: %s has no Chip ID. Reading MAC instead.' % esp.CHIP_NAME) + read_mac(esp, args) + + +def erase_flash(esp, args): + print('Erasing flash (this may take a while)...') + t = time.time() + esp.erase_flash() + print('Chip erase completed successfully in %.1fs' % (time.time() - t)) + + +def erase_region(esp, args): + print('Erasing region (may be slow depending on size)...') + t = time.time() + esp.erase_region(args.address, args.size) + print('Erase completed successfully in %.1f seconds.' % (time.time() - t)) + + +def run(esp, args): + esp.run() + + +def flash_id(esp, args): + flash_id = esp.flash_id() + print('Manufacturer: %02x' % (flash_id & 0xff)) + flid_lowbyte = (flash_id >> 16) & 0xFF + print('Device: %02x%02x' % ((flash_id >> 8) & 0xff, flid_lowbyte)) + print('Detected flash size: %s' % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown"))) + + +def read_flash(esp, args): + if args.no_progress: + flash_progress = None + else: + def flash_progress(progress, length): + msg = '%d (%d %%)' % (progress, progress * 100.0 / length) + padding = '\b' * len(msg) + if progress == length: + padding = '\n' + sys.stdout.write(msg + padding) + sys.stdout.flush() + t = time.time() + data = esp.read_flash(args.address, args.size, flash_progress) + t = time.time() - t + print('\rRead %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...' + % (len(data), args.address, t, len(data) / t * 8 / 1000)) + with open(args.filename, 'wb') as f: + f.write(data) + + +def verify_flash(esp, args): + differences = False + + for address, argfile in args.addr_filename: + image = pad_to(argfile.read(), 4) + argfile.seek(0) # rewind in case we need it again + + image = _update_image_flash_params(esp, address, args, image) + + image_size = len(image) + print('Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s...' % (image_size, image_size, address, argfile.name)) + # Try digest first, only read if there are differences. + digest = esp.flash_md5sum(address, image_size) + expected_digest = hashlib.md5(image).hexdigest() + if digest == expected_digest: + print('-- verify OK (digest matched)') + continue + else: + differences = True + if getattr(args, 'diff', 'no') != 'yes': + print('-- verify FAILED (digest mismatch)') + continue + + flash = esp.read_flash(address, image_size) + assert flash != image + diff = [i for i in range(image_size) if flash[i] != image[i]] + print('-- verify FAILED: %d differences, first @ 0x%08x' % (len(diff), address + diff[0])) + for d in diff: + flash_byte = flash[d] + image_byte = image[d] + if PYTHON2: + flash_byte = ord(flash_byte) + image_byte = ord(image_byte) + print(' %08x %02x %02x' % (address + d, flash_byte, image_byte)) + if differences: + raise FatalError("Verify failed.") + + +def read_flash_status(esp, args): + print('Status value: 0x%04x' % esp.read_status(args.bytes)) + + +def write_flash_status(esp, args): + fmt = "0x%%0%dx" % (args.bytes * 2) + args.value = args.value & ((1 << (args.bytes * 8)) - 1) + print(('Initial flash status: ' + fmt) % esp.read_status(args.bytes)) + print(('Setting flash status: ' + fmt) % args.value) + esp.write_status(args.value, args.bytes, args.non_volatile) + print(('After flash status: ' + fmt) % esp.read_status(args.bytes)) + + +def version(args): + print(__version__) + +# +# End of operations functions +# + + +def main(custom_commandline=None): + """ + Main function for esptool + + custom_commandline - Optional override for default arguments parsing (that uses sys.argv), can be a list of custom arguments + as strings. + """ + parser = argparse.ArgumentParser(description='esptool.py v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='esptool') + + parser.add_argument('--chip', '-c', + help='Target chip type', + choices=['auto', 'esp8266', 'esp32'], + default=os.environ.get('ESPTOOL_CHIP', 'auto')) + + parser.add_argument( + '--port', '-p', + help='Serial port device', + default=os.environ.get('ESPTOOL_PORT', None)) + + parser.add_argument( + '--baud', '-b', + help='Serial port baud rate used when flashing/reading', + type=arg_auto_int, + default=os.environ.get('ESPTOOL_BAUD', ESPLoader.ESP_ROM_BAUD)) + + parser.add_argument( + '--before', + help='What to do before connecting to the chip', + choices=['default_reset', 'no_reset', 'no_reset_no_sync'], + default=os.environ.get('ESPTOOL_BEFORE', 'default_reset')) + + parser.add_argument( + '--after', '-a', + help='What to do after esptool.py is finished', + choices=['hard_reset', 'soft_reset', 'no_reset'], + default=os.environ.get('ESPTOOL_AFTER', 'hard_reset')) + + parser.add_argument( + '--no-stub', + help="Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.", + action='store_true') + + parser.add_argument( + '--trace', '-t', + help="Enable trace-level output of esptool.py interactions.", + action='store_true') + + parser.add_argument( + '--override-vddsdio', + help="Override ESP32 VDDSDIO internal voltage regulator (use with care)", + choices=ESP32ROM.OVERRIDE_VDDSDIO_CHOICES, + nargs='?') + + subparsers = parser.add_subparsers( + dest='operation', + help='Run esptool {command} -h for additional help') + + def add_spi_connection_arg(parent): + parent.add_argument('--spi-connection', '-sc', help='ESP32-only argument. Override default SPI Flash connection. ' + + 'Value can be SPI, HSPI or a comma-separated list of 5 I/O numbers to use for SPI flash (CLK,Q,D,HD,CS).', + action=SpiConnectionAction) + + parser_load_ram = subparsers.add_parser( + 'load_ram', + help='Download an image to RAM and execute') + parser_load_ram.add_argument('filename', help='Firmware image') + + parser_dump_mem = subparsers.add_parser( + 'dump_mem', + help='Dump arbitrary memory to disk') + parser_dump_mem.add_argument('address', help='Base address', type=arg_auto_int) + parser_dump_mem.add_argument('size', help='Size of region to dump', type=arg_auto_int) + parser_dump_mem.add_argument('filename', help='Name of binary dump') + + parser_read_mem = subparsers.add_parser( + 'read_mem', + help='Read arbitrary memory location') + parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int) + + parser_write_mem = subparsers.add_parser( + 'write_mem', + help='Read-modify-write to arbitrary memory location') + parser_write_mem.add_argument('address', help='Address to write', type=arg_auto_int) + parser_write_mem.add_argument('value', help='Value', type=arg_auto_int) + parser_write_mem.add_argument('mask', help='Mask of bits to write', type=arg_auto_int) + + def add_spi_flash_subparsers(parent, is_elf2image): + """ Add common parser arguments for SPI flash properties """ + extra_keep_args = [] if is_elf2image else ['keep'] + auto_detect = not is_elf2image + + parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency', + choices=extra_keep_args + ['40m', '26m', '20m', '80m'], + default=os.environ.get('ESPTOOL_FF', '40m' if is_elf2image else 'keep')) + parent.add_argument('--flash_mode', '-fm', help='SPI Flash mode', + choices=extra_keep_args + ['qio', 'qout', 'dio', 'dout'], + default=os.environ.get('ESPTOOL_FM', 'qio' if is_elf2image else 'keep')) + parent.add_argument('--flash_size', '-fs', help='SPI Flash size in MegaBytes (1MB, 2MB, 4MB, 8MB, 16M)' + ' plus ESP8266-only (256KB, 512KB, 2MB-c1, 4MB-c1)', + action=FlashSizeAction, auto_detect=auto_detect, + default=os.environ.get('ESPTOOL_FS', 'detect' if auto_detect else '1MB')) + add_spi_connection_arg(parent) + + parser_write_flash = subparsers.add_parser('write_flash', help='Write a binary blob to flash') + parser_write_flash.add_argument('addr_filename', metavar='
', help='Address followed by binary filename, separated by space', + action=AddrFilenamePairAction) + parser_write_flash.add_argument('--erase-all', '-e', + help='Erase all regions of flash (not just write areas) before programming', + action="store_true") + + add_spi_flash_subparsers(parser_write_flash, is_elf2image=False) + parser_write_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") + parser_write_flash.add_argument('--verify', help='Verify just-written data on flash ' + + '(mostly superfluous, data is read back during flashing)', action='store_true') + + compress_args = parser_write_flash.add_mutually_exclusive_group(required=False) + compress_args.add_argument('--compress', '-z', help='Compress data in transfer (default unless --no-stub is specified)',action="store_true", default=None) + compress_args.add_argument('--no-compress', '-u', help='Disable data compression during transfer (default if --no-stub is specified)',action="store_true") + + subparsers.add_parser( + 'run', + help='Run application code in flash') + + parser_image_info = subparsers.add_parser( + 'image_info', + help='Dump headers from an application image') + parser_image_info.add_argument('filename', help='Image file to parse') + + parser_make_image = subparsers.add_parser( + 'make_image', + help='Create an application image from binary files') + parser_make_image.add_argument('output', help='Output image file') + parser_make_image.add_argument('--segfile', '-f', action='append', help='Segment input file') + parser_make_image.add_argument('--segaddr', '-a', action='append', help='Segment base address', type=arg_auto_int) + parser_make_image.add_argument('--entrypoint', '-e', help='Address of entry point', type=arg_auto_int, default=0) + + parser_elf2image = subparsers.add_parser( + 'elf2image', + help='Create an application image from ELF file') + parser_elf2image.add_argument('input', help='Input ELF file') + parser_elf2image.add_argument('--output', '-o', help='Output filename prefix (for version 1 image), or filename (for version 2 single image)', type=str) + parser_elf2image.add_argument('--version', '-e', help='Output image version', choices=['1','2'], default='1') + parser_elf2image.add_argument('--secure-pad', action='store_true', help='Pad image so once signed it will end on a 64KB boundary. For ESP32 images only.') + parser_elf2image.add_argument('--elf-sha256-offset', help='If set, insert SHA256 hash (32 bytes) of the input ELF file at specified offset in the binary.', + type=arg_auto_int, default=None) + + add_spi_flash_subparsers(parser_elf2image, is_elf2image=True) + + subparsers.add_parser( + 'read_mac', + help='Read MAC address from OTP ROM') + + subparsers.add_parser( + 'chip_id', + help='Read Chip ID from OTP ROM') + + parser_flash_id = subparsers.add_parser( + 'flash_id', + help='Read SPI flash manufacturer and device ID') + add_spi_connection_arg(parser_flash_id) + + parser_read_status = subparsers.add_parser( + 'read_flash_status', + help='Read SPI flash status register') + + add_spi_connection_arg(parser_read_status) + parser_read_status.add_argument('--bytes', help='Number of bytes to read (1-3)', type=int, choices=[1,2,3], default=2) + + parser_write_status = subparsers.add_parser( + 'write_flash_status', + help='Write SPI flash status register') + + add_spi_connection_arg(parser_write_status) + parser_write_status.add_argument('--non-volatile', help='Write non-volatile bits (use with caution)', action='store_true') + parser_write_status.add_argument('--bytes', help='Number of status bytes to write (1-3)', type=int, choices=[1,2,3], default=2) + parser_write_status.add_argument('value', help='New value', type=arg_auto_int) + + parser_read_flash = subparsers.add_parser( + 'read_flash', + help='Read SPI flash content') + add_spi_connection_arg(parser_read_flash) + parser_read_flash.add_argument('address', help='Start address', type=arg_auto_int) + parser_read_flash.add_argument('size', help='Size of region to dump', type=arg_auto_int) + parser_read_flash.add_argument('filename', help='Name of binary dump') + parser_read_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") + + parser_verify_flash = subparsers.add_parser( + 'verify_flash', + help='Verify a binary blob against flash') + parser_verify_flash.add_argument('addr_filename', help='Address and binary file to verify there, separated by space', + action=AddrFilenamePairAction) + parser_verify_flash.add_argument('--diff', '-d', help='Show differences', + choices=['no', 'yes'], default='no') + add_spi_flash_subparsers(parser_verify_flash, is_elf2image=False) + + parser_erase_flash = subparsers.add_parser( + 'erase_flash', + help='Perform Chip Erase on SPI flash') + add_spi_connection_arg(parser_erase_flash) + + parser_erase_region = subparsers.add_parser( + 'erase_region', + help='Erase a region of the flash') + add_spi_connection_arg(parser_erase_region) + parser_erase_region.add_argument('address', help='Start address (must be multiple of 4096)', type=arg_auto_int) + parser_erase_region.add_argument('size', help='Size of region to erase (must be multiple of 4096)', type=arg_auto_int) + + subparsers.add_parser( + 'version', help='Print esptool version') + + # internal sanity check - every operation matches a module function of the same name + for operation in subparsers.choices.keys(): + assert operation in globals(), "%s should be a module function" % operation + + expand_file_arguments() + + args = parser.parse_args(custom_commandline) + + print('esptool.py v%s' % __version__) + + # operation function can take 1 arg (args), 2 args (esp, arg) + # or be a member function of the ESPLoader class. + + if args.operation is None: + parser.print_help() + sys.exit(1) + + operation_func = globals()[args.operation] + + if PYTHON2: + # This function is depreciated in Python3 + operation_args = inspect.getargspec(operation_func).args + else: + operation_args = inspect.getfullargspec(operation_func).args + + if operation_args[0] == 'esp': # operation function takes an ESPLoader connection object + if args.before != "no_reset_no_sync": + initial_baud = min(ESPLoader.ESP_ROM_BAUD, args.baud) # don't sync faster than the default baud rate + else: + initial_baud = args.baud + + if args.port is None: + ser_list = sorted(ports.device for ports in list_ports.comports()) + print("Found %d serial ports" % len(ser_list)) + else: + ser_list = [args.port] + esp = None + for each_port in reversed(ser_list): + print("Serial port %s" % each_port) + try: + if args.chip == 'auto': + esp = ESPLoader.detect_chip(each_port, initial_baud, args.before, args.trace) + else: + chip_class = { + 'esp8266': ESP8266ROM, + 'esp32': ESP32ROM, + }[args.chip] + esp = chip_class(each_port, initial_baud, args.trace) + esp.connect(args.before) + break + except (FatalError, OSError) as err: + if args.port is not None: + raise + print("%s failed to connect: %s" % (each_port, err)) + esp = None + if esp is None: + raise FatalError("All of the %d available serial ports could not connect to a Espressif device." % len(ser_list)) + + print("Chip is %s" % (esp.get_chip_description())) + + print("Features: %s" % ", ".join(esp.get_chip_features())) + + read_mac(esp, args) + + if not args.no_stub: + esp = esp.run_stub() + + if args.override_vddsdio: + esp.override_vddsdio(args.override_vddsdio) + + if args.baud > initial_baud: + try: + esp.change_baud(args.baud) + except NotImplementedInROMError: + print("WARNING: ROM doesn't support changing baud rate. Keeping initial baud rate %d" % initial_baud) + + # override common SPI flash parameter stuff if configured to do so + if hasattr(args, "spi_connection") and args.spi_connection is not None: + if esp.CHIP_NAME != "ESP32": + raise FatalError("Chip %s does not support --spi-connection option." % esp.CHIP_NAME) + print("Configuring SPI flash mode...") + esp.flash_spi_attach(args.spi_connection) + elif args.no_stub: + print("Enabling default SPI flash mode...") + # ROM loader doesn't enable flash unless we explicitly do it + esp.flash_spi_attach(0) + + if hasattr(args, "flash_size"): + print("Configuring flash size...") + detect_flash_size(esp, args) + esp.flash_set_parameters(flash_size_bytes(args.flash_size)) + + try: + operation_func(esp, args) + finally: + try: # Clean up AddrFilenamePairAction files + for address, argfile in args.addr_filename: + argfile.close() + except AttributeError: + pass + + # Handle post-operation behaviour (reset or other) + if operation_func == load_ram: + # the ESP is now running the loaded image, so let it run + print('Exiting immediately.') + elif args.after == 'hard_reset': + print('Hard resetting via RTS pin...') + esp.hard_reset() + elif args.after == 'soft_reset': + print('Soft resetting...') + # flash_finish will trigger a soft reset + esp.soft_reset(False) + else: + print('Staying in bootloader.') + if esp.IS_STUB: + esp.soft_reset(True) # exit stub back to ROM loader + + esp._port.close() + + else: + operation_func(args) + + +def expand_file_arguments(): + """ Any argument starting with "@" gets replaced with all values read from a text file. + Text file arguments can be split by newline or by space. + Values are added "as-is", as if they were specified in this order on the command line. + """ + new_args = [] + expanded = False + for arg in sys.argv: + if arg.startswith("@"): + expanded = True + with open(arg[1:],"r") as f: + for line in f.readlines(): + new_args += shlex.split(line) + else: + new_args.append(arg) + if expanded: + print("esptool.py %s" % (" ".join(new_args[1:]))) + sys.argv = new_args + + +class FlashSizeAction(argparse.Action): + """ Custom flash size parser class to support backwards compatibility with megabit size arguments. + + (At next major relase, remove deprecated sizes and this can become a 'normal' choices= argument again.) + """ + def __init__(self, option_strings, dest, nargs=1, auto_detect=False, **kwargs): + super(FlashSizeAction, self).__init__(option_strings, dest, nargs, **kwargs) + self._auto_detect = auto_detect + + def __call__(self, parser, namespace, values, option_string=None): + try: + value = { + '2m': '256KB', + '4m': '512KB', + '8m': '1MB', + '16m': '2MB', + '32m': '4MB', + '16m-c1': '2MB-c1', + '32m-c1': '4MB-c1', + }[values[0]] + print("WARNING: Flash size arguments in megabits like '%s' are deprecated." % (values[0])) + print("Please use the equivalent size '%s'." % (value)) + print("Megabit arguments may be removed in a future release.") + except KeyError: + value = values[0] + + known_sizes = dict(ESP8266ROM.FLASH_SIZES) + known_sizes.update(ESP32ROM.FLASH_SIZES) + if self._auto_detect: + known_sizes['detect'] = 'detect' + if value not in known_sizes: + raise argparse.ArgumentError(self, '%s is not a known flash size. Known sizes: %s' % (value, ", ".join(known_sizes.keys()))) + setattr(namespace, self.dest, value) + + +class SpiConnectionAction(argparse.Action): + """ Custom action to parse 'spi connection' override. Values are SPI, HSPI, or a sequence of 5 pin numbers separated by commas. + """ + def __call__(self, parser, namespace, value, option_string=None): + if value.upper() == "SPI": + value = 0 + elif value.upper() == "HSPI": + value = 1 + elif "," in value: + values = value.split(",") + if len(values) != 5: + raise argparse.ArgumentError(self, '%s is not a valid list of comma-separate pin numbers. Must be 5 numbers - CLK,Q,D,HD,CS.' % value) + try: + values = tuple(int(v,0) for v in values) + except ValueError: + raise argparse.ArgumentError(self, '%s is not a valid argument. All pins must be numeric values' % values) + if any([v for v in values if v > 33 or v < 0]): + raise argparse.ArgumentError(self, 'Pin numbers must be in the range 0-33.') + # encode the pin numbers as a 32-bit integer with packed 6-bit values, the same way ESP32 ROM takes them + # TODO: make this less ESP32 ROM specific somehow... + clk,q,d,hd,cs = values + value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk + else: + raise argparse.ArgumentError(self, '%s is not a valid spi-connection value. ' + + 'Values are SPI, HSPI, or a sequence of 5 pin numbers CLK,Q,D,HD,CS).' % value) + setattr(namespace, self.dest, value) + + +class AddrFilenamePairAction(argparse.Action): + """ Custom parser class for the address/filename pairs passed as arguments """ + def __init__(self, option_strings, dest, nargs='+', **kwargs): + super(AddrFilenamePairAction, self).__init__(option_strings, dest, nargs, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + # validate pair arguments + pairs = [] + for i in range(0,len(values),2): + try: + address = int(values[i],0) + except ValueError: + raise argparse.ArgumentError(self,'Address "%s" must be a number' % values[i]) + try: + argfile = open(values[i + 1], 'rb') + except IOError as e: + raise argparse.ArgumentError(self, e) + except IndexError: + raise argparse.ArgumentError(self,'Must be pairs of an address and the binary filename to write there') + pairs.append((address, argfile)) + + # Sort the addresses and check for overlapping + end = 0 + for address, argfile in sorted(pairs): + argfile.seek(0,2) # seek to end + size = argfile.tell() + argfile.seek(0) + sector_start = address & ~(ESPLoader.FLASH_SECTOR_SIZE - 1) + sector_end = ((address + size + ESPLoader.FLASH_SECTOR_SIZE - 1) & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)) - 1 + if sector_start < end: + message = 'Detected overlap at address: 0x%x for file: %s' % (address, argfile.name) + raise argparse.ArgumentError(self, message) + end = sector_end + setattr(namespace, self.dest, pairs) + + +# Binary stub code (see flasher_stub dir for source & details) +ESP8266ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b""" +eNrNPXt/00a2X8WSQ0iCoRpJ1iMNxXaCeRS2ATYBdtNtpJEE5ZZuYvzbUJZ+96vzmhnJDoG+7v0j1CNpZs6c9zlzZvrf68v6/fL67qC8fvK+yE7eq+DkfRBM2n/Uyfumgb/5HB51/7L2r6nvfHd/+qDtF7d/JXx6\ +p32ruVHfoc8yp1vTftnkMMuEvqQXp70J1Prfyh2poT8DkO7ORDP0oLadJmuXc/I+1zd4HUUgv9pprzsDxw7UZkCGpIOJXkOGKzvY6iBosO3A2hIjqxCsFw6AQCPTO4dG7TRyg/jYeQOdVWmHLoKTRQ85mQHhZCk/\ +D9t/aqehQmcI7YBRBk5DNWYRe+3jnAEKXFCBWEXlQBc40AWdl5rmMvOosYMi1eWBIHBYDxsye6mFRi3hs8xpFLbxAntNDpDdJ6NH+J/ga/zP+/uGax7yrzJ+wL+0vsW/VDtPHXKjynL89do8awepZOK8ha9G5p48\ +2hTIeEivHb2kteVtz0IR70MX1f7WgV8MfaQjrTss9tunYTFrxw+LKcxXtMM1YXGHJKhOaDRtMAVTIPraf8qQ8QhYiuc9AQwApvBbP4WvMp420zsedIB5W4qUEXTyhHDtQyUsoeThzgjmH9CoGlATypSyljFNXeVr\ +oAVCNQ1jI1BmLCBMGMQD8wBHxhEHPFzUH46fh5/7vH2Yo6RPCdCmeSI/zuUHryUsGUIeTFdr1pLXjJl8jJgZGuAzQotK3kk3QVjmICzqLpaeqtCfAgv5/BEAoTfw2Xj2ZD8s/E1kpyWwaZRCB0Ba4ko//En/SGYL\ +QUZ9YL3AHzQz6LhlabmF6C58wzZMzBacJ/v8WzMyQPV1kSHsFAOH+zBRyLyQ3SUNinbILNx3YaLBlBrAj0GL15TVcZMSU6uUmArwHSieq65p0KK4BBiBljiuyx9ZAnLNnGjRjEzTGok8ItgAqxlqqjR80gjACUMC\ +gCfvhg1+PP9enuyf/NwM5dNHPCf0qVzyNPvY6xx/P3Omi5hREjIFrjrNO8AFyQeCDLDcvHOGcBaJH4NipF7z93a00hlt/haet7TPWA8r3UNETnNtNawlcHUT+RgetID8TCM3PThvWSRkqe2iXABm8tx3Hr4UqEKH\ +rpWCL1Q0aidcyrOAnrV/GUD7a48XumTNkmvuu6j7rv0NwjR2caUqMA9gFeGLU9ZxibGxDWMunBfy7tzVRotVFqzbL+sKXBUyyVny1hjv0RQ6/bvf6ZjsiCpIuVWgk3Dxt+irTD8eoHd2/AwMZvumIKGr1Zh1G6ws\ +dvlwAFKHCvvVynRPCZ11QHNpnOsr/pF+Q2uAOdAurg5Ny/jhsmUka5dxzIamBjo+tyQAH6YKRbQ3DcJfCLM3jHkhSNkIRSuhydcrr/bplU7+5hJ7Uz6ZP0Zt/sjzHNvgAhIK3zniPr/D88FYMU+nwnYU8t8+Z6C5\ +aIFvaCxAUmvTpy5+PcKhHS5kgQuccdsP6tQVZPSOshHov6fEHbohAjRkkIz65GFysCDI/OI11M3czlQX+zNLeVhfXU98GC69BeM/ZejGpbgJM/6BquoINL8o4fT0CI3RATw8PBjAB+iSTKIBwKXFrGglax6QY5BH\ +8xsOXhrmIIsGBzcqiU6WoFLM2+yYtVoWsh5C5OhkYLVVRiqE2GLDUhh9a+Sa+cBw33f41ZbwHclsh/WCZFtYr++oUW/ggq3uNJb9SU+ptNPNMO2mMy+OPCUmAIxYgr8nqmmddtml0f8GV3Pcw2D+mjyWJk1G0H1A\ +MZQLU24YhTz+nPVpgHDutI2c3fbsW59awXjn+YC+0sgzoK2alwQafhDNOWIcGyXLq88cMassllYRSgpK3GyBGOSmCgagw8dL9q+xqcfvHD9D3IgmZ6Vk9VirsHVh/DSZokGfrDVSEYr/TOhNMwdxU1pTXaHS3BTm\ +THwM1IVdgPGGKeGxJSshN4hDsWk40QM2puQK2qFF0+jEDoeygXhm56ku+4uCgKKWh9aXhccoU4MIXbwRUGYwBLEIRwy9wzV2vNvw8iYGqvA+6r8HLLcP64yd9qb/ARO0dGNEnJ4mjmbwb/zi5ARctf8w6ZnpqMd6\ +rzNClzMaALoAKtQvyLx3/8k+f2LTDZ/rV/uoykmdWrVZ3GA2rdnBALaNPQoxmnpqcV5CyBOQl0NcCiRCvop80DrhwrtRFtcoimarfD1lBSyyF+IHYQHTjB+z0x5643PyKWGiPPRunH3Ltrw4IAyD/14V/1MW2zjI\ +aPKCI9aWf4qGhFqN5+DmxRcU3BK7/UwAwOC1XoyoVwYEUuOtn2C2YnNRbOCoO3vPQL9+BKmED+LXoHNAuZaRlRVMdMSK9EVTk1XaoveIDlTm3kfmZVcRAzvlJDXw3yzMQVjafwrom97ZxcB64y11aX9uso8DxqTF\ +CziHehhgfst7x+jSbEPRpj8ovyeEQViIzl8ixg1mIEVcg6BSTsJ7S6PAonSTs0Ob8F/AD2iwuetGhTYn1em7Aj/6BzVMvjdicFwJwpEXtLJrhymu7NSuLIhemhWJt26WkdhlgJB11pHLOqJ+lk6kI/BeOH009vGO\ ++RHEChGkxcLXbC2CayBIb/xZETwt4rL4CqgGCoK5GRhCraoIovvMC556celhJy8inichrElLDRpiTa3/cziIRVizAQZeh3+HILs8kjj8/DEzF+arMNbBrMCr1FIlGE8mkAUSfySZgBhFQQOspOc8VDLv08LbhbFH\ ++znhvX1fxX00MkWa5PYdJOy2VUnmpWFAZlt/sMUiq2lxgY4AH02dl8K9NIwuaD5D51Do6svyeI4gcXICku0lcvLvaiKqHIL4GuQoK5kpwQHFeP78GqGzNbIb7VLKdOBNYUkpSV8dTclCYqqlOvaHaJ43UDFUe4iB\ +a5/AQG4wsM0Og352DPa04E5xL8No+PtPWPdcFs3KHNbBOb4JLxdopD4C5QaEKRVYv1glj8m5aYAzIZHWrn546eoXsnQMInAcfUwWqIkdhWLWnP0ptAa1g5avARccWBesGC6jfuPv+sBkMzJRhtAcX9bZVFbeJ9Pe\ +9PP5XjEAHFdTdmexrWkENLKNq6E9sFfw/ZSyTU1llN0b/6EvWCKoeWrr0U4ZxdWb6GHk78M3BtkeOzswMqSqaGQtIwseBLUK4GmSeddZxMwx50nBEVjmfhu5gJqkhFnT+hvLr8OT5a7rgyjyHpoq8DEtkrOhQadZ\ +ryBx9G4umW1JYQHPXSVxPZ7bi47JwII8/eXyRp0bx1mrcUdmiR7HpsNjlvGcGDYI9vbnxAFBuKpXWoWSA/TAyMHCg/CqbBZ3BcAj6wiSHXpMjSrYB6fhdIiuw3VyryABhoSonLxeRM9qvWb+DOfPzPxz4T5H5Y3A\ +fVpKaPTBOv5r6DaiHCPMV2aXSBXSCrKh9cLbPgTQNUZCH+CfC0rKBgqSbCG0EmklN0HcweXXgIOUTPgC/a6eFdc1iam14gsvFOtNdhtsOMyoPxydNZwawh2QjPMaAG3YSuBiZa+jtx7ci4o4qYoMsmmDpSzxLFf5\ +r0CIRuye56gSZvAsFL9z/IlFlfmaRUnSJNu5AO+F122X2K64XTdgLp1J0ny2L8wVXRB/YGSswzPaTQzYFc3jmSROS58jxSb1Ze9CNp2AznrZD41n901XkrMskyHGIW3z1dkuR+yQAUllvGbn/uLAdmbXHdOIGAoo\ +J22XeL+givR9I2+fadFXfBrMWoue0bqrZ662bdlKYuC3+jO16BnvFVGT5N5Z45TJjMOFOBz4WmoyZxIDwmoMQoOBTa+ucRHuIy9+B8M9jAab0B02pyCxg0ajTEBdK++fFpI2KEbQn+LWdYdH/9tnUKFSy4JdrzmQ\ +FIuxCrd/kxMGdvCYDIwO/x+YBm8frbLqM2WL7mBjZ2M6IQxZT9Qaa+Omtoj3dtCm+5h54T0DpSMkDQgn5u7UeeZS5WwBTI1CPjl7D/ZpAf7vC4hD1IHEO63BUi7ZFgXWLajDVd1ycn0KOdaKlr1zct1JcwXq234H\ +ghmphEMiUDvbM9qhw20jNV9lkFYOVcseW5AoRfWM5SA8VRaCcCq/uImQ37K20GUmFL2xS3UAfgf3riJXC6GUBLOhMPjBTNPPFohbZAYxro9lHuAzBalU9dg1wewAYJJngehtWwVp9DwDWwktCCVzyOA15Dyhwt0m\ +YufFAe91QVJLloueWLLBlsOgW3FNjMb5l7u+wDEg7Y0WHsQZkwofyW9UsKmm1OWWOvJY1+i85K29XGZGCuZFxrkEQWLygiRHYaEG7qpHklyfFShfB1AIEC2K7CwdcNJfqbBIQy+ZeSnCES+8JCy2JtFg5mVn58jx\ +B4simRXZPcryVJzwANHN0lSd3ael5MGBzeBPJmpWbNmwEyHMeNMQS1uA3Qj2WbugyTsYYOZtAXWi2XNoLchyN7gXnFLGMktMph26BWdgCCcX2JlJDL5ChlP6OMxgZlJzdZBPBSWsWVCJhO3iz2DxZwfA/wtKeTXN\ +5B4yKzzF8dtAfdkuC2aKJZsAEtTC7f8d9bkzV4tIQN5Fi7z2Z6CmLaJB9LUxZyk7l8XCS4ExsjOkAKbmgyALWDpzBGKCfHQWURFJHj6zmjDPXSFRuc0El1pqULybm9s2gV6kdp84c9xRUNUVb73Tt7InDTl8gLxI\ +fHm1LUnjjNi/Rsc0Pufcpt4ZnvmMOLPNw9yB9RiyR60mD5QIzT1KNsuevOFgke50Qju2KT/DiOmaXYwpQ9LkKtBitimWwnIW/ZHcSMfeYhYHpEYHkNEJ4oeA5SIiZIc2/QesX0eSpZzdaP0f8HjRg4s5CE4k0Nzb\ +cI3X1YE7xmcRx+75Xxi7W8KHTgyaOqFEupLam9naiay82ZkjsuUz7P5s4KzDAU18g6ASK6uAhYBvXRb6t8s/ocs/qLKCAfMPVQcEuYKxUegTrEQIiOBYTaKZG2ImvCrFB4kt9cHPNNT30TTQ5D5nOpH6yFyPuXwn\ +4byB3ZqAaLOVw01TCkEvoo5T5+yoo1etIu8lQIkZUU25EvQUlOQGNG1e27/czGGt3owqspoAQzNQp1l0Goqo8AYRslj2VgwJwHupP6e0uDP/xLROSaikuVv0mO0O40f5BWfs7gj7f27OSunhMXFEFq36iYb90/Xs\ +P1/D+5rDhID8h41VKbAcGGRGItLbMJX3N1or0TKn7po2XbOCd2lUjmNdwFiruAC4LoDkF7DCnQsPdeR0hHy9+ZDGWHYYgxQ78QYPqLSowQbV7FJNkEkOdg9CwpG6Itp+RiuxPl3rxVG0/aFx/bR5Pyg4J1eo0TMk\ +ksJQT4U7ROJEKoea5pWzFZDI8ld46RkH202joIpWe7wFoFdT/biKQH3Th7wF2yNnouGSCTVjoKIP4C8pRa9wUw9Rp9L+IKvRN5d3NGDFMGteRjn9wt0GiAPKLareQwVQok/7TCoY2SzWyYyr8DSZCDQ8kYhQ0hHe\ +7cNcjOlQNO53dsOzya1WccwteFPBOdfMlphMB13ZsbHln2Jj51cbWNatWOsa0w7lbzWwzWcb2GtfomHe8R4me0boWVbrjGzyFxrZ7I83sqxSwlU7O6XiGYd3JmLiLO9gMDQ4FfuaUFG7Oj3lHGZiqssxsfaRjGDm\ +0F2Vw47hQ+V2+gp9WrS6u5DgKZoJ+KWA1zwR4s9N2cAa+9rNjQwcVUklHa/EbZ0/sEWHpvBHW9uCEWRwRa61dHO8yHeumwzbyimEcLkpInV4QioMhoPL+GKFNhxOloGhzZTPApS7fodMgUOmQLWkIakpIivBQAlV\ +bnwkYem7OkHiyuHCEk5MUFN+QLlcFc5n4v8IfaS2MHjr7GBeQiiuADXVaBv9sAMMXj5bi0/UaIONz8fnbAWf57/Cyh76l8Yk8FhhvUIbbD1gtDZOeXaRmvyJKu99dDy8FFd8JijEcrUzDCg/wGwKtHZpMP3BOJbW\ +u/Qx01OhFUEXDvx/zCs0fjtjeNexz3EHv5Mefk0RxdFbSygSg1BGUjdg4yJ5JdQj6W6YX6DcqCwEw5jw3Gm8D6I5Gtqm56rxDe89vHgDNjJISnCVVLLJo5fVAQMh2woBynh2mcIeoqfKe9gFm/E8gvK6pvqURxj2\ +dLVNd4d9XW1cnZu8N9Z3D1m3kuYOOq5h0XEHo44nKCrZSe+6Lt0aj09yRa6/QBSLJere7TkKOtlBN2BXqIHxkrGjUA8ZNJKI1FwbqNTphBU61eOf/ijOQPyKnYFw5uQPHa9ApadYQ3rPim6G9HWqs6w5kHXswPIe\ +AoxutE324BS59j4sDPguiN8cYMrNZFnyWPTNAYcupb4RAf5pgynpOgKkfDo85lrUK9ksYzYroNyw5jDHcFj+B3BYn7eCdb4B4s45fFCNP+kblK5v8PhS3yCecB19gYW065XlOYnk5Qw1cxmq613SoYVWX0piz3oI\ +oAxVReMieyQmAhcMJZYjyD1kjrjUPdwXesdrPIRLFOOUdD0tDy3z7gFurcyKjSnxAbwDPbAbZ1jfB5GxSl7bgwtlNft8LlspJesyWskOLjoafyyvlVerslegx/RiRYmlnQ6UYXBUmWwGWnMneD5zDBAiDuiOxw+K\ +EuPngKoedRvBwbEAzCwVpxgCneHYGA2ExU6wP3guWvDw7oCcsp2De3CQCGtZQ6ljxSFGzyg3jmdP957B85iKfgK9kZfzH9YTxtnSr/MFbIyYOH+EqXrc81Z2NHK9gp2hE92r8sJHm7wodtDYiqiX5TGzWxtcmDOa\ +M3GPSs4UVLO77nOFTSXN8OsIH4RfH4HYJBICieZjpi+4aAbPXdH4BFSDZeDyUOFD9eaI/XRlozk8QVW7Z/SAJyFxj650ytUh6FP8RNxqC7NuYxCG20mfneYcEUwUic3fcJ7lsoTP+E8JxbSzj/sbyrPkRIZUy3zz\ +OWhY2dIe8c4QIeLo/wQRWdKtVaMa+DX1WXNr579sF3hEToENvGew0D8q6lZvSUBV2dznidg64fQY4idcNIMmtZC1dqDGnfxiNMxLW8mbXU5KMGIxpkMLLAgi/7Fd+A6ce12AyIADFv8Cv36kKCHDR5j9gR9jrl3G\ +s0+cn4JQRY85m5SyF4Q4QwK+AMIo8LWLQ6JLgypBH8Lm8JiKZju1sqy9bO7rV3bfZFNa/0JVs3bbf4uJ4mwr4lm1wnmm/vEv0hsgQuYpbMwWR2teRJe9iC97Mb7sRXLZi7T3AhsZeqFFdIEu9NkQKu2BhUrGdYF3\ +FtjYyNXt/q4ZaGd3APgZXMBCavWckFoHtwYtyjFVSZv2rf36itD/lHzsoo/+FtPqO+5e0dEIPi/9ff/ThUcbvVy7f0a2l87ZeF+d34PvW9p9zwTVr28Tg5bBtj0iVEfC7XjQCs+xITGPXpKQ16zGgANhTwS2l4rw\ +I+l8lHxhR970B4WFnbK5E+5rqxz7Z29M0bMcY4PNt6pWu+Qc3NjCQt9Gzhf6ciaBdbOizcym2B5mJ4tzaVEBs47O9uxmqhqXctICa2QqVAan19hBLf91+K/B4Q987Co/WRz6oAf1guFLb7IjN47N6Xas2PBu8Imz\ +Mc+s71NiIKu5ZILwsLUHpmHm2OVaxFeTe4neKq5MThel21BUr50aeixsqmEsbY6GQg19IV0y1kymeBG+Fe0aJEfutzkOH0DSBn2iAI5822+fwGOPw6icD/932RAebq4+xLOO+DATSLEg66HM7hzqwaNIvf5CM4oA\ +7jF4io7Ydj8e0LGgjMUfC3v5QFNNBxn3gD1vDmD/D3g4M3jjTTjGfI7iuDVCiMeImMBNjXRKw+wRI+CWce/wmD10YA4g2iOwjbkGg/r7fEKq4hNLVEf4FReOYIyXt5Isjgack8o1xl03wJoMB39zajZTWcP9Ry9O\ +Tl7/9P4jQrKQ2hQiZOekceXeLsBnXUILcpNY7GOdVL5SILZwqqiYjRCJK7cX4DFFPotiSv+Bw9Dhze+fnGSjB3ygDA+i5blNV1EwL6ehcpHkF86hN51BwQHkRSEN0+SHvF1IhwDxXWA3EVEDCswhVH6593Do0MOb\ +Njy8acPDmza8O8QmLREWtF65xIVY5JQdwdC97yZcd/kNbhzpzvUPW3hobrBxPON7I2iTZyo6wXwI64DNLPHx+fED1ibOjRnHy84XmZOzCSkcHgwDs5TMufNGrb+2B8Jwc8dNFrvX2kzMbTuP5OTgHfc2H+iaUVcH\ +dnT5mLMqKXiS6zAqQuwSnuVonKL+hRlyII4qkOHWi+QjdpBrY/hTf8jyRM7sALzYbPydlMHyucI8AVaOsA4yn+K/7ZeTfVonD/WCWazGnZmHkWQ+EAMN5tUryFE0selSYlb84V0LzgHPTD09uiWmafj6JNxb2/Mg\ +nMoV/jtlVm12LYnxyO0TMs7tUqWWnxNCcmuRwzQWoJd8dFY0m/M47vKZuSpl11+5lwRPb/PFPnikBC/x4V1eTI3DUY9KiiuzDm2lBCnziNBYX8fcoAJXvtz7m7pqeGKXaTM8rjA63KXDgmdUeFAN58dTTqiAY4rb\ +0GFM7zIHN2yxMToYk5dLD7kSujF31kiNX9PXe+452WO88GXrERue6OT6kJ0KFMnpPYbL2Put0WAQy1kdOAlqICDdNyrYbjWTo47whJh/DYKN44ihw8O3DTp65ixu02UIRSGF83imO/yBBYOkVLJHA2SE/FE22vW3\ +R0JfIOdlZHwktyrJBzU6IWDO8QoK2DLLn9wHe90y/PIc2d9aKLQcsOWM1TkQi1dwI5BKvmfDmnHFQMGb8gEWVHQPPMKFLaRGgAw1pYx+pudV0ydv35diH+k6qYyaKwQbc/wY4nYNnnb92Bxq1msumNFS96/j1gXD\ +Xjs3IRzW4K1VbKTQ0833ALXngFq4TyN7wkdVmswIzEDQ7N5ShgeQ0BvOO4LA2lR84qeoiYKTk+nLjSdiaqBHvB1jLUT8D9+IvsZLO3T+923ohT563xvI1G1zNRN6Rnyi5JOM4QIo966UCGmgVH8S+QBT27fWgvB5\ +M7UTmOsJ5Bz9+C7zjlyHMhYTgTDJZ+i6XrsMMqycGH4xZD3oiEpsFJd8y1QQ7jJvfCqrg0zOTAm5VHSKY1dEr9a4qzJauYRtsXceFvYKrFU6IY5uEnDaANS9N2GxDknC6z3mpg9euD7Fa7dx5jaWbuO92/jYvWIw\ +6105mPfb7j1yWPmdmWvgvsVfsXlWfe3cOpjncgecsvU6Fql89w5qBsAuaDxUd6D2Wi3oXgQHVzTghXuJZ9OSGAKwr6oKH60NK8um4ZBCYX7LCUoonn/K7NKEP/HtD2v1lFxQl5GmswW/j2QzgXeX6nVXuVU1e6+U\ +ZNh5AlMdiIeR7MmnBd/x0+AqX9tQokrkLiFy+TEawuDB3B5nnR9Y6hZZTVUc/SL7gMqo4WpleYWtYG0nfs+5c20vovmJLAFuK0SsUMkXusk8XxzATPHtEzjuR54loqU39cpVc08oTkfxFINeHL3gLEhRP2GJoec/\ +wvOCtzQcaD2+mwIf0WY7Zt2hEBCiGNQjzdlXwkJRJvYztGXeVW33EGtBqhyFbSef1c41d+EaHJZmARgH8Z1odLPME678buxGUjvkfVpniXu35uQcnnsYD44CzMHEfKlbgIlFyx8PE98cfjtw09OQk4pYPZf55gGE\ +CwW6oLd5cqzKVHIjYv/iwcjxqxBM2BEhJ8m5wQT3PrKVC0hwNxMvtijMSZALuXDHLHAqdwLaYLvloBluLtxLHeUaBHB3CmqhtHRIFfP9L3VlD//mcoVkwUj/TFcB+6q+VvosZVSLIRD1o+XMoTOmi6EGb/nKLKrs\ +nrbm5L4RbhSguitA2RoXqqHLEAWI2zxE8Jjvg8zy1T4VJ38ryaMFx5CmqrBwpenqUx5lvMIrFyuMQsJKuoS1ovU4kaRQoaKWv41Q1jt4KqYT3fDn7IqUlptyOMVpDj4FEeVpcZxNPmMz3tzgE1OmMGMmzg0c3gl3\ +cGtmW+LDzqWR8qW+J3dM0sz4NaWqO0DL99W6762fZ/pQvRQ2r48GeGnxD++WxQKuLlZBGmdxmsZx+6b+ebn4xX2YtQ+rYlnIHccmR4E6ZOxsmTkbAlRwwn+IiuccEeA1rdppVKHTwBx3yg081CtvIGjkN3t8FgM7\ +hE6j0wFSKLW5Kjmm+A4bKI/Fuj5O4weasv94j9gcH0N9c41HvibfmV+Xj0je7/rPMF8lty6jwlTUWFJIRZgqnQZeBRpfOeXljZoPDq++wajboDEl/6VtfG8ocMPpCsd6DJ5xi0PIUSjzJnVHDL4c2N/ZGBpAXppf\ +U2cNKzsn/ax0/5ad3jna7glLt2KI6s46rd7V0ao3Nx4gc+1n4F6jaxudGwmLXjqnN6ZWay4IV73v+5eGh7121GvHvXbSa2e9tu62VQ8e1fl+4DY6X7o3j6vT1R2vP+1PXdEOv5CHruKpq3is306uaKdXtLNPtpef\ +aP38iVb3avJ1bf3J9uJTsnPl35fKbfJFOFp+wbr7kDdXaIEe5KoHiephUXXGG7qNG26jM2znYNu+23jmNjoEedfTND04i15b99p1tEZK1F8oxX+2Fvi9WuL3apHfq2V+rxa6qv2FfyqwyTQjgSlKHp01HbOkxWYL\ +ZcFY47yGkTR1+f8YY3Wl19nrdZ3kKA3bkDP79X8BZBZfEg==\ +"""))) +ESP32ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b""" +eNqNWnt31LgV/yqOYfJaskeyPbZEz0IS6BBg2xIoIdDp2bFlOyFbcoDOQjgL/ezVfVnyzNDtHxNkPa/u43cf4vedZXez3LmbNDvzm94k/k9xH1oKW+XR/Eb5ptX+s/W/fn7jVEKdxsyX/i+01O2zExrFmfX/M1PD\ +foomyE8roUBFrehnhKJu6ocd7WTgzIbayvepbDh7D9bEVG2tkZe+pR35c3e+PPu4Tj9uA5vrTG7iv4tkV22+iVKHRGoX6NSlp6QONHdtxDO3cqa1dGboQCLOvnyfe8NPh7ZRYbUDofb3aQP5KbpHJO4toSZLYQjo\ +fuAbU7iJCTfpahqtp8L982NiUS+syo9gWxh65edBb3OeAk0vQdSeIDeFGRlvCqLLgdvp4bn/1BPfn0ciVtyGa01hh9PQGWQFfJvSijYbDR5fjiR9gvxc8qbm+CRlUTt1t4CNjm26wmhD/6JeIrlqRUnxw6qI18oc\ +cstE4kBKi/j78FBaJ9SNa/Ro32LYN0gK2QvcqaBxJI3hludgD08egNrp0cCu/1MmyZKlmYGJuvIWDPnJXUeTGx3aA4/rbJsb+FtOWLqgZ34vx5oBet3F+t5EezaRMC3928CP28NZjm3PelnqZnQscZqgigZBy6HP\ +cl8wnuwfzHRPj3Gju5zwSHQAUq4jCnk/W8EZ9+Llhu6K91PBnlu07YoMXtsZoZJSX/00v8T4EQ1s9iPLsfXy1mvmDx3FsM/3kcIGglo9gOKSr56PmHLOxzbpNm8M0OlF2FoWObQLQQqvb26sC69pyRiNnh2AajH+\ ++T81bnBeqKljZcvB0sHGizcvns3nfo4pZXVHHCKjfOhX+xEtXDa3iXcIOhm5AWF7DK8gFl0Al/LEE9pkCWMII0IXWbNxd1PSKVfs/R2puvvyNfwDJMNVwfDGODB2Tmi679GIvHe5f3Ib7w/zU+JEHZyWcLZuCahN\ +BP6Bqp/m18HddI7sCD2AJk2ts4DvYD9aoE8Tc9ou8i1ZBKL5qi0H31wnMY5m7BebbItngor363gc87JWt0jJVv0TKpC4GyVevNY3EZnsDvF6Sq76BGZmV/CJsjpKoZ0jZWCRNTgUu79PxxqaE+IAY5/svWF9Qa06\ +mF/z/m7K9NoRvXcIawYHHggC7EWxZaSEOK0jWcB424xjkRFjZI5jXc/He+Na2dPwPtX/2KflOcX6nPXYgG5yF/YuA4LIfvKtm5QDq0bAK2Ns3RirSfs8/vAA14K6T73+t+ZHtgTrom7wnHBf/1FubRENAFUo4Ch+\ +HBlpGXAS3H7JrgSt6NkDQPSW4xGRkA7T4p3AmuAEmd+wIa5Rka+ufZIeInyeOVJWNAg23SZaDcBe1+wTug0yhH4bBT2NrJkELUUNIb4jOrlVKQw2FGknnOjaP9KIy1he7+OPZfxxE38ASF0w1gGes4nAEZdsLFsg\ +Nhvhg1yx7ul+xjwGdfgYOIVWWt6ZX4MfMc0Fz/uO5PB2CECPPMOhM5ux20ChV/GUeOlf4JRTQS+QfSOkvbyiRRI2qvIgitAGQR06YjBdI2QEpDyLS9G/MgGBsLMBgFl3NozlUYSBXJg+h9uX74gMW5/O5h+Fkonw\ +Y+FPxCNYZ9H9sQYixPGJRAvxuNGJl3FdMVHNmnFdpVcPK0JT5zhwwNOetZsvbSo/XOd0RBfnEcyzttgsPIlHNAfaGPRlu+j1f2Mtq5A/kIGVs/k1XHhKE+vsqcBUT0cTPn6lYLLhWKuttmfzHTIyZEn/HgaTsVnW\ +a9DahavgUrXJPpnj08Bxsu874K5R5Pn3jO85EKXhXLhQq18Rx01HQdaQBG1AWoz5u/t/Ozl6TMZHwfP9AmP65SFrEKUJmG4U91cSvA3ZIWiiGeH54Sh93UgDQebw4QW5E+1QRMnKcDyTENEt4Spv0oR06eslH90x\ +2JxLZHj4r310JSZjj+LcHWz9TP8UlBPiYtBiS5dQJFXvac4HzPqZvC5O1QUth6xFc4gr8aRn6nUKPsscc0AikLAGMFvk0DARa9ke0dPinseYVTxNK5Bx1XGciCc8Jfx2OplEoeFUsOGIqemErI6ypyEkB7m7fYg3\ +3ZsheHscHGC3Fu/CarxgSooOHVGqmHtVTibJqICSM0R+z6qbDexwdhRnGk4tHAEQWJXLklugQSk046JBQuAC4Zxi6/seqAC/HV+x1c/5yKizERRgrtdSGnGDxfYbOIQZS+f3a/Mfjng3QO0GZXt1e7jXJec/5afR\ +dW84qHKkBtz7NvTqLD3iy4PbQj1xu9iHUe/pfPn2dDuEg9qVF5QM6H4W7q+GDfKGG1iegrqFXqRJfwyNXTDSKOvP9k5n4yqRdmnqj+zGmmUa0U4gdSJucziS7Wh8KsfgWcKBHUYDFQVIWsfhiiBfHzq7bhI6MVtC\ +M5hJPt3E7ML6zg6VHBSHthYvdZH9Fe/AyWzNENSXn5K+lP5OOpGf/bb0o5fxfTVO6CXjLn/HzzO+bEnQzaEpugbWK5DZClUEYRcoy/5TIMw00Q6IXn1YiBPysG0TbTv7IkRqEeDoUFdaOnEXbGDofCCTocOTwzlm\ +v0Lwj3xoybUFXqJH00z5dNCdiDDHhNmCtROTFY3OrzzwZy6lT1EfJfd42T5SapyycuAkHsujQ2FAz6jCIexCW8Hd70lOcIA2X4qn2OaIRsTJvEb1M2PvB6pYI1AsZskiJR3X5T4nqeV/ooQKfpCBGv1xFVnOKAnS\ +3TEbGkbTGbk/526zpuhgnLVagD89o7t4DHrDFhWlkDX/eqx6Xm04FGtQbvEQDhpOfMQ13LWtiHi3YR8IgnSzTvyZIOaMAhk1hPZSWAnMnl0Fne+zmM29BPU8KB4XDXR1tG1EWotIK5TeDtNU+YvUuTBKwaLYQ4aR\ +OlY0xwm9tmIp/+Tah6HC9U5HJj6UxPINcRUoXYFk3oFFj+C4P0V+ulw9rUZ+vJQ3hio6LyF2ac5MHMf6FLZ6ZNUV+TQsV+u3kLi32Q8wGQ8BW8gP+M7l41DU1BmtE89UZ5RWC2ngZWwtbD4hF7ez+5qgHmqxPRad\ +etJZO4D5Yg/qKJ6xriKLtpyCNALtzLpBszNJ6LFwa2lW04DC15bT2WqffPzgZ6ZcsYB2Id6gZJ/DDKvxuSNhpYEeLBOXicyvIsI130ZRI1kQKuPpao9qQ8SsWT5OcjC9bQMrxVWvMHNQqR/lIcbIuLkIKgdZsB9v\ +kOW7EIkhujD0GkJCMiDOvVQ4mvButj1Y0olMrATKkxUbQuFbBmuSCa2Kh9asmLPKbrTZ6Byu1TJrMQmI1c1IzMoPaZFSicIY1ivgfA24YSEitJbDpYizzym+Q0F070CdWnxjklnwlFD99GfaspMEZXwj2Ki7O+jt\ +AVX03FAl2ycn41m/fJoiGddP2d3DHWxCz3ikrRl9oNuxt5mLjmoIGMZGUcOAebEPstnYgQ0BZDmO15RahbC+T/hNBAIJK9UC39PJ50QCsjPG7WolnOrlYaLUF2BL0xD8nCGxQ0yFjynbJUtfVyHjkReUtcgAhp+t\ +OXcNUUQILnQU+2jFod/w6iMveh0nBKxcwUXVHDB2UbmhG8XqAEhwRcMeSEvhDLFj0U4ZDug5iovRDQEy1nXwjapkEIczpqtEcDSb80NPRHSYM4uK4sws4ge+NwzuikI+cM7gEyZURhX3j0XaTmr0EpOncUAOeAr3\ +ArqbhKZhDK9EA0DnJ9+Y0f007Dho2eZQv5dQv09S2afh2En01zKLW34GajBE+EA8BOTs8FXn7QfgzGeQw5f6BkI5Zz+zHtjoKQAQoOWCnNNS9QJMbPUHCvF6fjsC6MEijuOUmUvClh9IeklqDCUWcC8nfVwCa/J1\ +wXqIugZ1wPfOD+KxSIMaC73iGBt+sKrheaI2XDyCuWi57O1qgI+GH+ywA2sV78OuWMqoiTp8IhDmtuE5Ap9bohxEaqHEsMkyZtQXjgkgnyhvQKzuM/nHqkdH+Zkde0mPnVBig2IglkcH+K32XoAt3RM1OYMIo99l\ +OgC4mmxwcnElZCXAd3bvxQye3YLegaKfUZGgYUdrN5mY1CPY1K1an4P92Xo/KCkm8t1lxe+RwL0uLt2a9o/NGjBjdU7HTlAi3CYyKQnkpNy8Rq9oLj8LoQMEWZoZmWDf+0z+slpgeh0eppuCgZPXO85w+FlCEIRa\ +2QRELfWfOHQtB3c74wAHppnPcOELcK2vQJqPGEBBTpEfBwsc3+cdZ5fXC36aU+W34W12Jy7t/LrOCnm9U1JXgcLFDohrvZRCFXu4f/kbRsevJTR+dQkHoepmW6+g922IcOSBweqXUYxiLocE1ZnFLlD+Dpef8qYW\ +X0ttXMmhabDrUHElU0gheOW0FBZvfr+x8l41xNK7jLhswxiQuCxwG6OBMuYUVum+UomLTtnmAGK6nXKrDGXi0aOj2rslZ8E2LuWSnypX4tnWoTe38f+FwrkHXKOKrjec0H4luB0u04yWpoGQsJyXjNm1cyfB/6v2\ +y7+X9Uf4H2taVUVuc2WMH+mulx+/DJ1FUUJnWy9r/q9tUcF5h0fijfIqL0udffsvV73qkg==\ +"""))) + + +def _main(): + try: + main() + except FatalError as e: + print('\nA fatal error occurred: %s' % e) + sys.exit(2) + + +if __name__ == '__main__': + _main() diff --git a/flasher_stub/Makefile b/flasher_stub/Makefile new file mode 100644 index 0000000..a2260e4 --- /dev/null +++ b/flasher_stub/Makefile @@ -0,0 +1,88 @@ +# Makefile to compile the flasher stub program +# +# Note that YOU DO NOT NEED TO COMPILE THIS IN ORDER TO JUST USE +# esptool.py - a precompiled version is embedded in esptool.py, +# so if you don't want to modify the stub code then you are good to go. +# +# See the comments in the top of the Makefile for parameters that +# you probably want to override. +# +# Copyright (c) 2016 Cesanta Software Limited & Angus Gratton +# All rights reserved +# +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +# Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Adapted from Cesanta's original Makefile at +# https://github.com/cesanta/fnc/tree/master/common/platforms/esp8266/stubs + +# Override these variables on the command line +# or set them in a local.mk +-include local.mk + +# Prefix for ESP8266 & ESP32 cross compilers (can include a directory path) +CROSS_8266 ?= xtensa-lx106-elf- +CROSS_32 ?= xtensa-esp32-elf- + +# Path to the ESP8266 SDK root dir +SDK_PATH ?= ../../esp_iot_sdk_v2.0.0 + +# Path to the esp-idf root dir +IDF_PATH ?= ../../esp-idf + +# Python command to invoke wrap_stub.py +WRAP_STUB ?= ./wrap_stub.py + +# Pass V=1 to see the commands being executed by make +ifneq ("$(V)","1") +Q = @ +endif + +STUB = stub_flasher +SRCS = stub_flasher.c slip.c stub_commands.c stub_write_flash.c +SRCS_8266 = miniz.c + +BUILD_DIR = build + +STUB_ELF_8266 = $(BUILD_DIR)/$(STUB)_8266.elf +STUB_ELF_32 = $(BUILD_DIR)/$(STUB)_32.elf +STUB_PY = $(BUILD_DIR)/$(STUB)_snippet.py + +.PHONY: all clean + +all: $(STUB_PY) + +$(BUILD_DIR): + $(Q) mkdir $@ + +CFLAGS = -std=c99 -Wall -Werror -Os \ + -mtext-section-literals -mlongcalls -nostdlib -fno-builtin -flto \ + -Wl,-static -g -ffunction-sections -Wl,--gc-sections + +CFLAGS_8266 = $(CFLAGS) -I$(SDK_PATH) -I$(SDK_PATH)/include -L$(SDK_PATH)/ld +CFLAGS_32 = $(CFLAGS) -I$(IDF_PATH)/components/esp32/include -I$(IDF_PATH)/components/soc/esp32/include -L$(IDF_PATH) + +$(STUB_ELF_8266): $(SRCS) $(SRCS_8266) $(BUILD_DIR) stub_8266.ld | Makefile + @echo " CC(8266) $^ -> $@" + $(Q) $(CROSS_8266)gcc $(CFLAGS_8266) -DESP8266 -Tstub_8266.ld -Wl,-Map=$(@:.elf=.map) -o $@ $(filter %.c, $^) + +$(STUB_ELF_32): $(SRCS) $(BUILD_DIR) stub_32.ld | Makefile + @echo " CC(32) $^ -> $@" + $(Q) $(CROSS_32)gcc $(CFLAGS_32) -DESP32 -Tstub_32.ld -Wl,-Map=$(@:.elf=.map) -o $@ $(filter %.c, $^) + +$(STUB_PY): $(STUB_ELF_8266) $(STUB_ELF_32) wrap_stub.py + @echo " WRAP $^ -> $@" + $(Q) $(WRAP_STUB) $(filter %.elf,$^) $@ + +clean: + $(Q) rm -rf $(BUILD_DIR) diff --git a/flasher_stub/README.md b/flasher_stub/README.md new file mode 100644 index 0000000..0e4ae68 --- /dev/null +++ b/flasher_stub/README.md @@ -0,0 +1,39 @@ +This is the source of the software flasher stub. + +esptool.py loads the flasher stub into memory and executes it to: + +* Add features that the ESP8266 & ESP32 bootloader ROMs do not have. + +* Add features to the ESP8266 bootloader ROM which are only in the ESP32 ROM. + +* Improve flashing performance over the ROM bootloaders. + +* Work around bugs in the ESP8266 ROM bootloader. + +Thanks to [Cesanta](http://cesanta.com/) who provided the original ESP8266 stub loader upon which this loader is based. + +# To Use + +The stub loader is already automatically integrated into esptool.py. You don't need to do anything special to use it. + +# To Build + +If you want to build the stub to test modifications or updates, here's how: + +* You will need both an ESP8266 gcc toolchain (xtensa-lx106-elf-) and an ESP32 toolchain (xtensa-esp32-elf-) on your PATH. + +* Set the environment variables SDK_PATH to the path to an ESP8266 IoT NON-OS SDK directory (last stub was built with SDK v1.5.1). + +* Set the environment variable IDF_PATH to the path to an ESP-IDF directory. + +* Set any other environment variables you'd like to override in the Makefile. + +* To build type `make` + +# To Test + +To test the build stub, you can either copy-paste the code from `build/stub_flasher_snippet.py` into esptool.py manually. Or there are some convenience wrappers to make testing quicker to iterate on: + +* Running `esptool_test_stub.py` is the same as running `esptool.py`, only it uses the just-compiled stubs from the build directory. + +* Running `run_tests_with_stub.py` is the same as running `test/test_esptool.py`, only it uses the just-compiled stubs from the build directory. diff --git a/flasher_stub/compare_stubs.py b/flasher_stub/compare_stubs.py new file mode 100644 index 0000000..e05b76e --- /dev/null +++ b/flasher_stub/compare_stubs.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +import sys + +# Compare the esptool stub loaders to freshly built ones +# in the build directory +# +# (Used by Travis to verify the stubs are up to date.) + +def verbose_diff(new, old): + for k in [ "data_start", "text_start"]: + if new[k] != old[k]: + print("New %s 0x%x old %s 0%x" % (k, new[k], old[k])) + + for k in [ "data", "text" ]: + if len(new[k]) != len(old[k]): + print("New %s %d bytes, old stub code %d bytes" % (k, len(new[k]), len(old)[k])) + if new[k] != old[k]: + print("%s is different" % k) + if len(new[k]) == len(old[k]): + for b in range(len(new[k])): + if new[k][b] != old[k][b]: + print(" Byte 0x%x: new 0x%02x old 0x%02x" % (b, ord(new[k][b]), ord(old[k][b]))) + +if __name__ == "__main__": + same = True + sys.path.append("..") + import esptool + + old_8266_stub = esptool.ESP8266ROM.STUB_CODE + old_32_stub = esptool.ESP32ROM.STUB_CODE + + # hackiness: importing this module edits the esptool module + import esptool_test_stub + + if esptool.ESP8266ROM.STUB_CODE != old_8266_stub: + print("ESP8266 stub code in esptool.py is different to just-built stub") + verbose_diff(esptool.ESP8266ROM.STUB_CODE, old_8266_stub) + same = False + if esptool.ESP32ROM.STUB_CODE != old_32_stub: + print("ESP32 stub code in esptool.py is different to just-built stub.") + verbose_diff(esptool.ESP32ROM.STUB_CODE, old_32_stub) + same = False + if same: + print("Stub code is the same") + + sys.exit(0 if same else 1) diff --git a/flasher_stub/esptool_test_stub.py b/flasher_stub/esptool_test_stub.py new file mode 100644 index 0000000..3f242ec --- /dev/null +++ b/flasher_stub/esptool_test_stub.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# +# Trivial wrapper program to run esptool.py using the just-compiled +# flasher stub in the build/ subdirectory +# +# For use when developing new flasher_stubs, not important otherwise. +# +# Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, other contributors as noted. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later version. +# +import os.path +import sys + +THIS_DIR=os.path.dirname(sys.argv[0]) + +sys.path.append("..") +import esptool +# Python hackiness: evaluate the snippet in the context of the esptool module, so it +# edits the esptool's global variables +exec(open("%s/build/stub_flasher_snippet.py" % THIS_DIR).read(), esptool.__dict__, esptool.__dict__) + +if __name__ == "__main__": + try: + esptool.main() + except esptool.FatalError as e: + print('\nA fatal error occurred: %s' % e) + sys.exit(2) + diff --git a/flasher_stub/miniz.c b/flasher_stub/miniz.c new file mode 100644 index 0000000..9cecf43 --- /dev/null +++ b/flasher_stub/miniz.c @@ -0,0 +1,4932 @@ +/* + +Remark: If you change anything in the configuration of miniz, you MUST also update the header file miniz.h! - JD + +*/ + +/* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing + See "unlicense" statement at the end of this file. + Rich Geldreich , last updated Oct. 13, 2013 + Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define + MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Change History + 10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major release with Zip64 support (almost there!): + - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug + would only have occured in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place() + (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag). + - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size + - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries. + Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice). + - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes + - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed + - Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6. + - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti + - Merged MZ_FORCEINLINE fix from hdeanclark + - Fix include before config #ifdef, thanks emil.brink + - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can + set it to 1 for real-time compression). + - Merged in some compiler fixes from paulharris's github repro. + - Retested this build under Windows (VS 2010, including static analysis), tcc 0.9.26, gcc v4.6 and clang v3.3. + - Added example6.c, which dumps an image of the mandelbrot set to a PNG file. + - Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. + - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled + - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch + 5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). + 5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. + - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. + - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. + - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly + "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). + - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. + - Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. + - Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. + - Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) + - Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). + 4/12/12 v1.12 - More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's. + level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson for the feedback/bug report. + 5/28/11 v1.11 - Added statement from unlicense.org + 5/27/11 v1.10 - Substantial compressor optimizations: + - Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a + - Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86). + - Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types. + - Refactored the compression code for better readability and maintainability. + - Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large + drop in throughput on some files). + 5/15/11 v1.09 - Initial stable release. + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or + greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses + approximately as well as zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function + coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory + block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in + zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. + Supports raw deflate streams or standard zlib streams with adler-32 checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. + I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but + there are no guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by + Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to + get the job done with minimal fuss. There are simple API's to retrieve file information, read files from + existing archives, create new archives, append new files to existing archives, or clone archive data from + one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), + or you can specify custom file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central + directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one example) can be used to identify + multiple versions of the same file in an archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and + retrieve detailed info on each file by calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data + to disk and builds an exact image of the central directory in memory. The central directory image is written + all at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file data to any power of 2 alignment, + which can be useful when the archive will be read from optical media. Also, the writer supports placing + arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still + readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, + const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be appended to. + Note the appending is done in-place and is not an atomic operation, so if something goes wrong + during the operation it's possible the archive could be left without a central directory (although the local + file headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to + preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and + you're done. This is safe but requires a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), + append new files as needed, then finalize the archive which will write an updated central directory to the + original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a + possibility that the archive's central directory could be lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the + below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your target platform: + #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 + #define MINIZ_LITTLE_ENDIAN 1 + #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz + uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files + (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ + +#ifndef MINIZ_HEADER_INCLUDED +#define MINIZ_HEADER_INCLUDED + +#include + +// Defines to completely disable specific portions of miniz.c: +// If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. + +// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. +#define MINIZ_NO_STDIO + +// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or +// get/set file times, and the C run-time funcs that get/set times won't be called. +// The current downside is the times written to your archives will be from 1979. +#define MINIZ_NO_TIME + +// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. +#define MINIZ_NO_ARCHIVE_APIS + +// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive API's. +#define MINIZ_NO_ARCHIVE_WRITING_APIS + +// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. +#define MINIZ_NO_ZLIB_APIS + +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. +#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc +// callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user +// functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. +#define MINIZ_NO_MALLOC + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) + // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux + #define MINIZ_NO_TIME +#endif + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) + #include +#endif + +//Hardcoded options for Xtensa - JD +#define MINIZ_X86_OR_X64_CPU 0 +#define MINIZ_LITTLE_ENDIAN 1 +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#define MINIZ_HAS_64BIT_REGISTERS 0 +#define TINFL_USE_64BIT_BITBUF 0 + + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +// MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define MINIZ_X86_OR_X64_CPU 1 +#endif + +#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. +#define MINIZ_LITTLE_ENDIAN 1 +#endif + +#if MINIZ_X86_OR_X64_CPU +// Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). +#define MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API Definitions. + +// For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! +typedef unsigned long mz_ulong; + +// mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +// mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +// mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +// Compression strategies. +enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; + +// Method +#define MZ_DEFLATED 8 + +#ifndef MINIZ_NO_ZLIB_APIS + +// Heap allocation callbacks. +// Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +#define MZ_VERSION "9.1.15" +#define MZ_VERNUM 0x91F0 +#define MZ_VER_MAJOR 9 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 15 +#define MZ_VER_SUBREVISION 0 + +// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). +enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; + +// Return status codes. MZ_PARAM_ERROR is non-standard. +enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; + +// Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. +enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; + +// Window bits +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +// Compression/decompression stream struct. +typedef struct mz_stream_s +{ + const unsigned char *next_in; // pointer to next byte to read + unsigned int avail_in; // number of bytes available at next_in + mz_ulong total_in; // total number of bytes consumed so far + + unsigned char *next_out; // pointer to next byte to write + unsigned int avail_out; // number of bytes that can be written to next_out + mz_ulong total_out; // total number of bytes produced so far + + char *msg; // error msg (unused) + struct mz_internal_state *state; // internal state, allocated by zalloc/zfree + + mz_alloc_func zalloc; // optional heap allocation function (defaults to malloc) + mz_free_func zfree; // optional heap free function (defaults to free) + void *opaque; // heap alloc function user pointer + + int data_type; // data_type (unused) + mz_ulong adler; // adler32 of the source or uncompressed data + mz_ulong reserved; // not used +} mz_stream; + +typedef mz_stream *mz_streamp; + +// Returns the version string of miniz.c. +const char *mz_version(void); + +// mz_deflateInit() initializes a compressor with default options: +// Parameters: +// pStream must point to an initialized mz_stream struct. +// level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. +// level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. +// (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if the input parameters are bogus. +// MZ_MEM_ERROR on out of memory. +int mz_deflateInit(mz_streamp pStream, int level); + +// mz_deflateInit2() is like mz_deflate(), except with more control: +// Additional parameters: +// method must be MZ_DEFLATED +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) +// mem_level must be between [1, 9] (it's checked but ignored by miniz.c) +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +// Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). +int mz_deflateReset(mz_streamp pStream); + +// mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. +// Return values: +// MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). +// MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) +int mz_deflate(mz_streamp pStream, int flush); + +// mz_deflateEnd() deinitializes a compressor: +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +int mz_deflateEnd(mz_streamp pStream); + +// mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +// Single-call compression functions mz_compress() and mz_compress2(): +// Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +// mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). +mz_ulong mz_compressBound(mz_ulong source_len); + +// Initializes a decompressor. +int mz_inflateInit(mz_streamp pStream); + +// mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +// Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. +// On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). +// MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. +// Return values: +// MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. +// MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_DATA_ERROR if the deflate stream is invalid. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again +// with more input data, or with more room in the output buffer (except when using single call decompression, described above). +int mz_inflate(mz_streamp pStream, int flush); + +// Deinitializes a decompressor. +int mz_inflateEnd(mz_streamp pStream); + +// Single-call decompression. +// Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); + +// Returns a string description of the specified error code, or NULL if the error code is invalid. +const char *mz_error(int err); + +// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + typedef unsigned char Byte; + typedef unsigned int uInt; + typedef mz_ulong uLong; + typedef Byte Bytef; + typedef uInt uIntf; + typedef char charf; + typedef int intf; + typedef void *voidpf; + typedef uLong uLongf; + typedef void *voidp; + typedef void *const voidpc; + #define Z_NULL 0 + #define Z_NO_FLUSH MZ_NO_FLUSH + #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH + #define Z_SYNC_FLUSH MZ_SYNC_FLUSH + #define Z_FULL_FLUSH MZ_FULL_FLUSH + #define Z_FINISH MZ_FINISH + #define Z_BLOCK MZ_BLOCK + #define Z_OK MZ_OK + #define Z_STREAM_END MZ_STREAM_END + #define Z_NEED_DICT MZ_NEED_DICT + #define Z_ERRNO MZ_ERRNO + #define Z_STREAM_ERROR MZ_STREAM_ERROR + #define Z_DATA_ERROR MZ_DATA_ERROR + #define Z_MEM_ERROR MZ_MEM_ERROR + #define Z_BUF_ERROR MZ_BUF_ERROR + #define Z_VERSION_ERROR MZ_VERSION_ERROR + #define Z_PARAM_ERROR MZ_PARAM_ERROR + #define Z_NO_COMPRESSION MZ_NO_COMPRESSION + #define Z_BEST_SPEED MZ_BEST_SPEED + #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION + #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION + #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY + #define Z_FILTERED MZ_FILTERED + #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY + #define Z_RLE MZ_RLE + #define Z_FIXED MZ_FIXED + #define Z_DEFLATED MZ_DEFLATED + #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS + #define alloc_func mz_alloc_func + #define free_func mz_free_func + #define internal_state mz_internal_state + #define z_stream mz_stream + #define deflateInit mz_deflateInit + #define deflateInit2 mz_deflateInit2 + #define deflateReset mz_deflateReset + #define deflate mz_deflate + #define deflateEnd mz_deflateEnd + #define deflateBound mz_deflateBound + #define compress mz_compress + #define compress2 mz_compress2 + #define compressBound mz_compressBound + #define inflateInit mz_inflateInit + #define inflateInit2 mz_inflateInit2 + #define inflate mz_inflate + #define inflateEnd mz_inflateEnd + #define uncompress mz_uncompress + #define crc32 mz_crc32 + #define adler32 mz_adler32 + #define MAX_WBITS 15 + #define MAX_MEM_LEVEL 9 + #define zError mz_error + #define ZLIB_VERSION MZ_VERSION + #define ZLIB_VERNUM MZ_VERNUM + #define ZLIB_VER_MAJOR MZ_VER_MAJOR + #define ZLIB_VER_MINOR MZ_VER_MINOR + #define ZLIB_VER_REVISION MZ_VER_REVISION + #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION + #define zlibVersion mz_version + #define zlib_version mz_version() +#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Types and macros + +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef long long mz_int64; +typedef unsigned long long mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +// An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message. +#ifdef _MSC_VER + #define MZ_MACRO_END while (0, 0) +#else + #define MZ_MACRO_END while (0) +#endif + +// ------------------- ZIP archive reading/writing + +#ifndef MINIZ_NO_ARCHIVE_APIS + +enum +{ + MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +}; + +typedef struct +{ + mz_uint32 m_file_index; + mz_uint32 m_central_dir_ofs; + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; +#ifndef MINIZ_NO_TIME + time_t m_time; +#endif + mz_uint32 m_crc32; + mz_uint64 m_comp_size; + mz_uint64 m_uncomp_size; + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + mz_uint64 m_local_header_ofs; + mz_uint32 m_comment_size; + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum +{ + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef struct mz_zip_archive_tag +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + mz_uint m_total_files; + mz_zip_mode m_zip_mode; + + mz_uint m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef enum +{ + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +} mz_zip_flags; + +// ZIP archive reading + +// Inits a ZIP archive reader. +// These functions read and validate the archive's central directory. +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags); +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +#endif + +// Returns the total number of files in the archive. +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +// Returns detailed information about an archive file entry. +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +// Determines if an archive file entry is a directory entry. +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +// Retrieves the filename of an archive file entry. +// Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +// Attempts to locates a file in the archive's central directory. +// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH +// Returns -1 if the file cannot be found. +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + +// Extracts a archive file to a memory buffer using no memory allocation. +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +// Extracts a archive file to a memory buffer. +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +// Extracts a archive file to a dynamically allocated heap buffer. +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +// Extracts a archive file using a callback function to output the file's data. +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +// Extracts a archive file to a disk file and sets its last accessed and modified times. +// This function only extracts files, not archive directory records. +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); +#endif + +// Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +// ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +// Inits a ZIP archive writer. +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +#endif + +// Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. +// For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. +// For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). +// Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. +// Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before +// the archive is finalized the file's central directory will be hosed. +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); + +// Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. +// To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +#ifndef MINIZ_NO_STDIO +// Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +#endif + +// Adds a file to an archive by fully cloning the data from another archive. +// This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields. +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index); + +// Finalizes the archive by writing the central directory records followed by the end of central directory record. +// After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). +// An archive must be manually finalized by calling this function for it to be valid. +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize); + +// Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. +// Note for the archive to be valid, it must have been finalized before ending. +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +// Misc. high-level helper functions: + +// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +// Reads a single file from an archive into a heap block. +// Returns NULL on failure. +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +// ------------------- Low-level Decompression API Definitions + +// Decompression flags used by tinfl_decompress(). +// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. +// TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. +// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). +// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +// High level decompression functions: +// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. +// On return: +// Function returns a pointer to the decompressed data, or NULL on failure. +// *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must call mz_free() on the returned block when it's no longer needed. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. +// Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. +// Returns 1 on success or 0 on failure. +typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; + +// Max size of LZ dictionary. +#define TINFL_LZ_DICT_SIZE 32768 + +// Return status. +typedef enum +{ + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +// Initializes the decompressor to its initial state. +#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +// Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. +// This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +// Internal/private bits follow. +enum +{ + TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS + #define TINFL_USE_64BIT_BITBUF 1 +#endif + +#if TINFL_USE_64BIT_BITBUF + typedef mz_uint64 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (64) +#else + typedef mz_uint32 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +// ------------------- Low-level Compression API Definitions + +// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). +#define TDEFL_LESS_MEMORY 1 + +// tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): +// TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). +enum +{ + TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. +// TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). +// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. +// TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). +// TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) +// TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. +// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. +// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. +// The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). +enum +{ + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +// High level compression functions: +// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of source block to compress. +// flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must free() the returned block when it's no longer needed. +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. +// Returns 0 on failure. +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// Compresses an image to a compressed PNG file in memory. +// On entry: +// pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. +// The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. +// level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL +// If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pLen_out will be set to the size of the PNG image file. +// The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +// Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); + +// tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = (8*1024), TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; + +// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). +#if TDEFL_LESS_MEMORY +enum { TDEFL_LZ_CODE_BUF_SIZE = 8 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#else +enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#endif + +// The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. +typedef enum +{ + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1, +} tdefl_status; + +// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums +typedef enum +{ + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +// tdefl's compression state structure. +typedef struct +{ + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +// Initializes the compressor. +// There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. +// pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. +// If pBut_buf_func is NULL the user should always call the tdefl_compress() API. +// flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +// Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. +// tdefl_compress_buffer() always consumes the entire input buffer. +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros. +#ifndef MINIZ_NO_ZLIB_APIS +// Create tdefl_compress() flags given zlib-style compression parameters. +// level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) +// window_bits may be -15 (raw deflate) or 15 (zlib) +// strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); +#endif // #ifndef MINIZ_NO_ZLIB_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_INCLUDED + +// ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.) + +#ifndef MINIZ_HEADER_FILE_ONLY + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16)==2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32)==4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64)==8 ? 1 : -1]; + +#include +#include + +//Disable asserts +#define MZ_ASSERT(x) +//#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC + #define MZ_MALLOC(x) NULL + #define MZ_FREE(x) (void)x, ((void)0) + #define MZ_REALLOC(p, x) NULL +#else + #define MZ_MALLOC(x) malloc(x) + #define MZ_FREE(x) free(x) + #define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a,b) (((a)>(b))?(a):(b)) +#define MZ_MIN(a,b) (((a)<(b))?(a):(b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) + #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else + #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) + #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#ifdef _MSC_VER + #define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) + #define MZ_FORCEINLINE inline __attribute__((__always_inline__)) +#else + #define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +// ------------------- zlib-style API's + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; + if (!ptr) return MZ_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + return (s2 << 16) + s1; +} + +// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) return MZ_CRC32_INIT; + crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } + return ~crcu32; +} + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +#ifndef MINIZ_NO_ZLIB_APIS + +static void *def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } +static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } +static void *def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, ((tdefl_compressor*)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; + if (!pStream->avail_out) return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; + for ( ; ; ) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; // Can't make forward progress without some input. + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state* pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + + pState = (inflate_state*)pStream->state; + if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; pState->m_first_call = 0; + if (pState->m_last_status < 0) return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + // flush != MZ_FINISH then we must assume there's more input. + if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for ( ; ; ) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. + else if (flush == MZ_FINISH) + { + // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) +{ + static struct { int m_err; const char *m_pDesc; } s_error_descs[] = + { + { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, + { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } + }; + mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif //MINIZ_NO_ZLIB_APIS + +// ------------------- Low-level Decompression (completely independent from all compression API's) + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN switch(r->m_state) { case 0: +#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END +#define TINFL_CR_FINISH } + +// TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never +// reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. +#define TINFL_GET_BYTE(state_index, c) do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for ( ; ; ) { \ + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ + TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else c = *pIn_buf_cur++; } MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END + +// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. +// It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a +// Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the +// bit buffer contains >=15 bits (deflate's max. Huffman code size). +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ + } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ + } while (num_bits < 15); + +// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read +// beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully +// decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. +// The slow path is only executed at the very end of the input buffer. +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ + int temp; mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ + } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; + static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } + + num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } + while (pIn_buf_cur >= pIn_buf_end) + { + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) + { + TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); + } + else + { + TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); + } + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; + r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } + r->m_table_sizes[2] = 19; + } + for ( ; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; + cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) + { + mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for ( ; ; ) + { + mz_uint8 *pSrc; + for ( ; ; ) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } +#else + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + counter = sym2; bit_buf >>= code_len; num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + bit_buf >>= code_len; num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) break; + + num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + TINFL_CR_FINISH + +common_exit: + r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +// Higher level helper functions. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for ( ; ; ) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) break; + new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for ( ; ; ) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +// ------------------- Low-level Compression (independent from all decompression API's) + +// Purposely making these tables static for faster init and thread safety. +static const mz_uint16 s_tdefl_len_sym[256] = { + 257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272, + 273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276, + 277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, + 279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280, + 281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281, + 282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282, + 283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283, + 284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 }; + +static const mz_uint8 s_tdefl_len_extra[256] = { + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 }; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = { + 0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 }; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = { + 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7 }; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = { + 0,0,18,19,20,20,21,21,22,22,22,22,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,26,26,26,26,26,26,26,26,26,26,26,26, + 26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28, + 28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29 }; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = { + 0,0,8,8,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 }; + +// Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. +typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; +static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, tdefl_sym_freq* pSyms1) +{ + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq* pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const mz_uint32* pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } + for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { tdefl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } + } + return pCur_syms; +} + +// tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) +{ + int root, leaf, next, avbl, used, dpth; + if (n==0) return; else if (n==1) { A[0].m_key = 1; return; } + A[0].m_key += A[1].m_key; root = 0; leaf = 2; + for (next=1; next < n-1; next++) + { + if (leaf>=n || A[root].m_key=n || (root=0; next--) A[next].m_key = A[A[next].m_key].m_key+1; + avbl = 1; used = dpth = 0; root = n-2; next = n-1; + while (avbl>0) + { + while (root>=0 && (int)A[root].m_key==dpth) { used++; root--; } + while (avbl>used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } + avbl = 2*used; dpth++; used = 0; + } +} + +// Limits canonical Huffman code table's max code size. +enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) +{ + int i; mz_uint32 total = 0; if (code_list_len <= 1) return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) +{ + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; + code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) do { \ + mz_uint bits = b; mz_uint len = l; MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); d->m_bits_in += len; \ + while (d->m_bits_in >= 8) { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ +} MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \ + if (rle_repeat_count < 3) { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } else { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ +} rle_repeat_count = 0; } } + +#define TDEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \ + if (rle_z_count < 3) { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \ + } else if (rle_z_count <= 10) { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } else { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ +} rle_z_count = 0; } } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) +{ + int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; ) + { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) +{ + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) *p++ = 8; + for ( ; i <= 255; ++i) *p++ = 9; + for ( ; i <= 279; ++i) *p++ = 7; + for ( ; i <= 287; ++i) *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) { bit_buffer |= (((mz_uint64)(b)) << bits_in); bits_in += (l); } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) + { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + // This sequence coaxes MSVC into using cmov's vs. jmp's. + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64*)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) + { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) + { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + if (match_dist < 512) + { + sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } + else + { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) +{ + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. + if ( ((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size) ) + { + mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } + } + else + { + mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16*)(p) +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for ( ; ; ) + { + for ( ; ; ) + { + if (--num_probes_left == 0) return; + #define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; q = (const mz_uint16*)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD(q) != s01) continue; p = s; probe_len = 32; + do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); + if (!probe_len) + { + *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for ( ; ; ) + { + for ( ; ; ) + { + if (--num_probes_left == 0) return; + #define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; + if (probe_len > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; + c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) + { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) + { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + // Simple lazy/greedy parsing state machine. + len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + // Move the lookahead forward by len_to_move bytes. + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); + // Check if it's time to flush the current LZ codes to the internal output buffer. + if ( (d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ( (d->m_total_lz_bytes > 31*1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) ) + { + int n; + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) +{ + if (!d) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if ( ((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf) ) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; + d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; + pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; + do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); + pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; + p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; + } + memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; + *pOut_len = out_buf.m_size; return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) return 0; + out_buf.m_pBuf = (mz_uint8*)pOut_buf; out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; + return out_buf.m_size; +} + +#ifndef MINIZ_NO_ZLIB_APIS +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +// level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} +#endif //MINIZ_NO_ZLIB_APIS + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable:4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) +#endif + +// Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at +// http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. +// This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) +{ + // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; + if (!pComp) return NULL; + MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } + // write dummy header + for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); + // compress image data + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + // write real header + *pLen_out = out_buf.m_size-41; + { + static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; + mz_uint8 pnghdr[41]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52, + 0,0,(mz_uint8)(w>>8),(mz_uint8)w,0,0,(mz_uint8)(h>>8),(mz_uint8)h,8,chans[num_chans],0,0,0,0,0,0,0, + (mz_uint8)(*pLen_out>>24),(mz_uint8)(*pLen_out>>16),(mz_uint8)(*pLen_out>>8),(mz_uint8)*pLen_out,0x49,0x44,0x41,0x54}; + c=(mz_uint32)mz_crc32(MZ_CRC32_INIT,pnghdr+12,17); for (i=0; i<4; ++i, c<<=8) ((mz_uint8*)(pnghdr+29))[i]=(mz_uint8)(c>>24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + // write footer (IDAT CRC-32, followed by IEND chunk) + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+41-4, *pLen_out+4); for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-16)[i] = (mz_uint8)(c >> 24); + // compute final size of file, grab compressed data buffer and return + *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +// ------------------- .ZIP archive reading + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef MINIZ_NO_STDIO + #define MZ_FILE void * +#else + #include + #include + + #if defined(_MSC_VER) || defined(__MINGW64__) + static FILE *mz_fopen(const char *pFilename, const char *pMode) + { + FILE* pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; + } + static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) + { + FILE* pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; + } + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN mz_fopen + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 _ftelli64 + #define MZ_FSEEK64 _fseeki64 + #define MZ_FILE_STAT_STRUCT _stat + #define MZ_FILE_STAT _stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN mz_freopen + #define MZ_DELETE_FILE remove + #elif defined(__MINGW32__) + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello64 + #define MZ_FSEEK64 fseeko64 + #define MZ_FILE_STAT_STRUCT _stat + #define MZ_FILE_STAT _stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #elif defined(__TINYC__) + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftell + #define MZ_FSEEK64 fseek + #define MZ_FILE_STAT_STRUCT stat + #define MZ_FILE_STAT stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #elif defined(__GNUC__) && _LARGEFILE64_SOURCE + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen64(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello64 + #define MZ_FSEEK64 fseeko64 + #define MZ_FILE_STAT_STRUCT stat64 + #define MZ_FILE_STAT stat64 + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) + #define MZ_DELETE_FILE remove + #else + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello + #define MZ_FSEEK64 fseeko + #define MZ_FILE_STAT_STRUCT stat + #define MZ_FILE_STAT stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #endif // #ifdef _MSC_VER +#endif // #ifdef MINIZ_NO_STDIO + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +// Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. +enum +{ + // ZIP archive identifiers and record sizes + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + // Central directory header record offsets + MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + // Local directory header offsets + MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + // End of central directory offsets + MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + MZ_FILE *m_pFile; + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; + if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; + pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; + memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +static void mz_zip_time_to_dos_time(time_t time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif + +#ifndef MINIZ_NO_STDIO +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef MINIZ_NO_TIME + (void)pFilename; *pDOS_date = *pDOS_time = 0; +#else + struct MZ_FILE_STAT_STRUCT file_stat; + // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date); +#endif // #ifdef MINIZ_NO_TIME + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, time_t modified_time) +{ + struct utimbuf t; t.actime = access_time; t.modtime = modified_time; + return !utime(pFilename, &t); +} +#endif // #ifndef MINIZ_NO_TIME +#endif // #ifndef MINIZ_NO_STDIO + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) do { mz_uint32 t = a; a = b; b = t; } MZ_MACRO_END + +// Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + int start = (size - 2) >> 1, end; + while (start >= 0) + { + int child, root = start; + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= size) + break; + child += (((child + 1) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + start--; + } + + end = size - 1; + while (end > 0) + { + int child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= end) + break; + child += (((child + 1) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint32 flags) +{ + mz_uint cdir_size, num_this_disk, cdir_disk_index; + mz_uint64 cdir_ofs; + mz_int64 cur_file_ofs; + const mz_uint8 *p; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + // Find the end of central directory record by scanning the file from the end towards the beginning. + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for ( ; ; ) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + for (i = n - 4; i >= 0; --i) + if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + break; + if (i >= 0) + { + cur_file_ofs += i; + break; + } + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return MZ_FALSE; + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + // Read and verify the end of central directory record. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || + ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) + return MZ_FALSE; + + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return MZ_FALSE; + + if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return MZ_FALSE; + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + + // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices. + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return MZ_FALSE; + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return MZ_FALSE; + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return MZ_FALSE; + + // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported). + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, comp_size, decomp_size, disk_index; + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return MZ_FALSE; + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF)) + return MZ_FALSE; + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index != num_this_disk) && (disk_index != 1)) + return MZ_FALSE; + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return MZ_FALSE; + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return MZ_FALSE; + n -= total_header_size; p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return MZ_FALSE; + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags) +{ + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + pZip->m_pState->m_mem_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + mz_uint64 file_size; + MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return MZ_FALSE; + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + file_size = MZ_FTELL64(pFile); + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & 1); +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, external_attr; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + + // First see if the filename ends with a '/' character. + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. + // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. + // FIXME: Remove this check? Is it necessary - we already check the filename. + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & 0x10) != 0) + return MZ_TRUE; + + return MZ_FALSE; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if ((!p) || (!pStat)) + return MZ_FALSE; + + // Unpack the central directory record. + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + // Copy as much of the filename and comment as possible. + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; + + return MZ_TRUE; +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + int l = 0, h = size - 1; + while (l <= h) + { + int m = (l + h) >> 1, file_index = pIndices[m], comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + return file_index; + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + return -1; +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint file_index; size_t name_len, comment_len; + if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return -1; + if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + return mz_zip_reader_locate_file_binary_search(pZip, pName); + name_len = strlen(pName); if (name_len > 0xFFFF) return -1; + comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1; + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) + return file_index; + } + return -1; +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((buf_size) && (!pBuf)) + return MZ_FALSE; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Ensure supplied output buffer is large enough. + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return MZ_FALSE; + return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); + } + + // Decompress the file either directly from memory or from a file input buffer. + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + // Read directly from the archive in memory. + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + // Use a user provided read buffer. + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + // Temporarily allocate a read buffer. + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#endif + return MZ_FALSE; + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + if (!p) + return NULL; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#endif + return NULL; + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + return NULL; + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + { + if (pSize) *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + // Decompress the file either directly from memory or from a file input buffer. + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pState->m_pMem) + { +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#endif + return MZ_FALSE; + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + status = TINFL_STATUS_FAILED; + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + status = TINFL_STATUS_FAILED; + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + status = TINFL_STATUS_FAILED; + break; + } + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return MZ_FALSE; + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + if (MZ_FCLOSE(pFile) == EOF) + return MZ_FALSE; +#ifndef MINIZ_NO_TIME + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + return status; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} +#endif + +// ------------------- .ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } +static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (pZip->m_file_offset_alignment) + { + // Ensure user specified file offset alignment is a power of 2. + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return MZ_FALSE; + } + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); +#ifdef _MSC_VER + if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#else + if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#endif + return 0; + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + return 0; + pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + MZ_FILE *pFile; + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_pFile = pFile; + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf); + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + cur_ofs += n; size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + // No sense in trying to write to an archive that's already at the support max size + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + pFilename; return MZ_FALSE; +#else + // Archive is being read from stdio - try to reopen as writable. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + if (!pFilename) + return MZ_FALSE; + pZip->m_pWrite = mz_zip_file_write_func; + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. + mz_zip_reader_end(pZip); + return MZ_FALSE; + } +#endif // #ifdef MINIZ_NO_STDIO + } + else if (pState->m_pMem) + { + // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + } + // Archive is being read via a user provided read function - make sure the user has specified a write function too. + else if (!pZip->m_pWrite) + return MZ_FALSE; + + // Start writing new files at the archive's current central directory location. + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_central_directory_file_ofs = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + // No zip64 support yet + if ((local_header_ofs > 0xFFFFFFFF) || (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + comment_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) + return MZ_FALSE; + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + // Try to push the central directory array back into its original state. + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. + if (*pArchive_name == '/') + return MZ_FALSE; + while (*pArchive_name) + { + if ((*pArchive_name == '\\') || (*pArchive_name == ':')) + return MZ_FALSE; + pArchive_name++; + } + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return MZ_FALSE; + cur_file_ofs += s; n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return MZ_FALSE; + // No zip64 support yet + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + +#ifndef MINIZ_NO_TIME + { + time_t cur_time; time(&cur_time); + mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif // #ifndef MINIZ_NO_TIME + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + // Set DOS Subdirectory attribute bit. + ext_attributes |= 0x10; + // Subdirectories cannot contain data. + if ((buf_size) || (uncomp_size)) + return MZ_FALSE; + } + + // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return MZ_FALSE; + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return MZ_FALSE; + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + method = MZ_DEFLATED; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + MZ_FILE *pSrc_file = NULL; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date)) + return MZ_FALSE; + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return MZ_FALSE; + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + if (uncomp_size > 0xFFFFFFFF) + { + // No zip64 support yet + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (uncomp_size) + { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + if (!level) + { + while (uncomp_remaining) + { + mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + for ( ; ; ) + { + size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + + if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) + break; + + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + break; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + MZ_FCLOSE(pSrc_file); pSrc_file = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes; + mz_uint64 comp_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; const mz_uint8 *pSrc_central_header; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) + return MZ_FALSE; + pState = pZip->m_pState; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + cur_dst_file_ofs = pZip->m_archive_size; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + cur_dst_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(sizeof(mz_uint32) * 4, MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining))))) + return MZ_FALSE; + + while (comp_bytes_remaining) + { + n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_dst_file_ofs += n; + + comp_bytes_remaining -= n; + } + + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + // Copy data descriptor + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + // no zip64 support yet + if (cur_dst_file_ofs > 0xFFFFFFFF) + return MZ_FALSE; + + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return MZ_FALSE; + + n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + if (pState->m_central_dir.m_size > 0xFFFFFFFF) + return MZ_FALSE; + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + + pState = pZip->m_pState; + + // no zip64 support yet + if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + // Write central directory + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) + return MZ_FALSE; + pZip->m_archive_size += central_dir_size; + } + + // Write end of central directory record + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr)) + return MZ_FALSE; +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return MZ_FALSE; +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_archive_size += sizeof(hdr); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) + return MZ_FALSE; + if (pZip->m_pWrite != mz_zip_heap_write_func) + return MZ_FALSE; + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *pBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + return MZ_FALSE; + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + MZ_CLEAR_OBJ(zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + // Create a new archive. + if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) + return MZ_FALSE; + created_new_archive = MZ_TRUE; + } + else + { + // Append to an existing archive. + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return MZ_FALSE; + if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) + { + mz_zip_reader_end(&zip_archive); + return MZ_FALSE; + } + } + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) + if (!mz_zip_writer_finalize_archive(&zip_archive)) + status = MZ_FALSE; + if (!mz_zip_writer_end(&zip_archive)) + status = MZ_FALSE; + if ((!status) && (created_new_archive)) + { + // It's a new archive and something went wrong, so just delete it. + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + return status; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + int file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + return NULL; + + MZ_CLEAR_OBJ(zip_archive); + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return NULL; + + if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0) + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + + mz_zip_reader_end(&zip_archive); + return p; +} + +#endif // #ifndef MINIZ_NO_STDIO + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_FILE_ONLY + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ diff --git a/flasher_stub/miniz.h b/flasher_stub/miniz.h new file mode 100644 index 0000000..ed79beb --- /dev/null +++ b/flasher_stub/miniz.h @@ -0,0 +1,777 @@ +#ifndef MINIZ_HEADER_INCLUDED +#define MINIZ_HEADER_INCLUDED + +#include + +// Defines to completely disable specific portions of miniz.c: +// If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. + +// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. +#define MINIZ_NO_STDIO + +// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or +// get/set file times, and the C run-time funcs that get/set times won't be called. +// The current downside is the times written to your archives will be from 1979. +#define MINIZ_NO_TIME + +// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. +#define MINIZ_NO_ARCHIVE_APIS + +// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive API's. +#define MINIZ_NO_ARCHIVE_WRITING_APIS + +// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. +#define MINIZ_NO_ZLIB_APIS + +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. +#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc +// callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user +// functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. +#define MINIZ_NO_MALLOC + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) + // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux + #define MINIZ_NO_TIME +#endif + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) + #include +#endif + +//Hardcoded options for Xtensa - JD +#define MINIZ_X86_OR_X64_CPU 0 +#define MINIZ_LITTLE_ENDIAN 1 +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#define MINIZ_HAS_64BIT_REGISTERS 0 +#define TINFL_USE_64BIT_BITBUF 0 + + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +// MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define MINIZ_X86_OR_X64_CPU 1 +#endif + +#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. +#define MINIZ_LITTLE_ENDIAN 1 +#endif + +#if MINIZ_X86_OR_X64_CPU +// Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). +#define MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API Definitions. + +// For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! +typedef unsigned long mz_ulong; + +// mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +// mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +// mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +// Compression strategies. +enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; + +// Method +#define MZ_DEFLATED 8 + +#ifndef MINIZ_NO_ZLIB_APIS + +// Heap allocation callbacks. +// Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +#define MZ_VERSION "9.1.15" +#define MZ_VERNUM 0x91F0 +#define MZ_VER_MAJOR 9 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 15 +#define MZ_VER_SUBREVISION 0 + +// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). +enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; + +// Return status codes. MZ_PARAM_ERROR is non-standard. +enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; + +// Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. +enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; + +// Window bits +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +// Compression/decompression stream struct. +typedef struct mz_stream_s +{ + const unsigned char *next_in; // pointer to next byte to read + unsigned int avail_in; // number of bytes available at next_in + mz_ulong total_in; // total number of bytes consumed so far + + unsigned char *next_out; // pointer to next byte to write + unsigned int avail_out; // number of bytes that can be written to next_out + mz_ulong total_out; // total number of bytes produced so far + + char *msg; // error msg (unused) + struct mz_internal_state *state; // internal state, allocated by zalloc/zfree + + mz_alloc_func zalloc; // optional heap allocation function (defaults to malloc) + mz_free_func zfree; // optional heap free function (defaults to free) + void *opaque; // heap alloc function user pointer + + int data_type; // data_type (unused) + mz_ulong adler; // adler32 of the source or uncompressed data + mz_ulong reserved; // not used +} mz_stream; + +typedef mz_stream *mz_streamp; + +// Returns the version string of miniz.c. +const char *mz_version(void); + +// mz_deflateInit() initializes a compressor with default options: +// Parameters: +// pStream must point to an initialized mz_stream struct. +// level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. +// level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. +// (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if the input parameters are bogus. +// MZ_MEM_ERROR on out of memory. +int mz_deflateInit(mz_streamp pStream, int level); + +// mz_deflateInit2() is like mz_deflate(), except with more control: +// Additional parameters: +// method must be MZ_DEFLATED +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) +// mem_level must be between [1, 9] (it's checked but ignored by miniz.c) +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +// Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). +int mz_deflateReset(mz_streamp pStream); + +// mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. +// Return values: +// MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). +// MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) +int mz_deflate(mz_streamp pStream, int flush); + +// mz_deflateEnd() deinitializes a compressor: +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +int mz_deflateEnd(mz_streamp pStream); + +// mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +// Single-call compression functions mz_compress() and mz_compress2(): +// Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +// mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). +mz_ulong mz_compressBound(mz_ulong source_len); + +// Initializes a decompressor. +int mz_inflateInit(mz_streamp pStream); + +// mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +// Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. +// On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). +// MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. +// Return values: +// MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. +// MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_DATA_ERROR if the deflate stream is invalid. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again +// with more input data, or with more room in the output buffer (except when using single call decompression, described above). +int mz_inflate(mz_streamp pStream, int flush); + +// Deinitializes a decompressor. +int mz_inflateEnd(mz_streamp pStream); + +// Single-call decompression. +// Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); + +// Returns a string description of the specified error code, or NULL if the error code is invalid. +const char *mz_error(int err); + +// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + typedef unsigned char Byte; + typedef unsigned int uInt; + typedef mz_ulong uLong; + typedef Byte Bytef; + typedef uInt uIntf; + typedef char charf; + typedef int intf; + typedef void *voidpf; + typedef uLong uLongf; + typedef void *voidp; + typedef void *const voidpc; + #define Z_NULL 0 + #define Z_NO_FLUSH MZ_NO_FLUSH + #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH + #define Z_SYNC_FLUSH MZ_SYNC_FLUSH + #define Z_FULL_FLUSH MZ_FULL_FLUSH + #define Z_FINISH MZ_FINISH + #define Z_BLOCK MZ_BLOCK + #define Z_OK MZ_OK + #define Z_STREAM_END MZ_STREAM_END + #define Z_NEED_DICT MZ_NEED_DICT + #define Z_ERRNO MZ_ERRNO + #define Z_STREAM_ERROR MZ_STREAM_ERROR + #define Z_DATA_ERROR MZ_DATA_ERROR + #define Z_MEM_ERROR MZ_MEM_ERROR + #define Z_BUF_ERROR MZ_BUF_ERROR + #define Z_VERSION_ERROR MZ_VERSION_ERROR + #define Z_PARAM_ERROR MZ_PARAM_ERROR + #define Z_NO_COMPRESSION MZ_NO_COMPRESSION + #define Z_BEST_SPEED MZ_BEST_SPEED + #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION + #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION + #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY + #define Z_FILTERED MZ_FILTERED + #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY + #define Z_RLE MZ_RLE + #define Z_FIXED MZ_FIXED + #define Z_DEFLATED MZ_DEFLATED + #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS + #define alloc_func mz_alloc_func + #define free_func mz_free_func + #define internal_state mz_internal_state + #define z_stream mz_stream + #define deflateInit mz_deflateInit + #define deflateInit2 mz_deflateInit2 + #define deflateReset mz_deflateReset + #define deflate mz_deflate + #define deflateEnd mz_deflateEnd + #define deflateBound mz_deflateBound + #define compress mz_compress + #define compress2 mz_compress2 + #define compressBound mz_compressBound + #define inflateInit mz_inflateInit + #define inflateInit2 mz_inflateInit2 + #define inflate mz_inflate + #define inflateEnd mz_inflateEnd + #define uncompress mz_uncompress + #define crc32 mz_crc32 + #define adler32 mz_adler32 + #define MAX_WBITS 15 + #define MAX_MEM_LEVEL 9 + #define zError mz_error + #define ZLIB_VERSION MZ_VERSION + #define ZLIB_VERNUM MZ_VERNUM + #define ZLIB_VER_MAJOR MZ_VER_MAJOR + #define ZLIB_VER_MINOR MZ_VER_MINOR + #define ZLIB_VER_REVISION MZ_VER_REVISION + #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION + #define zlibVersion mz_version + #define zlib_version mz_version() +#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Types and macros + +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef long long mz_int64; +typedef unsigned long long mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +// An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message. +#ifdef _MSC_VER + #define MZ_MACRO_END while (0, 0) +#else + #define MZ_MACRO_END while (0) +#endif + +// ------------------- ZIP archive reading/writing + +#ifndef MINIZ_NO_ARCHIVE_APIS + +enum +{ + MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +}; + +typedef struct +{ + mz_uint32 m_file_index; + mz_uint32 m_central_dir_ofs; + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; +#ifndef MINIZ_NO_TIME + time_t m_time; +#endif + mz_uint32 m_crc32; + mz_uint64 m_comp_size; + mz_uint64 m_uncomp_size; + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + mz_uint64 m_local_header_ofs; + mz_uint32 m_comment_size; + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum +{ + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef struct mz_zip_archive_tag +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + mz_uint m_total_files; + mz_zip_mode m_zip_mode; + + mz_uint m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef enum +{ + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +} mz_zip_flags; + +// ZIP archive reading + +// Inits a ZIP archive reader. +// These functions read and validate the archive's central directory. +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags); +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +#endif + +// Returns the total number of files in the archive. +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +// Returns detailed information about an archive file entry. +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +// Determines if an archive file entry is a directory entry. +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +// Retrieves the filename of an archive file entry. +// Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +// Attempts to locates a file in the archive's central directory. +// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH +// Returns -1 if the file cannot be found. +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + +// Extracts a archive file to a memory buffer using no memory allocation. +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +// Extracts a archive file to a memory buffer. +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +// Extracts a archive file to a dynamically allocated heap buffer. +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +// Extracts a archive file using a callback function to output the file's data. +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +// Extracts a archive file to a disk file and sets its last accessed and modified times. +// This function only extracts files, not archive directory records. +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); +#endif + +// Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +// ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +// Inits a ZIP archive writer. +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +#endif + +// Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. +// For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. +// For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). +// Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. +// Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before +// the archive is finalized the file's central directory will be hosed. +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); + +// Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. +// To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +#ifndef MINIZ_NO_STDIO +// Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +#endif + +// Adds a file to an archive by fully cloning the data from another archive. +// This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields. +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index); + +// Finalizes the archive by writing the central directory records followed by the end of central directory record. +// After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). +// An archive must be manually finalized by calling this function for it to be valid. +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize); + +// Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. +// Note for the archive to be valid, it must have been finalized before ending. +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +// Misc. high-level helper functions: + +// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +// Reads a single file from an archive into a heap block. +// Returns NULL on failure. +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +// ------------------- Low-level Decompression API Definitions + +// Decompression flags used by tinfl_decompress(). +// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. +// TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. +// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). +// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +// High level decompression functions: +// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. +// On return: +// Function returns a pointer to the decompressed data, or NULL on failure. +// *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must call mz_free() on the returned block when it's no longer needed. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. +// Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. +// Returns 1 on success or 0 on failure. +typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; + +// Max size of LZ dictionary. +#define TINFL_LZ_DICT_SIZE 32768 + +// Return status. +typedef enum +{ + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +// Initializes the decompressor to its initial state. +#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +// Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. +// This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +// Internal/private bits follow. +enum +{ + TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS + #define TINFL_USE_64BIT_BITBUF 1 +#endif + +#if TINFL_USE_64BIT_BITBUF + typedef mz_uint64 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (64) +#else + typedef mz_uint32 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +// ------------------- Low-level Compression API Definitions + +// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). +#define TDEFL_LESS_MEMORY 1 + +// tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): +// TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). +enum +{ + TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. +// TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). +// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. +// TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). +// TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) +// TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. +// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. +// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. +// The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). +enum +{ + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +// High level compression functions: +// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of source block to compress. +// flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must free() the returned block when it's no longer needed. +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. +// Returns 0 on failure. +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// Compresses an image to a compressed PNG file in memory. +// On entry: +// pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. +// The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. +// level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL +// If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pLen_out will be set to the size of the PNG image file. +// The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +// Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); + +// tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; + +// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). +#if TDEFL_LESS_MEMORY +enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#else +enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#endif + +// The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. +typedef enum +{ + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1, +} tdefl_status; + +// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums +typedef enum +{ + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +// tdefl's compression state structure. +typedef struct +{ + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +// Initializes the compressor. +// There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. +// pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. +// If pBut_buf_func is NULL the user should always call the tdefl_compress() API. +// flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +// Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. +// tdefl_compress_buffer() always consumes the entire input buffer. +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros. +#ifndef MINIZ_NO_ZLIB_APIS +// Create tdefl_compress() flags given zlib-style compression parameters. +// level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) +// window_bits may be -15 (raw deflate) or 15 (zlib) +// strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); +#endif // #ifndef MINIZ_NO_ZLIB_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_INCLUDED + diff --git a/flasher_stub/rom_32.ld b/flasher_stub/rom_32.ld new file mode 100644 index 0000000..00271cf --- /dev/null +++ b/flasher_stub/rom_32.ld @@ -0,0 +1,1779 @@ +/* Taken from esp-idf */ +/* +ESP32 ROM address table +Generated for ROM with MD5sum: +ab8282ae908fe9e7a63fb2a4ac2df013 ../../rom_image/prorom.elf +*/ +PROVIDE ( abort = 0x4000bba4 ); +PROVIDE ( abs = 0x40056340 ); +PROVIDE ( __absvdi2 = 0x4006387c ); +PROVIDE ( __absvsi2 = 0x40063868 ); +PROVIDE ( Add2SelfBigHex256 = 0x40015b7c ); +PROVIDE ( AddBigHex256 = 0x40015b28 ); +PROVIDE ( AddBigHexModP256 = 0x40015c98 ); +PROVIDE ( __adddf3 = 0x40002590 ); +PROVIDE ( AddP256 = 0x40015c74 ); +PROVIDE ( AddPdiv2_256 = 0x40015ce0 ); +PROVIDE ( __addsf3 = 0x400020e8 ); +PROVIDE ( __addvdi3 = 0x40002cbc ); +PROVIDE ( __addvsi3 = 0x40002c98 ); +PROVIDE ( aes_128_cbc_decrypt = 0x4005cc7c ); +PROVIDE ( aes_128_cbc_encrypt = 0x4005cc18 ); +PROVIDE ( aes_unwrap = 0x4005ccf0 ); +PROVIDE ( app_gpio_arg = 0x3ffe003c ); +PROVIDE ( app_gpio_handler = 0x3ffe0040 ); +PROVIDE ( __ascii_wctomb = 0x40058ef0 ); +PROVIDE ( asctime = 0x40059588 ); +PROVIDE ( asctime_r = 0x40000ec8 ); +PROVIDE ( __ashldi3 = 0x4000c818 ); +PROVIDE ( __ashrdi3 = 0x4000c830 ); +PROVIDE ( atoi = 0x400566c4 ); +PROVIDE ( _atoi_r = 0x400566d4 ); +PROVIDE ( atol = 0x400566ec ); +PROVIDE ( _atol_r = 0x400566fc ); +PROVIDE ( base64_decode = 0x4005ced8 ); +PROVIDE ( base64_encode = 0x4005cdbc ); +PROVIDE ( BasePoint_x_256 = 0x3ff97488 ); +PROVIDE ( BasePoint_y_256 = 0x3ff97468 ); +PROVIDE ( bigHexInversion256 = 0x400168f0 ); +PROVIDE ( bigHexP256 = 0x3ff973bc ); +PROVIDE ( __bswapdi2 = 0x400649c4 ); +PROVIDE ( __bswapsi2 = 0x4006499c ); +PROVIDE ( btdm_r_ble_bt_handler_tab_p_get = 0x40019b0c ); +PROVIDE ( btdm_r_btdm_option_data_p_get = 0x40010004 ); +PROVIDE ( btdm_r_btdm_rom_version_get = 0x40010078 ); +PROVIDE ( btdm_r_data_init = 0x4001002c ); +PROVIDE ( btdm_r_import_rf_phy_func_p_get = 0x40054298 ); +PROVIDE ( btdm_r_ip_func_p_get = 0x40019af0 ); +PROVIDE ( btdm_r_ip_func_p_set = 0x40019afc ); +PROVIDE ( btdm_r_modules_func_p_get = 0x4005427c ); +PROVIDE ( btdm_r_modules_func_p_set = 0x40054270 ); +PROVIDE ( btdm_r_plf_func_p_set = 0x40054288 ); +PROVIDE ( bt_util_buf_env = 0x3ffb8bd4 ); +PROVIDE ( bzero = 0x4000c1f4 ); +PROVIDE ( cache_flash_mmu_set = 0x400095e0 ); +PROVIDE ( Cache_Flush = 0x40009a14 ); +PROVIDE ( Cache_Read_Disable = 0x40009ab8 ); +PROVIDE ( Cache_Read_Enable = 0x40009a84 ); +PROVIDE ( Cache_Read_Init = 0x40009950 ); +PROVIDE ( cache_sram_mmu_set = 0x400097f4 ); +/* This is static function, but can be used, not generated by script*/ +PROVIDE ( calc_rtc_memory_crc = 0x40008170 ); +PROVIDE ( calloc = 0x4000bee4 ); +PROVIDE ( _calloc_r = 0x4000bbf8 ); +PROVIDE ( check_pos = 0x400068b8 ); +PROVIDE ( _cleanup = 0x40001df8 ); +PROVIDE ( _cleanup_r = 0x40001d48 ); +PROVIDE ( __clear_cache = 0x40063860 ); +PROVIDE ( close = 0x40001778 ); +PROVIDE ( _close_r = 0x4000bd3c ); +PROVIDE ( __clrsbdi2 = 0x40064a38 ); +PROVIDE ( __clrsbsi2 = 0x40064a20 ); +PROVIDE ( __clzdi2 = 0x4000ca50 ); +PROVIDE ( __clzsi2 = 0x4000c7e8 ); +PROVIDE ( __cmpdi2 = 0x40063820 ); +PROVIDE ( co_default_bdaddr = 0x3ffae704 ); +PROVIDE ( co_null_bdaddr = 0x3ffb80e0 ); +PROVIDE ( co_sca2ppm = 0x3ff971e8 ); +PROVIDE ( crc16_be = 0x4005d09c ); +PROVIDE ( crc16_le = 0x4005d05c ); +PROVIDE ( crc32_be = 0x4005d024 ); +PROVIDE ( crc32_le = 0x4005cfec ); +PROVIDE ( crc8_be = 0x4005d114 ); +PROVIDE ( crc8_le = 0x4005d0e0 ); +PROVIDE ( creat = 0x40000e8c ); +PROVIDE ( ctime = 0x400595b0 ); +PROVIDE ( ctime_r = 0x400595c4 ); +PROVIDE ( _ctype_ = 0x3ff96354 ); +PROVIDE ( __ctype_ptr__ = 0x3ff96350 ); +PROVIDE ( __ctzdi2 = 0x4000ca64 ); +PROVIDE ( __ctzsi2 = 0x4000c7f0 ); +PROVIDE ( _data_end = 0x4000d5c8 ); +PROVIDE ( _data_end_btdm_rom = 0x4000d4f8 ); +PROVIDE ( _data_start = 0x4000d4f8 ); +PROVIDE ( _data_start_btdm_rom = 0x4000d4f4 ); +PROVIDE ( _data_start_btdm = 0x3ffae6e0); +PROVIDE ( _data_end_btdm = 0x3ffaff10); +PROVIDE ( _bss_start_btdm = 0x3ffb8000); +PROVIDE ( _bss_end_btdm = 0x3ffbff70); +PROVIDE ( _daylight = 0x3ffae0a4 ); +PROVIDE ( dbg_default_handler = 0x3ff97218 ); +PROVIDE ( dbg_state = 0x3ffb8d5d ); +PROVIDE ( DebugE256PublicKey_x = 0x3ff97428 ); +PROVIDE ( DebugE256PublicKey_y = 0x3ff97408 ); +PROVIDE ( DebugE256SecretKey = 0x3ff973e8 ); +PROVIDE ( _DebugExceptionVector = 0x40000280 ); +PROVIDE ( debug_timer = 0x3ffe042c ); +PROVIDE ( debug_timerfn = 0x3ffe0430 ); +PROVIDE ( dh_group14_generator = 0x3ff9ac60 ); +PROVIDE ( dh_group14_prime = 0x3ff9ab60 ); +PROVIDE ( dh_group15_generator = 0x3ff9ab5f ); +PROVIDE ( dh_group15_prime = 0x3ff9a9df ); +PROVIDE ( dh_group16_generator = 0x3ff9a9de ); +PROVIDE ( dh_group16_prime = 0x3ff9a7de ); +PROVIDE ( dh_group17_generator = 0x3ff9a7dd ); +PROVIDE ( dh_group17_prime = 0x3ff9a4dd ); +PROVIDE ( dh_group18_generator = 0x3ff9a4dc ); +PROVIDE ( dh_group18_prime = 0x3ff9a0dc ); +PROVIDE ( dh_group1_generator = 0x3ff9ae03 ); +PROVIDE ( dh_group1_prime = 0x3ff9ada3 ); +PROVIDE ( dh_group2_generator = 0x3ff9ada2 ); +PROVIDE ( dh_group2_prime = 0x3ff9ad22 ); +PROVIDE ( dh_group5_generator = 0x3ff9ad21 ); +PROVIDE ( dh_group5_prime = 0x3ff9ac61 ); +PROVIDE ( div = 0x40056348 ); +PROVIDE ( __divdc3 = 0x40064460 ); +PROVIDE ( __divdf3 = 0x40002954 ); +PROVIDE ( __divdi3 = 0x4000ca84 ); +PROVIDE ( __divsc3 = 0x40064200 ); +PROVIDE ( __divsf3 = 0x4000234c ); +PROVIDE ( __divsi3 = 0x4000c7b8 ); +PROVIDE ( _DoubleExceptionVector = 0x400003c0 ); +PROVIDE ( dummy_len_plus = 0x3ffae290 ); +PROVIDE ( __dummy_lock = 0x4000c728 ); +PROVIDE ( __dummy_lock_try = 0x4000c730 ); +PROVIDE ( ecc_env = 0x3ffb8d60 ); +PROVIDE ( ecc_Jacobian_InfinityPoint256 = 0x3ff972e8 ); +PROVIDE ( em_buf_env = 0x3ffb8d74 ); +PROVIDE ( environ = 0x3ffae0b4 ); +PROVIDE ( __env_lock = 0x40001fd4 ); +PROVIDE ( __env_unlock = 0x40001fe0 ); +PROVIDE ( __eqdf2 = 0x400636a8 ); +PROVIDE ( __eqsf2 = 0x40063374 ); +PROVIDE ( esp_crc8 = 0x4005d144 ); +PROVIDE ( _etext = 0x4000d66c ); +PROVIDE ( ets_aes_crypt = 0x4005c9b8 ); +PROVIDE ( ets_aes_disable = 0x4005c8f8 ); +PROVIDE ( ets_aes_enable = 0x4005c8cc ); +PROVIDE ( ets_aes_set_endian = 0x4005c928 ); +PROVIDE ( ets_aes_setkey_dec = 0x4005c994 ); +PROVIDE ( ets_aes_setkey_enc = 0x4005c97c ); +PROVIDE ( ets_bigint_disable = 0x4005c4e0 ); +PROVIDE ( ets_bigint_enable = 0x4005c498 ); +PROVIDE ( ets_bigint_mod_mult_getz = 0x4005c818 ); +PROVIDE ( ets_bigint_mod_mult_prepare = 0x4005c7b4 ); +PROVIDE ( ets_bigint_mod_power_getz = 0x4005c614 ); +PROVIDE ( ets_bigint_mod_power_prepare = 0x4005c54c ); +PROVIDE ( ets_bigint_montgomery_mult_getz = 0x4005c7a4 ); +PROVIDE ( ets_bigint_montgomery_mult_prepare = 0x4005c6fc ); +PROVIDE ( ets_bigint_mult_getz = 0x4005c6e8 ); +PROVIDE ( ets_bigint_mult_prepare = 0x4005c630 ); +PROVIDE ( ets_bigint_wait_finish = 0x4005c520 ); +PROVIDE ( ets_delay_us = 0x40008534 ); +PROVIDE ( ets_efuse_get_8M_clock = 0x40008710 ); +PROVIDE ( ets_efuse_get_spiconfig = 0x40008658 ); +PROVIDE ( ets_efuse_program_op = 0x40008628 ); +PROVIDE ( ets_efuse_read_op = 0x40008600 ); +PROVIDE ( ets_get_cpu_frequency = 0x4000855c ); +PROVIDE ( ets_get_detected_xtal_freq = 0x40008588 ); +PROVIDE ( ets_get_xtal_scale = 0x4000856c ); +PROVIDE ( ets_install_putc1 = 0x40007d18 ); +PROVIDE ( ets_install_putc2 = 0x40007d38 ); +PROVIDE ( ets_install_uart_printf = 0x40007d28 ); +PROVIDE ( ets_post = 0x4000673c ); +PROVIDE ( ets_printf = 0x40007d54 ); +PROVIDE ( ets_readySet_ = 0x3ffe01f0 ); +PROVIDE ( ets_run = 0x400066bc ); +PROVIDE ( ets_secure_boot_check = 0x4005cb40 ); +PROVIDE ( ets_secure_boot_check_finish = 0x4005cc04 ); +PROVIDE ( ets_secure_boot_check_start = 0x4005cbcc ); +PROVIDE ( ets_secure_boot_finish = 0x4005ca84 ); +PROVIDE ( ets_secure_boot_hash = 0x4005cad4 ); +PROVIDE ( ets_secure_boot_obtain = 0x4005cb14 ); +PROVIDE ( ets_secure_boot_rd_abstract = 0x4005cba8 ); +PROVIDE ( ets_secure_boot_rd_iv = 0x4005cb84 ); +PROVIDE ( ets_secure_boot_start = 0x4005ca34 ); +PROVIDE ( ets_set_appcpu_boot_addr = 0x4000689c ); +PROVIDE ( ets_set_idle_cb = 0x40006674 ); +PROVIDE ( ets_set_startup_callback = 0x4000688c ); +PROVIDE ( ets_set_user_start = 0x4000687c ); +PROVIDE ( ets_sha_disable = 0x4005c0a8 ); +PROVIDE ( ets_sha_enable = 0x4005c07c ); +PROVIDE ( ets_sha_finish = 0x4005c104 ); +PROVIDE ( ets_sha_init = 0x4005c0d4 ); +PROVIDE ( ets_sha_update = 0x4005c2a0 ); +PROVIDE ( ets_startup_callback = 0x3ffe0404 ); +PROVIDE ( ets_task = 0x40006688 ); +PROVIDE ( ets_timer_arm = 0x40008368 ); +PROVIDE ( ets_timer_arm_us = 0x400083ac ); +PROVIDE ( ets_timer_disarm = 0x400083ec ); +PROVIDE ( ets_timer_done = 0x40008428 ); +PROVIDE ( ets_timer_handler_isr = 0x40008454 ); +PROVIDE ( ets_timer_init = 0x400084e8 ); +PROVIDE ( ets_timer_setfn = 0x40008350 ); +PROVIDE ( ets_unpack_flash_code = 0x40007018 ); +PROVIDE ( ets_unpack_flash_code_legacy = 0x4000694c ); +PROVIDE ( ets_update_cpu_frequency = 0x40008550 ); +PROVIDE ( ets_waiti0 = 0x400067d8 ); +PROVIDE ( exc_cause_table = 0x3ff991d0 ); +PROVIDE ( _exit_r = 0x4000bd28 ); +PROVIDE ( __extendsfdf2 = 0x40002c34 ); +PROVIDE ( fclose = 0x400020ac ); +PROVIDE ( _fclose_r = 0x40001fec ); +PROVIDE ( fflush = 0x40059394 ); +PROVIDE ( _fflush_r = 0x40059320 ); +PROVIDE ( __ffsdi2 = 0x4000ca2c ); +PROVIDE ( __ffssi2 = 0x4000c804 ); +PROVIDE ( FilePacketSendDeflatedReqMsgProc = 0x40008b24 ); +PROVIDE ( FilePacketSendReqMsgProc = 0x40008860 ); +PROVIDE ( _findenv_r = 0x40001f44 ); +PROVIDE ( __fixdfdi = 0x40002ac4 ); +PROVIDE ( __fixdfsi = 0x40002a78 ); +PROVIDE ( __fixsfdi = 0x4000244c ); +PROVIDE ( __fixsfsi = 0x4000240c ); +PROVIDE ( __fixunsdfsi = 0x40002b30 ); +PROVIDE ( __fixunssfdi = 0x40002504 ); +PROVIDE ( __fixunssfsi = 0x400024ac ); +PROVIDE ( FlashDwnLdDeflatedStartMsgProc = 0x40008ad8 ); +PROVIDE ( FlashDwnLdParamCfgMsgProc = 0x4000891c ); +PROVIDE ( FlashDwnLdStartMsgProc = 0x40008820 ); +PROVIDE ( FlashDwnLdStopDeflatedReqMsgProc = 0x40008c18 ); +PROVIDE ( FlashDwnLdStopReqMsgProc = 0x400088ec ); +PROVIDE ( __floatdidf = 0x4000c988 ); +PROVIDE ( __floatdisf = 0x4000c8c0 ); +PROVIDE ( __floatsidf = 0x4000c944 ); +PROVIDE ( __floatsisf = 0x4000c870 ); +PROVIDE ( __floatundidf = 0x4000c978 ); +PROVIDE ( __floatundisf = 0x4000c8b0 ); +PROVIDE ( __floatunsidf = 0x4000c938 ); +PROVIDE ( __floatunsisf = 0x4000c864 ); +PROVIDE ( __fp_lock_all = 0x40001f1c ); +PROVIDE ( __fp_unlock_all = 0x40001f30 ); +PROVIDE ( fputwc = 0x40058ea8 ); +PROVIDE ( __fputwc = 0x40058da0 ); +PROVIDE ( _fputwc_r = 0x40058e4c ); +PROVIDE ( free = 0x4000beb8 ); +PROVIDE ( _free_r = 0x4000bbcc ); +PROVIDE ( _fstat_r = 0x4000bccc ); +PROVIDE ( _fwalk = 0x4000c738 ); +PROVIDE ( _fwalk_reent = 0x4000c770 ); +PROVIDE ( __gcc_bcmp = 0x40064a70 ); +PROVIDE ( __gedf2 = 0x40063768 ); +PROVIDE ( _GeneralException = 0x40000e14 ); +PROVIDE ( __gesf2 = 0x4006340c ); +PROVIDE ( __get_current_time_locale = 0x40001834 ); +PROVIDE ( _getenv_r = 0x40001fbc ); +PROVIDE ( _getpid_r = 0x4000bcfc ); +PROVIDE ( __getreent = 0x4000be8c ); +PROVIDE ( _gettimeofday_r = 0x4000bc58 ); +PROVIDE ( __gettzinfo = 0x40001fcc ); +PROVIDE ( GetUartDevice = 0x40009598 ); +PROVIDE ( GF_Jacobian_Point_Addition256 = 0x400163a4 ); +PROVIDE ( GF_Jacobian_Point_Double256 = 0x40016260 ); +PROVIDE ( GF_Point_Jacobian_To_Affine256 = 0x40016b0c ); +PROVIDE ( _global_impure_ptr = 0x3ffae0b0 ); +PROVIDE ( gmtime = 0x40059848 ); +PROVIDE ( gmtime_r = 0x40059868 ); +PROVIDE ( g_phyFuns_instance = 0x3ffae0c4 ); +PROVIDE ( g_rom_flashchip = 0x3ffae270 ); +PROVIDE ( gpio_init = 0x40009c20 ); +PROVIDE ( gpio_input_get = 0x40009b88 ); +PROVIDE ( gpio_input_get_high = 0x40009b9c ); +PROVIDE ( gpio_intr_ack = 0x40009dd4 ); +PROVIDE ( gpio_intr_ack_high = 0x40009e1c ); +PROVIDE ( gpio_intr_handler_register = 0x40009e6c ); +PROVIDE ( gpio_intr_pending = 0x40009cec ); +PROVIDE ( gpio_intr_pending_high = 0x40009cf8 ); +PROVIDE ( gpio_matrix_in = 0x40009edc ); +PROVIDE ( gpio_matrix_out = 0x40009f0c ); +PROVIDE ( gpio_output_set = 0x40009b24 ); +PROVIDE ( gpio_output_set_high = 0x40009b5c ); +PROVIDE ( gpio_pad_hold = 0x4000a734 ); +PROVIDE ( gpio_pad_pulldown = 0x4000a348 ); +PROVIDE ( gpio_pad_pullup = 0x4000a22c ); +PROVIDE ( gpio_pad_select_gpio = 0x40009fdc ); +PROVIDE ( gpio_pad_set_drv = 0x4000a11c ); +PROVIDE ( gpio_pad_unhold = 0x4000a484 ); +PROVIDE ( gpio_pending_mask = 0x3ffe0038 ); +PROVIDE ( gpio_pending_mask_high = 0x3ffe0044 ); +PROVIDE ( gpio_pin_intr_state_set = 0x40009d04 ); +PROVIDE ( gpio_pin_wakeup_disable = 0x40009eb0 ); +PROVIDE ( gpio_pin_wakeup_enable = 0x40009e7c ); +PROVIDE ( gpio_register_get = 0x40009cbc ); +PROVIDE ( gpio_register_set = 0x40009bbc ); +PROVIDE ( __gtdf2 = 0x400636dc ); +PROVIDE ( __gtsf2 = 0x400633a0 ); +PROVIDE ( gTxMsg = 0x3ffe0050 ); +PROVIDE ( hci_cmd_desc_root_tab = 0x3ff976d4 ); +PROVIDE ( hci_cmd_desc_tab_ctrl_bb = 0x3ff97b70 ); +PROVIDE ( hci_cmd_desc_tab_info_par = 0x3ff97b1c ); +PROVIDE ( hci_cmd_desc_tab_le = 0x3ff97870 ); +PROVIDE ( hci_cmd_desc_tab_lk_ctrl = 0x3ff97fc0 ); +PROVIDE ( hci_cmd_desc_tab_lk_pol = 0x3ff97f3c ); +PROVIDE ( hci_cmd_desc_tab_stat_par = 0x3ff97ac8 ); +PROVIDE ( hci_cmd_desc_tab_testing = 0x3ff97a98 ); +PROVIDE ( hci_cmd_desc_tab_vs = 0x3ff97714 ); +PROVIDE ( hci_command_handler = 0x4004c928 ); +PROVIDE ( hci_env = 0x3ffb9350 ); +PROVIDE ( hci_evt_dbg_desc_tab = 0x3ff9750c ); +PROVIDE ( hci_evt_desc_tab = 0x3ff9751c ); +PROVIDE ( hci_evt_le_desc_tab = 0x3ff974b4 ); +PROVIDE ( hci_fc_env = 0x3ffb9340 ); +PROVIDE ( hmac_md5 = 0x4005d264 ); +PROVIDE ( hmac_md5_vector = 0x4005d17c ); +PROVIDE ( hmac_sha1 = 0x40060acc ); +PROVIDE ( hmac_sha1_vector = 0x400609e4 ); +PROVIDE ( hmac_sha256 = 0x40060d58 ); +PROVIDE ( hmac_sha256_vector = 0x40060c84 ); +PROVIDE ( intr_matrix_set = 0x4000681c ); +PROVIDE ( isalnum = 0x40000f04 ); +PROVIDE ( isalpha = 0x40000f18 ); +PROVIDE ( isascii = 0x4000c20c ); +PROVIDE ( _isatty_r = 0x40000ea0 ); +PROVIDE ( isblank = 0x40000f2c ); +PROVIDE ( iscntrl = 0x40000f50 ); +PROVIDE ( isdigit = 0x40000f64 ); +PROVIDE ( isgraph = 0x40000f94 ); +PROVIDE ( islower = 0x40000f78 ); +PROVIDE ( isprint = 0x40000fa8 ); +PROVIDE ( ispunct = 0x40000fc0 ); +PROVIDE ( isspace = 0x40000fd4 ); +PROVIDE ( isupper = 0x40000fe8 ); +PROVIDE ( itoa = 0x400566b4 ); +PROVIDE ( __itoa = 0x40056678 ); +PROVIDE ( jd_decomp = 0x400613e8 ); +PROVIDE ( jd_prepare = 0x40060fa8 ); +PROVIDE ( ke_env = 0x3ffb93cc ); +PROVIDE ( _KernelExceptionVector = 0x40000300 ); +PROVIDE ( _kill_r = 0x4000bd10 ); +PROVIDE ( labs = 0x40056370 ); +PROVIDE ( lb_default_handler = 0x3ff982b8 ); +PROVIDE ( lb_default_state_tab_p_get = 0x4001c198 ); +PROVIDE ( lb_env = 0x3ffb9424 ); +PROVIDE ( lb_hci_cmd_handler_tab_p_get = 0x4001c18c ); +PROVIDE ( lb_state = 0x3ffb94e8 ); +PROVIDE ( lc_default_handler = 0x3ff98648 ); +PROVIDE ( lc_default_state_tab_p_get = 0x4002f494 ); +PROVIDE ( lc_env = 0x3ffb94ec ); +PROVIDE ( lc_hci_cmd_handler_tab_p_get = 0x4002f488 ); +PROVIDE ( lc_state = 0x3ffb9508 ); +PROVIDE ( ld_acl_br_sizes = 0x3ff98a2a ); +PROVIDE ( ld_acl_br_types = 0x3ff98a36 ); +PROVIDE ( ld_acl_edr_sizes = 0x3ff98a14 ); +PROVIDE ( ld_acl_edr_types = 0x3ff98a22 ); +PROVIDE ( ld_env = 0x3ffb9510 ); +PROVIDE ( ldiv = 0x40056378 ); +PROVIDE ( ld_pcm_settings_dft = 0x3ff98a0c ); +PROVIDE ( ld_sched_params = 0x3ffb96c0 ); +PROVIDE ( ld_sync_train_channels = 0x3ff98a3c ); +PROVIDE ( __ledf2 = 0x40063704 ); +PROVIDE ( __lesf2 = 0x400633c0 ); +PROVIDE ( _Level2FromVector = 0x40000954 ); +PROVIDE ( _Level2Vector = 0x40000180 ); +PROVIDE ( _Level3FromVector = 0x40000a28 ); +PROVIDE ( _Level3Vector = 0x400001c0 ); +PROVIDE ( _Level4FromVector = 0x40000af8 ); +PROVIDE ( _Level4Vector = 0x40000200 ); +PROVIDE ( _Level5FromVector = 0x40000c68 ); +PROVIDE ( _Level5Vector = 0x40000240 ); +PROVIDE ( _LevelOneInterrupt = 0x40000835 ); +PROVIDE ( _link_r = 0x4000bc9c ); +PROVIDE ( llc_default_handler = 0x3ff98b3c ); +PROVIDE ( llc_default_state_tab_p_get = 0x40046058 ); +PROVIDE ( llc_env = 0x3ffb96d0 ); +PROVIDE ( llc_hci_acl_data_tx_handler = 0x40042398 ); +PROVIDE ( llc_hci_cmd_handler_tab_p_get = 0x40042358 ); +PROVIDE ( llc_hci_command_handler = 0x40042360 ); +PROVIDE ( llcp_pdu_handler_tab_p_get = 0x40043f64 ); +PROVIDE ( llc_state = 0x3ffb96f8 ); +PROVIDE ( lldesc_build_chain = 0x4000a850 ); +PROVIDE ( lldesc_num2link = 0x4000a948 ); +PROVIDE ( lldesc_set_owner = 0x4000a974 ); +PROVIDE ( lld_evt_env = 0x3ffb9704 ); +PROVIDE ( lld_pdu_adv_pk_desc_tab = 0x3ff98c70 ); +PROVIDE ( lld_pdu_llcp_pk_desc_tab = 0x3ff98b68 ); +PROVIDE ( LLM_AA_CT1 = 0x3ff98d8a ); +PROVIDE ( LLM_AA_CT2 = 0x3ff98d88 ); +PROVIDE ( llm_default_handler = 0x3ff98d80 ); +PROVIDE ( llm_default_state_tab_p_get = 0x4004e718 ); +PROVIDE ( llm_hci_cmd_handler_tab_p_get = 0x4004c920 ); +PROVIDE ( llm_le_env = 0x3ffb976c ); +PROVIDE ( llm_local_cmds = 0x3ff98d38 ); +PROVIDE ( llm_local_data_len_values = 0x3ff98d1c ); +PROVIDE ( llm_local_le_feats = 0x3ff98d30 ); +PROVIDE ( llm_local_le_states = 0x3ff98d28 ); +PROVIDE ( llm_state = 0x3ffb985c ); +PROVIDE ( lm_default_handler = 0x3ff990e0 ); +PROVIDE ( lm_default_state_tab_p_get = 0x40054268 ); +PROVIDE ( lm_env = 0x3ffb9860 ); +PROVIDE ( lm_hci_cmd_handler_tab_p_get = 0x4005425c ); +PROVIDE ( lm_local_supp_feats = 0x3ff990ee ); +PROVIDE ( lm_n_page_tab = 0x3ff990e8 ); +PROVIDE ( lmp_desc_tab = 0x3ff96e6c ); +PROVIDE ( lmp_ext_desc_tab = 0x3ff96d9c ); +PROVIDE ( lm_state = 0x3ffb9a1c ); +PROVIDE ( __locale_charset = 0x40059540 ); +PROVIDE ( __locale_cjk_lang = 0x40059558 ); +PROVIDE ( localeconv = 0x4005957c ); +PROVIDE ( _localeconv_r = 0x40059560 ); +PROVIDE ( __locale_mb_cur_max = 0x40059548 ); +PROVIDE ( __locale_msgcharset = 0x40059550 ); +PROVIDE ( localtime = 0x400595dc ); +PROVIDE ( localtime_r = 0x400595fc ); +PROVIDE ( _lock_acquire = 0x4000be14 ); +PROVIDE ( _lock_acquire_recursive = 0x4000be28 ); +PROVIDE ( _lock_close = 0x4000bdec ); +PROVIDE ( _lock_close_recursive = 0x4000be00 ); +PROVIDE ( _lock_init = 0x4000bdc4 ); +PROVIDE ( _lock_init_recursive = 0x4000bdd8 ); +PROVIDE ( _lock_release = 0x4000be64 ); +PROVIDE ( _lock_release_recursive = 0x4000be78 ); +PROVIDE ( _lock_try_acquire = 0x4000be3c ); +PROVIDE ( _lock_try_acquire_recursive = 0x4000be50 ); +PROVIDE ( longjmp = 0x400562cc ); +PROVIDE ( _lseek_r = 0x4000bd8c ); +PROVIDE ( __lshrdi3 = 0x4000c84c ); +PROVIDE ( __ltdf2 = 0x40063790 ); +PROVIDE ( __ltsf2 = 0x4006342c ); +PROVIDE ( malloc = 0x4000bea0 ); +PROVIDE ( _malloc_r = 0x4000bbb4 ); +PROVIDE ( maxSecretKey_256 = 0x3ff97448 ); +PROVIDE ( __mb_cur_max = 0x3ff96530 ); +PROVIDE ( MD5Final = 0x4005db1c ); +PROVIDE ( MD5Init = 0x4005da7c ); +PROVIDE ( MD5Update = 0x4005da9c ); +PROVIDE ( md5_vector = 0x4005db80 ); +PROVIDE ( memccpy = 0x4000c220 ); +PROVIDE ( memchr = 0x4000c244 ); +PROVIDE ( memcmp = 0x4000c260 ); +PROVIDE ( memcpy = 0x4000c2c8 ); +PROVIDE ( MemDwnLdStartMsgProc = 0x40008948 ); +PROVIDE ( MemDwnLdStopReqMsgProc = 0x400089dc ); +PROVIDE ( memmove = 0x4000c3c0 ); +PROVIDE ( MemPacketSendReqMsgProc = 0x40008978 ); +PROVIDE ( memrchr = 0x4000c400 ); +PROVIDE ( memset = 0x4000c44c ); +PROVIDE ( mktime = 0x4005a5e8 ); +PROVIDE ( mmu_init = 0x400095a4 ); +PROVIDE ( __moddi3 = 0x4000cd4c ); +PROVIDE ( __modsi3 = 0x4000c7c0 ); +PROVIDE ( __month_lengths = 0x3ff9609c ); +PROVIDE ( __muldc3 = 0x40063bf4 ); +PROVIDE ( __muldf3 = 0x4006358c ); +PROVIDE ( __muldi3 = 0x4000c9fc ); +PROVIDE ( __mulsc3 = 0x40063934 ); +PROVIDE ( __mulsf3 = 0x400632c8 ); +PROVIDE ( __mulsi3 = 0x4000c7b0 ); +PROVIDE ( MultiplyBigHexByUint32_256 = 0x40016214 ); +PROVIDE ( MultiplyBigHexModP256 = 0x400160b8 ); +PROVIDE ( MultiplyByU32ModP256 = 0x40015fdc ); +PROVIDE ( multofup = 0x4000ab8c ); +PROVIDE ( __mulvdi3 = 0x40002d78 ); +PROVIDE ( __mulvsi3 = 0x40002d60 ); +PROVIDE ( mz_adler32 = 0x4005edbc ); +PROVIDE ( mz_crc32 = 0x4005ee88 ); +PROVIDE ( mz_free = 0x4005eed4 ); +PROVIDE ( __nedf2 = 0x400636a8 ); +PROVIDE ( __negdf2 = 0x400634a0 ); +PROVIDE ( __negdi2 = 0x4000ca14 ); +PROVIDE ( __negsf2 = 0x400020c0 ); +PROVIDE ( __negvdi2 = 0x40002e98 ); +PROVIDE ( __negvsi2 = 0x40002e78 ); +PROVIDE ( __nesf2 = 0x40063374 ); +PROVIDE ( _NMIExceptionVector = 0x400002c0 ); +PROVIDE ( notEqual256 = 0x40015b04 ); +PROVIDE ( __nsau_data = 0x3ff96544 ); +PROVIDE ( one_bits = 0x3ff971f8 ); +PROVIDE ( open = 0x4000178c ); +PROVIDE ( _open_r = 0x4000bd54 ); +PROVIDE ( __paritysi2 = 0x40002f3c ); +PROVIDE ( pbkdf2_sha1 = 0x40060ba4 ); +PROVIDE ( phy_get_romfuncs = 0x40004100 ); +PROVIDE ( __popcountdi2 = 0x40002ef8 ); +PROVIDE ( __popcountsi2 = 0x40002ed0 ); +PROVIDE ( __popcount_tab = 0x3ff96544 ); +PROVIDE ( __powidf2 = 0x400638d4 ); +PROVIDE ( __powisf2 = 0x4006389c ); +PROVIDE ( _Pri_4_HandlerAddress = 0x3ffe0648 ); +PROVIDE ( _Pri_5_HandlerAddress = 0x3ffe064c ); +PROVIDE ( qsort = 0x40056424 ); +PROVIDE ( _raise_r = 0x4000bc70 ); +PROVIDE ( rand = 0x40001058 ); +PROVIDE ( rand_r = 0x400010d4 ); +PROVIDE ( r_btdm_option_data = 0x3ffae6e0 ); +PROVIDE ( r_bt_util_buf_acl_rx_alloc = 0x40010218 ); +PROVIDE ( r_bt_util_buf_acl_rx_free = 0x40010234 ); +PROVIDE ( r_bt_util_buf_acl_tx_alloc = 0x40010268 ); +PROVIDE ( r_bt_util_buf_acl_tx_free = 0x40010280 ); +PROVIDE ( r_bt_util_buf_init = 0x400100e4 ); +PROVIDE ( r_bt_util_buf_lmp_tx_alloc = 0x400101d0 ); +PROVIDE ( r_bt_util_buf_lmp_tx_free = 0x400101ec ); +PROVIDE ( r_bt_util_buf_sync_clear = 0x400103c8 ); +PROVIDE ( r_bt_util_buf_sync_init = 0x400102c4 ); +PROVIDE ( r_bt_util_buf_sync_rx_alloc = 0x40010468 ); +PROVIDE ( r_bt_util_buf_sync_rx_free = 0x4001049c ); +PROVIDE ( r_bt_util_buf_sync_tx_alloc = 0x400103ec ); +PROVIDE ( r_bt_util_buf_sync_tx_free = 0x40010428 ); +PROVIDE ( rc4_skip = 0x40060928 ); +PROVIDE ( r_co_bdaddr_compare = 0x40014324 ); +PROVIDE ( r_co_bytes_to_string = 0x400142e4 ); +PROVIDE ( r_co_list_check_size_available = 0x400142c4 ); +PROVIDE ( r_co_list_extract = 0x4001404c ); +PROVIDE ( r_co_list_extract_after = 0x40014118 ); +PROVIDE ( r_co_list_find = 0x4001419c ); +PROVIDE ( r_co_list_init = 0x40013f14 ); +PROVIDE ( r_co_list_insert_after = 0x40014254 ); +PROVIDE ( r_co_list_insert_before = 0x40014200 ); +PROVIDE ( r_co_list_merge = 0x400141bc ); +PROVIDE ( r_co_list_pool_init = 0x40013f30 ); +PROVIDE ( r_co_list_pop_front = 0x40014028 ); +PROVIDE ( r_co_list_push_back = 0x40013fb8 ); +PROVIDE ( r_co_list_push_front = 0x40013ff4 ); +PROVIDE ( r_co_list_size = 0x400142ac ); +PROVIDE ( r_co_nb_good_channels = 0x40014360 ); +PROVIDE ( r_co_slot_to_duration = 0x40014348 ); +PROVIDE ( RcvMsg = 0x4000954c ); +PROVIDE ( r_dbg_init = 0x40014394 ); +PROVIDE ( r_dbg_platform_reset_complete = 0x400143d0 ); +PROVIDE ( r_dbg_swdiag_init = 0x40014470 ); +PROVIDE ( r_dbg_swdiag_read = 0x400144a4 ); +PROVIDE ( r_dbg_swdiag_write = 0x400144d0 ); +PROVIDE ( r_E1 = 0x400108e8 ); +PROVIDE ( r_E21 = 0x40010968 ); +PROVIDE ( r_E22 = 0x400109b4 ); +PROVIDE ( r_E3 = 0x40010a58 ); +PROVIDE ( r_ea_alarm_clear = 0x40015ab4 ); +PROVIDE ( r_ea_alarm_set = 0x40015a10 ); +PROVIDE ( read = 0x400017dc ); +PROVIDE ( _read_r = 0x4000bda8 ); +PROVIDE ( r_ea_elt_cancel = 0x400150d0 ); +PROVIDE ( r_ea_elt_create = 0x40015264 ); +PROVIDE ( r_ea_elt_insert = 0x400152a8 ); +PROVIDE ( r_ea_elt_remove = 0x400154f0 ); +PROVIDE ( r_ea_finetimer_isr = 0x400155d4 ); +PROVIDE ( r_ea_init = 0x40015228 ); +PROVIDE ( r_ea_interval_create = 0x4001555c ); +PROVIDE ( r_ea_interval_delete = 0x400155a8 ); +PROVIDE ( r_ea_interval_duration_req = 0x4001597c ); +PROVIDE ( r_ea_interval_insert = 0x4001557c ); +PROVIDE ( r_ea_interval_remove = 0x40015590 ); +PROVIDE ( realloc = 0x4000becc ); +PROVIDE ( _realloc_r = 0x4000bbe0 ); +PROVIDE ( r_ea_offset_req = 0x40015748 ); +PROVIDE ( r_ea_sleep_check = 0x40015928 ); +PROVIDE ( r_ea_sw_isr = 0x40015724 ); +PROVIDE ( r_ea_time_get_halfslot_rounded = 0x40015894 ); +PROVIDE ( r_ea_time_get_slot_rounded = 0x400158d4 ); +PROVIDE ( r_ecc_abort_key256_generation = 0x40017070 ); +PROVIDE ( r_ecc_generate_key256 = 0x40016e00 ); +PROVIDE ( r_ecc_gen_new_public_key = 0x400170c0 ); +PROVIDE ( r_ecc_gen_new_secret_key = 0x400170e4 ); +PROVIDE ( r_ecc_get_debug_Keys = 0x40017224 ); +PROVIDE ( r_ecc_init = 0x40016dbc ); +PROVIDE ( RecvBuff = 0x3ffe009c ); +PROVIDE ( recv_packet = 0x40009424 ); +PROVIDE ( r_em_buf_init = 0x4001729c ); +PROVIDE ( r_em_buf_rx_buff_addr_get = 0x400173e8 ); +PROVIDE ( r_em_buf_rx_free = 0x400173c4 ); +PROVIDE ( r_em_buf_tx_buff_addr_get = 0x40017404 ); +PROVIDE ( r_em_buf_tx_free = 0x4001741c ); +PROVIDE ( _rename_r = 0x4000bc28 ); +PROVIDE ( _ResetHandler = 0x40000450 ); +PROVIDE ( _ResetVector = 0x40000400 ); +PROVIDE ( r_F1_256 = 0x400133e4 ); +PROVIDE ( r_F2_256 = 0x40013568 ); +PROVIDE ( r_F3_256 = 0x40013664 ); +PROVIDE ( RFPLL_ICP_TABLE = 0x3ffb8b7c ); +PROVIDE ( r_G_256 = 0x40013470 ); +PROVIDE ( r_H3 = 0x40013760 ); +PROVIDE ( r_H4 = 0x40013830 ); +PROVIDE ( r_h4tl_init = 0x40017878 ); +PROVIDE ( r_h4tl_start = 0x40017924 ); +PROVIDE ( r_h4tl_stop = 0x40017934 ); +PROVIDE ( r_h4tl_write = 0x400178d0 ); +PROVIDE ( r_H5 = 0x400138dc ); +PROVIDE ( r_hashConcat = 0x40013a38 ); +PROVIDE ( r_hci_acl_tx_data_alloc = 0x4001951c ); +PROVIDE ( r_hci_acl_tx_data_received = 0x40019654 ); +PROVIDE ( r_hci_bt_acl_bdaddr_register = 0x40018900 ); +PROVIDE ( r_hci_bt_acl_bdaddr_unregister = 0x400189ac ); +PROVIDE ( r_hci_bt_acl_conhdl_register = 0x4001895c ); +PROVIDE ( r_hci_cmd_get_max_param_size = 0x400192d0 ); +PROVIDE ( r_hci_cmd_received = 0x400192f8 ); +PROVIDE ( r_hci_evt_filter_add = 0x40018a64 ); +PROVIDE ( r_hci_evt_mask_set = 0x400189e4 ); +PROVIDE ( r_hci_fc_acl_buf_size_set = 0x40017988 ); +PROVIDE ( r_hci_fc_acl_en = 0x400179d8 ); +PROVIDE ( r_hci_fc_acl_packet_sent = 0x40017a3c ); +PROVIDE ( r_hci_fc_check_host_available_nb_acl_packets = 0x40017aa4 ); +PROVIDE ( r_hci_fc_check_host_available_nb_sync_packets = 0x40017ac8 ); +PROVIDE ( r_hci_fc_host_nb_acl_pkts_complete = 0x40017a6c ); +PROVIDE ( r_hci_fc_host_nb_sync_pkts_complete = 0x40017a88 ); +PROVIDE ( r_hci_fc_init = 0x40017974 ); +PROVIDE ( r_hci_fc_sync_buf_size_set = 0x400179b0 ); +PROVIDE ( r_hci_fc_sync_en = 0x40017a30 ); +PROVIDE ( r_hci_fc_sync_packet_sent = 0x40017a54 ); +PROVIDE ( r_hci_init = 0x40018538 ); +PROVIDE ( r_hci_look_for_cmd_desc = 0x40018454 ); +PROVIDE ( r_hci_look_for_dbg_evt_desc = 0x400184c4 ); +PROVIDE ( r_hci_look_for_evt_desc = 0x400184a0 ); +PROVIDE ( r_hci_look_for_le_evt_desc = 0x400184e0 ); +PROVIDE ( r_hci_reset = 0x4001856c ); +PROVIDE ( r_hci_send_2_host = 0x400185bc ); +PROVIDE ( r_hci_sync_tx_data_alloc = 0x40019754 ); +PROVIDE ( r_hci_sync_tx_data_received = 0x400197c0 ); +PROVIDE ( r_hci_tl_init = 0x40019290 ); +PROVIDE ( r_hci_tl_send = 0x40019228 ); +PROVIDE ( r_hci_util_pack = 0x40019874 ); +PROVIDE ( r_hci_util_unpack = 0x40019998 ); +PROVIDE ( r_hci_voice_settings_get = 0x40018bdc ); +PROVIDE ( r_hci_voice_settings_set = 0x40018be8 ); +PROVIDE ( r_HMAC = 0x40013968 ); +PROVIDE ( r_import_rf_phy_func = 0x3ffb8354 ); +PROVIDE ( r_import_rf_phy_func_p = 0x3ffafd64 ); +PROVIDE ( r_ip_funcs = 0x3ffae710 ); +PROVIDE ( r_ip_funcs_p = 0x3ffae70c ); +PROVIDE ( r_ke_check_malloc = 0x40019de0 ); +PROVIDE ( r_ke_event_callback_set = 0x40019ba8 ); +PROVIDE ( r_ke_event_clear = 0x40019c2c ); +PROVIDE ( r_ke_event_flush = 0x40019ccc ); +PROVIDE ( r_ke_event_get = 0x40019c78 ); +PROVIDE ( r_ke_event_get_all = 0x40019cc0 ); +PROVIDE ( r_ke_event_init = 0x40019b90 ); +PROVIDE ( r_ke_event_schedule = 0x40019cdc ); +PROVIDE ( r_ke_event_set = 0x40019be0 ); +PROVIDE ( r_ke_flush = 0x4001a374 ); +PROVIDE ( r_ke_free = 0x4001a014 ); +PROVIDE ( r_ke_get_max_mem_usage = 0x4001a1c8 ); +PROVIDE ( r_ke_get_mem_usage = 0x4001a1a0 ); +PROVIDE ( r_ke_init = 0x4001a318 ); +PROVIDE ( r_ke_is_free = 0x4001a184 ); +PROVIDE ( r_ke_malloc = 0x40019eb4 ); +PROVIDE ( r_ke_mem_init = 0x40019d3c ); +PROVIDE ( r_ke_mem_is_empty = 0x40019d8c ); +PROVIDE ( r_ke_msg_alloc = 0x4001a1e0 ); +PROVIDE ( r_ke_msg_dest_id_get = 0x4001a2e0 ); +PROVIDE ( r_ke_msg_discard = 0x4001a850 ); +PROVIDE ( r_ke_msg_forward = 0x4001a290 ); +PROVIDE ( r_ke_msg_forward_new_id = 0x4001a2ac ); +PROVIDE ( r_ke_msg_free = 0x4001a2cc ); +PROVIDE ( r_ke_msg_in_queue = 0x4001a2f8 ); +PROVIDE ( r_ke_msg_save = 0x4001a858 ); +PROVIDE ( r_ke_msg_send = 0x4001a234 ); +PROVIDE ( r_ke_msg_send_basic = 0x4001a26c ); +PROVIDE ( r_ke_msg_src_id_get = 0x4001a2ec ); +PROVIDE ( r_ke_queue_extract = 0x40055fd0 ); +PROVIDE ( r_ke_queue_insert = 0x40056020 ); +PROVIDE ( r_ke_sleep_check = 0x4001a3d8 ); +PROVIDE ( r_ke_state_get = 0x4001a7d8 ); +PROVIDE ( r_ke_state_set = 0x4001a6fc ); +PROVIDE ( r_ke_stats_get = 0x4001a3f0 ); +PROVIDE ( r_ke_task_check = 0x4001a8a4 ); +PROVIDE ( r_ke_task_create = 0x4001a674 ); +PROVIDE ( r_ke_task_delete = 0x4001a6c0 ); +PROVIDE ( r_ke_task_init = 0x4001a650 ); +PROVIDE ( r_ke_task_msg_flush = 0x4001a860 ); +PROVIDE ( r_ke_timer_active = 0x4001ac08 ); +PROVIDE ( r_ke_timer_adjust_all = 0x4001ac30 ); +PROVIDE ( r_ke_timer_clear = 0x4001ab90 ); +PROVIDE ( r_ke_timer_init = 0x4001aa9c ); +PROVIDE ( r_ke_timer_set = 0x4001aac0 ); +PROVIDE ( r_ke_timer_sleep_check = 0x4001ac50 ); +PROVIDE ( r_KPrimC = 0x40010ad4 ); +PROVIDE ( r_lb_clk_adj_activate = 0x4001ae70 ); +PROVIDE ( r_lb_clk_adj_id_get = 0x4001af14 ); +PROVIDE ( r_lb_clk_adj_period_update = 0x4001af20 ); +PROVIDE ( r_lb_init = 0x4001acd4 ); +PROVIDE ( r_lb_mst_key = 0x4001afc0 ); +PROVIDE ( r_lb_mst_key_cmp = 0x4001af74 ); +PROVIDE ( r_lb_mst_key_restart_enc = 0x4001b0d4 ); +PROVIDE ( r_lb_mst_start_act_bcst_enc = 0x4001b198 ); +PROVIDE ( r_lb_mst_stop_act_bcst_enc = 0x4001b24c ); +PROVIDE ( r_lb_reset = 0x4001ad38 ); +PROVIDE ( r_lb_send_lmp = 0x4001adbc ); +PROVIDE ( r_lb_send_pdu_clk_adj = 0x4001af3c ); +PROVIDE ( r_lb_util_get_csb_mode = 0x4001ada4 ); +PROVIDE ( r_lb_util_get_nb_broadcast = 0x4001ad80 ); +PROVIDE ( r_lb_util_get_res_lt_addr = 0x4001ad98 ); +PROVIDE ( r_lb_util_set_nb_broadcast = 0x4001ad8c ); +PROVIDE ( r_lc_afh_set = 0x4001cc74 ); +PROVIDE ( r_lc_afh_start = 0x4001d240 ); +PROVIDE ( r_lc_auth_cmp = 0x4001cd54 ); +PROVIDE ( r_lc_calc_link_key = 0x4001ce7c ); +PROVIDE ( r_lc_chg_pkt_type_cmp = 0x4001d038 ); +PROVIDE ( r_lc_chg_pkt_type_cont = 0x4001cfbc ); +PROVIDE ( r_lc_chg_pkt_type_retry = 0x4001d0ac ); +PROVIDE ( r_lc_chk_to = 0x4001d2a8 ); +PROVIDE ( r_lc_cmd_stat_send = 0x4001c914 ); +PROVIDE ( r_lc_comb_key_svr = 0x4001d30c ); +PROVIDE ( r_lc_con_cmp = 0x4001d44c ); +PROVIDE ( r_lc_con_cmp_evt_send = 0x4001d4fc ); +PROVIDE ( r_lc_conn_seq_done = 0x40021334 ); +PROVIDE ( r_lc_detach = 0x4002037c ); +PROVIDE ( r_lc_dhkey = 0x4001d564 ); +PROVIDE ( r_lc_enc_cmp = 0x4001d8bc ); +PROVIDE ( r_lc_enc_key_refresh = 0x4001d720 ); +PROVIDE ( r_lc_end_chk_colli = 0x4001d858 ); +PROVIDE ( r_lc_end_of_sniff_nego = 0x4001d9a4 ); +PROVIDE ( r_lc_enter_sniff_mode = 0x4001ddb8 ); +PROVIDE ( r_lc_epr_change_lk = 0x4001db38 ); +PROVIDE ( r_lc_epr_cmp = 0x4001da88 ); +PROVIDE ( r_lc_epr_resp = 0x4001e0b4 ); +PROVIDE ( r_lc_epr_rsw_cmp = 0x4001dd40 ); +PROVIDE ( r_lc_ext_feat = 0x40020d6c ); +PROVIDE ( r_lc_feat = 0x40020984 ); +PROVIDE ( r_lc_hl_connect = 0x400209e8 ); +PROVIDE ( r_lc_init = 0x4001c948 ); +PROVIDE ( r_lc_init_calc_f3 = 0x4001deb0 ); +PROVIDE ( r_lc_initiator_epr = 0x4001e064 ); +PROVIDE ( r_lc_init_passkey_loop = 0x4001dfc0 ); +PROVIDE ( r_lc_init_start_mutual_auth = 0x4001df60 ); +PROVIDE ( r_lc_key_exch_end = 0x4001e140 ); +PROVIDE ( r_lc_legacy_pair = 0x4001e1c0 ); +PROVIDE ( r_lc_local_switch = 0x4001e22c ); +PROVIDE ( r_lc_local_trans_mode = 0x4001e2e4 ); +PROVIDE ( r_lc_local_untrans_mode = 0x4001e3a0 ); +PROVIDE ( r_lc_loc_auth = 0x40020ecc ); +PROVIDE ( r_lc_locepr_lkref = 0x4001d648 ); +PROVIDE ( r_lc_locepr_rsw = 0x4001d5d0 ); +PROVIDE ( r_lc_loc_sniff = 0x40020a6c ); +PROVIDE ( r_lc_max_slot_mgt = 0x4001e410 ); +PROVIDE ( r_lc_mst_key = 0x4001e7c0 ); +PROVIDE ( r_lc_mst_qos_done = 0x4001ea80 ); +PROVIDE ( r_lc_mst_send_mst_key = 0x4001e8f4 ); +PROVIDE ( r_lc_mutual_auth_end = 0x4001e670 ); +PROVIDE ( r_lc_mutual_auth_end2 = 0x4001e4f4 ); +PROVIDE ( r_lc_packet_type = 0x40021038 ); +PROVIDE ( r_lc_pair = 0x40020ddc ); +PROVIDE ( r_lc_pairing_cont = 0x4001eafc ); +PROVIDE ( r_lc_passkey_comm = 0x4001ed20 ); +PROVIDE ( r_lc_prepare_all_links_for_clk_adj = 0x40021430 ); +PROVIDE ( r_lc_proc_rcv_dhkey = 0x4001edec ); +PROVIDE ( r_lc_ptt = 0x4001ee2c ); +PROVIDE ( r_lc_ptt_cmp = 0x4001eeec ); +PROVIDE ( r_lc_qos_setup = 0x4001ef50 ); +PROVIDE ( r_lc_rd_rem_name = 0x4001efd0 ); +PROVIDE ( r_lc_release = 0x4001f8a8 ); +PROVIDE ( r_lc_rem_enc = 0x4001f124 ); +PROVIDE ( r_lc_rem_name_cont = 0x4001f290 ); +PROVIDE ( r_lc_rem_nego_trans_mode = 0x4001f1b4 ); +PROVIDE ( r_lc_rem_sniff = 0x40020ca4 ); +PROVIDE ( r_lc_rem_sniff_sub_rate = 0x40020b10 ); +PROVIDE ( r_lc_rem_switch = 0x4001f070 ); +PROVIDE ( r_lc_rem_trans_mode = 0x4001f314 ); +PROVIDE ( r_lc_rem_unsniff = 0x400207a0 ); +PROVIDE ( r_lc_rem_untrans_mode = 0x4001f36c ); +PROVIDE ( r_lc_reset = 0x4001c99c ); +PROVIDE ( r_lc_resp_auth = 0x4001f518 ); +PROVIDE ( r_lc_resp_calc_f3 = 0x4001f710 ); +PROVIDE ( r_lc_resp_num_comp = 0x40020074 ); +PROVIDE ( r_lc_resp_oob_nonce = 0x4001f694 ); +PROVIDE ( r_lc_resp_oob_wait_nonce = 0x4001f66c ); +PROVIDE ( r_lc_resp_pair = 0x400208a4 ); +PROVIDE ( r_lc_resp_sec_auth = 0x4001f4a0 ); +PROVIDE ( r_lc_resp_wait_dhkey_cont = 0x4001f86c ); +PROVIDE ( r_lc_restart_enc = 0x4001f8ec ); +PROVIDE ( r_lc_restart_enc_cont = 0x4001f940 ); +PROVIDE ( r_lc_restore_afh_reporting = 0x4001f028 ); +PROVIDE ( r_lc_restore_to = 0x4001f9e0 ); +PROVIDE ( r_lc_ret_sniff_max_slot_chg = 0x4001fa30 ); +PROVIDE ( r_lc_rsw_clean_up = 0x4001dc70 ); +PROVIDE ( r_lc_rsw_done = 0x4001db94 ); +PROVIDE ( r_lc_sco_baseband_ack = 0x40022b00 ); +PROVIDE ( r_lc_sco_detach = 0x40021e40 ); +PROVIDE ( r_lc_sco_host_accept = 0x40022118 ); +PROVIDE ( r_lc_sco_host_reject = 0x400222b8 ); +PROVIDE ( r_lc_sco_host_request = 0x40021f4c ); +PROVIDE ( r_lc_sco_host_request_disc = 0x4002235c ); +PROVIDE ( r_lc_sco_init = 0x40021dc8 ); +PROVIDE ( r_lc_sco_peer_accept = 0x40022780 ); +PROVIDE ( r_lc_sco_peer_accept_disc = 0x40022a08 ); +PROVIDE ( r_lc_sco_peer_reject = 0x40022824 ); +PROVIDE ( r_lc_sco_peer_reject_disc = 0x40022a8c ); +PROVIDE ( r_lc_sco_peer_request = 0x4002240c ); +PROVIDE ( r_lc_sco_peer_request_disc = 0x400228ec ); +PROVIDE ( r_lc_sco_release = 0x40021eec ); +PROVIDE ( r_lc_sco_reset = 0x40021dfc ); +PROVIDE ( r_lc_sco_timeout = 0x40022bd4 ); +PROVIDE ( r_lc_sec_auth_compute_sres = 0x4001f3ec ); +PROVIDE ( r_lc_semi_key_cmp = 0x40020294 ); +PROVIDE ( r_lc_send_enc_chg_evt = 0x4002134c ); +PROVIDE ( r_lc_send_enc_mode = 0x40020220 ); +PROVIDE ( r_lc_send_lmp = 0x4001c1a8 ); +PROVIDE ( r_lc_send_pdu_acc = 0x4001c21c ); +PROVIDE ( r_lc_send_pdu_acc_ext4 = 0x4001c240 ); +PROVIDE ( r_lc_send_pdu_au_rand = 0x4001c308 ); +PROVIDE ( r_lc_send_pdu_auto_rate = 0x4001c5d0 ); +PROVIDE ( r_lc_send_pdu_clk_adj_ack = 0x4001c46c ); +PROVIDE ( r_lc_send_pdu_clk_adj_req = 0x4001c494 ); +PROVIDE ( r_lc_send_pdu_comb_key = 0x4001c368 ); +PROVIDE ( r_lc_send_pdu_dhkey_chk = 0x4001c8e8 ); +PROVIDE ( r_lc_send_pdu_encaps_head = 0x4001c440 ); +PROVIDE ( r_lc_send_pdu_encaps_payl = 0x4001c410 ); +PROVIDE ( r_lc_send_pdu_enc_key_sz_req = 0x4001c670 ); +PROVIDE ( r_lc_send_pdu_esco_lk_rem_req = 0x4001c5a8 ); +PROVIDE ( r_lc_send_pdu_feats_ext_req = 0x4001c6ec ); +PROVIDE ( r_lc_send_pdu_feats_res = 0x4001c694 ); +PROVIDE ( r_lc_send_pdu_in_rand = 0x4001c338 ); +PROVIDE ( r_lc_send_pdu_io_cap_res = 0x4001c72c ); +PROVIDE ( r_lc_send_pdu_lsto = 0x4001c64c ); +PROVIDE ( r_lc_send_pdu_max_slot = 0x4001c3c8 ); +PROVIDE ( r_lc_send_pdu_max_slot_req = 0x4001c3ec ); +PROVIDE ( r_lc_send_pdu_not_acc = 0x4001c26c ); +PROVIDE ( r_lc_send_pdu_not_acc_ext4 = 0x4001c294 ); +PROVIDE ( r_lc_send_pdu_num_comp_fail = 0x4001c770 ); +PROVIDE ( r_lc_send_pdu_pause_enc_aes_req = 0x4001c794 ); +PROVIDE ( r_lc_send_pdu_paus_enc_req = 0x4001c7c0 ); +PROVIDE ( r_lc_send_pdu_ptt_req = 0x4001c4c0 ); +PROVIDE ( r_lc_send_pdu_qos_req = 0x4001c82c ); +PROVIDE ( r_lc_send_pdu_resu_enc_req = 0x4001c7e4 ); +PROVIDE ( r_lc_send_pdu_sco_lk_rem_req = 0x4001c580 ); +PROVIDE ( r_lc_send_pdu_set_afh = 0x4001c2c8 ); +PROVIDE ( r_lc_send_pdu_setup_cmp = 0x4001c808 ); +PROVIDE ( r_lc_send_pdu_slot_off = 0x4001c854 ); +PROVIDE ( r_lc_send_pdu_sniff_req = 0x4001c5f0 ); +PROVIDE ( r_lc_send_pdu_sp_cfm = 0x4001c518 ); +PROVIDE ( r_lc_send_pdu_sp_nb = 0x4001c4e8 ); +PROVIDE ( r_lc_send_pdu_sres = 0x4001c548 ); +PROVIDE ( r_lc_send_pdu_tim_acc = 0x4001c6cc ); +PROVIDE ( r_lc_send_pdu_unit_key = 0x4001c398 ); +PROVIDE ( r_lc_send_pdu_unsniff_req = 0x4001c894 ); +PROVIDE ( r_lc_send_pdu_vers_req = 0x4001c8b4 ); +PROVIDE ( r_lc_skip_hl_oob_req = 0x400201bc ); +PROVIDE ( r_lc_sniff_init = 0x40022cac ); +PROVIDE ( r_lc_sniff_max_slot_chg = 0x40020590 ); +PROVIDE ( r_lc_sniff_reset = 0x40022cc8 ); +PROVIDE ( r_lc_sniff_slot_unchange = 0x40021100 ); +PROVIDE ( r_lc_sniff_sub_mode = 0x400204fc ); +PROVIDE ( r_lc_sp_end = 0x400213a8 ); +PROVIDE ( r_lc_sp_fail = 0x40020470 ); +PROVIDE ( r_lc_sp_oob_tid_fail = 0x400204cc ); +PROVIDE ( r_lc_ssr_nego = 0x4002125c ); +PROVIDE ( r_lc_start = 0x4001ca28 ); +PROVIDE ( r_lc_start_enc = 0x4001fb28 ); +PROVIDE ( r_lc_start_enc_key_size = 0x4001fd9c ); +PROVIDE ( r_lc_start_key_exch = 0x4001fe10 ); +PROVIDE ( r_lc_start_lmp_to = 0x4001fae8 ); +PROVIDE ( r_lc_start_oob = 0x4001fffc ); +PROVIDE ( r_lc_start_passkey = 0x4001feac ); +PROVIDE ( r_lc_start_passkey_loop = 0x4001ff88 ); +PROVIDE ( r_lc_stop_afh_report = 0x40020184 ); +PROVIDE ( r_lc_stop_enc = 0x40020110 ); +PROVIDE ( r_lc_switch_cmp = 0x40020448 ); +PROVIDE ( r_lc_unit_key_svr = 0x400206d8 ); +PROVIDE ( r_lc_unsniff = 0x40020c50 ); +PROVIDE ( r_lc_unsniff_cmp = 0x40020810 ); +PROVIDE ( r_lc_unsniff_cont = 0x40020750 ); +PROVIDE ( r_lc_upd_to = 0x4002065c ); +PROVIDE ( r_lc_util_convert_pref_rate_to_packet_type = 0x4002f9b0 ); +PROVIDE ( r_lc_util_get_max_packet_size = 0x4002f4ac ); +PROVIDE ( r_lc_util_get_offset_clke = 0x4002f538 ); +PROVIDE ( r_lc_util_get_offset_clkn = 0x4002f51c ); +PROVIDE ( r_lc_util_set_loc_trans_coll = 0x4002f500 ); +PROVIDE ( r_lc_version = 0x40020a30 ); +PROVIDE ( r_ld_acl_active_hop_types_get = 0x40036e10 ); +PROVIDE ( r_ld_acl_afh_confirm = 0x40036d40 ); +PROVIDE ( r_ld_acl_afh_prepare = 0x40036c84 ); +PROVIDE ( r_ld_acl_afh_set = 0x40036b60 ); +PROVIDE ( r_ld_acl_allowed_tx_packet_types_set = 0x40036810 ); +PROVIDE ( r_ld_acl_bcst_rx_dec = 0x40036394 ); +PROVIDE ( r_ld_acl_bit_off_get = 0x40036b18 ); +PROVIDE ( r_ld_acl_clk_adj_set = 0x40036a00 ); +PROVIDE ( r_ld_acl_clk_off_get = 0x40036b00 ); +PROVIDE ( r_ld_acl_clk_set = 0x40036950 ); +PROVIDE ( r_ld_acl_clock_offset_get = 0x400364c0 ); +PROVIDE ( r_ld_acl_current_tx_power_get = 0x400368f0 ); +PROVIDE ( r_ld_acl_data_flush = 0x400357bc ); +PROVIDE ( r_ld_acl_data_tx = 0x4003544c ); +PROVIDE ( r_ld_acl_edr_set = 0x4003678c ); +PROVIDE ( r_ld_acl_enc_key_load = 0x40036404 ); +PROVIDE ( r_ld_acl_flow_off = 0x40035400 ); +PROVIDE ( r_ld_acl_flow_on = 0x4003541c ); +PROVIDE ( r_ld_acl_flush_timeout_get = 0x40035f9c ); +PROVIDE ( r_ld_acl_flush_timeout_set = 0x40035fe0 ); +PROVIDE ( r_ld_acl_init = 0x40034d08 ); +PROVIDE ( r_ld_acl_lmp_flush = 0x40035d80 ); +PROVIDE ( r_ld_acl_lmp_tx = 0x40035b34 ); +PROVIDE ( r_ld_acl_lsto_get = 0x400366b4 ); +PROVIDE ( r_ld_acl_lsto_set = 0x400366f8 ); +PROVIDE ( r_ld_acl_reset = 0x40034d24 ); +PROVIDE ( r_ld_acl_role_get = 0x40036b30 ); +PROVIDE ( r_ld_acl_rssi_delta_get = 0x40037028 ); +PROVIDE ( r_ld_acl_rsw_req = 0x40035e74 ); +PROVIDE ( r_ld_acl_rx_enc = 0x40036344 ); +PROVIDE ( r_ld_acl_rx_max_slot_get = 0x40036e58 ); +PROVIDE ( r_ld_acl_rx_max_slot_set = 0x40036ea0 ); +PROVIDE ( r_ld_acl_slot_offset_get = 0x4003653c ); +PROVIDE ( r_ld_acl_slot_offset_set = 0x40036658 ); +PROVIDE ( r_ld_acl_sniff = 0x4003617c ); +PROVIDE ( r_ld_acl_sniff_trans = 0x400360a8 ); +PROVIDE ( r_ld_acl_ssr_set = 0x40036274 ); +PROVIDE ( r_ld_acl_start = 0x40034ddc ); +PROVIDE ( r_ld_acl_stop = 0x4003532c ); +PROVIDE ( r_ld_acl_test_mode_set = 0x40036f24 ); +PROVIDE ( r_ld_acl_timing_accuracy_set = 0x4003673c ); +PROVIDE ( r_ld_acl_t_poll_get = 0x40036024 ); +PROVIDE ( r_ld_acl_t_poll_set = 0x40036068 ); +PROVIDE ( r_ld_acl_tx_enc = 0x400362f8 ); +PROVIDE ( r_ld_acl_unsniff = 0x400361e0 ); +PROVIDE ( r_ld_active_check = 0x4003cac4 ); +PROVIDE ( r_ld_afh_ch_assess_data_get = 0x4003caec ); +PROVIDE ( r_ld_bcst_acl_data_tx = 0x40038d3c ); +PROVIDE ( r_ld_bcst_acl_init = 0x40038bd0 ); +PROVIDE ( r_ld_bcst_acl_reset = 0x40038bdc ); +PROVIDE ( r_ld_bcst_acl_start = 0x4003882c ); +PROVIDE ( r_ld_bcst_afh_update = 0x40038f3c ); +PROVIDE ( r_ld_bcst_enc_key_load = 0x4003906c ); +PROVIDE ( r_ld_bcst_lmp_tx = 0x40038bf8 ); +PROVIDE ( r_ld_bcst_tx_enc = 0x40038ff8 ); +PROVIDE ( r_ld_bd_addr_get = 0x4003ca20 ); +PROVIDE ( r_ld_channel_assess = 0x4003c184 ); +PROVIDE ( r_ld_class_of_dev_get = 0x4003ca34 ); +PROVIDE ( r_ld_class_of_dev_set = 0x4003ca50 ); +PROVIDE ( r_ld_csb_rx_afh_update = 0x40039af4 ); +PROVIDE ( r_ld_csb_rx_init = 0x40039690 ); +PROVIDE ( r_ld_csb_rx_reset = 0x4003969c ); +PROVIDE ( r_ld_csb_rx_start = 0x4003972c ); +PROVIDE ( r_ld_csb_rx_stop = 0x40039bb8 ); +PROVIDE ( r_ld_csb_tx_afh_update = 0x4003a5fc ); +PROVIDE ( r_ld_csb_tx_clr_data = 0x4003a71c ); +PROVIDE ( r_ld_csb_tx_dis = 0x4003a5e8 ); +PROVIDE ( r_ld_csb_tx_en = 0x4003a1c0 ); +PROVIDE ( r_ld_csb_tx_init = 0x4003a0e8 ); +PROVIDE ( r_ld_csb_tx_reset = 0x4003a0f8 ); +PROVIDE ( r_ld_csb_tx_set_data = 0x4003a6c0 ); +PROVIDE ( r_ld_fm_clk_isr = 0x4003a7a8 ); +PROVIDE ( r_ld_fm_frame_isr = 0x4003a82c ); +PROVIDE ( r_ld_fm_init = 0x4003a760 ); +PROVIDE ( r_ld_fm_prog_check = 0x4003ab28 ); +PROVIDE ( r_ld_fm_prog_disable = 0x4003a984 ); +PROVIDE ( r_ld_fm_prog_enable = 0x4003a944 ); +PROVIDE ( r_ld_fm_prog_push = 0x4003a9d4 ); +PROVIDE ( r_ld_fm_reset = 0x4003a794 ); +PROVIDE ( r_ld_fm_rx_isr = 0x4003a7f4 ); +PROVIDE ( r_ld_fm_sket_isr = 0x4003a8a4 ); +PROVIDE ( r_ld_init = 0x4003c294 ); +PROVIDE ( r_ld_inq_init = 0x4003b15c ); +PROVIDE ( r_ld_inq_reset = 0x4003b168 ); +PROVIDE ( r_ld_inq_start = 0x4003b1f0 ); +PROVIDE ( r_ld_inq_stop = 0x4003b4f0 ); +PROVIDE ( r_ld_iscan_eir_get = 0x4003c118 ); +PROVIDE ( r_ld_iscan_eir_set = 0x4003bfa0 ); +PROVIDE ( r_ld_iscan_init = 0x4003b9f0 ); +PROVIDE ( r_ld_iscan_reset = 0x4003ba14 ); +PROVIDE ( r_ld_iscan_restart = 0x4003ba44 ); +PROVIDE ( r_ld_iscan_start = 0x4003bb28 ); +PROVIDE ( r_ld_iscan_stop = 0x4003bf1c ); +PROVIDE ( r_ld_iscan_tx_pwr_get = 0x4003c138 ); +PROVIDE ( r_ld_page_init = 0x4003d808 ); +PROVIDE ( r_ld_page_reset = 0x4003d814 ); +PROVIDE ( r_ld_page_start = 0x4003d848 ); +PROVIDE ( r_ld_page_stop = 0x4003da54 ); +PROVIDE ( r_ld_pca_coarse_clock_adjust = 0x4003e324 ); +PROVIDE ( r_ld_pca_init = 0x4003deb4 ); +PROVIDE ( r_ld_pca_initiate_clock_dragging = 0x4003e4ac ); +PROVIDE ( r_ld_pca_local_config = 0x4003df6c ); +PROVIDE ( r_ld_pca_mws_frame_sync = 0x4003e104 ); +PROVIDE ( r_ld_pca_mws_moment_offset_gt = 0x4003e278 ); +PROVIDE ( r_ld_pca_mws_moment_offset_lt = 0x4003e280 ); +PROVIDE ( r_ld_pca_reporting_enable = 0x4003e018 ); +PROVIDE ( r_ld_pca_reset = 0x4003df0c ); +PROVIDE ( r_ld_pca_update_target_offset = 0x4003e050 ); +PROVIDE ( r_ld_pscan_evt_handler = 0x4003f238 ); +PROVIDE ( r_ld_pscan_init = 0x4003f474 ); +PROVIDE ( r_ld_pscan_reset = 0x4003f498 ); +PROVIDE ( r_ld_pscan_restart = 0x4003f4b8 ); +PROVIDE ( r_ld_pscan_start = 0x4003f514 ); +PROVIDE ( r_ld_pscan_stop = 0x4003f618 ); +PROVIDE ( r_ld_read_clock = 0x4003c9e4 ); +PROVIDE ( r_ld_reset = 0x4003c714 ); +PROVIDE ( r_ld_sched_acl_add = 0x4003f978 ); +PROVIDE ( r_ld_sched_acl_remove = 0x4003f99c ); +PROVIDE ( r_ld_sched_compute = 0x4003f6f8 ); +PROVIDE ( r_ld_sched_init = 0x4003f7ac ); +PROVIDE ( r_ld_sched_inq_add = 0x4003f8a8 ); +PROVIDE ( r_ld_sched_inq_remove = 0x4003f8d0 ); +PROVIDE ( r_ld_sched_iscan_add = 0x4003f7e8 ); +PROVIDE ( r_ld_sched_iscan_remove = 0x4003f808 ); +PROVIDE ( r_ld_sched_page_add = 0x4003f910 ); +PROVIDE ( r_ld_sched_page_remove = 0x4003f938 ); +PROVIDE ( r_ld_sched_pscan_add = 0x4003f828 ); +PROVIDE ( r_ld_sched_pscan_remove = 0x4003f848 ); +PROVIDE ( r_ld_sched_reset = 0x4003f7d4 ); +PROVIDE ( r_ld_sched_sco_add = 0x4003fa4c ); +PROVIDE ( r_ld_sched_sco_remove = 0x4003fa9c ); +PROVIDE ( r_ld_sched_sniff_add = 0x4003f9c4 ); +PROVIDE ( r_ld_sched_sniff_remove = 0x4003fa0c ); +PROVIDE ( r_ld_sched_sscan_add = 0x4003f868 ); +PROVIDE ( r_ld_sched_sscan_remove = 0x4003f888 ); +PROVIDE ( r_ld_sco_audio_isr = 0x40037cc8 ); +PROVIDE ( r_ld_sco_data_tx = 0x40037ee8 ); +PROVIDE ( r_ld_sco_start = 0x40037110 ); +PROVIDE ( r_ld_sco_stop = 0x40037c40 ); +PROVIDE ( r_ld_sco_update = 0x40037a74 ); +PROVIDE ( r_ld_sscan_activated = 0x4004031c ); +PROVIDE ( r_ld_sscan_init = 0x400402f0 ); +PROVIDE ( r_ld_sscan_reset = 0x400402fc ); +PROVIDE ( r_ld_sscan_start = 0x40040384 ); +PROVIDE ( r_ld_strain_init = 0x400409f4 ); +PROVIDE ( r_ld_strain_reset = 0x40040a00 ); +PROVIDE ( r_ld_strain_start = 0x40040a8c ); +PROVIDE ( r_ld_strain_stop = 0x40040df0 ); +PROVIDE ( r_ld_timing_accuracy_get = 0x4003caac ); +PROVIDE ( r_ld_util_active_master_afh_map_get = 0x4004131c ); +PROVIDE ( r_ld_util_active_master_afh_map_set = 0x40041308 ); +PROVIDE ( r_ld_util_bch_create = 0x40040fcc ); +PROVIDE ( r_ld_util_fhs_pk = 0x400411c8 ); +PROVIDE ( r_ld_util_fhs_unpk = 0x40040e54 ); +PROVIDE ( r_ld_util_stp_pk = 0x400413f4 ); +PROVIDE ( r_ld_util_stp_unpk = 0x40041324 ); +PROVIDE ( r_ld_version_get = 0x4003ca6c ); +PROVIDE ( r_ld_wlcoex_set = 0x4003caf8 ); +PROVIDE ( r_llc_ch_assess_get_current_ch_map = 0x40041574 ); +PROVIDE ( r_llc_ch_assess_get_local_ch_map = 0x4004150c ); +PROVIDE ( r_llc_ch_assess_local = 0x40041494 ); +PROVIDE ( r_llc_ch_assess_merge_ch = 0x40041588 ); +PROVIDE ( r_llc_ch_assess_reass_ch = 0x400415c0 ); +PROVIDE ( r_llc_common_cmd_complete_send = 0x40044eac ); +PROVIDE ( r_llc_common_cmd_status_send = 0x40044ee0 ); +PROVIDE ( r_llc_common_enc_change_evt_send = 0x40044f6c ); +PROVIDE ( r_llc_common_enc_key_ref_comp_evt_send = 0x40044f38 ); +PROVIDE ( r_llc_common_flush_occurred_send = 0x40044f0c ); +PROVIDE ( r_llc_common_nb_of_pkt_comp_evt_send = 0x40045000 ); +PROVIDE ( r_llc_con_update_complete_send = 0x40044d68 ); +PROVIDE ( r_llc_con_update_finished = 0x4004518c ); +PROVIDE ( r_llc_con_update_ind = 0x40045038 ); +PROVIDE ( r_llc_discon_event_complete_send = 0x40044a30 ); +PROVIDE ( r_llc_end_evt_defer = 0x40046330 ); +PROVIDE ( r_llc_feats_rd_event_send = 0x40044e0c ); +PROVIDE ( r_llc_init = 0x40044778 ); +PROVIDE ( r_llc_le_con_cmp_evt_send = 0x40044a78 ); +PROVIDE ( r_llc_llcp_ch_map_update_pdu_send = 0x40043f94 ); +PROVIDE ( r_llc_llcp_con_param_req_pdu_send = 0x400442fc ); +PROVIDE ( r_llc_llcp_con_param_rsp_pdu_send = 0x40044358 ); +PROVIDE ( r_llc_llcp_con_update_pdu_send = 0x400442c4 ); +PROVIDE ( r_llc_llcp_enc_req_pdu_send = 0x40044064 ); +PROVIDE ( r_llc_llcp_enc_rsp_pdu_send = 0x40044160 ); +PROVIDE ( r_llc_llcp_feats_req_pdu_send = 0x400443b4 ); +PROVIDE ( r_llc_llcp_feats_rsp_pdu_send = 0x400443f0 ); +PROVIDE ( r_llc_llcp_get_autorize = 0x4004475c ); +PROVIDE ( r_llc_llcp_length_req_pdu_send = 0x40044574 ); +PROVIDE ( r_llc_llcp_length_rsp_pdu_send = 0x400445ac ); +PROVIDE ( r_llc_llcp_pause_enc_req_pdu_send = 0x40043fd8 ); +PROVIDE ( r_llc_llcp_pause_enc_rsp_pdu_send = 0x40044010 ); +PROVIDE ( r_llc_llcp_ping_req_pdu_send = 0x4004454c ); +PROVIDE ( r_llc_llcp_ping_rsp_pdu_send = 0x40044560 ); +PROVIDE ( r_llc_llcp_recv_handler = 0x40044678 ); +PROVIDE ( r_llc_llcp_reject_ind_pdu_send = 0x4004425c ); +PROVIDE ( r_llc_llcp_start_enc_req_pdu_send = 0x4004441c ); +PROVIDE ( r_llc_llcp_start_enc_rsp_pdu_send = 0x400441f8 ); +PROVIDE ( r_llc_llcp_terminate_ind_pdu_send = 0x400444b0 ); +PROVIDE ( r_llc_llcp_tester_send = 0x400445e4 ); +PROVIDE ( r_llc_llcp_unknown_rsp_send_pdu = 0x40044534 ); +PROVIDE ( r_llc_llcp_version_ind_pdu_send = 0x40043f6c ); +PROVIDE ( r_llc_lsto_con_update = 0x40045098 ); +PROVIDE ( r_llc_ltk_req_send = 0x40044dc0 ); +PROVIDE ( r_llc_map_update_finished = 0x40045260 ); +PROVIDE ( r_llc_map_update_ind = 0x400450f0 ); +PROVIDE ( r_llc_pdu_acl_tx_ack_defer = 0x400464dc ); +PROVIDE ( r_llc_pdu_defer = 0x40046528 ); +PROVIDE ( r_llc_pdu_llcp_tx_ack_defer = 0x400463ac ); +PROVIDE ( r_llc_reset = 0x400447b8 ); +PROVIDE ( r_llc_start = 0x400447f4 ); +PROVIDE ( r_llc_stop = 0x400449ac ); +PROVIDE ( r_llc_util_bw_mgt = 0x4004629c ); +PROVIDE ( r_llc_util_clear_operation_ptr = 0x40046234 ); +PROVIDE ( r_llc_util_dicon_procedure = 0x40046130 ); +PROVIDE ( r_llc_util_get_free_conhdl = 0x400460c8 ); +PROVIDE ( r_llc_util_get_nb_active_link = 0x40046100 ); +PROVIDE ( r_llc_util_set_auth_payl_to_margin = 0x400461f4 ); +PROVIDE ( r_llc_util_set_llcp_discard_enable = 0x400461c8 ); +PROVIDE ( r_llc_util_update_channel_map = 0x400461ac ); +PROVIDE ( r_llc_version_rd_event_send = 0x40044e60 ); +PROVIDE ( r_lld_adv_start = 0x40048b38 ); +PROVIDE ( r_lld_adv_stop = 0x40048ea0 ); +PROVIDE ( r_lld_ch_map_ind = 0x4004a2f4 ); +PROVIDE ( r_lld_con_param_req = 0x40049f0c ); +PROVIDE ( r_lld_con_param_rsp = 0x40049e00 ); +PROVIDE ( r_lld_con_start = 0x400491f8 ); +PROVIDE ( r_lld_con_stop = 0x40049fdc ); +PROVIDE ( r_lld_con_update_after_param_req = 0x40049bcc ); +PROVIDE ( r_lld_con_update_ind = 0x4004a30c ); +PROVIDE ( r_lld_con_update_req = 0x40049b60 ); +PROVIDE ( r_lld_core_reset = 0x40048a9c ); +PROVIDE ( r_lld_crypt_isr = 0x4004a324 ); +PROVIDE ( r_lld_evt_adv_create = 0x400481f4 ); +PROVIDE ( r_lld_evt_canceled = 0x400485c8 ); +PROVIDE ( r_lld_evt_channel_next = 0x40046aac ); +PROVIDE ( r_lld_evt_deffered_elt_handler = 0x400482bc ); +PROVIDE ( r_lld_evt_delete_elt_handler = 0x40046974 ); +PROVIDE ( r_lld_evt_delete_elt_push = 0x40046a3c ); +PROVIDE ( r_lld_evt_drift_compute = 0x40047670 ); +PROVIDE ( r_lld_evt_elt_delete = 0x40047538 ); +PROVIDE ( r_lld_evt_elt_insert = 0x400474c8 ); +PROVIDE ( r_lld_evt_end = 0x400483e8 ); +PROVIDE ( r_lld_evt_end_isr = 0x4004862c ); +PROVIDE ( r_lld_evt_init = 0x40046b3c ); +PROVIDE ( r_lld_evt_init_evt = 0x40046cd0 ); +PROVIDE ( r_lld_evt_move_to_master = 0x40047ba0 ); +PROVIDE ( r_lld_evt_move_to_slave = 0x40047e18 ); +PROVIDE ( r_lld_evt_prevent_stop = 0x40047adc ); +PROVIDE ( r_lld_evt_restart = 0x40046d50 ); +PROVIDE ( r_lld_evt_rx = 0x40048578 ); +PROVIDE ( r_lld_evt_rx_isr = 0x40048678 ); +PROVIDE ( r_lld_evt_scan_create = 0x40047ae8 ); +PROVIDE ( r_lld_evt_schedule = 0x40047908 ); +PROVIDE ( r_lld_evt_schedule_next = 0x400477dc ); +PROVIDE ( r_lld_evt_schedule_next_instant = 0x400476a8 ); +PROVIDE ( r_lld_evt_slave_update = 0x40048138 ); +PROVIDE ( r_lld_evt_update_create = 0x40047cd8 ); +PROVIDE ( r_lld_get_mode = 0x40049ff8 ); +PROVIDE ( r_lld_init = 0x4004873c ); +PROVIDE ( r_lld_move_to_master = 0x400499e0 ); +PROVIDE ( r_lld_move_to_slave = 0x4004a024 ); +PROVIDE ( r_lld_pdu_adv_pack = 0x4004b488 ); +PROVIDE ( r_lld_pdu_check = 0x4004ac34 ); +PROVIDE ( r_lld_pdu_data_send = 0x4004b018 ); +PROVIDE ( r_lld_pdu_data_tx_push = 0x4004aecc ); +PROVIDE ( r_lld_pdu_rx_handler = 0x4004b4d4 ); +PROVIDE ( r_lld_pdu_send_packet = 0x4004b774 ); +PROVIDE ( r_lld_pdu_tx_flush = 0x4004b414 ); +PROVIDE ( r_lld_pdu_tx_loop = 0x4004ae40 ); +PROVIDE ( r_lld_pdu_tx_prog = 0x4004b120 ); +PROVIDE ( r_lld_pdu_tx_push = 0x4004b080 ); +PROVIDE ( r_lld_ral_renew_req = 0x4004a73c ); +PROVIDE ( r_lld_scan_start = 0x40048ee0 ); +PROVIDE ( r_lld_scan_stop = 0x40049190 ); +PROVIDE ( r_lld_test_mode_rx = 0x4004a540 ); +PROVIDE ( r_lld_test_mode_tx = 0x4004a350 ); +PROVIDE ( r_lld_test_stop = 0x4004a710 ); +PROVIDE ( r_lld_util_anchor_point_move = 0x4004bacc ); +PROVIDE ( r_lld_util_compute_ce_max = 0x4004bc0c ); +PROVIDE ( r_lld_util_connection_param_set = 0x4004ba40 ); +PROVIDE ( r_lld_util_dle_set_cs_fields = 0x4004ba90 ); +PROVIDE ( r_lld_util_eff_tx_time_set = 0x4004bd88 ); +PROVIDE ( r_lld_util_elt_programmed = 0x4004bce0 ); +PROVIDE ( r_lld_util_flush_list = 0x4004bbd8 ); +PROVIDE ( r_lld_util_freq2chnl = 0x4004b9e4 ); +PROVIDE ( r_lld_util_get_bd_address = 0x4004b8ac ); +PROVIDE ( r_lld_util_get_local_offset = 0x4004ba10 ); +PROVIDE ( r_lld_util_get_peer_offset = 0x4004ba24 ); +PROVIDE ( r_lld_util_get_tx_pkt_cnt = 0x4004bd80 ); +PROVIDE ( r_lld_util_instant_get = 0x4004b890 ); +PROVIDE ( r_lld_util_instant_ongoing = 0x4004bbfc ); +PROVIDE ( r_lld_util_priority_set = 0x4004bd10 ); +PROVIDE ( r_lld_util_priority_update = 0x4004bd78 ); +PROVIDE ( r_lld_util_ral_force_rpa_renew = 0x4004b980 ); +PROVIDE ( r_lld_util_set_bd_address = 0x4004b8f8 ); +PROVIDE ( r_lld_wlcoex_set = 0x4004bd98 ); +PROVIDE ( r_llm_ble_ready = 0x4004cc34 ); +PROVIDE ( r_llm_common_cmd_complete_send = 0x4004d288 ); +PROVIDE ( r_llm_common_cmd_status_send = 0x4004d2b4 ); +PROVIDE ( r_llm_con_req_ind = 0x4004cc54 ); +PROVIDE ( r_llm_con_req_tx_cfm = 0x4004d158 ); +PROVIDE ( r_llm_create_con = 0x4004de78 ); +PROVIDE ( r_llm_encryption_done = 0x4004dff8 ); +PROVIDE ( r_llm_encryption_start = 0x4004e128 ); +PROVIDE ( r_llm_end_evt_defer = 0x4004eb6c ); +PROVIDE ( r_llm_init = 0x4004c9f8 ); +PROVIDE ( r_llm_le_adv_report_ind = 0x4004cdf4 ); +PROVIDE ( r_llm_pdu_defer = 0x4004ec48 ); +PROVIDE ( r_llm_ral_clear = 0x4004e1fc ); +PROVIDE ( r_llm_ral_dev_add = 0x4004e23c ); +PROVIDE ( r_llm_ral_dev_rm = 0x4004e3bc ); +PROVIDE ( r_llm_ral_get_rpa = 0x4004e400 ); +PROVIDE ( r_llm_ral_set_timeout = 0x4004e4a0 ); +PROVIDE ( r_llm_ral_update = 0x4004e4f8 ); +PROVIDE ( r_llm_set_adv_data = 0x4004d960 ); +PROVIDE ( r_llm_set_adv_en = 0x4004d7ec ); +PROVIDE ( r_llm_set_adv_param = 0x4004d5f4 ); +PROVIDE ( r_llm_set_scan_en = 0x4004db64 ); +PROVIDE ( r_llm_set_scan_param = 0x4004dac8 ); +PROVIDE ( r_llm_set_scan_rsp_data = 0x4004da14 ); +PROVIDE ( r_llm_test_mode_start_rx = 0x4004d534 ); +PROVIDE ( r_llm_test_mode_start_tx = 0x4004d2fc ); +PROVIDE ( r_llm_util_adv_data_update = 0x4004e8fc ); +PROVIDE ( r_llm_util_apply_bd_addr = 0x4004e868 ); +PROVIDE ( r_llm_util_bd_addr_in_ral = 0x4004eb08 ); +PROVIDE ( r_llm_util_bd_addr_in_wl = 0x4004e788 ); +PROVIDE ( r_llm_util_bd_addr_wl_position = 0x4004e720 ); +PROVIDE ( r_llm_util_bl_add = 0x4004e9ac ); +PROVIDE ( r_llm_util_bl_check = 0x4004e930 ); +PROVIDE ( r_llm_util_bl_rem = 0x4004ea70 ); +PROVIDE ( r_llm_util_check_address_validity = 0x4004e7e4 ); +PROVIDE ( r_llm_util_check_evt_mask = 0x4004e8b0 ); +PROVIDE ( r_llm_util_check_map_validity = 0x4004e800 ); +PROVIDE ( r_llm_util_get_channel_map = 0x4004e8d4 ); +PROVIDE ( r_llm_util_get_supp_features = 0x4004e8e8 ); +PROVIDE ( r_llm_util_set_public_addr = 0x4004e89c ); +PROVIDE ( r_llm_wl_clr = 0x4004dc54 ); +PROVIDE ( r_llm_wl_dev_add = 0x4004dcc0 ); +PROVIDE ( r_llm_wl_dev_add_hdl = 0x4004dd38 ); +PROVIDE ( r_llm_wl_dev_rem = 0x4004dcfc ); +PROVIDE ( r_llm_wl_dev_rem_hdl = 0x4004dde0 ); +PROVIDE ( r_lm_acl_disc = 0x4004f148 ); +PROVIDE ( r_LM_AddSniff = 0x40022d20 ); +PROVIDE ( r_lm_add_sync = 0x40051358 ); +PROVIDE ( r_lm_afh_activate_timer = 0x4004f444 ); +PROVIDE ( r_lm_afh_ch_ass_en_get = 0x4004f3f8 ); +PROVIDE ( r_lm_afh_host_ch_class_get = 0x4004f410 ); +PROVIDE ( r_lm_afh_master_ch_map_get = 0x4004f43c ); +PROVIDE ( r_lm_afh_peer_ch_class_set = 0x4004f418 ); +PROVIDE ( r_lm_check_active_sync = 0x40051334 ); +PROVIDE ( r_LM_CheckEdrFeatureRequest = 0x4002f90c ); +PROVIDE ( r_LM_CheckSwitchInstant = 0x4002f8c0 ); +PROVIDE ( r_lm_check_sync_hl_rsp = 0x4005169c ); +PROVIDE ( r_lm_clk_adj_ack_pending_clear = 0x4004f514 ); +PROVIDE ( r_lm_clk_adj_instant_pending_set = 0x4004f4d8 ); +PROVIDE ( r_LM_ComputePacketType = 0x4002f554 ); +PROVIDE ( r_LM_ComputeSniffSubRate = 0x400233ac ); +PROVIDE ( r_lm_debug_key_compare_192 = 0x4004f3a8 ); +PROVIDE ( r_lm_debug_key_compare_256 = 0x4004f3d0 ); +PROVIDE ( r_lm_dhkey_calc_init = 0x40013234 ); +PROVIDE ( r_lm_dhkey_compare = 0x400132d8 ); +PROVIDE ( r_lm_dut_mode_en_get = 0x4004f3ec ); +PROVIDE ( r_LM_ExtractMaxEncKeySize = 0x4001aca4 ); +PROVIDE ( r_lm_f1 = 0x40012bb8 ); +PROVIDE ( r_lm_f2 = 0x40012cfc ); +PROVIDE ( r_lm_f3 = 0x40013050 ); +PROVIDE ( r_lm_g = 0x40012f90 ); +PROVIDE ( r_LM_GetAFHSwitchInstant = 0x4002f86c ); +PROVIDE ( r_lm_get_auth_en = 0x4004f1ac ); +PROVIDE ( r_lm_get_common_pkt_types = 0x4002fa1c ); +PROVIDE ( r_LM_GetConnectionAcceptTimeout = 0x4004f1f4 ); +PROVIDE ( r_LM_GetFeature = 0x4002f924 ); +PROVIDE ( r_LM_GetLinkTimeout = 0x400233ec ); +PROVIDE ( r_LM_GetLocalNameSeg = 0x4004f200 ); +PROVIDE ( r_lm_get_loopback_mode = 0x4004f248 ); +PROVIDE ( r_LM_GetMasterEncKeySize = 0x4001b29c ); +PROVIDE ( r_LM_GetMasterEncRand = 0x4001b288 ); +PROVIDE ( r_LM_GetMasterKey = 0x4001b260 ); +PROVIDE ( r_LM_GetMasterKeyRand = 0x4001b274 ); +PROVIDE ( r_lm_get_min_sync_intv = 0x400517a8 ); +PROVIDE ( r_lm_get_nb_acl = 0x4004ef9c ); +PROVIDE ( r_lm_get_nb_sync_link = 0x4005179c ); +PROVIDE ( r_lm_get_nonce = 0x400131c4 ); +PROVIDE ( r_lm_get_oob_local_commit = 0x4004f374 ); +PROVIDE ( r_lm_get_oob_local_data_192 = 0x4004f2d4 ); +PROVIDE ( r_lm_get_oob_local_data_256 = 0x4004f318 ); +PROVIDE ( r_LM_GetPINType = 0x4004f1e8 ); +PROVIDE ( r_lm_get_priv_key_192 = 0x4004f278 ); +PROVIDE ( r_lm_get_priv_key_256 = 0x4004f2b8 ); +PROVIDE ( r_lm_get_pub_key_192 = 0x4004f258 ); +PROVIDE ( r_lm_get_pub_key_256 = 0x4004f298 ); +PROVIDE ( r_LM_GetQoSParam = 0x4002f6e0 ); +PROVIDE ( r_lm_get_sec_con_host_supp = 0x4004f1d4 ); +PROVIDE ( r_LM_GetSniffSubratingParam = 0x4002325c ); +PROVIDE ( r_lm_get_sp_en = 0x4004f1c0 ); +PROVIDE ( r_LM_GetSwitchInstant = 0x4002f7f8 ); +PROVIDE ( r_lm_get_synchdl = 0x4005175c ); +PROVIDE ( r_lm_get_sync_param = 0x400503b4 ); +PROVIDE ( r_lm_init = 0x4004ed34 ); +PROVIDE ( r_lm_init_sync = 0x400512d8 ); +PROVIDE ( r_lm_is_acl_con = 0x4004f47c ); +PROVIDE ( r_lm_is_acl_con_role = 0x4004f49c ); +PROVIDE ( r_lm_is_clk_adj_ack_pending = 0x4004f4e8 ); +PROVIDE ( r_lm_is_clk_adj_instant_pending = 0x4004f4c8 ); +PROVIDE ( r_lm_local_ext_fr_configured = 0x4004f540 ); +PROVIDE ( r_lm_look_for_stored_link_key = 0x4002f948 ); +PROVIDE ( r_lm_look_for_sync = 0x40051774 ); +PROVIDE ( r_lm_lt_addr_alloc = 0x4004ef1c ); +PROVIDE ( r_lm_lt_addr_free = 0x4004ef74 ); +PROVIDE ( r_lm_lt_addr_reserve = 0x4004ef48 ); +PROVIDE ( r_LM_MakeCof = 0x4002f84c ); +PROVIDE ( r_LM_MakeRandVec = 0x400112d8 ); +PROVIDE ( r_lm_master_clk_adj_req_handler = 0x40054180 ); +PROVIDE ( r_LM_MaxSlot = 0x4002f694 ); +PROVIDE ( r_lm_modif_sync = 0x40051578 ); +PROVIDE ( r_lm_n_is_zero = 0x40012170 ); +PROVIDE ( r_lm_num_clk_adj_ack_pending_set = 0x4004f500 ); +PROVIDE ( r_lm_oob_f1 = 0x40012e54 ); +PROVIDE ( r_lm_pca_sscan_link_get = 0x4004f560 ); +PROVIDE ( r_lm_pca_sscan_link_set = 0x4004f550 ); +PROVIDE ( r_lmp_pack = 0x4001135c ); +PROVIDE ( r_lmp_unpack = 0x4001149c ); +PROVIDE ( r_lm_read_features = 0x4004f0d8 ); +PROVIDE ( r_LM_RemoveSniff = 0x40023124 ); +PROVIDE ( r_LM_RemoveSniffSubrating = 0x400233c4 ); +PROVIDE ( r_lm_remove_sync = 0x400517c8 ); +PROVIDE ( r_lm_reset_sync = 0x40051304 ); +PROVIDE ( r_lm_role_switch_finished = 0x4004f028 ); +PROVIDE ( r_lm_role_switch_start = 0x4004efe0 ); +PROVIDE ( r_lm_sco_nego_end = 0x40051828 ); +PROVIDE ( r_LM_SniffSubrateNegoRequired = 0x40023334 ); +PROVIDE ( r_LM_SniffSubratingHlReq = 0x40023154 ); +PROVIDE ( r_LM_SniffSubratingPeerReq = 0x400231dc ); +PROVIDE ( r_lm_sp_debug_mode_get = 0x4004f398 ); +PROVIDE ( r_lm_sp_n192_convert_wnaf = 0x400123c0 ); +PROVIDE ( r_lm_sp_n_one = 0x400123a4 ); +PROVIDE ( r_lm_sp_p192_add = 0x40012828 ); +PROVIDE ( r_lm_sp_p192_dbl = 0x4001268c ); +PROVIDE ( r_lm_sp_p192_invert = 0x40012b6c ); +PROVIDE ( r_lm_sp_p192_point_jacobian_to_affine = 0x40012468 ); +PROVIDE ( r_lm_sp_p192_points_jacobian_to_affine = 0x400124e4 ); +PROVIDE ( r_lm_sp_p192_point_to_inf = 0x40012458 ); +PROVIDE ( r_lm_sp_pre_compute_points = 0x40012640 ); +PROVIDE ( r_lm_sp_sha256_calculate = 0x400121a0 ); +PROVIDE ( r_LM_SuppressAclPacket = 0x4002f658 ); +PROVIDE ( r_lm_sync_flow_ctrl_en_get = 0x4004f404 ); +PROVIDE ( r_LM_UpdateAclEdrPacketType = 0x4002f5d8 ); +PROVIDE ( r_LM_UpdateAclPacketType = 0x4002f584 ); +PROVIDE ( r_modules_funcs = 0x3ffafd6c ); +PROVIDE ( r_modules_funcs_p = 0x3ffafd68 ); +PROVIDE ( r_nvds_del = 0x400544c4 ); +PROVIDE ( r_nvds_get = 0x40054488 ); +PROVIDE ( r_nvds_init = 0x40054410 ); +PROVIDE ( r_nvds_lock = 0x400544fc ); +PROVIDE ( r_nvds_put = 0x40054534 ); +PROVIDE ( rom_abs_temp = 0x400054f0 ); +PROVIDE ( rom_bb_bss_bw_40_en = 0x4000401c ); +PROVIDE ( rom_bb_bss_cbw40_dig = 0x40003bac ); +PROVIDE ( rom_bb_rx_ht20_cen_bcov_en = 0x40003734 ); +PROVIDE ( rom_bb_tx_ht20_cen = 0x40003760 ); +PROVIDE ( rom_bb_wdg_test_en = 0x40003b70 ); +PROVIDE ( rom_cbw2040_cfg = 0x400040b0 ); +PROVIDE ( rom_check_noise_floor = 0x40003c78 ); +PROVIDE ( rom_chip_i2c_readReg = 0x40004110 ); +PROVIDE ( rom_chip_i2c_writeReg = 0x40004168 ); +PROVIDE ( rom_chip_v7_bt_init = 0x40004d8c ); +PROVIDE ( rom_chip_v7_rx_init = 0x40004cec ); +PROVIDE ( rom_chip_v7_rx_rifs_en = 0x40003d90 ); +PROVIDE ( rom_chip_v7_tx_init = 0x40004d18 ); +PROVIDE ( rom_clk_force_on_vit = 0x40003710 ); +PROVIDE ( rom_correct_rf_ana_gain = 0x400062a8 ); +PROVIDE ( rom_dc_iq_est = 0x400055c8 ); +PROVIDE ( rom_disable_agc = 0x40002fa4 ); +PROVIDE ( rom_enable_agc = 0x40002fcc ); +PROVIDE ( rom_en_pwdet = 0x4000506c ); +PROVIDE ( rom_gen_rx_gain_table = 0x40003e3c ); +PROVIDE ( rom_get_data_sat = 0x4000312c ); +PROVIDE ( rom_get_fm_sar_dout = 0x40005204 ); +PROVIDE ( rom_get_power_db = 0x40005fc8 ); +PROVIDE ( rom_get_pwctrl_correct = 0x400065d4 ); +PROVIDE ( rom_get_rfcal_rxiq_data = 0x40005bbc ); +PROVIDE ( rom_get_rf_gain_qdb = 0x40006290 ); +PROVIDE ( rom_get_sar_dout = 0x40006564 ); +PROVIDE ( rom_i2c_readReg = 0x40004148 ); +PROVIDE ( rom_i2c_readReg_Mask = 0x400041c0 ); +PROVIDE ( rom_i2c_writeReg = 0x400041a4 ); +PROVIDE ( rom_i2c_writeReg_Mask = 0x400041fc ); +PROVIDE ( rom_index_to_txbbgain = 0x40004df8 ); +PROVIDE ( rom_iq_est_disable = 0x40005590 ); +PROVIDE ( rom_iq_est_enable = 0x40005514 ); +PROVIDE ( rom_linear_to_db = 0x40005f64 ); +PROVIDE ( rom_loopback_mode_en = 0x400030f8 ); +PROVIDE ( rom_main = 0x400076c4 ); +PROVIDE ( rom_meas_tone_pwr_db = 0x40006004 ); +PROVIDE ( rom_mhz2ieee = 0x4000404c ); +PROVIDE ( rom_noise_floor_auto_set = 0x40003bdc ); +PROVIDE ( rom_pbus_debugmode = 0x40004458 ); +PROVIDE ( rom_pbus_force_mode = 0x40004270 ); +PROVIDE ( rom_pbus_force_test = 0x400043c0 ); +PROVIDE ( rom_pbus_rd = 0x40004414 ); +PROVIDE ( rom_pbus_rd_addr = 0x40004334 ); +PROVIDE ( rom_pbus_rd_shift = 0x40004374 ); +PROVIDE ( rom_pbus_rx_dco_cal = 0x40005620 ); +PROVIDE ( rom_pbus_set_dco = 0x40004638 ); +PROVIDE ( rom_pbus_set_rxgain = 0x40004480 ); +PROVIDE ( rom_pbus_workmode = 0x4000446c ); +PROVIDE ( rom_pbus_xpd_rx_off = 0x40004508 ); +PROVIDE ( rom_pbus_xpd_rx_on = 0x4000453c ); +PROVIDE ( rom_pbus_xpd_tx_off = 0x40004590 ); +PROVIDE ( rom_pbus_xpd_tx_on = 0x400045e0 ); +PROVIDE ( rom_phy_disable_agc = 0x40002f6c ); +PROVIDE ( rom_phy_disable_cca = 0x40003000 ); +PROVIDE ( rom_phy_enable_agc = 0x40002f88 ); +PROVIDE ( rom_phy_enable_cca = 0x4000302c ); +PROVIDE ( rom_phy_freq_correct = 0x40004b44 ); +PROVIDE ( rom_phyFuns = 0x3ffae0c0 ); +PROVIDE ( rom_phy_get_noisefloor = 0x40003c2c ); +PROVIDE ( rom_phy_get_vdd33 = 0x4000642c ); +PROVIDE ( rom_pow_usr = 0x40003044 ); +PROVIDE ( rom_read_sar_dout = 0x400051c0 ); +PROVIDE ( rom_restart_cal = 0x400046e0 ); +PROVIDE ( rom_rfcal_pwrctrl = 0x40006058 ); +PROVIDE ( rom_rfcal_rxiq = 0x40005b4c ); +PROVIDE ( rom_rfcal_txcap = 0x40005dec ); +PROVIDE ( rom_rfpll_reset = 0x40004680 ); +PROVIDE ( rom_rfpll_set_freq = 0x400047f8 ); +PROVIDE ( rom_rtc_mem_backup = 0x40003db4 ); +PROVIDE ( rom_rtc_mem_recovery = 0x40003df4 ); +PROVIDE ( rom_rx_gain_force = 0x4000351c ); +PROVIDE ( rom_rxiq_cover_mg_mp = 0x40005a68 ); +PROVIDE ( rom_rxiq_get_mis = 0x400058e4 ); +PROVIDE ( rom_rxiq_set_reg = 0x40005a00 ); +PROVIDE ( rom_set_cal_rxdc = 0x400030b8 ); +PROVIDE ( rom_set_chan_cal_interp = 0x40005ce0 ); +PROVIDE ( rom_set_channel_freq = 0x40004880 ); +PROVIDE ( rom_set_loopback_gain = 0x40003060 ); +PROVIDE ( rom_set_noise_floor = 0x40003d48 ); +PROVIDE ( rom_set_pbus_mem = 0x400031a4 ); +PROVIDE ( rom_set_rf_freq_offset = 0x40004ca8 ); +PROVIDE ( rom_set_rxclk_en = 0x40003594 ); +PROVIDE ( rom_set_txcap_reg = 0x40005d50 ); +PROVIDE ( rom_set_txclk_en = 0x40003564 ); +PROVIDE ( rom_spur_coef_cfg = 0x40003ac8 ); +PROVIDE ( rom_spur_reg_write_one_tone = 0x400037f0 ); +PROVIDE ( rom_start_tx_tone = 0x400036b4 ); +PROVIDE ( rom_start_tx_tone_step = 0x400035d0 ); +PROVIDE ( rom_stop_tx_tone = 0x40003f98 ); +PROVIDE ( _rom_store = 0x4000d66c ); +PROVIDE ( _rom_store_table = 0x4000d4f8 ); +PROVIDE ( rom_target_power_add_backoff = 0x40006268 ); +PROVIDE ( rom_tx_atten_set_interp = 0x400061cc ); +PROVIDE ( rom_txbbgain_to_index = 0x40004dc0 ); +PROVIDE ( rom_txcal_work_mode = 0x4000510c ); +PROVIDE ( rom_txdc_cal_init = 0x40004e10 ); +PROVIDE ( rom_txdc_cal_v70 = 0x40004ea4 ); +PROVIDE ( rom_txiq_cover = 0x4000538c ); +PROVIDE ( rom_txiq_get_mis_pwr = 0x400052dc ); +PROVIDE ( rom_txiq_set_reg = 0x40005154 ); +PROVIDE ( rom_tx_pwctrl_bg_init = 0x4000662c ); +PROVIDE ( rom_txtone_linear_pwr = 0x40005290 ); +PROVIDE ( rom_wait_rfpll_cal_end = 0x400047a8 ); +PROVIDE ( rom_write_gain_mem = 0x4000348c ); +PROVIDE ( rom_write_rfpll_sdm = 0x40004740 ); +PROVIDE ( roundup2 = 0x4000ab7c ); +PROVIDE ( r_plf_funcs_p = 0x3ffb8360 ); +PROVIDE ( r_rf_rw_bt_init = 0x40054868 ); +PROVIDE ( r_rf_rw_init = 0x40054b0c ); +PROVIDE ( r_rf_rw_le_init = 0x400549d0 ); +PROVIDE ( r_rwble_activity_ongoing_check = 0x40054d8c ); +PROVIDE ( r_rwble_init = 0x40054bf4 ); +PROVIDE ( r_rwble_isr = 0x40054e08 ); +PROVIDE ( r_rwble_reset = 0x40054ce8 ); +PROVIDE ( r_rwble_sleep_check = 0x40054d78 ); +PROVIDE ( r_rwble_version = 0x40054dac ); +PROVIDE ( r_rwbt_init = 0x40055160 ); +PROVIDE ( r_rwbt_isr = 0x40055248 ); +PROVIDE ( r_rwbt_reset = 0x400551bc ); +PROVIDE ( r_rwbt_sleep_check = 0x4005577c ); +PROVIDE ( r_rwbt_sleep_enter = 0x400557a4 ); +PROVIDE ( r_rwbt_sleep_wakeup = 0x400557fc ); +PROVIDE ( r_rwbt_sleep_wakeup_end = 0x400558cc ); +PROVIDE ( r_rwbt_version = 0x4005520c ); +PROVIDE ( r_rwip_assert_err = 0x40055f88 ); +PROVIDE ( r_rwip_check_wakeup_boundary = 0x400558fc ); +PROVIDE ( r_rwip_ext_wakeup_enable = 0x40055f3c ); +PROVIDE ( r_rwip_init = 0x4005595c ); +PROVIDE ( r_rwip_pca_clock_dragging_only = 0x40055f48 ); +PROVIDE ( r_rwip_prevent_sleep_clear = 0x40055ec8 ); +PROVIDE ( r_rwip_prevent_sleep_set = 0x40055e64 ); +PROVIDE ( r_rwip_reset = 0x40055ab8 ); +PROVIDE ( r_rwip_schedule = 0x40055b38 ); +PROVIDE ( r_rwip_sleep = 0x40055b5c ); +PROVIDE ( r_rwip_sleep_enable = 0x40055f30 ); +PROVIDE ( r_rwip_version = 0x40055b20 ); +PROVIDE ( r_rwip_wakeup = 0x40055dc4 ); +PROVIDE ( r_rwip_wakeup_delay_set = 0x40055e4c ); +PROVIDE ( r_rwip_wakeup_end = 0x40055e18 ); +PROVIDE ( r_rwip_wlcoex_set = 0x40055f60 ); +PROVIDE ( r_SHA_256 = 0x40013a90 ); +PROVIDE ( rtc_boot_control = 0x4000821c ); +PROVIDE ( rtc_get_reset_reason = 0x400081d4 ); +PROVIDE ( rtc_get_wakeup_cause = 0x400081f4 ); +PROVIDE ( rtc_select_apb_bridge = 0x40008288 ); +PROVIDE ( rwip_coex_cfg = 0x3ff9914c ); +PROVIDE ( rwip_priority = 0x3ff99159 ); +PROVIDE ( rwip_rf = 0x3ffbdb28 ); +PROVIDE ( rwip_rf_p_get = 0x400558f4 ); +PROVIDE ( r_XorKey = 0x400112c0 ); +PROVIDE ( sbrk = 0x400017f4 ); +PROVIDE ( _sbrk_r = 0x4000bce4 ); +PROVIDE ( __sccl = 0x4000c498 ); +PROVIDE ( __sclose = 0x400011b8 ); +PROVIDE ( SelectSpiFunction = 0x40061f84 ); +PROVIDE ( SelectSpiQIO = 0x40061ddc ); +PROVIDE ( SendMsg = 0x40009384 ); +PROVIDE ( send_packet = 0x40009340 ); +PROVIDE ( __seofread = 0x40001148 ); +PROVIDE ( setjmp = 0x40056268 ); +PROVIDE ( setlocale = 0x40059568 ); +PROVIDE ( _setlocale_r = 0x4005950c ); +PROVIDE ( set_rtc_memory_crc = 0x40008208 ); +PROVIDE ( SetSpiDrvs = 0x40061e78 ); +PROVIDE ( __sf_fake_stderr = 0x3ff96458 ); +PROVIDE ( __sf_fake_stdin = 0x3ff96498 ); +PROVIDE ( __sf_fake_stdout = 0x3ff96478 ); +PROVIDE ( __sflush_r = 0x400591e0 ); +PROVIDE ( __sfmoreglue = 0x40001dc8 ); +PROVIDE ( __sfp = 0x40001e90 ); +PROVIDE ( __sfp_lock_acquire = 0x40001e08 ); +PROVIDE ( __sfp_lock_release = 0x40001e14 ); +PROVIDE ( __sfputs_r = 0x40057790 ); +PROVIDE ( __sfvwrite_r = 0x4005893c ); +PROVIDE ( sha1_prf = 0x40060ae8 ); +PROVIDE ( sha1_vector = 0x40060b64 ); +PROVIDE ( sha256_prf = 0x40060d70 ); +PROVIDE ( sha256_vector = 0x40060e08 ); +PROVIDE ( sha_blk_bits = 0x3ff99290 ); +PROVIDE ( sha_blk_bits_bytes = 0x3ff99288 ); +PROVIDE ( sha_blk_hash_bytes = 0x3ff9928c ); +PROVIDE ( sig_matrix = 0x3ffae293 ); +PROVIDE ( __sinit = 0x40001e38 ); +PROVIDE ( __sinit_lock_acquire = 0x40001e20 ); +PROVIDE ( __sinit_lock_release = 0x40001e2c ); +PROVIDE ( sip_after_tx_complete = 0x4000b358 ); +PROVIDE ( sip_alloc_to_host_evt = 0x4000ab9c ); +PROVIDE ( sip_get_ptr = 0x4000b34c ); +PROVIDE ( sip_get_state = 0x4000ae2c ); +PROVIDE ( sip_init_attach = 0x4000ae58 ); +PROVIDE ( sip_install_rx_ctrl_cb = 0x4000ae10 ); +PROVIDE ( sip_install_rx_data_cb = 0x4000ae20 ); +PROVIDE ( sip_is_active = 0x4000b3c0 ); +PROVIDE ( sip_post_init = 0x4000aed8 ); +PROVIDE ( sip_reclaim_from_host_cmd = 0x4000adbc ); +PROVIDE ( sip_reclaim_tx_data_pkt = 0x4000ad5c ); +PROVIDE ( sip_send = 0x4000af54 ); +PROVIDE ( sip_to_host_chain_append = 0x4000aef8 ); +PROVIDE ( sip_to_host_evt_send_done = 0x4000ac04 ); +PROVIDE ( slc_add_credits = 0x4000baf4 ); +PROVIDE ( slc_enable = 0x4000b64c ); +PROVIDE ( slc_from_host_chain_fetch = 0x4000b7e8 ); +PROVIDE ( slc_from_host_chain_recycle = 0x4000bb10 ); +PROVIDE ( slc_has_pkt_to_host = 0x4000b5fc ); +PROVIDE ( slc_init_attach = 0x4000b918 ); +PROVIDE ( slc_init_credit = 0x4000badc ); +PROVIDE ( slc_reattach = 0x4000b62c ); +PROVIDE ( slc_send_to_host_chain = 0x4000b6a0 ); +PROVIDE ( slc_set_host_io_max_window = 0x4000b89c ); +PROVIDE ( slc_to_host_chain_recycle = 0x4000b758 ); +PROVIDE ( __smakebuf_r = 0x40059108 ); +PROVIDE ( software_reset = 0x4000824c ); +PROVIDE ( software_reset_cpu = 0x40008264 ); +PROVIDE ( specialModP256 = 0x4001600c ); +PROVIDE ( spi_cache_sram_init = 0x400626e4 ); +PROVIDE ( SPIClkConfig = 0x40062bc8 ); +PROVIDE ( SPI_Common_Command = 0x4006246c ); +PROVIDE ( spi_dummy_len_fix = 0x40061d90 ); +PROVIDE ( SPI_Encrypt_Write = 0x40062e78 ); +PROVIDE ( SPIEraseArea = 0x400631ac ); +PROVIDE ( SPIEraseBlock = 0x40062c4c ); +PROVIDE ( SPIEraseChip = 0x40062c14 ); +PROVIDE ( SPIEraseSector = 0x40062ccc ); +PROVIDE ( spi_flash_attach = 0x40062a6c ); +/* NB: SPIUnlock @ 0x400628b0 has been replaced with an updated + version in the "spi_flash" component */ +PROVIDE ( SPILock = 0x400628f0 ); +PROVIDE ( SPIMasterReadModeCnfig = 0x40062b64 ); +PROVIDE ( spi_modes = 0x3ff99270 ); +PROVIDE ( SPIParamCfg = 0x40063238 ); +PROVIDE ( SPI_Prepare_Encrypt_Data = 0x40062e1c ); +PROVIDE ( SPIRead = 0x40062ed8 ); +PROVIDE ( SPIReadModeCnfig = 0x40062944 ); +/* This is static function, but can be used, not generated by script*/ +PROVIDE ( SPI_read_status = 0x4006226c ); +/* This is static function, but can be used, not generated by script*/ +PROVIDE ( SPI_read_status_high = 0x40062448 ); +PROVIDE ( SPI_user_command_read = 0x400621b0 ); +PROVIDE ( SPI_flashchip_data = 0x3ffae270 ); +PROVIDE ( SPIWrite = 0x40062d50 ); +/* This is static function, but can be used, not generated by script*/ +PROVIDE ( SPI_write_enable = 0x40062320 ); +PROVIDE ( SPI_Write_Encrypt_Disable = 0x40062e60 ); +PROVIDE ( SPI_Write_Encrypt_Enable = 0x40062df4 ); +/* This is static function, but can be used, not generated by script*/ +PROVIDE ( SPI_write_status = 0x400622f0 ); +PROVIDE ( srand = 0x40001004 ); +PROVIDE ( __sread = 0x40001118 ); +PROVIDE ( __srefill_r = 0x400593d4 ); +PROVIDE ( __sseek = 0x40001184 ); +PROVIDE ( __ssprint_r = 0x40056ff8 ); +PROVIDE ( __ssputs_r = 0x40056f2c ); +PROVIDE ( __ssrefill_r = 0x40057fec ); +PROVIDE ( __stack = 0x3ffe3f20 ); +PROVIDE ( __stack_app = 0x3ffe7e30 ); +PROVIDE ( _stack_sentry = 0x3ffe1320 ); +PROVIDE ( _stack_sentry_app = 0x3ffe5230 ); +PROVIDE ( _start = 0x40000704 ); +PROVIDE ( start_tb_console = 0x4005a980 ); +PROVIDE ( _stat_r = 0x4000bcb4 ); +PROVIDE ( _stext = 0x40000560 ); +PROVIDE ( strcasecmp = 0x400011cc ); +PROVIDE ( strcasestr = 0x40001210 ); +PROVIDE ( strcat = 0x4000c518 ); +PROVIDE ( strchr = 0x4000c53c ); +PROVIDE ( strcmp = 0x40001274 ); +PROVIDE ( strcoll = 0x40001398 ); +PROVIDE ( strcpy = 0x400013ac ); +PROVIDE ( strcspn = 0x4000c558 ); +PROVIDE ( strdup = 0x4000143c ); +PROVIDE ( _strdup_r = 0x40001450 ); +PROVIDE ( strftime = 0x40059ab4 ); +PROVIDE ( strlcat = 0x40001470 ); +PROVIDE ( strlcpy = 0x4000c584 ); +PROVIDE ( strlen = 0x400014c0 ); +PROVIDE ( strlwr = 0x40001524 ); +PROVIDE ( strncasecmp = 0x40001550 ); +PROVIDE ( strncat = 0x4000c5c4 ); +PROVIDE ( strncmp = 0x4000c5f4 ); +PROVIDE ( strncpy = 0x400015d4 ); +PROVIDE ( strndup = 0x400016b0 ); +PROVIDE ( _strndup_r = 0x400016c4 ); +PROVIDE ( strnlen = 0x4000c628 ); +PROVIDE ( strrchr = 0x40001708 ); +PROVIDE ( strsep = 0x40001734 ); +PROVIDE ( strspn = 0x4000c648 ); +PROVIDE ( strstr = 0x4000c674 ); +PROVIDE ( __strtok_r = 0x4000c6a8 ); +PROVIDE ( strtok_r = 0x4000c70c ); +PROVIDE ( strtol = 0x4005681c ); +PROVIDE ( _strtol_r = 0x40056714 ); +PROVIDE ( strtoul = 0x4005692c ); +PROVIDE ( _strtoul_r = 0x40056834 ); +PROVIDE ( strupr = 0x4000174c ); +PROVIDE ( __subdf3 = 0x400026e4 ); +PROVIDE ( __submore = 0x40058f3c ); +PROVIDE ( __subsf3 = 0x400021d0 ); +PROVIDE ( SubtractBigHex256 = 0x40015bcc ); +PROVIDE ( SubtractBigHexMod256 = 0x40015e8c ); +PROVIDE ( SubtractBigHexUint32_256 = 0x40015f8c ); +PROVIDE ( SubtractFromSelfBigHex256 = 0x40015c20 ); +PROVIDE ( SubtractFromSelfBigHexSign256 = 0x40015dc8 ); +PROVIDE ( __subvdi3 = 0x40002d20 ); +PROVIDE ( __subvsi3 = 0x40002cf8 ); +PROVIDE ( _sungetc_r = 0x40057f6c ); +PROVIDE ( __swbuf = 0x40058cb4 ); +PROVIDE ( __swbuf_r = 0x40058bec ); +PROVIDE ( __swrite = 0x40001150 ); +PROVIDE ( __swsetup_r = 0x40058cc8 ); +PROVIDE ( sw_to_hw = 0x3ffb8d40 ); +PROVIDE ( _SyscallException = 0x400007cf ); +PROVIDE ( syscall_table_ptr_app = 0x3ffae020 ); +PROVIDE ( syscall_table_ptr_pro = 0x3ffae024 ); +PROVIDE ( _system_r = 0x4000bc10 ); +PROVIDE ( tdefl_compress = 0x400600bc ); +PROVIDE ( tdefl_compress_buffer = 0x400607f4 ); +PROVIDE ( tdefl_compress_mem_to_mem = 0x40060900 ); +PROVIDE ( tdefl_compress_mem_to_output = 0x400608e0 ); +PROVIDE ( tdefl_get_adler32 = 0x400608d8 ); +PROVIDE ( tdefl_get_prev_return_status = 0x400608d0 ); +PROVIDE ( tdefl_init = 0x40060810 ); +PROVIDE ( tdefl_write_image_to_png_file_in_memory = 0x4006091c ); +PROVIDE ( tdefl_write_image_to_png_file_in_memory_ex = 0x40060910 ); +PROVIDE ( time = 0x40001844 ); +PROVIDE ( __time_load_locale = 0x4000183c ); +PROVIDE ( times = 0x40001808 ); +PROVIDE ( _times_r = 0x4000bc40 ); +PROVIDE ( _timezone = 0x3ffae0a0 ); +PROVIDE ( tinfl_decompress = 0x4005ef30 ); +PROVIDE ( tinfl_decompress_mem_to_callback = 0x40060090 ); +PROVIDE ( tinfl_decompress_mem_to_mem = 0x40060050 ); +PROVIDE ( toascii = 0x4000c720 ); +PROVIDE ( tolower = 0x40001868 ); +PROVIDE ( toupper = 0x40001884 ); +PROVIDE ( __truncdfsf2 = 0x40002b90 ); +PROVIDE ( __tzcalc_limits = 0x400018a0 ); +PROVIDE ( __tz_lock = 0x40001a04 ); +PROVIDE ( _tzname = 0x3ffae030 ); +PROVIDE ( tzset = 0x40001a1c ); +PROVIDE ( _tzset_r = 0x40001a28 ); +PROVIDE ( __tz_unlock = 0x40001a10 ); +PROVIDE ( uartAttach = 0x40008fd0 ); +PROVIDE ( uart_baudrate_detect = 0x40009034 ); +PROVIDE ( uart_buff_switch = 0x400093c0 ); +PROVIDE ( UartConnCheck = 0x40008738 ); +PROVIDE ( UartConnectProc = 0x40008a04 ); +PROVIDE ( UartDev = 0x3ffe019c ); +PROVIDE ( uart_div_modify = 0x400090cc ); +PROVIDE ( UartDwnLdProc = 0x40008ce8 ); +PROVIDE ( UartGetCmdLn = 0x40009564 ); +PROVIDE ( Uart_Init = 0x40009120 ); +PROVIDE ( UartRegReadProc = 0x40008a58 ); +PROVIDE ( UartRegWriteProc = 0x40008a14 ); +PROVIDE ( uart_rx_intr_handler = 0x40008f4c ); +PROVIDE ( uart_rx_one_char = 0x400092d0 ); +PROVIDE ( uart_rx_one_char_block = 0x400092a4 ); +PROVIDE ( uart_rx_readbuff = 0x40009394 ); +PROVIDE ( UartRxString = 0x400092fc ); +PROVIDE ( UartSetBaudProc = 0x40008aac ); +PROVIDE ( UartSpiAttachProc = 0x40008a6c ); +PROVIDE ( UartSpiReadProc = 0x40008a80 ); +PROVIDE ( uart_tx_flush = 0x40009258 ); +/*PROVIDE ( uart_tx_one_char = 0x40009200 );*/ +/* Note: remapping this to uart_tx_one_char2, to keep consistency */ +PROVIDE ( uart_tx_one_char = 0x4000922c ); +PROVIDE ( uart_tx_one_char2 = 0x4000922c ); +PROVIDE ( uart_tx_switch = 0x40009028 ); +PROVIDE ( uart_tx_wait_idle = 0x40009278 ); +PROVIDE ( __ucmpdi2 = 0x40063840 ); +PROVIDE ( __udivdi3 = 0x4000cff8 ); +PROVIDE ( __udivmoddi4 = 0x40064ab0 ); +PROVIDE ( __udivsi3 = 0x4000c7c8 ); +PROVIDE ( __udiv_w_sdiv = 0x40064aa8 ); +PROVIDE ( __umoddi3 = 0x4000d280 ); +PROVIDE ( __umodsi3 = 0x4000c7d0 ); +PROVIDE ( __umulsidi3 = 0x4000c7d8 ); +PROVIDE ( ungetc = 0x400590f4 ); +PROVIDE ( _ungetc_r = 0x40058fa0 ); +PROVIDE ( _unlink_r = 0x4000bc84 ); +PROVIDE ( __unorddf2 = 0x400637f4 ); +PROVIDE ( __unordsf2 = 0x40063478 ); +PROVIDE ( user_code_start = 0x3ffe0400 ); +PROVIDE ( _UserExceptionVector = 0x40000340 ); +PROVIDE ( utoa = 0x40056258 ); +PROVIDE ( __utoa = 0x400561f0 ); +PROVIDE ( VerifyFlashMd5Proc = 0x40008c44 ); +PROVIDE ( veryBigHexP256 = 0x3ff9736c ); +PROVIDE ( wcrtomb = 0x40058920 ); +PROVIDE ( _wcrtomb_r = 0x400588d8 ); +PROVIDE ( __wctomb = 0x3ff96540 ); +PROVIDE ( _wctomb_r = 0x40058f14 ); +PROVIDE ( _WindowOverflow12 = 0x40000100 ); +PROVIDE ( _WindowOverflow4 = 0x40000000 ); +PROVIDE ( _WindowOverflow8 = 0x40000080 ); +PROVIDE ( _WindowUnderflow12 = 0x40000140 ); +PROVIDE ( _WindowUnderflow4 = 0x40000040 ); +PROVIDE ( _WindowUnderflow8 = 0x400000c0 ); +PROVIDE ( write = 0x4000181c ); +PROVIDE ( _write_r = 0x4000bd70 ); +PROVIDE ( xthal_bcopy = 0x4000c098 ); +PROVIDE ( xthal_copy123 = 0x4000c124 ); +PROVIDE ( xthal_get_ccompare = 0x4000c078 ); +PROVIDE ( xthal_get_ccount = 0x4000c050 ); +PROVIDE ( xthal_get_interrupt = 0x4000c1e4 ); +PROVIDE ( xthal_get_intread = 0x4000c1e4 ); +PROVIDE ( Xthal_intlevel = 0x3ff9c2b4 ); +PROVIDE ( xthal_memcpy = 0x4000c0bc ); +PROVIDE ( xthal_set_ccompare = 0x4000c058 ); +PROVIDE ( xthal_set_intclear = 0x4000c1ec ); +PROVIDE ( _xtos_set_intlevel = 0x4000bfdc ); +PROVIDE ( _xtos_alloca_handler = 0x40000010 ); +PROVIDE ( _xtos_cause3_handler = 0x40000dd8 ); +PROVIDE ( _xtos_c_handler_table = 0x3ffe0548 ); +PROVIDE ( _xtos_c_wrapper_handler = 0x40000de8 ); +PROVIDE ( _xtos_enabled = 0x3ffe0650 ); +PROVIDE ( _xtos_exc_handler_table = 0x3ffe0448 ); +PROVIDE ( _xtos_interrupt_mask_table = 0x3ffe0758 ); +PROVIDE ( _xtos_interrupt_table = 0x3ffe0658 ); +PROVIDE ( _xtos_ints_off = 0x4000bfac ); +PROVIDE ( _xtos_ints_on = 0x4000bf88 ); +PROVIDE ( _xtos_intstruct = 0x3ffe0650 ); +PROVIDE ( _xtos_l1int_handler = 0x40000814 ); +PROVIDE ( _xtos_p_none = 0x4000bfd4 ); +PROVIDE ( _xtos_restore_intlevel = 0x40000928 ); +PROVIDE ( _xtos_return_from_exc = 0x4000c034 ); +PROVIDE ( _xtos_set_exception_handler = 0x4000074c ); +PROVIDE ( _xtos_set_interrupt_handler = 0x4000bf78 ); +PROVIDE ( _xtos_set_interrupt_handler_arg = 0x4000bf34 ); +PROVIDE ( _xtos_set_min_intlevel = 0x4000bff8 ); +PROVIDE ( _xtos_set_vpri = 0x40000934 ); +PROVIDE ( _xtos_syscall_handler = 0x40000790 ); +PROVIDE ( _xtos_unhandled_exception = 0x4000c024 ); +PROVIDE ( _xtos_unhandled_interrupt = 0x4000c01c ); +PROVIDE ( _xtos_vpri_enabled = 0x3ffe0654 ); +PROVIDE ( ets_intr_count = 0x3ffe03fc ); +PROVIDE ( ets_intr_lock = 0x400067b0 ); +PROVIDE ( ets_intr_unlock = 0x400067c4 ); +PROVIDE ( ets_isr_attach = 0x400067ec ); +PROVIDE ( ets_isr_mask = 0x400067fc ); +PROVIDE ( ets_isr_unmask = 0x40006808 ); +/* Following are static data, but can be used, not generated by script <<<<< btdm data */ +PROVIDE ( ld_acl_env = 0x3ffb8258 ); +PROVIDE ( ld_active_ch_map = 0x3ffb8334 ); +PROVIDE ( ld_bcst_acl_env = 0x3ffb8274 ); +PROVIDE ( ld_csb_rx_env = 0x3ffb8278 ); +PROVIDE ( ld_csb_tx_env = 0x3ffb827c ); +PROVIDE ( ld_env = 0x3ffb9510 ); +PROVIDE ( ld_fm_env = 0x3ffb8284 ); +PROVIDE ( ld_inq_env = 0x3ffb82e4 ); +PROVIDE ( ld_iscan_env = 0x3ffb82e8 ); +PROVIDE ( ld_page_env = 0x3ffb82f0 ); +PROVIDE ( ld_pca_env = 0x3ffb82f4 ); +PROVIDE ( ld_pscan_env = 0x3ffb8308 ); +PROVIDE ( ld_sched_env = 0x3ffb830c ); +PROVIDE ( ld_sched_params = 0x3ffb96c0 ); +PROVIDE ( ld_sco_env = 0x3ffb824c ); +PROVIDE ( ld_sscan_env = 0x3ffb832c ); +PROVIDE ( ld_strain_env = 0x3ffb8330 ); +/* Above are static data, but can be used, not generated by script >>>>> btdm data */ diff --git a/flasher_stub/rom_8266.ld b/flasher_stub/rom_8266.ld new file mode 100644 index 0000000..9269fa2 --- /dev/null +++ b/flasher_stub/rom_8266.ld @@ -0,0 +1,351 @@ +PROVIDE ( Cache_Read_Disable = 0x400047f0 ); +PROVIDE ( Cache_Read_Enable = 0x40004678 ); +PROVIDE ( FilePacketSendReqMsgProc = 0x400035a0 ); +PROVIDE ( FlashDwnLdParamCfgMsgProc = 0x4000368c ); +PROVIDE ( FlashDwnLdStartMsgProc = 0x40003538 ); +PROVIDE ( FlashDwnLdStopReqMsgProc = 0x40003658 ); +PROVIDE ( GetUartDevice = 0x40003f4c ); +PROVIDE ( MD5Final = 0x40009900 ); +PROVIDE ( MD5Init = 0x40009818 ); +PROVIDE ( MD5Update = 0x40009834 ); +PROVIDE ( MemDwnLdStartMsgProc = 0x400036c4 ); +PROVIDE ( MemDwnLdStopReqMsgProc = 0x4000377c ); +PROVIDE ( MemPacketSendReqMsgProc = 0x400036f0 ); +PROVIDE ( RcvMsg = 0x40003eac ); +PROVIDE ( SHA1Final = 0x4000b648 ); +PROVIDE ( SHA1Init = 0x4000b584 ); +PROVIDE ( SHA1Transform = 0x4000a364 ); +PROVIDE ( SHA1Update = 0x4000b5a8 ); +PROVIDE ( SPI_read_status = 0x400043c8 ); +PROVIDE ( SPI_write_status = 0x40004400 ); +PROVIDE ( SPI_write_enable = 0x4000443c ); +PROVIDE ( Wait_SPI_Idle = 0x4000448c ); +PROVIDE ( Enable_QMode = 0x400044c0 ); +PROVIDE ( SPIEraseArea = 0x40004b44 ); +PROVIDE ( SPIEraseBlock = 0x400049b4 ); +PROVIDE ( SPIEraseChip = 0x40004984 ); +PROVIDE ( SPIEraseSector = 0x40004a00 ); +PROVIDE ( SPILock = 0x400048a8 ); +PROVIDE ( SPIParamCfg = 0x40004c2c ); +PROVIDE ( SPIRead = 0x40004b1c ); +PROVIDE ( SPIReadModeCnfig = 0x400048ec ); +PROVIDE ( SPIUnlock = 0x40004878 ); +PROVIDE ( SPIWrite = 0x40004a4c ); +PROVIDE ( SelectSpiFunction = 0x40003f58 ); +PROVIDE ( SendMsg = 0x40003cf4 ); +PROVIDE ( UartConnCheck = 0x40003230 ); +PROVIDE ( UartConnectProc = 0x400037a0 ); +PROVIDE ( UartDwnLdProc = 0x40003368 ); +PROVIDE ( UartGetCmdLn = 0x40003ef4 ); +PROVIDE ( UartRegReadProc = 0x4000381c ); +PROVIDE ( UartRegWriteProc = 0x400037ac ); +PROVIDE ( UartRxString = 0x40003c30 ); +PROVIDE ( Uart_Init = 0x40003a14 ); +PROVIDE ( _DebugExceptionVector = 0x40000010 ); +PROVIDE ( _DoubleExceptionVector = 0x40000070 ); +PROVIDE ( _KernelExceptionVector = 0x40000030 ); +PROVIDE ( _NMIExceptionVector = 0x40000020 ); +PROVIDE ( _ResetHandler = 0x400000a4 ); +PROVIDE ( _ResetVector = 0x40000080 ); +PROVIDE ( _UserExceptionVector = 0x40000050 ); +PROVIDE ( __adddf3 = 0x4000c538 ); +PROVIDE ( __addsf3 = 0x4000c180 ); +PROVIDE ( __divdf3 = 0x4000cb94 ); +PROVIDE ( __divdi3 = 0x4000ce60 ); +PROVIDE ( __divsi3 = 0x4000dc88 ); +PROVIDE ( __extendsfdf2 = 0x4000cdfc ); +PROVIDE ( __fixdfsi = 0x4000ccb8 ); +PROVIDE ( __fixunsdfsi = 0x4000cd00 ); +PROVIDE ( __fixunssfsi = 0x4000c4c4 ); +PROVIDE ( __floatsidf = 0x4000e2f0 ); +PROVIDE ( __floatsisf = 0x4000e2ac ); +PROVIDE ( __floatunsidf = 0x4000e2e8 ); +PROVIDE ( __floatunsisf = 0x4000e2a4 ); +PROVIDE ( __muldf3 = 0x4000c8f0 ); +PROVIDE ( __muldi3 = 0x40000650 ); +PROVIDE ( __mulsf3 = 0x4000c3dc ); +PROVIDE ( __subdf3 = 0x4000c688 ); +PROVIDE ( __subsf3 = 0x4000c268 ); +PROVIDE ( __truncdfsf2 = 0x4000cd5c ); +PROVIDE ( __udivdi3 = 0x4000d310 ); +PROVIDE ( __udivsi3 = 0x4000e21c ); +PROVIDE ( __umoddi3 = 0x4000d770 ); +PROVIDE ( __umodsi3 = 0x4000e268 ); +PROVIDE ( __umulsidi3 = 0x4000dcf0 ); +PROVIDE ( _rom_store = 0x4000e388 ); +PROVIDE ( _rom_store_table = 0x4000e328 ); +PROVIDE ( _start = 0x4000042c ); +PROVIDE ( _xtos_alloca_handler = 0x4000dbe0 ); +PROVIDE ( _xtos_c_wrapper_handler = 0x40000598 ); +PROVIDE ( _xtos_cause3_handler = 0x40000590 ); +PROVIDE ( _xtos_ints_off = 0x4000bda4 ); +PROVIDE ( _xtos_ints_on = 0x4000bd84 ); +PROVIDE ( _xtos_l1int_handler = 0x4000048c ); +PROVIDE ( _xtos_p_none = 0x4000dbf8 ); +PROVIDE ( _xtos_restore_intlevel = 0x4000056c ); +PROVIDE ( _xtos_return_from_exc = 0x4000dc54 ); +PROVIDE ( _xtos_set_exception_handler = 0x40000454 ); +PROVIDE ( _xtos_set_interrupt_handler = 0x4000bd70 ); +PROVIDE ( _xtos_set_interrupt_handler_arg = 0x4000bd28 ); +PROVIDE ( _xtos_set_intlevel = 0x4000dbfc ); +PROVIDE ( _xtos_set_min_intlevel = 0x4000dc18 ); +PROVIDE ( _xtos_set_vpri = 0x40000574 ); +PROVIDE ( _xtos_syscall_handler = 0x4000dbe4 ); +PROVIDE ( _xtos_unhandled_exception = 0x4000dc44 ); +PROVIDE ( _xtos_unhandled_interrupt = 0x4000dc3c ); +PROVIDE ( aes_decrypt = 0x400092d4 ); +PROVIDE ( aes_decrypt_deinit = 0x400092e4 ); +PROVIDE ( aes_decrypt_init = 0x40008ea4 ); +PROVIDE ( aes_unwrap = 0x40009410 ); +PROVIDE ( base64_decode = 0x40009648 ); +PROVIDE ( base64_encode = 0x400094fc ); +PROVIDE ( bzero = 0x4000de84 ); +PROVIDE ( cmd_parse = 0x40000814 ); +PROVIDE ( conv_str_decimal = 0x40000b24 ); +PROVIDE ( conv_str_hex = 0x40000cb8 ); +PROVIDE ( convert_para_str = 0x40000a60 ); +PROVIDE ( dtm_get_intr_mask = 0x400026d0 ); +PROVIDE ( dtm_params_init = 0x4000269c ); +PROVIDE ( dtm_set_intr_mask = 0x400026c8 ); +PROVIDE ( dtm_set_params = 0x400026dc ); +PROVIDE ( eprintf = 0x40001d14 ); +PROVIDE ( eprintf_init_buf = 0x40001cb8 ); +PROVIDE ( eprintf_to_host = 0x40001d48 ); +PROVIDE ( est_get_printf_buf_remain_len = 0x40002494 ); +PROVIDE ( est_reset_printf_buf_len = 0x4000249c ); +PROVIDE ( ets_bzero = 0x40002ae8 ); +PROVIDE ( ets_char2xdigit = 0x40002b74 ); +PROVIDE ( ets_delay_us = 0x40002ecc ); +PROVIDE ( ets_enter_sleep = 0x400027b8 ); +PROVIDE ( ets_external_printf = 0x40002578 ); +PROVIDE ( ets_get_cpu_frequency = 0x40002f0c ); +PROVIDE ( ets_getc = 0x40002bcc ); +PROVIDE ( ets_install_external_printf = 0x40002450 ); +PROVIDE ( ets_install_putc1 = 0x4000242c ); +PROVIDE ( ets_install_putc2 = 0x4000248c ); +PROVIDE ( ets_install_uart_printf = 0x40002438 ); +PROVIDE ( ets_intr_lock = 0x40000f74 ); +PROVIDE ( ets_intr_unlock = 0x40000f80 ); +PROVIDE ( ets_isr_attach = 0x40000f88 ); +PROVIDE ( ets_isr_mask = 0x40000f98 ); +PROVIDE ( ets_isr_unmask = 0x40000fa8 ); +PROVIDE ( ets_memcmp = 0x400018d4 ); +PROVIDE ( ets_memcpy = 0x400018b4 ); +PROVIDE ( ets_memmove = 0x400018c4 ); +PROVIDE ( ets_memset = 0x400018a4 ); +PROVIDE ( ets_post = 0x40000e24 ); +PROVIDE ( ets_printf = 0x400024cc ); +PROVIDE ( ets_putc = 0x40002be8 ); +PROVIDE ( ets_rtc_int_register = 0x40002a40 ); +PROVIDE ( ets_run = 0x40000e04 ); +PROVIDE ( ets_set_idle_cb = 0x40000dc0 ); +PROVIDE ( ets_set_user_start = 0x40000fbc ); +PROVIDE ( ets_str2macaddr = 0x40002af8 ); +PROVIDE ( ets_strcmp = 0x40002aa8 ); +PROVIDE ( ets_strcpy = 0x40002a88 ); +PROVIDE ( ets_strlen = 0x40002ac8 ); +PROVIDE ( ets_strncmp = 0x40002ab8 ); +PROVIDE ( ets_strncpy = 0x40002a98 ); +PROVIDE ( ets_strstr = 0x40002ad8 ); +PROVIDE ( ets_task = 0x40000dd0 ); +PROVIDE ( ets_timer_arm = 0x40002cc4 ); +PROVIDE ( ets_timer_disarm = 0x40002d40 ); +PROVIDE ( ets_timer_done = 0x40002d80 ); +PROVIDE ( ets_timer_handler_isr = 0x40002da8 ); +PROVIDE ( ets_timer_init = 0x40002e68 ); +PROVIDE ( ets_timer_setfn = 0x40002c48 ); +PROVIDE ( ets_uart_printf = 0x40002544 ); +PROVIDE ( ets_update_cpu_frequency = 0x40002f04 ); +PROVIDE ( ets_vprintf = 0x40001f00 ); +PROVIDE ( ets_wdt_disable = 0x400030f0 ); +PROVIDE ( ets_wdt_enable = 0x40002fa0 ); +PROVIDE ( ets_wdt_get_mode = 0x40002f34 ); +PROVIDE ( ets_wdt_init = 0x40003170 ); +PROVIDE ( ets_wdt_restore = 0x40003158 ); +PROVIDE ( ets_write_char = 0x40001da0 ); +PROVIDE ( get_first_seg = 0x4000091c ); +PROVIDE ( gpio_init = 0x40004c50 ); +PROVIDE ( gpio_input_get = 0x40004cf0 ); +PROVIDE ( gpio_intr_ack = 0x40004dcc ); +PROVIDE ( gpio_intr_handler_register = 0x40004e28 ); +PROVIDE ( gpio_intr_pending = 0x40004d88 ); +PROVIDE ( gpio_intr_test = 0x40004efc ); +PROVIDE ( gpio_output_set = 0x40004cd0 ); +PROVIDE ( gpio_pin_intr_state_set = 0x40004d90 ); +PROVIDE ( gpio_pin_wakeup_disable = 0x40004ed4 ); +PROVIDE ( gpio_pin_wakeup_enable = 0x40004e90 ); +PROVIDE ( gpio_register_get = 0x40004d5c ); +PROVIDE ( gpio_register_set = 0x40004d04 ); +PROVIDE ( hmac_md5 = 0x4000a2cc ); +PROVIDE ( hmac_md5_vector = 0x4000a160 ); +PROVIDE ( hmac_sha1 = 0x4000ba28 ); +PROVIDE ( hmac_sha1_vector = 0x4000b8b4 ); +PROVIDE ( lldesc_build_chain = 0x40004f40 ); +PROVIDE ( lldesc_num2link = 0x40005050 ); +PROVIDE ( lldesc_set_owner = 0x4000507c ); +PROVIDE ( main = 0x40000fec ); +PROVIDE ( md5_vector = 0x400097ac ); +PROVIDE ( mem_calloc = 0x40001c2c ); +PROVIDE ( mem_free = 0x400019e0 ); +PROVIDE ( mem_init = 0x40001998 ); +PROVIDE ( mem_malloc = 0x40001b40 ); +PROVIDE ( mem_realloc = 0x40001c6c ); +PROVIDE ( mem_trim = 0x40001a14 ); +PROVIDE ( mem_zalloc = 0x40001c58 ); +PROVIDE ( memcmp = 0x4000dea8 ); +PROVIDE ( memcpy = 0x4000df48 ); +PROVIDE ( memmove = 0x4000e04c ); +PROVIDE ( memset = 0x4000e190 ); +PROVIDE ( multofup = 0x400031c0 ); +PROVIDE ( pbkdf2_sha1 = 0x4000b840 ); +PROVIDE ( phy_get_romfuncs = 0x40006b08 ); +PROVIDE ( rand = 0x40000600 ); +PROVIDE ( rc4_skip = 0x4000dd68 ); +PROVIDE ( recv_packet = 0x40003d08 ); +PROVIDE ( remove_head_space = 0x40000a04 ); +PROVIDE ( rijndaelKeySetupDec = 0x40008dd0 ); +PROVIDE ( rijndaelKeySetupEnc = 0x40009300 ); +PROVIDE ( rom_abs_temp = 0x400060c0 ); +PROVIDE ( rom_ana_inf_gating_en = 0x40006b10 ); +PROVIDE ( rom_cal_tos_v50 = 0x40007a28 ); +PROVIDE ( rom_chip_50_set_channel = 0x40006f84 ); +PROVIDE ( rom_chip_v5_disable_cca = 0x400060d0 ); +PROVIDE ( rom_chip_v5_enable_cca = 0x400060ec ); +PROVIDE ( rom_chip_v5_rx_init = 0x4000711c ); +PROVIDE ( rom_chip_v5_sense_backoff = 0x4000610c ); +PROVIDE ( rom_chip_v5_tx_init = 0x4000718c ); +PROVIDE ( rom_dc_iq_est = 0x4000615c ); +PROVIDE ( rom_en_pwdet = 0x400061b8 ); +PROVIDE ( rom_get_bb_atten = 0x40006238 ); +PROVIDE ( rom_get_corr_power = 0x40006260 ); +PROVIDE ( rom_get_fm_sar_dout = 0x400062dc ); +PROVIDE ( rom_get_noisefloor = 0x40006394 ); +PROVIDE ( rom_get_power_db = 0x400063b0 ); +PROVIDE ( rom_i2c_readReg = 0x40007268 ); +PROVIDE ( rom_i2c_readReg_Mask = 0x4000729c ); +PROVIDE ( rom_i2c_writeReg = 0x400072d8 ); +PROVIDE ( rom_i2c_writeReg_Mask = 0x4000730c ); +PROVIDE ( rom_iq_est_disable = 0x40006400 ); +PROVIDE ( rom_iq_est_enable = 0x40006430 ); +PROVIDE ( rom_linear_to_db = 0x40006484 ); +PROVIDE ( rom_mhz2ieee = 0x400065a4 ); +PROVIDE ( rom_pbus_dco___SA2 = 0x40007bf0 ); +PROVIDE ( rom_pbus_debugmode = 0x4000737c ); +PROVIDE ( rom_pbus_enter_debugmode = 0x40007410 ); +PROVIDE ( rom_pbus_exit_debugmode = 0x40007448 ); +PROVIDE ( rom_pbus_force_test = 0x4000747c ); +PROVIDE ( rom_pbus_rd = 0x400074d8 ); +PROVIDE ( rom_pbus_set_rxgain = 0x4000754c ); +PROVIDE ( rom_pbus_set_txgain = 0x40007610 ); +PROVIDE ( rom_pbus_workmode = 0x40007648 ); +PROVIDE ( rom_pbus_xpd_rx_off = 0x40007688 ); +PROVIDE ( rom_pbus_xpd_rx_on = 0x400076cc ); +PROVIDE ( rom_pbus_xpd_tx_off = 0x400076fc ); +PROVIDE ( rom_pbus_xpd_tx_on = 0x40007740 ); +PROVIDE ( rom_pbus_xpd_tx_on__low_gain = 0x400077a0 ); +PROVIDE ( rom_phy_reset_req = 0x40007804 ); +PROVIDE ( rom_restart_cal = 0x4000781c ); +PROVIDE ( rom_rfcal_pwrctrl = 0x40007eb4 ); +PROVIDE ( rom_rfcal_rxiq = 0x4000804c ); +PROVIDE ( rom_rfcal_rxiq_set_reg = 0x40008264 ); +PROVIDE ( rom_rfcal_txcap = 0x40008388 ); +PROVIDE ( rom_rfcal_txiq = 0x40008610 ); +PROVIDE ( rom_rfcal_txiq_cover = 0x400088b8 ); +PROVIDE ( rom_rfcal_txiq_set_reg = 0x40008a70 ); +PROVIDE ( rom_rfpll_reset = 0x40007868 ); +PROVIDE ( rom_rfpll_set_freq = 0x40007968 ); +PROVIDE ( rom_rxiq_cover_mg_mp = 0x40008b6c ); +PROVIDE ( rom_rxiq_get_mis = 0x40006628 ); +PROVIDE ( rom_sar_init = 0x40006738 ); +PROVIDE ( rom_set_ana_inf_tx_scale = 0x4000678c ); +PROVIDE ( rom_set_channel_freq = 0x40006c50 ); +PROVIDE ( rom_set_loopback_gain = 0x400067c8 ); +PROVIDE ( rom_set_noise_floor = 0x40006830 ); +PROVIDE ( rom_set_rxclk_en = 0x40006550 ); +PROVIDE ( rom_set_txbb_atten = 0x40008c6c ); +PROVIDE ( rom_set_txclk_en = 0x4000650c ); +PROVIDE ( rom_set_txiq_cal = 0x40008d34 ); +PROVIDE ( rom_start_noisefloor = 0x40006874 ); +PROVIDE ( rom_start_tx_tone = 0x400068b4 ); +PROVIDE ( rom_stop_tx_tone = 0x4000698c ); +PROVIDE ( rom_tx_mac_disable = 0x40006a98 ); +PROVIDE ( rom_tx_mac_enable = 0x40006ad4 ); +PROVIDE ( rom_txtone_linear_pwr = 0x40006a1c ); +PROVIDE ( rom_write_rfpll_sdm = 0x400078dc ); +PROVIDE ( roundup2 = 0x400031b4 ); +PROVIDE ( rtc_enter_sleep = 0x40002870 ); +PROVIDE ( rtc_get_reset_reason = 0x400025e0 ); +PROVIDE ( rtc_intr_handler = 0x400029ec ); +PROVIDE ( rtc_set_sleep_mode = 0x40002668 ); +PROVIDE ( save_rxbcn_mactime = 0x400027a4 ); +PROVIDE ( save_tsf_us = 0x400027ac ); +PROVIDE ( send_packet = 0x40003c80 ); +PROVIDE ( sha1_prf = 0x4000ba48 ); +PROVIDE ( sha1_vector = 0x4000a2ec ); +PROVIDE ( sip_alloc_to_host_evt = 0x40005180 ); +PROVIDE ( sip_get_ptr = 0x400058a8 ); +PROVIDE ( sip_get_state = 0x40005668 ); +PROVIDE ( sip_init_attach = 0x4000567c ); +PROVIDE ( sip_install_rx_ctrl_cb = 0x4000544c ); +PROVIDE ( sip_install_rx_data_cb = 0x4000545c ); +PROVIDE ( sip_post = 0x400050fc ); +PROVIDE ( sip_post_init = 0x400056c4 ); +PROVIDE ( sip_reclaim_from_host_cmd = 0x4000534c ); +PROVIDE ( sip_reclaim_tx_data_pkt = 0x400052c0 ); +PROVIDE ( sip_send = 0x40005808 ); +PROVIDE ( sip_to_host_chain_append = 0x40005864 ); +PROVIDE ( sip_to_host_evt_send_done = 0x40005234 ); +PROVIDE ( slc_add_credits = 0x400060ac ); +PROVIDE ( slc_enable = 0x40005d90 ); +PROVIDE ( slc_from_host_chain_fetch = 0x40005f24 ); +PROVIDE ( slc_from_host_chain_recycle = 0x40005e94 ); +PROVIDE ( slc_init_attach = 0x40005c50 ); +PROVIDE ( slc_init_credit = 0x4000608c ); +PROVIDE ( slc_pause_from_host = 0x40006014 ); +PROVIDE ( slc_reattach = 0x40005c1c ); +PROVIDE ( slc_resume_from_host = 0x4000603c ); +PROVIDE ( slc_select_tohost_gpio = 0x40005dc0 ); +PROVIDE ( slc_select_tohost_gpio_mode = 0x40005db8 ); +PROVIDE ( slc_send_to_host_chain = 0x40005de4 ); +PROVIDE ( slc_set_host_io_max_window = 0x40006068 ); +PROVIDE ( slc_to_host_chain_recycle = 0x40005f10 ); +PROVIDE ( software_reset = 0x4000264c ); +PROVIDE ( spi_flash_attach = 0x40004644 ); +PROVIDE ( srand = 0x400005f0 ); +PROVIDE ( strcmp = 0x4000bdc8 ); +PROVIDE ( strcpy = 0x4000bec8 ); +PROVIDE ( strlen = 0x4000bf4c ); +PROVIDE ( strncmp = 0x4000bfa8 ); +PROVIDE ( strncpy = 0x4000c0a0 ); +PROVIDE ( strstr = 0x4000e1e0 ); +PROVIDE ( timer_insert = 0x40002c64 ); +PROVIDE ( uartAttach = 0x4000383c ); +PROVIDE ( uart_baudrate_detect = 0x40003924 ); +PROVIDE ( uart_buff_switch = 0x400038a4 ); +PROVIDE ( uart_div_modify = 0x400039d8 ); +PROVIDE ( uart_rx_intr_handler = 0x40003bbc ); +PROVIDE ( uart_rx_one_char = 0x40003b8c ); +PROVIDE ( uart_rx_one_char_block = 0x40003b64 ); +PROVIDE ( uart_rx_readbuff = 0x40003ec8 ); +PROVIDE ( uart_tx_one_char = 0x40003b30 ); +PROVIDE ( wepkey_128 = 0x4000bc40 ); +PROVIDE ( wepkey_64 = 0x4000bb3c ); +PROVIDE ( xthal_bcopy = 0x40000688 ); +PROVIDE ( xthal_copy123 = 0x4000074c ); +PROVIDE ( xthal_get_ccompare = 0x4000dd4c ); +PROVIDE ( xthal_get_ccount = 0x4000dd38 ); +PROVIDE ( xthal_get_interrupt = 0x4000dd58 ); +PROVIDE ( xthal_get_intread = 0x4000dd58 ); +PROVIDE ( xthal_memcpy = 0x400006c4 ); +PROVIDE ( xthal_set_ccompare = 0x4000dd40 ); +PROVIDE ( xthal_set_intclear = 0x4000dd60 ); +PROVIDE ( xthal_spill_registers_into_stack_nw = 0x4000e320 ); +PROVIDE ( xthal_window_spill = 0x4000e324 ); +PROVIDE ( xthal_window_spill_nw = 0x4000e320 ); + +PROVIDE ( Te0 = 0x3fffccf0 ); +PROVIDE ( Td0 = 0x3fffd100 ); +PROVIDE ( Td4s = 0x3fffd500); +PROVIDE ( rcons = 0x3fffd0f0); +PROVIDE ( UartDev = 0x3fffde10 ); +PROVIDE ( flashchip = 0x3fffc714); diff --git a/flasher_stub/rom_functions.h b/flasher_stub/rom_functions.h new file mode 100644 index 0000000..2912334 --- /dev/null +++ b/flasher_stub/rom_functions.h @@ -0,0 +1,116 @@ +/* Declarations for functions in ESP8266 ROM code + * + * Copyright (c) 2016-2017 Cesanta Software Limited & Espressif Systems (Shanghai) PTE LTD + * All rights reserved + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#pragma once + +#include + +int uart_rx_one_char(uint8_t *ch); +uint8_t uart_rx_one_char_block(); +int uart_tx_one_char(char ch); +void uart_div_modify(uint32_t uart_no, uint32_t baud_div); + +int SendMsg(uint8_t *msg, uint8_t size); +int send_packet(const void *packet, uint32_t size); + +void _putc1(char *ch); + +void ets_delay_us(uint32_t us); + +typedef enum { SPI_FLASH_RESULT_OK = 0, + SPI_FLASH_RESULT_ERR = 1, + SPI_FLASH_RESULT_TIMEOUT = 2 } SpiFlashOpResult; + +SpiFlashOpResult SPILock(); +SpiFlashOpResult SPIUnlock(); +SpiFlashOpResult SPIRead(uint32_t addr, void *dst, uint32_t size); +SpiFlashOpResult SPIWrite(uint32_t addr, const uint8_t *src, uint32_t size); +SpiFlashOpResult SPIEraseChip(); +SpiFlashOpResult SPIEraseBlock(uint32_t block_num); +SpiFlashOpResult SPIEraseSector(uint32_t sector_num); +uint32_t SPI_read_status(); +uint32_t Wait_SPI_Idle(); +void spi_flash_attach(); + +void SelectSpiFunction(); +void SPIFlashModeConfig(uint32_t a, uint32_t b); +void SPIReadModeCnfig(uint32_t a); +uint32_t SPIParamCfg(uint32_t deviceId, uint32_t chip_size, uint32_t block_size, uint32_t sector_size, uint32_t page_size, uint32_t status_mask); + +void Cache_Read_Disable(); + +void memset(void *addr, uint8_t c, uint32_t len); + +void ets_delay_us(uint32_t delay_micros); + +void ets_isr_mask(uint32_t ints); +void ets_isr_unmask(uint32_t ints); +void ets_intr_lock(); +void ets_intr_unlock(); +void ets_set_user_start(void (*user_start_fn)()); + +uint32_t rtc_get_reset_reason(); +void software_reset(); +void rom_phy_reset_req(); + +void uart_rx_intr_handler(void *arg); + +void _ResetVector(); + +/* Crypto functions are from wpa_supplicant. */ +int md5_vector(uint32_t num_msgs, const uint8_t *msgs[], + const uint32_t *msg_lens, uint8_t *digest); +int sha1_vector(uint32_t num_msgs, const uint8_t *msgs[], + const uint32_t *msg_lens, uint8_t *digest); + +struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + uint8_t in[64]; +}; + +void MD5Init(struct MD5Context *ctx); +void MD5Update(struct MD5Context *ctx, void *buf, uint32_t len); +void MD5Final(uint8_t digest[16], struct MD5Context *ctx); + +typedef struct { + uint32_t device_id; + uint32_t chip_size; // chip size in bytes + uint32_t block_size; + uint32_t sector_size; + uint32_t page_size; + uint32_t status_mask; +} esp_rom_spiflash_chip_t; + +/* Some ESP32-only ROM functions */ +#ifdef ESP32 +uint32_t ets_get_detected_xtal_freq(void); +void uart_tx_flush(int uart); +uint32_t ets_efuse_get_spiconfig(void); + +/* These functions are in ets_sys.h on ESP8266 */ +typedef void (*int_handler_t)(void *arg); +int_handler_t ets_isr_attach(uint32_t int_num, int_handler_t handler, + void *arg); + +/* Note: this is a static function whose first argument was elided by the + compiler. */ +SpiFlashOpResult SPI_read_status_high(uint32_t *status); + +SpiFlashOpResult SPI_write_status(esp_rom_spiflash_chip_t *spi, uint32_t status_value); + +#endif diff --git a/flasher_stub/run_tests_with_stub.sh b/flasher_stub/run_tests_with_stub.sh new file mode 100644 index 0000000..f903e0b --- /dev/null +++ b/flasher_stub/run_tests_with_stub.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# +# Run test/test_esptool.py using the newly compiled stub, for quick tests +# +# Usage same as test/test_esptool.py +[ -z $PYTHON ] && PYTHON=python + +THISDIR=`readlink -f $(dirname $0)` + +export ESPTOOL_PY="${THISDIR}/esptool_test_stub.py" +${PYTHON} ${THISDIR}/../test/test_esptool.py $@ diff --git a/flasher_stub/slip.c b/flasher_stub/slip.c new file mode 100644 index 0000000..74c4964 --- /dev/null +++ b/flasher_stub/slip.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited & Espressif Systems (Shanghai) PTE LTD + * All rights reserved + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "rom_functions.h" +#include "slip.h" + +void SLIP_send_frame_delimiter(void) { + uart_tx_one_char('\xc0'); +} + +void SLIP_send_frame_data(char ch) { + if(ch == '\xc0') { + uart_tx_one_char('\xdb'); + uart_tx_one_char('\xdc'); + } else if (ch == '\xdb') { + uart_tx_one_char('\xdb'); + uart_tx_one_char('\xdd'); + } else { + uart_tx_one_char(ch); + } +} + +void SLIP_send_frame_data_buf(const void *buf, uint32_t size) { + const uint8_t *buf_c = (const uint8_t *)buf; + for(int i = 0; i < size; i++) { + SLIP_send_frame_data(buf_c[i]); + } +} + +void SLIP_send(const void *pkt, uint32_t size) { + SLIP_send_frame_delimiter(); + SLIP_send_frame_data_buf(pkt, size); + SLIP_send_frame_delimiter(); +} + +int16_t SLIP_recv_byte(char byte, slip_state_t *state) +{ + if (byte == '\xc0') { + if (*state == SLIP_NO_FRAME) { + *state = SLIP_FRAME; + return SLIP_NO_BYTE; + } else { + *state = SLIP_NO_FRAME; + return SLIP_FINISHED_FRAME; + } + } + + switch(*state) { + case SLIP_NO_FRAME: + return SLIP_NO_BYTE; + case SLIP_FRAME: + if (byte == '\xdb') { + *state = SLIP_FRAME_ESCAPING; + return SLIP_NO_BYTE; + } + return byte; + case SLIP_FRAME_ESCAPING: + if (byte == '\xdc') { + *state = SLIP_FRAME; + return '\xc0'; + } + if (byte == '\xdd') { + *state = SLIP_FRAME; + return '\xdb'; + } + return SLIP_NO_BYTE; /* actually a framing error */ + } + return SLIP_NO_BYTE; /* actually a framing error */ +} + +uint32_t SLIP_recv(void *pkt, uint32_t max_len) { + uint32_t len = 0; + slip_state_t state = SLIP_NO_FRAME; + uint8_t *p = (uint8_t *) pkt; + + int16_t r; + do { + r = SLIP_recv_byte(uart_rx_one_char_block(), &state); + if(r >= 0 && len < max_len) { + p[len++] = (uint8_t)r; + } + } while(r != SLIP_FINISHED_FRAME); + + return len; +} diff --git a/flasher_stub/slip.h b/flasher_stub/slip.h new file mode 100644 index 0000000..b1392e9 --- /dev/null +++ b/flasher_stub/slip.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef SLIP_H_ +#define SLIP_H_ + +#ifdef ESP8266 +#include +#else +#include +#endif + +/* Send the SLIP frame begin/end delimiter. */ +void SLIP_send_frame_delimiter(void); + +/* Send a single character of SLIP frame data, escaped as per SLIP escaping. */ +void SLIP_send_frame_data(char ch); + +/* Send some SLIP frame data, escaped as per SLIP escaping. */ +void SLIP_send_frame_data_buf(const void *buf, uint32_t size); + +/* Send a full SLIP frame, with specified contents. */ +void SLIP_send(const void *pkt, uint32_t size); + +typedef enum { + SLIP_NO_FRAME, + SLIP_FRAME, + SLIP_FRAME_ESCAPING +} slip_state_t; + +int16_t SLIP_recv_byte(char byte, slip_state_t *state); + +#define SLIP_FINISHED_FRAME -2 +#define SLIP_NO_BYTE -1 + +/* Receive a SLIP frame, with specified contents. */ +uint32_t SLIP_recv(void *pkt, uint32_t max_len); + +#endif /* SLIP_H_ */ diff --git a/flasher_stub/soc_support.h b/flasher_stub/soc_support.h new file mode 100644 index 0000000..d5fba8a --- /dev/null +++ b/flasher_stub/soc_support.h @@ -0,0 +1,47 @@ +/* SoC support for ESP8266/ESP32. + * + * Provide a unified interface where possible, from ESP8266 Non-OS SDK & esp-idf + * headers. + * + */ +#pragma once + +#ifdef ESP8266 +#define SPI_IDX 0 + +#include "ets_sys.h" +#include "eagle_soc.h" +#include "driver_lib/include/driver/uart_register.h" +#include "driver_lib/include/driver/spi_register.h" + +/* Harmonise register names between ESP8266 & -32 */ +#define SPI_CMD_REG(X) SPI_CMD(X) +#define SPI_W0_REG(X) SPI_W0(X) +#define SPI_ADDR_REG(X) SPI_ADDR(X) +#define SPI_EXT2_REG(X) SPI_EXT2(X) +#define SPI_RD_STATUS_REG(X) SPI_RD_STATUS(X) + +#define UART_CLKDIV_REG(X) UART_CLKDIV(X) +#define UART_CLKDIV_M (UART_CLKDIV_CNT << UART_CLKDIV_S) + +#define SPI_ST 0x7 /* field in SPI_EXT2_REG */ + +#define REG_READ READ_PERI_REG +#define REG_WRITE WRITE_PERI_REG + +#define ETS_UART0_INUM ETS_UART_INUM + +#else /* ESP32 */ +#define SPI_IDX 1 + +#include +#include +#include +#include "soc/soc.h" +#include "soc/uart_reg.h" +#include "soc/gpio_reg.h" +#include "soc/spi_reg.h" + +/* Harmonise register names between ESP8266 and ESP32 */ + +#endif diff --git a/flasher_stub/stub_32.ld b/flasher_stub/stub_32.ld new file mode 100644 index 0000000..80d4517 --- /dev/null +++ b/flasher_stub/stub_32.ld @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* Note: stub is deliberately loaded close to the very top + of available RAM, to reduce change of colliding with anything + else... */ +MEMORY { + iram : org = 0x4009F000, len = 0x1000 + dram : org = 0x3ffec000, len = 0x14000 +} + +ENTRY(stub_main) + +SECTIONS { + .text : ALIGN(4) { + *(.literal) + *(.text .text.*) + } > iram + + .bss : ALIGN(4) { + _bss_start = ABSOLUTE(.); + *(.bss) + _bss_end = ABSOLUTE(.); + } > dram + + .data : ALIGN(4) { + *(.data) + *(.rodata .rodata.*) + } > dram +} + +INCLUDE "rom_32.ld" diff --git a/flasher_stub/stub_8266.ld b/flasher_stub/stub_8266.ld new file mode 100644 index 0000000..f2f1df6 --- /dev/null +++ b/flasher_stub/stub_8266.ld @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* Note: stub is deliberately loaded close to the very top + of available RAM, to reduce change of colliding with anything + else... */ +MEMORY { + iram : org = 0x4010E000, len = 0x2000 + dram : org = 0x3FFE8000, len = 0x14000 +} + +ENTRY(stub_main_8266) + +SECTIONS { + .text : ALIGN(4) { + *(.literal) + *(.text .text.*) + } > iram + + .bss : ALIGN(4) { + _bss_start = ABSOLUTE(.); + *(.bss) + _bss_end = ABSOLUTE(.); + } > dram + + .data : ALIGN(4) { + *(.data) + *(.rodata .rodata.*) + } > dram +} + +INCLUDE "rom_8266.ld" + +PROVIDE(SPIFlashModeConfig = 0x40004568); +PROVIDE(SPIParamCfg = 0x40004c2c); diff --git a/flasher_stub/stub_commands.c b/flasher_stub/stub_commands.c new file mode 100644 index 0000000..45a0ffa --- /dev/null +++ b/flasher_stub/stub_commands.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2016 Espressif Systems (Shanghai) PTE LTD + * All rights reserved + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "stub_commands.h" +#include "stub_flasher.h" +#include "rom_functions.h" +#include "slip.h" +#include "soc_support.h" + +int handle_flash_erase(uint32_t addr, uint32_t len) { + if (addr % FLASH_SECTOR_SIZE != 0) return 0x32; + if (len % FLASH_SECTOR_SIZE != 0) return 0x33; + if (SPIUnlock() != 0) return 0x34; + + while (len > 0 && (addr % FLASH_BLOCK_SIZE != 0)) { + if (SPIEraseSector(addr / FLASH_SECTOR_SIZE) != 0) return 0x35; + len -= FLASH_SECTOR_SIZE; + addr += FLASH_SECTOR_SIZE; + } + + while (len > FLASH_BLOCK_SIZE) { + if (SPIEraseBlock(addr / FLASH_BLOCK_SIZE) != 0) return 0x36; + len -= FLASH_BLOCK_SIZE; + addr += FLASH_BLOCK_SIZE; + } + + while (len > 0) { + if (SPIEraseSector(addr / FLASH_SECTOR_SIZE) != 0) return 0x37; + len -= FLASH_SECTOR_SIZE; + addr += FLASH_SECTOR_SIZE; + } + + return 0; +} + +void handle_flash_read(uint32_t addr, uint32_t len, uint32_t block_size, + uint32_t max_in_flight) { + uint8_t buf[FLASH_SECTOR_SIZE]; + uint8_t digest[16]; + struct MD5Context ctx; + uint32_t num_sent = 0, num_acked = 0; + + /* This is one routine where we still do synchronous I/O */ + ets_isr_mask(1 << ETS_UART0_INUM); + + if (block_size > sizeof(buf)) { + return; + } + MD5Init(&ctx); + while (num_acked < len && num_acked <= num_sent) { + while (num_sent < len && num_sent - num_acked < max_in_flight) { + uint32_t n = len - num_sent; + if (n > block_size) n = block_size; + if (SPIRead(addr, (uint32_t *)buf, n) != 0) { + break; + } + SLIP_send(buf, n); + MD5Update(&ctx, buf, n); + addr += n; + num_sent += n; + } + int r = SLIP_recv(&num_acked, sizeof(num_acked)); + if (r != 4) { + break; + } + } + MD5Final(digest, &ctx); + SLIP_send(digest, sizeof(digest)); + + /* Go back to async UART */ + ets_isr_unmask(1 << ETS_UART0_INUM); +} + +int handle_flash_get_md5sum(uint32_t addr, uint32_t len) { + uint8_t buf[FLASH_SECTOR_SIZE]; + uint8_t digest[16]; + struct MD5Context ctx; + MD5Init(&ctx); + while (len > 0) { + uint32_t n = len; + if (n > FLASH_SECTOR_SIZE) { + n = FLASH_SECTOR_SIZE; + } + if (SPIRead(addr, (uint32_t *)buf, n) != 0) { + return 0x63; + } + MD5Update(&ctx, buf, n); + addr += n; + len -= n; + } + MD5Final(digest, &ctx); + /* ESP32 ROM sends as hex, but we just send raw bytes - esptool.py can handle either. */ + SLIP_send_frame_data_buf(digest, sizeof(digest)); + return 0; +} + +esp_command_error handle_spi_set_params(uint32_t *args, int *status) +{ + *status = SPIParamCfg(args[0], args[1], args[2], args[3], args[4], args[5]); + return *status ? ESP_FAILED_SPI_OP : ESP_OK; +} + +esp_command_error handle_spi_attach(uint32_t hspi_config_arg) +{ +#ifdef ESP8266 + /* ESP8266 doesn't yet support SPI flash on HSPI, but could: + see https://github.com/themadinventor/esptool/issues/98 */ + SelectSpiFunction(); +#else + /* spi_flash_attach calls SelectSpiFunction() and another + function to initialise SPI flash interface. + + Second argument 'legacy' mode is not currently supported. + */ + spi_flash_attach(hspi_config_arg, 0); +#endif + return ESP_OK; /* neither function/attach command takes an arg */ +} + +static uint32_t *mem_offset; +static uint32_t mem_remaining; + +esp_command_error handle_mem_begin(uint32_t size, uint32_t offset) +{ + mem_offset = (uint32_t *)offset; + mem_remaining = size; + return ESP_OK; +} + +esp_command_error handle_mem_data(void *data, uint32_t length) +{ + uint32_t *data_words = (uint32_t *)data; + if (mem_offset == NULL && length > 0) { + return ESP_NOT_IN_FLASH_MODE; + } + if (length > mem_remaining) { + return ESP_TOO_MUCH_DATA; + } + if (length % 4 != 0) { + return ESP_BAD_DATA_LEN; + } + + for(int i = 0; i < length; i+= 4) { + *mem_offset++ = *data_words++; + mem_remaining -= 4; + } + return ESP_OK; +} + +esp_command_error handle_mem_finish() +{ + esp_command_error res = mem_remaining > 0 ? ESP_NOT_ENOUGH_DATA : ESP_OK; + mem_remaining = 0; + mem_offset = NULL; + return res; +} diff --git a/flasher_stub/stub_commands.h b/flasher_stub/stub_commands.h new file mode 100644 index 0000000..3e92fa2 --- /dev/null +++ b/flasher_stub/stub_commands.h @@ -0,0 +1,25 @@ +/* Flasher command handlers, called from stub_flasher.c + + Commands related to writing flash are in stub_write_flash_xxx. +*/ +#pragma once +#include "stub_flasher.h" +#include + +int handle_flash_erase(uint32_t addr, uint32_t len); + +void handle_flash_read(uint32_t addr, uint32_t len, uint32_t block_size, uint32_t max_in_flight); + +int handle_flash_get_md5sum(uint32_t addr, uint32_t len); + +int handle_flash_read_chip_id(); + +esp_command_error handle_spi_set_params(uint32_t *args, int *status); + +esp_command_error handle_spi_attach(uint32_t hspi_config_arg); + +esp_command_error handle_mem_begin(uint32_t size, uint32_t offset); + +esp_command_error handle_mem_data(void *data, uint32_t length); + +esp_command_error handle_mem_finish(void); diff --git a/flasher_stub/stub_flasher.c b/flasher_stub/stub_flasher.c new file mode 100644 index 0000000..d4dff0c --- /dev/null +++ b/flasher_stub/stub_flasher.c @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited & Espressif Systems (Shanghai) PTE LTD + * All rights reserved + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Spiffy flasher. Implements strong checksums (MD5) and can use higher + * baud rates. Actual max baud rate will differ from device to device, + * but 921K seems to be common. + * + * SLIP protocol is used for communication. + * First packet is a single byte - command number. + * After that, a packet with a variable number of 32-bit (LE) arguments, + * depending on command. + * + * Then command produces variable number of packets of output, but first + * packet of length 1 is the response code: 0 for success, non-zero - error. + * + * See individual command description below. + */ + +#include "stub_flasher.h" +#include "rom_functions.h" +#include "slip.h" +#include "stub_commands.h" +#include "stub_write_flash.h" +#include "soc_support.h" + +#define UART_RX_INTS (UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA) + + +#ifdef ESP32 +/* ESP32 register naming is a bit more consistent */ +#define UART_INT_CLR(X) UART_INT_CLR_REG(X) +#define UART_INT_ST(X) UART_INT_ST_REG(X) +#define UART_INT_ENA(X) UART_INT_ENA_REG(X) +#define UART_STATUS(X) UART_STATUS_REG(X) +#define UART_FIFO(X) UART_FIFO_REG(X) +#endif + +static uint32_t get_new_uart_divider(uint32_t current_baud, uint32_t new_baud) +{ + uint32_t master_freq; + /* ESP32 has ROM code to detect the crystal freq but ESP8266 does not have this... + So instead we use the previously auto-synced 115200 baud rate (which we know + is correct wrt the relative crystal accuracy of the ESP & the USB/serial adapter). + From this we can estimate crystal freq, and update for a new baud rate relative to that. + */ + uint32_t uart_reg = REG_READ(UART_CLKDIV_REG(0)); + uint32_t uart_div = uart_reg & UART_CLKDIV_M; +#ifdef ESP32 + // account for fractional part of divider (bottom 4 bits) + uint32_t fraction = (uart_reg >> UART_CLKDIV_FRAG_S) & UART_CLKDIV_FRAG_V; + uart_div = (uart_div << 4) + fraction; +#endif + master_freq = uart_div * current_baud; + return master_freq / new_baud; + +} + +/* Buffers for reading from UART. Data is read double-buffered, so + we can read into one buffer while handling data from the other one + (used for flashing throughput.) */ +typedef struct { + uint8_t buf_a[MAX_WRITE_BLOCK+64]; + uint8_t buf_b[MAX_WRITE_BLOCK+64]; + volatile uint8_t *reading_buf; /* Pointer to buf_a, or buf_b - which are we reading_buf? */ + uint16_t read; /* how many bytes have we read in the frame */ + slip_state_t state; + esp_command_req_t *command; /* Pointer to buf_a or buf_b as latest command received */ +} uart_buf_t; +static volatile uart_buf_t ub; + +/* esptool protcol "checksum" is XOR of 0xef and each byte of + data payload. */ +static uint8_t calculate_checksum(uint8_t *buf, int length) +{ + uint8_t res = 0xef; + for(int i = 0; i < length; i++) { + res ^= buf[i]; + } + return res; +} + +static void uart_isr_receive(char byte) +{ + int16_t r = SLIP_recv_byte(byte, (slip_state_t *)&ub.state); + if (r >= 0) { + ub.reading_buf[ub.read++] = (uint8_t) r; + if (ub.read == MAX_WRITE_BLOCK+64) { + /* shouldn't happen unless there are data errors */ + r = SLIP_FINISHED_FRAME; + } + } + if (r == SLIP_FINISHED_FRAME) { + /* end of frame, set 'command' + to be processed by main thread */ + if(ub.reading_buf == ub.buf_a) { + ub.command = (esp_command_req_t *)ub.buf_a; + ub.reading_buf = ub.buf_b; + } else { + ub.command = (esp_command_req_t *)ub.buf_b; + ub.reading_buf = ub.buf_a; + } + ub.read = 0; + } +} + +void uart_isr(void *arg) { + uint32_t int_st = READ_PERI_REG(UART_INT_ST(0)); + while (1) { + uint32_t fifo_len = READ_PERI_REG(UART_STATUS(0)) & 0xff; + if (fifo_len == 0) { + break; + } + while (fifo_len-- > 0) { + uint8_t byte = READ_PERI_REG(UART_FIFO(0)) & 0xff; + uart_isr_receive(byte); + } + } + WRITE_PERI_REG(UART_INT_CLR(0), int_st); +} + +static esp_command_error verify_data_len(esp_command_req_t *command, uint8_t len) +{ + return (command->data_len == len) ? ESP_OK : ESP_BAD_DATA_LEN; +} + +void cmd_loop() { + while(1) { + /* Wait for a command */ + while(ub.command == NULL) { } + esp_command_req_t *command = ub.command; + ub.command = NULL; + /* provide easy access for 32-bit data words */ + uint32_t *data_words = (uint32_t *)command->data_buf; + + /* Send command response header */ + esp_command_response_t resp = { + .resp = 1, + .op_ret = command->op, + .len_ret = 2, /* esptool.py ignores this value, but other users of the stub may not */ + .value = 0, + }; + + /* Some commands need to set resp.len_ret or resp.value before it is sent back */ + switch(command->op) { + case ESP_READ_REG: + if (command->data_len == 4) { + resp.value = REG_READ(data_words[0]); + } + break; + case ESP_FLASH_VERIFY_MD5: + resp.len_ret = 16 + 2; /* Will sent 16 bytes of data with MD5 value */ + break; + default: + break; + } + + /* Send the command response */ + SLIP_send_frame_delimiter(); + SLIP_send_frame_data_buf(&resp, sizeof(esp_command_response_t)); + + if(command->data_len > MAX_WRITE_BLOCK+16) { + SLIP_send_frame_data(ESP_BAD_DATA_LEN); + SLIP_send_frame_data(0xEE); + SLIP_send_frame_delimiter(); + continue; + } + + /* ... ESP_FLASH_VERIFY_MD5 will insert in-frame response data + between here and when we send the status bytes at the + end of the frame */ + + esp_command_error error = ESP_CMD_NOT_IMPLEMENTED; + int status = 0; + + /* First stage of command processing - before sending error/status */ + switch (command->op) { + case ESP_ERASE_FLASH: + error = verify_data_len(command, 0) || SPIEraseChip(); + break; + case ESP_ERASE_REGION: + /* Params for ERASE_REGION are addr, len */ + error = verify_data_len(command, 8) || handle_flash_erase(data_words[0], data_words[1]); + break; + case ESP_SET_BAUD: + /* ESP_SET_BAUD sends two args, new and old baud rates */ + error = verify_data_len(command, 8); + /* actual baud setting happens after we send the reply */ + break; + case ESP_READ_FLASH: + error = verify_data_len(command, 16); + /* actual data is sent after we send the reply */ + break; + case ESP_FLASH_VERIFY_MD5: + /* unsure why the MD5 command has 4 params but we only pass 2 of them, + but this is in ESP32 ROM so we can't mess with it. + */ + error = verify_data_len(command, 16) || handle_flash_get_md5sum(data_words[0], data_words[1]); + break; + case ESP_FLASH_BEGIN: + /* parameters (interpreted differently to ROM flasher): + 0 - erase_size (used as total size to write) + 1 - num_blocks (ignored) + 2 - block_size (should be MAX_WRITE_BLOCK, relies on num_blocks * block_size >= erase_size) + 3 - offset (used as-is) + */ + if (command->data_len == 16 && data_words[2] != MAX_WRITE_BLOCK) { + error = ESP_BAD_BLOCKSIZE; + } else { + error = verify_data_len(command, 16) || handle_flash_begin(data_words[0], data_words[3]); + } + break; + case ESP_FLASH_DEFLATED_BEGIN: + /* parameters: + 0 - uncompressed size + 1 - num_blocks (based on compressed size) + 2 - block_size (should be MAX_WRITE_BLOCK, total bytes over serial = num_blocks * block_size) + 3 - offset (used as-is) + */ + if (command->data_len == 16 && data_words[2] != MAX_WRITE_BLOCK) { + error = ESP_BAD_BLOCKSIZE; + } else { + error = verify_data_len(command, 16) || handle_flash_deflated_begin(data_words[0], data_words[1] * data_words[2], data_words[3]); } + break; + case ESP_FLASH_DATA: + case ESP_FLASH_DEFLATED_DATA: + /* ACK DATA commands immediately, then process them a few lines down, + allowing next command to buffer */ + if(is_in_flash_mode()) { + error = get_flash_error(); + int payload_len = command->data_len - 16; + if (data_words[0] != payload_len) { + /* First byte of data payload header is length (repeated) as a word */ + error = ESP_BAD_DATA_LEN; + } + uint8_t data_checksum = calculate_checksum(command->data_buf + 16, payload_len); + if (data_checksum != command->checksum) { + error = ESP_BAD_DATA_CHECKSUM; + } + } + else { + error = ESP_NOT_IN_FLASH_MODE; + } + break; + case ESP_FLASH_END: + case ESP_FLASH_DEFLATED_END: + error = handle_flash_end(); + break; + case ESP_SPI_SET_PARAMS: + /* data params: fl_id, total_size, block_size, sector_Size, page_size, status_mask */ + error = verify_data_len(command, 24) || handle_spi_set_params(data_words, &status); + break; + case ESP_SPI_ATTACH: + /* parameter is 'hspi mode' (0, 1 or a pin mask for ESP32. Ignored on ESP8266.) */ + error = verify_data_len(command, 4) || handle_spi_attach(data_words[0]); + break; + case ESP_WRITE_REG: + /* params are addr, value, mask (ignored), delay_us (ignored) */ + error = verify_data_len(command, 16); + if (error == ESP_OK) { + REG_WRITE(data_words[0], data_words[1]); + } + break; + case ESP_READ_REG: + /* actual READ_REG operation happens higher up */ + error = verify_data_len(command, 4); + break; + case ESP_MEM_BEGIN: + error = verify_data_len(command, 16) || handle_mem_begin(data_words[0], data_words[3]); + break; + case ESP_MEM_DATA: + error = handle_mem_data(command->data_buf + 16, command->data_len - 16); + break; + case ESP_MEM_END: + error = verify_data_len(command, 8) || handle_mem_finish(); + break; + case ESP_RUN_USER_CODE: + /* Returning from here will run user code, ie standard boot process + + This command does not send a response. + */ + return; + } + + SLIP_send_frame_data(error); + SLIP_send_frame_data(status); + SLIP_send_frame_delimiter(); + + /* Some commands need to do things after after sending this response */ + if (error == ESP_OK) { + switch(command->op) { + case ESP_SET_BAUD: + ets_delay_us(10000); + uart_div_modify(0, get_new_uart_divider(data_words[1], data_words[0])); + ets_delay_us(1000); + break; + case ESP_READ_FLASH: + /* args are: offset, length, block_size, max_in_flight */ + handle_flash_read(data_words[0], data_words[1], data_words[2], + data_words[3]); + break; + case ESP_FLASH_DATA: + /* drop into flashing mode, discard 16 byte payload header */ + handle_flash_data(command->data_buf + 16, command->data_len - 16); + break; + case ESP_FLASH_DEFLATED_DATA: + handle_flash_deflated_data(command->data_buf + 16, command->data_len - 16); + break; + case ESP_FLASH_DEFLATED_END: + case ESP_FLASH_END: + /* passing 0 as parameter for ESP_FLASH_END means reboot now */ + if (data_words[0] == 0) { + /* Flush the FLASH_END response before rebooting */ +#ifdef ESP32 + uart_tx_flush(0); +#endif + ets_delay_us(10000); + software_reset(); + } + break; + case ESP_MEM_END: + if (data_words[1] != 0) { + void (*entrypoint_fn)(void) = (void (*))data_words[1]; + /* Make sure the command response has been flushed out + of the UART before we run the new code */ +#ifdef ESP32 + uart_tx_flush(0); +#endif + ets_delay_us(1000); + /* this is a little different from the ROM loader, + which exits the loader routine and _then_ calls this + function. But for our purposes so far, having a bit of + extra stuff on the stack doesn't really matter. + */ + entrypoint_fn(); + } + break; + } + } + } +} + + +extern uint32_t _bss_start; +extern uint32_t _bss_end; + +void __attribute__((used)) stub_main(); + + +#ifdef ESP8266 +__asm__ ( + ".global stub_main_8266\n" + ".literal_position\n" + ".align 4\n" + "stub_main_8266:\n" +/* ESP8266 wrapper for "stub_main()" manipulates the return address in + * a0, so 'return' from here runs user code. + * + * After setting a0, we jump directly to stub_main_inner() which is a + * normal C function + * + * Adapted from similar approach used by Cesanta Software for ESP8266 + * flasher stub. + * + */ + "movi a0, 0x400010a8;" + "j stub_main;"); +#endif + +/* This function is called from stub_main, with return address + reset to point to user code. */ +void stub_main() +{ + const uint32_t greeting = 0x4941484f; /* OHAI */ + + /* this points to stub_main now, clear for next boot */ + ets_set_user_start(0); + + /* zero bss */ + for(uint32_t *p = &_bss_start; p < &_bss_end; p++) { + *p = 0; + } + + SLIP_send(&greeting, 4); + + /* All UART reads come via uart_isr */ + ub.reading_buf = ub.buf_a; + ets_isr_attach(ETS_UART0_INUM, uart_isr, NULL); + SET_PERI_REG_MASK(UART_INT_ENA(0), UART_RX_INTS); + ets_isr_unmask(1 << ETS_UART0_INUM); + + /* Configure default SPI flash functionality. + Can be overriden later by esptool.py. */ +#ifdef ESP8266 + SelectSpiFunction(); + + spi_flash_attach(); +#else + uint32_t spiconfig = ets_efuse_get_spiconfig(); + uint32_t strapping = REG_READ(GPIO_STRAP_REG); + /* If GPIO1 (U0TXD) is pulled low and no other boot mode is + set in efuse, assume HSPI flash mode (same as normal boot) + */ + if (spiconfig == 0 && (strapping & 0x1c) == 0x08) { + spiconfig = 1; /* HSPI flash mode */ + } + spi_flash_attach(spiconfig, 0); +#endif + SPIParamCfg(0, 16*1024*1024, FLASH_BLOCK_SIZE, FLASH_SECTOR_SIZE, + FLASH_PAGE_SIZE, FLASH_STATUS_MASK); + + cmd_loop(); + + /* if cmd_loop returns, it's due to ESP_RUN_USER_CODE command. */ + + return; +} diff --git a/flasher_stub/stub_flasher.h b/flasher_stub/stub_flasher.h new file mode 100644 index 0000000..0b473cb --- /dev/null +++ b/flasher_stub/stub_flasher.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef STUB_FLASHER_H_ +#define STUB_FLASHER_H_ + +#ifdef ESP8266 +#include +#else +#include +#endif + +/* Maximum write block size, used for various buffers. */ +#define MAX_WRITE_BLOCK 0x4000 + +/* Flash geometry constants */ +#define FLASH_SECTOR_SIZE 4096 +#define FLASH_BLOCK_SIZE 65536 +#define FLASH_PAGE_SIZE 256 +#define FLASH_STATUS_MASK 0xFFFF +#define SECTORS_PER_BLOCK (FLASH_BLOCK_SIZE / FLASH_SECTOR_SIZE) + +/* Full set of protocol commands */ +typedef enum { + /* Commands supported by the ESP8266 & ESP32 bootloaders */ + ESP_FLASH_BEGIN = 0x02, + ESP_FLASH_DATA = 0x03, + ESP_FLASH_END = 0x04, + ESP_MEM_BEGIN = 0x05, + ESP_MEM_END = 0x06, + ESP_MEM_DATA = 0x07, + ESP_SYNC = 0x08, + ESP_WRITE_REG = 0x09, + ESP_READ_REG = 0x0a, + + /* Commands supported by the ESP32 bootloader */ + ESP_SPI_SET_PARAMS = 0x0b, + ESP_PIN_READ = 0x0c, /* ??? */ + ESP_SPI_ATTACH = 0x0d, + ESP_SPI_READ = 0x0e, + ESP_SET_BAUD = 0x0f, + ESP_FLASH_DEFLATED_BEGIN = 0x10, + ESP_FLASH_DEFLATED_DATA = 0x11, + ESP_FLASH_DEFLATED_END = 0x12, + ESP_FLASH_VERIFY_MD5 = 0x13, + + /* Stub-only commands */ + ESP_ERASE_FLASH = 0xD0, + ESP_ERASE_REGION = 0xD1, + ESP_READ_FLASH = 0xD2, + ESP_RUN_USER_CODE = 0xD3, +} esp_command; + +/* Command request header */ +typedef struct __attribute__((packed)) { + uint8_t zero; + uint8_t op; /* maps to esp_command enum */ + uint16_t data_len; + int32_t checksum; + uint8_t data_buf[32]; /* actually variable length, determined by data_len */ +} esp_command_req_t; + +/* Command response header */ +typedef struct __attribute__((packed)) { + uint8_t resp; /* should be '1' */ + uint8_t op_ret; /* Should match 'op' */ + uint16_t len_ret; /* Length of result data (can be ignored as SLIP framing helps) */ + int32_t value; /* 32-bit response used by some commands */ +} esp_command_response_t; + + +/* command response has some (optional) data after it, then 2 (or 4 on ESP32 ROM) + bytes of status. + */ +typedef struct __attribute__((packed)) { + uint8_t error; /* non-zero = failed */ + uint8_t status; /* status of a failure */ +} esp_command_data_status_t; + +/* Error codes */ +typedef enum { + ESP_OK = 0, + + ESP_BAD_DATA_LEN = 0xC0, + ESP_BAD_DATA_CHECKSUM = 0xC1, + ESP_BAD_BLOCKSIZE = 0xC2, + ESP_INVALID_COMMAND = 0xC3, + ESP_FAILED_SPI_OP = 0xC4, + ESP_FAILED_SPI_UNLOCK = 0xC5, + ESP_NOT_IN_FLASH_MODE = 0xC6, + ESP_INFLATE_ERROR = 0xC7, + ESP_NOT_ENOUGH_DATA = 0xC8, + ESP_TOO_MUCH_DATA = 0xC9, + + ESP_CMD_NOT_IMPLEMENTED = 0xFF, +} esp_command_error; + +#endif /* STUB_FLASHER_H_ */ diff --git a/flasher_stub/stub_write_flash.c b/flasher_stub/stub_write_flash.c new file mode 100644 index 0000000..7062aec --- /dev/null +++ b/flasher_stub/stub_write_flash.c @@ -0,0 +1,292 @@ +/* Command handlers for writing out to flash. + * + * Called from stub_flasher.c + * + * Copyright (c) 2016 Cesanta Software Limited & Espressif Systems (Shanghai) PTE LTD. + * All rights reserved + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later version. + * + */ +#include "soc_support.h" +#include "stub_write_flash.h" +#include "stub_flasher.h" +#include "rom_functions.h" +#include "miniz.h" + +/* local flashing state + + This is wrapped in a structure because gcc 4.8 + generates significantly more code for ESP32 + if they are static variables (literal pool, I think!) +*/ +static struct { + /* set by flash_begin, cleared by flash_end */ + bool in_flash_mode; + /* offset of next SPI write */ + uint32_t next_write; + /* sector number for next erase */ + int next_erase_sector; + /* number of output bytes remaining to write */ + uint32_t remaining; + /* number of sectors remaining to erase */ + int remaining_erase_sector; + /* last error generated by a data packet */ + esp_command_error last_error; + + /* inflator state for deflate write */ + tinfl_decompressor inflator; + /* number of compressed bytes remaining to read */ + uint32_t remaining_compressed; +} fs; + +/* SPI status bits */ +static const uint32_t STATUS_WIP_BIT = (1 << 0); +static const uint32_t STATUS_CMP_BIT = (1 << 14); /* Complement Protect */ +static const uint32_t STATUS_QIE_BIT = (1 << 9); /* Quad Enable */ + +bool is_in_flash_mode(void) +{ + return fs.in_flash_mode; +} + +esp_command_error get_flash_error(void) +{ + return fs.last_error; +} + +/* Wait for the SPI state machine to be ready, + ie no command in progress in the internal host. +*/ +inline static void spi_wait_ready(void) +{ + /* Wait for SPI state machine ready */ + while((REG_READ(SPI_EXT2_REG(SPI_IDX)) & SPI_ST)) + { } +#ifdef ESP32 + while(REG_READ(SPI_EXT2_REG(0)) & SPI_ST) + { } +#endif +} + +/* Returns true if the spiflash is ready for its next write + operation. + + Doesn't block, except for the SPI state machine to finish + any previous SPI host operation. +*/ +static bool spiflash_is_ready(void) +{ + spi_wait_ready(); + REG_WRITE(SPI_RD_STATUS_REG(SPI_IDX), 0); + /* Issue read status command */ + REG_WRITE(SPI_CMD_REG(SPI_IDX), SPI_FLASH_RDSR); + while(REG_READ(SPI_CMD_REG(SPI_IDX)) != 0) + { } + uint32_t status_value = REG_READ(SPI_RD_STATUS_REG(SPI_IDX)); + return (status_value & STATUS_WIP_BIT) == 0; +} + +static void spi_write_enable(void) +{ + while(!spiflash_is_ready()) + { } + REG_WRITE(SPI_CMD_REG(SPI_IDX), SPI_FLASH_WREN); + while(REG_READ(SPI_CMD_REG(SPI_IDX)) != 0) + { } +} + +#ifdef ESP32 +static esp_rom_spiflash_chip_t *flashchip = (esp_rom_spiflash_chip_t *)0x3ffae270; + +/* Stub version of SPIUnlock() that replaces version in ROM. + + This works around a bug where SPIUnlock sometimes reads the wrong + high status byte (RDSR2 result) and then copies it back to the + flash status, causing lock bit CMP or Status Register Protect ` to + become set. + */ +SpiFlashOpResult SPIUnlock(void) +{ + uint32_t status; + + spi_wait_ready(); /* ROM SPI_read_status_high() doesn't wait for this */ + if (SPI_read_status_high(&status) != SPI_FLASH_RESULT_OK) { + return SPI_FLASH_RESULT_ERR; + } + + /* Clear all bits except QIE, if it is set. + (This is different from ROM SPIUnlock, which keeps all bits as-is.) + */ + status &= STATUS_QIE_BIT; + + spi_write_enable(); + + SET_PERI_REG_MASK(SPI_CTRL_REG(SPI_IDX), SPI_WRSR_2B); + if (SPI_write_status(flashchip, status) != SPI_FLASH_RESULT_OK) { + return SPI_FLASH_RESULT_ERR; + } + + return SPI_FLASH_RESULT_OK; +} +#endif + +esp_command_error handle_flash_begin(uint32_t total_size, uint32_t offset) { + fs.in_flash_mode = true; + fs.next_write = offset; + fs.next_erase_sector = offset / FLASH_SECTOR_SIZE; + fs.remaining = total_size; + fs.remaining_erase_sector = ((offset % FLASH_SECTOR_SIZE) + total_size + FLASH_SECTOR_SIZE - 1) / FLASH_SECTOR_SIZE; + fs.last_error = ESP_OK; + + if (SPIUnlock() != 0) { + return ESP_FAILED_SPI_UNLOCK; + } + + return ESP_OK; +} + +esp_command_error handle_flash_deflated_begin(uint32_t uncompressed_size, uint32_t compressed_size, uint32_t offset) { + esp_command_error err = handle_flash_begin(uncompressed_size, offset); + tinfl_init(&fs.inflator); + fs.remaining_compressed = compressed_size; + return err; +} + +/* Erase the next sector or block (depending if we're at a block boundary). + + Updates fs.next_erase_sector & fs.remaining_erase_sector on success. + + If nothing left to erase, returns immediately. + + Returns immediately if SPI flash not yet ready for a write operation. + + Does not wait for the erase to complete - the next SPI operation + should check if a write operation is currently in progress. + */ +static void start_next_erase(void) +{ + if(fs.remaining_erase_sector == 0) + return; /* nothing left to erase */ + if(!spiflash_is_ready()) + return; /* don't wait for flash to be ready, caller will call again if needed */ + + spi_write_enable(); + + uint32_t command = SPI_FLASH_SE; /* sector erase, 4KB */ + uint32_t sectors_to_erase = 1; + if(fs.remaining_erase_sector >= SECTORS_PER_BLOCK + && fs.next_erase_sector % SECTORS_PER_BLOCK == 0) { + /* perform a 64KB block erase if we have space for it */ + command = SPI_FLASH_BE; + sectors_to_erase = SECTORS_PER_BLOCK; + } + + uint32_t addr = fs.next_erase_sector * FLASH_SECTOR_SIZE; + spi_wait_ready(); + REG_WRITE(SPI_ADDR_REG(SPI_IDX), addr & 0xffffff); + REG_WRITE(SPI_CMD_REG(SPI_IDX), command); + while(REG_READ(SPI_CMD_REG(SPI_IDX)) != 0) + { } + fs.remaining_erase_sector -= sectors_to_erase; + fs.next_erase_sector += sectors_to_erase; +} + +/* Write data to flash (either direct for non-compressed upload, or + freshly decompressed.) Erases as it goes. + + Updates fs.remaining_erase_sector, fs.next_write, and fs.remaining +*/ +void handle_flash_data(void *data_buf, uint32_t length) { + int last_sector; + + if (length > fs.remaining) { + /* Trim the final block, as it may have padding beyond + the length we are writing */ + length = fs.remaining; + } + + if (length == 0) { + return; + } + + /* what sector is this write going to end in? + make sure we've erased at least that far. + */ + last_sector = (fs.next_write + length) / FLASH_SECTOR_SIZE; + while(fs.remaining_erase_sector > 0 && fs.next_erase_sector <= last_sector) { + start_next_erase(); + } + while(!spiflash_is_ready()) + {} + + /* do the actual write */ + if (SPIWrite(fs.next_write, data_buf, length)) { + fs.last_error = ESP_FAILED_SPI_OP; + } + fs.next_write += length; + fs.remaining -= length; +} + +void handle_flash_deflated_data(void *data_buf, uint32_t length) { + static uint8_t out_buf[32768]; + static uint8_t *next_out = out_buf; + int status = TINFL_STATUS_NEEDS_MORE_INPUT; + + while(length > 0 && fs.remaining > 0 && status > TINFL_STATUS_DONE) { + size_t in_bytes = length; /* input remaining */ + size_t out_bytes = out_buf + sizeof(out_buf) - next_out; /* output space remaining */ + int flags = TINFL_FLAG_PARSE_ZLIB_HEADER; + if(fs.remaining_compressed > length) { + flags |= TINFL_FLAG_HAS_MORE_INPUT; + } + + /* start an opportunistic erase: decompressing takes time, so might as + well be running a SPI erase in the background. */ + start_next_erase(); + + status = tinfl_decompress(&fs.inflator, data_buf, &in_bytes, + out_buf, next_out, &out_bytes, + flags); + + fs.remaining_compressed -= in_bytes; + length -= in_bytes; + data_buf += in_bytes; + + next_out += out_bytes; + size_t bytes_in_out_buf = next_out - out_buf; + if (status <= TINFL_STATUS_DONE || bytes_in_out_buf == sizeof(out_buf)) { + // Output buffer full, or done + handle_flash_data(out_buf, bytes_in_out_buf); + next_out = out_buf; + } + } // while + + if (status < TINFL_STATUS_DONE) { + /* error won't get sent back to esptool.py until next block is sent */ + fs.last_error = ESP_INFLATE_ERROR; + } + + if (status == TINFL_STATUS_DONE && fs.remaining > 0) { + fs.last_error = ESP_NOT_ENOUGH_DATA; + } + if (status != TINFL_STATUS_DONE && fs.remaining == 0) { + fs.last_error = ESP_TOO_MUCH_DATA; + } +} + +esp_command_error handle_flash_end(void) +{ + if (!fs.in_flash_mode) { + return ESP_NOT_IN_FLASH_MODE; + } + + if (fs.remaining > 0) { + return ESP_NOT_ENOUGH_DATA; + } + + fs.in_flash_mode = false; + return fs.last_error; +} diff --git a/flasher_stub/stub_write_flash.h b/flasher_stub/stub_write_flash.h new file mode 100644 index 0000000..1fe20dd --- /dev/null +++ b/flasher_stub/stub_write_flash.h @@ -0,0 +1,20 @@ +/* Flasher commands related to writing flash */ +#pragma once +#include "stub_flasher.h" +#include + +bool is_in_flash_mode(void); + +esp_command_error get_flash_error(void); + +esp_command_error handle_flash_begin(uint32_t total_size, uint32_t offset); + +esp_command_error handle_flash_deflated_begin(uint32_t uncompressed_size, uint32_t compressed_size, uint32_t offset); + +void handle_flash_data(void *data_buf, uint32_t length); + +void handle_flash_deflated_data(void *data_buf, uint32_t length); + +/* same command used for deflated or non-deflated mode */ +esp_command_error handle_flash_end(void); + diff --git a/flasher_stub/wrap_stub.py b/flasher_stub/wrap_stub.py new file mode 100644 index 0000000..6cdbf24 --- /dev/null +++ b/flasher_stub/wrap_stub.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# +# Stub has to be generated via Python 3, for correct repr() output +# +# Copyright (c) 2016 Cesanta Software Limited +# All rights reserved +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +# Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import base64 +import os +import os.path +import sys +import zlib + +sys.path.append('..') +import esptool + +def wrap_stub(elf_file): + """ Wrap an ELF file into a stub 'dict' """ + print('Wrapping ELF file %s...' % elf_file) + e = esptool.ELFFile(elf_file) + + text_section = e.get_section('.text') + try: + data_section = e.get_section('.data') + except ValueError: + data_section = None + stub = { + 'text': text_section.data, + 'text_start': text_section.addr, + 'entry': e.entrypoint, + } + if data_section is not None: + stub['data'] = data_section.data + stub['data_start'] = data_section.addr + + # Pad text with NOPs to mod 4. + if len(stub['text']) % 4 != 0: + stub['text'] += (4 - (len(stub['text']) % 4)) * '\0' + + print('Stub text: %d @ 0x%08x, data: %d @ 0x%08x, entry @ 0x%x' % ( + len(stub['text']), stub['text_start'], + len(stub.get('data', '')), stub.get('data_start', 0), + stub['entry']), file=sys.stderr) + return stub + +PYTHON_TEMPLATE = """\ +ESP%sROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b\"\"\" +%s\"\"\"))) +""" + +def write_python_snippet(stubs): + with open(sys.argv[-1], 'w') as f: + f.write("# Binary stub code (see flasher_stub dir for source & details)\n") + for key in "8266", "32": + stub_data = stubs["stub_flasher_%s" % key] + encoded = base64.b64encode(zlib.compress(repr(stub_data).encode("utf-8"), 9)).decode("utf-8") + in_lines = "" + # split encoded data into 160 character lines + LINE_LEN=160 + for c in range(0, len(encoded), LINE_LEN): + in_lines += encoded[c:c+LINE_LEN] + "\\\n" + f.write(PYTHON_TEMPLATE % (key, in_lines)) + print("Python snippet is %d bytes" % f.tell()) + +def stub_name(filename): + """ Return a dictionary key for the stub with filename 'filename' """ + return os.path.splitext(os.path.basename(filename))[0] + +if __name__ == '__main__': + stubs = dict( (stub_name(elf_file),wrap_stub(elf_file)) for elf_file in sys.argv[1:-1] ) + print('Dumping to Python snippet file %s.' % sys.argv[-1]) + write_python_snippet(stubs) diff --git a/logo.icns b/logo.icns new file mode 100644 index 0000000..bebe6e4 Binary files /dev/null and b/logo.icns differ diff --git a/logo.ico b/logo.ico new file mode 100644 index 0000000..bebe6e4 Binary files /dev/null and b/logo.ico differ diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..5dea188 Binary files /dev/null and b/logo.png differ diff --git a/logo.png.icns b/logo.png.icns new file mode 100644 index 0000000..5182825 Binary files /dev/null and b/logo.png.icns differ diff --git a/osx/dist/DoayeeESP32DFU.app/Contents/Info.plist b/osx/dist/DoayeeESP32DFU.app/Contents/Info.plist new file mode 100644 index 0000000..10303a2 --- /dev/null +++ b/osx/dist/DoayeeESP32DFU.app/Contents/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDisplayName + DoayeeESP32DFU + CFBundleExecutable + MacOS/DoayeeESP32DFU + CFBundleIconFile + logo.png.icns + CFBundleIdentifier + com.doayee.esp32dfu + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DoayeeESP32DFU + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.0.0 + NSHighResolutionCapable + True + + diff --git a/osx/dist/DoayeeESP32DFU.app/Contents/MacOS/DoayeeESP32DFU b/osx/dist/DoayeeESP32DFU.app/Contents/MacOS/DoayeeESP32DFU new file mode 100644 index 0000000..1a5506c Binary files /dev/null and b/osx/dist/DoayeeESP32DFU.app/Contents/MacOS/DoayeeESP32DFU differ diff --git a/osx/dist/DoayeeESP32DFU.app/Contents/Resources/logo.png.icns b/osx/dist/DoayeeESP32DFU.app/Contents/Resources/logo.png.icns new file mode 100644 index 0000000..5182825 Binary files /dev/null and b/osx/dist/DoayeeESP32DFU.app/Contents/Resources/logo.png.icns differ diff --git a/osxgui.png b/osxgui.png new file mode 100644 index 0000000..50e3d36 Binary files /dev/null and b/osxgui.png differ diff --git a/pyaes/__init__.py b/pyaes/__init__.py new file mode 100644 index 0000000..5712f79 --- /dev/null +++ b/pyaes/__init__.py @@ -0,0 +1,53 @@ +# The MIT License (MIT) +# +# Copyright (c) 2014 Richard Moore +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# This is a pure-Python implementation of the AES algorithm and AES common +# modes of operation. + +# See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard +# See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation + + +# Supported key sizes: +# 128-bit +# 192-bit +# 256-bit + + +# Supported modes of operation: +# ECB - Electronic Codebook +# CBC - Cipher-Block Chaining +# CFB - Cipher Feedback +# OFB - Output Feedback +# CTR - Counter + +# See the README.md for API details and general information. + +# Also useful, PyCrypto, a crypto library implemented in C with Python bindings: +# https://www.dlitz.net/software/pycrypto/ + + +VERSION = [1, 3, 0] + +from .aes import AES, AESModeOfOperationCTR, AESModeOfOperationCBC, AESModeOfOperationCFB, AESModeOfOperationECB, AESModeOfOperationOFB, AESModesOfOperation, Counter +from .blockfeeder import decrypt_stream, Decrypter, encrypt_stream, Encrypter +from .blockfeeder import PADDING_NONE, PADDING_DEFAULT diff --git a/pyaes/aes.py b/pyaes/aes.py new file mode 100644 index 0000000..c6e8bc0 --- /dev/null +++ b/pyaes/aes.py @@ -0,0 +1,589 @@ +# The MIT License (MIT) +# +# Copyright (c) 2014 Richard Moore +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# This is a pure-Python implementation of the AES algorithm and AES common +# modes of operation. + +# See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard + +# Honestly, the best description of the modes of operations are the wonderful +# diagrams on Wikipedia. They explain in moments what my words could never +# achieve. Hence the inline documentation here is sparer than I'd prefer. +# See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation + +# Also useful, PyCrypto, a crypto library implemented in C with Python bindings: +# https://www.dlitz.net/software/pycrypto/ + + +# Supported key sizes: +# 128-bit +# 192-bit +# 256-bit + + +# Supported modes of operation: +# ECB - Electronic Codebook +# CBC - Cipher-Block Chaining +# CFB - Cipher Feedback +# OFB - Output Feedback +# CTR - Counter + + +# See the README.md for API details and general information. + + +import copy +import struct + +__all__ = ["AES", "AESModeOfOperationCTR", "AESModeOfOperationCBC", "AESModeOfOperationCFB", + "AESModeOfOperationECB", "AESModeOfOperationOFB", "AESModesOfOperation", "Counter"] + + +def _compact_word(word): + return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3] + +def _string_to_bytes(text): + return list(ord(c) for c in text) + +def _bytes_to_string(binary): + return "".join(chr(b) for b in binary) + +def _concat_list(a, b): + return a + b + + +# Python 3 compatibility +try: + xrange +except Exception: + xrange = range + + # Python 3 supports bytes, which is already an array of integers + def _string_to_bytes(text): + if isinstance(text, bytes): + return text + return [ord(c) for c in text] + + # In Python 3, we return bytes + def _bytes_to_string(binary): + return bytes(binary) + + # Python 3 cannot concatenate a list onto a bytes, so we bytes-ify it first + def _concat_list(a, b): + return a + bytes(b) + + +# Based *largely* on the Rijndael implementation +# See: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf +class AES(object): + '''Encapsulates the AES block cipher. + + You generally should not need this. Use the AESModeOfOperation classes + below instead.''' + + # Number of rounds by keysize + number_of_rounds = {16: 10, 24: 12, 32: 14} + + # Round constant words + rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ] + + # S-box and Inverse S-box (S is for Substitution) + S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ] + Si =[ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ] + + # Transformations for encryption + T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ] + T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ] + T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ] + T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ] + + # Transformations for decryption + T5 = [ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 ] + T6 = [ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 ] + T7 = [ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 ] + T8 = [ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 ] + + # Transformations for decryption key expansion + U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ] + U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ] + U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ] + U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ] + + def __init__(self, key): + + if len(key) not in (16, 24, 32): + raise ValueError('Invalid key size') + + rounds = self.number_of_rounds[len(key)] + + # Encryption round keys + self._Ke = [[0] * 4 for i in xrange(rounds + 1)] + + # Decryption round keys + self._Kd = [[0] * 4 for i in xrange(rounds + 1)] + + round_key_count = (rounds + 1) * 4 + KC = len(key) // 4 + + # Convert the key into ints + tk = [ struct.unpack('>i', key[i:i + 4])[0] for i in xrange(0, len(key), 4) ] + + # Copy values into round key arrays + for i in xrange(0, KC): + self._Ke[i // 4][i % 4] = tk[i] + self._Kd[rounds - (i // 4)][i % 4] = tk[i] + + # Key expansion (fips-197 section 5.2) + rconpointer = 0 + t = KC + while t < round_key_count: + + tt = tk[KC - 1] + tk[0] ^= ((self.S[(tt >> 16) & 0xFF] << 24) ^ + (self.S[(tt >> 8) & 0xFF] << 16) ^ + (self.S[ tt & 0xFF] << 8) ^ + self.S[(tt >> 24) & 0xFF] ^ + (self.rcon[rconpointer] << 24)) + rconpointer += 1 + + if KC != 8: + for i in xrange(1, KC): + tk[i] ^= tk[i - 1] + + # Key expansion for 256-bit keys is "slightly different" (fips-197) + else: + for i in xrange(1, KC // 2): + tk[i] ^= tk[i - 1] + tt = tk[KC // 2 - 1] + + tk[KC // 2] ^= (self.S[ tt & 0xFF] ^ + (self.S[(tt >> 8) & 0xFF] << 8) ^ + (self.S[(tt >> 16) & 0xFF] << 16) ^ + (self.S[(tt >> 24) & 0xFF] << 24)) + + for i in xrange(KC // 2 + 1, KC): + tk[i] ^= tk[i - 1] + + # Copy values into round key arrays + j = 0 + while j < KC and t < round_key_count: + self._Ke[t // 4][t % 4] = tk[j] + self._Kd[rounds - (t // 4)][t % 4] = tk[j] + j += 1 + t += 1 + + # Inverse-Cipher-ify the decryption round key (fips-197 section 5.3) + for r in xrange(1, rounds): + for j in xrange(0, 4): + tt = self._Kd[r][j] + self._Kd[r][j] = (self.U1[(tt >> 24) & 0xFF] ^ + self.U2[(tt >> 16) & 0xFF] ^ + self.U3[(tt >> 8) & 0xFF] ^ + self.U4[ tt & 0xFF]) + + def encrypt(self, plaintext): + 'Encrypt a block of plain text using the AES block cipher.' + + if len(plaintext) != 16: + raise ValueError('wrong block length') + + rounds = len(self._Ke) - 1 + (s1, s2, s3) = [1, 2, 3] + a = [0, 0, 0, 0] + + # Convert plaintext to (ints ^ key) + t = [(_compact_word(plaintext[4 * i:4 * i + 4]) ^ self._Ke[0][i]) for i in xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(0, 4): + a[i] = (self.T1[(t[ i ] >> 24) & 0xFF] ^ + self.T2[(t[(i + s1) % 4] >> 16) & 0xFF] ^ + self.T3[(t[(i + s2) % 4] >> 8) & 0xFF] ^ + self.T4[ t[(i + s3) % 4] & 0xFF] ^ + self._Ke[r][i]) + t = copy.copy(a) + + # The last round is special + result = [ ] + for i in xrange(0, 4): + tt = self._Ke[rounds][i] + result.append((self.S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) + result.append((self.S[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) + result.append((self.S[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) + result.append((self.S[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF) + + return result + + def decrypt(self, ciphertext): + 'Decrypt a block of cipher text using the AES block cipher.' + + if len(ciphertext) != 16: + raise ValueError('wrong block length') + + rounds = len(self._Kd) - 1 + (s1, s2, s3) = [3, 2, 1] + a = [0, 0, 0, 0] + + # Convert ciphertext to (ints ^ key) + t = [(_compact_word(ciphertext[4 * i:4 * i + 4]) ^ self._Kd[0][i]) for i in xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(0, 4): + a[i] = (self.T5[(t[ i ] >> 24) & 0xFF] ^ + self.T6[(t[(i + s1) % 4] >> 16) & 0xFF] ^ + self.T7[(t[(i + s2) % 4] >> 8) & 0xFF] ^ + self.T8[ t[(i + s3) % 4] & 0xFF] ^ + self._Kd[r][i]) + t = copy.copy(a) + + # The last round is special + result = [ ] + for i in xrange(0, 4): + tt = self._Kd[rounds][i] + result.append((self.Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) + result.append((self.Si[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) + result.append((self.Si[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) + result.append((self.Si[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF) + + return result + + +class Counter(object): + '''A counter object for the Counter (CTR) mode of operation. + + To create a custom counter, you can usually just override the + increment method.''' + + def __init__(self, initial_value = 1): + + # Convert the value into an array of bytes long + self._counter = [ ((initial_value >> i) % 256) for i in xrange(128 - 8, -1, -8) ] + + value = property(lambda s: s._counter) + + def increment(self): + '''Increment the counter (overflow rolls back to 0).''' + + for i in xrange(len(self._counter) - 1, -1, -1): + self._counter[i] += 1 + + if self._counter[i] < 256: break + + # Carry the one + self._counter[i] = 0 + + # Overflow + else: + self._counter = [ 0 ] * len(self._counter) + + +class AESBlockModeOfOperation(object): + '''Super-class for AES modes of operation that require blocks.''' + def __init__(self, key): + self._aes = AES(key) + + def decrypt(self, ciphertext): + raise Exception('not implemented') + + def encrypt(self, plaintext): + raise Exception('not implemented') + + +class AESStreamModeOfOperation(AESBlockModeOfOperation): + '''Super-class for AES modes of operation that are stream-ciphers.''' + +class AESSegmentModeOfOperation(AESStreamModeOfOperation): + '''Super-class for AES modes of operation that segment data.''' + + segment_bytes = 16 + + + +class AESModeOfOperationECB(AESBlockModeOfOperation): + '''AES Electronic Codebook Mode of Operation. + + o Block-cipher, so data must be padded to 16 byte boundaries + + Security Notes: + o This mode is not recommended + o Any two identical blocks produce identical encrypted values, + exposing data patterns. (See the image of Tux on wikipedia) + + Also see: + o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_.28ECB.29 + o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.1''' + + + name = "Electronic Codebook (ECB)" + + def encrypt(self, plaintext): + if len(plaintext) != 16: + raise ValueError('plaintext block must be 16 bytes') + + plaintext = _string_to_bytes(plaintext) + return _bytes_to_string(self._aes.encrypt(plaintext)) + + def decrypt(self, ciphertext): + if len(ciphertext) != 16: + raise ValueError('ciphertext block must be 16 bytes') + + ciphertext = _string_to_bytes(ciphertext) + return _bytes_to_string(self._aes.decrypt(ciphertext)) + + + +class AESModeOfOperationCBC(AESBlockModeOfOperation): + '''AES Cipher-Block Chaining Mode of Operation. + + o The Initialization Vector (IV) + o Block-cipher, so data must be padded to 16 byte boundaries + o An incorrect initialization vector will only cause the first + block to be corrupt; all other blocks will be intact + o A corrupt bit in the cipher text will cause a block to be + corrupted, and the next block to be inverted, but all other + blocks will be intact. + + Security Notes: + o This method (and CTR) ARE recommended. + + Also see: + o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29 + o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.2''' + + + name = "Cipher-Block Chaining (CBC)" + + def __init__(self, key, iv = None): + if iv is None: + self._last_cipherblock = [ 0 ] * 16 + elif len(iv) != 16: + raise ValueError('initialization vector must be 16 bytes') + else: + self._last_cipherblock = _string_to_bytes(iv) + + AESBlockModeOfOperation.__init__(self, key) + + def encrypt(self, plaintext): + if len(plaintext) != 16: + raise ValueError('plaintext block must be 16 bytes') + + plaintext = _string_to_bytes(plaintext) + precipherblock = [ (p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock) ] + self._last_cipherblock = self._aes.encrypt(precipherblock) + + return _bytes_to_string(self._last_cipherblock) + + def decrypt(self, ciphertext): + if len(ciphertext) != 16: + raise ValueError('ciphertext block must be 16 bytes') + + cipherblock = _string_to_bytes(ciphertext) + plaintext = [ (p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock) ] + self._last_cipherblock = cipherblock + + return _bytes_to_string(plaintext) + + + +class AESModeOfOperationCFB(AESSegmentModeOfOperation): + '''AES Cipher Feedback Mode of Operation. + + o A stream-cipher, so input does not need to be padded to blocks, + but does need to be padded to segment_size + + Also see: + o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_.28CFB.29 + o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.3''' + + + name = "Cipher Feedback (CFB)" + + def __init__(self, key, iv, segment_size = 1): + if segment_size == 0: segment_size = 1 + + if iv is None: + self._shift_register = [ 0 ] * 16 + elif len(iv) != 16: + raise ValueError('initialization vector must be 16 bytes') + else: + self._shift_register = _string_to_bytes(iv) + + self._segment_bytes = segment_size + + AESBlockModeOfOperation.__init__(self, key) + + segment_bytes = property(lambda s: s._segment_bytes) + + def encrypt(self, plaintext): + if len(plaintext) % self._segment_bytes != 0: + raise ValueError('plaintext block must be a multiple of segment_size') + + plaintext = _string_to_bytes(plaintext) + + # Break block into segments + encrypted = [ ] + for i in xrange(0, len(plaintext), self._segment_bytes): + plaintext_segment = plaintext[i: i + self._segment_bytes] + xor_segment = self._aes.encrypt(self._shift_register)[:len(plaintext_segment)] + cipher_segment = [ (p ^ x) for (p, x) in zip(plaintext_segment, xor_segment) ] + + # Shift the top bits out and the ciphertext in + self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment) + + encrypted.extend(cipher_segment) + + return _bytes_to_string(encrypted) + + def decrypt(self, ciphertext): + if len(ciphertext) % self._segment_bytes != 0: + raise ValueError('ciphertext block must be a multiple of segment_size') + + ciphertext = _string_to_bytes(ciphertext) + + # Break block into segments + decrypted = [ ] + for i in xrange(0, len(ciphertext), self._segment_bytes): + cipher_segment = ciphertext[i: i + self._segment_bytes] + xor_segment = self._aes.encrypt(self._shift_register)[:len(cipher_segment)] + plaintext_segment = [ (p ^ x) for (p, x) in zip(cipher_segment, xor_segment) ] + + # Shift the top bits out and the ciphertext in + self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment) + + decrypted.extend(plaintext_segment) + + return _bytes_to_string(decrypted) + + + +class AESModeOfOperationOFB(AESStreamModeOfOperation): + '''AES Output Feedback Mode of Operation. + + o A stream-cipher, so input does not need to be padded to blocks, + allowing arbitrary length data. + o A bit twiddled in the cipher text, twiddles the same bit in the + same bit in the plain text, which can be useful for error + correction techniques. + + Also see: + o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_.28OFB.29 + o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.4''' + + + name = "Output Feedback (OFB)" + + def __init__(self, key, iv = None): + if iv is None: + self._last_precipherblock = [ 0 ] * 16 + elif len(iv) != 16: + raise ValueError('initialization vector must be 16 bytes') + else: + self._last_precipherblock = _string_to_bytes(iv) + + self._remaining_block = [ ] + + AESBlockModeOfOperation.__init__(self, key) + + def encrypt(self, plaintext): + encrypted = [ ] + for p in _string_to_bytes(plaintext): + if len(self._remaining_block) == 0: + self._remaining_block = self._aes.encrypt(self._last_precipherblock) + self._last_precipherblock = [ ] + precipherbyte = self._remaining_block.pop(0) + self._last_precipherblock.append(precipherbyte) + cipherbyte = p ^ precipherbyte + encrypted.append(cipherbyte) + + return _bytes_to_string(encrypted) + + def decrypt(self, ciphertext): + # AES-OFB is symetric + return self.encrypt(ciphertext) + + + +class AESModeOfOperationCTR(AESStreamModeOfOperation): + '''AES Counter Mode of Operation. + + o A stream-cipher, so input does not need to be padded to blocks, + allowing arbitrary length data. + o The counter must be the same size as the key size (ie. len(key)) + o Each block independant of the other, so a corrupt byte will not + damage future blocks. + o Each block has a uniue counter value associated with it, which + contributes to the encrypted value, so no data patterns are + leaked. + o Also known as: Counter Mode (CM), Integer Counter Mode (ICM) and + Segmented Integer Counter (SIC + + Security Notes: + o This method (and CBC) ARE recommended. + o Each message block is associated with a counter value which must be + unique for ALL messages with the same key. Otherwise security may be + compromised. + + Also see: + + o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29 + o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.5 + and Appendix B for managing the initial counter''' + + + name = "Counter (CTR)" + + def __init__(self, key, counter = None): + AESBlockModeOfOperation.__init__(self, key) + + if counter is None: + counter = Counter() + + self._counter = counter + self._remaining_counter = [ ] + + def encrypt(self, plaintext): + while len(self._remaining_counter) < len(plaintext): + self._remaining_counter += self._aes.encrypt(self._counter.value) + self._counter.increment() + + plaintext = _string_to_bytes(plaintext) + + encrypted = [ (p ^ c) for (p, c) in zip(plaintext, self._remaining_counter) ] + self._remaining_counter = self._remaining_counter[len(encrypted):] + + return _bytes_to_string(encrypted) + + def decrypt(self, crypttext): + # AES-CTR is symetric + return self.encrypt(crypttext) + + +# Simple lookup table for each mode +AESModesOfOperation = dict( + ctr = AESModeOfOperationCTR, + cbc = AESModeOfOperationCBC, + cfb = AESModeOfOperationCFB, + ecb = AESModeOfOperationECB, + ofb = AESModeOfOperationOFB, +) diff --git a/pyaes/blockfeeder.py b/pyaes/blockfeeder.py new file mode 100644 index 0000000..f4113c3 --- /dev/null +++ b/pyaes/blockfeeder.py @@ -0,0 +1,227 @@ +# The MIT License (MIT) +# +# Copyright (c) 2014 Richard Moore +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + + +from .aes import AESBlockModeOfOperation, AESSegmentModeOfOperation, AESStreamModeOfOperation +from .util import append_PKCS7_padding, strip_PKCS7_padding, to_bufferable + + +# First we inject three functions to each of the modes of operations +# +# _can_consume(size) +# - Given a size, determine how many bytes could be consumed in +# a single call to either the decrypt or encrypt method +# +# _final_encrypt(data, padding = PADDING_DEFAULT) +# - call and return encrypt on this (last) chunk of data, +# padding as necessary; this will always be at least 16 +# bytes unless the total incoming input was less than 16 +# bytes +# +# _final_decrypt(data, padding = PADDING_DEFAULT) +# - same as _final_encrypt except for decrypt, for +# stripping off padding +# + +PADDING_NONE = 'none' +PADDING_DEFAULT = 'default' + +# @TODO: Ciphertext stealing and explicit PKCS#7 +# PADDING_CIPHERTEXT_STEALING +# PADDING_PKCS7 + +# ECB and CBC are block-only ciphers + +def _block_can_consume(self, size): + if size >= 16: return 16 + return 0 + +# After padding, we may have more than one block +def _block_final_encrypt(self, data, padding = PADDING_DEFAULT): + if padding == PADDING_DEFAULT: + data = append_PKCS7_padding(data) + + elif padding == PADDING_NONE: + if len(data) != 16: + raise Exception('invalid data length for final block') + else: + raise Exception('invalid padding option') + + if len(data) == 32: + return self.encrypt(data[:16]) + self.encrypt(data[16:]) + + return self.encrypt(data) + + +def _block_final_decrypt(self, data, padding = PADDING_DEFAULT): + if padding == PADDING_DEFAULT: + return strip_PKCS7_padding(self.decrypt(data)) + + if padding == PADDING_NONE: + if len(data) != 16: + raise Exception('invalid data length for final block') + return self.decrypt(data) + + raise Exception('invalid padding option') + +AESBlockModeOfOperation._can_consume = _block_can_consume +AESBlockModeOfOperation._final_encrypt = _block_final_encrypt +AESBlockModeOfOperation._final_decrypt = _block_final_decrypt + + + +# CFB is a segment cipher + +def _segment_can_consume(self, size): + return self.segment_bytes * int(size // self.segment_bytes) + +# CFB can handle a non-segment-sized block at the end using the remaining cipherblock +def _segment_final_encrypt(self, data, padding = PADDING_DEFAULT): + if padding != PADDING_DEFAULT: + raise Exception('invalid padding option') + + faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes))) + padded = data + to_bufferable(faux_padding) + return self.encrypt(padded)[:len(data)] + +# CFB can handle a non-segment-sized block at the end using the remaining cipherblock +def _segment_final_decrypt(self, data, padding = PADDING_DEFAULT): + if padding != PADDING_DEFAULT: + raise Exception('invalid padding option') + + faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes))) + padded = data + to_bufferable(faux_padding) + return self.decrypt(padded)[:len(data)] + +AESSegmentModeOfOperation._can_consume = _segment_can_consume +AESSegmentModeOfOperation._final_encrypt = _segment_final_encrypt +AESSegmentModeOfOperation._final_decrypt = _segment_final_decrypt + + + +# OFB and CTR are stream ciphers + +def _stream_can_consume(self, size): + return size + +def _stream_final_encrypt(self, data, padding = PADDING_DEFAULT): + if padding not in [PADDING_NONE, PADDING_DEFAULT]: + raise Exception('invalid padding option') + + return self.encrypt(data) + +def _stream_final_decrypt(self, data, padding = PADDING_DEFAULT): + if padding not in [PADDING_NONE, PADDING_DEFAULT]: + raise Exception('invalid padding option') + + return self.decrypt(data) + +AESStreamModeOfOperation._can_consume = _stream_can_consume +AESStreamModeOfOperation._final_encrypt = _stream_final_encrypt +AESStreamModeOfOperation._final_decrypt = _stream_final_decrypt + + + +class BlockFeeder(object): + '''The super-class for objects to handle chunking a stream of bytes + into the appropriate block size for the underlying mode of operation + and applying (or stripping) padding, as necessary.''' + + def __init__(self, mode, feed, final, padding = PADDING_DEFAULT): + self._mode = mode + self._feed = feed + self._final = final + self._buffer = to_bufferable("") + self._padding = padding + + def feed(self, data = None): + '''Provide bytes to encrypt (or decrypt), returning any bytes + possible from this or any previous calls to feed. + + Call with None or an empty string to flush the mode of + operation and return any final bytes; no further calls to + feed may be made.''' + + if self._buffer is None: + raise ValueError('already finished feeder') + + # Finalize; process the spare bytes we were keeping + if not data: + result = self._final(self._buffer, self._padding) + self._buffer = None + return result + + self._buffer += to_bufferable(data) + + # We keep 16 bytes around so we can determine padding + result = to_bufferable('') + while len(self._buffer) > 16: + can_consume = self._mode._can_consume(len(self._buffer) - 16) + if can_consume == 0: break + result += self._feed(self._buffer[:can_consume]) + self._buffer = self._buffer[can_consume:] + + return result + + +class Encrypter(BlockFeeder): + 'Accepts bytes of plaintext and returns encrypted ciphertext.' + + def __init__(self, mode, padding = PADDING_DEFAULT): + BlockFeeder.__init__(self, mode, mode.encrypt, mode._final_encrypt, padding) + + +class Decrypter(BlockFeeder): + 'Accepts bytes of ciphertext and returns decrypted plaintext.' + + def __init__(self, mode, padding = PADDING_DEFAULT): + BlockFeeder.__init__(self, mode, mode.decrypt, mode._final_decrypt, padding) + + +# 8kb blocks +BLOCK_SIZE = (1 << 13) + +def _feed_stream(feeder, in_stream, out_stream, block_size = BLOCK_SIZE): + 'Uses feeder to read and convert from in_stream and write to out_stream.' + + while True: + chunk = in_stream.read(block_size) + if not chunk: + break + converted = feeder.feed(chunk) + out_stream.write(converted) + converted = feeder.feed() + out_stream.write(converted) + + +def encrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT): + 'Encrypts a stream of bytes from in_stream to out_stream using mode.' + + encrypter = Encrypter(mode, padding = padding) + _feed_stream(encrypter, in_stream, out_stream, block_size) + + +def decrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT): + 'Decrypts a stream of bytes from in_stream to out_stream using mode.' + + decrypter = Decrypter(mode, padding = padding) + _feed_stream(decrypter, in_stream, out_stream, block_size) diff --git a/pyaes/util.py b/pyaes/util.py new file mode 100644 index 0000000..081a375 --- /dev/null +++ b/pyaes/util.py @@ -0,0 +1,60 @@ +# The MIT License (MIT) +# +# Copyright (c) 2014 Richard Moore +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# Why to_bufferable? +# Python 3 is very different from Python 2.x when it comes to strings of text +# and strings of bytes; in Python 3, strings of bytes do not exist, instead to +# represent arbitrary binary data, we must use the "bytes" object. This method +# ensures the object behaves as we need it to. + +def to_bufferable(binary): + return binary + +def _get_byte(c): + return ord(c) + +try: + xrange +except: + + def to_bufferable(binary): + if isinstance(binary, bytes): + return binary + return bytes(ord(b) for b in binary) + + def _get_byte(c): + return c + +def append_PKCS7_padding(data): + pad = 16 - (len(data) % 16) + return data + to_bufferable(chr(pad) * pad) + +def strip_PKCS7_padding(data): + if len(data) % 16 != 0: + raise ValueError("invalid length") + + pad = _get_byte(data[-1]) + + if pad > 16: + raise ValueError("invalid padding byte") + + return data[:-pad] diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..d69aa54 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[flake8] +ignore = E231,E221,FI50,FI11,FI12,FI53,FI14,FI15,FI16,FI17,E241,W503,W504 +max-line-length = 160 + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4e58c9c --- /dev/null +++ b/setup.py @@ -0,0 +1,116 @@ +from __future__ import division, print_function + +import io +import os +import re + +from setuptools import setup + + +# Example code to pull version from esptool.py with regex, taken from +# http://python-packaging-user-guide.readthedocs.org/en/latest/single_source_version/ +def read(*names, **kwargs): + with io.open( + os.path.join(os.path.dirname(__file__), *names), + encoding=kwargs.get("encoding", "utf8") + ) as fp: + return fp.read() + + +def find_version(*file_paths): + version_file = read(*file_paths) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + version_file, re.M) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") + + +long_description = """ +========== +esptool.py +========== +A command line utility to communicate with the ROM bootloader in Espressif ESP8266 & ESP32 microcontrollers. + +Allows flashing firmware, reading back firmware, querying chip parameters, etc. + +The esptool.py project is hosted on github: https://github.com/espressif/esptool + +Installation +------------ + +esptool can be installed via pip: + + $ pip install --upgrade esptool + +Since version 1.3, esptool supports both Python 2.7 and Python 3.4 or newer. + +Since version 2.0, esptool supports both ESP8266 & ESP32. + +Usage +----- + +Please see the `Usage section of the README.md file `_. + +You can also get help information by running `esptool.py --help`. + +Contributing +------------ +Please see the `CONTRIBUTING.md file on github `_. +""" + +# For Windows, we want to install esptool.py.exe, etc. so that normal Windows command line can run them +# For Linux/macOS, we can't use console_scripts with extension .py as their names will clash with the modules' names. +if os.name == "nt": + scripts = None + entry_points = { + 'console_scripts': [ + 'esptool.py=esptool:_main', + 'espsecure.py=espsecure:_main', + 'espefuse.py=espefuse:_main', + ], + } +else: + scripts = ['esptool.py', + 'espsecure.py', + 'espefuse.py'] + entry_points = None + +setup( + name='esptool', + py_modules=['esptool', 'espsecure', 'espefuse'], + version=find_version('esptool.py'), + description='A serial utility to communicate & flash code to Espressif ESP8266 & ESP32 chips.', + long_description=long_description, + url='https://github.com/espressif/esptool', + author='Fredrik Ahlberg (themadinventor) & Angus Gratton (projectgus)', + author_email='angus@espressif.com', + license='GPLv2+', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'Operating System :: POSIX', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS :: MacOS X', + 'Topic :: Software Development :: Embedded Systems', + 'Environment :: Console', + 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], + tests_require=[ + 'flake8>=3.2.0', + 'flake8-future-import', + 'flake8-import-order', + ], + install_requires=[ + 'pyserial>=3.0', + 'pyaes', + 'ecdsa', + ], + scripts=scripts, + entry_points=entry_points, +) diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..d85741a --- /dev/null +++ b/test/README.md @@ -0,0 +1,24 @@ +# esptool.py test cases README + +# test_elf2image.py + +Exists to catch unexpected changes in elf2image or image_info output. Does not require an ESP8266 to verify. + +## About Tests + +Test method is fairly lo-fi: + +Directory test/elf2image/ contains subdirectories esp8266-v1, esp8266-v2 and esp32. These contain test cases. + +Each test case is a .elf file, which is stored alongside one or more .bin files representing the output of elf2image, and one .txt file representing the output of image_info when reading back the binary. + +Default run of test_elf2image.py will re-run elf2image & image_info on all these images. Suitable --chip and --version args are supplied, determined by the directory name. + +The test runner verifies that nothing in the output of either command has changed. + +## Dealing With Output Changes + +If changes are detected, we can check if valid images are still being produced. If the changes turn out to be OK, running "test_elf2image.py --regen" will regenerate all of the .bin and .txt files for the test cases. + +(--regen can also be used to evaluate test failures, by looking at git diff output.) + diff --git a/test/ecdsa_secure_boot_signing_key2.pem b/test/ecdsa_secure_boot_signing_key2.pem new file mode 100644 index 0000000..32f3e36 --- /dev/null +++ b/test/ecdsa_secure_boot_signing_key2.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIINRYjKaXM0dQJn4FLwoHYj3ovmpRee7UWv8ksQ9IS2toAoGCCqGSM49 +AwEHoUQDQgAEYWCmqAxxAmbr8cZk4AjTYkQJ0pCZOsESk2bPAe6lrzHrFKKR2t2W +ge+cNvXU2dYswEdgWr/fdnyAbHHEVEBSrA== +-----END EC PRIVATE KEY----- diff --git a/test/elf2image/esp32-app-template.elf b/test/elf2image/esp32-app-template.elf new file mode 100644 index 0000000..1f70758 Binary files /dev/null and b/test/elf2image/esp32-app-template.elf differ diff --git a/test/elf2image/esp32-bootloader.elf b/test/elf2image/esp32-bootloader.elf new file mode 100644 index 0000000..30befee Binary files /dev/null and b/test/elf2image/esp32-bootloader.elf differ diff --git a/test/elf2image/esp32-too-many-sections.elf b/test/elf2image/esp32-too-many-sections.elf new file mode 100644 index 0000000..b6c4d29 Binary files /dev/null and b/test/elf2image/esp32-too-many-sections.elf differ diff --git a/test/elf2image/esp8266-nonossdkv12-example.elf b/test/elf2image/esp8266-nonossdkv12-example.elf new file mode 100644 index 0000000..cc58201 Binary files /dev/null and b/test/elf2image/esp8266-nonossdkv12-example.elf differ diff --git a/test/elf2image/esp8266-nonossdkv20-at-v2.elf b/test/elf2image/esp8266-nonossdkv20-at-v2.elf new file mode 100644 index 0000000..ba31893 Binary files /dev/null and b/test/elf2image/esp8266-nonossdkv20-at-v2.elf differ diff --git a/test/elf2image/esp8266-nonosssdk20-iotdemo.elf b/test/elf2image/esp8266-nonosssdk20-iotdemo.elf new file mode 100644 index 0000000..38e3e2d Binary files /dev/null and b/test/elf2image/esp8266-nonosssdk20-iotdemo.elf differ diff --git a/test/elf2image/esp8266-openrtos-blink-v2.elf b/test/elf2image/esp8266-openrtos-blink-v2.elf new file mode 100644 index 0000000..07a71fb Binary files /dev/null and b/test/elf2image/esp8266-openrtos-blink-v2.elf differ diff --git a/test/images/bootloader.bin b/test/images/bootloader.bin new file mode 100644 index 0000000..6b23443 Binary files /dev/null and b/test/images/bootloader.bin differ diff --git a/test/images/esp8266_sdk/4096_user1.bin b/test/images/esp8266_sdk/4096_user1.bin new file mode 100644 index 0000000..2615b90 Binary files /dev/null and b/test/images/esp8266_sdk/4096_user1.bin differ diff --git a/test/images/esp8266_sdk/blank.bin b/test/images/esp8266_sdk/blank.bin new file mode 100644 index 0000000..7de9e36 --- /dev/null +++ b/test/images/esp8266_sdk/blank.bin @@ -0,0 +1 @@ +ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/test/images/esp8266_sdk/boot_v1.4(b1).bin b/test/images/esp8266_sdk/boot_v1.4(b1).bin new file mode 100644 index 0000000..7692abd Binary files /dev/null and b/test/images/esp8266_sdk/boot_v1.4(b1).bin differ diff --git a/test/images/esp8266_sdk/esp_init_data_default.bin b/test/images/esp8266_sdk/esp_init_data_default.bin new file mode 100644 index 0000000..73d7453 Binary files /dev/null and b/test/images/esp8266_sdk/esp_init_data_default.bin differ diff --git a/test/images/fifty_kb.bin b/test/images/fifty_kb.bin new file mode 100644 index 0000000..343282a Binary files /dev/null and b/test/images/fifty_kb.bin differ diff --git a/test/images/helloworld-esp32.bin b/test/images/helloworld-esp32.bin new file mode 100644 index 0000000..a52d3fb Binary files /dev/null and b/test/images/helloworld-esp32.bin differ diff --git a/test/images/helloworld-esp8266.bin b/test/images/helloworld-esp8266.bin new file mode 100644 index 0000000..ae7d316 Binary files /dev/null and b/test/images/helloworld-esp8266.bin differ diff --git a/test/images/image_header_only.bin b/test/images/image_header_only.bin new file mode 100644 index 0000000..2051118 Binary files /dev/null and b/test/images/image_header_only.bin differ diff --git a/test/images/nodemcu-master-7-modules-2017-01-19-11-10-03-integer.bin b/test/images/nodemcu-master-7-modules-2017-01-19-11-10-03-integer.bin new file mode 100644 index 0000000..97cc4f6 Binary files /dev/null and b/test/images/nodemcu-master-7-modules-2017-01-19-11-10-03-integer.bin differ diff --git a/test/images/one_kb.bin b/test/images/one_kb.bin new file mode 100644 index 0000000..98dcc9b Binary files /dev/null and b/test/images/one_kb.bin differ diff --git a/test/images/one_mb.bin b/test/images/one_mb.bin new file mode 100644 index 0000000..b97fd47 Binary files /dev/null and b/test/images/one_mb.bin differ diff --git a/test/images/one_mb_zeroes.bin b/test/images/one_mb_zeroes.bin new file mode 100644 index 0000000..9e0f96a Binary files /dev/null and b/test/images/one_mb_zeroes.bin differ diff --git a/test/images/onebyte.bin b/test/images/onebyte.bin new file mode 100644 index 0000000..2e65efe --- /dev/null +++ b/test/images/onebyte.bin @@ -0,0 +1 @@ +a \ No newline at end of file diff --git a/test/images/partitions_singleapp.bin b/test/images/partitions_singleapp.bin new file mode 100644 index 0000000..0c4252d Binary files /dev/null and b/test/images/partitions_singleapp.bin differ diff --git a/test/images/sector.bin b/test/images/sector.bin new file mode 100644 index 0000000..cd45016 Binary files /dev/null and b/test/images/sector.bin differ diff --git a/test/images/unaligned.bin b/test/images/unaligned.bin new file mode 100644 index 0000000..eb6cc8d Binary files /dev/null and b/test/images/unaligned.bin differ diff --git a/test/secure_images/256bit_iv.bin b/test/secure_images/256bit_iv.bin new file mode 100644 index 0000000..62723ac --- /dev/null +++ b/test/secure_images/256bit_iv.bin @@ -0,0 +1 @@ +EV8z"Œ ¿%ÜF«+ñxk¥û)~ãkÊ7JIõÀG> \ No newline at end of file diff --git a/test/secure_images/256bit_key.bin b/test/secure_images/256bit_key.bin new file mode 100644 index 0000000..2b6e32e --- /dev/null +++ b/test/secure_images/256bit_key.bin @@ -0,0 +1 @@ +æ>\'*çå›Ã8%ŽÞq+Ý8ÀÂLm*»E‡¢™ß \ No newline at end of file diff --git a/test/secure_images/bootloader.bin b/test/secure_images/bootloader.bin new file mode 100644 index 0000000..0d9df74 Binary files /dev/null and b/test/secure_images/bootloader.bin differ diff --git a/test/secure_images/bootloader_digested.bin b/test/secure_images/bootloader_digested.bin new file mode 100644 index 0000000..244ccca Binary files /dev/null and b/test/secure_images/bootloader_digested.bin differ diff --git a/test/secure_images/bootloader_signed.bin b/test/secure_images/bootloader_signed.bin new file mode 100644 index 0000000..df35d0e Binary files /dev/null and b/test/secure_images/bootloader_signed.bin differ diff --git a/test/secure_images/digest_iv.bin b/test/secure_images/digest_iv.bin new file mode 100644 index 0000000..3709b03 Binary files /dev/null and b/test/secure_images/digest_iv.bin differ diff --git a/test/secure_images/ecdsa_secure_boot_signing_key.pem b/test/secure_images/ecdsa_secure_boot_signing_key.pem new file mode 100644 index 0000000..0f30051 --- /dev/null +++ b/test/secure_images/ecdsa_secure_boot_signing_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIOI40eSC7ch6PxDSIclIGFCjfhCoXfHVqbbDn8XdY0GVoAoGCCqGSM49 +AwEHoUQDQgAENM762z/ushk+c0XOIYpi8wLSWuF5COnU0VAQnt3spTSX6l3bpwfu +ppsemDdwy+aKbdgeyMYDxFbROLOPTRbYJw== +-----END EC PRIVATE KEY----- diff --git a/test/secure_images/ecdsa_secure_boot_signing_key2.pem b/test/secure_images/ecdsa_secure_boot_signing_key2.pem new file mode 100644 index 0000000..51d7388 --- /dev/null +++ b/test/secure_images/ecdsa_secure_boot_signing_key2.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIP9iY7XY8g3qSaUkKwTbq6HEq/AwenIxrssLqXGTS0z3oAoGCCqGSM49 +AwEHoUQDQgAEG+Ah4OAejTBYKQNvJkEOP9AifgulBMr4E9f+OqRU1Uno9Efi1kMc +fzwZyx0A4mib0HfLUg9JNh8dNrUxLeVb4Q== +-----END EC PRIVATE KEY----- diff --git a/test/secure_images/ecdsa_secure_boot_signing_pubkey.pem b/test/secure_images/ecdsa_secure_boot_signing_pubkey.pem new file mode 100644 index 0000000..ec8e37e --- /dev/null +++ b/test/secure_images/ecdsa_secure_boot_signing_pubkey.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENM762z/ushk+c0XOIYpi8wLSWuF5 +COnU0VAQnt3spTSX6l3bpwfuppsemDdwy+aKbdgeyMYDxFbROLOPTRbYJw== +-----END PUBLIC KEY----- diff --git a/test/secure_images/ecdsa_secure_boot_signing_pubkey2.pem b/test/secure_images/ecdsa_secure_boot_signing_pubkey2.pem new file mode 100644 index 0000000..15abeeb --- /dev/null +++ b/test/secure_images/ecdsa_secure_boot_signing_pubkey2.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEG+Ah4OAejTBYKQNvJkEOP9Aifgul +BMr4E9f+OqRU1Uno9Efi1kMcfzwZyx0A4mib0HfLUg9JNh8dNrUxLeVb4Q== +-----END PUBLIC KEY----- diff --git a/test/test_espefuse.py b/test/test_espefuse.py new file mode 100644 index 0000000..527e518 --- /dev/null +++ b/test/test_espefuse.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# +# Tests for espefuse.py on ESP32 +# +# IMPORTANT These are not designed to be run on a real ESP32 chip. +# +# If you force them to run on a real chip, you will corrupt +# the chip's efuses on the first run and not get any useful +# test results either. +# +# Connections: +# RTS (active low) to reset +# DTR (active high) to clear efuses +# +import unittest +import serial +import struct +import sys +import StringIO +import time + +from collections import namedtuple + +sys.path.append('..') +import esptool, espefuse + +global serialport +serialport = None + +# Wrapper class containing all possible espefuse.py command line args +class EspEfuseArgs(object): + def __init__(self): + self.do_not_confirm = True + self.no_protect_key = False + self.force_write_always = False + self.voltage = None + self.block = None + self.keyfile = None + +class EfuseTestCase(unittest.TestCase): + + def setUp(self): + # reset and zero efuses + serialport.dtr = False + serialport.rts = True + time.sleep(0.05) + serialport.rts = False + time.sleep(0.05) + serialport.dtr = True + + # connect & verify efuses are really zero + self.esp = esptool.ESP32ROM(serialport) + self.esp.connect('no_reset') # takes ~7 seconds + self.efuses = espefuse.EspEfuses(self.esp) + + # Check every efuse is zero (~1 second) + for efuse in self.efuses: + val = efuse.get_raw() + BAD_EFUSE_MSG = "Efuse %s not all zeroes - either this is a real ESP32 chip (VERY BAD, read top of file), or the reset is not erasing all efuses correctly." % efuse.register_name + try: + self.assertEqual(b'\x00'*len(val), val, BAD_EFUSE_MSG) + except TypeError: + self.assertEqual(0, val, BAD_EFUSE_MSG) + + def _set_34_coding_scheme(self): + self.efuses["CODING_SCHEME"].burn(1) + # EspEfuses constructor needs to re-load CODING_SCHEME + self.efuses = espefuse.EspEfuses(self.esp) + +class TestBurnKey(EfuseTestCase): + def test_burn_key_no_coding_scheme(self): + key_256bit = b"".join(chr(x+1) for x in range(32)) + self._test_burn_key_common(key_256bit, b"\x00"*32) + + def test_burn_key_34_coding_scheme(self): + self._set_34_coding_scheme() + key_192bit = b"".join(chr(x+0xAA) for x in range(24)) + self._test_burn_key_common(key_192bit, b"\x00"*24) + + def _test_burn_key_common(self, new_key, empty_key): + # Burning key common routine, works in both coding schemes + args = EspEfuseArgs() + args.keyfile = StringIO.StringIO(new_key) + args.do_not_confirm = True + burn_params = (self.esp, self.efuses, args) + + # Burn BLK1 with no protection + args.block = "BLK1" + args.no_protect_key = True + espefuse.burn_key(*burn_params) + key_val = self.efuses["BLK1"].get_key() + self.assertEqual(new_key, key_val) + + # Burn BLK2 and read/write protect + args.no_protect_key = False + args.block = "BLK2" + espefuse.burn_key(*burn_params) + key_val = self.efuses["BLK2"].get_key() + self.assertEqual(empty_key, key_val) + + # Try to burn BLK1 again, will fail as not empty + with self.assertRaises(esptool.FatalError) as fail: + args.block = "BLK1" + espefuse.burn_key(*burn_params) + self.assertIn("already", str(fail.exception)) + + # Try to burn BLK2 again, will fail as protected + with self.assertRaises(esptool.FatalError) as fail: + args.block = "BLK2" + espefuse.burn_key(*burn_params) + self.assertIn("already", str(fail.exception)) + + # Force BLK1 to be burned again (and read protect this time) + args.force_write_always = True + args.block = "BLK1" + espefuse.burn_key(*burn_params) + key_val = self.efuses["BLK1"].get_key() + self.assertEqual(empty_key, key_val) + + self.assertEqual(0, self.efuses.get_coding_scheme_warnings()) + + +class TestBurnBlockData(EfuseTestCase): + + def test_burn_block_data_normal(self): + word_a = 0x1234 + word_b = 0x789A + data = struct.pack(" [optional tests]" % sys.argv[0]) + sys.exit(1) + serialport = serial.Serial(sys.argv[1], 115200) + serialport.dtr = False + serialport.rts = False + + # unittest also uses argv, so trim the args we used + sys.argv = [ sys.argv[0] ] + sys.argv[2:] + print("Running espefuse.py tests...") + unittest.main(buffer=True) diff --git a/test/test_espsecure.py b/test/test_espsecure.py new file mode 100644 index 0000000..5e01ed2 --- /dev/null +++ b/test/test_espsecure.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python +# +# Tests for espsecure.py +# +# Assumes openssl binary is in the PATH +import unittest +import subprocess +import os +import os.path +import io +import sys +import tempfile +import zlib +from collections import namedtuple + +TEST_DIR = os.path.abspath(os.path.dirname(__file__)) +os.chdir(TEST_DIR) + +try: + import espsecure +except ImportError: + sys.path.insert(0, os.path.join(TEST_DIR, "..")) + import espsecure + +import esptool + +class EspSecureTestCase(unittest.TestCase): + + def run_espsecure(self, args): + """ Run espsecure.py with the specified arguments + + Returns output as a string if there is any, raises an exception if espsecure.py fails + """ + cmd = [sys.executable, ESPSECURE_PY ] + args.split(" ") + print("Running %s..." % (" ".join(cmd))) + + try: + output = subprocess.check_output([str(s) for s in cmd], + cwd=TEST_DIR, + stderr=subprocess.STDOUT) + print(output) + return output.decode("utf-8") + except subprocess.CalledProcessError as e: + print(e.output) + raise e + + def setUp(self): + self.cleanup_files = [] # keep a list of files _open()ed by each test case + + def tearDown(self): + for f in self.cleanup_files: + f.close() + + def _open(self, image_file): + f = open(os.path.join('secure_images', image_file), 'rb') + self.cleanup_files.append(f) + return f + + +class ESP32SecureBootloaderTests(EspSecureTestCase): + + def test_digest_bootloader(self): + DBArgs = namedtuple('digest_bootloader_args', [ + 'keyfile', + 'output', + 'iv', + 'image' ]) + + try: + output_file = tempfile.NamedTemporaryFile(delete=False) + output_file.close() + + args = DBArgs(self._open('256bit_key.bin'), + output_file.name, + self._open('256bit_iv.bin'), + self._open('bootloader.bin')) + espsecure.digest_secure_bootloader(args) + + with open(output_file.name, 'rb') as of: + with self._open('bootloader_digested.bin') as ef: + self.assertEqual(ef.read(), of.read()) + finally: + os.unlink(output_file.name) + + +class ECDSASigningTests(EspSecureTestCase): + + VerifyArgs = namedtuple('verify_signature_args', [ + 'keyfile', + 'datafile' ]) + + def test_sign_data(self): + SignArgs = namedtuple('sign_data_args', [ + 'keyfile', + 'output', + 'datafile' ]) + + try: + output_file = tempfile.NamedTemporaryFile(delete=False) + output_file.close() + + # Note: signing bootloader is not actually needed + # for ESP32, it's just a handy file to sign + args = SignArgs(self._open('ecdsa_secure_boot_signing_key.pem'), + output_file.name, + self._open('bootloader.bin')) + espsecure.sign_data(args) + + with open(output_file.name, 'rb') as of: + with self._open('bootloader_signed.bin') as ef: + self.assertEqual(ef.read(), of.read()) + + finally: + os.unlink(output_file.name) + + def test_verify_signature_signing_key(self): + # correct key + args = self.VerifyArgs(self._open('ecdsa_secure_boot_signing_key.pem'), + self._open('bootloader_signed.bin')) + espsecure.verify_signature(args) + + # wrong key + args = self.VerifyArgs(self._open('ecdsa_secure_boot_signing_key2.pem'), + self._open('bootloader_signed.bin')) + with self.assertRaises(esptool.FatalError) as cm: + espsecure.verify_signature(args) + self.assertIn("Signature is not valid", str(cm.exception)) + + def test_verify_signature_public_key(self): + # correct key + args = self.VerifyArgs(self._open('ecdsa_secure_boot_signing_pubkey.pem'), + self._open('bootloader_signed.bin')) + espsecure.verify_signature(args) + + # wrong key + args = self.VerifyArgs(self._open('ecdsa_secure_boot_signing_pubkey2.pem'), + self._open('bootloader_signed.bin')) + with self.assertRaises(esptool.FatalError) as cm: + espsecure.verify_signature(args) + self.assertIn("Signature is not valid", str(cm.exception)) + + def test_extract_binary_public_key(self): + ExtractKeyArgs = namedtuple('extract_public_key_args', + [ 'keyfile', 'public_keyfile' ]) + + pub_keyfile = tempfile.NamedTemporaryFile(delete=False) + pub_keyfile2 = tempfile.NamedTemporaryFile(delete=False) + try: + args = ExtractKeyArgs(self._open('ecdsa_secure_boot_signing_key.pem'), + pub_keyfile) + espsecure.extract_public_key(args) + + args = ExtractKeyArgs(self._open('ecdsa_secure_boot_signing_key2.pem'), + pub_keyfile2) + espsecure.extract_public_key(args) + + pub_keyfile.seek(0) + pub_keyfile2.seek(0) + + # use correct extracted public key to verify + args = self.VerifyArgs(pub_keyfile, self._open('bootloader_signed.bin')) + espsecure.verify_signature(args) + + # use wrong extracted public key to try and verify + args = self.VerifyArgs(pub_keyfile2, self._open('bootloader_signed.bin')) + with self.assertRaises(esptool.FatalError) as cm: + espsecure.verify_signature(args) + self.assertIn("Signature is not valid", str(cm.exception)) + + finally: + os.unlink(pub_keyfile.name) + os.unlink(pub_keyfile2.name) + + +class ESP32FlashEncryptionTests(EspSecureTestCase): + + def test_encrypt_decrypt(self): + EncryptArgs = namedtuple('encrypt_flash_data_args', + [ 'keyfile', + 'output', + 'address', + 'flash_crypt_conf', + 'plaintext_file' + ]) + + DecryptArgs = namedtuple('decrypt_flash_data_args', + [ 'keyfile', + 'output', + 'address', + 'flash_crypt_conf', + 'encrypted_file' + ]) + + original_plaintext = self._open('bootloader.bin') + keyfile = self._open('256bit_key.bin') + ciphertext = io.BytesIO() + + args = EncryptArgs(keyfile, + ciphertext, + 0x1000, + 0xFF, + original_plaintext) + espsecure.encrypt_flash_data(args) + + self.assertNotEqual(original_plaintext, ciphertext.getvalue()) + # use compressed size as entropy estimate for effectiveness + compressed_cipher = zlib.compress(ciphertext.getvalue()) + self.assertGreaterEqual(len(compressed_cipher), len(ciphertext.getvalue())) + + ciphertext.seek(0) + keyfile.seek(0) + plaintext = io.BytesIO() + args = DecryptArgs(keyfile, + plaintext, + 0x1000, + 0xFF, + ciphertext) + espsecure.decrypt_flash_data(args) + + original_plaintext.seek(0) + self.assertEqual(original_plaintext.read(), plaintext.getvalue()) + + +if __name__ == '__main__': + print("Running espsecure tests...") + print("Using espsecure %s at %s" % (esptool.__version__, os.path.abspath(espsecure.__file__))) + unittest.main(buffer=True) diff --git a/test/test_esptool.py b/test/test_esptool.py new file mode 100644 index 0000000..a9c5764 --- /dev/null +++ b/test/test_esptool.py @@ -0,0 +1,447 @@ +#!/usr/bin/env python +""" +esptool.py "unit" tests (really integration tests). Uses a device connected to the serial port. + +WILL MESS UP THE DEVICE'S SPI FLASH CONTENTS + +Chip name & serial port are passed in as arguments to test. Same test suite +runs on esp8266 & esp32 (some addresses will change, see below.) + +""" +import os +import os.path +import re +import subprocess +import sys +import tempfile +import time +import unittest +import serial + +# point is this file is not 4 byte aligned in length +NODEMCU_FILE = "nodemcu-master-7-modules-2017-01-19-11-10-03-integer.bin" + +TEST_DIR = os.path.abspath(os.path.dirname(__file__)) +os.chdir(os.path.dirname(__file__)) +try: + ESPTOOL_PY = os.environ["ESPTOOL_PY"] +except KeyError: + ESPTOOL_PY = os.path.join(TEST_DIR, "..", "esptool.py") + +# Command line options for test environment +global default_baudrate, chip, serialport, trace_enabled +default_baudrate = 115200 +serialport = None +trace_enabled = False + +try: + chip = sys.argv[2] +except IndexError: + chip = None + +RETURN_CODE_FATAL_ERROR = 2 + +class EsptoolTestCase(unittest.TestCase): + + def run_esptool(self, args, baud=None): + """ Run esptool with the specified arguments. --chip, --port and --baud + are filled in automatically from the command line. (can override default baud rate with baud param.) + + Additional args passed in args parameter as a string. + + Returns output from esptool.py as a string if there is any. Raises an exception if esptool.py fails. + """ + if baud is None: + baud = default_baudrate + trace_args = [ "--trace" ] if trace_enabled else [] + cmd = [sys.executable, ESPTOOL_PY ] + trace_args + [ "--chip", chip, "--port", serialport, "--baud", str(baud) ] + args.split(" ") + print("Running %s..." % (" ".join(cmd))) + try: + output = subprocess.check_output([str(s) for s in cmd], cwd=TEST_DIR, stderr=subprocess.STDOUT) + print(output) # for more complete stdout logs on failure + return output.decode("utf-8") + except subprocess.CalledProcessError as e: + print(e.output) + raise e + + def run_esptool_error(self, args, baud=None): + """ Run esptool.py similar to run_esptool, but expect an + error. + + Verifies the error is an expected error not an unhandled exception, + and returns the output from esptool.py as a string. + """ + with self.assertRaises(subprocess.CalledProcessError) as fail: + self.run_esptool(args, baud) + failure = fail.exception + self.assertEqual(RETURN_CODE_FATAL_ERROR, failure.returncode) + return failure.output.decode("utf-8") + + def setUp(self): + self.tempfiles = [] + print(50*"*") + + def tearDown(self): + for t in self.tempfiles: + try: + os.remove(t) + except OSError: + pass + + def readback(self, offset, length): + """ Read contents of flash back, return to caller. """ + tf = tempfile.NamedTemporaryFile(delete=False) # need a file we can read into + self.tempfiles.append(tf.name) + tf.close() + self.run_esptool("read_flash %d %d %s" % (offset, length, tf.name)) + with open(tf.name, "rb") as f: + rb = f.read() + + self.assertEqual(length, len(rb), "read_flash length %d offset 0x%x yielded %d bytes!" % (length, offset, len(rb))) + return rb + + def verify_readback(self, offset, length, compare_to, is_bootloader=False): + rb = self.readback(offset, length) + with open(compare_to, "rb") as f: + ct = f.read() + if len(rb) != len(ct): + print("WARNING: Expected length %d doesn't match comparison %d") + print("Readback %d bytes" % len(rb)) + if is_bootloader: + # writing a bootloader image to bootloader offset can set flash size/etc, + # so don't compare the 8 byte header + self.assertEqual(ct[0], rb[0], "First bytes should be identical") + rb = rb[8:] + ct = ct[8:] + for rb_b,ct_b,offs in zip(rb,ct,range(len(rb))): + if rb_b != ct_b: + self.fail("First difference at offset 0x%x Expected %r got %r" % (offs, ct_b, rb_b)) + + +class TestFlashing(EsptoolTestCase): + + def test_short_flash(self): + self.run_esptool("write_flash 0x0 images/one_kb.bin") + self.verify_readback(0, 1024, "images/one_kb.bin") + + def test_highspeed_flash(self): + self.run_esptool("write_flash 0x0 images/fifty_kb.bin", baud=921600) + self.verify_readback(0, 50*1024, "images/fifty_kb.bin") + + def test_adjacent_flash(self): + self.run_esptool("write_flash 0x0 images/sector.bin 0x1000 images/fifty_kb.bin") + self.verify_readback(0, 4096, "images/sector.bin") + self.verify_readback(4096, 50*1024, "images/fifty_kb.bin") + + def test_adjacent_independent_flash(self): + self.run_esptool("write_flash 0x0 images/sector.bin") + self.verify_readback(0, 4096, "images/sector.bin") + self.run_esptool("write_flash 0x1000 images/fifty_kb.bin") + self.verify_readback(4096, 50*1024, "images/fifty_kb.bin") + # writing flash the second time shouldn't have corrupted the first time + self.verify_readback(0, 4096, "images/sector.bin") + + def test_correct_offset(self): + """ Verify writing at an offset actually writes to that offset. """ + self.run_esptool("write_flash 0x2000 images/sector.bin") + time.sleep(0.1) + three_sectors = self.readback(0, 0x3000) + last_sector = three_sectors[0x2000:] + with open("images/sector.bin", "rb") as f: + ct = f.read() + self.assertEqual(last_sector, ct) + + def test_no_compression_flash(self): + self.run_esptool("write_flash -u 0x0 images/sector.bin 0x1000 images/fifty_kb.bin") + self.verify_readback(0, 4096, "images/sector.bin") + self.verify_readback(4096, 50*1024, "images/fifty_kb.bin") + + @unittest.skipUnless(chip == 'esp32', 'ESP32 only') + def test_compressed_nostub_flash(self): + self.run_esptool("--no-stub write_flash -z 0x0 images/sector.bin 0x1000 images/fifty_kb.bin") + self.verify_readback(0, 4096, "images/sector.bin") + self.verify_readback(4096, 50*1024, "images/fifty_kb.bin") + + def _test_partition_table_then_bootloader(self, args): + self.run_esptool(args + " 0x4000 images/partitions_singleapp.bin") + self.verify_readback(0x4000, 96, "images/partitions_singleapp.bin") + self.run_esptool(args + " 0x1000 images/bootloader.bin") + self.verify_readback(0x1000, 7888, "images/bootloader.bin", True) + self.verify_readback(0x4000, 96, "images/partitions_singleapp.bin") + + def test_partition_table_then_bootloader(self): + self._test_partition_table_then_bootloader("write_flash") + + def test_partition_table_then_bootloader_no_compression(self): + self._test_partition_table_then_bootloader("write_flash -u") + + def test_partition_table_then_bootloader_nostub(self): + self._test_partition_table_then_bootloader("--no-stub write_flash") + + # note: there is no "partition table then bootloader" test that + # uses --no-stub and -z, as the ESP32 ROM over-erases and can't + # flash this set of files in this order. we do + # test_compressed_nostub_flash() instead. + + def test_length_not_aligned_4bytes(self): + nodemcu = "nodemcu-master-7-modules-2017-01-19-11-10-03-integer.bin" + size = 390411 + self.run_esptool("write_flash 0x0 images/%s" % nodemcu) + + def test_length_not_aligned_4bytes_no_compression(self): + self.run_esptool("write_flash -u 0x0 images/%s" % NODEMCU_FILE) + + def test_write_overlap(self): + output = self.run_esptool_error("write_flash 0x0 images/bootloader.bin 0x1000 images/one_kb.bin") + self.assertIn("Detected overlap at address: 0x1000 ", output) + + def test_write_sector_overlap(self): + # These two 1KB files don't overlap, but they do both touch sector at 0x1000 so should fail + output = self.run_esptool_error("write_flash 0xd00 images/one_kb.bin 0x1d00 images/one_kb.bin") + self.assertIn("Detected overlap at address: 0x1d00", output) + + def test_write_no_overlap(self): + output = self.run_esptool("write_flash 0x0 images/bootloader.bin 0x2000 images/one_kb.bin") + self.assertNotIn("Detected overlap at address", output) + + def test_compressible_file(self): + self.run_esptool("write_flash 0x10000 images/one_mb_zeroes.bin") + + def test_zero_length(self): + # Zero length files are skipped with a warning + output = self.run_esptool("write_flash 0x10000 images/one_kb.bin 0x11000 images/zerolength.bin") + self.verify_readback(0x10000, 1024, "images/one_kb.bin") + self.assertIn("zerolength.bin is empty", output) + + def test_single_byte(self): + output = self.run_esptool("write_flash 0x0 images/onebyte.bin") + self.verify_readback(0x0, 1, "images/onebyte.bin") + + +class TestFlashSizes(EsptoolTestCase): + + def test_high_offset(self): + self.run_esptool("write_flash -fs 4MB 0x300000 images/one_kb.bin") + self.verify_readback(0x300000, 1024, "images/one_kb.bin") + + def test_high_offset_no_compression(self): + self.run_esptool("write_flash -u -fs 4MB 0x300000 images/one_kb.bin") + self.verify_readback(0x300000, 1024, "images/one_kb.bin") + + def test_large_image(self): + self.run_esptool("write_flash -fs 4MB 0x280000 images/one_mb.bin") + self.verify_readback(0x280000, 0x100000, "images/one_mb.bin") + + def test_large_no_compression(self): + self.run_esptool("write_flash -u -fs 4MB 0x280000 images/one_mb.bin") + self.verify_readback(0x280000, 0x100000, "images/one_mb.bin") + + def test_invalid_size_arg(self): + self.run_esptool_error("write_flash -fs 10MB 0x6000 images/one_kb.bin") + + def test_write_past_end_fails(self): + output = self.run_esptool_error("write_flash -fs 1MB 0x280000 images/one_kb.bin") + self.assertIn("File images/one_kb.bin", output) + self.assertIn("will not fit", output) + + def test_write_no_compression_past_end_fails(self): + output = self.run_esptool_error("write_flash -u -fs 1MB 0x280000 images/one_kb.bin") + self.assertIn("File images/one_kb.bin", output) + self.assertIn("will not fit", output) + + +class TestFlashDetection(EsptoolTestCase): + def test_correct_offset(self): + """ Verify writing at an offset actually writes to that offset. """ + res = self.run_esptool("flash_id") + self.assertTrue("Manufacturer:" in res) + self.assertTrue("Device:" in res) + +class TestErase(EsptoolTestCase): + + def test_chip_erase(self): + self.run_esptool("write_flash 0x10000 images/one_kb.bin") + self.verify_readback(0x10000, 0x400, "images/one_kb.bin") + self.run_esptool("erase_flash") + empty = self.readback(0x10000, 0x400) + self.assertTrue(empty == b'\xFF'*0x400) + + def test_region_erase(self): + self.run_esptool("write_flash 0x10000 images/one_kb.bin") + self.run_esptool("write_flash 0x11000 images/sector.bin") + self.verify_readback(0x10000, 0x400, "images/one_kb.bin") + self.verify_readback(0x11000, 0x1000, "images/sector.bin") + # erase only the flash sector containing one_kb.bin + self.run_esptool("erase_region 0x10000 0x1000") + self.verify_readback(0x11000, 0x1000, "images/sector.bin") + empty = self.readback(0x10000, 0x1000) + self.assertTrue(empty == b'\xFF'*0x1000) + + def test_large_region_erase(self): + # verifies that erasing a large region doesn't time out + self.run_esptool("erase_region 0x0 0x100000") + + +class TestSectorBoundaries(EsptoolTestCase): + + def test_end_sector(self): + self.run_esptool("write_flash 0x10000 images/sector.bin") + self.run_esptool("write_flash 0x0FC00 images/one_kb.bin") + self.verify_readback(0x0FC00, 0x400, "images/one_kb.bin") + self.verify_readback(0x10000, 0x1000, "images/sector.bin") + + def test_end_sector_uncompressed(self): + self.run_esptool("write_flash -u 0x10000 images/sector.bin") + self.run_esptool("write_flash -u 0x0FC00 images/one_kb.bin") + self.verify_readback(0x0FC00, 0x400, "images/one_kb.bin") + self.verify_readback(0x10000, 0x1000, "images/sector.bin") + + def test_overlap(self): + self.run_esptool("write_flash 0x20800 images/sector.bin") + self.verify_readback(0x20800, 0x1000, "images/sector.bin") + + +class TestVerifyCommand(EsptoolTestCase): + + def test_verify_success(self): + self.run_esptool("write_flash 0x5000 images/one_kb.bin") + self.run_esptool("verify_flash 0x5000 images/one_kb.bin") + + def test_verify_failure(self): + self.run_esptool("write_flash 0x6000 images/sector.bin") + output = self.run_esptool_error("verify_flash --diff=yes 0x6000 images/one_kb.bin") + self.assertIn("verify FAILED", output) + self.assertIn("first @ 0x00006000", output) + + def test_verify_unaligned_length(self): + self.run_esptool("write_flash 0x0 images/%s" % NODEMCU_FILE) + self.run_esptool("verify_flash 0x0 images/%s" % NODEMCU_FILE) + + +class TestReadIdentityValues(EsptoolTestCase): + + def test_read_mac(self): + output = self.run_esptool("read_mac") + mac = re.search(r"[0-9a-f:]{17}", output) + self.assertIsNotNone(mac) + mac = mac.group(0) + self.assertNotEqual("00:00:00:00:00:00", mac) + self.assertNotEqual("ff:ff:ff:ff:ff:ff", mac) + + @unittest.skipUnless(chip == 'esp8266', 'ESP8266 only') + def test_read_chip_id(self): + output = self.run_esptool("chip_id") + idstr = re.search("Chip ID: 0x([0-9a-f]+)", output) + self.assertIsNotNone(idstr) + idstr = idstr.group(1) + self.assertNotEqual("0"*8, idstr) + self.assertNotEqual("f"*8, idstr) + +class TestKeepImageSettings(EsptoolTestCase): + """ Tests for the -fm keep, -ff keep options for write_flash """ + HEADER_ONLY = "images/image_header_only.bin" # 8 byte file, contains image header + def setUp(self): + super(TestKeepImageSettings, self).setUp() + self.flash_offset = 0x1000 if chip == "esp32" else 0 # bootloader offset + with open(self.HEADER_ONLY, "rb") as f: + self.header = f.read(8) + + def test_keep_does_not_change_settings(self): + # defaults should be keep, except for flash size which has to match header + flash_size = "1MB" if chip == "esp32" else "512KB" # hex 0 + self.run_esptool("write_flash -fs %s 0x%x %s" % (flash_size, self.flash_offset, self.HEADER_ONLY)) + self.verify_readback(self.flash_offset, 8, self.HEADER_ONLY, False) + # can also explicitly set these options + self.run_esptool("write_flash -fm keep -ff keep -fs %s 0x%x %s" % (flash_size, self.flash_offset, self.HEADER_ONLY)) + self.verify_readback(self.flash_offset, 8, self.HEADER_ONLY, False) + # verify_flash should also use 'keep' + self.run_esptool("verify_flash -fs %s 0x%x %s" % (flash_size, self.flash_offset, self.HEADER_ONLY)) + + def test_detect_size_changes_size(self): + self.run_esptool("write_flash 0x%x %s" % (self.flash_offset, self.HEADER_ONLY)) + readback = self.readback(self.flash_offset, 8) + self.assertEqual(self.header[:3], readback[:3]) # first 3 bytes unchanged + self.assertNotEqual(self.header[3], readback[3]) # size_freq byte changed + self.assertEqual(self.header[4:], readback[4:]) # rest unchanged + + def test_explicit_set_size_freq_mode(self): + self.run_esptool("write_flash -fs 2MB -fm qio -ff 80m 0x%x %s" % (self.flash_offset, self.HEADER_ONLY)) + + def val(x): + try: + return ord(x) # converts character to integer on Python 2 + except TypeError: + return x # throws TypeError on Python 3 where x is already an integer + + header = list(self.header) + readback = self.readback(self.flash_offset, 8) + self.assertEqual(self.header[0], readback[0]) + self.assertEqual(self.header[1], readback[1]) + self.assertEqual(0, val(readback[2])) # qio mode + self.assertNotEqual(0, val(self.header[2])) + self.assertEqual(0x1f if chip == "esp32" else 0x3f, val(readback[3])) # size_freq + self.assertNotEqual(self.header[3], readback[3]) + self.assertEqual(self.header[4:], readback[4:]) + # verify_flash should pass if we match params, fail otherwise + self.run_esptool("verify_flash -fs 2MB -fm qio -ff 80m 0x%x %s" % (self.flash_offset, self.HEADER_ONLY)) + self.run_esptool_error("verify_flash 0x%x %s" % (self.flash_offset, self.HEADER_ONLY)) + + +class TestLoadRAM(EsptoolTestCase): + def test_load_ram(self): + """ Verify load_ram command + + The "hello world" binary programs for each chip print + "Hello world!\n" to the serial port. + """ + self.run_esptool("load_ram images/helloworld-%s.bin" % chip) + p = serial.serial_for_url(serialport, default_baudrate) + p.timeout = 0.2 + self.assertIn(b"Hello world!", p.read(32)) + p.close() + + +class TestDeepSleepFlash(EsptoolTestCase): + + @unittest.skipUnless(chip == 'esp8266', 'ESP8266 only') + def test_deep_sleep_flash(self): + """ Regression test for https://github.com/espressif/esptool/issues/351 + + ESP8266 deep sleep can disable SPI flash chip, stub loader (or ROM loader) needs to re-enable it. + + NOTE: If this test fails, the ESP8266 may need a hard power cycle (probably with GPIO0 held LOW) + to recover. + """ + # not even necessary to wake successfully from sleep, going into deep sleep is enough + # (so GPIO16, etc, config is not important for this test) + self.run_esptool("write_flash 0x0 images/esp8266_deepsleep.bin", baud=230400) + + time.sleep(0.25) # give ESP8266 time to enter deep sleep + + self.run_esptool("write_flash 0x0 images/fifty_kb.bin", baud=230400) + self.verify_readback(0, 50*1024, "images/fifty_kb.bin") + + +if __name__ == '__main__': + if len(sys.argv) < 3: + print("Usage: %s [--trace] [optional default baud rate] [optional tests]" % sys.argv[0]) + sys.exit(1) + if sys.argv[1] == "--trace": + trace_enabled = True + sys.argv.pop(1) + serialport = sys.argv[1] + # chip is already set to sys.argv[2], so @skipUnless can evaluate against it + args_used = 2 + try: + default_baudrate = int(sys.argv[3]) + args_used = 3 + except IndexError: + pass # no additional args + except ValueError: + pass # arg3 not a number, must be a test name + + # unittest also uses argv, so trim the args we used + sys.argv = [ sys.argv[0] ] + sys.argv[args_used + 1:] + + print("Running esptool.py tests...") + unittest.main(buffer=True) diff --git a/test/test_imagegen.py b/test/test_imagegen.py new file mode 100644 index 0000000..09c1eec --- /dev/null +++ b/test/test_imagegen.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python +import os +import os.path +import subprocess +import struct +import sys +import unittest +import hashlib + +from elftools.elf.elffile import ELFFile + +TEST_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "elf2image") +os.chdir(TEST_DIR) +try: + ESPTOOL_PY = os.environ["ESPTOOL_PY"] +except KeyError: + ESPTOOL_PY = os.path.join(TEST_DIR, "../..", "esptool.py") + +# import the version of esptool we are testing with +sys.path.append(os.path.dirname(ESPTOOL_PY)) +import esptool + +def try_delete(path): + try: + os.remove(path) + except OSError: + pass + +def segment_matches_section(segment, section): + """ segment is an ImageSegment from an esptool binary. + section is an elftools ELF section + + Returns True if they match + """ + sh_size = (section.header.sh_size + 0x3) & ~3 # pad length of ELF sections + return (section.header.sh_addr == segment.addr + and sh_size == len(segment.data)) + + +class BaseTestCase(unittest.TestCase): + + def assertEqualHex(self, expected, actual, message=None): + try: + expected = hex(expected) + except TypeError: # if expected is character + expected = hex(ord(expected)) + try: + actual = hex(actual) + except TypeError: # if actual is character + actual = hex(ord(actual)) + self.assertEqual(expected, actual, message) + + def assertImageContainsSection(self, image, elf, section_name): + """ + Assert an esptool binary image object contains + the data for a particular ELF section. + """ + with open(elf, "rb") as f: + e = ELFFile(f) + section = e.get_section_by_name(section_name) + self.assertTrue(section, "%s should be in the ELF" % section_name) + sh_addr = section.header.sh_addr + data = section.data() + # section contents may be smeared across multiple image segments, + # so look through each segment and remove it from ELF section 'data' + # as we find it in the image segments. When we're done 'data' should + # all be accounted for + for seg in sorted(image.segments, key=lambda s:s.addr): + print("comparing seg 0x%x sec 0x%x len 0x%x" % (seg.addr, sh_addr, len(data))) + if seg.addr == sh_addr: + overlap_len = min(len(seg.data), len(data)) + self.assertEqual(data[:overlap_len], seg.data[:overlap_len], + "ELF '%s' section has mis-matching binary image data" % section_name) + sh_addr += overlap_len + data = data[overlap_len:] + + # no bytes in 'data' should be left unmatched + self.assertEqual(0, len(data), + "ELF %s section '%s' has no encompassing segment(s) in binary image (image segments: %s)" + % (elf, section_name, image.segments)) + + def assertImageInfo(self, binpath, chip="esp8266"): + """ + Run esptool.py image_info on a binary file, + assert no red flags about contents. + """ + cmd = [ sys.executable, ESPTOOL_PY, "--chip", chip, "image_info", binpath ] + try: + output = subprocess.check_output(cmd).decode("utf-8") + print(output) + except subprocess.CalledProcessError as e: + print(e.output) + raise + self.assertFalse("invalid" in output, "Checksum calculation should be valid") + self.assertFalse("warning" in output.lower(), "Should be no warnings in image_info output") + + def run_elf2image(self, chip, elf_path, version=None, extra_args=[]): + """ Run elf2image on elf_path """ + cmd = [sys.executable, ESPTOOL_PY, "--chip", chip, "elf2image" ] + if version is not None: + cmd += [ "--version", str(version) ] + cmd += [ elf_path ] + extra_args + print("Executing %s" % (" ".join(cmd))) + try: + output = str(subprocess.check_output(cmd)) + print(output) + self.assertFalse("warning" in output.lower(), "elf2image should not output warnings") + except subprocess.CalledProcessError as e: + print(e.output) + raise + +class ESP8266V1ImageTests(BaseTestCase): + ELF="esp8266-nonosssdk20-iotdemo.elf" + BIN_LOAD="esp8266-nonosssdk20-iotdemo.elf-0x00000.bin" + BIN_IROM="esp8266-nonosssdk20-iotdemo.elf-0x10000.bin" + + def setUp(self): + self.run_elf2image("esp8266", self.ELF, 1) + + def tearDown(self): + try_delete(self.BIN_LOAD) + try_delete(self.BIN_IROM) + + def test_irom_bin(self): + with open(self.ELF, "rb") as f: + e = ELFFile(f) + irom_section = e.get_section_by_name(".irom0.text") + self.assertEqual(irom_section.header.sh_size, + os.stat(self.BIN_IROM).st_size, + "IROM raw binary file should be same length as .irom0.text section") + + def test_loaded_sections(self): + image = esptool.LoadFirmwareImage("esp8266", self.BIN_LOAD) + self.assertEqual(3, len(image.segments)) + self.assertImageContainsSection(image, self.ELF, ".data") + self.assertImageContainsSection(image, self.ELF, ".text") + self.assertImageContainsSection(image, self.ELF, ".rodata") + +class ESP8266V12SectionHeaderNotAtEnd(BaseTestCase): + """ Ref https://github.com/espressif/esptool/issues/197 - + this ELF image has the section header not at the end of the file """ + ELF="esp8266-nonossdkv12-example.elf" + BIN_LOAD=ELF+"-0x00000.bin" + BIN_IROM=ELF+"-0x40000.bin" + + def test_elf_section_header_not_at_end(self): + self.run_elf2image("esp8266", self.ELF) + image = esptool.LoadFirmwareImage("esp8266", self.BIN_LOAD) + self.assertEqual(3, len(image.segments)) + self.assertImageContainsSection(image, self.ELF, ".data") + self.assertImageContainsSection(image, self.ELF, ".text") + self.assertImageContainsSection(image, self.ELF, ".rodata") + + def tearDown(self): + try_delete(self.BIN_LOAD) + try_delete(self.BIN_IROM) + +class ESP8266V2ImageTests(BaseTestCase): + + def _test_elf2image(self, elfpath, binpath): + try: + self.run_elf2image("esp8266", elfpath, 2) + image = esptool.LoadFirmwareImage("esp8266", binpath) + self.assertEqual(4, len(image.segments)) + self.assertImageContainsSection(image, elfpath, ".data") + self.assertImageContainsSection(image, elfpath, ".text") + self.assertImageContainsSection(image, elfpath, ".rodata") + irom_segment = image.segments[0] + self.assertEqual(0, irom_segment.addr, + "IROM segment 'load address' should be zero") + with open(elfpath, "rb") as f: + e = ELFFile(f) + sh_size = (e.get_section_by_name(".irom0.text").header.sh_size + 15) & ~15 + self.assertEqual(len(irom_segment.data), sh_size, "irom segment (0x%x) should be same size (16 padded) as .irom0.text section (0x%x)" % (len(irom_segment.data), sh_size)) + + # check V2 CRC (for ESP8266 SDK bootloader) + with open(binpath, "rb") as f: + f.seek(-4, os.SEEK_END) + image_len = f.tell() + crc_stored = struct.unpack("