From 137565636306535f8e21c45cad4da56a35c21278 Mon Sep 17 00:00:00 2001 From: "jacob.eva" Date: Fri, 18 Jul 2025 18:00:14 +0100 Subject: [PATCH] Initial commit --- .gitignore | 2 + .vimspector.json | 14 + LICENSE | 619 ++++ Makefile | 40 + Makefile.win32 | 37 + README.md | 3 + src/librnode.c | 1769 +++++++++ src/librnode.h | 476 +++ src/libs/eeprom.h | 19 + src/libs/flashers/esp32/LICENSE | 21 + src/libs/flashers/esp32/README.md | 3 + src/libs/flashers/esp32/esputil.c | 1218 +++++++ src/libs/flashers/esp32/esputil.h | 8 + src/libs/flashers/nrf/LICENSE | 20 + src/libs/flashers/nrf/Makefile | 39 + src/libs/flashers/nrf/Makefile.win32 | 37 + src/libs/flashers/nrf/README.md | 17 + src/libs/flashers/nrf/crc16.c | 67 + src/libs/flashers/nrf/crc16.h | 91 + src/libs/flashers/nrf/dfu.c | 673 ++++ src/libs/flashers/nrf/dfu.h | 82 + src/libs/flashers/nrf/dfu_serial.c | 356 ++ src/libs/flashers/nrf/dfu_serial.h | 78 + src/libs/flashers/nrf/hci.c | 42 + src/libs/flashers/nrf/hci.h | 27 + src/libs/flashers/nrf/jsmn.c | 318 ++ src/libs/flashers/nrf/jsmn.h | 80 + src/libs/flashers/nrf/uart_drv.h | 94 + src/libs/flashers/nrf/uart_linux.c | 283 ++ src/libs/flashers/nrf/uart_slip.c | 134 + src/libs/flashers/nrf/uart_slip.h | 85 + src/libs/flashers/nrf/uart_win32.c | 231 ++ src/libs/framing.h | 109 + src/libs/logging/LICENSE | 19 + src/libs/logging/README.md | 1 + src/libs/logging/log.c | 171 + src/libs/logging/log.h | 49 + src/libs/miniz.h | 4926 ++++++++++++++++++++++++++ src/libs/serial.h | 26 + src/libs/serial_linux.c | 96 + src/libs/serial_win32.c | 12 + src/libs/slip_enc.c | 175 + src/libs/slip_enc.h | 81 + src/libs/util.c | 29 + src/libs/util.h | 22 + src/libs/zip.c | 786 ++++ src/libs/zip.h | 302 ++ tests/test.c | 208 ++ 48 files changed, 13995 insertions(+) create mode 100644 .gitignore create mode 100644 .vimspector.json create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 Makefile.win32 create mode 100644 README.md create mode 100755 src/librnode.c create mode 100644 src/librnode.h create mode 100644 src/libs/eeprom.h create mode 100644 src/libs/flashers/esp32/LICENSE create mode 100644 src/libs/flashers/esp32/README.md create mode 100644 src/libs/flashers/esp32/esputil.c create mode 100644 src/libs/flashers/esp32/esputil.h create mode 100644 src/libs/flashers/nrf/LICENSE create mode 100644 src/libs/flashers/nrf/Makefile create mode 100644 src/libs/flashers/nrf/Makefile.win32 create mode 100644 src/libs/flashers/nrf/README.md create mode 100644 src/libs/flashers/nrf/crc16.c create mode 100644 src/libs/flashers/nrf/crc16.h create mode 100644 src/libs/flashers/nrf/dfu.c create mode 100644 src/libs/flashers/nrf/dfu.h create mode 100644 src/libs/flashers/nrf/dfu_serial.c create mode 100644 src/libs/flashers/nrf/dfu_serial.h create mode 100644 src/libs/flashers/nrf/hci.c create mode 100644 src/libs/flashers/nrf/hci.h create mode 100644 src/libs/flashers/nrf/jsmn.c create mode 100644 src/libs/flashers/nrf/jsmn.h create mode 100644 src/libs/flashers/nrf/uart_drv.h create mode 100644 src/libs/flashers/nrf/uart_linux.c create mode 100644 src/libs/flashers/nrf/uart_slip.c create mode 100644 src/libs/flashers/nrf/uart_slip.h create mode 100644 src/libs/flashers/nrf/uart_win32.c create mode 100644 src/libs/framing.h create mode 100644 src/libs/logging/LICENSE create mode 100644 src/libs/logging/README.md create mode 100644 src/libs/logging/log.c create mode 100644 src/libs/logging/log.h create mode 100644 src/libs/miniz.h create mode 100644 src/libs/serial.h create mode 100644 src/libs/serial_linux.c create mode 100644 src/libs/serial_win32.c create mode 100644 src/libs/slip_enc.c create mode 100644 src/libs/slip_enc.h create mode 100644 src/libs/util.c create mode 100644 src/libs/util.h create mode 100644 src/libs/zip.c create mode 100644 src/libs/zip.h create mode 100644 tests/test.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0100ab3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +old.w diff --git a/.vimspector.json b/.vimspector.json new file mode 100644 index 0000000..ed529b0 --- /dev/null +++ b/.vimspector.json @@ -0,0 +1,14 @@ +{ + "configurations": { + "Launch": { + "adapter": "vscode-cpptools", + "configuration": { + "request": "launch", + "program": "./build/librnode_test", + "args": [], + "cwd": "~/Work/Repos/librnode/", + "MIMode": "gdb" + } + } + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..281d399 --- /dev/null +++ b/LICENSE @@ -0,0 +1,619 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1bdc791 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +# Copyright (c) 2025 Jacob Eva (Liberated Embedded Systems) +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . + +# This makefile is for GNU / Linux. + +CC=gcc +FLAGS=-DLOG_USE_COLOR -l crypto +LIB=librnode +SRCS=$(shell find src ! -name '*win32.c' -name '*.c' -type f) +OBJS=$(patsubst %.c,%.o,$(SRCS)) +F_OBJS=$(notdir $(OBJS)) + +all: $(OBJS) + ar cr build/$(LIB).a -o $(F_OBJS) + mv *.o build + +%.o: %.c + $(CC) $(FLAGS) $< -c + +debug: FLAGS+=-g +debug: all + +test: all + $(CC) tests/test.c -L build -l rnode -l tap -l crypto -l md -o build/librnode_test + +test-debug: debug + $(CC) tests/test.c -L build -l rnode -l tap -l crypto -l md -g -o build/librnode_test + +clean: + - rm -rf build + - mkdir build diff --git a/Makefile.win32 b/Makefile.win32 new file mode 100644 index 0000000..db216d4 --- /dev/null +++ b/Makefile.win32 @@ -0,0 +1,37 @@ +CC = gcc +CFLAGS = -Wall -O2 -I. -DWIN32 +BIN = UartSecureDFU + +DEPS = crc32.h \ + delay_connect.h \ + dfu.h \ + dfu_serial.h \ + logging.h \ + slip_enc.h \ + uart_drv.h \ + uart_slip.h \ + zip.h \ + miniz.h \ + jsmn.h \ + Makefile + +OBJS = crc32.o \ + delay_connect.o \ + dfu.o \ + dfu_serial.o \ + jsmn.o \ + logging.o \ + slip_enc.o \ + uart_win32.o \ + UartSecureDFU.o \ + uart_slip.o \ + zip.o + +%.o: %.c $(DEPS) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BIN): $(OBJS) + $(CC) $(OBJS) -o $(BIN) + +clean: + rm -f $(BIN) $(OBJS) diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d08f53 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +`librnode` is a C library which provides an object to interface with +transcievers built on the RNode platform. It is intended for use in RNode +configuration applications. diff --git a/src/librnode.c b/src/librnode.c new file mode 100755 index 0000000..02836a8 --- /dev/null +++ b/src/librnode.c @@ -0,0 +1,1769 @@ +/* Copyright (c) 2025 - Jacob Eva (Liberated Embedded Systems) + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "librnode.h" +#include "libs/framing.h" +#include "libs/serial.h" +#include "libs/logging/log.h" +#include "libs/util.h" +#include "libs/slip_enc.h" +#include "libs/eeprom.h" +#include "libs/zip.h" + +// flashing +#include "libs/flashers/nrf/dfu.h" +#include "libs/flashers/nrf/uart_drv.h" +#include "libs/flashers/esp32/esputil.h" + +/* Handles incoming RNode communications from serial + * Scope: private + * Returns: + * > 0 - number of bytes read from port + * -1 - generic error + */ +int rnode_handle_resp(struct RNode* rn, bool* in_frame, uint8_t* cmd, uint8_t* cmd_buf, uint8_t* cmd_buf_l, uint16_t* frame_len) { + int len; + bool escape = false; + uint8_t sbyte; + uint8_t resp_buf[RESP_BUF_SIZE] = {0}; + + len = read_port(rn->fd, resp_buf, RESP_BUF_SIZE); + + if (len > 0) { + log_trace("Read %d bytes", len); + for (int i = 0; i < len; i++) { + log_trace("Byte: %02x", resp_buf[i]); + if (!(*in_frame) && resp_buf[i] == FEND) { + *in_frame = true; + *cmd = CMD_UNKNOWN; + sbyte = 0; + *cmd_buf_l = 0; + } else if (*in_frame) { + if (*frame_len == 0) { + *cmd = resp_buf[i]; + (*frame_len)++; + continue; + } else { + sbyte = resp_buf[i]; + } + if (sbyte == FEND && *cmd == CMD_ROM_READ) { + //rn->product = cmd_buf[0]; + //rn->model = cmd_buf[1]; + //rn->hw_rev = cmd_buf[2]; + + //memcpy(rn->serial, cmd_buf+3, SERIAL_SIZE); + //memcpy(rn->made, cmd_buf+3+SERIAL_SIZE, MADE_SIZE); + + //memcpy(rn->checksum, cmd_buf+3+SERIAL_SIZE+MADE_SIZE, CHECKSUM_SIZE); + //memcpy(rn->signature, cmd_buf+3+SERIAL_SIZE+MADE_SIZE, CHECKSUM_SIZE); + + + memcpy (rn->r_eeprom, cmd_buf, EEPROM_SIZE); + + *in_frame = false; + *frame_len = 0; + *cmd = CMD_UNKNOWN; + continue; + } else if (sbyte == FEND) { + *in_frame = false; + *frame_len = 0; + *cmd = CMD_UNKNOWN; + *cmd_buf_l = 0; + continue; + } else if (*cmd == CMD_ROM_READ) { + if (sbyte == FESC) { + escape = true; + } else { + if (escape) { + if (sbyte == TFEND) { + sbyte = FEND; + } else if (sbyte == TFESC) { + sbyte = FESC; + } + escape = false; + } + cmd_buf[*cmd_buf_l] = sbyte; + (*cmd_buf_l)++; + } + } else if (*cmd == CMD_DETECT && sbyte == DETECT_RESP) { + rn->connected = true; + } else if (*cmd == CMD_PLATFORM) { + rn->platform = sbyte; + } else if (*cmd == CMD_MCU) { + rn->mcu = sbyte; + } else if (*cmd == CMD_FW_VERSION) { + if (sbyte == FESC) { + escape = true; + } else { + if (escape) { + if (sbyte == TFEND) { + sbyte = FEND; + } else if (sbyte == TFESC) { + sbyte = FESC; + } + escape = false; + } + cmd_buf[*cmd_buf_l] = sbyte; + (*cmd_buf_l)++; + if (*cmd_buf_l == 2) { + rn->fw_ver = cmd_buf[0] + cmd_buf[1] / 100.0; + *cmd_buf_l = 0; + } + } + } else if (*cmd == CMD_INTERFACES) { + if (sbyte == FESC) { + escape = true; + } else { + if (escape) { + if (sbyte == TFEND) { + sbyte = FEND; + } else if (sbyte == TFESC) { + sbyte = FESC; + } + escape = false; + } + cmd_buf[*cmd_buf_l] = sbyte; + (*cmd_buf_l)++; + if (*cmd_buf_l == 2) { + rn->interfaces[cmd_buf[0]] = cmd_buf[1]; + *cmd_buf_l = 0; + } + } + } else if (*cmd == CMD_FREQUENCY) { + if (sbyte == FESC) { + escape = true; + } else { + if (escape) { + if (sbyte == TFEND) { + sbyte = FEND; + } else if (sbyte == TFESC) { + sbyte = FESC; + } + escape = false; + } + cmd_buf[*cmd_buf_l] = sbyte; + (*cmd_buf_l)++; + if (*cmd_buf_l == 4) { + rn->freq = cmd_buf[0] << 24 | cmd_buf[1] << 16 | cmd_buf[2] << 8 | cmd_buf[3]; + *cmd_buf_l = 0; + } + } + } else if (*cmd == CMD_BANDWIDTH) { + if (sbyte == FESC) { + escape = true; + } else { + if (escape) { + if (sbyte == TFEND) { + sbyte = FEND; + } else if (sbyte == TFESC) { + sbyte = FESC; + } + escape = false; + } + cmd_buf[*cmd_buf_l] = sbyte; + (*cmd_buf_l)++; + if (*cmd_buf_l == 4) { + rn->bw = cmd_buf[0] << 24 | cmd_buf[1] << 16 | cmd_buf[2] << 8 | cmd_buf[3]; + *cmd_buf_l = 0; + } + } + } else if (*cmd == CMD_TXPOWER) { + rn->txp = sbyte; + } else if (*cmd == CMD_SF) { + rn->sf = sbyte; + } else if (*cmd == CMD_CR) { + rn->cr = sbyte; + } else if (*cmd == CMD_ST_ALOCK) { + if (sbyte == FESC) { + escape = true; + } else { + if (escape) { + if (sbyte == TFEND) { + sbyte = FEND; + } else if (sbyte == TFESC) { + sbyte = FESC; + } + escape = false; + } + cmd_buf[*cmd_buf_l] = sbyte; + (*cmd_buf_l)++; + } + if (*cmd_buf_l == 2) { + float at = cmd_buf[0] << 8 | cmd_buf[1]; + + rn->st_alock = at / 100.0; + *cmd_buf_l = 0; + } + } else if (*cmd == CMD_LT_ALOCK) { + if (sbyte == FESC) { + escape = true; + } else { + if (escape) { + if (sbyte == TFEND) { + sbyte = FEND; + } else if (sbyte == TFESC) { + sbyte = FESC; + } + escape = false; + } + cmd_buf[*cmd_buf_l] = sbyte; + (*cmd_buf_l)++; + } + if (*cmd_buf_l == 2) { + float at = cmd_buf[0] << 8 | cmd_buf[1]; + + rn->lt_alock = at / 100.0; + *cmd_buf_l = 0; + } + } else if (*cmd == CMD_ROM_READ) { + if (sbyte == FESC) { + escape = true; + } else { + if (escape) { + if (sbyte == TFEND) { + sbyte = FEND; + } else if (sbyte == TFESC) { + sbyte = FESC; + } + escape = false; + } + cmd_buf[*cmd_buf_l] = sbyte; + (*cmd_buf_l)++; + } + } else if (*cmd == CMD_BT_PIN) { + if (sbyte == FESC) { + escape = true; + } else { + if (escape) { + if (sbyte == TFEND) { + sbyte = FEND; + } else if (sbyte == TFESC) { + sbyte = FESC; + } + escape = false; + } + cmd_buf[*cmd_buf_l] = sbyte; + (*cmd_buf_l)++; + } + if (*cmd_buf_l == 4) { + rn->bt_pairing_pin = cmd_buf[0] << 24 | cmd_buf[1] << 16 | cmd_buf[2] << 8 | cmd_buf[3]; + *cmd_buf_l = 0; + } + } + (*frame_len)++; + } + } + } + return len; +} + +/* Detects an RNode + * Scope: private + * Returns: + * 0 - success + * -1 - generic error + */ +int rnode_detect(struct RNode* rn) { + int err_code; + uint8_t tx_buf[20]; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[20]){FEND, CMD_DETECT, DETECT_REQ, FEND, FEND, CMD_FW_VERSION, 0x00, FEND, FEND, CMD_PLATFORM, 0x00, FEND, FEND, CMD_MCU, 0x00, FEND, FEND, CMD_INTERFACES, 0x00, FEND}, 20*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 20); + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + return (err_code == -1) ? -1 : rn->connected; + } else { + return -1; + } +} + +/* Establishes communication with an RNode + * Params: port (e.g. /dev/ttyACM0), baud rate (e.g. 115200), detect (attempt + * to communicate with RNode), force_detect (fail if cannot communicate with + * RNode) + * Scope: public + * Returns: + * 0 - success + * -1 - generic error + */ +int rnode_init(struct RNode* rn, char* port, uint32_t baud, bool detect, bool force_detect) { + rn->port = port; + rn->baud = baud; + rn->fd = open_port(rn->port, baud); + + if (rn->fd > 0) { + if (detect) { + if (rnode_detect(rn)) { + log_info("RNode detected successfully on port %s", port); + } else { + if (force_detect) { + log_error("RNode could not be detected on port %s", port); + return -1; + } else { + log_info("RNode could not be detected on port %s, this isn't a problem.", port); + } + } + } else { + return 0; + } + } else { + return -1; + } + return 0; +} + +/* Resets an RNode + * Scope: public + * Returns: + * 0 - success + * -1 - generic error + */ +int rnode_reset(struct RNode* rn) { + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_RESET, CMD_RESET_BYTE, FEND}, 4*sizeof(uint8_t)); + return write_port(rn->fd, tx_buf, 4); +} + +/* Sets an RNode's platform attribute manually + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_platform(struct RNode* rn, uint8_t platform) { + rn->platform = platform; + + return 0; +} + +/* Enable / disable Bluetooth on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_bt(struct RNode* rn, uint8_t val) { + int err_code; + if (val == BT_ON) { + log_info("Enabling Bluetooth..."); + } else if (val == BT_PAIRING) { + log_info("Starting Bluetooth pairing..."); + } else { + log_info("Disabling Bluetooth..."); + } + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_BT_CTRL, val, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + + return err_code; +} + +/* Attempt to retrieve the BT pairing pin of an RNode. Blocking function, + * should be run in a separate thread. + * Params: timeout (in ms, minimum value 200) + * Scope: public + * Returns + * > 0 - bluetooth pairing code + * 0 - success + * -1 - generic error + * -4 - rnode did not respond + */ +int rnode_get_bt_pin(struct RNode* rn, uint32_t timeout) { + int err_code = 0; + time_t ltime; + time(<ime); + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + unsigned int start = ltime; unsigned int current = ltime; + + if (timeout < 200) { + return -1; + } + + while (current <= start + timeout) { + if (err_code >= 0) { + sleep_ms(50); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + time(<ime); current = ltime; + if (rn->bt_pairing_pin != 0) { + // If we successfully retrieved the pairing pin, return it + return rn->bt_pairing_pin; + } + } else { + return err_code; + } + } + + return -4; +} + +/* Display related functions */ + +/* Set display intensity on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_disp_int(struct RNode* rn, uint8_t disp_int) { + int err_code = 0; + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DISP_INT, disp_int, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + + return err_code; +} + +/* Set display timeout on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_disp_timeout(struct RNode* rn, uint8_t timeout) { + int err_code = 0; + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DISP_INT, timeout, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + + return err_code; +} + +/* Set display address on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_disp_addr(struct RNode* rn, uint8_t addr) { + int err_code = 0; + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DISP_ADDR, addr, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + + return err_code; +} + +/* Set display rotation on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_disp_rot(struct RNode* rn, uint8_t rot) { + int err_code = 0; + if (rot < 0) rot = 0; + else if (rot > 3) rot = 3; + + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DISP_ROT, rot, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + + return err_code; +} + +/* Start display reconditioning on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_start_disp_recon(struct RNode* rn) { + int err_code = 0; + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DISP_RCND, 1, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + + return err_code; +} + +/* Set neopixel intensity on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_np_int(struct RNode* rn, uint8_t np_int) { + int err_code; + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_NP_INT, np_int, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + + return err_code; +} + +/* Get available interfaces on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_interfaces(struct RNode* rn) { + int err_code; + uint8_t tx_buf[4]; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[3]){FEND, CMD_INTERFACES, FEND}, 3*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 3); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + } + + return err_code; +} + +/* Radio configuration functions */ + +/* Select interface on an RNode (in preparation for running rnode_set_freq, etc) + * Scope: public + * Returns + * 0 - success + * -1 - generic error + * -13 - chosen interface does not exist (did you run rnode_get_interfaces)? + */ +int rnode_select_interface(struct RNode* rn, uint8_t interface) { + int err_code; + uint8_t tx_buf[4]; + + if (rn->interfaces[interface] != 0) { + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_SEL_INT, interface, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + } else { + return err_code; + } + + + if (!err_code) { + rn->sel_int = interface; + } + + return err_code; +} + +/* Set frequency on an RNode (in preparation for TNC mode) + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_freq(struct RNode* rn, uint32_t freq) { + uint8_t tx_buf[11]; + uint8_t escaped_freq[8]; + uint32_t escaped_freq_size; + uint8_t freq_a[4]; + int err_code; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(freq_a, (uint8_t[4]){freq >> 24, freq >> 16 & 0xFF, freq >> 8 & 0xFF, freq & 0xFF}, 4*sizeof(uint8_t)); + + memcpy(tx_buf, (uint8_t[2]){FEND, CMD_FREQUENCY}, 2*sizeof(uint8_t)); + encode_slip(escaped_freq, &escaped_freq_size, freq_a, 4, false); + memcpy(tx_buf + 2, escaped_freq, escaped_freq_size*sizeof(uint8_t)); + tx_buf[2 + escaped_freq_size] = FEND; + + err_code = write_port(rn->fd, tx_buf, 2 + escaped_freq_size + 1); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->freq; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Get frequency on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_freq(struct RNode* rn) { + uint8_t tx_buf[7]; + int err_code; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[7]){FEND, CMD_FREQUENCY, 0, 0, 0, 0, FEND}, 7*sizeof(uint8_t)); + + err_code = write_port(rn->fd, tx_buf, 7); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->freq; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Set bandwidth on an RNode (in preparation for TNC mode) + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_bw(struct RNode* rn, uint32_t bw) { + int err_code; + uint8_t tx_buf[11]; + uint8_t escaped_bw[8]; + uint32_t escaped_bw_size; + uint8_t bw_a[4]; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(bw_a, (uint8_t[4]){bw >> 24, bw >> 16 & 0xFF, bw >> 8 & 0xFF, bw & 0xFF}, 4*sizeof(uint8_t)); + + memcpy(tx_buf, (uint8_t[2]){FEND, CMD_BANDWIDTH}, 2*sizeof(uint8_t)); + encode_slip(escaped_bw, &escaped_bw_size, bw_a, 4, false); + memcpy(tx_buf + 2, escaped_bw, escaped_bw_size*sizeof(uint8_t)); + tx_buf[2 + escaped_bw_size] = FEND; + + err_code = write_port(rn->fd, tx_buf, 2 + escaped_bw_size + 1); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->bw; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Get bandwidth on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_bw(struct RNode* rn) { + uint8_t tx_buf[7]; + int err_code; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[7]){FEND, CMD_BANDWIDTH, 0, 0, 0, 0, FEND}, 7*sizeof(uint8_t)); + + err_code = write_port(rn->fd, tx_buf, 7); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->bw; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Set transmission power on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_txp(struct RNode* rn, uint8_t txp) { + int err_code; + uint8_t tx_buf[4]; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_TXPOWER, txp, FEND}, 4*sizeof(uint8_t)); + + err_code = write_port(rn->fd, tx_buf, 4); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->txp; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Get transmission power on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_txp(struct RNode* rn) { + int err_code; + uint8_t tx_buf[4]; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_TXPOWER, 0xFF, FEND}, 4*sizeof(uint8_t)); + + err_code = write_port(rn->fd, tx_buf, 4); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->txp; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Set spreading factor on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_sf(struct RNode* rn, uint8_t sf) { + int err_code; + uint8_t tx_buf[4]; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_SF, sf, FEND}, 4*sizeof(uint8_t)); + + err_code = write_port(rn->fd, tx_buf, 4); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->sf; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Get spreading factor on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_sf(struct RNode* rn) { + uint8_t tx_buf[11]; + int err_code; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_SF, 0xFF, FEND}, 4*sizeof(uint8_t)); + + err_code = write_port(rn->fd, tx_buf, 4); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->sf; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Set coding rate on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_cr(struct RNode* rn, uint8_t cr) { + uint8_t tx_buf[4]; + int err_code; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_CR, cr, FEND}, 4*sizeof(uint8_t)); + + err_code = write_port(rn->fd, tx_buf, 4); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->cr; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Get coding rate on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_cr(struct RNode* rn) { + uint8_t tx_buf[4]; + int err_code; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_CR, 0xFF, FEND}, 4*sizeof(uint8_t)); + + err_code = write_port(rn->fd, tx_buf, 4); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->cr; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Set short term airtime limit on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +float rnode_set_st_alock(struct RNode* rn, float at_l) { + int err_code; + int at = at_l * 100; + uint8_t tx_buf[4]; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[2]){FEND, CMD_ST_ALOCK}, 2*sizeof(uint8_t)); + memcpy(tx_buf + 2, (uint8_t[2]){(at >> 8 & 0xFF), (at & 0xFF)}, 1*sizeof(uint8_t)); + tx_buf[2 + 1] = FEND; + + err_code = write_port(rn->fd, tx_buf, 2 + 1 + 1); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->st_alock; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Set long term airtime limit on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +float rnode_set_lt_alock(struct RNode* rn, float at_l) { + int err_code; + int at = at_l * 100; + uint8_t tx_buf[4]; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[2]){FEND, CMD_LT_ALOCK}, 2*sizeof(uint8_t)); + memcpy(tx_buf + 2, (uint8_t[2]){(at >> 8 & 0xFF), (at & 0xFF)}, 1*sizeof(uint8_t)); + tx_buf[2 + 1] = FEND; + + err_code = write_port(rn->fd, tx_buf, 2 + 1 + 1); + + if (!err_code) { + sleep_ms(100); + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + if (!err_code) { + return rn->lt_alock; + } else { + return err_code; + } + } else { + return err_code; + } +} + +/* Mode selection functions */ + +/* Enable the host-controlled (normal) mode of operation on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_normal_mode(struct RNode* rn) { + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_CONF_DELETE, 0x00, FEND}, 4*sizeof(uint8_t)); + return write_port(rn->fd, tx_buf, 4); +} + +/* Enable the serial TNC mode of operation on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_tnc_mode(struct RNode* rn) { + int err_code = 0; + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_CONF_SAVE, 0x00, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + + return err_code; +} + +/* Misc functions */ +int rnode_set_int_avoid(struct RNode* rn, bool avoid) { + int err_code = 0; + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_DIS_IA, !avoid, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + + return err_code; +} + +/* EEPROM related functions */ + +/* Sets the firmware hash on an RNode. + * Scope: public + * Returns: + * 0 - success + * -1 - generic error + */ +int rnode_set_fw_hash(struct RNode* rn, uint8_t* hash) { + uint8_t tx_buf[2 + (FW_HASH_SIZE * 2) + 1]; + uint8_t escaped_hash[FW_HASH_SIZE*2]; + uint32_t escaped_hash_size; + unsigned char hash_str[FW_HASH_SIZE * 2 + 1] = ""; + + log_debug("Setting firmware hash..."); + + for (int i = 0; i < FW_HASH_SIZE; i++) { + sprintf(hash_str+i*2, "%02x", hash[i]); + } + + log_trace("Firmware hash is %s", hash_str); + + memcpy(tx_buf, (uint8_t[2]){FEND, CMD_FW_HASH}, 2*sizeof(uint8_t)); + encode_slip(escaped_hash, &escaped_hash_size, hash, FW_HASH_SIZE, false); + memcpy(tx_buf + 2, escaped_hash, escaped_hash_size*sizeof(uint8_t)); + tx_buf[2 + escaped_hash_size] = FEND; + return write_port(rn->fd, tx_buf, 2 + escaped_hash_size + 1); +} + +/* Write to an arbitrary address in an RNode's EEPROM + * Scope: private + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_write_eeprom(struct RNode* rn, uint8_t address, uint8_t value) { + int err_code = 0; + uint8_t tx_buf[3 + 2*2]; + uint8_t data[2]; + uint8_t escaped_data[2*2]; + uint32_t escaped_data_size; + + data[0] = address; + data[1] = value; + + memcpy(tx_buf, (uint8_t[2]){FEND, CMD_ROM_WRITE}, 2*sizeof(uint8_t)); + + encode_slip(escaped_data, &escaped_data_size, data, 2, false); + + memcpy(tx_buf+2, escaped_data, escaped_data_size*sizeof(uint8_t)); + + tx_buf[2 + escaped_data_size] = FEND; + + err_code = write_port(rn->fd, tx_buf, 2 + escaped_data_size + 1); + + if (!err_code) { + sleep_ms(6); + } + return err_code; +} + +/* Sets an RNode's product in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_product(struct RNode* rn, uint8_t product) { + rn->product = product; + + return rnode_write_eeprom(rn, ADDR_PRODUCT, product); +} + +/* Sets an RNode's model in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_model(struct RNode* rn, uint8_t model) { + rn->model = model; + + return rnode_write_eeprom(rn, ADDR_MODEL, model); +} + +/* Sets an RNode's hardware revision in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_hw_rev(struct RNode* rn, uint8_t hw_rev) { + rn->hw_rev = hw_rev; + + return rnode_write_eeprom(rn, ADDR_HW_REV, hw_rev); +} + +int rnode_set_serial(struct RNode* rn, uint8_t* serial) { + int err_code = 0; + for (int i = 0; i < 4; i++) { + err_code = rnode_write_eeprom(rn, ADDR_SERIAL+i, serial[i]); + rn->serial[i] = serial[i]; + if (err_code) { + break; + } + } + return err_code; +} + +/* Sets an RNode's manufacture time in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_made_time(struct RNode* rn, uint32_t time) { + int err_code = 0; + uint8_t data[4]; + + data[3] = time & 0x000000FF; + data[2] = time >> 8 & 0x000000FF; + data[1] = time >> 16 & 0x000000FF; + data[0] = time >> 24 & 0x000000FF; + + for (int i = 0; i < 4; i++) { + err_code = rnode_write_eeprom(rn, ADDR_MADE+i, data[i]); + rn->made[i] = data[i]; + if (err_code) { + break; + } + } + return err_code; +} + +/* Calculates an RNode's checksum for its EEPROM. Must ONLY be called AFTER + * rnode_set_product, rnode_set_model, rnode_set_hw_rev, rnode_set_serial and + * rnode_set_made_time are all called. OR alternatively, after rnode_get_eeprom. + * Scope: public + * Returns + * 0 - success + * -1 - generic error + * -3 - provided array was too small + */ +int rnode_calculate_checksum(struct RNode* rn, uint8_t* checksum) { + uint8_t data[11]; + int temp_val; + unsigned char checksum_s[CHECKSUM_SIZE * 2]; + + data[0] = rn->product; + data[1] = rn->model; + data[2] = rn->hw_rev; + data[3] = rn->serial[0]; + data[4] = rn->serial[1]; + data[5] = rn->serial[2]; + data[6] = rn->serial[3]; + data[7] = rn->made[0]; + data[8] = rn->made[1]; + data[9] = rn->made[2]; + data[10] = rn->made[3]; + + MD5Data(data, 11, checksum_s); + + for (int i = 0; i < CHECKSUM_SIZE && sscanf(checksum_s + i * 2, "%2x", &temp_val); i++) { + checksum[i] = temp_val; + } + + return 0; +} + +/* Sets an RNode's checksum in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_checksum(struct RNode* rn, uint8_t* checksum) { + int err_code = 0; + for (int i = 0; i < CHECKSUM_SIZE; i++) { + err_code = rnode_write_eeprom(rn, ADDR_CHKSUM+i, checksum[i]); + if (err_code) { + break; + } + } + memcpy(rn->checksum, checksum, CHECKSUM_SIZE); + return err_code; +} + +/* Generate's an RNode's EEPROM signature from the checksum + * Scope: public + * Returns + * 0 - success + * -1 - generic error + * -3 - malloc failure + */ +void* rnode_generate_signature(struct RNode* rn, EVP_PKEY *signing_key) { + EVP_PKEY_CTX *ctx; + size_t hash_len = 256 / 8, sig_len; + uint8_t hash[256 / 8]; + void* signature; + + /* + * NB: assumes signing_key and md are set up before the next + * step. signing_key must be an RSA private key and md must + * point to the SHA-256 digest to be signed. + */ + ctx = EVP_PKEY_CTX_new(signing_key, NULL /* no engine */); + if (ctx == NULL) return NULL; + + if (EVP_PKEY_sign_init(ctx) <= 0) return NULL; + + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) return NULL; + + if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0) return NULL; + + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, RSA_PSS_SALTLEN_MAX) <= 0) return NULL; + + if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256()) <= 0) return NULL; + + SHA256(rn->checksum, CHECKSUM_SIZE, hash); + + /* Determine buffer length */ + if (EVP_PKEY_sign(ctx, NULL, &sig_len, hash, hash_len) <= 0) return NULL; + + // todo need to free signature + signature = OPENSSL_malloc(sig_len); + + if (signature == NULL) return NULL; + + if (EVP_PKEY_sign(ctx, signature, &sig_len, hash, hash_len) <= 0) return NULL; + + /* Signature is siglen bytes written to buffer sig */ + + EVP_PKEY_CTX_free(ctx); + + return signature; +} + +/* Sets an RNode's signature in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_signature(struct RNode* rn, uint8_t* data) { + int err_code = 0; + for (int i = 0; i < SIGNATURE_SIZE; i++) { + err_code = rnode_write_eeprom(rn, ADDR_SIGNATURE+i, data[i]); + if (err_code) { + break; + } + } + return err_code; +} + +/* Sets an RNode's lock byte in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_lock(struct RNode* rn) { + return rnode_write_eeprom(rn, ADDR_INFO_LOCK, INFO_LOCK_BYTE); +} + +/* Retrieve an RNode's EEPROM values into its struct + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_eeprom(struct RNode* rn) { + int err_code = rnode_dump_eeprom(rn); + + // Populate remote EEPROM values + memcpy(&rn->product, rn->r_eeprom, 1); + memcpy(&rn->model, rn->r_eeprom+1, 1); + memcpy(&rn->hw_rev, rn->r_eeprom+2, 1); + memcpy(&rn->serial, rn->r_eeprom+3, 4); + memcpy(&rn->made, rn->r_eeprom+7, 4); + memcpy(&rn->checksum, rn->r_eeprom+11, 16); + memcpy(&rn->signature, rn->r_eeprom+27, 128); + memcpy(&rn->lock_byte, rn->r_eeprom+155, 1); + memcpy(&rn->sf, rn->r_eeprom+156, 1); + memcpy(&rn->cr, rn->r_eeprom+157, 1); + memcpy(&rn->txp, rn->r_eeprom+158, 1); + memcpy(&rn->bw, rn->r_eeprom+159, 4); + memcpy(&rn->freq, rn->r_eeprom+163, 4); + memcpy(&rn->cfg_ok, rn->r_eeprom+167, 4); + memcpy(&rn->bt, rn->r_eeprom+168, 1); + memcpy(&rn->disp_set, rn->r_eeprom+169, 1); + memcpy(&rn->disp_int, rn->r_eeprom+170, 1); + memcpy(&rn->disp_addr, rn->r_eeprom+171, 1); + + return err_code; +} + +/* Dump an RNode's entire EEPROM to r_eeprom + * Note: This will populate r_eeprom but not the cached EEPROM values. If you + * want to update the cached values, call rnode_get_eeprom instead. + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_dump_eeprom(struct RNode* rn) { + int err_code; + uint8_t tx_buf[4]; + bool in_frame = false; + uint8_t cmd = CMD_UNKNOWN; + uint8_t cmd_buf[10] = {0}; + uint8_t cmd_buf_l = 0; + uint16_t frame_len = 0; + + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_ROM_READ, 0x00, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + + if (!err_code) { + err_code = rnode_handle_resp(rn, &in_frame, &cmd, cmd_buf, &cmd_buf_l, &frame_len); + } + + return err_code; +} + +/* Verify an RNode's EEPROM matches the expected values + * Scope: public + * Returns + * 0 - success + * value > 0 or value < 0 - EEPROM invalid + */ +int rnode_verify_eeprom(struct RNode* rn) { + int err_code; + + err_code = rnode_dump_eeprom(rn); + + if (!err_code) { + err_code = memcmp(&rn->product, rn->r_eeprom, sizeof(1)); + } + + if (!err_code) { + err_code = memcmp(&rn->model, rn->r_eeprom+1, sizeof(1)); + } + + if (!err_code) { + err_code = memcmp(&rn->hw_rev, rn->r_eeprom+2, sizeof(1)); + } + + if (!err_code) { + err_code = memcmp(&rn->serial, rn->r_eeprom+3, sizeof(4)); + } + + if (!err_code) { + err_code = memcmp(&rn->made, rn->r_eeprom+7, sizeof(4)); + } + + if (!err_code) { + err_code = memcmp(&rn->checksum, rn->r_eeprom+11, sizeof(16)); + } + + if (!err_code) { + err_code = memcmp(&rn->signature, rn->r_eeprom+27, sizeof(128)); + } + + if (!err_code) { + err_code = memcmp(&rn->lock_byte, rn->r_eeprom+155, sizeof(1)); + } + + if (!err_code) { + err_code = memcmp(&rn->sf, rn->r_eeprom+156, sizeof(1)); + } + + if (!err_code) { + err_code = memcmp(&rn->cr, rn->r_eeprom+157, sizeof(1)); + } + + if (!err_code) { + err_code = memcmp(&rn->txp, rn->r_eeprom+158, sizeof(1)); + } + + if (!err_code) { + err_code = memcmp(&rn->bw, rn->r_eeprom+159, sizeof(4)); + } + + if (!err_code) { + err_code = memcmp(&rn->freq, rn->r_eeprom+163, sizeof(4)); + } + + if (!err_code) { + err_code = memcmp(&rn->cfg_ok, rn->r_eeprom+167, sizeof(1)); + } + + if (!err_code) { + err_code = memcmp(&rn->bt, rn->r_eeprom+168, sizeof(1)); + } + + if (!err_code) { + err_code = memcmp(&rn->disp_set, rn->r_eeprom+169, sizeof(1)); + } + + if (!err_code) { + err_code = memcmp(&rn->disp_int, rn->r_eeprom+170, sizeof(1)); + } + + if (!err_code) { + err_code = memcmp(&rn->disp_addr, rn->r_eeprom+171, sizeof(1)); + } + + return err_code; +} + +/* Wipes an RNode's EEPROM. + * Scope: public + * Returns: + * 0 - success + * -1 - generic error + * -2 - rnode not supported (firmware too old, please update) + */ +int rnode_wipe_eeprom(struct RNode* rn) { + log_info("Erasing EEPROM, please switch off your device IMMEDIATELY if you did not intend to do this!"); + int err_code = 0; + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_UNLOCK_ROM, 0xF8, FEND}, 4*sizeof(uint8_t)); + if (rn->fw_ver != 0 && rn->fw_ver < MIN_FW_VER) { + log_trace("RNode firmware version too low!"); + return -2; + } else { + err_code = write_port(rn->fd, tx_buf, 4); + } + + if (err_code) { + return err_code; + } + + err_code = close_port(rn->fd); + + if (!err_code) { + sleep_ms(13000); + + if (rn->platform == PLATFORM_NRF52) { + // Due to the current janky emulated EEPROM implementation for the + // RAK4631, extra time must be given to allow for writing. + sleep_ms(10000); + } + } else { + return err_code; + } + + rn->fd = open_port(rn->port, rn->baud); + + return (err_code == 0) ? 0 : -1; +} + +int rnode_flash_progress_cb(struct RNode* rn, void (*ptr)(uint8_t)) { + rn->prog_cb = ptr; +} + +/* Flashes an RNode. + * Scope: public + * Returns: + * 0 - success + * -1 - generic error + * -9 - ESP32 image invalid, SHA256 digest incorrect + */ +int rnode_flash(struct RNode* rn, char* zip_path, bool update, uint8_t* serial, EVP_PKEY* priv_key, bool touch) { + int err_code = 0; + uint8_t hash[FW_HASH_SIZE]; + + if (update) { + // show update logo on RNode display + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_FW_UPD, 0x01, FEND}, 4*sizeof(uint8_t)); + err_code = write_port(rn->fd, tx_buf, 4); + } + + // Close port to prepare for flashing + if (!err_code) { + err_code = close_port(rn->fd); + } + + if (!err_code) { + switch (rn->platform) { + case PLATFORM_ESP32: + uint8_t img_hash[32]; + struct zip_t *zip_pkg; + char* bin_ext = ".bin"; + char* bootloader_ext = ".bootloader"; + char* boot_app0_ext = ".boot_app0"; + char* partitions_ext = ".partitions"; + char* console_image_name = "console_image.bin"; + char *bin, *bootloader, *boot_app0, *partitions, *console_image = 0, *bin_path, *bootloader_path, *boot_app0_path, *partitions_path, *console_image_path, *filename, *filename_no_ext, *dir, *zip_path_cpy; + size_t bin_size, bootloader_size, boot_app0_size, partitions_size, console_image_size = 0, bin_path_size, bootloader_path_size, boot_app0_path_size, partitions_path_size, console_image_path_size, filename_no_ext_size; + zip_pkg = zip_open(zip_path, 0, 'r'); + if (zip_pkg == NULL) + { + log_error("Cannot open ZIP firmware package file!"); + + return -1; + } + else + { + zip_path_cpy = strdup(zip_path); + filename = basename(zip_path_cpy); + + for (int i = 0; i < strlen(filename); i++) { + if (filename[i] == '.') { + filename_no_ext_size = i; + break; + } + } + + filename_no_ext = malloc(filename_no_ext_size+2); + + memcpy(filename_no_ext, filename, filename_no_ext_size); + + filename_no_ext[filename_no_ext_size+1] = '\0'; + + // Retrieve bin file + bin_path_size = filename_no_ext_size+strlen(bin_ext); + bin_path = malloc(bin_path_size+1); + strcpy(bin_path, filename_no_ext); + strcat(bin_path, bin_ext); + + if (zip_entry_open(zip_pkg, bin_path)) { + return -1; + } else { + bin = malloc(FILE_MAX_SIZE); + if (zip_entry_read(zip_pkg, (void**)&bin, &bin_size)) { + return -1; + } else { + if (zip_entry_close(zip_pkg)) { + return -1; + } + } + } + + // Retrieve bootloader + bootloader_path_size = filename_no_ext_size+strlen(bootloader_ext); + bootloader_path = malloc(bootloader_path_size+1); + strcpy(bootloader_path, filename_no_ext); + strcat(bootloader_path, bootloader_ext); + + if (zip_entry_open(zip_pkg, bootloader_path)) { + return -1; + } else { + bootloader = malloc(FILE_MAX_SIZE); + if (zip_entry_read(zip_pkg, (void**)&bootloader, &bootloader_size)) { + return -1; + } else { + if (zip_entry_close(zip_pkg)) { + return -1; + } + } + } + + // Retrieve boot_app0 + boot_app0_path_size = filename_no_ext_size+strlen(boot_app0_ext); + boot_app0_path = malloc(boot_app0_path_size+1); + strcpy(boot_app0_path, filename_no_ext); + strcat(boot_app0_path, boot_app0_ext); + + if (zip_entry_open(zip_pkg, boot_app0_path)) { + return -1; + } else { + boot_app0 = malloc(FILE_MAX_SIZE); + if (zip_entry_read(zip_pkg, (void**)&boot_app0, &boot_app0_size)) { + return -1; + } else { + if (zip_entry_close(zip_pkg)) { + return -1; + } + } + } + + // Retrieve partitions + partitions_path_size = filename_no_ext_size+strlen(partitions_ext); + partitions_path = malloc(partitions_path_size+1); + strcpy(partitions_path, filename_no_ext); + strcat(partitions_path, partitions_ext); + + if (zip_entry_open(zip_pkg, partitions_path)) { + return -1; + } else { + partitions = malloc(FILE_MAX_SIZE); + if (zip_entry_read(zip_pkg, (void**)&partitions, &partitions_size)) { + return -1; + } else { + if (zip_entry_close(zip_pkg)) { + return -1; + } + } + } + + // Retrieve console image + if (zip_entry_open(zip_pkg, console_image_name)) { + // No console image on this build, skipping + } else { + console_image = malloc(FILE_MAX_SIZE); + if (zip_entry_read(zip_pkg, (void**)&console_image, &console_image_size)) { + return -1; + } else { + if (zip_entry_close(zip_pkg)) { + return -1; + } + } + } + + // Check firmware hash + memcpy(img_hash, (bin + bin_size) - 32, 32); + SHA256(bin, bin_size-32, hash); + + if (memcmp(hash, img_hash, 32*sizeof(uint8_t) != 0)) { + return -9; + } + + + flash_full(rn, boot_app0, boot_app0_size, bootloader, bootloader_size, bin, bin_size, partitions, partitions_size, console_image, console_image_size); + + free(console_image); + free(partitions); + free(partitions_path); + free(boot_app0); + free(boot_app0_path); + free(bootloader); + free(bootloader_path); + free(bin); + free(bin_path); + free(filename_no_ext); + free(zip_path_cpy); + } + break; + case PLATFORM_NRF52: + { + // No space for console image on flash on this target currently + dfu_param_t dfu_param; + uart_drv_t uart_drv; + uart_drv.p_PortName = rn->port; + err_code = uart_slip_open(&uart_drv, touch); + + if (!err_code) { + dfu_param.p_uart = &uart_drv; + dfu_param.p_pkg_file = zip_path; + err_code = dfu_send_package(&dfu_param, rn, hash); + } else { + return -1; + } + + err_code = uart_slip_close(&uart_drv); + } + } + } + // Wait for reset and boot. This value may have to be adjusted in the + // future if firmware becomes more complex. + sleep_ms(15000); + + rn->fd = open_port(rn->port, rn->baud); + + if (rn->fd > 0) { + if (!update) { + uint8_t checksum[CHECKSUM_SIZE]; + void* signature; + + log_info("Bootstrapping RNode EEPROM..."); + // Provision EEPROM + err_code = rnode_set_product(rn, rn->product); + if (!err_code) { + err_code = rnode_set_model(rn, rn->model); + } + + if (!err_code) { + err_code = rnode_set_hw_rev(rn, rn->hw_rev); + } + + if (!err_code) { + err_code = rnode_set_serial(rn, serial); + } + + if (!err_code) { + time_t current = time(NULL); + err_code = rnode_set_made_time(rn, current); + } + + if (!err_code) { + err_code = rnode_calculate_checksum(rn, checksum); + } + + if (!err_code) { + err_code = rnode_set_checksum(rn, checksum); + } + + if (!err_code) { + signature = rnode_generate_signature(rn, priv_key); + } + + if (signature != NULL) { + err_code = rnode_set_signature(rn, signature); + } + + if (!err_code) { + OPENSSL_free(signature); + err_code = rnode_set_lock(rn); + } + } + } else { + err_code = -1; + } + + if (!err_code) { + sleep_ms(750); + err_code = rnode_set_fw_hash(rn, hash); + } else { + return err_code; + } + + + if (!err_code) { + log_info("Waiting 10 seconds for RNode to come online..."); + sleep_ms(10000); + if (rn->platform == PLATFORM_NRF52) { + err_code = close_port(rn->fd); + // Due to the current janky emulated EEPROM implementation for the + // RAK4631, extra time must be given to allow for writing. + log_info("Waiting an extra 15 seconds for RNode to come online..."); + sleep_ms(15000); + } else if (rn->platform == PLATFORM_ESP32) { + rnode_reset(rn); + err_code = close_port(rn->fd); + log_info("Waiting for ESP32 reset..."); + sleep_ms(7000); + } + err_code = rnode_init(rn, rn->port, rn->baud, true, true); + } else { + return err_code; + } + + if (!err_code) { + err_code = rnode_verify_eeprom(rn); + } + + return err_code; +} + +int rnode_disconnect(struct RNode* rn) { + uint8_t tx_buf[4]; + memcpy(tx_buf, (uint8_t[4]){FEND, CMD_LEAVE, 0xFF, FEND}, 4*sizeof(uint8_t)); + return write_port(rn->fd, tx_buf, 4); +} + +int rnode_cleanup(struct RNode* rn) { + close_port(rn->fd); +} diff --git a/src/librnode.h b/src/librnode.h new file mode 100644 index 0000000..a2db305 --- /dev/null +++ b/src/librnode.h @@ -0,0 +1,476 @@ +/* Copyright (c) 2025 - Jacob Eva (Liberated Embedded Systems) + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef LIBRNODE_H + +#define LIBRNODE_H + +#include +#include +#include +#include + +#define MIN_FW_VER 1.66 + +// PLATFORMS +#define PLATFORM_ESP32 0x80 +#define PLATFORM_NRF52 0x70 +// + +#define SERIAL_SIZE 4 + +#define MADE_SIZE 4 + +#define CHECKSUM_SIZE 16 + +#define SIGNATURE_SIZE 128 + +#define FW_HASH_SIZE 256 / 8 + +#define RESP_BUF_SIZE 512 + +#define EEPROM_SIZE 296 + +#define MAX_INTERFACES 256 + +#define FILE_MAX_SIZE 4 * 1024 * 1024 // 4MB max file size for single fw binary + +// BLUETOOTH VALUES +#define BT_OFF 0 +#define BT_ON 1 +#define BT_PAIRING 2 +// + +struct RNode { + char* port; + int fd; + uint32_t baud; + + uint8_t platform; + uint8_t mcu; + + // EEPROM below here! + + uint8_t product; + uint8_t model; + uint8_t hw_rev; + + uint8_t serial[4]; + + uint8_t made[4]; + + uint8_t checksum[16]; + + uint8_t signature[128]; + + uint8_t lock_byte; + + // All these have their own getter functions as they can change on runtime; the cached EEPROM values may not reflect the true status of the radio + uint8_t sf; + uint8_t cr; + uint8_t txp; + uint32_t bw; + uint32_t freq; + + uint8_t cfg_ok; + + bool bt; + + bool disp_set; + uint8_t disp_int; + uint8_t disp_addr; + + // This is where the EEPROM read directly from the RNode is stored + uint8_t r_eeprom[EEPROM_SIZE]; + + // END EEPROM + + float st_alock; + float lt_alock; + + uint32_t bt_pairing_pin; + + // ESP32 only! + + uint32_t boot_app0_addr; + uint32_t bootloader_addr; + uint32_t bin_addr; + uint32_t partitions_addr; + uint32_t console_image_addr; + + // + + float fw_ver; + + bool connected; + + // Multiple modems + + uint8_t sel_int; + + uint8_t interfaces[MAX_INTERFACES]; + + // + + void (*prog_cb)(uint8_t); +}; + +/* Establishes communication with an RNode + * Params: port (e.g. /dev/ttyACM0), baud rate (e.g. 115200), detect (attempt + * to communicate with RNode), force_detect (fail if cannot communicate with + * RNode) + * Scope: public + * Returns: + * 0 - success + * -1 - generic error + */ +int rnode_init(struct RNode* rn, char* path, uint32_t baud, bool detect, bool force_detect); + +/* Resets an RNode + * Scope: public + * Returns: + * 0 - success + * -1 - generic error + */ +int rnode_reset(struct RNode* rn); + +/* Sets an RNode's platform attribute manually + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_platform(struct RNode* rn, uint8_t platform); + +/* Enable / disable Bluetooth on an RNode + * Scope: public + * Returns + * > 0 - bluetooth pairing pin value + * 0 - success + * -1 - generic error + * -4 - rnode did not respond + */ +int rnode_set_bt(struct RNode* rn, uint8_t val); + +/* Attempt to retrieve the BT pairing code of an RNode. Blocking function, + * should be run in a separate thread. + * Params: timeout (in ms, minimum value 200) + * Scope: public + * Returns + * > 0 - bluetooth pairing code + * 0 - success + * -1 - generic error + * -4 - rnode did not respond + */ +int rnode_get_bt_pin(struct RNode* rn, uint32_t timeout); + +/* Display related functions */ + +/* Set display intensity on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_disp_int(struct RNode* rn, uint8_t disp_int); + +/* Set display timeout on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_disp_timeout(struct RNode* rn, uint8_t timeout); + +/* Set display address on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_disp_addr(struct RNode* rn, uint8_t addr); + +#define DISPLAY_PORTRAIT 0 +#define DISPLAY_LANDSCAPE 1 +#define DISPLAY_PORTRAIT_INV 2 +#define DISPLAY_LANDSCAPE_INV 3 +/* Set display rotation on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_disp_rot(struct RNode* rn, uint8_t rot); + +/* Start display reconditioning on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_start_disp_recon(struct RNode* rn); + +/* Set neopixel intensity on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_np_int(struct RNode* rn, uint8_t np_int); + +/* Get available interfaces on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_interfaces(struct RNode* rn); + +/* Select interface on an RNode (in preparation for running rnode_set_freq, etc) + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_select_interface(struct RNode* rn, uint8_t modem); + +/* Set frequency on an RNode (in preparation for TNC mode) + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_freq(struct RNode* rn, uint32_t freq); + +/* Get frequency on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_freq(struct RNode* rn); + +/* Set bandwidth on an RNode (in preparation for TNC mode) + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_bw(struct RNode* rn, uint32_t bw); + +/* Get bandwidth on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_bw(struct RNode* rn); + +/* Set transmission power on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_txp(struct RNode* rn, uint8_t txp); + +/* Get transmission power on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_txp(struct RNode* rn); + +/* Set spreading factor on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_sf(struct RNode* rn, uint8_t sf); + +/* Get spreading factor on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_sf(struct RNode* rn); + +/* Set coding rate on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_cr(struct RNode* rn, uint8_t cr); + +/* Get coding rate on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_cr(struct RNode* rn); + +/* Set short term airtime limit on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +float rnode_set_st_alock(struct RNode* rn, float at_l); + +/* Set long term airtime limit on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +float rnode_set_lt_alock(struct RNode* rn, float at_l); + +/* Set radio modem state (on or off) + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_radio_state(struct RNode* rn, bool state); + +/* Mode selection functions */ + +/* Enable the host-controlled (normal) mode of operation on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_normal_mode(struct RNode* rn); + +/* Enable the serial TNC mode of operation on an RNode + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_tnc_mode(struct RNode* rn); + +/* EEPROM related functions */ + +/* Sets the firmware hash on an RNode. + * Scope: public + * Returns: + * 0 - success + * -1 - generic error + */ +int rnode_set_fw_hash(struct RNode* rn, uint8_t* hash); + +/* Sets an RNode's product in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_product(struct RNode* rn, uint8_t product); + +/* Sets an RNode's model in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_model(struct RNode* rn, uint8_t model); + +/* Sets an RNode's hardware revision in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_hw_rev(struct RNode* rn, uint8_t hw_rev); + +/* Generate's an RNode's EEPROM signature from the checksum + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +void* rnode_generate_signature(struct RNode* rn, EVP_PKEY *signing_key); + +/* Sets an RNode's signature in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_signature(struct RNode* rn, uint8_t* data); + +/* Sets an RNode's lock byte in its EEPROM + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_set_lock(struct RNode* rn); + +/* Retrieve an RNode's EEPROM values into its struct + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_get_eeprom(struct RNode* rn); + +/* Dump an RNode's entire EEPROM to r_eeprom + * Note: This will populate r_eeprom but not the cached EEPROM values. If you + * want to update the cached values, call rnode_get_eeprom instead. + * Scope: public + * Returns + * 0 - success + * -1 - generic error + */ +int rnode_dump_eeprom(struct RNode* rn); + +/* Verify an RNode's EEPROM matches the expected values + * Scope: public + * Returns + * 0 - success + * value > 0 or value < 0 - EEPROM invalid + */ +int rnode_verify_eeprom(struct RNode* rn); + +/* Wipes an RNode's EEPROM. + * Scope: public + * Returns: + * 0 - success + * -1 - generic error + * -2 - rnode not supported (firmware too old, please update) + */ +int rnode_wipe_eeprom(struct RNode* rn); + + +/* Flashes an RNode. + * Scope: public + * Returns: + * 0 - success + * -1 - generic error + * -9 - ESP32 image invalid, SHA256 digest incorrect + */ +int rnode_flash(struct RNode* rn, char* zip_path, bool update, uint8_t* serial, EVP_PKEY* priv_key, bool touch); + + +int rnode_cleanup(struct RNode* rn); + + +#endif diff --git a/src/libs/eeprom.h b/src/libs/eeprom.h new file mode 100644 index 0000000..a59e60a --- /dev/null +++ b/src/libs/eeprom.h @@ -0,0 +1,19 @@ +#ifndef EEPROM_H + #define EEPROM_H + #define ADDR_PRODUCT 0x00 + #define ADDR_MODEL 0x01 + #define ADDR_HW_REV 0x02 + #define ADDR_SERIAL 0x03 + #define ADDR_MADE 0x07 + #define ADDR_CHKSUM 0x0B + #define ADDR_SIGNATURE 0x1B + #define ADDR_INFO_LOCK 0x9B + #define ADDR_CONF_SF 0x9C + #define ADDR_CONF_CR 0x9D + #define ADDR_CONF_TXP 0x9E + #define ADDR_CONF_BW 0x9F + #define ADDR_CONF_FREQ 0xA3 + #define ADDR_CONF_OK 0xA7 + #define INFO_LOCK_BYTE 0x73 + #define CONF_OK_BYTE 0x73 +#endif diff --git a/src/libs/flashers/esp32/LICENSE b/src/libs/flashers/esp32/LICENSE new file mode 100644 index 0000000..550ba82 --- /dev/null +++ b/src/libs/flashers/esp32/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021,2022 Cesanta + +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. diff --git a/src/libs/flashers/esp32/README.md b/src/libs/flashers/esp32/README.md new file mode 100644 index 0000000..e7d7585 --- /dev/null +++ b/src/libs/flashers/esp32/README.md @@ -0,0 +1,3 @@ +Original repo can be found here: https://github.com/cpq/esputil + +Modified for use in librnode. diff --git a/src/libs/flashers/esp32/esputil.c b/src/libs/flashers/esp32/esputil.c new file mode 100644 index 0000000..21790a8 --- /dev/null +++ b/src/libs/flashers/esp32/esputil.c @@ -0,0 +1,1218 @@ +// Copyright (c) 2021-2022 Cesanta +// All rights reserved +// +// Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +// Changes licensed under the GPL. +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// You should have received a copy of the GNU General Public License along +// with this program. If not, see . +// +// Use MSVC98 for _WIN32, thus ISO C90. MCVC98 links against un-versioned +// msvcrt.dll, therefore produced .exe works everywhere. + +#include "esputil.h" + +// Needed by MSVC + +#define WIN32_LEAN_AND_MEAN +#define _CRT_SECURE_NO_WARNINGS +#define _WINSOCK_DEPRECATED_NO_WARNINGS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 // Windows includes +#include +#include +#include +#include +#define strcasecmp(x, y) _stricmp((x), (y)) +#define mkdir(x, y) _mkdir(x) +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define snprintf _snprintf +#define inline __inline +typedef unsigned __int64 uint64_t; +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef enum { false = 0, true = 1 } bool; +#else +#include +#include +#endif +#else // UNIX includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +enum { READY_STDIN = 1, READY_SERIAL = 2, READY_SOCK = 4 }; +//#define ALIGN(a, b) (((a) + (b) -1) / (b) * (b)) + +// https://datatracker.ietf.org/doc/html/rfc1055 +enum { END = 192, ESC = 219, ESC_END = 220, ESC_ESC = 221 }; + +// SLIP state machine +struct slip { + unsigned char *buf; // Buffer for the network mode + size_t size; // Buffer size + size_t len; // Number of currently buffered bytes + int mode; // Operation mode. 0 - serial, 1 - network + unsigned char prev; // Previously read character +}; + +struct chip { + uint32_t id; // Chip ID, stored in the ROM address 0x40001000 +#define CHIP_ID_ESP32 0x00f01d83 +#define CHIP_ID_ESP32_S2 0x000007c6 +#define CHIP_ID_ESP32_C3_ECO_1_2 0x6921506f +#define CHIP_ID_ESP32_C3_ECO3 0x1b31506f +#define CHIP_ID_ESP8266 0xfff0c101 +#define CHIP_ID_ESP32_S3_BETA2 0xeb004136 +#define CHIP_ID_ESP32_S3_BETA3 0x9 +#define CHIP_ID_ESP32_C6_BETA 0x0da1806f + const char *name; // Chpi name, e.g. "ESP32-S2" + uint32_t bla; // Bootloader flash offset +}; + +struct ctx { + struct slip slip; // SLIP state machine + uint32_t baud; // Baud rate, e.g. "115200" + const char *port; // Serial port, e.g. "/dev/ttyUSB0" + const char *fpar; // Flash params, e.g. "0x220" + const char *fspi; // Flash SPI pins: CLK,Q,D,HD,CS. E.g. "6,17,8,11,16" + bool verbose; // Hexdump serial comms + int fd; // Serial port file descriptor + int sock; // UDP socket for exchanging SLIP frames when monitor + struct sockaddr_in sin; // UDP sockaddr of the remote peer + struct chip chip; // Chip descriptor +}; + +static struct chip s_known_chips[] = { + {0, "Unknown", 0}, + {CHIP_ID_ESP8266, "ESP8266", 0}, + {CHIP_ID_ESP32, "ESP32", 4096}, + {CHIP_ID_ESP32_C3_ECO_1_2, "ESP32-C3-ECO2", 0}, + {CHIP_ID_ESP32_C3_ECO3, "ESP32-C3-ECO3", 0}, + {CHIP_ID_ESP32_S2, "ESP32-S2", 4096}, + {CHIP_ID_ESP32_S3_BETA2, "ESP32-S3-BETA2", 0}, + {CHIP_ID_ESP32_S3_BETA3, "ESP32-S3-BETA3", 0}, + {CHIP_ID_ESP32_C6_BETA, "ESP32-C6-BETA", 0}, +}; + +static int s_signo; + +static void slip_send(const void *buf, size_t len, + void (*fn)(unsigned char, void *), void *arg) { + const unsigned char *p = buf; + size_t i; + fn(END, arg); + for (i = 0; i < len; i++) { + if (p[i] == END) { + fn(ESC, arg); + fn(ESC_END, arg); + } else if (p[i] == ESC) { + fn(ESC, arg); + fn(ESC_ESC, arg); + } else { + fn(p[i], arg); + } + } + fn(END, arg); +} + +// Process incoming byte `c`. +// In serial mode, do nothing, return 1. +// In network mode, append a byte to the `buf` and increment `len`. +// Return size of the buffered packet when switching to serial mode, or 0 +static size_t slip_recv(unsigned char c, struct slip *slip) { + size_t res = 0; + if (slip->mode) { + if (slip->prev == ESC && c == ESC_END) { + slip->buf[slip->len++] = END; + } else if (slip->prev == ESC && c == ESC_ESC) { + slip->buf[slip->len++] = ESC; + } else if (c == END) { + res = slip->len; + } else if (c != ESC) { + slip->buf[slip->len++] = c; + } + if (slip->len >= slip->size) slip->len = 0; // Silent overflow + } + slip->prev = c; + // The "END" character flips the mode + if (c == END) slip->len = 0, slip->mode = !slip->mode; + return res; +} + +void signal_handler(int signo) { + s_signo = signo; +} + +static int fail(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +static char *hexdump(const void *buf, size_t len, char *dst, size_t dlen) { + const unsigned char *p = (const unsigned char *) buf; + size_t i, idx, n = 0, ofs = 0; + char ascii[17] = ""; + if (dst == NULL) return dst; + memset(dst, ' ', dlen); + for (i = 0; i < len; i++) { + idx = i % 16; + if (idx == 0) { + if (i > 0 && dlen > n) + n += (size_t) snprintf(dst + n, dlen - n, " %s\n", ascii); + if (dlen > n) + n += (size_t) snprintf(dst + n, dlen - n, "%04x ", (int) (i + ofs)); + } + if (dlen < n) break; + n += (size_t) snprintf(dst + n, dlen - n, " %02x", p[i]); + ascii[idx] = (char) (p[i] < 0x20 || p[i] > 0x7e ? '.' : p[i]); + ascii[idx + 1] = '\0'; + } + while (i++ % 16) { + if (n < dlen) n += (size_t) snprintf(dst + n, dlen - n, "%s", " "); + } + if (n < dlen) n += (size_t) snprintf(dst + n, dlen - n, " %s\n", ascii); + if (n > dlen - 1) n = dlen - 1; + dst[n] = '\0'; + return dst; +} + +static void dump(const char *label, const uint8_t *buf, size_t len) { + size_t n = len * 5 + 100; // Hexdump buffer len + char *tmp = malloc(n); // Hexdump buffer + printf("%s [%d bytes]\n%s\n", label, (int) len, hexdump(buf, len, tmp, n)); + free(tmp); +} + +static void uart_tx(unsigned char ch, void *arg) { + int fd = *(int *) arg; + if (write(fd, &ch, 1) != 1) fail("failed to write %d to fd %d\n", ch, fd); +} + +static void usage(struct ctx *ctx) { + printf("Defaults: BAUD=%s, PORT=%s\n", ctx->baud, ctx->port); + printf("Usage:\n"); + printf(" esputil [-v] [-b BAUD] [-p PORT] info\n"); + printf(" esputil [-v] [-b BAUD] [-p PORT] [-udp PORT] monitor\n"); + printf(" esputil [-v] [-b BAUD] [-p PORT] readmem ADDR SIZE\n"); + printf(" esputil [-v] [-b BAUD] [-p PORT] readflash ADDR SIZE\n"); + printf(" esputil [-v] [-b BAUD] [-p PORT] [-fp FLASH_PARAMS] "); + printf("[-fspi FLASH_SPI] flash ADDrESS1 FILE1.bin ...\n"); + printf(" esputil [-v] [-b BAUD] [-p PORT] [-fp FLASH_PARAMS] "); + printf("[-fspi FLASH_SPI] flash FILE.HEX\n"); + printf(" esputil [-v] [-chip detect] mkbin FIRMWARE.ELF FIRMWARE.BIN\n"); + printf(" esputil mkhex ADDRESS1 BINFILE1 ADDRESS2 BINFILE2 ...\n"); + printf(" esputil [-tmp TMP_DIR] unhex HEXFILE\n"); + exit(EXIT_FAILURE); +} + +// clang-format off +static const char *ecode_to_str(int ecode) { + switch (ecode) { + case 5: return "Received message is invalid"; + case 6: return "Failed to act on received message"; + case 7: return "Invalid CRC in message"; + case 8: return "Flash write error"; + case 9: return "Flash read error" ; + case 10: return "Flash read length error"; + case 11: return "Deflate error"; + default: return "Unknown error"; + } +} + +static const char *cmdstr(int code) { + switch (code) { + case 2: return "FLASH_BEGIN"; + case 3: return "FLASH_DATA"; + case 4: return "FLASH_END"; + case 5: return "MEM_BEGIN"; + case 6: return "MEM_END" ; + case 7: return "MEM_DATA"; + case 8: return "SYNC"; + case 9: return "WRITE_REG"; + case 10: return "READ_REG"; + case 11: return "SPI_SET_PARAMS"; + case 13: return "SPI_ATTACH"; + case 14: return "READ_FLASH_SLOW"; + case 15: return "CHANGE_BAUD_RATE"; + default: return "CMD_UNKNOWN"; + } +} +// clang-format on + +static uint8_t checksum2(uint8_t v, const uint8_t *buf, size_t len) { + while (len--) v ^= *buf++; + return v; +} + +static uint8_t checksum(const uint8_t *buf, size_t len) { + return checksum2(0xef, buf, len); +} + +#ifdef _WIN32 // Windows - specific routines +static void sleep_ms(int milliseconds) { + Sleep(milliseconds); +} + +static void flushio(int fd) { + PurgeComm((HANDLE) _get_osfhandle(fd), PURGE_RXCLEAR | PURGE_TXCLEAR); +} + +static void change_baud(int fd, int baud, bool verbose) { + DCB cfg = {sizeof(cfg)}; + HANDLE h = (HANDLE) _get_osfhandle(fd); + if (GetCommState(h, &cfg)) { + cfg.ByteSize = 8; + cfg.Parity = NOPARITY; + cfg.StopBits = ONESTOPBIT; + cfg.fBinary = TRUE; + cfg.fParity = TRUE; + cfg.BaudRate = baud; + SetCommState(h, &cfg); + } else { + fail("GetCommState(%x): %d\n", h, GetLastError()); + } +} + +static int open_serial(const char *name, int baud, bool verbose) { + char path[100]; + COMMTIMEOUTS ct = {1, 0, 1, 0, MAXDWORD}; // 1 ms read timeout + int fd; + // If serial port is specified as e.g. "COM3", prepend "\\.\" to it + snprintf(path, sizeof(path), "%s%s", name[0] == '\\' ? "" : "\\\\.\\", name); + fd = open(path, O_RDWR | O_BINARY); + #ifdef linux + int flag = TIOCM_RTS; + ioctl(fd,TIOCMBIS,&flag); + sleep_ms(100); + ioctl(fd,TIOCMBIC,&flag); + #endif + #ifdef _WIN32 + // todo do the RTS pin logic for Windows + #endif + + if (fd < 0) fail("open(%s): %s\n", path, strerror(errno)); + change_baud(fd, baud, verbose); + SetCommTimeouts((HANDLE) _get_osfhandle(fd), &ct); + return fd; +} + +static bool is_ready(int fd) { + DWORD errors = 0; + COMSTAT cs = {0}; + ClearCommError((HANDLE) _get_osfhandle(fd), &errors, &cs); + return cs.cbInQue > 0; +} + +static int iowait(int fd, int sock, int ms) { + DWORD errors, flags = 0; + int i; + for (i = 0; i < ms && flags == 0; i++) { + if (is_ready(fd)) flags |= READY_SERIAL; + if (is_ready(0)) flags |= READY_STDIN; + if (flags == 0) sleep_ms(1); + } + return flags; +} + +static void set_rts(int fd, bool value) { + EscapeCommFunction((HANDLE) _get_osfhandle(fd), value ? SETRTS : CLRRTS); +} + +static void set_dtr(int fd, bool value) { + EscapeCommFunction((HANDLE) _get_osfhandle(fd), value ? SETDTR : CLRDTR); +} +#else // UNIX - specific routines +static void set_rts(int fd, bool value) { + int v = TIOCM_RTS; + ioctl(fd, value ? TIOCMBIS : TIOCMBIC, &v); +} + +static void set_dtr(int fd, bool value) { + int v = TIOCM_DTR; + ioctl(fd, value ? TIOCMBIS : TIOCMBIC, &v); +} + +static void flushio(int fd) { + tcflush(fd, TCIOFLUSH); +} + +static void sleep_ms(int milliseconds) { + usleep(milliseconds * 1000); +} + +// clang-format off +static speed_t termios_baud(int baud) { + switch (baud) { + case 9600: return B9600; + case 19200: return B19200; + case 38400: return B38400; + case 57600: return B57600; + case 115200: return B115200; + case 230400: return B230400; +#ifndef __APPLE__ + case 460800: return B460800; + case 500000: return B500000; + case 576000: return B576000; + case 921600: return B921600; + case 1000000: return B1000000; + case 1152000: return B1152000; + case 1500000: return B1500000; + case 2000000: return B2000000; + case 2500000: return B2500000; + case 3000000: return B3000000; + case 3500000: return B3500000; + case 4000000: return B4000000; +#endif + default: return B0; + } +} +// clang-format on + +static void change_baud(int fd, int baud, bool verbose) { + struct termios tio; + if (tcgetattr(fd, &tio) != 0) + fail("Can't set fd %d to baud %d: %d\n", fd, baud, errno); + cfsetospeed(&tio, termios_baud(baud)); + cfsetispeed(&tio, termios_baud(baud)); + tcsetattr(fd, TCSANOW, &tio); + if (verbose) printf("fd %d set to baud %d\n", fd, baud); +} + +static int open_serial(const char *name, int baud, bool verbose) { + struct termios tio; + int fd = open(name, O_RDWR | O_NOCTTY | O_SYNC); + if (fd < 0) { + fail("open(%s): %d (%s)\n", name, fd, strerror(errno)); + } else if (tcgetattr(fd, &tio) == 0) { + tio.c_iflag = 0; // input mode + tio.c_oflag = 0; // output mode + tio.c_lflag = 0; // local flags + tio.c_cflag = CLOCAL | CREAD | CS8; // control flags + // Order is important: setting speed must go after setting flags, + // becase (depending on implementation) speed flags could reside in flags + cfsetospeed(&tio, termios_baud(baud)); + cfsetispeed(&tio, termios_baud(baud)); + tcsetattr(fd, TCSANOW, &tio); + } + if (verbose) printf("Opened %s @ %d fd=%d\n", name, baud, fd); + return fd; +} + +// Return true if port is readable (has data), false otherwise +static int iowait(int fd, int sock, int ms) { + int ready = 0; + struct timeval tv = {.tv_sec = ms / 1000, .tv_usec = (ms % 1000) * 1000}; + fd_set rset; + FD_ZERO(&rset); + FD_SET(0, &rset); // Listen to stdin too + FD_SET(fd, &rset); // Listen to the UART fd + if (sock > 0) FD_SET(sock, &rset); + if (select((fd > sock ? fd : sock) + 1, &rset, 0, 0, &tv) < 0) FD_ZERO(&rset); + if (FD_ISSET(0, &rset)) ready |= READY_STDIN; + if (FD_ISSET(fd, &rset)) ready |= READY_SERIAL; + if (sock > 0 && FD_ISSET(sock, &rset)) ready |= READY_SOCK; + return ready; +} +#endif // End of UNIX-specific routines + +static void hard_reset(int fd) { + set_dtr(fd, false); // IO0 -> HIGH + set_rts(fd, true); // EN -> LOW + sleep_ms(100); // Wait + set_rts(fd, false); // EN -> HIGH +} + +static void reset_to_bootloader_usb_jtag_serial(int fd) { + set_rts(fd, false); + set_dtr(fd, false); + sleep_ms(100); + set_dtr(fd, true); + set_rts(fd, false); + sleep_ms(100); + set_rts(fd, true); + set_dtr(fd, false); + set_rts(fd, true); + sleep_ms(100); + set_dtr(fd, false); + set_rts(fd, false); +} + +static void reset_to_bootloader(int fd) { + sleep_ms(100); // Wait + set_dtr(fd, false); // IO0 -> HIGH + set_rts(fd, true); // EN -> LOW + sleep_ms(100); // Wait + set_dtr(fd, true); // IO0 -> LOW + set_rts(fd, false); // EN -> HIGH + sleep_ms(50); // Wait + set_dtr(fd, false); // IO0 -> HIGH +} + +// Execute serial command. +// Return 0 on sucess, or error code on failure +static int cmd(struct ctx *ctx, uint8_t op, void *buf, uint16_t len, + uint32_t cs, int timeout_ms) { + uint8_t tmp[8 + 16384]; // 8 is size of the header + memset(tmp, 0, 8); // Clear header + tmp[1] = op; // Operation + memcpy(&tmp[2], &len, 2); // Length + memcpy(&tmp[4], &cs, 4); // Checksum + memcpy(&tmp[8], buf, len); // Data + + slip_send(tmp, 8 + len, uart_tx, &ctx->fd); // Send command + if (ctx->verbose) dump(cmdstr(op), tmp, 8 + len); // Hexdump if required + + for (;;) { + int i, n, ready, eofs, ecode; + ready = iowait(ctx->fd, ctx->sock, timeout_ms); // Wait for data + if (!(ready & READY_SERIAL)) return 1; // Interrupted, fail + n = read(ctx->fd, tmp, sizeof(tmp)); // Read from a device + if (n <= 0) fail("Serial line closed\n"); // Doh. Unplugged maybe? + // if (ctx->verbose) dump("--RAW_RESPONSE:", tmp, n); + for (i = 0; i < n; i++) { + size_t r = slip_recv(tmp[i], &ctx->slip); // Pass to SLIP state machine + // if (r == 0 && ctx->slip.mode == 0) putchar(tmp[i]); // In serial mode + if (r == 0) continue; + if (ctx->verbose) dump("--SLIP_RESPONSE:", ctx->slip.buf, r); + if (r < 10 || ctx->slip.buf[0] != 1 || ctx->slip.buf[1] != op) continue; + // ESP8266's error indicator is in the 2 last bytes, ESP32's - last 4 + eofs = + ctx->chip.id == 0 || ctx->chip.id == CHIP_ID_ESP8266 ? r - 2 : r - 4; + ecode = ctx->slip.buf[eofs] ? ctx->slip.buf[eofs + 1] : 0; + if (ecode) printf("error %d: %s\n", ecode, ecode_to_str(ecode)); + return ecode; + } + } + return 42; +} + +static int read32(struct ctx *ctx, uint32_t addr, uint32_t *value) { + int ok = cmd(ctx, 10, &addr, sizeof(addr), 0, 100); + if (ok == 0 && value != NULL) *value = *(uint32_t *) &ctx->slip.buf[4]; + return ok; +} + +// Read chip ID from ROM and setup ctx->chip pointer +static void chip_detect(struct ctx *ctx) { + size_t i, nchips; + uint32_t chipid; + if (read32(ctx, 0x40001000, &chipid)) fail("Error reading chip ID\n"); + nchips = sizeof(s_known_chips) / sizeof(s_known_chips[0]); + for (i = 0; i < nchips; i++) { + if (s_known_chips[i].id == chipid) { + if (ctx->chip.id && ctx->chip.id != chipid) { + fail("Chip specified (%s) does not match chip detected (%s)\n", + ctx->chip.name, s_known_chips[i].name); + } + ctx->chip = s_known_chips[i]; + return; + } + } + fail("Unknown chip ID: %08x\n", chipid); +} + +// Assume chip is rebooted and is in download mode. +// Send SYNC commands until success, and detect chip ID +static bool chip_connect(struct ctx *ctx) { + int i, j; + for (j = 0; j < 6; j++) { + // Alternate different reset methods + if (j & 1) { + reset_to_bootloader_usb_jtag_serial(ctx->fd); + } else { + reset_to_bootloader(ctx->fd); + } + flushio(ctx->fd); + for (i = 0; i < 2 + j; i++) { + uint8_t data[36] = {7, 7, 0x12, 0x20}; // SYNC command + memset(data + 4, 0x55, sizeof(data) - 4); // Fill with 0x55 + if (cmd(ctx, 8, data, sizeof(data), 0, 100) == 0) { + sleep_ms(50); + flushio(ctx->fd); // Discard all data + chip_detect(ctx); + return true; + } + } + } + return false; +} + +static void set_chip_id(struct ctx *ctx, const char *name) { + size_t i, nchips; + nchips = sizeof(s_known_chips) / sizeof(s_known_chips[0]); + for (i = 0; i < nchips; i++) { + if (strcasecmp(name, "detect") == 0) { + if (!chip_connect(ctx)) fail("Cannot detect chip\n"); + return; + } else if (strcasecmp(s_known_chips[i].name, name) == 0) { + ctx->chip = s_known_chips[i]; + return; + } + } + fail("Unknown chip type: %s\n", name); +} + +static void monitor(struct ctx *ctx) { + int i, ready = iowait(ctx->fd, ctx->sock, 1000); + if (ready & READY_SERIAL) { + uint8_t buf[BUFSIZ]; + int n = read(ctx->fd, buf, sizeof(buf)); // Read from a device + if (n <= 0) fail("Serial line closed\n"); // If serial is closed, exit + + if (n > 0 && ctx->verbose) dump("READ", buf, n); + for (i = 0; i < n; i++) { + size_t len = slip_recv(buf[i], &ctx->slip); // Pass to SLIP + if (len == 0 && ctx->slip.mode == 0) putchar(buf[i]); // In serial mode + if (len <= 0) continue; + if (len > 0 && ctx->slip.mode && ctx->sock) + sendto(ctx->sock, ctx->slip.buf, ctx->slip.len, 0, + (struct sockaddr *) &ctx->sin, sizeof(ctx->sin)); + if (ctx->verbose) dump("SR", ctx->slip.buf, len); + } + fflush(stdout); + } + if (ready & READY_STDIN) { // Forward stdin to a device + uint8_t buf[BUFSIZ]; + int n = read(0, buf, sizeof(buf)); + if (n > 0 && ctx->verbose) dump("WRITE", buf, n); + for (i = 0; i < n; i++) uart_tx(buf[i], &ctx->fd); + } + if (ready & READY_SOCK) { // Something in the UDP socket + uint8_t buf[2048]; + unsigned sl = sizeof(ctx->sin); + int n = recvfrom(ctx->sock, buf, sizeof(buf), 0, + (struct sockaddr *) &ctx->sin, &sl); + // printf("GOT %d\n", n); + if (n > 0) { + if (ctx->verbose) dump("RSOCK", buf, n); + slip_send(buf, n, uart_tx, &ctx->fd); // Inject frame + } + } +} + +static void info(struct ctx *ctx) { + if (!chip_connect(ctx)) fail("Error connecting\n"); + printf("Chip ID: 0x%x (%s)\n", ctx->chip.id, ctx->chip.name); + + if (ctx->chip.id == CHIP_ID_ESP32_C3_ECO3) { + uint32_t efuse_base = 0x60008800, mac0, mac1; + read32(ctx, efuse_base + 0x44, &mac0); + read32(ctx, efuse_base + 0x48, &mac1); + printf("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", (mac1 >> 8) & 255, + mac1 & 255, (mac0 >> 24) & 255, (mac0 >> 16) & 255, + (mac0 >> 8) & 255, mac0 & 255); + } +} + +static void readmem(struct ctx *ctx, const char **args) { + if (!chip_connect(ctx)) { + fail("Error connecting\n"); + } else if (args[0] == NULL || args[1] == NULL) { + usage(ctx); + } else { + uint32_t i, value, base = strtoul(args[0], NULL, 0), + size = strtoul(args[1], NULL, 0); + for (i = 0; i < size; i += 4) { + if (read32(ctx, base + i, &value) == 0) { + fwrite(&value, 1, sizeof(value), stdout); + } else { + fprintf(stderr, "Error: mem read @ addr %#x\n", base + i); + break; + } + } + } +} + +static void spiattach(struct ctx *ctx) { + uint32_t d3[] = {0, 0}; + uint32_t d4[] = {0, 4 * 1024 * 1024, 65536, 4096, 256, 0xffff}; + if (ctx->fspi != NULL) { + // 6,17,8,11,16 -> 0xb408446, like esptool does + unsigned a = 0, b = 0, c = 0, d = 0, e = 0; + sscanf(ctx->fspi, "%u,%u,%u,%u,%u", &a, &b, &c, &e, &d); + d3[0] = a | (b << 6) | (c << 12) | (d << 18) | (e << 24); + // printf("-----> %u,%u,%u,%u,%u -> %x\n", a, b, c, d, e, pins); + } + if (cmd(ctx, 13, d3, sizeof(d3), 0, 250)) fail("SPI_ATTACH failed\n"); + // flash_id, flash size, block_size, sector_size, page_size, status_mask + if (cmd(ctx, 11, d4, sizeof(d4), 0, 250)) fail("SPI_SET_PARAMS failed\n"); +} + +static void readflash(struct ctx *ctx, const char **args) { + if (!chip_connect(ctx)) { + fail("Error connecting\n"); + } else if (args[0] == NULL || args[1] == NULL) { + usage(ctx); + } else if (ctx->chip.id == CHIP_ID_ESP8266) { + fail("Can't do it on esp8266\n"); + } else { + uint32_t i = 0, base = strtoul(args[0], NULL, 0), + size = strtoul(args[1], NULL, 0); + spiattach(ctx); + while (i < size) { + uint32_t bs = size - i > 64 ? 64 : size - i; + uint32_t d[] = {base + i, bs}; + if (cmd(ctx, 14, d, sizeof(d), 0, 500) != 0) { + printf("Error: flash read @ addr %#x\n", base + i); + break; + } else { + fwrite(&ctx->slip.buf[8], 1, bs, stdout); + i += bs; + } + } + } +} + +static inline unsigned long hex_to_ul(const char *s, int len) { + unsigned long i = 0, v = 0; + for (i = 0; i < (unsigned long) len; i++) { + int c = s[i]; + if (i > 0) v <<= 4; + v |= (c >= '0' && c <= '9') ? c - '0' + : (c >= 'A' && c <= 'F') ? c - '7' + : c - 'W'; + } + return v; +} + +static int rmrf(const char *dirname) { +#ifdef _WIN32 + char tmp[MAX_PATH], path[MAX_PATH]; + WIN32_FIND_DATA data; + HANDLE hFind; + snprintf(tmp, sizeof(tmp), "%s\\*", dirname); + hFind = FindFirstFile(tmp, &data); + if (hFind != INVALID_HANDLE_VALUE) { + do { + struct _stat st; + snprintf(path, sizeof(path), "%s/%s", dirname, data.cFileName); + if (data.cFileName[0] == '.') continue; + if (_stat(path, &st) == 0 && (st.st_mode & S_IFDIR)) rmrf(path); + remove(path); + } while (FindNextFile(hFind, &data)); + FindClose(hFind); + } + RemoveDirectory(dirname); + return _access(dirname, 0) != 0; +#else + DIR *dp = opendir(dirname); + if (dp != NULL) { + struct dirent *de; + while ((de = readdir(dp)) != NULL) { + struct stat st; + char path[PATH_MAX]; + if (de->d_name[0] == '.') continue; + snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name); + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) rmrf(path); + remove(path); + } + closedir(dp); + } + (void) rmdir(dirname); + return access(dirname, 0) != 0; +#endif +} + +// Unpack hex file into a given directory, as a collection of OFFSET.bin files +// If buf is not null, append all created file names to it. +static int unhex(const char *hexfile, const char *dir, char *buf, size_t bl) { + char tmp[600]; + int c, n = 0, line = 0; + FILE *in = fopen(hexfile, "rb"), *out = NULL; + unsigned long upper = 0, next = 0; + if (in == NULL) return fail("ERROR: cannot open %s\n", hexfile); + if (rmrf(dir) == 0) return fail("Cannot delete dir %s\n", dir); + mkdir(dir, 0755); + buf[0] = '\0'; + while ((c = fgetc(in)) != EOF) { + if (!isspace(c)) tmp[n++] = c; + if (n >= (int) sizeof(tmp) || c == '\n') { + int i, len = hex_to_ul(tmp + 1, 2); + unsigned long lower = hex_to_ul(tmp + 3, 4); + int type = hex_to_ul(tmp + 7, 2); + unsigned long addr = upper | lower; + if (tmp[0] != ':') return fail("line %d: no colon\n", line); + if (n != 1 + 2 + 4 + 2 + len * 2 + 2) + return fail("line %d: len %d, expected %d\n", n, + 1 + 2 + 4 + 2 + len * 2 + 2); + if (type == 0) { + if (out == NULL || next != addr) { + char path[200]; + snprintf(path, sizeof(path), "%s/%#lx.bin", dir, addr); + if (out != NULL) fclose(out); + out = fopen(path, "wb"); + if (out == NULL) return fail("Cannot open %s", path); + // Append created filename to the list of created files + snprintf(buf + strlen(buf), bl - strlen(buf), "%s%s", + buf[0] == '\0' ? "" : " ", path); + } + for (i = 0; i < len; i++) { + int byte = hex_to_ul(tmp + 9 + i * 2, 2); + fputc(byte, out); + } + next = addr + len; + } else if (type == 1) { + if (out != NULL) fclose(out); + out = NULL; + } else if (type == 4) { + upper = hex_to_ul(tmp + 9, 4) << 16; + } + n = 0; + } + } + fclose(in); + if (out != NULL) fclose(out); + return EXIT_SUCCESS; +} + +static int has_suffix(const char *word, const char *suffix) { + size_t word_len = strlen(word), suffix_len = strlen(suffix); + return word_len > suffix_len && + strcasecmp(&word[word_len - suffix_len], suffix) == 0; +} + +static void flashbin(struct ctx *ctx, uint16_t flash_params, + uint32_t flash_offset, char* data, size_t size) { + int seq = 0, copied = 0; + uint32_t block_size = 4096, hs = 16, encrypted = 0, cs, tmp; + uint8_t buf[16 + 4096]; // First 16 bytes are for serial cmd + + memset(buf, 0, hs); // Clear them + + printf("Erasing %d bytes @ %#x", size, flash_offset); + fflush(stdout); + + { + uint32_t num_blocks = (size + block_size - 1) / block_size; + uint32_t d1[] = {size, num_blocks, block_size, flash_offset, encrypted}; + uint16_t d1size = sizeof(d1) - 4; + // Flash begin. S2, S3, C3 chips have an extra 5th parameter. + if (ctx->chip.id == CHIP_ID_ESP32_S2 || + ctx->chip.id == CHIP_ID_ESP32_S3_BETA2 || + ctx->chip.id == CHIP_ID_ESP32_S3_BETA3 || + ctx->chip.id == CHIP_ID_ESP32_C6_BETA || + ctx->chip.id == CHIP_ID_ESP32_C3_ECO_1_2 || + ctx->chip.id == CHIP_ID_ESP32_C3_ECO3) + d1size += 4; + if (cmd(ctx, 2, d1, d1size, 0, 15000)) fail("\nerase failed\n"); + } + + // Read from file into a buffer, but skip initial 16 bytes + while (copied < size) { + int round_copied = 0; + if (copied + block_size < size) { + memcpy(buf + hs, data+copied, block_size); + round_copied = block_size; + } else { + memcpy(buf + hs, data+copied, size - copied); + round_copied = size - copied; + } + copied = copied + round_copied; + //int oft = ftell(fp); + //for (i = 0; i < 100; i++) putchar('\b'); + //printf("Writing %s, %d/%d bytes @ 0x%x (%d%%)", path, n, size, + //flash_offset + oft - n, oft * 100 / size); + //fflush(stdout); + + // Embed flash params into a bootloader image + if (seq == 0 && flash_offset == ctx->chip.bla) { + if (flash_params != 0) { + buf[hs + 2] = (uint8_t) ((flash_params >> 8) & 255); + buf[hs + 3] = (uint8_t) (flash_params & 255); + } + // Set chip type in the extended header at offset 4. + // Common header is 8, plus extended header offset 4 = 12 + if (ctx->chip.id == CHIP_ID_ESP32_C3_ECO3) buf[hs + 12] = 5; + if (ctx->chip.id == CHIP_ID_ESP32_C3_ECO_1_2) buf[hs + 12] = 5; + if (ctx->chip.id == CHIP_ID_ESP32_S2) { + buf[hs + 8] = 0; + buf[hs + 12] = 2; + } + } + + // Align buffer to block_size and pad with 0xff + // memset(buf + hs + n, 255, sizeof(buf) - hs - n); + // n = ALIGN(n, block_size); + + // Flash write + tmp = round_copied, memcpy(&buf[0], &tmp, 4); // Set buffer size + tmp = seq++, memcpy(&buf[4], &tmp, 4); // Set sequence number + cs = checksum(buf + hs, round_copied); + if (cmd(ctx, 3, buf, (uint16_t) (hs + round_copied), cs, 1500)) + fail("flash_data failed\n"); + } + + //for (i = 0; i < 100; i++) printf("\b \b"); + //printf("Written %s, %d bytes @ %#x\n", path, size, flash_offset); + //fclose(fp); +} + +static const char *download(const char *url) { + char cmd[2048]; + const char *slash = strrchr(url, '/'); + if (slash == NULL) fail("Invalid URL: %s\n", url); + snprintf(cmd, sizeof(cmd), "curl -sL %s -o %s", url, slash + 1); + printf("%s\n", cmd); + if (system(cmd) != 0) fail("Download failed\n"); + return slash + 1; +} + +static void flash(struct ctx *ctx, uint16_t flash_params, uint32_t offset, char* data, size_t size) { + printf("Using flash params %#hx\n", flash_params); + fflush(stdout); + + flashbin(ctx, flash_params, offset, data, size); + +} + +static unsigned long align_to(unsigned long n, unsigned to) { + return ((n + to - 1) / to) * to; +} + +////////////////////////////////// mkbin command - ELF related functionality + +struct mem { + unsigned char *ptr; + int len; +}; + +struct Elf32_Ehdr { + unsigned char e_ident[16]; + uint16_t e_type, e_machine; + uint32_t e_version, e_entry, e_phoff, e_shoff, e_flags; + uint16_t e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx; +}; + +struct Elf32_Phdr { + uint32_t p_type, p_offset, p_vaddr, p_paddr; + uint32_t p_filesz, p_memsz, p_flags, p_align; +}; + +static struct mem read_entire_file(const char *path) { + struct mem mem; + FILE *fp = fopen(path, "rb"); + if (fp == NULL) fail("Cannot open %s: %s\n", path, strerror(errno)); + fseek(fp, 0, SEEK_END); + mem.len = ftell(fp); + rewind(fp); + mem.ptr = malloc(mem.len); + if (mem.ptr == NULL) fail("malloc(%d) failed\n", mem.len); + if (fread(mem.ptr, 1, mem.len, fp) != (size_t) mem.len) { + fail("fread(%s) failed: %s\n", path, strerror(errno)); + } + fclose(fp); + return mem; +} + +static int elf_get_num_segments(const struct mem *elf) { + struct Elf32_Ehdr *e = (struct Elf32_Ehdr *) elf->ptr; + return e->e_phnum; +} + +static uint32_t elf_get_entry_point(const struct mem *elf) { + return ((struct Elf32_Ehdr *) elf->ptr)->e_entry; +} + +static struct Elf32_Phdr elf_get_phdr(const struct mem *elf, int no) { + struct Elf32_Ehdr *e = (struct Elf32_Ehdr *) elf->ptr; + struct Elf32_Phdr *h = (struct Elf32_Phdr *) (elf->ptr + e->e_phoff); + if (h->p_filesz == 0) no++; // GCC-generated phdrs have empty 1st phdr + return h[no]; +} + +static int mkbin(const char *elf_path, const char *bin_path, struct ctx *ctx) { + struct mem elf = read_entire_file(elf_path); + FILE *bin_fp = fopen(bin_path, "w+b"); + uint8_t common_hdr[] = {0xe9, 1, 0, 0}; + uint8_t extended_hdr[] = {0xee, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t i, j, cs = 0xef, zero = 0, num_segments = elf_get_num_segments(&elf); + uint32_t entrypoint = elf_get_entry_point(&elf); + + if (ctx->chip.id == CHIP_ID_ESP32_S2) { + extended_hdr[0] = 0x00; + extended_hdr[4] = 2; + } + if (ctx->chip.id == CHIP_ID_ESP32_C3_ECO_1_2 || + ctx->chip.id == CHIP_ID_ESP32_C3_ECO3) { + extended_hdr[4] = 5; + } + + if (bin_fp == NULL) fail("Cannot open %s: %s\n", bin_path, strerror(errno)); + if (elf.len < (int) sizeof(struct Elf32_Phdr)) fail("corrupt ELF file\n"); + if (elf.ptr[4] != '\x01') fail("Not ELF32: %d\n", elf.ptr[4]); + + // GCC generates 2 segments. TCC - 4, first two are .text and .data + // num_segments = 2; + common_hdr[1] = num_segments; + fwrite(common_hdr, 1, sizeof(common_hdr), bin_fp); // Common header + fwrite(&entrypoint, 1, sizeof(entrypoint), bin_fp); // Entry point + fwrite(extended_hdr, 1, sizeof(extended_hdr), bin_fp); // Extended header + if (ctx->verbose) + printf("%s: %d segments found\n", elf_path, (int) num_segments); + + // Iterate over segments + for (i = 0; i < num_segments; i++) { + struct Elf32_Phdr h = elf_get_phdr(&elf, i); + uint32_t load_address = h.p_vaddr; + uint32_t aligned_size = align_to(h.p_filesz, 4); + if (ctx->verbose) printf(" addr %x size %u\n", load_address, aligned_size); + fwrite(&load_address, 1, sizeof(load_address), bin_fp); + fwrite(&aligned_size, 1, sizeof(aligned_size), bin_fp); + fwrite(elf.ptr + h.p_offset, 1, h.p_filesz, bin_fp); + for (j = 0; j < aligned_size - h.p_filesz; j++) fputc(zero, bin_fp); + cs = checksum2(cs, elf.ptr + h.p_offset, h.p_filesz); + } + + { + // Pad to 16 bytes and write checksum + long ofs = ftell(bin_fp), aligned_ofs = align_to(ofs + 1, 16); + for (i = 0; i < aligned_ofs - ofs - 1; i++) fputc(zero, bin_fp); + fputc(cs, bin_fp); + } + + fclose(bin_fp); + free(elf.ptr); + return EXIT_SUCCESS; +} +///////////////////////////////////////////////// End of mkbin command + +static void printhexline(int type, int len, int addr, int *buf) { + unsigned i, cs = type + len + ((addr >> 8) & 255) + (addr & 255); + printf(":%02x%04x%02x", len, addr & 0xffff, type); + for (i = 0; i < (unsigned) len; i++) cs += buf[i], printf("%02x", buf[i]); + printf("%02x\n", (~cs + 1) & 255); +} + +static void printhexhiaddrline(unsigned long addr) { + int buf[2] = {(addr >> 24) & 255, (addr >> 16) & 255}; + printhexline(4, 2, 0, buf); +} + +static int mkhex(const char **args) { + for (; args[0] && args[1]; args += 2) { + unsigned long addr = strtoul(args[0], NULL, 0); + FILE *fp = fopen(args[1], "rb"); + int c, n = 0, buf[16]; + if (fp == NULL) return fail("ERROR: cannot open %s\n", args[1]); + // if (addr >= 0xffff && (addr & 0xffff)) printhexhiaddrline(addr); + printhexhiaddrline(addr); + for (;;) { + if ((c = fgetc(fp)) != EOF) buf[n++] = c; + if (n >= (int) (sizeof(buf) / sizeof(buf[0])) || c == EOF) { + if (addr >= 0xffff && !(addr & 0xffff)) printhexhiaddrline(addr); + if (n > 0) printhexline(0, n, addr & 0xffff, buf); + addr += n; + n = 0; + } + if (c == EOF) break; + } + fclose(fp); + } + printhexline(1, 0, 0, NULL); + return EXIT_SUCCESS; +} + +static int open_udp_socket(const char *portspec) { + int sock; + struct sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_port = htons((unsigned short) atoi(portspec)); + sin.sin_addr.s_addr = 0; + sock = socket(AF_INET, SOCK_DGRAM, 17); + bind(sock, (struct sockaddr *) &sin, sizeof(sin)); + return sock; +} + +/*int main(int argc, const char **argv) { + const char *temp_dir = getenv("TMP_DIR"); // Temp dir for unhex + const char *udp_port = getenv("UDP_PORT"); // Listening UDP port + const char **command = NULL; // Command to perform + uint8_t slipbuf[32 * 1024]; // Buffer for SLIP context + struct ctx ctx = {0}; // Program context + int i; + + ctx.port = getenv("PORT"); // Serial port + ctx.port = getenv("PORT"); // Serial port + ctx.baud = getenv("BAUD"); // Serial port baud rate + ctx.fpar = getenv("FLASH_PARAMS"); // Flash parameters + ctx.fspi = getenv("FLASH_SPI"); // Flash SPI pins + ctx.verbose = getenv("V") != NULL; // Verbose output + ctx.slip.buf = slipbuf; // Set SLIP context - buffer + ctx.slip.size = sizeof(slipbuf); // Buffer size + ctx.chip = s_known_chips[0]; // Set chip to unknown + +#ifdef _WIN32 + if (ctx.port == NULL) ctx.port = "COM99"; // Non-existent default port +#elif defined(__APPLE__) + if (ctx.port == NULL) ctx.port = "/dev/cu.usbmodem"; +#else + if (ctx.port == NULL) ctx.port = "/dev/ttyUSB0"; +#endif + + if (ctx.baud == NULL) ctx.baud = "115200"; // Default baud rate + if (temp_dir == NULL) temp_dir = "tmp"; // Default temp dir + if (udp_port == NULL) udp_port = "1999"; // Default UDP_PORT + + // Parse options + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) { + ctx.baud = argv[++i]; + } else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) { + ctx.port = argv[++i]; + } else if (strcmp(argv[i], "-fp") == 0 && i + 1 < argc) { + ctx.fpar = argv[++i]; + } else if (strcmp(argv[i], "-fspi") == 0 && i + 1 < argc) { + ctx.fspi = argv[++i]; + } else if (strcmp(argv[i], "-chip") == 0 && i + 1 < argc) { + set_chip_id(&ctx, argv[++i]); + } else if (strcmp(argv[i], "-tmp") == 0 && i + 1 < argc) { + temp_dir = argv[++i]; + } else if (strcmp(argv[i], "-udp") == 0 && i + 1 < argc) { + udp_port = argv[++i]; + } else if (strcmp(argv[i], "-v") == 0) { + ctx.verbose = true; + } else if (argv[i][0] == '-') { + usage(&ctx); + } else { + command = &argv[i]; + break; + } + } + if (!command || !*command) usage(&ctx); + + // Commands that do not require serial port + if (strcmp(*command, "mkbin") == 0) { + if (!command[1] || !command[2]) usage(&ctx); + return mkbin(command[1], command[2], &ctx); + } else if (strcmp(*command, "mkhex") == 0) { + return mkhex(&command[1]); + } else if (strcmp(*command, "unhex") == 0) { + char file_list[500]; + return unhex(command[1], temp_dir, file_list, sizeof(file_list)); + } + + // Commands that require serial port. First, open serial. + ctx.sock = open_udp_socket(udp_port); + ctx.fd = open_serial(ctx.port, 115200, ctx.verbose); + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + if (strcmp(*command, "info") == 0) { + info(&ctx); + } else if (strcmp(*command, "flash") == 0) { + flash(&ctx, &command[1]); + } else if (strcmp(*command, "readmem") == 0) { + readmem(&ctx, &command[1]); + } else if (strcmp(*command, "readflash") == 0) { + readflash(&ctx, &command[1]); + } else if (strcmp(*command, "monitor") == 0) { + if (atoi(ctx.baud) != 115200) { + change_baud(ctx.fd, atoi(ctx.baud), ctx.verbose); + } + while (s_signo == 0) monitor(&ctx); + } else { + printf("Unknown command: %s\n", *command); + usage(&ctx); + } + close(ctx.fd); + return 0; +}*/ + +int flash_full(struct RNode* rn, char* boot_app0, size_t boot_app0_size, char* bootloader, size_t bootloader_size, char* bin, size_t bin_size, char* partitions, size_t partitions_size, char* console_image, size_t console_image_size) { + if (/*rn->boot_app0_addr == NULL || rn->bootloader_addr == NULL || rn->bin_addr == NULL || rn->partitions_addr == NULL || */boot_app0 == NULL || bootloader == NULL || bin == NULL || partitions == NULL || boot_app0_size == NULL || bootloader_size == NULL || bin_size == NULL || partitions_size == NULL) { + // Invalid parameters + return -1; + } + + uint16_t flash_params = 0; + struct ctx ctx = {0}; // Program context + uint8_t slipbuf[32 * 1024]; // Buffer for SLIP context + + ctx.port = rn->port; // Serial port + ctx.baud = rn->baud; // Serial port baud rate + ctx.fpar = NULL; // Flash parameters + ctx.fspi = NULL; // Flash SPI pins + ctx.verbose = true; // Verbose output + ctx.slip.buf = slipbuf; // Set SLIP context - buffer + ctx.slip.size = sizeof(slipbuf); // Buffer size + ctx.chip = s_known_chips[0]; // Set chip to unknown + ctx.fd = open_serial(ctx.port, rn->baud, ctx.verbose); + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + if (!chip_connect(&ctx)) fail("Error connecting\n"); + if (ctx.fpar != NULL) flash_params = (uint16_t) strtoul(ctx.fpar, NULL, 0); + if (ctx.baud > 115200) { + uint32_t data[] = {(ctx.baud), 0}; + if (cmd(&ctx, 15, data, sizeof(data), 0, 50)) fail("SET_BAUD failed\n"); + change_baud(ctx.fd, ctx.baud, ctx.verbose); + } + + // For non-ESP8266, SPI attach is mandatory + if (ctx.chip.id != CHIP_ID_ESP8266) { + spiattach(&ctx); + + // Load first word from the bootloader - flash params are encoded there, + // in the last 2 bytes, see README.md in the repo root + if (ctx.fpar == NULL) { + uint32_t d5[] = {ctx.chip.bla, 16}; + if (cmd(&ctx, 14, d5, sizeof(d5), 0, 2000) != 0) { + printf("Error: can't read bootloader @ addr %#x\n", ctx.chip.bla); + } else if (ctx.slip.buf[8] != 0xe9) { + printf("Wrong magic for bootloader @ addr %#x\n", ctx.chip.bla); + } else { + flash_params = (ctx.slip.buf[10] << 8) | ctx.slip.buf[11]; + } + } + } + + flashbin(&ctx, flash_params, rn->boot_app0_addr, boot_app0, boot_app0_size); + flashbin(&ctx, flash_params, rn->bootloader_addr, bootloader, bootloader_size); + flashbin(&ctx, flash_params, rn->bin_addr, bin, bin_size); + flashbin(&ctx, flash_params, rn->partitions_addr, partitions, partitions_size); + + if (rn->console_image_addr != NULL && console_image != NULL && console_image_size != NULL) { + flashbin(&ctx, flash_params, rn->console_image_addr, console_image, console_image_size); + } + + { + // Flash end + uint32_t d3[] = {0}; // 0: reboot, 1: run user code + if (cmd(&ctx, 4, d3, sizeof(d3), 0, 250)) fail("flash_end failed\n"); + } + + hard_reset(ctx.fd); +} diff --git a/src/libs/flashers/esp32/esputil.h b/src/libs/flashers/esp32/esputil.h new file mode 100644 index 0000000..e2c00b2 --- /dev/null +++ b/src/libs/flashers/esp32/esputil.h @@ -0,0 +1,8 @@ +#ifndef ESPUTIL_H + +#define ESPUTIL_H + +#include "../../../librnode.h" + +int flash_full(struct RNode* rn, char* boot_app0, size_t boot_app0_size, char* bootloader, size_t bootloader_size, char* bin, size_t bin_size, char* partitions, size_t partitions_size, char* console, size_t console_size); +#endif diff --git a/src/libs/flashers/nrf/LICENSE b/src/libs/flashers/nrf/LICENSE new file mode 100644 index 0000000..c84fb2e --- /dev/null +++ b/src/libs/flashers/nrf/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2010 Serge A. Zaitsev + +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. + diff --git a/src/libs/flashers/nrf/Makefile b/src/libs/flashers/nrf/Makefile new file mode 100644 index 0000000..5b7757b --- /dev/null +++ b/src/libs/flashers/nrf/Makefile @@ -0,0 +1,39 @@ +CC = gcc +CFLAGS = -Wall -O2 -I. -g -O0 -DLOG_USE_COLOR +BIN = UartSecureDFU + +DEPS = crc16.h \ + dfu.h \ + dfu_serial.h \ + hci.h \ + slip_enc.h \ + uart_drv.h \ + uart_slip.h \ + zip.h \ + miniz.h \ + jsmn.h \ + ../../../util.h \ + ../../logging/log.h \ + Makefile + +OBJS = crc16.o \ + dfu.o \ + dfu_serial.o \ + hci.o \ + jsmn.o \ + slip_enc.o \ + uart_linux.o \ + UartSecureDFU.o \ + uart_slip.o \ + ../../../util.o \ + ../../logging/log.o \ + zip.o + +%.o: %.c $(DEPS) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BIN): $(OBJS) + $(CC) $(OBJS) -o $(BIN) + +clean: + rm -f $(BIN) $(OBJS) diff --git a/src/libs/flashers/nrf/Makefile.win32 b/src/libs/flashers/nrf/Makefile.win32 new file mode 100644 index 0000000..db216d4 --- /dev/null +++ b/src/libs/flashers/nrf/Makefile.win32 @@ -0,0 +1,37 @@ +CC = gcc +CFLAGS = -Wall -O2 -I. -DWIN32 +BIN = UartSecureDFU + +DEPS = crc32.h \ + delay_connect.h \ + dfu.h \ + dfu_serial.h \ + logging.h \ + slip_enc.h \ + uart_drv.h \ + uart_slip.h \ + zip.h \ + miniz.h \ + jsmn.h \ + Makefile + +OBJS = crc32.o \ + delay_connect.o \ + dfu.o \ + dfu_serial.o \ + jsmn.o \ + logging.o \ + slip_enc.o \ + uart_win32.o \ + UartSecureDFU.o \ + uart_slip.o \ + zip.o + +%.o: %.c $(DEPS) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BIN): $(OBJS) + $(CC) $(OBJS) -o $(BIN) + +clean: + rm -f $(BIN) $(OBJS) diff --git a/src/libs/flashers/nrf/README.md b/src/libs/flashers/nrf/README.md new file mode 100644 index 0000000..41dcf8d --- /dev/null +++ b/src/libs/flashers/nrf/README.md @@ -0,0 +1,17 @@ +# librnode - nRF52 serial DFU flasher + +This utility is for flashing nRF52-based modules which run the Adafruit bootloader, for example the RAK4631. + +It is based on the `nrf-slim-serial-uart-dfu-host-c-code` project by [Jimmy Wong](https://github.com/jimmywong2003/nrf-slim-serial-uart-dfu-host-c-code), which has been extended to specifically support devices with an Adafruit bootloader. This has been achieved by referencing Adafruit nRFutil and [Liam Cottle's web flasher for RNodes](https://github.com/liamcottle/rnode-flasher). + +UART bit rate is 115200bps with 8-N-1 data format. RTS/CTS flow control is +disabled. This program takes the DFU package ZIP file generated by +`adafruit-nrfutil`, which is used in the +[RNode_Firmware](https://github.com/markqvist/RNode_Firmware) project's build +system. + +## Supports + +nRF52840 (tested). + +Whether this flasher will work on other nRF52 chips is currently unknown, but it is expected they *should* work. diff --git a/src/libs/flashers/nrf/crc16.c b/src/libs/flashers/nrf/crc16.c new file mode 100644 index 0000000..133d600 --- /dev/null +++ b/src/libs/flashers/nrf/crc16.c @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2013 - 2018, Nordic Semiconductor ASA + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. + * Changes licensed under the GPL. + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +#include "crc16.h" + +#include + +uint16_t crc16_compute(uint8_t const * p_data, uint16_t data_len) +{ + uint16_t crc = 0xFFFF; + for (int i = 0; i < data_len; i++) { + crc = (crc >> 8 & 0x00FF) | (crc << 8 & 0xFF00); + crc = crc ^ p_data[i]; + crc = crc ^ (crc & 0x00FF) >> 4; + crc = crc ^ (crc << 8) << 4; + crc = crc ^ ((crc & 0x00FF) << 4) << 1; + } + return crc; +} diff --git a/src/libs/flashers/nrf/crc16.h b/src/libs/flashers/nrf/crc16.h new file mode 100644 index 0000000..3b223ab --- /dev/null +++ b/src/libs/flashers/nrf/crc16.h @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2015 - 2018, Nordic Semiconductor ASA + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. + * Changes licensed under the GPL. + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +/** @file + * + * @defgroup crc16 CRC16 compute + * @{ + * @ingroup hci_transport + * + * @brief This module implements the CRC-16 calculation in the blocks. + */ + +#ifndef CRC16_H__ +#define CRC16_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@brief Function for calculating CRC-16 in blocks. + * + * Feed each consecutive data block into this function, along with the current value of p_crc as + * returned by the previous call of this function. The first call of this function should pass NULL + * as the initial value of the crc in p_crc. + * + * @param[in] p_data The input data block for computation. + * @param[in] size The size of the input data block in bytes. + * @param[in] p_crc The previous calculated CRC-16 value or NULL if first call. + * + * @return The updated CRC-16 value, based on the input supplied. + */ +uint16_t crc16_compute(uint8_t const * p_data, uint16_t data_len); + + +#ifdef __cplusplus +} +#endif + +#endif // CRC16_H__ + +/** @} */ diff --git a/src/libs/flashers/nrf/dfu.c b/src/libs/flashers/nrf/dfu.c new file mode 100644 index 0000000..565d788 --- /dev/null +++ b/src/libs/flashers/nrf/dfu.c @@ -0,0 +1,673 @@ +/** +* Copyright (c) 2018, Nordic Semiconductor ASA +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form, except as embedded into a Nordic +* Semiconductor ASA integrated circuit in a product or a software update for +* such product, must reproduce the above copyright notice, this list of +* conditions and the following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. Neither the name of Nordic Semiconductor ASA nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* 4. This software, with or without modification, must only be used with a +* Nordic Semiconductor ASA integrated circuit. +* +* 5. Any software provided in binary form under this license must not be reverse +* engineered, decompiled, modified and/or disassembled. +* +* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* Changes licensed under the GPL. +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) +* any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* You should have received a copy of the GNU General Public License along +* with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include "dfu.h" +#include "dfu_serial.h" +#include "../../zip.h" +#include "jsmn.h" +#include "../../logging/log.h" + + +// maximum number of JSON tokens to process +#define JSON_TOKEN_NUM_MAX 30 + +// maximum number of DFU objects to process +#define DFU_OBJECT_NUM_MAX 3 + +typedef enum { + DFU_IMG_NIL = 0, //!< DFU image invalid + DFU_IMG_APP = 1, //!< DFU application image + DFU_IMG_BL = 2, //!< DFU bootloader image + DFU_IMG_SD = 3, //!< DFU SoftDevice image + DFU_IMG_SD_BL = 4 //!< DFU SoftDevice & bootloader image +} dfu_image_type_t; + +typedef enum { + STR_UNDEFINED = 0, + STR_FILE_BIN = 1, + STR_FILE_DAT = 2 +} dfu_json_str_t; + +typedef struct { + jsmntype_t type; //!< Token type. + int size; //!< Number of child tokens. + char *str; //!< String value. + dfu_json_str_t str_type; //!< String usage type. +} jsmn_entity_t; + +typedef struct { + dfu_image_type_t img_type; //!< DFU image type. + const jsmn_entity_t *p_pattern; //!< JSMN token pattern. +} dfu_image_jsmn_pattern; + +typedef struct +{ + dfu_image_type_t img_type; //!< DFU image type. + + char *file_bin; //!< BIN file name. + char *file_dat; //!< DAT file name. +} dfu_json_object_t; + +typedef struct +{ + uart_drv_t *p_uart; + + uint8_t *p_img_dat; //!< Image DAT pointer. + uint32_t n_dat_size; //!< Image DAT size. + uint8_t *p_img_bin; //!< Image BIN pointer. + uint32_t n_bin_size; //!< Image BIN size. +} dfu_img_param_t; + +// JSON tokens +static jsmntok_t json_tokens[JSON_TOKEN_NUM_MAX]; + +// DFU objects +static dfu_json_object_t dfu_objects[DFU_OBJECT_NUM_MAX]; + +// JSMN token pattern for Manifest +static const jsmn_entity_t dfu_mft_pattern[] = +{ + { JSMN_OBJECT, 1, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "manifest", STR_UNDEFINED }, + { JSMN_UNDEFINED, 0, NULL, STR_UNDEFINED } +}; + +// JSMN token pattern for 1 DFU image +static const jsmn_entity_t dfu_img_1_pattern[] = +{ + { JSMN_OBJECT, 2, NULL, STR_UNDEFINED }, + { JSMN_UNDEFINED, 0, NULL, STR_UNDEFINED } +}; + +// JSMN token pattern for 2 DFU images +static const jsmn_entity_t dfu_img_2_pattern[] = +{ + { JSMN_OBJECT, 3, NULL, STR_UNDEFINED }, + { JSMN_UNDEFINED, 0, NULL, STR_UNDEFINED } +}; + +// JSMN token pattern for DFU application +static const jsmn_entity_t dfu_app_pattern[] = +{ + { JSMN_STRING, 1, "application", STR_UNDEFINED }, + { JSMN_OBJECT, 3, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "bin_file", STR_UNDEFINED }, + { JSMN_STRING, 0, NULL, STR_FILE_BIN }, + { JSMN_STRING, 1, "dat_file", STR_UNDEFINED }, + { JSMN_STRING, 0, NULL, STR_FILE_DAT }, + { JSMN_STRING, 1, "init_packet_data", STR_UNDEFINED }, + { JSMN_OBJECT, 5, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "application_version", STR_UNDEFINED }, + { JSMN_PRIMITIVE, 0, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "device_revision", STR_UNDEFINED }, + { JSMN_PRIMITIVE, 0, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "device_type", STR_UNDEFINED }, + { JSMN_PRIMITIVE, 0, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "firmware_crc16", STR_UNDEFINED }, + { JSMN_PRIMITIVE, 0, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "softdevice_req", STR_UNDEFINED }, + { JSMN_ARRAY, 1, NULL, STR_UNDEFINED }, + { JSMN_PRIMITIVE, 0, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "dfu_version", STR_UNDEFINED }, + { JSMN_PRIMITIVE, 0, NULL, STR_UNDEFINED }, + { JSMN_UNDEFINED, 0, NULL, STR_UNDEFINED } +}; + +// JSMN token pattern for DFU bootloader +static const jsmn_entity_t dfu_bl_pattern[] = +{ + { JSMN_STRING, 1, "bootloader", STR_UNDEFINED }, + { JSMN_OBJECT, 2, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "bin_file", STR_UNDEFINED }, + { JSMN_STRING, 0, NULL, STR_FILE_BIN }, + { JSMN_STRING, 1, "dat_file", STR_UNDEFINED }, + { JSMN_STRING, 0, NULL, STR_FILE_DAT }, + { JSMN_UNDEFINED, 0, NULL, STR_UNDEFINED } +}; + +// JSMN token pattern for DFU SoftDevice +static const jsmn_entity_t dfu_sd_pattern[] = +{ + { JSMN_STRING, 1, "softdevice", STR_UNDEFINED }, + { JSMN_OBJECT, 2, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "bin_file", STR_UNDEFINED }, + { JSMN_STRING, 0, NULL, STR_FILE_BIN }, + { JSMN_STRING, 1, "dat_file", STR_UNDEFINED }, + { JSMN_STRING, 0, NULL, STR_FILE_DAT }, + { JSMN_UNDEFINED, 0, NULL, STR_UNDEFINED } +}; + +// JSMN token pattern for DFU SoftDevice & bootloader +static const jsmn_entity_t dfu_sd_bl_pattern[] = +{ + { JSMN_STRING, 1, "softdevice_bootloader", STR_UNDEFINED }, + { JSMN_OBJECT, 3, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "bin_file", STR_UNDEFINED }, + { JSMN_STRING, 0, NULL, STR_FILE_BIN }, + { JSMN_STRING, 1, "dat_file", STR_UNDEFINED }, + { JSMN_STRING, 0, NULL, STR_FILE_DAT }, + { JSMN_STRING, 1, "info_read_only_metadata", STR_UNDEFINED }, + { JSMN_OBJECT, 2, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "bl_size", STR_UNDEFINED }, + { JSMN_PRIMITIVE, 0, NULL, STR_UNDEFINED }, + { JSMN_STRING, 1, "sd_size", STR_UNDEFINED }, + { JSMN_PRIMITIVE, 0, NULL, STR_UNDEFINED }, + { JSMN_UNDEFINED, 0, NULL, STR_UNDEFINED } +}; + +// JSMN token pattern table +static const dfu_image_jsmn_pattern dfu_pattern_tbl[] = +{ + { DFU_IMG_APP, dfu_app_pattern }, + { DFU_IMG_BL, dfu_bl_pattern }, + { DFU_IMG_SD, dfu_sd_pattern }, + { DFU_IMG_SD_BL, dfu_sd_bl_pattern }, + { DFU_IMG_NIL, NULL } +}; + +static void free_dfu_json_obj(dfu_json_object_t *p_dfu_obj); + +// allocate memory to store a JSON string +static char *put_json_to_string(const uint8_t *p_data, int len) +{ + char *p_str; + + if (len >= 0) + p_str = (char *)malloc(len + 1); + else + p_str = NULL; + + if (p_str != NULL) + { + memcpy(p_str, p_data, len); + *(p_str + len) = '\0'; + } + + return p_str; +} + +// map a JSMN token to a DFU JSON pattern +static int map_jsmn_token_to_pattern(dfu_json_object_t *p_dfu_obj, const jsmn_entity_t *p_pattern, const jsmntok_t *p_tokens, int num_tokens, const uint8_t *p_data) +{ + int i, j = 0; + jsmntype_t jsmn_type; + + // compare the JSMN tokens one by one + for (i = 0; (jsmn_type = (p_pattern + i)->type) != JSMN_UNDEFINED; i++) + { + jsmntype_t comp_type = (p_tokens + i)->type; + uint16_t size1 = (p_pattern + i)->size; + uint16_t size2 = (p_tokens + i)->size; + if (i >= num_tokens || + jsmn_type != (p_tokens + i)->type || + (p_pattern + i)->size != (p_tokens + i)->size) + { + // type or size mismatch... + j = -1; + break; + } + + if (jsmn_type == JSMN_STRING) + { + int len = (p_tokens + i)->end - (p_tokens + i)->start; + char *p_str = put_json_to_string(p_data + (p_tokens + i)->start, len); + + if (p_str == NULL) + { + // cannot allocate memory... + j = -1; + break; + } + + // check the pattern string + char *comp_string = (p_pattern +i)->str; + if ((p_pattern + i)->str != NULL && strcmp(p_str, (p_pattern + i)->str)) + { + free(p_str); + + // mismatch... + j = -1; + break; + } + + // store string target, if any + switch ((p_pattern + i)->str_type) + { + case STR_FILE_DAT: + if (p_dfu_obj != NULL) + p_dfu_obj->file_dat = p_str; + break; + case STR_FILE_BIN: + if (p_dfu_obj != NULL) + p_dfu_obj->file_bin = p_str; + break; + default: + free(p_str); + break; + } + } + } + + if (j < 0) + { + i = j; + + if (p_dfu_obj != NULL) + free_dfu_json_obj(p_dfu_obj); + } + + return i; +} + +// free allocated strings +static void free_dfu_json_obj(dfu_json_object_t *p_dfu_obj) +{ + if (p_dfu_obj->file_dat != NULL) + { + free(p_dfu_obj->file_dat); + p_dfu_obj->file_dat = NULL; + } + if (p_dfu_obj->file_bin != NULL) + { + free(p_dfu_obj->file_bin); + p_dfu_obj->file_bin = NULL; + } +} + +static int dfu_send_image(dfu_img_param_t *p_dfu_img, struct RNode* rn) +{ + int err_code; + + err_code = dfu_serial_send_start_dfu(p_dfu_img->p_uart, 4, 0, 0, p_dfu_img->n_bin_size); // todo remove hardcode to mode 4 + + if (!err_code) + { + err_code = dfu_serial_send_init(p_dfu_img->p_uart, p_dfu_img->p_img_dat, p_dfu_img->n_dat_size); + } + + if (!err_code) + { + err_code = dfu_serial_send_firmware(p_dfu_img->p_uart, rn, p_dfu_img->p_img_bin, p_dfu_img->n_bin_size); + } + + return err_code; +} + +static int dfu_send_object(uart_drv_t *p_uart, struct RNode* rn, dfu_json_object_t *p_dfu_obj, struct zip_t *p_zip_pkg) +{ + int err_code = 0; + uint8_t *buf_dat = NULL; + size_t buf_dat_size; + uint8_t *buf_bin = NULL; + size_t buf_bin_size; + dfu_img_param_t dfu_img; + + if (zip_entry_open(p_zip_pkg, p_dfu_obj->file_dat)) + { + log_error("Cannot open firmware package DAT file!"); + + err_code = 1; + } + else + { + if (zip_entry_read(p_zip_pkg, (void **)&buf_dat, &buf_dat_size)) + { + log_error("Cannot read firmware package DAT file!"); + + err_code = 1; + } + + zip_entry_close(p_zip_pkg); + } + + if (!err_code) + { + if (zip_entry_open(p_zip_pkg, p_dfu_obj->file_bin)) + { + log_error("Cannot open firmware package BIN file!"); + + err_code = 1; + } + else + { + if (zip_entry_read(p_zip_pkg, (void **)&buf_bin, &buf_bin_size)) + { + log_error("Cannot read firmware package BIN file!"); + + err_code = 1; + } + + zip_entry_close(p_zip_pkg); + } + } + + if (!err_code) + { + dfu_img.p_uart = p_uart; + dfu_img.p_img_dat = buf_dat; + dfu_img.n_dat_size = buf_dat_size; + dfu_img.p_img_bin = buf_bin; + dfu_img.n_bin_size = buf_bin_size; + err_code = dfu_send_image(&dfu_img, rn); + } + + if (buf_dat != NULL) + free(buf_dat); + + if (buf_bin != NULL) + free(buf_bin); + + return err_code; +} + +static dfu_json_object_t *find_dfu_object(dfu_json_object_t *p_dfu_obj, int num_obj, dfu_image_type_t img_type) +{ + dfu_json_object_t *p_obj = NULL; + int i; + + for (i = 0; i < num_obj; i++) + { + if ((p_dfu_obj + i)->img_type == img_type) + { + p_obj = p_dfu_obj + i; + break; + } + } + + return p_obj; +} + +int dfu_send_package(dfu_param_t *p_dfu, struct RNode* rn, uint8_t* hash) +{ + int err_code = 0; + struct zip_t *zip_pkg; + uint8_t *buf_json = NULL; + size_t bufsize; + jsmn_parser parser; + int num_tokens; + int num_images, img_n = 0; + dfu_json_object_t *p_dfu_object; + int i, n; + + zip_pkg = zip_open(p_dfu->p_pkg_file, 0, 'r'); + if (zip_pkg == NULL) + { + log_error("Cannot open ZIP firmware package file!"); + + err_code = 1; + } + else + { + if (zip_entry_open(zip_pkg, "manifest.json")) + { + log_error("Cannot open firmware package manifest file!"); + + err_code = 1; + } + else + { + if (zip_entry_read(zip_pkg, (void **)&buf_json, &bufsize)) + { + log_error("Cannot read firmware package manifest file!"); + + err_code = 1; + } + else + { + zip_entry_close(zip_pkg); + } + } + } + + if (!err_code) + { + jsmn_init(&parser); + + num_tokens = jsmn_parse(&parser, (char *)buf_json, bufsize, json_tokens, JSON_TOKEN_NUM_MAX); + + if (num_tokens < 0) + { + log_error("Cannot parse firmware package manifest JSON (%d)!", num_tokens); + + err_code = 1; + } + } + + for (i = 0; i < DFU_OBJECT_NUM_MAX; i++) + { + dfu_objects[i].file_bin = NULL; + dfu_objects[i].file_dat = NULL; + } + + if (!err_code) + { + // check that JSON starts with a manifest object + i = map_jsmn_token_to_pattern(NULL, dfu_mft_pattern, json_tokens, num_tokens, buf_json); + if (i < 0) + { + log_error("Cannot get JSON manifest object from firmware package!"); + + err_code = 1; + } + n = i; + + if (!err_code) + { + // check whether there are 1 or 2 DFU images + i = map_jsmn_token_to_pattern(NULL, dfu_img_1_pattern, json_tokens + n, num_tokens - n, buf_json); + if (i > 0) + { + num_images = 1; + } + else + { + i = map_jsmn_token_to_pattern(NULL, dfu_img_2_pattern, json_tokens + n, num_tokens - n, buf_json); + if (i > 0) + { + num_images = 2; + } + } + + if (i <= 0) + { + log_error("Cannot get number of DFU images from manifest!"); + + err_code = 1; + } + } + n += i; + + while (!err_code && n < num_tokens) + { + int t; + + if (img_n >= DFU_OBJECT_NUM_MAX) + { + log_error("Too many DFU images in the manifest!"); + + err_code = 1; + break; + } + + // determine the DFU image type + for (t = 0; dfu_pattern_tbl[t].img_type != DFU_IMG_NIL; t++) + { + i = map_jsmn_token_to_pattern(dfu_objects + img_n, dfu_pattern_tbl[t].p_pattern, json_tokens + n, num_tokens - n, buf_json); + + if (i > 0) + { + dfu_objects[img_n].img_type = dfu_pattern_tbl[t].img_type; + break; + } + } + + if (i <= 0) + { + log_error("Cannot find JSON DFU image object in manifest!"); + + err_code = 1; + break; + } + else + { + n += i; + + img_n++; + } + } + + if (!err_code && (n != num_tokens || img_n != num_images)) + { + log_error("Incoherent JSON object structure detected in manifest!"); + + err_code = 1; + } + } + + // todo, redo this section to support softdevice & bootloader flashing + if (!err_code) + { + // send SoftDevice & bootloader image, if any + //p_dfu_object = find_dfu_object(dfu_objects, num_images, DFU_IMG_SD_BL); + //if (p_dfu_object != NULL) + //{ + // //logger_info_1("Sending SoftDevice+Bootloader image."); + + // //err_code = dfu_send_object(p_dfu->p_uart, p_dfu_object, zip_pkg); + + // //if (!err_code && num_images > 1) + // // err_code = delay_connect(); + //} + } + + if (!err_code) + { + // send SoftDevice image, if any + //p_dfu_object = find_dfu_object(dfu_objects, num_images, DFU_IMG_SD); + //if (p_dfu_object != NULL) + //{ + // //logger_info_1("Sending SoftDevice image."); + + // // err_code = dfu_send_object(p_dfu->p_uart, p_dfu_object, zip_pkg); + + // // if (!err_code && num_images > 1) + // // err_code = delay_connect(); + //} + } + + if (!err_code) + { + // send bootloader image, if any + //p_dfu_object = find_dfu_object(dfu_objects, num_images, DFU_IMG_BL); + //if (p_dfu_object != NULL) + //{ + // //logger_info_1("Sending Bootloader image."); + + // // err_code = dfu_send_object(p_dfu->p_uart, p_dfu_object, zip_pkg); + + // // if (!err_code && num_images > 1) + // // err_code = delay_connect(); + //} + } + + if (!err_code) + { + // send application image, if any + p_dfu_object = find_dfu_object(dfu_objects, num_images, DFU_IMG_APP); + if (p_dfu_object != NULL) + { + uint8_t *buf_bin = NULL; + size_t buf_bin_size; + + log_info("Sending Application image to RNode..."); + + err_code = dfu_send_object(p_dfu->p_uart, rn, p_dfu_object, zip_pkg); + + if (zip_entry_open(zip_pkg, p_dfu_object->file_bin)) + { + log_error("Cannot open firmware package BIN file!"); + + err_code = 1; + } + else + { + if (zip_entry_read(zip_pkg, (void **)&buf_bin, &buf_bin_size)) + { + log_error("Cannot read firmware package BIN file!"); + + err_code = 1; + } + + zip_entry_close(zip_pkg); + } + + // Set firmware hash + SHA256(buf_bin, buf_bin_size, hash); + } + } + + for (i = 0; i < DFU_OBJECT_NUM_MAX; i++) + free_dfu_json_obj(dfu_objects + i); + + if (buf_json != NULL) + free(buf_json); + + if (zip_pkg != NULL) + zip_close(zip_pkg); + + return err_code; +} diff --git a/src/libs/flashers/nrf/dfu.h b/src/libs/flashers/nrf/dfu.h new file mode 100644 index 0000000..bf92164 --- /dev/null +++ b/src/libs/flashers/nrf/dfu.h @@ -0,0 +1,82 @@ +/** +* Copyright (c) 2018, Nordic Semiconductor ASA +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form, except as embedded into a Nordic +* Semiconductor ASA integrated circuit in a product or a software update for +* such product, must reproduce the above copyright notice, this list of +* conditions and the following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. Neither the name of Nordic Semiconductor ASA nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* 4. This software, with or without modification, must only be used with a +* Nordic Semiconductor ASA integrated circuit. +* +* 5. Any software provided in binary form under this license must not be reverse +* engineered, decompiled, modified and/or disassembled. +* +* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* Changes licensed under the GPL. +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) +* any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* You should have received a copy of the GNU General Public License along +* with this program. If not, see . +*/ + +#pragma once + +#ifndef _INC_DFU +#define _INC_DFU + +#include "uart_drv.h" +#include "uart_slip.h" +#include "../../../librnode.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +typedef struct +{ + uart_drv_t *p_uart; + + char *p_pkg_file; +} dfu_param_t; + +int dfu_send_package(dfu_param_t *p_dfu, struct RNode* rn, uint8_t* hash); + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif /* __cplusplus */ + + +#endif // _INC_DFU diff --git a/src/libs/flashers/nrf/dfu_serial.c b/src/libs/flashers/nrf/dfu_serial.c new file mode 100644 index 0000000..58b08e8 --- /dev/null +++ b/src/libs/flashers/nrf/dfu_serial.c @@ -0,0 +1,356 @@ +/** +* Copyright (c) 2018, Nordic Semiconductor ASA +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form, except as embedded into a Nordic +* Semiconductor ASA integrated circuit in a product or a software update for +* such product, must reproduce the above copyright notice, this list of +* conditions and the following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. Neither the name of Nordic Semiconductor ASA nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* 4. This software, with or without modification, must only be used with a +* Nordic Semiconductor ASA integrated circuit. +* +* 5. Any software provided in binary form under this license must not be reverse +* engineered, decompiled, modified and/or disassembled. +* +* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* Changes licensed under the GPL. +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) +* any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* You should have received a copy of the GNU General Public License along +* with this program. If not, see . +*/ + +#include +#include +#include +#include +#include "dfu_serial.h" +#include "hci.h" +#include "../../util.h" +#include "../../logging/log.h" + +// SLIP data log buffer size +#define MAX_BUFF_SIZE 1024 + +#define DFU_INIT_PACKET 1 +#define DFU_START_PACKET 3 +#define DFU_DATA_PACKET 4 +#define DFU_STOP_DATA_PACKET 5 +#define DFU_PACKET_MAX_SIZE 512 + +#define FLASH_PAGE_SIZE 4096 +#define FLASH_WORD_WRITE_TIME 0.000100 +#define FLASH_PAGE_WRITE_TIME (FLASH_PAGE_SIZE / 4) * FLASH_WORD_WRITE_TIME +#define FLASH_PAGE_ERASE_TIME 0.0897 +#define FLASH_MAX_FW_SIZE 300000 + +#define DAT_FILE_SIZE 14 + +#define LOG_BUF_SIZE 2048 + +static uint8_t send_data[UART_SLIP_SIZE_MAX]; +static uint8_t receive_data[UART_SLIP_SIZE_MAX]; +char logger_buf[LOG_BUF_SIZE]; +void (*progress_cb)(uint8_t) = NULL; + +static uint32_t get_uint32_le(const uint8_t *p_data) +{ + uint32_t data; + + data = ((uint32_t)*(p_data + 0) << 0); + data += ((uint32_t)*(p_data + 1) << 8); + data += ((uint32_t)*(p_data + 2) << 16); + data += ((uint32_t)*(p_data + 3) << 24); + + return data; +} + +static void put_uint32_le(uint8_t *p_data, uint32_t data) +{ + *(p_data + 0) = (uint8_t)(data >> 0); + *(p_data + 1) = (uint8_t)(data >> 8); + *(p_data + 2) = (uint8_t)(data >> 16); + *(p_data + 3) = (uint8_t)(data >> 24); +} + +static void uart_data_to_buff(const uint8_t *pData, uint32_t nSize) +{ + uint32_t n; + int len, pos; + + pos = 0; + + if (nSize*3 <= sizeof(logger_buf)) { + for (n = 0; n < nSize; n++) + { + sprintf(logger_buf+n*3, "%02x ", *(pData + n)); + } + } + +} + +static int dfu_serial_send(uart_drv_t *p_uart, const uint8_t *pData, uint32_t nSize) +{ + uart_data_to_buff(pData, nSize); + log_trace("SLIP: --> [%s]", logger_buf); + + return uart_drv_send(p_uart, pData, nSize); +} + +//static int dfu_serial_get_rsp(uart_drv_t *p_uart, nrf_dfu_op_t oper, uint32_t *p_data_cnt) +//{ +// int err_code; +// +// err_code = uart_slip_receive(p_uart, receive_data, sizeof(receive_data), p_data_cnt); +// +// if (!err_code) +// { +// //int info_lvl = //logger_get_info_level(); +// +// //if (info_lvl >= LOGGER_INFO_LVL_3) +// //{ +// // uart_data_to_buff(receive_data, *p_data_cnt); +// // //logger_info_3("SLIP: <-- [%s]", //logger_buf); +// //} +// +// if (*p_data_cnt >= 3 && +// receive_data[0] == NRF_DFU_OP_RESPONSE && +// receive_data[1] == oper) +// { +// if (receive_data[2] != NRF_DFU_RES_CODE_SUCCESS) +// { +// uint16_t rsp_error = receive_data[2]; +// +// // get 2-byte error code, if applicable +// if (*p_data_cnt >= 4) +// rsp_error = (rsp_error << 8) + receive_data[3]; +// +// //logger_error("Bad result code (0x%X)!", rsp_error); +// +// err_code = 1; +// } +// } +// else +// { +// //logger_error("Invalid response!"); +// +// err_code = 1; +// } +// } +// +// return err_code; +//} + +static void dfu_serial_u32_to_bytes(uint32_t val, uint8_t* array, uint16_t offset) { + array[offset] = val & 0x000000ff; + array[offset+1] = (val & 0x0000ff00) >> 8; + array[offset+2] = (val & 0x00ff0000) >> 16; + array[offset+3] = (val & 0xff000000) >> 24; +} + +// Waits the time necessary for the MCU to erase its flash before we begin sending the new image +int dfu_serial_wait_for_erase(uint32_t total_img_size) { + return sleep_ms(MAX(0.5, (total_img_size / FLASH_PAGE_SIZE) * FLASH_PAGE_ERASE_TIME)); +} + +int dfu_serial_send_start_dfu(uart_drv_t *p_uart, uint8_t mode, uint32_t softdevice_size, uint32_t bootloader_size, uint32_t application_size) { + int error = 0; + uint8_t frame[5*4] = {0}; + uint8_t hci_frame[(5*4)*2]; + + dfu_serial_u32_to_bytes(DFU_START_PACKET, frame, 0); + dfu_serial_u32_to_bytes(mode, frame, 4); + dfu_serial_u32_to_bytes(softdevice_size, frame, 8); + dfu_serial_u32_to_bytes(bootloader_size, frame, 12); + dfu_serial_u32_to_bytes(application_size, frame, 16); + + uint32_t size = encode_hci(frame, 5*4, hci_frame); + + error = dfu_serial_wait_for_erase(softdevice_size + bootloader_size + application_size); + + if (!error) { + error = dfu_serial_send(p_uart, hci_frame, size); + } + + return error; +} + +int dfu_serial_send_init(uart_drv_t *p_uart, const uint8_t* dat_file, uint32_t dat_size) { + if (dat_size > DAT_FILE_SIZE) { + log_error("Firmware DAT file is invalid! Is your ZIP file corrupt?"); + return -1; + } + uint8_t frame[6+DAT_FILE_SIZE] = {0}; + uint8_t hci_frame[(6+DAT_FILE_SIZE) * 2]; + + dfu_serial_u32_to_bytes(DFU_INIT_PACKET, frame, 0); + + memcpy(frame+4, dat_file, DAT_FILE_SIZE); + + uint32_t size = encode_hci(frame, 6+DAT_FILE_SIZE, hci_frame); + + return dfu_serial_send(p_uart, hci_frame, size); +} + +// todo, may allow for firmware extraction from board? +//static int dfu_serial_try_to_recover_fw(uart_drv_t *p_uart, const uint8_t *p_data, uint32_t data_size, +// nrf_dfu_response_select_t *p_rsp_recover, +// const nrf_dfu_response_select_t *p_rsp_select) +//{ +// int err_code = 0; +// uint32_t max_size, stp_size; +// uint32_t pos_start, len_remain; +// uint32_t crc_32 = 0; +// int obj_exec = 1; +// +// *p_rsp_recover = *p_rsp_select; +// +// pos_start = p_rsp_recover->offset; +// +// if (pos_start > data_size) +// { +// //logger_error("Invalid firmware offset reported!"); +// +// err_code = 1; +// } +// else if (pos_start > 0) +// { +// max_size = p_rsp_select->max_size; +// //crc_32 = crc32_compute(p_data, pos_start, NULL); +// len_remain = pos_start % max_size; +// +// if (p_rsp_select->crc != crc_32) +// { +// pos_start -= ((len_remain > 0) ? len_remain : max_size); +// p_rsp_recover->offset = pos_start; +// +// return err_code; +// } +// +// if (len_remain > 0) +// { +// stp_size = max_size - len_remain; +// +// err_code = dfu_serial_stream_data_crc(p_uart, p_data + pos_start, stp_size, pos_start, &crc_32); +// if (!err_code) +// { +// pos_start += stp_size; +// } +// else if (err_code == 2) +// { +// err_code = 0; +// +// pos_start -= len_remain; +// +// obj_exec = 0; +// } +// +// p_rsp_recover->offset = pos_start; +// } +// +// if (!err_code && obj_exec) +// { +// err_code = dfu_serial_execute_obj(p_uart); +// } +// } +// +// return err_code; +//} + +int dfu_serial_send_firmware(uart_drv_t *p_uart, struct RNode* rn, const uint8_t *p_data, uint32_t data_size) +{ + uint8_t frame[DFU_PACKET_MAX_SIZE + 4]; + uint8_t hci_frame[(DFU_PACKET_MAX_SIZE + 4) * 2]; + int err_code = 0; + + //logger_info_1("Sending firmware file..."); + + if (p_data == NULL || !data_size) + { + log_error("Payload data is invalid! Is your ZIP file corrupt?"); + + err_code = 1; + } + + if (!err_code) { + uint16_t len; + uint32_t hci_len; + + for (int i = 0; i < data_size; i = i + DFU_PACKET_MAX_SIZE) { + dfu_serial_u32_to_bytes(DFU_DATA_PACKET, frame, 0); + if (data_size - i > DFU_PACKET_MAX_SIZE) { + len = DFU_PACKET_MAX_SIZE; + } else { + len = data_size - i; + } + memcpy(frame+4, p_data+i, len); + hci_len = encode_hci(frame, len+4, hci_frame); + + err_code = dfu_serial_send(p_uart, hci_frame, hci_len); + + if (err_code) { + log_error("Sending firmware to RNode failed! Is the device still connected?"); + break; + } + + // Trigger callback and provide percentage of packets sent + if (rn->prog_cb != NULL) { + (*rn->prog_cb)(i / data_size * 100); + } + + // nrf52 is erasing and writing to flash + if (i % 8 == 0) { + err_code = sleep_ms(FLASH_PAGE_WRITE_TIME); + if (err_code) { + log_error("Timing failed during flash, please try flashing again!"); + break; + } + } + } + + if (!err_code) { + err_code = sleep_ms(FLASH_PAGE_WRITE_TIME); + + if (err_code) { + log_error("Timing failed during flash, please try flashing again!"); + } + + dfu_serial_u32_to_bytes(DFU_STOP_DATA_PACKET, frame, 0); + hci_len = encode_hci(frame, 4, hci_frame); + + err_code = dfu_serial_send(p_uart, hci_frame, hci_len); + } + } + + return err_code; +} diff --git a/src/libs/flashers/nrf/dfu_serial.h b/src/libs/flashers/nrf/dfu_serial.h new file mode 100644 index 0000000..0782647 --- /dev/null +++ b/src/libs/flashers/nrf/dfu_serial.h @@ -0,0 +1,78 @@ +/** +* Copyright (c) 2018, Nordic Semiconductor ASA +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form, except as embedded into a Nordic +* Semiconductor ASA integrated circuit in a product or a software update for +* such product, must reproduce the above copyright notice, this list of +* conditions and the following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. Neither the name of Nordic Semiconductor ASA nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* 4. This software, with or without modification, must only be used with a +* Nordic Semiconductor ASA integrated circuit. +* +* 5. Any software provided in binary form under this license must not be reverse +* engineered, decompiled, modified and/or disassembled. +* +* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* Changes licensed under the GPL. +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) +* any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* You should have received a copy of the GNU General Public License along +* with this program. If not, see . +*/ + +#pragma once + +#ifndef _INC_DFU_SERIAL +#define _INC_DFU_SERIAL + +#include "uart_drv.h" +#include "uart_slip.h" +#include "../../../librnode.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int dfu_serial_send_start_dfu(uart_drv_t *p_uart, uint8_t mode, uint32_t softdevice_size, uint32_t bootloader_size, uint32_t application_size); + +int dfu_serial_send_init(uart_drv_t *p_uart, const uint8_t *p_data, uint32_t data_size); + +int dfu_serial_send_firmware(uart_drv_t *p_uart, struct RNode* rn, const uint8_t *p_data, uint32_t data_size); + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif /* __cplusplus */ + + +#endif // _INC_DFU_SERIAL diff --git a/src/libs/flashers/nrf/hci.c b/src/libs/flashers/nrf/hci.c new file mode 100644 index 0000000..4ce967b --- /dev/null +++ b/src/libs/flashers/nrf/hci.c @@ -0,0 +1,42 @@ +/* Copyright (c) 2025 / Jacob Eva (Liberated Embedded Systems) + * This file provides some helper functions for encapsulating frames into HCI + * commands + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +#include "hci.h" +#include "../../slip_enc.h" +#include +#include "crc16.h" + +uint8_t hci_seq = 0; + +uint32_t encode_hci(uint8_t* raw_data, uint32_t raw_data_len, uint8_t* encoded_data) { + uint32_t encoded_data_size; + uint8_t temp_data[HCI_TEMP_BUF_SIZE]; + + hci_seq = (hci_seq + 1) % 8; + + slip_header(hci_seq, true, true, HCI_PACKET_TYPE, raw_data_len, temp_data); + + // Copy in raw data + memcpy(temp_data + SLIP_HEADER_LEN, raw_data, raw_data_len); + + uint16_t crc = crc16_compute(temp_data, raw_data_len + SLIP_HEADER_LEN); + + temp_data[SLIP_HEADER_LEN + raw_data_len] = crc & 0xFF; + temp_data[SLIP_HEADER_LEN + raw_data_len + 1] = (crc & 0xFF00) >> 8; + + + encode_slip(encoded_data, &encoded_data_size, temp_data, raw_data_len + SLIP_HEADER_LEN + CRC_SIZE, true); + + return encoded_data_size; +} diff --git a/src/libs/flashers/nrf/hci.h b/src/libs/flashers/nrf/hci.h new file mode 100644 index 0000000..113354a --- /dev/null +++ b/src/libs/flashers/nrf/hci.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2025 / Jacob Eva (Liberated Embedded Systems) + * This file provides some helper functions for encapsulating frames into HCI + * commands + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef __HCI_H +#define __HCI_H + +#include + +#define HCI_PACKET_TYPE 14 +#define CRC_SIZE 16 / 8 +#define HCI_TEMP_BUF_SIZE 2048 + +uint32_t encode_hci(uint8_t* raw_data, uint32_t raw_data_len, uint8_t* encoded_data); + +#endif diff --git a/src/libs/flashers/nrf/jsmn.c b/src/libs/flashers/nrf/jsmn.c new file mode 100644 index 0000000..33bbde2 --- /dev/null +++ b/src/libs/flashers/nrf/jsmn.c @@ -0,0 +1,318 @@ +/* Copyright (c) / Jimmy Wong + * Originally distributed under the MIT license + */ + +#include "jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + if(token->type != type || parser->toksuper == -1) { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + diff --git a/src/libs/flashers/nrf/jsmn.h b/src/libs/flashers/nrf/jsmn.h new file mode 100644 index 0000000..e3dba5a --- /dev/null +++ b/src/libs/flashers/nrf/jsmn.h @@ -0,0 +1,80 @@ +/* Copyright (c) / Jimmy Wong + * Originally distributed under the MIT license + */ + +#ifndef __JSMN_H_ +#define __JSMN_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; + +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; + +/** + * JSON token description. + * type type (object, array, string etc.) + * start start position in JSON data string + * end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * a single JSON object. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens); + +#ifdef __cplusplus +} +#endif + +#endif /* __JSMN_H_ */ diff --git a/src/libs/flashers/nrf/uart_drv.h b/src/libs/flashers/nrf/uart_drv.h new file mode 100644 index 0000000..24ea9e4 --- /dev/null +++ b/src/libs/flashers/nrf/uart_drv.h @@ -0,0 +1,94 @@ +/** +* Copyright (c) 2018, Nordic Semiconductor ASA +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form, except as embedded into a Nordic +* Semiconductor ASA integrated circuit in a product or a software update for +* such product, must reproduce the above copyright notice, this list of +* conditions and the following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. Neither the name of Nordic Semiconductor ASA nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* 4. This software, with or without modification, must only be used with a +* Nordic Semiconductor ASA integrated circuit. +* +* 5. Any software provided in binary form under this license must not be reverse +* engineered, decompiled, modified and/or disassembled. +* +* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* Changes licensed under the GPL. +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) +* any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* You should have received a copy of the GNU General Public License along +* with this program. If not, see . +*/ + +#pragma once + +#ifndef _INC_UART_DRV +#define _INC_UART_DRV + +#include +#include +#ifdef WIN32 +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +typedef struct { + const char *p_PortName; + +#ifdef WIN32 + HANDLE portHandle; +#else + int tty_fd; +#endif +} uart_drv_t; + + +int uart_drv_open(uart_drv_t *p_uart, bool touch); + +int uart_drv_close(uart_drv_t *p_uart); + +int uart_drv_send(uart_drv_t *p_uart, const uint8_t *pData, uint32_t nSize); + +int uart_drv_receive(uart_drv_t *p_uart, uint8_t *pData, uint32_t nSize, uint32_t *pSize); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif /* __cplusplus */ + + +#endif // _INC_UART_DRV diff --git a/src/libs/flashers/nrf/uart_linux.c b/src/libs/flashers/nrf/uart_linux.c new file mode 100644 index 0000000..291d1ad --- /dev/null +++ b/src/libs/flashers/nrf/uart_linux.c @@ -0,0 +1,283 @@ +/** +* Copyright (c) 2018, Nordic Semiconductor ASA +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form, except as embedded into a Nordic +* Semiconductor ASA integrated circuit in a product or a software update for +* such product, must reproduce the above copyright notice, this list of +* conditions and the following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. Neither the name of Nordic Semiconductor ASA nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* 4. This software, with or without modification, must only be used with a +* Nordic Semiconductor ASA integrated circuit. +* +* 5. Any software provided in binary form under this license must not be reverse +* engineered, decompiled, modified and/or disassembled. +* +* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* Changes licensed under the GPL. +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) +* any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* You should have received a copy of the GNU General Public License along +* with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include "uart_drv.h" +#include "../../util.h" +#include "../../logging/log.h" + +#define SERIAL_WAIT_TIME 100 // ms +#define TOUCH_RESET_WAIT_TIME 1500 // ms +#define NSEC_EXPONENT 1000000 + +int uart_drv_open(uart_drv_t *p_uart, bool touch) +{ + int err_code = 0; + int fd = -1; + char tty_path[200]; + struct termios options; + + strcpy(tty_path, p_uart->p_PortName); + //if (strlen(tty_name) <= 14) + // strcat(tty_path, tty_name); + //else + //{ + // log_error("Invalid access port for RNode!"); + + // err_code = 1; + //} + + if (!err_code) + { + fd = open(tty_path, O_RDWR | O_NOCTTY); + + if (fd < 0) + { + log_error("Cannot open RNode port! Is the RNode connected?"); + + err_code = 1; + } + } + + if (!err_code) + { + tcgetattr(fd, &options); + + if (touch) { + log_debug("Touching RNode port."); + // 1200 bps + cfsetispeed(&options, B1200); + cfsetospeed(&options, B1200); + } else { + // 115200 bps + cfsetispeed(&options, B115200); + cfsetospeed(&options, B115200); + } + + if (tcsetattr(fd, TCSANOW, &options)) + { + log_error("Cannot set RNode port options!"); + + err_code = 1; + } + } + + if (!err_code) + { + if (tcflush(fd, TCIFLUSH)) + { + log_error("Cannot flush RNode port reception buffer!"); + + err_code = 1; + } + } + + if (err_code && fd >= 0) + { + p_uart->tty_fd = fd; + uart_drv_close(p_uart); + + fd = -1; + } + + // Opening and closing serial port once is necessary to enter DFU mode + if (touch) { + sleep_ms(SERIAL_WAIT_TIME); + + close(fd); + + // Wait, then open it again + sleep_ms(TOUCH_RESET_WAIT_TIME); + + fd = open(tty_path, O_RDWR | O_NOCTTY); + + // 115200 bps + cfsetispeed(&options, B115200); + cfsetospeed(&options, B115200); + + log_debug("Opened RNode port after touch."); + + } + + // 8N1 + options.c_cflag &= ~PARENB; + options.c_cflag &= ~CSTOPB; + options.c_cflag &= ~CSIZE; + options.c_cflag |= CS8; + // ignore DCD line + options.c_cflag |= (CLOCAL | CREAD); + // disable RTS/CTS handshake + options.c_cflag &= CRTSCTS; + // disable XON/XOFF handshake + options.c_iflag &= ~(IXON | IXOFF | IXANY); + // disable input mapping options + options.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IUCLC); + // select RAW input + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + // select RAW output + options.c_oflag &= ~OPOST; + // disable output mapping options + options.c_oflag &= ~(OLCUC | ONLCR | OCRNL | ONOCR | ONLRET); + // set read timeout + options.c_cc[VMIN] = 0; + options.c_cc[VTIME] = 5; // 0.5 seconds + + if (tcsetattr(fd, TCSANOW, &options)) + { + log_error("Cannot set RNode port options!"); + + err_code = 1; + } + p_uart->tty_fd = fd; + + return err_code; +} + +int uart_drv_close(uart_drv_t *p_uart) +{ + int err_code = 0; + int fd = p_uart->tty_fd; + + if (fd >= 0) + { + if (close(fd)) + { + log_error("Cannot close RNode port!"); + + err_code = 1; + } + } + else + err_code = 1; + + return err_code; +} + +extern uint8_t hci_seq; +uint8_t hci_errors = 0; + +int uart_drv_send(uart_drv_t *p_uart, const uint8_t *pData, uint32_t nSize) +{ + int err_code = 0; + uint32_t length; + uint8_t rx[256]; + + length = write(p_uart->tty_fd, pData, nSize); + if (length != nSize) + { + log_error("Cannot write to the RNode port!"); + + err_code = 1; + } + else + { + if (tcdrain(p_uart->tty_fd)) + { + log_error("Cannot empty RNode port transmission buffer!"); + + err_code = 1; + } + } + + if (err_code) { + return err_code; + } + + err_code = uart_drv_receive(p_uart, rx, sizeof(rx), &length); + + if (err_code) { + return err_code; + } + + if (length > 0) { + uint8_t hci_ack = (rx[1] >> 3) & 0x07; + if ((hci_seq + 1) % 8 != hci_ack) { + log_error("HCI sequence number returned from RNode was invalid!"); + log_trace("Expected %u, but got %u", (hci_seq + 1 % 8), hci_ack); + hci_errors++; + } else { + hci_errors = 0; + } + } else { + log_debug("RNode hasn't responded with a HCI sequence, ignoring for now..."); + hci_errors++; + } + + if (hci_errors >= 3) { + log_error("RNode hasn't responded with a HCI sequence 3 times, aborting..."); + err_code = 1; + } + + return err_code; +} + +int uart_drv_receive(uart_drv_t *p_uart, uint8_t *pData, uint32_t nSize, uint32_t *pSize) +{ + int err_code = 0; + int32_t length; + + length = read(p_uart->tty_fd, pData, nSize); + if (length < 0) + { + log_error("Cannot read RNode port!"); + + err_code = 1; + } + else + *pSize = length; + + return err_code; +} diff --git a/src/libs/flashers/nrf/uart_slip.c b/src/libs/flashers/nrf/uart_slip.c new file mode 100644 index 0000000..e8910c8 --- /dev/null +++ b/src/libs/flashers/nrf/uart_slip.c @@ -0,0 +1,134 @@ +/** +* Copyright (c) 2017, Nordic Semiconductor ASA +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form, except as embedded into a Nordic +* Semiconductor ASA integrated circuit in a product or a software update for +* such product, must reproduce the above copyright notice, this list of +* conditions and the following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. Neither the name of Nordic Semiconductor ASA nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* 4. This software, with or without modification, must only be used with a +* Nordic Semiconductor ASA integrated circuit. +* +* 5. Any software provided in binary form under this license must not be reverse +* engineered, decompiled, modified and/or disassembled. +* +* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* Changes licensed under the GPL. +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) +* any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* You should have received a copy of the GNU General Public License along +* with this program. If not, see . +*/ + +#include +#include "uart_slip.h" +#include "../../slip_enc.h" +#include "../../logging/log.h" + +#define UART_SLIP_BUFF_SIZE (UART_SLIP_SIZE_MAX * 2 + 1) + +static uint8_t uart_slip_buff[UART_SLIP_BUFF_SIZE]; + +int uart_slip_open(uart_drv_t *p_uart, bool touch) +{ + return uart_drv_open(p_uart, touch); +} + +int uart_slip_close(uart_drv_t *p_uart) +{ + return uart_drv_close(p_uart); +} + +int uart_slip_send(uart_drv_t *p_uart, const uint8_t *pData, uint32_t nSize) +{ + int err_code = 0; + uint32_t nSlipSize; + + if (nSize > UART_SLIP_SIZE_MAX) + { + log_error("Cannot encode SLIP, allocated buffer too small!"); + + err_code = 1; + } + else + { + encode_slip(uart_slip_buff, &nSlipSize, pData, nSize, true); + + err_code = uart_drv_send(p_uart, uart_slip_buff, nSlipSize); + } + + return err_code; +} + +int uart_slip_receive(uart_drv_t *p_uart, uint8_t *pData, uint32_t nSize, uint32_t *pSize) +{ + int err_code = 0; + uint32_t sizeBuffer; + uint32_t length, slip_len = 0; + + do + { + sizeBuffer = sizeof(uart_slip_buff) - slip_len; + if (!sizeBuffer) + { + log_error("RNode port buffer overflow!"); + + err_code = 1; + + break; + } + + length = 0; + err_code = uart_drv_receive(p_uart, uart_slip_buff + slip_len, sizeBuffer, &length); + if (err_code) + break; + + if (!length) + { + log_error("Read no data from RNode port!"); + + err_code = 1; + + break; + } + + slip_len += length; + + if (!decode_slip(pData, pSize, uart_slip_buff, slip_len)) + { + break; + } + } while (!err_code); + + return err_code; +} diff --git a/src/libs/flashers/nrf/uart_slip.h b/src/libs/flashers/nrf/uart_slip.h new file mode 100644 index 0000000..66707d3 --- /dev/null +++ b/src/libs/flashers/nrf/uart_slip.h @@ -0,0 +1,85 @@ +/** +* Copyright (c) 2017, Nordic Semiconductor ASA +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form, except as embedded into a Nordic +* Semiconductor ASA integrated circuit in a product or a software update for +* such product, must reproduce the above copyright notice, this list of +* conditions and the following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. Neither the name of Nordic Semiconductor ASA nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* 4. This software, with or without modification, must only be used with a +* Nordic Semiconductor ASA integrated circuit. +* +* 5. Any software provided in binary form under this license must not be reverse +* engineered, decompiled, modified and/or disassembled. +* +* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* Changes licensed under the GPL. +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) +* any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* You should have received a copy of the GNU General Public License along +* with this program. If not, see . +*/ + +#pragma once + +#ifndef _INC_UART_SLIP +#define _INC_UART_SLIP + +#include +#include +#include "uart_drv.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define UART_SLIP_SIZE_MAX 128 + + +int uart_slip_open(uart_drv_t *p_uart, bool touch); + +int uart_slip_close(uart_drv_t *p_uart); + +int uart_slip_send(uart_drv_t *p_uart, const uint8_t *pData, uint32_t nSize); + +int uart_slip_receive(uart_drv_t *p_uart, uint8_t *pData, uint32_t nSize, uint32_t *pSize); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif /* __cplusplus */ + + +#endif // _INC_UART_SLIP diff --git a/src/libs/flashers/nrf/uart_win32.c b/src/libs/flashers/nrf/uart_win32.c new file mode 100644 index 0000000..07095f1 --- /dev/null +++ b/src/libs/flashers/nrf/uart_win32.c @@ -0,0 +1,231 @@ +/** +* Copyright (c) 2018, Nordic Semiconductor ASA +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form, except as embedded into a Nordic +* Semiconductor ASA integrated circuit in a product or a software update for +* such product, must reproduce the above copyright notice, this list of +* conditions and the following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. Neither the name of Nordic Semiconductor ASA nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* 4. This software, with or without modification, must only be used with a +* Nordic Semiconductor ASA integrated circuit. +* +* 5. Any software provided in binary form under this license must not be reverse +* engineered, decompiled, modified and/or disassembled. +* +* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* Changes licensed under the GPL. +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) +* any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* You should have received a copy of the GNU General Public License along +* with this program. If not, see . +*/ +*/ + +#include +#include "uart_drv.h" +#include "logging.h" + +int uart_drv_open(uart_drv_t *p_uart, bool touch) +{ + int err_code = 0; + const char *portName = p_uart->p_PortName; + CHAR portFileName[14]; + HANDLE handlePort_ = INVALID_HANDLE_VALUE; + + strcpy(portFileName, "\\\\.\\"); + if (strlen(portName) <= 6) + strcat(portFileName, portName); + else + { + logger_error("Invalid COM port!"); + + err_code = 1; + } + + if (!err_code) + { + handlePort_ = CreateFile(portFileName, // Specify port device: default "COM1" + GENERIC_READ | GENERIC_WRITE, // Specify mode that open device. + 0, // the devide isn't shared. + NULL, // the object gets a default security. + OPEN_EXISTING, // Specify which action to take on file. + 0, // default. + NULL); // default. + + if (handlePort_ == INVALID_HANDLE_VALUE) + { + logger_error("Cannot open COM port!"); + + err_code = 1; + } + } + + if (!err_code) + { + DCB config_; + + // Get current configuration of serial communication port. + if (GetCommState(handlePort_, &config_) == FALSE) + { + logger_error("Cannot get COM configuration!"); + + err_code = 1; + } + + if (!err_code) + { + config_.BaudRate = 115200; // Specify buad rate of communicaiton. + config_.StopBits = 0; // Specify stopbit of communication. + config_.Parity = 0; // Specify parity of communication. + config_.ByteSize = 8; // Specify byte of size of communication. + config_.fDtrControl = DTR_CONTROL_DISABLE; + config_.fDsrSensitivity = 0; + config_.fRtsControl = RTS_CONTROL_HANDSHAKE; + config_.fInX = 0; + config_.fOutX = 0; + config_.fBinary = 1; + if (SetCommState(handlePort_, &config_) == FALSE) + { + logger_error("Cannot set COM configuration!"); + + err_code = 1; + } + } + } + + if (!err_code) + { + // instance an object of COMMTIMEOUTS. + COMMTIMEOUTS comTimeOut; + // Specify value is added to the product of the + // ReadTotalTimeoutMultiplier member + comTimeOut.ReadTotalTimeoutConstant = 500; + // Specify value that is multiplied + // by the requested number of bytes to be read. + comTimeOut.ReadTotalTimeoutMultiplier = 10; + // Specify time-out between charactor for receiving. + comTimeOut.ReadIntervalTimeout = 100; + // Specify value that is multiplied + // by the requested number of bytes to be sent. + comTimeOut.WriteTotalTimeoutMultiplier = 15; + // Specify value is added to the product of the + // WriteTotalTimeoutMultiplier member + comTimeOut.WriteTotalTimeoutConstant = 300; + // set the time-out parameter into device control. + SetCommTimeouts(handlePort_, &comTimeOut); + } + + if (!err_code) + { + if (PurgeComm(handlePort_, PURGE_RXCLEAR) == FALSE) + { + logger_error("Cannot purge COM RX buffer!"); + + err_code = 1; + } + } + + if (err_code && handlePort_ != INVALID_HANDLE_VALUE) + { + p_uart->portHandle = handlePort_; + uart_drv_close(p_uart); + + handlePort_ = INVALID_HANDLE_VALUE; + } + + p_uart->portHandle = handlePort_; + + return err_code; +} + +int uart_drv_close(uart_drv_t *p_uart) +{ + int err_code = 0; + HANDLE portHandle = p_uart->portHandle; + + if (portHandle != INVALID_HANDLE_VALUE) + { + if (CloseHandle(portHandle) == FALSE) // Call this function to close port. + { + logger_error("Cannot close COM port!"); + + err_code = 1; + } + } + else + err_code = 1; + + return err_code; +} + +int uart_drv_send(uart_drv_t *p_uart, const uint8_t *pData, uint32_t nSize) +{ + int err_code = 0; + HANDLE portHandle = p_uart->portHandle; + DWORD length; + + if (WriteFile(portHandle, // handle to file to write to + pData, // pointer to data to write to file + nSize, // number of bytes to write + &length, NULL) == FALSE || // pointer to number of bytes written + length < nSize) + { + logger_error("Cannot write COM port!"); + + err_code = 1; + } + + return err_code; +} + +int uart_drv_receive(uart_drv_t *p_uart, uint8_t *pData, uint32_t nSize, uint32_t *pSize) +{ + int err_code = 0; + HANDLE portHandle = p_uart->portHandle; + DWORD length = 0; + + if (ReadFile(portHandle, // handle of file to read + pData, // pointer to data to read from file + nSize, // number of bytes to read + &length, // pointer to number of bytes read + NULL) == FALSE) // pointer to structure for data + { + logger_error("Cannot read COM port!"); + + err_code = 1; + } + + *pSize = length; + + return err_code; +} diff --git a/src/libs/framing.h b/src/libs/framing.h new file mode 100644 index 0000000..fcc07ef --- /dev/null +++ b/src/libs/framing.h @@ -0,0 +1,109 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Modified by Jacob Eva (Liberated Embedded Systems), still under the terms of +// the GPLv3. + +#ifndef FRAMING_H + #define FRAMING_H + + #define FEND 0xC0 + #define FESC 0xDB + #define TFEND 0xDC + #define TFESC 0xDD + + #define CMD_UNKNOWN 0xFE + #define CMD_DATA 0x00 + #define CMD_FREQUENCY 0x01 + #define CMD_BANDWIDTH 0x02 + #define CMD_TXPOWER 0x03 + #define CMD_SF 0x04 + #define CMD_CR 0x05 + #define CMD_RADIO_STATE 0x06 + #define CMD_RADIO_LOCK 0x07 + #define CMD_DETECT 0x08 + #define CMD_IMPLICIT 0x09 + #define CMD_LEAVE 0x0A + #define CMD_ST_ALOCK 0x0B + #define CMD_LT_ALOCK 0x0C + #define CMD_PROMISC 0x0E + #define CMD_READY 0x0F + + #define CMD_STAT_RX 0x21 + #define CMD_STAT_TX 0x22 + #define CMD_STAT_RSSI 0x23 + #define CMD_STAT_SNR 0x24 + #define CMD_STAT_CHTM 0x25 + #define CMD_STAT_PHYPRM 0x26 + #define CMD_STAT_BAT 0x27 + #define CMD_STAT_CSMA 0x28 + #define CMD_BLINK 0x30 + #define CMD_RANDOM 0x40 + + #define CMD_FB_EXT 0x41 + #define CMD_FB_READ 0x42 + #define CMD_FB_WRITE 0x43 + #define CMD_FB_READL 0x44 + #define CMD_DISP_READ 0x66 + #define CMD_DISP_INT 0x45 + #define CMD_DISP_ADDR 0x63 + #define CMD_DISP_BLNK 0x64 + #define CMD_DISP_ROT 0x67 + #define CMD_DISP_RCND 0x68 + #define CMD_NP_INT 0x65 + #define CMD_BT_CTRL 0x46 + #define CMD_BT_PIN 0x62 + #define CMD_DIS_IA 0x69 + + #define CMD_BOARD 0x47 + #define CMD_PLATFORM 0x48 + #define CMD_MCU 0x49 + #define CMD_FW_VERSION 0x50 + #define CMD_ROM_READ 0x51 + #define CMD_ROM_WRITE 0x52 + #define CMD_CONF_SAVE 0x53 + #define CMD_CONF_DELETE 0x54 + #define CMD_DEV_HASH 0x56 + #define CMD_DEV_SIG 0x57 + #define CMD_FW_HASH 0x58 + #define CMD_HASHES 0x60 + #define CMD_FW_UPD 0x61 + #define CMD_UNLOCK_ROM 0x59 + #define ROM_UNLOCK_BYTE 0xF8 + #define CMD_RESET 0x55 + #define CMD_RESET_BYTE 0xF8 + + #define CMD_INTERFACES 0x64 + #define CMD_SEL_INT 0x1F + + #define DETECT_REQ 0x73 + #define DETECT_RESP 0x46 + + #define RADIO_STATE_OFF 0x00 + #define RADIO_STATE_ON 0x01 + + #define NIBBLE_SEQ 0xF0 + #define NIBBLE_FLAGS 0x0F + #define FLAG_SPLIT 0x01 + #define SEQ_UNSET 0xFF + + #define CMD_ERROR 0x90 + #define ERROR_INITRADIO 0x01 + #define ERROR_TXFAILED 0x02 + #define ERROR_EEPROM_LOCKED 0x03 + #define ERROR_QUEUE_FULL 0x04 + #define ERROR_MEMORY_LOW 0x05 + #define ERROR_MODEM_TIMEOUT 0x06 +#endif diff --git a/src/libs/logging/LICENSE b/src/libs/logging/LICENSE new file mode 100644 index 0000000..39ddd05 --- /dev/null +++ b/src/libs/logging/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 rxi + +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. diff --git a/src/libs/logging/README.md b/src/libs/logging/README.md new file mode 100644 index 0000000..4f742af --- /dev/null +++ b/src/libs/logging/README.md @@ -0,0 +1 @@ +Sourced from [here](https://github.com/rxi/log.c). diff --git a/src/libs/logging/log.c b/src/libs/logging/log.c new file mode 100644 index 0000000..79483b7 --- /dev/null +++ b/src/libs/logging/log.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2020 rxi + * + * 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. + */ + +#include "log.h" + +#define MAX_CALLBACKS 32 + +const char library[] = "librnode"; + +typedef struct { + log_LogFn fn; + void *udata; + int level; +} Callback; + +static struct { + void *udata; + log_LockFn lock; + int level; + bool quiet; + Callback callbacks[MAX_CALLBACKS]; +} L; + + +static const char *level_strings[] = { + "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" +}; + +#ifdef LOG_USE_COLOR +static const char *level_colors[] = { + "\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m" +}; +#endif + + +static void stdout_callback(log_Event *ev) { + char buf[16]; + buf[strftime(buf, sizeof(buf), "%H:%M:%S", ev->time)] = '\0'; +#ifdef LOG_USE_COLOR + fprintf( + ev->udata, "%s %s%-5s\x1b[0m \x1b[90m(%s) %s:%d:\x1b[0m ", + buf, level_colors[ev->level], level_strings[ev->level], + library, ev->file, ev->line); +#else + fprintf( + ev->udata, "%s %-5s (%s) %s:%d: ", + buf, level_strings[ev->level], library, ev->file, ev->line); +#endif + vfprintf(ev->udata, ev->fmt, ev->ap); + fprintf(ev->udata, "\n"); + fflush(ev->udata); +} + + +static void file_callback(log_Event *ev) { + char buf[64]; + buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0'; + fprintf( + ev->udata, "%s %-5s (%s) %s:%d: ", + buf, level_strings[ev->level], library, ev->file, ev->line); + vfprintf(ev->udata, ev->fmt, ev->ap); + fprintf(ev->udata, "\n"); + fflush(ev->udata); +} + + +static void lock(void) { + if (L.lock) { L.lock(true, L.udata); } +} + + +static void unlock(void) { + if (L.lock) { L.lock(false, L.udata); } +} + + +const char* log_level_string(int level) { + return level_strings[level]; +} + + +void log_set_lock(log_LockFn fn, void *udata) { + L.lock = fn; + L.udata = udata; +} + + +void log_set_level(int level) { + L.level = level; +} + + +void log_set_quiet(bool enable) { + L.quiet = enable; +} + + +int log_add_callback(log_LogFn fn, void *udata, int level) { + for (int i = 0; i < MAX_CALLBACKS; i++) { + if (!L.callbacks[i].fn) { + L.callbacks[i] = (Callback) { fn, udata, level }; + return 0; + } + } + return -1; +} + + +int log_add_fp(FILE *fp, int level) { + return log_add_callback(file_callback, fp, level); +} + + +static void init_event(log_Event *ev, void *udata) { + if (!ev->time) { + time_t t = time(NULL); + ev->time = localtime(&t); + } + ev->udata = udata; +} + + +void log_log(int level, const char *file, int line, const char *fmt, ...) { + log_Event ev = { + .fmt = fmt, + .file = file, + .line = line, + .level = level, + }; + + lock(); + + if (!L.quiet && level >= L.level) { + init_event(&ev, stderr); + va_start(ev.ap, fmt); + stdout_callback(&ev); + va_end(ev.ap); + } + + for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++) { + Callback *cb = &L.callbacks[i]; + if (level >= cb->level) { + init_event(&ev, cb->udata); + va_start(ev.ap, fmt); + cb->fn(&ev); + va_end(ev.ap); + } + } + + unlock(); +} + diff --git a/src/libs/logging/log.h b/src/libs/logging/log.h new file mode 100644 index 0000000..b1fae24 --- /dev/null +++ b/src/libs/logging/log.h @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2020 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See `log.c` for details. + */ + +#ifndef LOG_H +#define LOG_H + +#include +#include +#include +#include + +#define LOG_VERSION "0.1.0" + +typedef struct { + va_list ap; + const char *fmt; + const char *file; + struct tm *time; + void *udata; + int line; + int level; +} log_Event; + +typedef void (*log_LogFn)(log_Event *ev); +typedef void (*log_LockFn)(bool lock, void *udata); + +enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL }; + +#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__) +#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__) +#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__) +#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__) +#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__) +#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__) + +const char* log_level_string(int level); +void log_set_lock(log_LockFn fn, void *udata); +void log_set_level(int level); +void log_set_quiet(bool enable); +int log_add_callback(log_LogFn fn, void *udata, int level); +int log_add_fp(FILE *fp, int level); + +void log_log(int level, const char *file, int line, const char *fmt, ...); + +#endif diff --git a/src/libs/miniz.h b/src/libs/miniz.h new file mode 100644 index 0000000..882f090 --- /dev/null +++ b/src/libs/miniz.h @@ -0,0 +1,4926 @@ +/* + 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 + +#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 __APPLE__ +#define ftello64 ftello +#define fseeko64 fseeko +#define fopen64 fopen +#define freopen64 freopen +#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 0 + +// 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 + +// ------------------- 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 + +#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], sizeof(mz_uint8) * num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], sizeof(mz_uint8) * 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) +{ + if (0 == n) return MZ_TRUE; + if (!pElements) return MZ_FALSE; + + 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; + + 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); + + if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) + 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, cur_archive_file_ofs, 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; + + local_dir_header_ofs = cur_archive_file_ofs = pZip->m_archive_size; + 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, cur_archive_file_ofs, 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; + + local_dir_header_ofs = cur_archive_file_ofs = pZip->m_archive_size; + + 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/src/libs/serial.h b/src/libs/serial.h new file mode 100644 index 0000000..9934f19 --- /dev/null +++ b/src/libs/serial.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2025 - Jacob Eva (Liberated Embedded Systems) + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef SERIAL_H + +#define SERIAL_H + +int open_port(char* path, uint32_t baud); + +int write_port(int fd, uint8_t* data, uint16_t data_len); + +int read_port(int fd, uint8_t* rx_data, uint16_t size); + +int close_port(int fd); + +#endif diff --git a/src/libs/serial_linux.c b/src/libs/serial_linux.c new file mode 100644 index 0000000..8b5268c --- /dev/null +++ b/src/libs/serial_linux.c @@ -0,0 +1,96 @@ +/* Copyright (c) 2025 - Jacob Eva (Liberated Embedded Systems) + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include +#include +#include +#include +#include "logging/log.h" + +int open_port(char* path, uint32_t baud) { + struct termios options; + speed_t baud_s; + + // Currently RNodes only support a baud rate of 115200, add more support as needed in future. + switch (baud) { + case 115200: + baud_s = B115200; + break; + default: + baud_s = B115200; + break; + } + + int fd = open(path, O_RDWR | O_NOCTTY); + + tcgetattr(fd, &options); + + cfsetispeed(&options, baud_s); + cfsetospeed(&options, baud_s); + + // 8N1 + options.c_cflag &= ~PARENB; + options.c_cflag &= ~CSTOPB; + options.c_cflag &= ~CSIZE; + options.c_cflag |= CS8; + // ignore DCD line + options.c_cflag |= (CLOCAL | CREAD); + // disable input mapping options + options.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IUCLC); + // select RAW input + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + // select RAW output + options.c_oflag &= ~OPOST; + // disable output mapping options + options.c_oflag &= ~(OLCUC | ONLCR | OCRNL | ONOCR | ONLRET); + // set read timeout + options.c_cc[VMIN] = 0; + options.c_cc[VTIME] = 5; // 0.5 seconds + + if (tcsetattr(fd, TCSANOW, &options)) + { + log_error("Cannot set RNode port options!"); + return -1; + } else { + return fd; + } + +} + +int write_port(int fd, uint8_t* data, uint16_t data_len) { + unsigned char data_str[1024] = ""; + for (int i = 0; i < data_len; i++) { + sprintf(data_str+(i*3), "%02x ", data[i]); + } + + log_trace("Writing [%s] to RNode serial port.", data_str); + + if (write(fd, data, data_len) != data_len) { + log_error("Could not write all data to the RNode port! Is the RNode still connected?"); + return -1; + } else { + return 0; + } +} + +int read_port(int fd, uint8_t* rx_data, uint16_t size) { + int ret = read(fd, rx_data, size); + if (ret == -1) { + log_error("Could not read from RNode port! Is the RNode still connected?"); + } + return ret; +} + +int close_port(int fd) { + return close(fd); +} diff --git a/src/libs/serial_win32.c b/src/libs/serial_win32.c new file mode 100644 index 0000000..228850c --- /dev/null +++ b/src/libs/serial_win32.c @@ -0,0 +1,12 @@ +/* Copyright (c) 2025 - Jacob Eva (Liberated Embedded Systems) + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ diff --git a/src/libs/slip_enc.c b/src/libs/slip_enc.c new file mode 100644 index 0000000..da18beb --- /dev/null +++ b/src/libs/slip_enc.c @@ -0,0 +1,175 @@ +/** +* Copyright (c) 2017, Nordic Semiconductor ASA +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form, except as embedded into a Nordic +* Semiconductor ASA integrated circuit in a product or a software update for +* such product, must reproduce the above copyright notice, this list of +* conditions and the following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. Neither the name of Nordic Semiconductor ASA nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* 4. This software, with or without modification, must only be used with a +* Nordic Semiconductor ASA integrated circuit. +* +* 5. Any software provided in binary form under this license must not be reverse +* engineered, decompiled, modified and/or disassembled. +* +* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* Changes licensed under the GPL. +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) +* any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* You should have received a copy of the GNU General Public License along +* with this program. If not, see . +*/ + +#include +#include "slip_enc.h" + +#define SLIP_ESC 0333 +#define SLIP_ESC_END 0334 +#define SLIP_ESC_ESC 0335 + +// https://github.com/adafruit/Adafruit_nRF52_nrfutil/blob/master/nordicsemi/dfu/util.py#L82 +void slip_header(uint8_t seq, bool dip, bool rp, uint8_t pkt_type, uint32_t pkt_len, uint8_t* header) { + header[0] = seq | (((seq + 1) % 8) << 3) | (dip << 6) | (rp << 7); + header[1] = pkt_type | ((pkt_len & 0x000F) << 4); + header[2] = (pkt_len & 0x0FF0) >> 4; + header[3] = (~(header[0] + header[1] + header[2]) + 1) & 0xFF; +} + +void encode_slip(uint8_t *pDestData, uint32_t *pDestSize, const uint8_t *pSrcData, uint32_t nSrcSize, bool frame) +{ + uint32_t n, nDestSize; + + nDestSize = 0; + + if (frame) { + *pDestData++ = SLIP_END; + nDestSize++; + } + + for (n = 0; n < nSrcSize; n++) + { + uint8_t nSrcByte = *(pSrcData + n); + + if (nSrcByte == SLIP_END) + { + *pDestData++ = SLIP_ESC; + *pDestData++ = SLIP_ESC_END; + nDestSize += 2; + } + else if (nSrcByte == SLIP_ESC) + { + *pDestData++ = SLIP_ESC; + *pDestData++ = SLIP_ESC_ESC; + nDestSize += 2; + } + else + { + *pDestData++ = nSrcByte; + nDestSize++; + } + } + + if (frame) { + *pDestData = SLIP_END; + nDestSize++; + } + + *pDestSize = nDestSize; +} + +int decode_slip(uint8_t *pDestData, uint32_t *pDestSize, const uint8_t *pSrcData, uint32_t nSrcSize) +{ + int err_code = 1; + uint32_t n, nDestSize = 0; + bool is_escaped = false; + + for (n = 0; n < nSrcSize; n++) + { + uint8_t nSrcByte = *(pSrcData + n); + + if (nSrcByte == SLIP_END) + { + if (!is_escaped) + err_code = 0; // Done. OK + + break; + } + else if (nSrcByte == SLIP_ESC) + { + if (is_escaped) + { + // should not get SLIP_ESC twice... + err_code = 1; + break; + } + else + is_escaped = true; + } + else if (nSrcByte == SLIP_ESC_END) + { + if (is_escaped) + { + is_escaped = false; + + *pDestData++ = SLIP_END; + } + else + *pDestData++ = nSrcByte; + + nDestSize++; + } + else if (nSrcByte == SLIP_ESC_ESC) + { + if (is_escaped) + { + is_escaped = false; + + *pDestData++ = SLIP_ESC; + } + else + *pDestData++ = nSrcByte; + + nDestSize++; + } + else + { + *pDestData++ = nSrcByte; + nDestSize++; + } + } + + *pDestSize = nDestSize; + + return err_code; +} diff --git a/src/libs/slip_enc.h b/src/libs/slip_enc.h new file mode 100644 index 0000000..437be7a --- /dev/null +++ b/src/libs/slip_enc.h @@ -0,0 +1,81 @@ +/** +* Copyright (c) 2017, Nordic Semiconductor ASA +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form, except as embedded into a Nordic +* Semiconductor ASA integrated circuit in a product or a software update for +* such product, must reproduce the above copyright notice, this list of +* conditions and the following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. Neither the name of Nordic Semiconductor ASA nor the names of its +* contributors may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* 4. This software, with or without modification, must only be used with a +* Nordic Semiconductor ASA integrated circuit. +* +* 5. Any software provided in binary form under this license must not be reverse +* engineered, decompiled, modified and/or disassembled. +* +* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Modified by Jacob Eva (Liberated Embedded Systems) (c) 2025. +* Changes licensed under the GPL. +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the Free +* Software Foundation, either version 3 of the License, or (at your option) +* any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* You should have received a copy of the GNU General Public License along +* with this program. If not, see . +*/ + +#pragma once + +#ifndef _INC_SLIP_ENC +#define _INC_SLIP_ENC + +#include +#include + +#define SLIP_END 0300 +#define SLIP_HEADER_LEN 4 +#define SLIP_GROW_FACTOR 2 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void slip_header(uint8_t seq, bool dip, bool rp, uint8_t pkt_type, uint32_t pkt_len, uint8_t* header); + +void encode_slip(uint8_t *pDestData, uint32_t *pDestSize, const uint8_t *pSrcData, uint32_t nSrcSize, bool frame); + +int decode_slip(uint8_t *pDestData, uint32_t *pDestSize, const uint8_t *pSrcData, uint32_t nSrcSize); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif /* __cplusplus */ + + +#endif // _INC_SLIP_ENC diff --git a/src/libs/util.c b/src/libs/util.c new file mode 100644 index 0000000..33554e4 --- /dev/null +++ b/src/libs/util.c @@ -0,0 +1,29 @@ +/* Copyright (c) 2025 - Jacob Eva (Liberated Embedded Systems) + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "util.h" + +int sleep_ms(int ms) +{ + #ifdef WIN32 + Sleep(ms); + return 0; + #elif _POSIX_C_SOURCE >= 199309L + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + return nanosleep(&ts, NULL); + #else + return usleep(ms * 1000); + #endif +} diff --git a/src/libs/util.h b/src/libs/util.h new file mode 100644 index 0000000..61ed827 --- /dev/null +++ b/src/libs/util.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2025 - Jacob Eva (Liberated Embedded Systems) + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef __UTIL_H +#define __UTIL_H + +#include +#include + +int sleep_ms(int ms); + +#endif diff --git a/src/libs/zip.c b/src/libs/zip.c new file mode 100644 index 0000000..71c6c52 --- /dev/null +++ b/src/libs/zip.c @@ -0,0 +1,786 @@ +/* + * 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. + */ + +#include "zip.h" +#include "miniz.h" + +#include +#include +#include + +#if defined _WIN32 || defined __WIN32__ +/* Win32, DOS */ +#include + +#define MKDIR(DIRNAME) _mkdir(DIRNAME) +#define STRCLONE(STR) ((STR) ? _strdup(STR) : NULL) +#define HAS_DEVICE(P) \ + ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) && \ + (P)[1] == ':') +#define FILESYSTEM_PREFIX_LEN(P) (HAS_DEVICE(P) ? 2 : 0) +#define ISSLASH(C) ((C) == '/' || (C) == '\\') + +#else +#define MKDIR(DIRNAME) mkdir(DIRNAME, 0755) +#define STRCLONE(STR) ((STR) ? strdup(STR) : NULL) +#endif + +#ifndef FILESYSTEM_PREFIX_LEN +#define FILESYSTEM_PREFIX_LEN(P) 0 +#endif + +#ifndef ISSLASH +#define ISSLASH(C) ((C) == '/') +#endif + +#define CLEANUP(ptr) \ + do { \ + if (ptr) { \ + free((void *)ptr); \ + ptr = NULL; \ + } \ + } while (0) + +static char *basename(const char *name) { + char const *p; + char const *base = name += FILESYSTEM_PREFIX_LEN(name); + int all_slashes = 1; + + for (p = name; *p; p++) { + if (ISSLASH(*p)) + base = p + 1; + else + all_slashes = 0; + } + + /* If NAME is all slashes, arrange to return `/'. */ + if (*base == '\0' && ISSLASH(*name) && all_slashes) --base; + + return (char *)base; +} + +static int mkpath(const char *path) { + char const *p; + char npath[MAX_PATH + 1] = {0}; + int len = 0; + + for (p = path; *p && len < MAX_PATH; p++) { + if (ISSLASH(*p) && len > 0) { + if (MKDIR(npath) == -1) + if (errno != EEXIST) return -1; + } + npath[len++] = *p; + } + + return 0; +} + +static char *strrpl(const char *str, size_t n, char oldchar, char newchar) { + char c; + size_t i; + char *rpl = (char *)calloc((1 + n), sizeof(char)); + char *begin = rpl; + if (!rpl) { + return NULL; + } + + for(i = 0; (i < n) && (c = *str++); ++i) { + if (c == oldchar) { + c = newchar; + } + *rpl++ = c; + } + + return begin; +} + +struct zip_entry_t { + int index; + const char *name; + mz_uint64 uncomp_size; + mz_uint64 comp_size; + mz_uint32 uncomp_crc32; + mz_uint64 offset; + mz_uint8 header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint64 header_offset; + mz_uint16 method; + mz_zip_writer_add_state state; + tdefl_compressor comp; +}; + +struct zip_t { + mz_zip_archive archive; + mz_uint level; + struct zip_entry_t entry; +}; + +struct zip_t *zip_open(const char *zipname, int level, char mode) { + struct zip_t *zip = NULL; + + if (!zipname || strlen(zipname) < 1) { + // zip_t archive name is empty or NULL + goto cleanup; + } + + if (level < 0) level = MZ_DEFAULT_LEVEL; + if ((level & 0xF) > MZ_UBER_COMPRESSION) { + // Wrong compression level + goto cleanup; + } + + zip = (struct zip_t *)calloc((size_t)1, sizeof(struct zip_t)); + if (!zip) goto cleanup; + + zip->level = level; + switch (mode) { + case 'w': + // Create a new archive. + if (!mz_zip_writer_init_file(&(zip->archive), zipname, 0)) { + // Cannot initialize zip_archive writer + goto cleanup; + } + break; + + case 'r': + case 'a': + if (!mz_zip_reader_init_file( + &(zip->archive), zipname, + level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) { + // An archive file does not exist or cannot initialize + // zip_archive reader + goto cleanup; + } + if (mode == 'a' && + !mz_zip_writer_init_from_reader(&(zip->archive), zipname)) { + mz_zip_reader_end(&(zip->archive)); + goto cleanup; + } + break; + + default: + goto cleanup; + } + + return zip; + +cleanup: + CLEANUP(zip); + return NULL; +} + +void zip_close(struct zip_t *zip) { + if (zip) { + // Always finalize, even if adding failed for some reason, so we have a + // valid central directory. + mz_zip_writer_finalize_archive(&(zip->archive)); + + mz_zip_writer_end(&(zip->archive)); + mz_zip_reader_end(&(zip->archive)); + + CLEANUP(zip); + } +} + +int zip_entry_open(struct zip_t *zip, const char *entryname) { + size_t entrylen = 0; + mz_zip_archive *pzip = NULL; + mz_uint num_alignment_padding_bytes, level; + mz_zip_archive_file_stat stats; + + if (!zip || !entryname) { + return -1; + } + + entrylen = strlen(entryname); + if (entrylen < 1) { + return -1; + } + + /* + .ZIP File Format Specification Version: 6.3.3 + + 4.4.17.1 The name of the file, with optional relative path. + The path stored MUST not contain a drive or + device letter, or a leading slash. All slashes + MUST be forward slashes '/' as opposed to + backwards slashes '\' for compatibility with Amiga + and UNIX file systems etc. If input came from standard + input, there is no file name field. + */ + zip->entry.name = strrpl(entryname, entrylen, '\\', '/'); + if (!zip->entry.name) { + // Cannot parse zip entry name + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) { + zip->entry.index = mz_zip_reader_locate_file(pzip, zip->entry.name, NULL, 0); + if (zip->entry.index < 0) { + goto cleanup; + } + + if (!mz_zip_reader_file_stat(pzip, zip->entry.index, &stats)) { + goto cleanup; + } + + zip->entry.comp_size = stats.m_comp_size; + zip->entry.uncomp_size = stats.m_uncomp_size; + zip->entry.uncomp_crc32 = stats.m_crc32; + zip->entry.offset = stats.m_central_dir_ofs; + zip->entry.header_offset = stats.m_local_header_ofs; + zip->entry.method = stats.m_method; + + return 0; + } + + zip->entry.index = zip->archive.m_total_files; + zip->entry.comp_size = 0; + zip->entry.uncomp_size = 0; + zip->entry.uncomp_crc32 = MZ_CRC32_INIT; + zip->entry.offset = zip->archive.m_archive_size; + zip->entry.header_offset = zip->archive.m_archive_size; + memset(zip->entry.header, 0, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE * sizeof(mz_uint8)); + zip->entry.method = 0; + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pzip); + + if (!pzip->m_pState || (pzip->m_zip_mode != MZ_ZIP_MODE_WRITING)) { + // Wrong zip mode + goto cleanup; + } + if (zip->level & MZ_ZIP_FLAG_COMPRESSED_DATA) { + // Wrong zip compression level + goto cleanup; + } + // 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 + + entrylen) > 0xFFFFFFFF)) { + // No zip64 support yet + goto cleanup; + } + if (!mz_zip_writer_write_zeros( + pzip, zip->entry.offset, + num_alignment_padding_bytes + sizeof(zip->entry.header))) { + // Cannot memset zip entry header + goto cleanup; + } + + zip->entry.header_offset += num_alignment_padding_bytes; + if (pzip->m_file_offset_alignment) { + MZ_ASSERT((zip->entry.header_offset & + (pzip->m_file_offset_alignment - 1)) == 0); + } + zip->entry.offset += + num_alignment_padding_bytes + sizeof(zip->entry.header); + + if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, zip->entry.name, + entrylen) != entrylen) { + // Cannot write data to zip entry + goto cleanup; + } + + zip->entry.offset += entrylen; + level = zip->level & 0xF; + if (level) { + zip->entry.state.m_pZip = pzip; + zip->entry.state.m_cur_archive_file_ofs = zip->entry.offset; + zip->entry.state.m_comp_size = 0; + + if (tdefl_init(&(zip->entry.comp), mz_zip_writer_add_put_buf_callback, + &(zip->entry.state), + tdefl_create_comp_flags_from_zip_params( + level, -15, MZ_DEFAULT_STRATEGY)) != + TDEFL_STATUS_OKAY) { + // Cannot initialize the zip compressor + goto cleanup; + } + } + + return 0; + + cleanup: + CLEANUP(zip->entry.name); + return -1; +} + +int zip_entry_openbyindex(struct zip_t *zip, int index) { + mz_zip_archive *pZip = NULL; + mz_zip_archive_file_stat stats; + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pZip = &(zip->archive); + if (pZip->m_zip_mode != MZ_ZIP_MODE_READING) { + // open by index requires readonly mode + return -1; + } + + if (index < 0 || (mz_uint)index >= pZip->m_total_files) { + // index out of range + return -1; + } + + 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, index)); + if (!pHeader) { + // cannot find header in central directory + return -1; + } + + mz_uint namelen = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + + /* + .ZIP File Format Specification Version: 6.3.3 + + 4.4.17.1 The name of the file, with optional relative path. + The path stored MUST not contain a drive or + device letter, or a leading slash. All slashes + MUST be forward slashes '/' as opposed to + backwards slashes '\' for compatibility with Amiga + and UNIX file systems etc. If input came from standard + input, there is no file name field. + */ + zip->entry.name = strrpl(pFilename, namelen, '\\', '/'); + if (!zip->entry.name) { + // local entry name is NULL + return -1; + } + + if (!mz_zip_reader_file_stat(pZip, index, &stats)) { + return -1; + } + + zip->entry.index = index; + zip->entry.comp_size = stats.m_comp_size; + zip->entry.uncomp_size = stats.m_uncomp_size; + zip->entry.uncomp_crc32 = stats.m_crc32; + zip->entry.offset = stats.m_central_dir_ofs; + zip->entry.header_offset = stats.m_local_header_ofs; + zip->entry.method = stats.m_method; + + return 0; +} + +int zip_entry_close(struct zip_t *zip) { + mz_zip_archive *pzip = NULL; + mz_uint level; + tdefl_status done; + mz_uint16 entrylen; + time_t t; + struct tm *tm; + mz_uint16 dos_time, dos_date; + int status = -1; + + if (!zip) { + // zip_t handler is not initialized + goto cleanup; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) { + status = 0; + goto cleanup; + } + + level = zip->level & 0xF; + if (level) { + done = tdefl_compress_buffer(&(zip->entry.comp), "", 0, TDEFL_FINISH); + if (done != TDEFL_STATUS_DONE && done != TDEFL_STATUS_OKAY) { + // Cannot flush compressed buffer + goto cleanup; + } + zip->entry.comp_size = zip->entry.state.m_comp_size; + zip->entry.offset = zip->entry.state.m_cur_archive_file_ofs; + zip->entry.method = MZ_DEFLATED; + } + + entrylen = (mz_uint16)strlen(zip->entry.name); + t = time(NULL); + tm = localtime(&t); + dos_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + + ((tm->tm_sec) >> 1)); + dos_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + + ((tm->tm_mon + 1) << 5) + tm->tm_mday); + + // no zip64 support yet + if ((zip->entry.comp_size > 0xFFFFFFFF) || + (zip->entry.offset > 0xFFFFFFFF)) { + // No zip64 support, yet + goto cleanup; + } + + if (!mz_zip_writer_create_local_dir_header( + pzip, zip->entry.header, entrylen, 0, zip->entry.uncomp_size, + zip->entry.comp_size, zip->entry.uncomp_crc32, zip->entry.method, 0, + dos_time, dos_date)) { + // Cannot create zip entry header + goto cleanup; + } + + if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.header_offset, + zip->entry.header, sizeof(zip->entry.header)) != + sizeof(zip->entry.header)) { + // Cannot write zip entry header + goto cleanup; + } + + if (!mz_zip_writer_add_to_central_dir( + pzip, zip->entry.name, entrylen, NULL, 0, "", 0, + zip->entry.uncomp_size, zip->entry.comp_size, + zip->entry.uncomp_crc32, zip->entry.method, 0, dos_time, dos_date, + zip->entry.header_offset, 0)) { + // Cannot write to zip central dir + goto cleanup; + } + + pzip->m_total_files++; + pzip->m_archive_size = zip->entry.offset; + status = 0; + +cleanup: + CLEANUP(zip->entry.name); + return status; +} + +const char *zip_entry_name(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return NULL; + } + + return zip->entry.name; +} + +int zip_entry_index(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + return zip->entry.index; +} + +int zip_entry_isdir(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + if (zip->entry.index < 0) { + // zip entry is not opened + return -1; + } + + return (int)mz_zip_reader_is_file_a_directory(&zip->archive, (mz_uint)zip->entry.index); +} + +unsigned long long zip_entry_size(struct zip_t *zip) { + return zip->entry.uncomp_size; +} + +unsigned int zip_entry_crc32(struct zip_t *zip) { + return zip->entry.uncomp_crc32; +} + +int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize) { + mz_uint level; + mz_zip_archive *pzip = NULL; + tdefl_status status; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (buf && bufsize > 0) { + zip->entry.uncomp_size += bufsize; + zip->entry.uncomp_crc32 = (mz_uint32)mz_crc32( + zip->entry.uncomp_crc32, (const mz_uint8 *)buf, bufsize); + + level = zip->level & 0xF; + if (!level) { + if ((pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, buf, + bufsize) != bufsize)) { + // Cannot write buffer + return -1; + } + zip->entry.offset += bufsize; + zip->entry.comp_size += bufsize; + } else { + status = tdefl_compress_buffer(&(zip->entry.comp), buf, bufsize, + TDEFL_NO_FLUSH); + if (status != TDEFL_STATUS_DONE && status != TDEFL_STATUS_OKAY) { + // Cannot compress buffer + return -1; + } + } + } + + return 0; +} + +int zip_entry_fwrite(struct zip_t *zip, const char *filename) { + int status = 0; + size_t n = 0; + FILE *stream = NULL; + mz_uint8 buf[MZ_ZIP_MAX_IO_BUF_SIZE] = {0}; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + stream = fopen(filename, "rb"); + if (!stream) { + // Cannot open filename + return -1; + } + + while ((n = fread(buf, sizeof(mz_uint8), MZ_ZIP_MAX_IO_BUF_SIZE, stream)) > + 0) { + if (zip_entry_write(zip, buf, n) < 0) { + status = -1; + break; + } + } + fclose(stream); + + return status; +} + +int zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + idx = (mz_uint)zip->entry.index; + if (mz_zip_reader_is_file_a_directory(pzip, idx)) { + // the entry is a directory + return -1; + } + + *buf = mz_zip_reader_extract_to_heap(pzip, idx, bufsize, 0); + return (*buf) ? 0 : -1; +} + +int zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + idx = (mz_uint)zip->entry.index; + if (!mz_zip_reader_extract_to_mem_no_alloc(pzip, idx, buf, bufsize, 0, NULL, 0)) { + return -1; + } + + return 0; +} + +int zip_entry_fread(struct zip_t *zip, const char *filename) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + idx = (mz_uint)zip->entry.index; + if (mz_zip_reader_is_file_a_directory(pzip, idx)) { + // the entry is a directory + return -1; + } + + return (mz_zip_reader_extract_to_file(pzip, idx, filename, 0)) ? 0 : -1; +} + +int zip_entry_extract(struct zip_t *zip, + size_t (*on_extract)(void *arg, unsigned long long offset, + const void *buf, size_t bufsize), + void *arg) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + idx = (mz_uint)zip->entry.index; + return (mz_zip_reader_extract_to_callback(pzip, idx, on_extract, arg, 0)) ? 0 : -1; +} + +int zip_total_entries(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + return zip->archive.m_total_files; +} + +int zip_create(const char *zipname, const char *filenames[], size_t len) { + int status = 0; + size_t i; + mz_zip_archive zip_archive; + + if (!zipname || strlen(zipname) < 1) { + // zip_t archive name is empty or NULL + return -1; + } + + // Create a new archive. + if (!memset(&(zip_archive), 0, sizeof(zip_archive))) { + // Cannot memset zip archive + return -1; + } + + if (!mz_zip_writer_init_file(&zip_archive, zipname, 0)) { + // Cannot initialize zip_archive writer + return -1; + } + + for (i = 0; i < len; ++i) { + const char *name = filenames[i]; + if (!name) { + status = -1; + break; + } + + if (!mz_zip_writer_add_file(&zip_archive, basename(name), name, "", 0, + ZIP_DEFAULT_COMPRESSION_LEVEL)) { + // Cannot add file to zip_archive + status = -1; + break; + } + } + + mz_zip_writer_finalize_archive(&zip_archive); + mz_zip_writer_end(&zip_archive); + return status; +} + +int zip_extract(const char *zipname, const char *dir, + int (*on_extract)(const char *filename, void *arg), void *arg) { + int status = -1; + mz_uint i, n; + char path[MAX_PATH + 1] = {0}; + mz_zip_archive zip_archive; + mz_zip_archive_file_stat info; + size_t dirlen = 0; + + if (!memset(&(zip_archive), 0, sizeof(zip_archive))) { + // Cannot memset zip archive + return -1; + } + + if (!zipname || !dir) { + // Cannot parse zip archive name + return -1; + } + + dirlen = strlen(dir); + if (dirlen + 1 > MAX_PATH) { + return -1; + } + + // Now try to open the archive. + if (!mz_zip_reader_init_file(&zip_archive, zipname, 0)) { + // Cannot initialize zip_archive reader + return -1; + } + + strcpy(path, dir); + if (!ISSLASH(path[dirlen - 1])) { +#if defined _WIN32 || defined __WIN32__ + path[dirlen] = '\\'; +#else + path[dirlen] = '/'; +#endif + ++dirlen; + } + + // Get and print information about each file in the archive. + n = mz_zip_reader_get_num_files(&zip_archive); + for (i = 0; i < n; ++i) { + if (!mz_zip_reader_file_stat(&zip_archive, i, &info)) { + // Cannot get information about zip archive; + goto out; + } + strncpy(&path[dirlen], info.m_filename, MAX_PATH - dirlen); + if (mkpath(path) < 0) { + // Cannot make a path + goto out; + } + + if (!mz_zip_reader_is_file_a_directory(&zip_archive, i)) { + if (!mz_zip_reader_extract_to_file(&zip_archive, i, path, 0)) { + // Cannot extract zip archive to file + goto out; + } + } + + if (on_extract) { + if (on_extract(path, arg) < 0) { + goto out; + } + } + } + status = 0; + +out: + // Close the archive, freeing any resources it was using + if (!mz_zip_reader_end(&zip_archive)) { + // Cannot end zip reader + status = -1; + } + + return status; +} diff --git a/src/libs/zip.h b/src/libs/zip.h new file mode 100644 index 0000000..c15f62a --- /dev/null +++ b/src/libs/zip.h @@ -0,0 +1,302 @@ +/* + * 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. + */ + +#pragma once +#ifndef ZIP_H +#define ZIP_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MAX_PATH +#define MAX_PATH 32767 /* # chars in a path name including NULL */ +#endif + +#define ZIP_DEFAULT_COMPRESSION_LEVEL 6 + +/* + This data structure is used throughout the library to represent zip archive + - forward declaration. +*/ +struct zip_t; + +/* + Opens zip archive with compression level using the given mode. + + Args: + zipname: zip archive file name. + level: compression level (0-9 are the standard zlib-style levels). + mode: file access mode. + 'r': opens a file for reading/extracting (the file must exists). + 'w': creates an empty file for writing. + 'a': appends to an existing archive. + + Returns: + The zip archive handler or NULL on error +*/ +extern struct zip_t *zip_open(const char *zipname, int level, char mode); + +/* + Closes the zip archive, releases resources - always finalize. + + Args: + zip: zip archive handler. +*/ +extern void zip_close(struct zip_t *zip); + +/* + Opens an entry by name in the zip archive. + For zip archive opened in 'w' or 'a' mode the function will append + a new entry. In readonly mode the function tries to locate the entry + in global dictionary. + + Args: + zip: zip archive handler. + entryname: an entry name in local dictionary. + + Returns: + The return code - 0 on success, negative number (< 0) on error. +*/ +extern int zip_entry_open(struct zip_t *zip, const char *entryname); + +/* + Opens a new entry by index in the zip archive. + This function is only valid if zip archive was opened in 'r' (readonly) mode. + + Args: + zip: zip archive handler. + index: index in local dictionary. + + Returns: + The return code - 0 on success, negative number (< 0) on error. +*/ +extern int zip_entry_openbyindex(struct zip_t *zip, int index); + +/* + Closes a zip entry, flushes buffer and releases resources. + + Args: + zip: zip archive handler. + + Returns: + The return code - 0 on success, negative number (< 0) on error. +*/ +extern int zip_entry_close(struct zip_t *zip); + +/* + Returns a local name of the current zip entry. + The main difference between user's entry name and local entry name + is optional relative path. + Following .ZIP File Format Specification - the path stored MUST not contain + a drive or device letter, or a leading slash. + All slashes MUST be forward slashes '/' as opposed to backwards slashes '\' + for compatibility with Amiga and UNIX file systems etc. + + Args: + zip: zip archive handler. + + Returns: + The pointer to the current zip entry name, or NULL on error. +*/ +extern const char *zip_entry_name(struct zip_t *zip); + +/* + Returns an index of the current zip entry. + + Args: + zip: zip archive handler. + + Returns: + The index on success, negative number (< 0) on error. +*/ +extern int zip_entry_index(struct zip_t *zip); + +/* + Determines if the current zip entry is a directory entry. + + Args: + zip: zip archive handler. + + Returns: + The return code - 1 (true), 0 (false), negative number (< 0) on error. +*/ +extern int zip_entry_isdir(struct zip_t *zip); + +/* + Returns an uncompressed size of the current zip entry. + + Args: + zip: zip archive handler. + + Returns: + The uncompressed size in bytes. +*/ +extern unsigned long long zip_entry_size(struct zip_t *zip); + +/* + Returns CRC-32 checksum of the current zip entry. + + Args: + zip: zip archive handler. + + Returns: + The CRC-32 checksum. +*/ +extern unsigned int zip_entry_crc32(struct zip_t *zip); + +/* + Compresses an input buffer for the current zip entry. + + Args: + zip: zip archive handler. + buf: input buffer. + bufsize: input buffer size (in bytes). + + Returns: + The return code - 0 on success, negative number (< 0) on error. +*/ +extern int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize); + +/* + Compresses a file for the current zip entry. + + Args: + zip: zip archive handler. + filename: input file. + + Returns: + The return code - 0 on success, negative number (< 0) on error. +*/ +extern int zip_entry_fwrite(struct zip_t *zip, const char *filename); + +/* + Extracts the current zip entry into output buffer. + The function allocates sufficient memory for a output buffer. + + Args: + zip: zip archive handler. + buf: output buffer. + bufsize: output buffer size (in bytes). + + Note: + - remember to release memory allocated for a output buffer. + - for large entries, please take a look at zip_entry_extract function. + + Returns: + The return code - 0 on success, negative number (< 0) on error. +*/ +extern int zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize); + +/* + Extracts the current zip entry into a memory buffer using no memory allocation. + + Args: + zip: zip archive handler. + buf: preallocated output buffer. + bufsize: output buffer size (in bytes). + + Note: + - ensure supplied output buffer is large enough. + - zip_entry_size function (returns uncompressed size for the current entry) + can be handy to estimate how big buffer is needed. + - for large entries, please take a look at zip_entry_extract function. + + Returns: + The return code - 0 on success, negative number (< 0) on error (e.g. bufsize + is not large enough). +*/ +extern int zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize); + +/* + Extracts the current zip entry into output file. + + Args: + zip: zip archive handler. + filename: output file. + + Returns: + The return code - 0 on success, negative number (< 0) on error. +*/ +extern int zip_entry_fread(struct zip_t *zip, const char *filename); + +/* + Extracts the current zip entry using a callback function (on_extract). + + Args: + zip: zip archive handler. + on_extract: callback function. + arg: opaque pointer (optional argument, + which you can pass to the on_extract callback) + + Returns: + The return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_extract(struct zip_t *zip, + size_t (*on_extract)(void *arg, + unsigned long long offset, + const void *data, + size_t size), + void *arg); + +/* + Returns the number of all entries (files and directories) in the zip archive. + + Args: + zip: zip archive handler. + + Returns: + The return code - the number of entries on success, + negative number (< 0) on error. +*/ +extern int zip_total_entries(struct zip_t *zip); + +/* + Creates a new archive and puts files into a single zip archive. + + Args: + zipname: zip archive file. + filenames: input files. + len: number of input files. + + Returns: + The return code - 0 on success, negative number (< 0) on error. +*/ +extern int zip_create(const char *zipname, const char *filenames[], size_t len); + +/* + Extracts a zip archive file into directory. + + If on_extract_entry is not NULL, the callback will be called after + successfully extracted each zip entry. + Returning a negative value from the callback will cause abort and return an + error. The last argument (void *arg) is optional, which you can use to pass + data to the on_extract_entry callback. + + Args: + zipname: zip archive file. + dir: output directory. + on_extract_entry: on extract callback. + arg: opaque pointer. + + Returns: + The return code - 0 on success, negative number (< 0) on error. +*/ +extern int zip_extract(const char *zipname, const char *dir, + int (*on_extract_entry)(const char *filename, void *arg), + void *arg); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tests/test.c b/tests/test.c new file mode 100644 index 0000000..17aaf1c --- /dev/null +++ b/tests/test.c @@ -0,0 +1,208 @@ +#include +#include "../src/librnode.h" +#include "../src/libs/logging/log.h" +#include "../src/libs/util.h" +#include +#include +#include +#include +#include + +#include // todo remove me +#include // todo remove me + +//char* port = "/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0"; +char* port = "/dev/ttyACM0"; +//char* port = "/dev/serial/by-id/usb-Heltec_HT-n5262_1E35113239EFFE55-if00"; +char* zip = "/home/elimin8/Downloads/rnode_firmware_t3s3_sx1280_pa.zip"; +char* key = "/home/elimin8/.config/rnodeconf/firmware/signing.key"; +uint32_t baud = 115200; +struct RNode rn = {0}; + +EVP_PKEY* load_key(struct RNode* rn, char* path) { + EVP_PKEY *priv_key = EVP_PKEY_new(); + OSSL_DECODER_CTX *dctx; + const char *format = "DER"; /* NULL for any format */ + const char *structure = NULL; /* any structure */ + const char *keytype = "RSA"; /* NULL for any key */ + size_t len; + uint8_t key_buf[1024]; + const uint8_t *data = key_buf; + + FILE *file = fopen(path, "rb"); + if (file <= 0) { + return NULL; + } + fseek(file, 0L, SEEK_END); + len = ftell(file); + + fseek(file, 0L, SEEK_SET); + + fread(key_buf, sizeof(uint8_t), len, file); + + fclose(file); + + dctx = OSSL_DECODER_CTX_new_for_pkey(&priv_key, format, structure, + keytype, + OSSL_KEYMGMT_SELECT_KEYPAIR, + NULL, NULL); + if (dctx == NULL) { + /* error: no suitable potential decoders found */ + return NULL; + } + //if (pass != NULL) + // OSSL_DECODER_CTX_set_passphrase(dctx, pass, strlen(pass)); + + if (!OSSL_DECODER_from_data(dctx, &data, &len)) { + /* decoding failure */ + return NULL; + } + OSSL_DECODER_CTX_free(dctx); + + return priv_key; +} + +void esp32_flash(struct RNode* rn, char* zip) { + uint8_t serial[4] = {0}; + + ok(rnode_set_platform(rn, PLATFORM_ESP32) == 0, "RNode set platform"); + + time_t start = time(NULL); + EVP_PKEY *priv_key = load_key(rn, key); + ok(priv_key != NULL, "Load EEPROM signing key"); + rn->product = 0xf0; + rn->model = 0xac; + rn->hw_rev = 0x01; + rn->boot_app0_addr = 0xe000; + rn->bootloader_addr = 0x0; + rn->bin_addr = 0x10000; + rn->partitions_addr = 0x8000; + rn->console_image_addr = 0x210000; + ok(rnode_flash(rn, zip, false, serial, priv_key, true) == 0, "RNode update"); + time_t end = time(NULL); + printf("Time taken for flash: %ld seconds.\n", end - start); +} + +void nrf52_flash(struct RNode* rn, char* zip) { + uint8_t serial[4] = {0}; + + ok(rnode_set_platform(rn, PLATFORM_NRF52) == 0, "RNode set platform"); + + time_t start = time(NULL); + EVP_PKEY *priv_key = load_key(rn, key); + ok(priv_key != NULL, "Load EEPROM signing key"); + rn->product = 0x10; + rn->model = 0x12; + rn->hw_rev = 0x01; + ok(rnode_flash(rn, zip, false, serial, priv_key, true) == 0, "RNode update"); + time_t end = time(NULL); + printf("Time taken for flash: %ld seconds.\n", end - start); +} + +int main() { + //printf("--- RNode reset test ---\n\n"); + + ok(rnode_init(&rn, port, baud, true, false) == 0, "RNode initialisation & detection"); + + //ok(rnode_reset(&rn) == 0, "RNode reset"); + //sleep_ms(5000); + + //ok(rnode_cleanup(&rn) == 0, "RNode cleanup"); + + //ok(rnode_init(&rn, port, baud, true, false) == 0, "RNode initialisation & detection"); + + //printf("--- RNode display test --- \n\n"); + //ok(rnode_set_disp_int(&rn, 255) == 0, "Set display intensity to 255"); + //sleep_ms(500); + //ok(rnode_set_disp_int(&rn, 0) == 0, "Set display intensity to 0"); + //sleep_ms(500); + //ok(rnode_set_disp_int(&rn, 127) == 0, "Set display intensity to 127"); + //sleep_ms(100); + + //ok(rnode_set_disp_timeout(&rn, 1) == 0, "Set display timeout to 1 second"); + //sleep_ms(5000); + //ok(rnode_set_disp_timeout(&rn, 1) == 0, "Disable display timeout"); + //sleep_ms(500); + + //ok(rnode_set_disp_addr(&rn, 0x3B) == 0, "Set display address to 0x3B"); + //sleep_ms(500); + //ok(rnode_set_disp_addr(&rn, 0x3C) == 0, "Set display address to 0x3C"); + //sleep_ms(500); + + //ok(rnode_set_disp_rot(&rn, 0) == 0, "Set display rotation to 0"); + //sleep_ms(2000); + //ok(rnode_cleanup(&rn) == 0, "RNode cleanup"); + //ok(rnode_init(&rn, port, baud, true, false) == 0, "RNode initialisation & detection"); + //ok(rnode_set_disp_rot(&rn, 1) == 0, "Set display rotation to 1"); + //sleep_ms(2000); + //ok(rnode_cleanup(&rn) == 0, "RNode cleanup"); + //ok(rnode_init(&rn, port, baud, true, false) == 0, "RNode initialisation & detection"); + //ok(rnode_set_disp_rot(&rn, 2) == 0, "Set display rotation to 2"); + //sleep_ms(2000); + //ok(rnode_cleanup(&rn) == 0, "RNode cleanup"); + //ok(rnode_init(&rn, port, baud, true, false) == 0, "RNode initialisation & detection"); + //ok(rnode_set_disp_rot(&rn, 3) == 0, "Set display rotation to 3"); + //sleep_ms(2000); + //ok(rnode_cleanup(&rn) == 0, "RNode cleanup"); + //ok(rnode_init(&rn, port, baud, true, false) == 0, "RNode initialisation & detection"); + + //ok(rnode_start_disp_recon(&rn) == 0, "Start display reconditioning (5s)"); + //sleep_ms(5000); + + //ok(rnode_reset(&rn) == 0, "RNode reset"); + //sleep_ms(5000); + + //ok(rnode_cleanup(&rn) == 0, "RNode cleanup"); + //ok(rnode_init(&rn, port, baud, true, false) == 0, "RNode initialisation & detection"); + + //printf("--- RNode NP test (if present) --- \n\n"); + + //ok(rnode_set_np_int(&rn, 0) == 0, "Set NP intensity to 0"); + //sleep_ms(500); + + //ok(rnode_set_np_int(&rn, 255) == 0, "Set NP intensity to 255"); + //sleep_ms(500); + // + //printf("--- RNode BT test --- \n\n"); + + //ok(rnode_set_bt(&rn, BT_OFF) == 0, "Disable Bluetooth"); + //sleep_ms(1000); + + //ok(rnode_set_bt(&rn, BT_ON) == 0, "Enable Bluetooth"); + //sleep_ms(1000); + + //ok(rnode_set_bt(&rn, BT_PAIRING) == 0, "Enable Bluetooth pairing"); + //sleep_ms(1000); + + //int pin = rnode_get_bt_pin(&rn, 30000); + + //ok(pin > 0, "Get Bluetooth code (30s timeout)"); + + //printf("Pin: %u\n", pin); + + //ok(rnode_wipe_eeprom(&rn) == 0, "Wipe RNode EEPROM"); + + //esp32_flash(&rn, zip); + //printf("--- RNode flash & provision test ---\n\n"); + + + printf("--- RNode TNC mode test ---\n\n"); + + ok(rnode_get_interfaces(&rn) == 0, "Get interfaces"); + + ok(rnode_select_interface(&rn, 0) == 0, "Select interface 0"); + + ok(rnode_set_freq(&rn, 865700000) > 0, "Set frequency"); + + ok(rnode_set_sf(&rn, 7) == 0, "Set spreading factor"); + + ok(rnode_set_cr(&rn, 7) == 0, "Set coding rate"); + + ok(rnode_set_txp(&rn, 7) == 0, "Set TX power"); + + ok(rnode_set_tnc_mode(&rn) == 0, "Enable TNC mode"); + + ok(rnode_cleanup(&rn) == 0, "RNode cleanup"); + + done_testing(); +}