commit ea24dbaeff41d6d0291d4563455ae2da281eb223 Author: Guillaume Gonnet Date: Thu Jan 3 12:05:24 2019 +0100 Release Maconv v1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3d6549 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..71d3ab1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,86 @@ +# +# Build Maconv. +# +# Copyright (C) 2019, Guillaume Gonnet +# License GPL3 + +cmake_minimum_required(VERSION 3.2) +project(maconv) + + +# Compile vendor libraries. +add_subdirectory("vendors/libhfs") + + +# Source files. +set(MACONV_SRC + "src/fs/file.h" + "src/fs/file.cc" + "src/fs/file_reader.h" + "src/fs/file_reader.cc" + "src/fs/file_writer.h" + "src/fs/file_writer.cc" + + "src/conv/converters.h" + # "src/conv/appledouble.cc" + "src/conv/applesingle.cc" + "src/conv/macbinary.cc" + "src/conv/binhex.cc" + "src/conv/rsrc.cc" + + "src/disk/disk.h" + "src/disk/extract.cc" + "src/disk/pack.cc" + + "src/stuffit/stuffit.h" + "src/stuffit/stuffit.cc" + "src/stuffit/stuffit_v1.cc" + "src/stuffit/stuffit_v5.cc" + "src/stuffit/utils/bwt.h" + "src/stuffit/utils/bwt.cc" + "src/stuffit/utils/crc.h" + "src/stuffit/utils/crc.cc" + "src/stuffit/utils/huffman.h" + "src/stuffit/utils/huffman.cc" + "src/stuffit/methods.h" + "src/stuffit/methods.cc" + "src/stuffit/methods/rle90.h" + "src/stuffit/methods/rle90.cc" + "src/stuffit/methods/compress.h" + "src/stuffit/methods/compress.cc" + "src/stuffit/methods/algorithm13.h" + "src/stuffit/methods/algorithm13.cc" + "src/stuffit/methods/arsenic.h" + "src/stuffit/methods/arsenic.cc" + + "src/formats/file_signature.h" + "src/formats/file_signature.cc" + "src/formats/formats.h" + "src/formats/formats.cc" + "src/formats/unpack.cc" + "src/formats/pack.cc" + + "src/utils/buffer_stream.h" + "src/utils/buffer_stream.cc" + "src/utils/bit_reader.h" + "src/utils/bit_reader.cc" + + "src/commands.h" + "src/commands.cc" + "src/main.cc" +) + +# Include "src" and "vendors" folder for resolving #include. +include_directories("src" "vendors") + + +# Create the executable. +add_executable(maconv ${MACONV_SRC}) + +# Link "libhfs" library. +target_link_libraries(maconv hfs) + + +# Install rules for Maconv. +install(TARGETS maconv RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") +install(FILES "src/maconv.1" DESTINATION "${CMAKE_INSTALL_PREFIX}/man/man1") diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..810fce6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,621 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 0000000..cbcbfc9 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Maconv + +**Maconv** is a Linux software that can convert all kinds of old Macintosh formats, +including MacBinary, Stuffit archives and HFS disk files. + + +## Usage + +Maconv has three sub-commands: +- `maconv c [options] input-file [output-file]` +- `maconv e [options] input-file [output-folder]` +- `maconv d [options] input-folder [output-file]` + +The `c` sub-commamd converts a file from a format to another. The `e` +sub-commamd extracts a Stuffit archive (versions 1 and 5) or a HFS disk image. +The `d` sub-commamd creates an HFS disk image from a folder (like a file +archiver). + +You can get more information about these commands with `maconv -h` or `man +maconv`. + + +## Supported formats + +You can get more information on these format in `docs` folder. + +Maconv supports the following container formats: [MacBinary][macbin], +[AppleSingle][as]. + +Also, Maconv can extract old [Stuffit archives][stuffit] (encoded with +compression method 0, 1, 2, 13 or 15). + + +## License + +This project is under the GPLv3 license. +See `LICENSE` file at the root of this project for more information. + + +[macbin]: docs/formats/MacBinary.md +[as]: docs/formats/AppleSingle.md +[stuffit]: docs/stuffit/Stuffit.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..57c88f5 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,20 @@ +# Maconv documentations + +Welcome to the documentation associated with **Maconv**. + + +## Information about supported formats + +- [MacBinary](formats/MacBinary.md) +- [BinHex](formats/BinHex.md) +- [AppleSingle](formats/AppleSingle.md) +- [AppleDouble](formats/AppleDouble.md) + + +## Stuffit specifications + +You can find Stuffit specifications [on this page](stuffit/Stuffit.md). +In particular, the documentation presents the following versions of Stuffit: + +- [Stuffit (v1)](stuffit/Stuffit_v1.md) +- [Stuffit (v5)](stuffit/Stuffit_v5.md) diff --git a/docs/file-signature.md b/docs/file-signature.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/formats/AppleDouble.md b/docs/formats/AppleDouble.md new file mode 100644 index 0000000..9652509 --- /dev/null +++ b/docs/formats/AppleDouble.md @@ -0,0 +1,13 @@ +# AppleSingle format + +TODO. + +All integers are big-endians. + + +--------------------------- + +**Sources** + +https://en.wikipedia.org/wiki/AppleSingle_and_AppleDouble_formats +http://kaiser-edv.de/documents/AppleSingle_AppleDouble.pdf diff --git a/docs/formats/AppleSingle.md b/docs/formats/AppleSingle.md new file mode 100644 index 0000000..fe9815c --- /dev/null +++ b/docs/formats/AppleSingle.md @@ -0,0 +1,60 @@ +# AppleSingle format + +AppleSingle is a file format developed to store Mac OS "dual-forked" files on +the Unix filesystem. AppleSingle is similar in concept to the more popular +MacBinary format, in that the resource and data forks are combined together with +a header containing the Finder information. In fact, the format is so similar, +it seemed there were no reason why Apple did not simply use MacBinary instead, +which by that point, was widely known and used. + +All integers are big-endians. + +Data are arranged as follows: +> `data = + + ... + + + ... + ` + + +## File header + +| **Offset** | **Length** | **Contents** | +|:-----------|:-----------|:-------------| +| 00 | Word | Magic number (always `0x00051600`) | +| 04 | Word | Version number (always `0x00020000`) | +| 08 | 16 Bytes | Always zero | +| 24 | Half | Number of entries | + + +## Entry + +| **Offset** | **Length** | **Contents** | +|:-----------|:-----------|:-------------| +| 00 | Word | Entry ID (see below) | +| 04 | Word | Data offset | +| 08 | Word | Data length (can be zero) | + + +Available entry IDs are described in the following table. + +| **Entry name** | **ID** | **Description** | +|:--------------------|:-------|:----------------| +| Data Fork | 1 | Magic number (always `0x00051600`) | +| Resource Fork | 2 | Version number (always `0x00020000`) | +| Real Name | 3 | File’s name as created on home file system | +| Comment | 4 | Standard Macintosh comment | +| Icon, B&W | 5 | Standard Macintosh black and white icon | +| Icon, Color | 6 | Macintosh color icon | +| File Dates Info | 8 | File creation date, modification date, and so on | +| Finder Info | 9 | Standard Macintosh Finder information | +| Macintosh File Info | 10 | Macintosh file information, attributes, and so on | +| ProDOS File Info | 11 | ProDOS file information, attributes, and so on | +| MS-DOS File Info | 12 | MS-DOS file information, attributes, and so on | +| Short Name | 13 | AFP short name | +| AFP File Info | 14 | AFP file information, attributes, and so on | +| Directory ID | 15 | AFP directory ID | + + +--------------------------- + +**Sources** + +https://en.wikipedia.org/wiki/AppleSingle_and_AppleDouble_formats +http://kaiser-edv.de/documents/AppleSingle_AppleDouble.pdf diff --git a/docs/formats/BinHex.md b/docs/formats/BinHex.md new file mode 100644 index 0000000..d7859ab --- /dev/null +++ b/docs/formats/BinHex.md @@ -0,0 +1,9 @@ +# BinHex format + + + +--------------------------- + +**Sources** + +https://files.stairways.com/other/binhex-40-specs-info.txt diff --git a/docs/formats/MacBinary.md b/docs/formats/MacBinary.md new file mode 100644 index 0000000..3c32948 --- /dev/null +++ b/docs/formats/MacBinary.md @@ -0,0 +1,76 @@ +# MacBinary format + +MacBinary is a file format that combines the two forks of a classic Mac OS file +into a single file, along with HFS's extended metadata. It was used to share Mac +OS files over FTP, Web or e-mail because it could be stored on computers that +run operating systems with no HFS support, such as Unix or Windows. + +This file describes a summary of MacBinnary I, II and III. +All integers are big-endians. + +Data are arranged as follows: +> `data =
+ + + + ` + + +## Header – `
` + +| **Offset** | **Length** | **Contents** | +|:-----------|:-----------|:-------------| +| 000 | Byte | Always zero | +| 001 | Byte | Length of filename (in the range 1-31) | +| 002 | 63 Bytes | Filename (remaning bytes are zero) | +| 065 | Word | File type (4 characters) | +| 069 | Word | File creator (4 characters) | +| 073 | Byte | Finder flags: Bit 7 - isAlias. Bit 6 - isInvisible. Bit 5 - hasBundle. Bit 4 - nameLocked. Bit 3 - isStationery. Bit 2 - hasCustomIcon. Bit 1 - reserved. Bit 0 - hasBeenInited. | +| 074 | Byte | Always zero | +| 075 | Half | File's vertical position within its window | +| 077 | Half | File's horizontal position within its window | +| 079 | Half | File's window or folder ID. | +| 081 | Byte | "Protected" flag (in low order bit) | +| 082 | Byte | Always zero | +| 083 | Word | Data Fork length in B (zero if no Data Fork) | +| 087 | Word | Resource Fork length in B (zero if no R.F.) | +| 091 | Word | File's creation date | +| 095 | Word | File's "last modified" date | +| 099 | Half | Length of Get Info comment to be sent after the resource fork (if implemented, see below). | +| 101 **²** | Byte | Finder Flags, bits 0-7. (Bits 8-15 are already in byte 73) Bit 7 - hasNoInits Bit 6 - isShared Bit 5 - requiresSwitchLaunch Bit 4 - ColorReserved Bits 1-3 - color Bit 0 - isOnDesk | +| 102 **³** | Word | Signature for indentification purposes (always `mBIN`) | +| 106 **³** | Byte | Script of file name (from the fdScript field of an fxInfo record) | +| 107 **³** | Byte | Extended Finder flags (from the fdXFlags field of an fxInfo record) | +| 108-115 | | Unused (must be zeroed by creators, must be ignored by readers) | +| 116 **²** | Word | Length of total files when packed files are unpacked. As of the writing of this document, this field has never been used. | +| 120 **²** | Half | Length of a secondary header. If this is non-zero, skip this many bytes (rounded up to the next multiple of 128). This is for future expansion only, when sending files with MacBinary, this word should be zero. | +| 122 **²** | Byte | Version number of MacBinary (`129` for MacBinary II, `130` for MacBinary III) | +| 123 **²** | Byte | Minimum MacBinary version needed to read this file (set this value at 129 for backwards compatibility with MacBinary II) | +| 124 **²** | Half | CRC-16 of previous 124 bytes | +| 126 | Half | Padding (always zero) | + +**²** These fields have been added in MacBinary II. +**³** These fields have been added in MacBinary III. + +Cyclic redundancy check (CRC) used in header is CRC-16-CCITT, i.e. uses +polynomial number `0x1021` and starts with `0`. + + +## Data and ressource forks. + +Data fork directly follow the header (at byte 128). The length of these data +(which can be zero) must correspond with the length given in the header (at byte +83). + +Data are completed with some padding bytes (normally `0x00` but some +implementations use `0x7F`) until the total length of file is a multiple of 128. +If the total length is already a multiple of 128 after adding the data fork, no +padding is added. + +The ressource fork follows the same rules (i.e. padding bytes are added after +the fork to keep the total length a multiple of 128). + + +--------------------------- + +**Sources** + +https://en.wikipedia.org/wiki/MacBinary +https://github.com/mietek/theunarchiver/wiki/MacBinarySpecs +https://files.stairways.com/other/macbinaryii-standard-info.txt diff --git a/docs/libHFS.md b/docs/libHFS.md new file mode 100644 index 0000000..8c925c9 --- /dev/null +++ b/docs/libHFS.md @@ -0,0 +1,37 @@ +# Documentation of libHFS + +You can find the original API of this library in file `vendors/libhfs/libhfs.txt`. + + +## File paths + +File/directory names are up to 31 characters long, volume names up to 27 +characters long. In both cases, they must not contain `:` (directory separator). + +An abolute path must begin with the name of the volume containing the desired +file (as if it was a folder). + +Relative paths start with the `:` character (similar to `./` in Unix paths). A +path that doesn't contain `:` character is also considered as a relative path. + +The directory sperator is the `:` character. When alone, this character refers +to the current directory (as `.` on Unix). When there is a sequence of **N** `:` +(which follow each other), the path refers to the **N-1**-th parent (as `../` +**N-1** times on Unix). + + +## Directories + +On a volume, each directory has a unqiue ID. + +ID `0` is reserved for a meta-directory containing all mounted volumes. +ID `2` is the root directory (of each volume). + +Note: the root directory can't be deleted. + + +## Dates and times + +Dates and times are in classic Mac OS format. Classic Mac OS times are 32 bits +unsigned integers that count the number of seconds since January 1, 1904 00:00 +UTC. diff --git a/docs/licenses/Retro68-Runtime.txt b/docs/licenses/Retro68-Runtime.txt new file mode 100644 index 0000000..e86f7fb --- /dev/null +++ b/docs/licenses/Retro68-Runtime.txt @@ -0,0 +1,72 @@ +GCC RUNTIME LIBRARY EXCEPTION + +Version 3.1, 31 March 2009 + +Copyright (C) 2009 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +This GCC Runtime Library Exception ("Exception") is an additional +permission under section 7 of the GNU General Public License, version +3 ("GPLv3"). It applies to a given file (the "Runtime Library") that +bears a notice placed by the copyright holder of the file stating that +the file is governed by GPLv3 along with this Exception. + +When you use GCC to compile a program, GCC may combine portions of +certain GCC header files and runtime libraries with the compiled +program. The purpose of this Exception is to allow compilation of +non-GPL (including proprietary) programs to use, in this way, the +header files and runtime libraries covered by this Exception. + +0. Definitions. + +A file is an "Independent Module" if it either requires the Runtime +Library for execution after a Compilation Process, or makes use of an +interface provided by the Runtime Library, but is not otherwise based +on the Runtime Library. + +"GCC" means a version of the GNU Compiler Collection, with or without +modifications, governed by version 3 (or a specified later version) of +the GNU General Public License (GPL) with the option of using any +subsequent versions published by the FSF. + +"GPL-compatible Software" is software whose conditions of propagation, +modification and use would permit combination with GCC in accord with +the license of GCC. + +"Target Code" refers to output from any compiler for a real or virtual +target processor architecture, in executable form or suitable for +input to an assembler, loader, linker and/or execution +phase. Notwithstanding that, Target Code does not include data in any +format that is used as a compiler intermediate representation, or used +for producing a compiler intermediate representation. + +The "Compilation Process" transforms code entirely represented in +non-intermediate languages designed for human-written code, and/or in +Java Virtual Machine byte code, into Target Code. Thus, for example, +use of source code generators and preprocessors need not be considered +part of the Compilation Process, since the Compilation Process can be +understood as starting with the output of the generators or +preprocessors. + +A Compilation Process is "Eligible" if it is done using GCC, alone or +with other GPL-compatible software, or if it is done without using any +work based on GCC. For example, using non-GPL-compatible Software to +optimize any GCC intermediate representations would not qualify as an +Eligible Compilation Process. + +1. Grant of Additional Permission. + +You have permission to propagate a work of Target Code formed by +combining the Runtime Library with Independent Modules, even if such +propagation would otherwise violate the terms of GPLv3, provided that +all Target Code was generated by Eligible Compilation Processes. You +may then convey such a combination under terms of your choice, +consistent with the licensing of the Independent Modules. + +2. No Weakening of GCC Copyleft. + +The availability of this Exception does not imply any general +presumption that third-party software is unaffected by the copyleft +requirements of the license of GCC. diff --git a/docs/licenses/Retro68.txt b/docs/licenses/Retro68.txt new file mode 100644 index 0000000..20d40b6 --- /dev/null +++ b/docs/licenses/Retro68.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/docs/licenses/TheUnarchiver.txt b/docs/licenses/TheUnarchiver.txt new file mode 100644 index 0000000..e053fbc --- /dev/null +++ b/docs/licenses/TheUnarchiver.txt @@ -0,0 +1,528 @@ +This program, "The Unarchiver", its accompanying libraries, "XADMaster" +and "UniversalDetector", and the various smaller utility programs, such +as "unar" and "lsar", are distributed under the GNU Lesser General +Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +"UniversalDetector" is also available under other licenses, such as the +Mozilla Public License. Please refer to the files in its subdirectory +for further information. + +The GNU Lesser General Public License might be too restrictive for some +users of this code. Parts of the code are derived from earlier +LGPL-licensed code and will as such always be bound by the LGPL, but +some parts of the code are developed from scratch by the author of The +Unarchiver, Dag Ågren, and can thus be made available under a more +permissive license. For simplicity, everything is currently licensed +under the LGPL, but if you are interested in using any code from this +project under another license, please contact the author for further +information. + + - Dag Ågren, + + + +----------------------------------------------------------------------- + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + diff --git a/docs/stuffit/Stuffit.md b/docs/stuffit/Stuffit.md new file mode 100644 index 0000000..daa6013 --- /dev/null +++ b/docs/stuffit/Stuffit.md @@ -0,0 +1,3 @@ +# Stuffit specifications + +TODO. diff --git a/docs/stuffit/Stuffit_v1.md b/docs/stuffit/Stuffit_v1.md new file mode 100644 index 0000000..e3c1023 --- /dev/null +++ b/docs/stuffit/Stuffit_v1.md @@ -0,0 +1,50 @@ +# Stuffit format + +All intergers are big-endians. + + +## Archive header + +| **Offset** | **Length** | **Contents** | +|:-----------|:-----------|:-------------| +| 00 | Word | Magic number 1 (see below) | +| 04 | Half | Number of entries in root directory | +| 06 | Word | Total size of archive | +| 10 | Word | Magic number 2 (always `0x724c6175`) | +| 14 | Byte | Version | +| 15 | Byte | Unknown | +| 16 | Word | Header size (if version not `1`) | +| 20 | Half | CRC-16 of header | + +Magic number 1 must be one of the following values: `SIT!`, `ST46`, `ST50`, +`ST60`, `ST65`, `STin`, `STi2`, `STi3`, `STi4`. + + +## File / folder header + +| **Offset** | **Length** | **Contents** | +|:-----------|:-----------|:-------------| +| 000 | Byte | Resource fork compression method | +| 001 | Byte | Data fork compression method | +| 002 | Byte | File name length (in range 1-31) | +| 003 | 63 bytes | File name (remaning bytes are zero) | +| 066 | Word | Mac OS file type | +| 070 | Word | Mac OS file creator | +| 074 | Half | Mac OS Finder flags | +| 076 | Word | Creation date (Mac OS format) | +| 080 | Word | Modification date (Mac OS format) | +| 084 | Word | Resource fork uncompressed length | +| 088 | Word | Data fork uncompressed length | +| 092 | Word | Resource fork compressed length | +| 096 | Word | Data fork compressed length | +| 100 | Half | Resource fork CRC-16 | +| 102 | Half | Data fork CRC-16 | +| 104 | 6 bytes | Unknown | +| 110 | Half | Header CRC-16 | + + +--------------------------- + +**Sources** + +https://github.com/mietek/theunarchiver/wiki/StuffItFormat diff --git a/docs/stuffit/Stuffit_v5.md b/docs/stuffit/Stuffit_v5.md new file mode 100644 index 0000000..0bed856 --- /dev/null +++ b/docs/stuffit/Stuffit_v5.md @@ -0,0 +1,86 @@ +# Stuffit 5 format + +All intergers are big-endians. + + +## Archive header + +| **Offset** | **Length** | **Contents** | +|:-----------|:-----------|:-------------| +| 000 | 80 bytes | Magic string (always `StuffIt (c)1997-???? Aladdin Systems, Inc., http://www.aladdinsys.com/StuffIt/` followed by `0x0D` `0x0A`, where characters marked `?` can vary) | +| 080 | Half | Unknown | +| 082 | Byte | Version (always `5`) | +| 083 | Byte | Flags (`0x80` = encrypted) | +| 084 | Word | Total size of archive | +| 088 | Word | Unknown | +| 092 | Half | Number of entries in root directory | +| 094 | Word | Offset of first entry in root directory | +| 098 | Half | Header CRC-16? | +| 100 | ? | Unknown data until first entry | + + +## Entry header + +### Base header for file and folder + +Both file and folder headers start with the following header. + +| **Offset** | **Length** | **Contents** | +|:-----------|:-----------|:-------------| +| 00 | Word | Magic number (always `0xA5A5A5A5`) | +| 04 | Byte | Version | +| 05 | Byte | Unknown (but certainly `0x00`) | +| 06 | Half | Header size | +| 08 | Byte | Unknown | +| 09 | Byte | Entry flags/type | +| 10 | Word | Creation date (Mac OS format) | +| 14 | Word | Modification date (Mac OS format) | +| 18 | Word | Offset of previous entry | +| 22 | Word | Offset of next entry | +| 26 | Word | Offset of directory entry | +| 30 | Half | Name size (called **N** after) | +| 32 | Half | Header CRC-16 | + + +### File specific header + +A file (when `flags` doesn't contain `0x40`) continues with the following header. + +| **Offset** | **Length** | **Contents** | +|:-----------|:-----------|:-------------| +| 34 | Word | Data fork uncompressed length | +| 38 | Word | Data fork compressed length | +| 42 | Half | Data fork CRC-16 (Set to zero for method 15) | +| 44 | Half | Unknown | +| 46 | Byte | Data fork compression method (only `0`, `13` or `15`) | +| 47 | Byte | Password data lenght (called **M** after) | +| 48 | **M** | Password information | +| 48+**M** | **N** | Filename | +| 48+**M**+**N** | **K** | Comment size (called **K** after) | +| 48 | **N** | Filename | +| 48 | **N** | Filename | + + +### Folder specific header + +A folder (when `flags` contains `0x40`) continues with the following header. + +| **Offset** | **Length** | **Contents** | +|:-----------|:-----------|:-------------| +| 34 | Word | Offset of first entry in folder | +| 38 | Word | Size of complete directory | +| 42 | Word | Unknown | +| 46 | Half | Number of files in folder | +| 48 | **N** | Folder name | +| 48+**N** | ? | Unknown data until next entry | + +Note: if the offset of first entry is `0xFFFFFFFF`, this folder must be skiped +after reading 48 bytes of header (the usefulness of these folders is unclear). + + +--------------------------- + +**Sources** + +https://github.com/mietek/theunarchiver/wiki/StuffIt5Format +https://github.com/mietek/theunarchiver/blob/master/XADMaster/XADStuffIt5Parser.m#L29 diff --git a/src/commands.cc b/src/commands.cc new file mode 100644 index 0000000..81d2336 --- /dev/null +++ b/src/commands.cc @@ -0,0 +1,115 @@ +/* + +Run the selected command from the CLI. + +Copyright (C) 2019, Guillaume Gonnet + +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 "commands.h" +#include "formats/formats.h" +#include "disk/disk.h" + +#include + +namespace maconv { + + + +// Run convert "c" command. +void RunConvertCommand(std::string &input, std::string &output, std::string + &format) +{ + // Read input path given in argument. + if (!Path(input).is_file()) + StopOnError("input file doesn't exist"); + + // Unpack the input file. + auto u = UnPackLocalFile(input); + + + // If format is not present: guess output format. + bool no_out = output.empty() || Path(output).is_directory(); + if (format.empty()) + format = no_out ? "rsrc" : GuessFormatByExtension(output); + + // Get the converter for the selected format. + auto conv = GetConverter(format); + if (conv.type == ConvData::NotFound) + StopOnError("format '%s' doesn't exist", format.c_str()); + + + // Normalise the output directory. + if (no_out && !output.empty()) + output = Path(output).directory().string(); + else + output = Path(input).parent().string(); + + // If output is not present: use input name if single, real name if double. + bool is_double = (conv.type == ConvData::Double); + if (no_out) + output += is_double ? u.file.filename : GetFilenameFor(input, conv); + + + // Now save the output file(s). + PackLocalFile(u.file, output, conv); +} + + + +// Run extract "e" command. +void RunExtractCommand(std::string &input, std::string &output, std::string + &res_format) +{ + // Read input path given in argument. + if (!Path(input).is_file()) + StopOnError("input file doesn't exist"); + + // Read ressource format. + prefered_conv = GetConverter(res_format); + if (prefered_conv.type == ConvData::NotFound) + StopOnError("format '%s' doesn't exist", res_format.c_str()); + + // Unpack and extract the input file. + auto u = UnPackLocalFile(input); + if (!ExtractArchiveOrDisk(u, output)) + StopOnError("can't extract input file (unsupported format)"); +} + + + +// Run disk creation "d" command. +void RunDiskCommand(std::string &folder, std::string &output, std::string + &name) +{ + // Read input path given in argument. + auto abs_in = Path(folder).absolute(); + if (!abs_in.is_directory()) + StopOnError("input folder doesn't exist"); + + // If no output name: use directory name. + if (output.empty()) + output = abs_in.trim().string() + ".img"; + + // If no volume name: use directory name. + if (name.empty()) + name = abs_in.trim().filename(); + + disk::PackDiskImage(abs_in.string(), output, name); +} + + + +} // namespace maconv diff --git a/src/commands.h b/src/commands.h new file mode 100644 index 0000000..ed40b03 --- /dev/null +++ b/src/commands.h @@ -0,0 +1,52 @@ +/* + +Run the selected command from the CLI. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include + +namespace maconv { + + +// Is the verbose mode enabled? +extern bool verbose; + + +// Log debug message (if verbose mode is enabled). +extern "C" void LogDebug(const char *fmt, ...); + +// Stop the program on an error. +extern "C" void StopOnError(const char *fmt, ...); + + +// Run convert "c" command. +void RunConvertCommand(std::string &input, std::string &output, + std::string &format); + +// Run extract "e" command. +void RunExtractCommand(std::string &input, std::string &output, + std::string &res_format); + +// Run disk creation "d" command. +void RunDiskCommand(std::string &folder, std::string &output, + std::string &name); + + +} // namespace maconv diff --git a/src/conv/appledouble.cc b/src/conv/appledouble.cc new file mode 100644 index 0000000..cc4960d --- /dev/null +++ b/src/conv/appledouble.cc @@ -0,0 +1,120 @@ +/* + +Convert files from and to Apple Double format. + +Copyright (C) 2019, Guillaume Gonnet + +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 "conv/converters.h" + +namespace maconv { +namespace conv { + + + +// Get the name of the other file. +static bool GetOtherName(const std::string &input, std::string &other) +{ + bool is_res = input.size() >= 4 && input.compare(input.size() - 4, 4, ".rsrc") == 0; + + if (is_res) + other = input.substr(0, input.size() - 4); + else + other = input + ".rsrc"; + + return is_res; +} + + + +static bool IsFileRessource(const std::string &input) +{ + auto filename = Path(input).filename(); + return (filename.substr(0, 2) == "._") || (filename.substr(0, 1) == "%") || + (input.size() >= 4 && input.compare(input.size() - 4, 4, ".rsrc") == 0); +} + + + +static bool GetRessourceFile(const std::string &input) +{ + auto filename = Path(input).filename(); + auto folder = input.substr(0, input.size() - filename.size()); + + // The file given is a ressource file. + bool is_res = + + + if (is_res) { + + } + + // Serach for a ressource file. + std::string other; + if (Path(folder + "._" + filename).is_file()) + other = folder + "._" + filename; + else if (Path(folder + "%" + filename).is_file()) + other = folder + "%" + filename; + else if (Path(input + ".rsrc").is_file()) + other = input + ".rsrc"; + else + return false; +} + + + +// Read and decode a Apple Double file. +bool ReadAppleDouble(fs::FileReader &reader, UnPacked &u) +{ + // If the file given it's a not a ressource file: read the res. + if (!IsFileRessource(reader.filename)) { + IS_COND(GetRessourceFile(u.n2)); + int size = ReadLocalFile(u.n2, u.d2); + + fs::FileReader res_reader {u.d2.get(), size, u.n2}; + IS_COND(IsAppleDouble(res_reader)); + + ReadRessourceInfo(res_reader, u.file); + ReadDataFork(reader, u.file); + } + + // The file given it's a ressource file. + else { + IS_COND(IsAppleDouble(reader)); + ReadRessourceInfo(reader, u.file); + + if (!GetDataFile(u.n2)) return true; + int size = ReadLocalFile(u.n2, u.d2); + + fs::FileReader data_reader {u.d2.get(), size, u.n2}; + ReadDataFork(data_reader, u.file); + } + + return true; +} + + + +// Write a Apple Double file. +void WriteAppleDouble(fs::File &file, const std::string &name) +{ + // TODO. +} + + + +} // namespace maconv +} // namespace conv diff --git a/src/conv/applesingle.cc b/src/conv/applesingle.cc new file mode 100644 index 0000000..229f500 --- /dev/null +++ b/src/conv/applesingle.cc @@ -0,0 +1,177 @@ +/* + +Convert files from and to AppleSingle format. +See docs/formats/AppleSingle.md for more information on this format. + +Copyright (C) 2019, Guillaume Gonnet + +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 "conv/converters.h" + +#include + +namespace maconv { +namespace conv { + + + +// Return true if a file is in AppleSingle format. +bool IsFileAppleSingle(fs::FileReader &reader) +{ + IS_COND(reader.file_size >= 26); + + IS_COND(reader.ReadWordBE() == 0x00051600); + IS_COND(reader.ReadWordBE() == 0x00020000); + + return true; +} + + + +// Extract a single AppleSingle entry. +void ExtractAppleSimpleEntry(fs::FileReader &reader, fs::File &file, uint32_t id, + uint32_t length) +{ + switch (id) { + // Data fork. + case 1: { + file.data = reader.data + reader.Tell(); + file.data_size = length; + } break; + + // Ressource fork. + case 2: { + file.res = reader.data + reader.Tell(); + file.res_size = length; + } break; + + // Ressource fork. + case 3: { + file.filename = reader.ReadString(length); + } break; + + // File creation and modification dates. + case 8: { + file.creation_date = reader.ReadJ2000Date(); + file.modif_date = reader.ReadJ2000Date(); + } break; + + // Finder information. + case 9: { + file.type = reader.ReadWordBE(); + file.creator = reader.ReadWordBE(); + file.flags = reader.ReadHalfBE(); + } break; + } +} + + +// Read and decode an AppleSingle file. +void ReadAppleSingle(fs::FileReader &reader, fs::File &file) +{ + reader.Skip(26); + int num_entries = reader.ReadHalfBE(); + + // Extract each entries. + for (int i = 0; i < num_entries; i++) { + reader.Seek(26 + i * 12); + + uint32_t id = reader.ReadWordBE(); + uint32_t offset = reader.ReadWordBE(); + uint32_t length = reader.ReadWordBE(); + + reader.Seek(offset); + ExtractAppleSimpleEntry(reader, file, id, length); + } + + + // If the filename is not provided: use the input filename. + if (file.filename.empty()) + file.filename = Path(file.filename).filename(); + + // Add missing information. + GetLocalInfo(file.filename, file); +} + + + +// Write an AppleSingle file. +void WriteAppleSingle(fs::File &file, fs::FileWriter &writer) +{ + // Write file header. + writer.WriteWordBE(0x00051600); + writer.WriteWordBE(0x00020000); + writer.Fill(0x0, 16); + writer.WriteHalfBE(5); // Number of entries. + + + // Write filename entry. + uint32_t offset = 24 + 5 * 12; + writer.WriteWordBE(3); // ID. + writer.WriteWordBE(offset); // Offset. + writer.WriteWordBE(file.filename.size()); // Length. + + // Write date entry. + offset += file.filename.size(); + writer.WriteWordBE(3); // ID. + writer.WriteWordBE(offset); // Offset. + writer.WriteWordBE(12); // Length. + + // Write Finder information entry. + offset += 12; + writer.WriteWordBE(9); // ID. + writer.WriteWordBE(offset); // Offset. + writer.WriteWordBE(32); // Length. + + // Write data fork entry. + offset += 32; + writer.WriteWordBE(1); // ID. + writer.WriteWordBE(offset); // Offset. + writer.WriteWordBE(file.data_size); // Length. + + // Write ressource fork entry. + offset += file.data_size; + writer.WriteWordBE(2); // ID. + writer.WriteWordBE(offset); // Offset. + writer.WriteWordBE(file.res_size); // Length. + + + // Write filename. + writer.WriteString(file.filename); + + // Write creation and modification dates. + writer.WriteJ2000Date(file.creation_date); + writer.WriteJ2000Date(file.modif_date); + writer.WriteWordBE(0x80000000); + writer.WriteWordBE(file.modif_date); + + // Write Finder information. + writer.WriteWordBE(file.type); + writer.WriteWordBE(file.creator); + writer.WriteHalfBE(file.flags); + writer.Fill(0x0, 22); + + // Write data fork. + writer.Write(file.data, file.data_size); + + // Wrire ressource fork. + writer.Write(file.res, file.res_size); +} + + + +} // namespace maconv +} // namespace conv diff --git a/src/conv/binhex.cc b/src/conv/binhex.cc new file mode 100644 index 0000000..c9f7c56 --- /dev/null +++ b/src/conv/binhex.cc @@ -0,0 +1,56 @@ +/* + +Convert files from and to BinHex format. +See docs/formats/BinHex.md for more information on this format. + +Copyright (C) 2019, Guillaume Gonnet + +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 "conv/converters.h" + +namespace maconv { +namespace conv { + + + +// Return true if a file is in BinHex format. +bool IsFileBinHex(fs::FileReader &reader) +{ + // TODO. + + return false; +} + + + +// Read and decode a BinHex file. +void ReadBinHex(fs::FileReader &reader, fs::File &file) +{ + // TODO. +} + + + +// Write a BinHex file. +void WriteBinHex(fs::File &file, fs::FileWriter &writer) +{ + // TODO. +} + + + +} // namespace maconv +} // namespace conv diff --git a/src/conv/converters.h b/src/conv/converters.h new file mode 100644 index 0000000..0ce0747 --- /dev/null +++ b/src/conv/converters.h @@ -0,0 +1,64 @@ +/* + +All available converters. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include "fs/file.h" +#include "fs/file_reader.h" +#include "fs/file_writer.h" +#include "formats/formats.h" + +namespace maconv { +namespace conv { + + +// MacBinary format. +bool IsFileMacBinary(fs::FileReader &reader); +void ReadMacBinary(fs::FileReader &reader, fs::File &file); +void WriteMacBinary(fs::File &file, fs::FileWriter &writer); + +// BinHex format. +bool IsFileBinHex(fs::FileReader &reader); +void ReadBinHex(fs::FileReader &reader, fs::File &file); +void WriteBinHex(fs::File &file, fs::FileWriter &writer); + +// Apple Single format. +bool IsFileAppleSingle(fs::FileReader &reader); +void ReadAppleSingle(fs::FileReader &reader, fs::File &file); +void WriteAppleSingle(fs::File &file, fs::FileWriter &writer); + + +// Apple Double format. +bool ReadAppleDouble(fs::FileReader &reader, UnPacked &u); +void WriteAppleDouble(fs::File &file, const std::string &name); + +// RSRC format (alias no format). +bool ReadRsrc(fs::FileReader &reader, UnPacked &u); +void WriteRsrc(fs::File &file, const std::string &name); +void WriteOnlyData(fs::File &file, const std::string &name); + + +// Extract a single AppleSingle entry. +void ExtractAppleSimpleEntry(fs::FileReader &reader, fs::File &file, uint32_t id, + uint32_t length); + + +} // namespace maconv +} // namespace conv diff --git a/src/conv/macbinary.cc b/src/conv/macbinary.cc new file mode 100644 index 0000000..5259f1a --- /dev/null +++ b/src/conv/macbinary.cc @@ -0,0 +1,207 @@ +/* + +Convert files from and to MacBinary format. +See docs/formats/MacBinary.md for more information on this format. + +Copyright (C) 2019, Guillaume Gonnet + +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 "conv/converters.h" +#include "utils/buffer_stream.h" +#include "commands.h" + +namespace maconv { +namespace conv { + + + +// Precalculed CRC 16 table. +static uint16_t kCrc16Table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; + + +// Calculate CRC (Cyclic Redundancy Check) for some data. +static uint16_t CalculateCRC(uint8_t *data, uint32_t size) +{ + uint16_t crc = 0; + for (uint8_t *end = data + size; data != end; data++) + crc = (crc << 8) ^ kCrc16Table[(*data ^ (crc >> 8)) & 0x00FF]; + + return crc; +} + + + +// Return true if a file is in MacBinary format. +bool IsFileMacBinary(fs::FileReader &reader) +{ + IS_COND(reader.file_size >= 128); + IS_COND(reader.ReadByte() == 0x0); + + auto byte = reader.ReadByte(); + IS_COND(byte > 0 && byte < 64); + IS_COND(reader.ReadWordBE() > 0x1F000000); + + reader.Skip(68); // Byte #74 + IS_COND(reader.ReadByte() == 0x0); + reader.Skip(7); // Byte #82 + IS_COND(reader.ReadByte() == 0x0); + + reader.Skip(39); // Byte #122 + auto half = reader.ReadHalfBE(); + + IS_COND(half == 0 || half == 0x8181 || half == 0x8281); + return true; +} + + + +// Read and decode a MacBinary file. +void ReadMacBinary(fs::FileReader &reader, fs::File &file) +{ + reader.Skip(1); + + // Read name of the file. + uint8_t filelength = reader.ReadByte(); + file.filename = reader.ReadString(filelength); + reader.Skip(63 - filelength); + + // Read file type, creator and Finder flags. + file.type = reader.ReadWordBE(); + file.creator = reader.ReadWordBE(); + file.flags = reader.ReadByte() << 8; + + // Read window information. + reader.Skip(1); + reader.Skip(4); // TODO: read file positions within its window. + reader.Skip(4); // TODO: read folder ID and protected flags. + + // Read size of forks. + file.data_size = reader.ReadWordBE(); + file.res_size = reader.ReadWordBE(); + + // Read creation and modification dates. + file.creation_date = reader.ReadMacDate(); + file.modif_date = reader.ReadMacDate(); + + // Read second part of Finder flags. + reader.Skip(2); + file.flags |= reader.ReadByte(); + reader.Skip(22); + + // Assume that CRC is correct. + + // Set data and resource forks. + file.data = reader.data + 128; + file.res = reader.data + 128 + ((file.data_size + 127) & -128); +} + + + +// Write a MacBinary file. +void WriteMacBinary(fs::File &file, fs::FileWriter &base) +{ + utils::BufferStreamBuf header_buf {124}; + std::ostream header_stream {&header_buf}; + fs::FileWriter writer {header_stream}; + + // Write filename. + writer.WriteByte(0x0); + writer.WriteByte(file.filename.size()); + writer.WriteString(file.filename); + writer.Fill(0x0, 63 - file.filename.size()); + + // Write file type, creator and Finder flags. + writer.WriteWordBE(file.type); + writer.WriteWordBE(file.creator); + writer.WriteByte(file.flags >> 8); + + // Write window information. + writer.Fill(0x0, 1); + writer.Fill(0x0, 4); // TODO: write file positions within its window. + writer.Fill(0x0, 4); // TODO: write folder ID and protected flags. + + // Write size of ressource and data forks. + writer.WriteWordBE(file.data_size); + writer.WriteWordBE(file.res_size); + + // Write creation and modification dates. + writer.WriteMacDate(file.creation_date); + writer.WriteMacDate(file.modif_date); + + // Write second part of Finder flags. + writer.Fill(0x0, 2); + writer.WriteByte(file.flags & 0xFF); + + // Write MacBinray III magic strings/numbers. + writer.WriteString("mBIN"); + writer.Fill(0x0, 16); + writer.WriteByte(130); + writer.WriteByte(129); + + // Calculate CRC and write the header. + uint8_t *header_data = header_buf.buffer.get(); + base.Write(header_data, 124); + base.WriteHalfBE(CalculateCRC(header_data, 124)); + + // Write data fork. + base.Fill(0x0, 2); + base.Write(file.data, file.data_size); + + // Write ressource fork. + base.Fill(0x7F, ((file.data_size + 127) & -128) - file.data_size); + base.Write(file.res, file.res_size); + + // Write remaning padding. + base.Fill(0x7F, ((file.res_size + 127) & -128) - file.res_size); +} + + + +} // namespace maconv +} // namespace conv diff --git a/src/conv/rsrc.cc b/src/conv/rsrc.cc new file mode 100644 index 0000000..ffe0dac --- /dev/null +++ b/src/conv/rsrc.cc @@ -0,0 +1,131 @@ +/* + +Convert files from and to rsrc (raw) format. + +Copyright (C) 2019, Guillaume Gonnet + +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 "conv/converters.h" + +#include + +namespace maconv { +namespace conv { + + + +// Get the name of the other file. +static bool GetOtherName(const std::string &input, std::string &other) +{ + bool is_res = input.size() >= 4 && input.compare(input.size() - 4, 4, ".rsrc") == 0; + + if (is_res) + other = input.substr(0, input.size() - 4); + else + other = input + ".rsrc"; + + return is_res; +} + + + +// Read "rsrc" from two files (data and ressource). +static void ReadRsrcDouble(UnPacked &u, const std::string &other, bool is_res) +{ + uint32_t size = ReadLocalFile(other, u.d2); + + if (is_res) { + u.file.data = u.d2.get(); + u.file.data_size = size; + } else { + u.file.res = u.d2.get(); + u.file.res_size = size; + } +} + + +// Read rsrc (raw) file(s). +bool ReadRsrc(fs::FileReader &reader, UnPacked &u) +{ + u.file.Reset(); + + // Get the name of the other file to read. + std::string other; + bool is_res = GetOtherName(reader.filename, other); + + // Read a raw file with one or two files. + bool is_double = Path(other).is_file(); + if (is_double) + ReadRsrcDouble(u, other, is_res); + + // Set input fork size. + if (is_res) { + u.file.filename = Path(other).filename(); + u.file.res = u.d1.get(); + u.file.res_size = reader.file_size; + } else { + u.file.filename = Path(reader.filename).filename(); + u.file.data = u.d1.get(); + u.file.data_size = reader.file_size; + } + + // Find file information from the local file. + u.file.is_raw = true; + GetLocalInfo(is_double && is_res ? other : reader.filename, u.file); + return true; +} + + + +// Write a single fork into a file. +static void WriteFork(fs::File &file, const std::string &name, uint8_t *data, + uint32_t length) +{ + std::ofstream fstream {name, std::ios::binary}; + + fstream.write(reinterpret_cast(data), length); + fstream.close(); + + SetLocalInfo(file, name, file.res == data); +} + + +// Write raw (rsrc) file(s). +void WriteRsrc(fs::File &file, const std::string &name) +{ + // Get the name of the other file to write. + std::string other; + bool is_res = GetOtherName(name, other); + + // Write forks (write only if size is > 0). + if (file.data_size) + WriteFork(file, is_res ? other : name, file.data, file.data_size); + if (file.res_size) + WriteFork(file, is_res ? name : other, file.res, file.res_size); +} + + +// Write only the data fork (if exists). +void WriteOnlyData(fs::File &file, const std::string &name) +{ + file.res_size = 0; + WriteRsrc(file, name); +} + + + +} // namespace maconv +} // namespace conv diff --git a/src/disk/disk.h b/src/disk/disk.h new file mode 100644 index 0000000..7981603 --- /dev/null +++ b/src/disk/disk.h @@ -0,0 +1,44 @@ +/* + +Extract and pack disk images. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include "fs/file.h" +#include "fs/file_reader.h" +#include "formats/formats.h" + +namespace maconv { +namespace disk { + + +// Is a file a disk file? +bool IsFileDisk(const std::string &name); + +// Extract a disk file. +void ExtractDisk(UnPacked &u, const std::string &out_folder); + + +// Pack files into a single disk image. +void PackDiskImage(const std::string &folder, const std::string &out, + const std::string &volname); + + +} // namespace disk +} // namespace maconv diff --git a/src/disk/extract.cc b/src/disk/extract.cc new file mode 100644 index 0000000..1bf0e27 --- /dev/null +++ b/src/disk/extract.cc @@ -0,0 +1,157 @@ +/* + +Extract a disk image. + +Copyright (C) 2019, Guillaume Gonnet + +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 "disk/disk.h" +#include "commands.h" + +#include +#include + +#include +#include +#include + +namespace maconv { +namespace disk { + + + +// Extract a single fork. +static void ExtractFork(hfsfile *hfile, const hfsdirent &ent, fs::File &file, + bool is_res) +{ + // Allocate buffer. + int size = is_res ? ent.u.file.rsize : ent.u.file.dsize; + auto buffer = std::make_unique(size); + + // Read the data. + hfs_setfork(hfile, is_res ? 1 : 0); + if (hfs_read(hfile, buffer.get(), size) != size) + StopOnError("can't read %s (%d) from HFS disk", file.filename.c_str(), is_res); + + // Fill file information. + if (is_res) { + file.res = buffer.get(); + file.res_size = size; + } else { + file.data_size = size; + file.data = buffer.get(); + } + + // Save the buffer to the memory pool. + file.mem_pool.push_back(std::move(buffer)); +} + + +// Extract a file from a disk. +static void ExtractFile(Path &localp, hfsvol *vol, const hfsdirent &ent) +{ + Path::makedirs(localp); + fs::File file; + + // Extract file information. + file.Reset(); + file.type = d_getsl((const unsigned char *)ent.u.file.type); + file.creator = d_getsl((const unsigned char *)ent.u.file.creator); + file.flags = ent.fdflags; + file.filename = ent.name; + file.creation_date = ent.crdate; + file.modif_date = ent.mddate; + + // Extract the two forks. + hfsfile *hfile = hfs_open(vol, ent.name); + ExtractFork(hfile, ent, file, false); + ExtractFork(hfile, ent, file, true); + hfs_close(hfile); + + // Save the file. + auto out = Path::join(localp, GetFilenameFor(ent.name, prefered_conv)); + PackLocalFile(file, out.string(), prefered_conv); +} + + + +// Extract a directory from the disk. +static void ExtractDirectory(Path localp, hfsvol *vol, unsigned long id) +{ + unsigned long current = hfs_getcwd(vol); + hfs_setcwd(vol, id); + + hfsdir *dir = hfs_opendir(vol, ":"); + hfsdirent ent; + + while (hfs_readdir(dir, &ent) != -1) { + if (ent.fdflags & HFS_FNDR_ISINVISIBLE) + continue; + + if (ent.flags & HFS_ISDIR) + ExtractDirectory(Path::join(localp, ent.name), vol, ent.cnid); + else + ExtractFile(localp, vol, ent); + } + + hfs_closedir(dir); + hfs_setcwd(vol, current); +} + + + +// Extract a disk file. +static void ExtractDiskFrom(const std::string &name, const std::string &out) +{ + hfsvol *vol = hfs_mount(name.c_str(), 0, HFS_MODE_RDONLY); + ExtractDirectory(out, vol, HFS_CNID_ROOTDIR); + hfs_umount(vol); +} + + +// Extract a disk file. +void ExtractDisk(UnPacked &u, const std::string &out_folder) +{ + // If it's not a "raw" file: extract the file directly. + if (u.file.is_raw) + ExtractDiskFrom(u.n1, out_folder); + + // Else, save the data fork to a temporary file first and then extract. + char dirname[] = "/tmp/maconv.XXXXXX"; + if (mkdtemp(dirname) == nullptr) + StopOnError("can't create temporary folder for HFS disk"); + + std::string name = std::string {dirname} + "/disk.dsk"; + PackLocalFile(u.file, name, GetConverter("data")); + ExtractDiskFrom(name, out_folder); + + std::remove(name.c_str()); + rmdir(dirname); +} + + + +// Is a file a disk file? +bool IsFileDisk(const std::string &name) +{ + auto ext = Path(name).extension(); + return ext == "dsk" || ext == "img"; +} + + + +} // namespace disk +} // namespace maconv diff --git a/src/disk/pack.cc b/src/disk/pack.cc new file mode 100644 index 0000000..e87619b --- /dev/null +++ b/src/disk/pack.cc @@ -0,0 +1,170 @@ +/* + +Pack files into a single disk image. + +Copyright (C) 2019, Guillaume Gonnet + +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 "disk/disk.h" +#include "commands.h" + +#include +#include + +#include +#include +#include +#include + +namespace maconv { +namespace disk { + + +// Type for seen files. +using SeenList = std::unordered_set; + +// Pack a directory into the disk. +static void PackDirectory(const Path &localp, hfsvol *vol); + + + +// Write a single fork to an HFS file. +static void WriteFork(hfsfile *hfile, fs::File &file, bool is_res) +{ + int size = is_res ? file.res_size : file.data_size; + uint8_t *data = is_res ? file.res : file.data; + + hfs_setfork(hfile, is_res ? 1 : 0); + hfs_write(hfile, data, size); +} + + +// Pack a file into the disk. +static void PackFile(const Path &file, hfsvol *vol, SeenList &seen) +{ + // Unpack the local file. + UnPacked u = UnPackLocalFile(file.string(), false); + + // Insert file name(s) to seen list. + seen.insert(u.n1); + if (!u.n2.empty()) seen.insert(u.n2); + + + // Sanitize file name, type and creator. + auto filename = u.file.filename.substr(0, 31); + std::replace(filename.begin(), filename.end(), ':', '_'); + + char type[5] = {0}, creator[5] = {0}; + d_putsl((unsigned char *)type, u.file.type); + d_putsl((unsigned char *)creator, u.file.creator); + + // Create the file on the disk and write forks. + hfsfile *hfile = hfs_create(vol, filename.c_str(), type, creator); + if (hfile == nullptr) + StopOnError("can't create HFS file %s", filename.c_str()); + + WriteFork(hfile, u.file, false); + WriteFork(hfile, u.file, true); + + + // Set other attributes. + hfsdirent ent; + hfs_fstat(hfile, &ent); + + ent.crdate = u.file.creation_date; + ent.mddate = u.file.modif_date; + + ent.fdflags = u.file.flags; + ent.fdflags &= ~HFS_FNDR_ISINVISIBLE; + + hfs_fsetattr(hfile, &ent); + hfs_close(hfile); +} + + + +// Create a directory on the HFS disk and pack it. +static void CreateAndPackDirectory(const Path &localp, hfsvol *vol) +{ + unsigned long current = hfs_getcwd(vol); + + auto dirname = Path(localp).trim().filename().substr(0, 31); + std::replace(dirname.begin(), dirname.end(), ':', '_'); + + if (hfs_mkdir(vol, dirname.c_str()) != 0) + StopOnError("can't create HFS folder %s", dirname.c_str()); + hfs_chdir(vol, dirname.c_str()); + + PackDirectory(localp, vol); + hfs_setcwd(vol, current); +} + + +// Pack a directory into the disk. +static void PackDirectory(const Path &localp, hfsvol *vol) +{ + SeenList seen; + + for (auto &file : Path::listdir(localp)) { + if (seen.count(file.string()) == 1) continue; + + if (file.is_file()) + PackFile(file, vol, seen); + else if (file.is_directory()) + CreateAndPackDirectory(file, vol); + } +} + + + +// Create a new file disk and mount it. +static hfsvol *CreateAndMountNewDisk(const std::string &filename, + const std::string &volname) +{ + // Sanitize the volume name. + auto clean_name = volname.substr(0, 27); + std::replace(clean_name.begin(), clean_name.end(), ':', '_'); + + // Truncate the disk file. + int size = 20971520; + std::ofstream(filename, std::ios::binary | std::ios::trunc) + .seekp(size-1).put(0); + + // Create and mount the file. + if (hfs_format(filename.c_str(), 0, 0, clean_name.c_str(), 0, NULL) != 0) + StopOnError("can't format disk file to HFS"); + + hfsvol *vol = hfs_mount(filename.c_str(), 0, HFS_MODE_RDWR); + if (vol == nullptr) + StopOnError("can't mount output disk image"); + + return vol; +} + + +// Pack files into a single disk image. +void PackDiskImage(const std::string &folder, const std::string &out, + const std::string &volname) +{ + hfsvol *vol = CreateAndMountNewDisk(out, volname); + PackDirectory(folder, vol); + hfs_umount(vol); +} + + + +} // namespace disk +} // namespace maconv diff --git a/src/formats/file_signature.cc b/src/formats/file_signature.cc new file mode 100644 index 0000000..a888864 --- /dev/null +++ b/src/formats/file_signature.cc @@ -0,0 +1,140 @@ +/* + +Macintosh file signatures. + +Copyright (C) 2019, Guillaume Gonnet + +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 "formats/file_signature.h" + +#include + +namespace maconv { +namespace utils { + + +// Create a new entry in the "Unix-to-Mac" array. +#define S(type, creator, mac, helps...) \ + SignToMac(SignToMac::type, creator, mac, (const char *[])helps, \ + sizeof((const char *[])helps) / sizeof(const char *)) + + +// File signature array. +SignToMac signs_to_mac[] = { + S(Extension, "TVOD", "Mp3 ", { ".mp3" }), + S(FileCmd, "TVOD", "Midi", { "Standard MIDI" }), + S(FileCmd, "TVOD", "WAVE", { "WAVE audio" }), + + S(FileCmd, "CARO", "PDF ", { "PDF document" }), + S(FileCmd, "ZIP ", "ZIP ", { "Zip archive data" }), + S(Extension, "Gzip", "Gzip", { ".tar.gz", ".tgz" }), + S(FileCmd, "TARF", "TARF", { "tar archive" }), + + S(FileCmd, "8BIM", "JPEG", { "JPEG image data" }), + S(FileCmd, "8BIM", "PNGf", { "PNG image data" }), + S(FileCmd, "8BIM", "BMPf", { "PC bitmap" }), + S(FileCmd, "8BIM", "TIFF", { "TIFF image data" }), + S(FileCmd, "8BIM", "GIFf", { "GIF image data" }), + + S(Extension, "PPT3", "PPT3", { ".ppt", ".pptx" }), + S(Extension, "MSWD", "W8BN", { ".doc", ".docs" }), + S(Extension, "XCEL", "XLS ", { ".xls", ".xlsx" }), + + // Else: UNKN, TEXT +}; + + + +// File signature array. +SignToUnix signs_to_unix[] = { + +}; + + + +// Call Unix "file" command on a path. +static std::string CallFileCommand(const std::string &path) +{ + using PosixPtr = std::unique_ptr; + + auto cmd = std::string {"file -d "} + path; + PosixPtr pipe {popen(cmd.c_str(), "r"), pclose}; + if (!pipe) + return nullptr; + + std::array buffer; + std::string result; + + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) + result += buffer.data(); + + return result; +} + + + +// Try matching a file with an entry. +#define MAC_MATCH(type, func, entry, help) \ + case SignToMac::type: if (func(entry, help)) return &entry; break + + +// Try matching a path with the given entry. +static bool UnixMatchExtension(const SignToMac &entry, const std::string &path) +{ + auto pathlen = path.size(); + auto cpath = path.c_str() + pathlen; + + for (int i = 0; i < entry.help_len; i++) { + auto len = strlen(entry.helps[i]); + if (pathlen >= len && strcmp(cpath - len, entry.helps[i]) == 0) + return true; + } + + return false; +} + + +// Try matching a "file" cmd output with the given entry. +static bool UnixMatchFileCmd(const SignToMac &entry, const std::string &output) +{ + for (int i = 0; i < entry.help_len; i++) { + if (strstr(output.c_str(), entry.helps[i]) == nullptr) + return false; + } + + return true; +} + + +// Get the MAC type (and creator) for a specific file. +const SignToMac *GetMacType(const std::string &path) +{ + auto filetxt = CallFileCommand(path); + + for (auto const &entry : signs_to_mac) { + switch (entry.type) { + MAC_MATCH(Extension, UnixMatchExtension, entry, path); + MAC_MATCH(FileCmd, UnixMatchFileCmd, entry, filetxt); + } + } + + return nullptr; +} + + + +} // namespace utils +} // namespace maconv diff --git a/src/formats/file_signature.h b/src/formats/file_signature.h new file mode 100644 index 0000000..60b3d6f --- /dev/null +++ b/src/formats/file_signature.h @@ -0,0 +1,69 @@ +/* + +Macintosh file signatures. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include +#include + +namespace maconv { +namespace utils { + + +// An entry in the "Unix-to-Mac" array. +struct SignToMac { + + // Maching type: extension or file command. + enum Type { Extension, FileCmd } type; + + // Help strings (and count) + int help_len; + const char **helps; + + // MAC creator and type. + const char *creator; + const char *mac; + + constexpr SignToMac(Type t, const char *c, const char *m, const char **hs, int hl) + : type{t}, creator{c}, mac{m}, help_len{hl}, helps{hs} {} +}; + + +// An entry in the "Mac-to-Unix" array. +struct SignToUnix { + const char *mac; + const char *extension; +}; + + +// File signature array. +extern SignToMac signs_to_mac[]; + +// File signature array. +extern SignToUnix signs_to_unix[]; + + + +// Get the MAC type (and creator) for a specific file. +const SignToMac *GetMacType(const std::string &path); + + +} // namespace utils +} // namespace maconv diff --git a/src/formats/formats.cc b/src/formats/formats.cc new file mode 100644 index 0000000..c17c04b --- /dev/null +++ b/src/formats/formats.cc @@ -0,0 +1,124 @@ +/* + +All supported formats for file conversion. + +Copyright (C) 2019, Guillaume Gonnet + +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 "formats.h" +#include "conv/converters.h" +#include "disk/disk.h" +#include "stuffit/stuffit.h" + +#include +#include + +using namespace maconv::conv; +using namespace maconv::disk; +using namespace maconv::stuffit; + +namespace maconv { + + +// Prefered conversion format. +ConvData prefered_conv = { ConvData::NotFound }; + + +// All available converters. +ConvDataSingle formats_single[kNumFormatsSingle] = { + { "macbin", ".bin", IsFileMacBinary, ReadMacBinary, WriteMacBinary }, + { "binhex", ".hqx", IsFileBinHex, ReadBinHex, WriteBinHex }, + { "applesingle", ".as", IsFileAppleSingle, ReadAppleSingle, WriteAppleSingle }, +}; + + +// Format with two files. +ConvDataDouble formats_double[kNumFormatsDouble] = { + // { "appledouble", ReadAppleDouble, WriteAppleDouble }, + { "rsrc", ReadRsrc, WriteRsrc }, + { "data", ReadRsrc, WriteOnlyData } +}; + + + +// Get a format converter from its name. +ConvData GetConverter(const std::string &name) +{ + // Is the format a single format? + for (auto &format : formats_single) { + if (strcmp(format.name, name.c_str()) == 0) + return ConvData { ConvData::Single, static_cast(&format) }; + } + + // Or a double format? + for (auto &format : formats_double) { + if (strcmp(format.name, name.c_str()) == 0) + return ConvData { ConvData::Double, &format }; + } + + return ConvData { ConvData::NotFound }; +} + + + +// Guess a file format by its extension. +std::string GuessFormatByExtension(const std::string &filename) +{ + auto ext = std::string {"."} + Path(filename).extension(); + + // It is a single format? + for (auto &format : formats_single) { + if (strcmp(format.ext, ext.c_str()) == 0) + return format.name; + } + + // Other special formats. + if (ext == ".hex" || ext == ".hcx") + return "binhex"; + return "rsrc"; +} + + + +// Get a filename for a converter. +std::string GetFilenameFor(const std::string &original, ConvData conv) +{ + auto ext = (conv.type == ConvData::Double) ? "" : conv.s->ext; + return Path(original).filename() + ext; +} + + + +// Extract an archive or a disk. +bool ExtractArchiveOrDisk(UnPacked &u, const std::string &output) +{ + fs::FileReader reader {u.file}; + + if (IsFileDisk(u.file.filename)) + ExtractDisk(u, output); + else if (IsFileStuffit1(reader)) + ExtractStuffit1(reader, output); + else if (IsFileStuffit5(reader)) + ExtractStuffit5(reader, output); + else + return false; + + return true; +} + + + +} // namespace maconv diff --git a/src/formats/formats.h b/src/formats/formats.h new file mode 100644 index 0000000..e2e6dd4 --- /dev/null +++ b/src/formats/formats.h @@ -0,0 +1,111 @@ +/* + +All supported formats for file conversion. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include "fs/file.h" +#include "fs/file_reader.h" +#include "fs/file_writer.h" + +#include + +namespace maconv { + + +// Data for unpacked files. +struct UnPacked { + fs::DataPtr d1, d2; // Data from opened files. + fs::File file; // The unpacked file. + std::string n1, n2; // Input file names. +}; + + +// Convertion data with a single input file. +struct ConvDataSingle { + using TestF = bool (*)(fs::FileReader &); + using ReaderF = void (*)(fs::FileReader &, fs::File &); + using WriterF = void (*)(fs::File &, fs::FileWriter &); + + const char *name; // Converter name. + const char *ext; // File extension. + TestF test; + ReaderF read; + WriterF write; +}; + + +// Convertion data with two input files. +struct ConvDataDouble { + using ReaderF = bool (*)(fs::FileReader &reader, UnPacked &u); + using WriterF = void (*)(fs::File &, const std::string &); + + const char *name; // Converter name. + ReaderF read; + WriterF write; +}; + + +// Store data about a convertion format. +struct ConvData { + enum { NotFound, Single, Double } type; + union { void *v; ConvDataSingle *s; ConvDataDouble *d; }; +}; + + + +// Prefered conversion format. +extern ConvData prefered_conv; + +// Single converters (converters that need only one file). +constexpr int kNumFormatsSingle = 3; +extern ConvDataSingle formats_single[kNumFormatsSingle]; + +// Double converters (converters that need two files). +constexpr int kNumFormatsDouble = 2; +extern ConvDataDouble formats_double[kNumFormatsDouble]; + + + +// Get a format converter from its name. +ConvData GetConverter(const std::string &name); + +// Guess a file format by its extension. +std::string GuessFormatByExtension(const std::string &filename); + +// Get a filename for a converter. +std::string GetFilenameFor(const std::string &original, ConvData conv); + + +// Unpack a single file. +bool UnPackSingle(fs::File &file, fs::FileReader &reader); + +// Unpack a local file (recursively). +UnPacked UnPackLocalFile(const std::string &input, bool recurs = true); + + +// Extract an archive or a disk. +bool ExtractArchiveOrDisk(UnPacked &u, const std::string &output); + + +// Pack a local file. +void PackLocalFile(fs::File &file, const std::string &filename, ConvData data); + + +} // namespace maconv diff --git a/src/formats/pack.cc b/src/formats/pack.cc new file mode 100644 index 0000000..e94d72c --- /dev/null +++ b/src/formats/pack.cc @@ -0,0 +1,56 @@ +/* + +Pack a local file. + +Copyright (C) 2019, Guillaume Gonnet + +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 "formats/formats.h" + +#include + +namespace maconv { + + + +// Pack a single file. +static void PackSingleFile(fs::File &file, const std::string &filename, + ConvDataSingle *conv) +{ + std::ofstream fstream {filename, std::ios::binary}; + fs::FileWriter writer {fstream}; + + conv->write(file, writer); +} + + + +// Pack a local file. +void PackLocalFile(fs::File &file, const std::string &filename, ConvData data) +{ + // Create destination directory. + Path::makedirs(Path(filename).parent()); + + // Pack this file in one or two files. + if (data.type == ConvData::Single) + PackSingleFile(file, filename, data.s); + else + data.d->write(file, filename); +} + + + +} // namespace maconv diff --git a/src/formats/unpack.cc b/src/formats/unpack.cc new file mode 100644 index 0000000..fead57a --- /dev/null +++ b/src/formats/unpack.cc @@ -0,0 +1,97 @@ +/* + +Unpack a local file. + +Copyright (C) 2019, Guillaume Gonnet + +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 "formats/formats.h" +#include "commands.h" + +namespace maconv { + + + +// Unpack two files (double format). +static void UnPackDouble(fs::FileReader &reader, UnPacked &u) +{ + for (auto &format : formats_double) { + reader.Seek(0); + + // "read" returns true if the file is actually read. + if (format.read(reader, u)) + return; + } +} + + +// Unpack a single file. +bool UnPackSingle(fs::File &file, fs::FileReader &reader) +{ + for (auto &format : formats_single) { + // Test if the file is in this format (if true, unpack it). + reader.Seek(0); + if (!format.test(reader)) + continue; + + reader.Seek(0); + file.Reset(); + format.read(reader, file); + return true; + } + + return false; +} + + + +// Unpack the first local file. +static UnPacked UnPackFirstLocalFile(const std::string &input) +{ + UnPacked u; + u.n1 = input; + + // Read the input file given. + int size = ReadLocalFile(input, u.d1); + if (size == -1) + StopOnError("can't read input file %s", input.c_str()); + + // Unpack one or two files. + fs::FileReader reader {u.d1.get(), static_cast(size), input}; + if (!UnPackSingle(u.file, reader)) + UnPackDouble(reader, u); // Cannot fail ("rsrc" accepts all files). + + return u; +} + + +// Unpack a local file (recursively). +UnPacked UnPackLocalFile(const std::string &input, bool recurs) +{ + auto u = UnPackFirstLocalFile(input); + + while (recurs) { + fs::FileReader reader {u.file}; + if (!UnPackSingle(u.file, reader)) + break; + } + + return u; +} + + + +} // namespace maconv diff --git a/src/fs/file.cc b/src/fs/file.cc new file mode 100644 index 0000000..cb714bd --- /dev/null +++ b/src/fs/file.cc @@ -0,0 +1,92 @@ +/* + +Store a Macintosh file. + +Copyright (C) 2019, Guillaume Gonnet + +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 "fs/file.h" + +#include +#include +#include +#include + +namespace maconv { + + + +// Reset all data from its file. +void fs::File::Reset() +{ + data = nullptr; + res = nullptr; + data_size = 0; + res_size = 0; + + creation_date = 0; + modif_date = 0; + + creator = 0x63636363; // ???? + type = 0x63636363; // ???? + + filename.clear(); + is_raw = false; +} + + + +// Read data from a local file. +int ReadLocalFile(const std::string &filename, fs::DataPtr &ptr) +{ + std::ifstream file {filename, std::ios::binary}; + if (!file) + return -1; + + // Get file size and allocate this size. + file.seekg(0, std::ios_base::end); + int size = file.tellg(); + ptr = std::make_unique(size); + + // Read the file entirely. + file.seekg(0, std::ios_base::beg); + file.read(reinterpret_cast(ptr.get()), size); + + return size; +} + + + +// Get file infotmation from a local file. +void GetLocalInfo(const std::string &filename, fs::File &file, bool is_res) +{ + // TODO. +} + + + +// Set file infotmation to a local file. +void SetLocalInfo(fs::File &file, const std::string &filename, bool is_res) +{ + auto date = is_res ? file.creation_date : file.modif_date; + + utimbuf buf { date, date }; + utime(filename.c_str(), &buf); +} + + + +} // namespace maconv diff --git a/src/fs/file.h b/src/fs/file.h new file mode 100644 index 0000000..2a81389 --- /dev/null +++ b/src/fs/file.h @@ -0,0 +1,78 @@ +/* + +Store a Macintosh file. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include +#include +#include + +namespace maconv { +namespace fs { + + +// An unique pointer on memory data. +using DataPtr = std::unique_ptr; + + +// Store data about a Macintosh file. +struct File { + + // Reset all data from its file. + void Reset(); + + + uint8_t *data; // Data fork. + uint32_t data_size; // Size of the data fork. + + uint8_t *res; // Ressource fork. + uint32_t res_size; // Size of the ressource fork. + + + std::string filename; // Name of the file. + uint32_t type; // File type (4 chars). + uint32_t creator; // File creator (4 chars). + uint16_t flags; // Finder flags (see docs for more information). + + time_t creation_date; // Creation date of the file (Unix time). + time_t modif_date; // Modification date of the file (Unix file). + + + bool is_raw; // Is this file from "raw" format. + std::vector mem_pool; // Memory pool for storing data. +}; + + +} // namespace fs + + + +// Read data from a local file. +int ReadLocalFile(const std::string &filename, fs::DataPtr &ptr); + + +// Get file infotmation from a local file. +void GetLocalInfo(const std::string &filename, fs::File &file, bool is_res = false); + +// Set file infotmation to a local file. +void SetLocalInfo(fs::File &file, const std::string &filename, bool is_res = false); + + +} // namespace maconv diff --git a/src/fs/file_reader.cc b/src/fs/file_reader.cc new file mode 100644 index 0000000..485dc96 --- /dev/null +++ b/src/fs/file_reader.cc @@ -0,0 +1,124 @@ +/* + +File Reader: helper class for reading data from a file. + +Copyright (C) 2019, Guillaume Gonnet + +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 "fs/file_reader.h" + +#include + +namespace maconv { +namespace fs { + + + +// Constructor (with an istream). +FileReader::FileReader(uint8_t *d, uint32_t fs, std::string fn) + : data{d}, file_size{fs}, filename{std::move(fn)}, + stream_buf{d, fs}, stream{&stream_buf} +{ +} + + +// Constructor (with a MAC file). +FileReader::FileReader(File &file) + : data{file.data}, file_size{file.data_size}, filename{file.filename}, + stream_buf{file.data, file.data_size}, stream{&stream_buf} +{ +} + + + +// Read a single byte. +uint8_t FileReader::ReadByte() +{ + return static_cast(stream.get()); +} + + +// Read a single half (16bits, big endian). +uint16_t FileReader::ReadHalfBE() +{ + uint8_t b[2]; + stream.read(reinterpret_cast(b), 2); + return static_cast(b[1] | (b[0] << 8)); +} + +// Read a single half (16bits, little endian). +uint16_t FileReader::ReadHalfLE() +{ + uint16_t half; + stream.read(reinterpret_cast(&half), 2); + return half; +} + + +// Read a single word (32bits, big endian). +uint32_t FileReader::ReadWordBE() +{ + uint8_t b[4]; + stream.read(reinterpret_cast(b), 4); + return static_cast(b[3] | (b[2] << 8) | (b[1] << 16) | (b[0] << 24)); +} + +// Read a single word (32bits, little endian). +uint32_t FileReader::ReadWordLE() +{ + uint32_t word; + stream.read(reinterpret_cast(&word), 4); + return word; +} + + +// Read a Macintosh date (epoch on January 1, 1904). +time_t FileReader::ReadMacDate() +{ + return d_ltime(ReadWordBE()); +} + +// Read a J2000 date (epoch on January 1, 2000). +time_t FileReader::ReadJ2000Date() +{ + return ReadMacDate() + 3029529600UL; +} + + +// Read a string (read until \0). +std::string FileReader::ReadString() +{ + std::string result; + char c; + + while (c = stream.get(), c != '\0') + result += c; + return result; +} + + +// Read a string of |size|. +std::string FileReader::ReadString(uint32_t size) +{ + std::string result(size, '\0'); + stream.read(&result[0], size); + return result; +} + + + +} // namespace fs +} // namespace maconv diff --git a/src/fs/file_reader.h b/src/fs/file_reader.h new file mode 100644 index 0000000..c469509 --- /dev/null +++ b/src/fs/file_reader.h @@ -0,0 +1,87 @@ +/* + +File Reader: helper class for reading data from a file. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include "fs/file.h" +#include "utils/buffer_stream.h" + +#include +#include +#include + +namespace maconv { +namespace fs { + + +// Helper class for reading data from a file. +struct FileReader { + + FileReader(uint8_t *data, uint32_t size, std::string filename = ""); + FileReader(File &file); + + + // Set the position in the stream. + void Seek(uint32_t pos) { stream.seekg(pos); } + void Seek(uint32_t off, std::ios_base::seekdir dir) { stream.seekg(off, dir); } + + // Get the absolute position. + uint32_t Tell() { return stream.tellg(); } + + // Skip from reading a number of bytes. + void Skip(uint32_t length) { stream.ignore(length); } + + + // Read a single byte. + uint8_t ReadByte(); + + // Read a single short (16bits) (BE = big endian, LE = little endian). + uint16_t ReadHalfBE(); + uint16_t ReadHalfLE(); + + // Read a single word (32bits) (BE = big endian, LE = little endian). + uint32_t ReadWordBE(); + uint32_t ReadWordLE(); + + // Read a Macintosh date or a J2000 date (epoch on January 1, 2000). + time_t ReadMacDate(); + time_t ReadJ2000Date(); + + // Read a string of |size| (if no size, read until \0). + std::string ReadString(); + std::string ReadString(uint32_t size); + + + uint8_t *data; // Input data. + uint32_t file_size; // Total size of the file. + + utils::RawDataStreamBuf stream_buf; // Stream buffer from raw data buffer. + std::istream stream; // Stream for reading data. + std::string filename; // Name of the file to read. +}; + + + +// Test a condtion and return false if the condition is false. +#define IS_COND(cond) if (!(cond)) return false + + +} // namespace fs +} // namespace maconv diff --git a/src/fs/file_writer.cc b/src/fs/file_writer.cc new file mode 100644 index 0000000..7cb3d1c --- /dev/null +++ b/src/fs/file_writer.cc @@ -0,0 +1,121 @@ +/* + +File Reader: helper class for writing data to a file. + +Copyright (C) 2019, Guillaume Gonnet + +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 "fs/file_writer.h" + +#include + +namespace maconv { +namespace fs { + + + +// Constructor. +FileWriter::FileWriter(std::ostream &s) + : stream{s} +{ +} + + + +// Write some bytes. +void FileWriter::Write(uint8_t *bytes, uint32_t length) +{ + stream.write(reinterpret_cast(bytes), length); +} + + +// Fill |length| bytes of |byte|. +void FileWriter::Fill(uint8_t byte, uint32_t length) +{ + for (uint32_t i = 0; i < length; i++) + stream.put(byte); +} + + + +// Write a single byte. +void FileWriter::WriteByte(uint8_t byte) +{ + stream.put(byte); +} + + +// Write a single short (16bits, big endian). +void FileWriter::WriteHalfBE(uint16_t half) +{ + uint8_t b[2] = { + static_cast(half >> 8), + static_cast(half & 0xFF) + }; + + stream.write(reinterpret_cast(b), 2); +} + +// Write a single short (16bits, little endian). +void FileWriter::WriteHalfLE(uint16_t half) +{ + stream.write(reinterpret_cast(&half), 2); +} + + +// Write a single word (32bits, big endian). +void FileWriter::WriteWordBE(uint32_t w) +{ + uint8_t b[4] = { + static_cast(w >> 24), + static_cast((w >> 16) & 0xFF), + static_cast((w >> 8) & 0xFF), + static_cast(w & 0xFF) + }; + + stream.write(reinterpret_cast(b), 4); +} + +// Write a single word (32bits, little endian). +void FileWriter::WriteWordLE(uint32_t word) +{ + stream.write(reinterpret_cast(&word), 4); +} + + +// Write a Macintosh date (epoch on January 1, 1904). +void FileWriter::WriteMacDate(time_t date) +{ + WriteWordLE(d_mtime(date)); +} + +// Write a J2000 date (epoch on January 1, 2000). +void FileWriter::WriteJ2000Date(time_t date) +{ + return WriteMacDate(date - 3029529600UL); +} + + +// Write a string. +void FileWriter::WriteString(const std::string &str) +{ + stream << str; +} + + + +} // namespace fs +} // namespace maconv diff --git a/src/fs/file_writer.h b/src/fs/file_writer.h new file mode 100644 index 0000000..c9efd4b --- /dev/null +++ b/src/fs/file_writer.h @@ -0,0 +1,67 @@ +/* + +File Reader: helper class for writing data to a file. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include +#include + +namespace maconv { +namespace fs { + + +// Helper class for writing data to a file. +struct FileWriter { + + FileWriter(std::ostream &stream); + + + // Write some bytes. + void Write(uint8_t *bytes, uint32_t length); + + // Fill |length| bytes of |byte|. + void Fill(uint8_t byte, uint32_t length); + + + // Write a single byte. + void WriteByte(uint8_t byte); + + // Write a single short (16bits) (BE = big endian, LE = little endian). + void WriteHalfBE(uint16_t half); + void WriteHalfLE(uint16_t half); + + // Write a single word (32bits) (BE = big endian, LE = little endian). + void WriteWordBE(uint32_t word); + void WriteWordLE(uint32_t word); + + // Write a Macintosh date or a J2000 date (epoch on January 1, 2000). + void WriteMacDate(time_t date); + void WriteJ2000Date(time_t date); + + // Write a string. + void WriteString(const std::string &str); + + + std::ostream &stream; // Output stream for writing data. +}; + + +} // namespace fs +} // namespace maconv diff --git a/src/maconv.1 b/src/maconv.1 new file mode 100644 index 0000000..72e2d3e --- /dev/null +++ b/src/maconv.1 @@ -0,0 +1,134 @@ +.TH MACONV 1 "2019-01-04" "v1.0" "maconv" + +.SH NAME +maconv \- Macintosh format converter + +.SH SYNOPSIS +.B "maconv c [options] input-file [output-file]" +.br +.B "maconv e [options] input-file [output-folder]" +.br +.B "maconv d [options] input-folder [output-file]" + + +.SH DESCRIPTION +\fBmaconv\fR(1) is a Linux software that can convert all kinds +of old Macintosh formats, including MacBinary, Stuffit archives and HFS disk +files. + + +.SH OPTIONS +Maconv has three sub-commands: +.BR "c" ", " "e" " and " "d" . +.br +Each sub-command can take the following flags: + +.TP 4 +.B "-h,--help" +Print an help message about \fBMaconv\fR and exit. + +.TP 4 +.B "-v,--verbose" +Enable verbose mode (i.e. log useful information when converting/extracting +files). + +.RE +Some sub-commands take a format argument. This argument must be one of the +following formats: +.BR macbin ", " applesingle ", " rsrc " or " data " (saves only data fork)." + + +.RE +.B "FILE CONVERSION (maconv c)" +.RS 4 +This sub-command converts a file from a format to another. Supported formats are +listed above. The command takes the following arguments: + +.TP 4 +.B "input-file" +The output format. If not provided this format is determined by the output +filename extension. If both this format and the output filename are not provided +the selected format is +.BR rsrc . + +.TP 4 +.B "output-file" +Name of the output file. This argument is optional. By default it's the name of +the input file with another extension for a format that generates only one file +or the "real" name of the file for other formats. + +.TP 4 +.BI "-f,--format" " format" +The output format. If not provided this format is determined by the output +filename extension. If both this format and the output filename are not provided +the selected format is +.BR rsrc . + + +.RE +.B "ARCHIVE EXTRACTION (maconv e)" +.RS 4 +This sub-command extracts a Stuffit archive (version 1 or 5) or an HFS disk image. +The command takes the following arguments: + +.TP 4 +.B "input-file" +The input archive/disk image. + +.TP 4 +.B "output-folder" +The folder in which the archive/disk image will be extracted. This argument is +optional. By default the output folder is +.BR . " (current folder)." + +.TP 4 +.BI "-f,--format" " format" +Format with which extracted files will be saved. By default this format is +.BR rsrc . + + +.RE +.B "DISK CREATION (maconv d)" +.RS 4 +This sub-command creates an HFS disk image from a folder (like a file archiver). +The command takes the following arguments: + +.TP 4 +.B "input-folder" +The input folder. Files in this folder will be added to the HFS disk image. + +.TP 4 +.B "output-file" +Name of the disk image to create. This argument is optional. By default the disk +image has the same name as the input folder plus +.IR .img . + +.TP 4 +.BI "-n,--name" " vol-name" +Name of the volume to create on the disk image. By default it's the base name of +the input folder. + + +.SH EXAMPLES +.TP 4 +.B maconv c file-macbin.bin +Extract both data and ressource forks from +.I file-macbin.bin +(MacBinary format) and save them using +.BR rsrc " format." + +.TP 4 +.B maconv e -f macbin archive.sit +Extract a stuffit archive named +.IR archive.sit . +Extracted files are saved in +.B macbin +format. + + +.SH SEE ALSO +.BR unstuff "(1)," +.BR macunpack "(1)" + +.SH AUTHOR +Guillaume Gonnet diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..37d249d --- /dev/null +++ b/src/main.cc @@ -0,0 +1,137 @@ +/* + +Maconv: Macintosh format converter. + +Copyright (C) 2019, Guillaume Gonnet + +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 "commands.h" + +#include +#include + +using namespace maconv; + + +// Is the verbose mode enabled? +bool maconv::verbose; + + + +// Log debug message (if verbose mode is enabled). +extern "C" void LogDebug(const char *fmt, ...) +{ + if (!verbose) return; + + va_list args; + va_start(args, fmt); + + vfprintf(stderr, fmt, args); + va_end(args); + printf("\n"); +} + + +// Stop the program on an error. +extern "C" void StopOnError(const char *fmt, ...) +{ + va_list args; + fprintf(stderr, "\033[1m\033[31mERROR: "); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + fprintf(stderr, "\033[0m\n"); + exit(1); +} + + + +// Entry point of the application. +int main(int argc, char **argv) +{ + // Create the base command. + CLI::App app {"Maconv: Macintosh format converter"}; + app.require_subcommand(); + + app.add_flag("-v,--verbose", verbose, "Enable verbose mode"); + + + // Convert "c" sub-command. + auto c_app = app.add_subcommand("c", "Convert a file from a format to another"); + + std::string c_input; + c_app->add_option("input", c_input, "Input file to convert") + ->required() + ->type_name(""); + + std::string c_output; + c_app->add_option("output", c_output, "Output converted file (autodetected by default)") + ->type_name(""); + + std::string c_format; + c_app->add_option("-f,--format", c_format, "Target format (autodetected or rsrc by default)") + ->type_name(""); + + + // Extract "e" sub-command. + auto e_app = app.add_subcommand("e", "Extract a Stuffit archive or a disk file"); + + std::string e_input; + e_app->add_option("input", e_input, "Input file to extract") + ->required() + ->type_name(""); + + std::string e_output; + e_app->add_option("output", e_output, "Output folder (current folder by default)") + ->default_val(".") + ->type_name(""); + + std::string e_format; + e_app->add_option("-f,--format", e_format, "Res. format (rsrc by default)") + ->default_val("rsrc") + ->type_name(""); + + + // Disk creation "d" sub-command. + auto d_app = app.add_subcommand("d", "Create an HFS disk file"); + + std::string d_folder; + d_app->add_option("folder", d_folder, "Root of the disk") + ->required() + ->type_name(""); + + std::string d_output; + d_app->add_option("output", d_output, "Output disk file") + ->type_name(""); + + std::string d_name; + d_app->add_option("-n,--name", d_name, "Volume name (Input folder name by default)") + ->type_name(""); + + + // Parse the CLI. + CLI11_PARSE(app, argc, argv); + + // Select the right command to execute. + if (*c_app) + RunConvertCommand(c_input, c_output, c_format); + else if (*e_app) + RunExtractCommand(e_input, e_output, e_format); + else if (*d_app) + RunDiskCommand(d_folder, d_output, d_name); +} diff --git a/src/stuffit/methods.cc b/src/stuffit/methods.cc new file mode 100644 index 0000000..452f121 --- /dev/null +++ b/src/stuffit/methods.cc @@ -0,0 +1,101 @@ +/* + +All Stuffit compression methods. + +Copyright (C) 2019, Guillaume Gonnet + +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 "stuffit/methods.h" +#include "stuffit/methods/rle90.h" +#include "stuffit/methods/compress.h" +#include "stuffit/methods/algorithm13.h" +#include "stuffit/methods/arsenic.h" + +#include +#include + +namespace maconv { +namespace stuffit { + + + +// Get a compression method (from a method number). +CompMethodPtr GetCompressionMethod(uint8_t method) +{ + switch (method) { + case 0: // No compression. + return std::make_unique(); + case 1: // RLE 90 algorithm. + return std::make_unique(); + case 2: // Compress algorithm (LZW). + return std::make_unique(); + case 13: // LZSS and Huffman. + return std::make_unique(); + case 15: // Arsenic algorithm. + return std::make_unique(); + default: + return nullptr; + } +} + + + +// Extract data from the compressed fork. +void CompressionMethod::Extract(const StuffitCompInfo &info, uint8_t *data, + std::vector &mem_pool) +{ + uint32_t capacity = info.size ? info.size : info.comp_size; + this->data = data + info.offset; + this->end = this->data + info.comp_size; + + Initialize(); + std::unique_ptr buffer; + + buffer.reset((uint8_t *)malloc(capacity)); + total_size = 0; + + // Uncompress the data chunk by chunks. + for (uint32_t len = 0; true;) { + len = ReadBytes(buffer.get() + total_size, capacity - total_size); + if (len == -1) + break; + + total_size += len; + if (len == 0 || total_size == capacity) { + capacity = (capacity * 3) / 2; + buffer.reset((uint8_t *)realloc((void *)buffer.release(), capacity)); + } + } + + // Store the uncompressed buffer in the mempool. + uncompressed = buffer.get(); + mem_pool.emplace_back(std::move(buffer)); +} + + + +// Extract data from the compressed fork. +void NoneMethod::Extract(const StuffitCompInfo &info, uint8_t *data, + std::vector &mem_pool) +{ + total_size = info.size ? info.size : info.comp_size; + uncompressed = data + info.offset; +} + + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/methods.h b/src/stuffit/methods.h new file mode 100644 index 0000000..155fb37 --- /dev/null +++ b/src/stuffit/methods.h @@ -0,0 +1,87 @@ +/* + +All Stuffit compression methods. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include "stuffit/stuffit.h" + +#include +#include + +namespace maconv { +namespace stuffit { + + +// Exception when extracting a fork. +struct ExtractException : std::exception { + + ExtractException(std::string what) : msg{what} {} + + const char* what() const noexcept { return msg.c_str(); } + std::string msg; +}; + + + +// A compression method. +struct CompressionMethod { + + virtual ~CompressionMethod() = default; + + + // Read the next bytes. + virtual int32_t ReadBytes(uint8_t *data, uint32_t length) { return -1; } + + // Initialize the algorithm. + virtual void Initialize() {} + + + // Extract data from the compressed fork. + virtual void Extract(const StuffitCompInfo &info, uint8_t *data, + std::vector &mem_pool); + + uint8_t *data; // Compressed data. + uint8_t *end; // End of compressed data. + + uint8_t *uncompressed; // Pointer on t uncompressed data. + uint32_t total_size; // Length of uncompressed data. +}; + + + +// An unique pointer on a compression method. +using CompMethodPtr = std::unique_ptr; + +// Get a compression method (from a method number). +CompMethodPtr GetCompressionMethod(uint8_t method); + + + +// "No compression" method. +struct NoneMethod : CompressionMethod { + + // Extract data from the compressed fork. + void Extract(const StuffitCompInfo &info, uint8_t *data, + std::vector &mem_pool) override; +}; + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/methods/algorithm13.cc b/src/stuffit/methods/algorithm13.cc new file mode 100644 index 0000000..eb91cd5 --- /dev/null +++ b/src/stuffit/methods/algorithm13.cc @@ -0,0 +1,487 @@ +/* + +Stuffit algorithm 13: LZSS and Huffman. + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 "stuffit/methods/algorithm13.h" + +#include +#include + +namespace maconv { +namespace stuffit { + + + +// First code lengths (1). +static const int kFirstCodeLengths_1[321] = { + 4, 5, 7, 8, 8, 9, 9, 9, 9, 7, 9, 9, 9, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 10, 9, 9, 10, 10, 9, 10, 9, 9, 5, 9, 9, 9, 9, 10, + 9, 9, 9, 9, 9, 9, 9, 9, 7, 9, 9, 8, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 8, 9, 9, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 7, 8, 9, 7, 9, 9, 7, 7, 9, 9, 9, 9, 10, 9, 10, 10, 10, 9, + 9, 9, 5, 9, 8, 7, 5, 9, 8, 8, 7, 9, 9, 8, 8, 5, 5, 7, 10, + 5, 8, 5, 8, 9, 9, 9, 9, 9, 10, 9, 9, 10, 9, 9, 10, 10, 10, 10, + 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, 9, 9, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, + 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 10, 10, 9, 10, 10, 10, 10, 10, 10, + 10, 9, 10, 10, 10, 9, 10, 9, 5, 6, 5, 5, 8, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 9, 10, 9, 9, 9, 10, 9, 10, 9, 10, 9, 10, 9, + 10, 10, 10, 9, 10, 9, 10, 10, 9, 9, 9, 6, 9, 9, 10, 9, 5 +}; + +// First code lengths (2). +static const int kFirstCodeLengths_2[321] = { + 4, 7, 7, 8, 7, 8, 8, 8, 8, 7, 8, 7, 8, 7, 9, 8, 8, 8, 9, + 9, 9, 9, 10, 10, 9, 10, 10, 10, 10, 10, 9, 9, 5, 9, 8, 9, 9, 11, + 10, 9, 8, 9, 9, 9, 8, 9, 7, 8, 8, 8, 9, 9, 9, 9, 9, 10, 9, + 9, 9, 10, 9, 9, 10, 9, 8, 8, 7, 7, 7, 8, 8, 9, 8, 8, 9, 9, + 8, 8, 7, 8, 7, 10, 8, 7, 7, 9, 9, 9, 9, 10, 10, 11, 11, 11, 10, + 9, 8, 6, 8, 7, 7, 5, 7, 7, 7, 6, 9, 8, 6, 7, 6, 6, 7, 9, + 6, 6, 6, 7, 8, 8, 8, 8, 9, 10, 9, 10, 9, 9, 8, 9, 10, 10, 9, + 10, 10, 9, 9, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 11, 10, 10, 10, 10, + 10, 10, 10, 11, 10, 11, 10, 10, 9, 11, 10, 10, 10, 10, 10, 10, 9, 9, 10, + 11, 10, 11, 10, 11, 10, 12, 10, 11, 10, 12, 11, 12, 10, 12, 10, 11, 10, 11, + 11, 11, 9, 10, 11, 11, 11, 12, 12, 10, 10, 10, 11, 11, 10, 11, 10, 10, 9, + 11, 10, 11, 10, 11, 11, 11, 10, 11, 11, 12, 11, 11, 10, 10, 10, 11, 10, 10, + 11, 11, 12, 10, 10, 11, 11, 12, 11, 11, 10, 11, 9, 12, 10, 11, 11, 11, 10, + 11, 10, 11, 10, 11, 9, 10, 9, 7, 3, 5, 6, 6, 7, 7, 8, 8, 8, 9, + 9, 9, 11, 10, 10, 10, 12, 13, 11, 12, 12, 11, 13, 12, 12, 11, 12, 12, 13, + 12, 14, 13, 14, 13, 15, 13, 14, 15, 15, 14, 13, 15, 15, 14, 15, 14, 15, 15, + 14, 15, 13, 13, 14, 15, 15, 14, 14, 16, 16, 15, 15, 15, 12, 15, 10 +}; + +// First code lengths (3). +static const int kFirstCodeLengths_3[321] = { + 6, 6, 6, 6, 6, 9, 8, 8, 4, 9, 8, 9, 8, 9, 9, 9, 8, 9, 9, + 10, 8, 10, 10, 10, 9, 10, 10, 10, 9, 10, 10, 9, 9, 9, 8, 10, 9, 10, + 9, 10, 9, 10, 9, 10, 9, 9, 8, 9, 8, 9, 9, 9, 10, 10, 10, 10, 9, + 9, 9, 10, 9, 10, 9, 9, 7, 8, 8, 9, 8, 9, 9, 9, 8, 9, 9, 10, + 9, 9, 8, 9, 8, 9, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, + 9, 8, 8, 9, 8, 9, 7, 8, 8, 9, 8, 10, 10, 8, 9, 8, 8, 8, 10, + 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 9, 7, 9, 9, 10, 10, + 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 9, 9, 10, 10, 10, 10, 10, 10, 10, + 10, 9, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, + 10, 9, 8, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, 9, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 10, 10, 10, + 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 9, 9, 9, 10, 10, 10, 10, 10, 10, + 9, 9, 10, 9, 9, 8, 9, 8, 9, 4, 6, 6, 6, 7, 8, 8, 9, 9, 10, + 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 7, 10, 10, 10, 7, 10, 10, 7, 7, 7, 7, 7, 6, 7, 10, 7, 7, 10, + 7, 7, 7, 6, 7, 6, 6, 7, 7, 6, 6, 9, 6, 9, 10, 6, 10 +}; + +// First code lengths (4). +static const int kFirstCodeLengths_4[321] = { + 2, 6, 6, 7, 7, 8, 7, 8, 7, 8, 8, 9, 8, 9, 9, 9, 8, 8, 9, + 9, 9, 10, 10, 9, 8, 10, 9, 10, 9, 10, 9, 9, 6, 9, 8, 9, 9, 10, + 9, 9, 9, 10, 9, 9, 9, 9, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 10, 10, 9, 7, 7, 8, 8, 8, 8, 9, 9, 7, 8, 9, 10, + 8, 8, 7, 8, 8, 10, 8, 8, 8, 9, 8, 9, 9, 10, 9, 11, 10, 11, 9, + 9, 8, 7, 9, 8, 8, 6, 8, 8, 8, 7, 10, 9, 7, 8, 7, 7, 8, 10, + 7, 7, 7, 8, 9, 9, 9, 9, 10, 11, 9, 11, 10, 9, 7, 9, 10, 10, 10, + 11, 11, 10, 10, 11, 10, 10, 10, 11, 11, 10, 9, 10, 10, 11, 10, 11, 10, 11, + 10, 10, 10, 11, 10, 11, 10, 10, 9, 10, 10, 11, 10, 10, 10, 10, 9, 10, 10, + 10, 10, 11, 10, 11, 10, 11, 10, 11, 11, 11, 10, 12, 10, 11, 10, 11, 10, 11, + 11, 10, 8, 10, 10, 11, 10, 11, 11, 11, 10, 11, 10, 11, 10, 11, 11, 11, 9, + 10, 11, 11, 10, 11, 11, 11, 10, 11, 11, 11, 10, 10, 10, 10, 10, 11, 10, 10, + 11, 11, 10, 10, 9, 11, 10, 10, 11, 11, 10, 10, 10, 11, 10, 10, 10, 10, 10, + 10, 9, 11, 10, 10, 8, 10, 8, 6, 5, 6, 6, 7, 7, 8, 8, 8, 9, 10, + 11, 10, 10, 11, 11, 12, 12, 10, 11, 12, 12, 12, 12, 13, 13, 13, 13, 13, 12, + 13, 13, 15, 14, 12, 14, 15, 16, 12, 12, 13, 15, 14, 16, 15, 17, 18, 15, 17, + 16, 15, 15, 15, 15, 13, 13, 10, 14, 12, 13, 17, 17, 18, 10, 17, 4 +}; + +// First code lengths (5). +static const int kFirstCodeLengths_5[321] = { + 7, 9, 9, 9, 9, 9, 9, 9, 9, 8, 9, 9, 9, 7, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 10, 9, 10, 9, 10, 9, 10, 9, 9, 5, 9, 7, 9, 9, 9, + 9, 9, 7, 7, 7, 9, 7, 7, 8, 7, 8, 8, 7, 7, 9, 9, 9, 9, 7, + 7, 7, 9, 9, 9, 9, 9, 9, 7, 9, 7, 7, 7, 7, 9, 9, 7, 9, 9, + 7, 7, 7, 7, 7, 9, 7, 8, 7, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 7, 8, 7, 7, 7, 8, 8, 6, 7, 9, 7, 7, 8, 7, 5, 6, 9, + 5, 7, 5, 6, 7, 7, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 9, 10, + 10, 10, 9, 9, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 9, 10, 10, 10, 9, 10, 10, 10, 9, 9, 10, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 9, 10, 10, 10, 9, 10, 10, 10, 9, 9, 9, 10, 10, 10, 10, 10, 9, + 10, 9, 10, 10, 9, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, + 10, 9, 10, 9, 10, 9, 10, 10, 9, 5, 6, 8, 8, 7, 7, 7, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 5, 10, 8, 9, 8, 9 +}; + +// First code lengths (global array). +static const int *kFirstCodeLengths[5] = { + kFirstCodeLengths_1, kFirstCodeLengths_2, kFirstCodeLengths_3, + kFirstCodeLengths_4, kFirstCodeLengths_5 +}; + + + +// Second code lengths (1). +static const int kSecondCodeLengths_1[321] = { + 4, 5, 6, 6, 7, 7, 6, 7, 7, 7, 6, 8, 7, 8, 8, 8, 8, 9, 6, + 9, 8, 9, 8, 9, 9, 9, 8, 10, 5, 9, 7, 9, 6, 9, 8, 10, 9, 10, + 8, 8, 9, 9, 7, 9, 8, 9, 8, 9, 8, 8, 6, 9, 9, 8, 8, 9, 9, + 10, 8, 9, 9, 10, 8, 10, 8, 8, 8, 8, 8, 9, 7, 10, 6, 9, 9, 11, + 7, 8, 8, 9, 8, 10, 7, 8, 6, 9, 10, 9, 9, 10, 8, 11, 9, 11, 9, + 10, 9, 8, 9, 8, 8, 8, 8, 10, 9, 9, 10, 10, 8, 9, 8, 8, 8, 11, + 9, 8, 8, 9, 9, 10, 8, 11, 10, 10, 8, 10, 9, 10, 8, 9, 9, 11, 9, + 11, 9, 10, 10, 11, 10, 12, 9, 12, 10, 11, 10, 11, 9, 10, 10, 11, 10, 11, + 10, 11, 10, 11, 10, 10, 10, 9, 9, 9, 8, 7, 6, 8, 11, 11, 9, 12, 10, + 12, 9, 11, 11, 11, 10, 12, 11, 11, 10, 12, 10, 11, 10, 10, 10, 11, 10, 11, + 11, 11, 9, 12, 10, 12, 11, 12, 10, 11, 10, 12, 11, 12, 11, 12, 11, 12, 10, + 12, 11, 12, 11, 11, 10, 12, 10, 11, 10, 12, 10, 12, 10, 12, 10, 11, 11, 11, + 10, 11, 11, 11, 10, 12, 11, 12, 10, 10, 11, 11, 9, 12, 11, 12, 10, 11, 10, + 12, 10, 11, 10, 12, 10, 11, 10, 7, 5, 4, 6, 6, 7, 7, 7, 8, 8, 7, + 7, 6, 8, 6, 7, 7, 9, 8, 9, 9, 10, 11, 11, 11, 12, 11, 10, 11, 12, + 11, 12, 11, 12, 12, 12, 12, 11, 12, 12, 11, 12, 11, 12, 11, 13, 11, 12, 10, + 13, 10, 14, 14, 13, 14, 15, 14, 16, 15, 15, 18, 18, 18, 9, 18, 8 +}; + +// Second code lengths (2). +static const int kSecondCodeLengths_2[321] = { + 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 7, 8, 7, 7, 7, 8, 8, + 8, 8, 9, 8, 9, 8, 9, 9, 9, 7, 9, 8, 8, 6, 9, 8, 9, 8, 9, + 8, 9, 8, 9, 8, 9, 8, 9, 8, 8, 8, 8, 8, 9, 8, 9, 8, 9, 9, + 10, 8, 10, 8, 9, 9, 8, 8, 8, 7, 8, 8, 9, 8, 9, 7, 9, 8, 10, + 8, 9, 8, 9, 8, 9, 8, 8, 8, 9, 9, 9, 9, 10, 9, 11, 9, 10, 9, + 10, 8, 8, 8, 9, 8, 8, 8, 9, 9, 8, 9, 10, 8, 9, 8, 8, 8, 11, + 8, 7, 8, 9, 9, 9, 9, 10, 9, 10, 9, 10, 9, 8, 8, 9, 9, 10, 9, + 10, 9, 10, 8, 10, 9, 10, 9, 11, 10, 11, 9, 11, 10, 10, 10, 11, 9, 11, + 9, 10, 9, 11, 9, 11, 10, 10, 9, 10, 9, 9, 8, 10, 9, 11, 9, 9, 9, + 11, 10, 11, 9, 11, 9, 11, 9, 11, 10, 11, 10, 11, 10, 11, 9, 10, 10, 11, + 10, 10, 8, 10, 9, 10, 10, 11, 9, 11, 9, 10, 10, 11, 9, 10, 10, 9, 9, + 10, 9, 10, 9, 10, 9, 10, 9, 11, 9, 11, 10, 10, 9, 10, 9, 11, 9, 11, + 9, 11, 9, 10, 9, 11, 9, 11, 9, 11, 9, 10, 8, 11, 9, 10, 9, 10, 9, + 10, 8, 10, 8, 9, 8, 9, 8, 7, 4, 4, 5, 6, 6, 6, 7, 7, 7, 7, + 8, 8, 8, 7, 8, 8, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 10, 10, 12, + 11, 11, 12, 12, 11, 12, 12, 11, 12, 12, 12, 12, 12, 12, 11, 12, 11, 13, 12, + 13, 12, 13, 14, 14, 14, 15, 13, 14, 13, 14, 18, 18, 17, 7, 16, 9 +}; + +// Second code lengths (3). +static const int kSecondCodeLengths_3[321] = { + 5, 6, 6, 6, 6, 7, 7, 7, 6, 8, 7, 8, 7, 9, 8, 8, 7, 7, 8, + 9, 9, 9, 9, 10, 8, 9, 9, 10, 8, 10, 9, 8, 6, 10, 8, 10, 8, 10, + 9, 9, 9, 9, 9, 10, 9, 9, 8, 9, 8, 9, 8, 9, 9, 10, 9, 10, 9, + 9, 8, 10, 9, 11, 10, 8, 8, 8, 8, 9, 7, 9, 9, 10, 8, 9, 8, 11, + 9, 10, 9, 10, 8, 9, 9, 9, 9, 8, 9, 9, 10, 10, 10, 12, 10, 11, 10, + 10, 8, 9, 9, 9, 8, 9, 8, 8, 10, 9, 10, 11, 8, 10, 9, 9, 8, 12, + 8, 9, 9, 9, 9, 8, 9, 10, 9, 12, 10, 10, 10, 8, 7, 11, 10, 9, 10, + 11, 9, 11, 7, 11, 10, 12, 10, 12, 10, 11, 9, 11, 9, 12, 10, 12, 10, 12, + 10, 9, 11, 12, 10, 12, 10, 11, 9, 10, 9, 10, 9, 11, 11, 12, 9, 10, 8, + 12, 11, 12, 9, 12, 10, 12, 10, 13, 10, 12, 10, 12, 10, 12, 10, 9, 10, 12, + 10, 9, 8, 11, 10, 12, 10, 12, 10, 12, 10, 11, 10, 12, 8, 12, 10, 11, 10, + 10, 10, 12, 9, 11, 10, 12, 10, 12, 11, 12, 10, 9, 10, 12, 9, 10, 10, 12, + 10, 11, 10, 11, 10, 12, 8, 12, 9, 12, 8, 12, 8, 11, 10, 11, 10, 11, 9, + 10, 8, 10, 9, 9, 8, 9, 8, 7, 4, 3, 5, 5, 6, 5, 6, 6, 7, 7, + 8, 8, 8, 7, 7, 7, 9, 8, 9, 9, 11, 9, 11, 9, 8, 9, 9, 11, 12, + 11, 12, 12, 13, 13, 12, 13, 14, 13, 14, 13, 14, 13, 13, 13, 12, 13, 13, 12, + 13, 13, 14, 14, 13, 13, 14, 14, 14, 14, 15, 18, 17, 18, 8, 16, 10 +}; + +// Second code lengths (4). +static const int kSecondCodeLengths_4[321] = { + 4, 5, 6, 6, 6, 6, 7, 7, 6, 7, 7, 9, 6, 8, 8, 7, 7, 8, 8, + 8, 6, 9, 8, 8, 7, 9, 8, 9, 8, 9, 8, 9, 6, 9, 8, 9, 8, 10, + 9, 9, 8, 10, 8, 10, 8, 9, 8, 9, 8, 8, 7, 9, 9, 9, 9, 9, 8, + 10, 9, 10, 9, 10, 9, 8, 7, 8, 9, 9, 8, 9, 9, 9, 7, 10, 9, 10, + 9, 9, 8, 9, 8, 9, 8, 8, 8, 9, 9, 10, 9, 9, 8, 11, 9, 11, 10, + 10, 8, 8, 10, 8, 8, 9, 9, 9, 10, 9, 10, 11, 9, 9, 9, 9, 8, 9, + 8, 8, 8, 10, 10, 9, 9, 8, 10, 11, 10, 11, 11, 9, 8, 9, 10, 11, 9, + 10, 11, 11, 9, 12, 10, 10, 10, 12, 11, 11, 9, 11, 11, 12, 9, 11, 9, 10, + 10, 10, 10, 12, 9, 11, 10, 11, 9, 11, 11, 11, 10, 11, 11, 12, 9, 10, 10, + 12, 11, 11, 10, 11, 9, 11, 10, 11, 10, 11, 9, 11, 11, 9, 8, 11, 10, 11, + 11, 10, 7, 12, 11, 11, 11, 11, 11, 12, 10, 12, 11, 13, 11, 10, 12, 11, 10, + 11, 10, 11, 10, 11, 11, 11, 10, 12, 11, 11, 10, 11, 10, 10, 10, 11, 10, 12, + 11, 12, 10, 11, 9, 11, 10, 11, 10, 11, 10, 12, 9, 11, 11, 11, 9, 11, 10, + 10, 9, 11, 10, 10, 9, 10, 9, 7, 4, 5, 5, 5, 6, 6, 7, 6, 8, 7, + 8, 9, 9, 7, 8, 8, 10, 9, 10, 10, 12, 10, 11, 11, 11, 11, 10, 11, 12, + 11, 11, 11, 11, 11, 13, 12, 11, 12, 13, 12, 12, 12, 13, 11, 9, 12, 13, 7, + 13, 11, 13, 11, 10, 11, 13, 15, 15, 12, 14, 15, 15, 15, 6, 15, 5 +}; + +// Second code lengths (5). +static const int kSecondCodeLengths_5[321] = { + 8, 10, 11, 11, 11, 12, 11, 11, 12, 6, 11, 12, 10, 5, 12, 12, 12, 12, 12, + 12, 12, 13, 13, 14, 13, 13, 12, 13, 12, 13, 12, 15, 4, 10, 7, 9, 11, 11, + 10, 9, 6, 7, 8, 9, 6, 7, 6, 7, 8, 7, 7, 8, 8, 8, 8, 8, 8, + 9, 8, 7, 10, 9, 10, 10, 11, 7, 8, 6, 7, 8, 8, 9, 8, 7, 10, 10, + 8, 7, 8, 8, 7, 10, 7, 6, 7, 9, 9, 8, 11, 11, 11, 10, 11, 11, 11, + 8, 11, 6, 7, 6, 6, 6, 6, 8, 7, 6, 10, 9, 6, 7, 6, 6, 7, 10, + 6, 5, 6, 7, 7, 7, 10, 8, 11, 9, 13, 7, 14, 16, 12, 14, 14, 15, 15, + 16, 16, 14, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 13, 14, 14, 16, 15, 17, + 14, 17, 15, 17, 12, 14, 13, 16, 12, 17, 13, 17, 14, 13, 13, 14, 14, 12, 13, + 15, 15, 14, 15, 17, 14, 17, 15, 14, 15, 16, 12, 16, 15, 14, 15, 16, 15, 16, + 17, 17, 15, 15, 17, 17, 13, 14, 15, 15, 13, 12, 16, 16, 17, 14, 15, 16, 15, + 15, 13, 13, 15, 13, 16, 17, 15, 17, 17, 17, 16, 17, 14, 17, 14, 16, 15, 17, + 15, 15, 14, 17, 15, 17, 15, 16, 15, 15, 16, 16, 14, 17, 17, 15, 15, 16, 15, + 17, 15, 14, 16, 16, 16, 16, 16, 12, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 11, 10, 11, 11, 11, 11, 11, + 12, 12, 12, 13, 13, 12, 13, 12, 14, 14, 12, 13, 13, 13, 13, 14, 12, 13, 13, + 14, 14, 14, 13, 14, 14, 15, 15, 13, 15, 13, 17, 17, 17, 9, 17, 7 +}; + +// Second code lengths (global array). +static const int *kSecondCodeLengths[5] = { + kSecondCodeLengths_1, kSecondCodeLengths_2, kSecondCodeLengths_3, + kSecondCodeLengths_4, kSecondCodeLengths_5 +}; + + + +// Offset code lengths (1). +static const int kOffsetCodeLengths_1[11] = { + 5, 6, 3, 3, 3, 3, 3, 3, 3, 4, 6 +}; + +// Offset code lengths (2). +static const int kOffsetCodeLengths_2[13] = { + 5, 6, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 6 +}; + +// Offset code lengths (3). +static const int kOffsetCodeLengths_3[14] = { + 6, 7, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 5, 7 +}; + +// Offset code lengths (4). +static const int kOffsetCodeLengths_4[11] = { + 3, 6, 5, 4, 2, 3, 3, 3, 4, 4, 6 +}; + +// Offset code lengths (5). +static const int kOffsetCodeLengths_5[11] = { + 6, 7, 7, 6, 4, 3, 2, 2, 3, 3, 6 +}; + +// Offset code lengths (global array). +static const int *kOffsetCodeLengths[5] = { + kOffsetCodeLengths_1, kOffsetCodeLengths_2, kOffsetCodeLengths_3, + kOffsetCodeLengths_4, kOffsetCodeLengths_5 +}; + +// Offset code size. +static const int kOffsetCodeSize[5] = { 11, 13, 14, 11, 11 }; + + + +// Meta codes. +static const int kMetaCodes[37] = { + 0x5D8, 0x058, 0x040, 0x0C0, 0x000, 0x078, 0x02B, 0x014, 0x00C, 0x01C, 0x01B, + 0x00B, 0x010, 0x020, 0x038, 0x018, 0x0D8, 0xBD8, 0x180, 0x680, 0x380, 0xF80, + 0x780, 0x480, 0x080, 0x280, 0x3D8, 0xFD8, 0x7D8, 0x9D8, 0x1D8, 0x004, 0x001, + 0x002, 0x007, 0x003, 0x008 +}; + +// Meta code lengths. +static const int kMetaCodeLengths[37] = { + 11, 8, 8, 8, 8, 7, 6, 5, 5, 5, 5, 6, 5, 6, 7, 7, 9, 12, 10, + 11, 11, 12, 12, 11, 11, 11, 12, 12, 12, 12, 12, 5, 2, 2, 3, 4, 5 +}; + + + + +// LZSS decoder constants. +constexpr int kLzssMatch = -1; +constexpr int kLzssEnd = -2; + + + +// Intialize LZSS. +void Algorithm13Method::InitializeLZSS() +{ + int val = *(input.data++); + int code = (val >> 4); + + if (code == 0) { + HuffmanDecoder metacode; + metacode.Initialize(); + + for (int i = 0; i < 37; i++) + metacode.AddValueLF(i, kMetaCodes[i], kMetaCodeLengths[i]); + metacode.MakeTable(true); + + ParseHuffmanCode(firstcode, 321, metacode); + if (val & 0x08) secondcode = firstcode; + else ParseHuffmanCode(secondcode, 321, metacode); + ParseHuffmanCode(offsetcode, (val & 0x07) + 10, metacode); + } + else if (code < 6) { + firstcode.Initialize(kFirstCodeLengths[code-1], 321, 32, true); + secondcode.Initialize(kSecondCodeLengths[code-1], 321, 32, true); + offsetcode.Initialize(kOffsetCodeLengths[code-1], kOffsetCodeSize[code-1], 32, true); + } + else { + throw ExtractException("Algo13: invalid compressed data [code]"); + } + + currcode = &firstcode; + firstcode.MakeTable(true); + secondcode.MakeTable(true); + offsetcode.MakeTable(true); +} + + + +// Parse code of size. +void Algorithm13Method::ParseHuffmanCode(HuffmanDecoder &decoder, int numcodes, + HuffmanDecoder &metacode) +{ + int length = 0; + int lengths[numcodes]; + + for (int i = 0; i < numcodes; i++) { + int val = metacode.NextSymbol(input); + + switch(val) { + case 31: length = -1; break; + case 32: length++; break; + case 33: length--; break; + case 34: + if (input.ReadBit()) lengths[i++] = length; + break; + case 35: + val = input.ReadWord(3) + 2; + while (val--) lengths[i++] = length; + break; + case 36: + val = input.ReadWord(6) + 10; + while (val--) lengths[i++] = length; + break; + default: length = val+1; break; + } + + lengths[i] = length; + } + + decoder.Initialize(lengths, numcodes, 32, true); +} + + + +// Get the next litteral or offset. +int Algorithm13Method::NextLiteralOrOffset(int &offset, int &length) +{ + if (input.HasEnded()) + throw ExtractException("Algo13: all data read but algo has not finished"); + int val = currcode->NextSymbol(input); + + if (val < 0x100) { + currcode = &firstcode; + return val; + } + else { + currcode = &secondcode; + + if (val < 0x13E) length = val - 0x100 + 3; + else if (val == 0x13E) length = input.ReadWord(10) + 65; + else if (val == 0x13F) length = input.ReadWord(15) + 65; + else return kLzssEnd; + + int bit_length = offsetcode.NextSymbol(input); + if (bit_length == 0) offset = 1; + else if (bit_length == 1) offset = 2; + else offset = (1 << (bit_length-1)) + input.ReadWord(bit_length-1) + 1; + + return kLzssMatch; + } +} + + + + +// Constants. +constexpr int kWindowSize = 65536; +constexpr int kWindowMask = kWindowSize - 1; + + +// Initialize the algorithm. +void Algorithm13Method::Initialize() +{ + ended = false; + input.Load(data, end - data); + window_buffer = std::make_unique(kWindowSize); + + match_length = 0; match_offset = 0; + memset(window_buffer.get(), 0, kWindowSize); + + InitializeLZSS(); +} + + + +// Read the next byte. +int32_t Algorithm13Method::ReadNextByte(uint32_t pos) +{ + if (!match_length) { + int offset, length; + int val = NextLiteralOrOffset(offset, length); + + if (val >= 0) { + window_buffer[pos & kWindowMask] = val; + return val; + } else if (val == kLzssEnd) { + ended = true; + return -1; + } else { + match_length = length; + match_offset = pos - offset; + } + } + + match_length--; + uint8_t byte = window_buffer[match_offset++ & kWindowMask]; + window_buffer[pos & kWindowMask] = byte; + return byte; +} + + + +// Read the next bytes. +int32_t Algorithm13Method::ReadBytes(uint8_t *buffer, uint32_t length) +{ + if (ended) return -1; + + uint8_t *start = buffer; + uint8_t *end_capacity = buffer + length; + + int32_t byte; + while (buffer != end_capacity && (byte = ReadNextByte(total_size + (buffer - start))) != -1) + *(buffer++) = byte; + + return buffer - start; +} + + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/methods/algorithm13.h b/src/stuffit/methods/algorithm13.h new file mode 100644 index 0000000..34b89bb --- /dev/null +++ b/src/stuffit/methods/algorithm13.h @@ -0,0 +1,71 @@ +/* + +Stuffit algorithm 13: LZSS and Huffman. + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include "stuffit/methods.h" +#include "stuffit/utils/huffman.h" + +#include + +namespace maconv { +namespace stuffit { + + +// Algorithm 13 compression algorithm. +struct Algorithm13Method : CompressionMethod { + + // Intialize LZSS. + void InitializeLZSS(); + + // Parse Huffman code given in data. + void ParseHuffmanCode(HuffmanDecoder &decoder, int numcodes, + HuffmanDecoder &metacode); + + // Get the next litteral or offset. + int NextLiteralOrOffset(int &offset, int &length); + + + // Initialize the algorithm. + void Initialize() override; + + // Read the next byte. + int32_t ReadNextByte(uint32_t pos); + + // Read the next bytes. + int32_t ReadBytes(uint8_t *data, uint32_t length) override; + + + bool ended; + utils::BitReaderLE input; + + std::unique_ptr window_buffer; + int match_length, match_offset; + + HuffmanDecoder firstcode, secondcode, offsetcode; + HuffmanDecoder *currcode; +}; + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/methods/arsenic.cc b/src/stuffit/methods/arsenic.cc new file mode 100644 index 0000000..26f3c8a --- /dev/null +++ b/src/stuffit/methods/arsenic.cc @@ -0,0 +1,336 @@ +/* + +Arsenic algorithm: BWT and arithmetic coding. + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 "stuffit/methods/arsenic.h" +#include "commands.h" + +#include +#include + +namespace maconv { +namespace stuffit { + + + +// The randomization table. +static const uint16_t kRandomizationTable[] = { + 0xEE, 0x56, 0xF8, 0xC3, 0x9D, 0x9F, 0xAE, 0x2C, + 0xAD, 0xCD, 0x24, 0x9D, 0xA6, 0x101, 0x18, 0xB9, + 0xA1, 0x82, 0x75, 0xE9, 0x9F, 0x55, 0x66, 0x6A, + 0x86, 0x71, 0xDC, 0x84, 0x56, 0x96, 0x56, 0xA1, + 0x84, 0x78, 0xB7, 0x32, 0x6A, 0x3, 0xE3, 0x2, + 0x11, 0x101, 0x8, 0x44, 0x83, 0x100, 0x43, 0xE3, + 0x1C, 0xF0, 0x86, 0x6A, 0x6B, 0xF, 0x3, 0x2D, + 0x86, 0x17, 0x7B, 0x10, 0xF6, 0x80, 0x78, 0x7A, + 0xA1, 0xE1, 0xEF, 0x8C, 0xF6, 0x87, 0x4B, 0xA7, + 0xE2, 0x77, 0xFA, 0xB8, 0x81, 0xEE, 0x77, 0xC0, + 0x9D, 0x29, 0x20, 0x27, 0x71, 0x12, 0xE0, 0x6B, + 0xD1, 0x7C, 0xA, 0x89, 0x7D, 0x87, 0xC4, 0x101, + 0xC1, 0x31, 0xAF, 0x38, 0x3, 0x68, 0x1B, 0x76, + 0x79, 0x3F, 0xDB, 0xC7, 0x1B, 0x36, 0x7B, 0xE2, + 0x63, 0x81, 0xEE, 0xC, 0x63, 0x8B, 0x78, 0x38, + 0x97, 0x9B, 0xD7, 0x8F, 0xDD, 0xF2, 0xA3, 0x77, + 0x8C, 0xC3, 0x39, 0x20, 0xB3, 0x12, 0x11, 0xE, + 0x17, 0x42, 0x80, 0x2C, 0xC4, 0x92, 0x59, 0xC8, + 0xDB, 0x40, 0x76, 0x64, 0xB4, 0x55, 0x1A, 0x9E, + 0xFE, 0x5F, 0x6, 0x3C, 0x41, 0xEF, 0xD4, 0xAA, + 0x98, 0x29, 0xCD, 0x1F, 0x2, 0xA8, 0x87, 0xD2, + 0xA0, 0x93, 0x98, 0xEF, 0xC, 0x43, 0xED, 0x9D, + 0xC2, 0xEB, 0x81, 0xE9, 0x64, 0x23, 0x68, 0x1E, + 0x25, 0x57, 0xDE, 0x9A, 0xCF, 0x7F, 0xE5, 0xBA, + 0x41, 0xEA, 0xEA, 0x36, 0x1A, 0x28, 0x79, 0x20, + 0x5E, 0x18, 0x4E, 0x7C, 0x8E, 0x58, 0x7A, 0xEF, + 0x91, 0x2, 0x93, 0xBB, 0x56, 0xA1, 0x49, 0x1B, + 0x79, 0x92, 0xF3, 0x58, 0x4F, 0x52, 0x9C, 0x2, + 0x77, 0xAF, 0x2A, 0x8F, 0x49, 0xD0, 0x99, 0x4D, + 0x98, 0x101, 0x60, 0x93, 0x100, 0x75, 0x31, 0xCE, + 0x49, 0x20, 0x56, 0x57, 0xE2, 0xF5, 0x26, 0x2B, + 0x8A, 0xBF, 0xDE, 0xD0, 0x83, 0x34, 0xF4, 0x17 +}; + + + + +// Initialize the model with some values. +void ArithmeticModel::Initialize(int first_symbol, int last_symbol, int increment, + int frequency_limit) +{ + this->increment = increment; + this->freq_limit = frequency_limit; + this->num_symbols = last_symbol - first_symbol + 1; + + ResetModel(); + for (int i = 0; i < num_symbols; i++) + symbols[i].symbol = i + first_symbol; +} + + +// Reset the model. +void ArithmeticModel::ResetModel() +{ + total_freq = increment * num_symbols; + for (int i = 0; i < num_symbols; i++) + symbols[i].freq = increment; +} + + +// Increase the model frequency at |symindex| by |increment|. +void ArithmeticModel::IncreaseFrequency(int symindex) +{ + symbols[symindex].freq += increment; + + total_freq += increment; + if (total_freq <= freq_limit) + return; + + total_freq = 0; + for (int i = 0; i < num_symbols; i++) { + symbols[i].freq++; + symbols[i].freq >>= 1; + total_freq += symbols[i].freq; + } +} + + + +// Decoder constants. +constexpr int kDecoderNumBits = 26; +constexpr int kDecoderOne = (1 << (kDecoderNumBits - 1)); +constexpr int kDecoderHalf = (1 << (kDecoderNumBits - 2)); + + +// Initialize the decoder with some values. +void ArithmeticDecoder::Initialize(uint8_t *data, uint32_t length) +{ + input.Load(data, length); + range = kDecoderOne; + code = input.ReadLongWord(kDecoderNumBits); +} + + +// Get the next arithmetic code. +void ArithmeticDecoder::NextCode(int symlow, int symsize, int symtot) +{ + int renormf = range / symtot; + int lowincr = renormf * symlow; + + code -= lowincr; + range = (symlow + symsize == symtot) ? (range - lowincr) : (symsize * renormf); + + for (; range <= kDecoderHalf; range <<= 1) + code = (code << 1) | input.ReadBit(); +} + + +// Get the next arithmetic symbol. +int ArithmeticDecoder::NextSymbol(ArithmeticModel *model) +{ + int freq = code / (range / model->total_freq); + int cumulative = 0, n = 0; + + for (; n < model->num_symbols - 1; n++) { + if (cumulative + model->symbols[n].freq > freq) break; + cumulative += model->symbols[n].freq; + } + + NextCode(cumulative, model->symbols[n].freq, model->total_freq); + model->IncreaseFrequency(n); + + return model->symbols[n].symbol; +} + + +// Get the next word (that has |n| bits). +int ArithmeticDecoder::NextWord(ArithmeticModel *model, int n) +{ + int word = 0; + for (int i = 0; i < n; i++) { + if (NextSymbol(model)) + word |= (1 << i); + } + + return word; +} + + + + +// Initialize the algorithm. +void ArsenicMethod::Initialize() +{ + decoder.Initialize(data, end - data); + + initial_model.Initialize(0, 1, 1, 256); + selector_model.Initialize(0, 10, 8, 1024); + mtf_model[0].Initialize(2, 3, 8, 1024); + mtf_model[1].Initialize(4, 7, 4, 1024); + mtf_model[2].Initialize(8, 15, 4, 1024); + mtf_model[3].Initialize(16, 31, 4, 1024); + mtf_model[4].Initialize(32, 63, 2, 1024); + mtf_model[5].Initialize(64, 127, 2, 1024); + mtf_model[6].Initialize(128, 255, 1, 1024); + + if (decoder.NextWord(&initial_model, 8) != 'A') + throw ExtractException("Arsenic: invalid compressed data [A]"); + if (decoder.NextWord(&initial_model, 8) != 's') + throw ExtractException("Arsenic: invalid compressed data [s]"); + + block_bits = decoder.NextWord(&initial_model, 4) + 9; + block_size = (1 << block_bits); + + num_bytes = 0; byte_count = 0; repeat = 0; + crc = 0xFFFFFFFF; compcrc = 0; + + block = std::make_unique(block_size); + end_of_blocks = decoder.NextSymbol(&initial_model); // Check first end marker. +} + + + +// Read the next block. +void ArsenicMethod::ReadNextBlock() +{ + mtf.ResetDecoder(); + + randomized = decoder.NextSymbol(&initial_model); + transform_index = decoder.NextWord(&initial_model, block_bits); + num_bytes = 0; + + while (true) { + int sel = decoder.NextSymbol(&selector_model); + if (sel == 0 || sel == 1) { // Zero counting. + int zero_state = 1, zero_count = 0; + while (sel < 2) { + if (sel == 0) zero_count += zero_state; + else if (sel == 1) zero_count += (2 * zero_state); + zero_state *= 2; + sel = decoder.NextSymbol(&selector_model); + } + + if (num_bytes + zero_count > block_size) + throw ExtractException("Arsenic: invalid block [zero count]"); + + memset(&block[num_bytes], mtf.Decode(0), zero_count); + num_bytes += zero_count; + } + + int symbol; + if (sel == 10) break; + else if (sel == 2) symbol = 1; + else symbol = decoder.NextSymbol(&mtf_model[sel - 3]); + + if (num_bytes >= block_size) + throw ExtractException("Arsenic: invalid block [num of bytes]"); + block[num_bytes++] = mtf.Decode(symbol); + } + + if (transform_index >= num_bytes) + throw ExtractException("Arsenic: invalid block [transform index]"); + + selector_model.ResetModel(); + for (int i = 0; i < 7;i++) + mtf_model[i].ResetModel(); + + if (decoder.NextSymbol(&initial_model)) { // End marker. + compcrc = decoder.NextWord(&initial_model, 32); + end_of_blocks = true; + } + + transform = std::make_unique(num_bytes); + CalculateInverseBWT(transform.get(), block.get(), num_bytes); +} + + + +// Read the next byte. +int32_t ArsenicMethod::ReadNextByte() +{ + int byte, out_byte; + + if (repeat) { + out_byte = last; repeat--; + goto end; + } + +retry: + if (byte_count >= num_bytes) { + if (end_of_blocks) return -1; + + ReadNextBlock(); + byte_count = 0; count = 0; last = 0; + rand_index = 0; rand_count = kRandomizationTable[0]; + } + + transform_index = transform[transform_index]; + byte = block[transform_index]; + + if (randomized && rand_count == byte_count) { + byte ^= 1; + rand_index = (rand_index + 1) & 0xFF; + rand_count += kRandomizationTable[rand_index]; + } + + byte_count++; + + if (count == 4) { + count = 0; + if (byte == 0) goto retry; + repeat = byte - 1; + out_byte = last; + } + else { + if (byte == last) count++; + else { count = 1; last = byte; } + out_byte = byte; + } + +end: + crc = CalcCRC(crc, out_byte, CRCTable_edb88320); + return out_byte; +} + + + +// Read the next bytes. +int32_t ArsenicMethod::ReadBytes(uint8_t *buffer, uint32_t length) +{ + if (end_of_blocks) { + // if (compcrc != ~crc) // FIX ME + // throw ExtractException("Arsenic: invalid CRC after uncompressing"); + return -1; + } + + uint8_t *start = buffer; + uint8_t *end_capacity = buffer + length; + + int32_t byte; + while (buffer != end_capacity && (byte = ReadNextByte()) != -1) + *(buffer++) = byte; + + return buffer - start; +} + + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/methods/arsenic.h b/src/stuffit/methods/arsenic.h new file mode 100644 index 0000000..1afe3a5 --- /dev/null +++ b/src/stuffit/methods/arsenic.h @@ -0,0 +1,122 @@ +/* + +Arsenic algorithm: BWT and arithmetic coding. + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include "stuffit/methods.h" +#include "stuffit/utils/bwt.h" +#include "stuffit/utils/crc.h" +#include "utils/bit_reader.h" + +#include + +namespace maconv { +namespace stuffit { + + +// Store an arithmetic symbol. +struct ArithmeticSymbol { + int symbol; + int freq; +}; + + +// The arithmetic model. +struct ArithmeticModel { + + // Initialize the model with some values. + void Initialize(int firstsymb, int lastsym, int incr, int freqlimit); + + // Reset the model. + void ResetModel(); + + // Increase the model frequency at |symindex| by |increment|. + void IncreaseFrequency(int symindex); + + int total_freq; + int increment; + int freq_limit; + + int num_symbols; + ArithmeticSymbol symbols[128]; +}; + + +// The arithmetic decoder. +struct ArithmeticDecoder { + + // Initialize the decoder with some values. + void Initialize(uint8_t *data, uint32_t length); + + // Get the next arithmetic code. + void NextCode(int symlow, int symsize, int symtot); + + // Get the next arithmetic symbol. + int NextSymbol(ArithmeticModel *model); + + // Get the next word (that has |n| bits). + int NextWord(ArithmeticModel *model, int n); + + utils::BitReaderBE input; + int range, code; +}; + + + +// Arsenic compression algorithm. +struct ArsenicMethod : CompressionMethod { + + // Initialize the algorithm. + void Initialize() override; + + // Read the next block. + void ReadNextBlock(); + + + // Read the next byte. + int32_t ReadNextByte(); + + // Read the next bytes. + int32_t ReadBytes(uint8_t *data, uint32_t length) override; + + + ArithmeticModel initial_model, selector_model, mtf_model[7]; + ArithmeticDecoder decoder; + MtfDecoder mtf; + + std::unique_ptr block; + int block_bits, block_size; + bool end_of_blocks; + + int num_bytes, byte_count, transform_index; + std::unique_ptr transform; + + int randomized, rand_count, rand_index; + int repeat, count, last; + + uint32_t crc, compcrc; +}; + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/methods/compress.cc b/src/stuffit/methods/compress.cc new file mode 100644 index 0000000..2d24bbf --- /dev/null +++ b/src/stuffit/methods/compress.cc @@ -0,0 +1,174 @@ +/* + +Compress Algorithm (LZW, block mode). + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 "stuffit/methods/compress.h" + +#include + +namespace maconv { +namespace stuffit { + + + +// Initialize the decoder. +void CompressLzw::Initialize(int max_symbols, int reserved_symbols) +{ + this->max_symbols = max_symbols; + this->reserved_symbols = reserved_symbols; + + nodes = std::make_unique(max_symbols); + for (int i = 0; i < 256; i++) { + nodes[i].chr = i; + nodes[i].parent = -1; + } + + ClearTable(); +} + + +// Clear the decoder table. +void CompressLzw::ClearTable() +{ + num_symbols = 256 + reserved_symbols; + prev_symbol = -1; + symbol_size = 9; +} + + + +// The the first bytes corresponding to symbol in the LZW tree. +uint8_t CompressLzw::FindFirstByte(int symbol) +{ + while (nodes[symbol].parent >= 0) + symbol = nodes[symbol].parent; + return nodes[symbol].chr; +} + + +// Get the next symbol. +void CompressLzw::NextSymbol(int symbol) +{ + if (symbol > num_symbols || (prev_symbol < 0 && symbol == num_symbols)) + throw ExtractException("Compress: invalid code"); + + int parent = prev_symbol; + prev_symbol = symbol; + + if (parent < 0) return; + int postfix_byte = FindFirstByte(symbol == num_symbols ? parent : symbol); + + if (num_symbols == max_symbols) // Too many symbols. + return; + + nodes[num_symbols].parent = parent; + nodes[num_symbols].chr = postfix_byte; + num_symbols++; + + if (num_symbols != max_symbols && ((num_symbols & (num_symbols-1)) == 0)) + symbol_size++; +} + + + +// Calculate the number of bytes needed to write the output. +int CompressLzw::CalcOutputLength() +{ + int n = 0; + for (int symbol = prev_symbol; symbol >= 0; n++) + symbol = nodes[symbol].parent; + return n; +} + + +// Write the ouput data to a buffer. +void CompressLzw::OutputToBuffer(int len, uint8_t *buffer) +{ + int symbol = prev_symbol; + buffer += len; + + while (symbol >= 0) { + *(--buffer) = nodes[symbol].chr; + symbol = nodes[symbol].parent; + } +} + + + + +// Initialize the algorithm. +void CompressMethod::Initialize() +{ + block_mode = (flags & 0x80) != 0; + lzw.Initialize(1 << (flags & 0x1F), block_mode ? 1 : 0); + + input.Load(data, end - data); + output_len = -1; + symbol_counter = 0; +} + + + +// Load the next block of data. +bool CompressMethod::LoadNextBlock() +{ + int symbol; + while (true) { + if (input.HasEnded(lzw.symbol_size - 1)) return false; + + symbol = input.ReadWord(lzw.symbol_size); + symbol_counter++; + + if (symbol != 256 || !block_mode) break; + if (symbol_counter % 8) + input.IgnoreBits(lzw.symbol_size * (8 - symbol_counter % 8)); + + lzw.ClearTable(); + symbol_counter = 0; + } + + lzw.NextSymbol(symbol); + output_len = lzw.CalcOutputLength(); + return true; +} + + + +// Read the next bytes. +int32_t CompressMethod::ReadBytes(uint8_t *data, uint32_t length) +{ + if (output_len == -1 && !LoadNextBlock()) + return -1; + if (output_len > length) + return 0; + + int32_t len = output_len; + lzw.OutputToBuffer(output_len, data); + + output_len = -1; + return len; +} + + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/methods/compress.h b/src/stuffit/methods/compress.h new file mode 100644 index 0000000..0395392 --- /dev/null +++ b/src/stuffit/methods/compress.h @@ -0,0 +1,102 @@ +/* + +Compress Algorithm (LZW, block mode). + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include "stuffit/methods.h" +#include "utils/bit_reader.h" + +#include + +namespace maconv { +namespace stuffit { + + +struct CompressTreeNode { + uint8_t chr; + int parent; +}; + + +// LZW (Lempel-Ziv-Welch) decoder. +struct CompressLzw { + + // Initialize the decoder. + void Initialize(int max_symbols,int reserved_symbols); + + // Clear the decoder table. + void ClearTable(); + + + // The the first bytes corresponding to symbol in the LZW tree. + uint8_t FindFirstByte(int symbol); + + // Read the next symbol. + void NextSymbol(int symbol); + + + // Calculate the number of bytes needed to write the output. + int CalcOutputLength(); + + // Write the ouput data to a buffer. + void OutputToBuffer(int len, uint8_t *buffer); + + + int num_symbols, max_symbols, reserved_symbols; + int prev_symbol; + int symbol_size; + + std::unique_ptr nodes; +}; + + + +// Compress algorithm. +struct CompressMethod : CompressionMethod { + + CompressMethod(int flags_ = 0x8E) : flags{flags_} {} + + + // Initialize the algorithm. + void Initialize() override; + + // Load the next block of data. + bool LoadNextBlock(); + + // Read the next bytes. + int32_t ReadBytes(uint8_t *data, uint32_t length) override; + + + int flags; + bool block_mode; + + int symbol_counter; + int output_len; + + utils::BitReaderLE input; + CompressLzw lzw; +}; + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/methods/rle90.cc b/src/stuffit/methods/rle90.cc new file mode 100644 index 0000000..f88fb4e --- /dev/null +++ b/src/stuffit/methods/rle90.cc @@ -0,0 +1,78 @@ +/* + +RLE 90 compression algorithm. + +Copyright (C) 2019, Guillaume Gonnet + +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 "stuffit/methods/rle90.h" + +namespace maconv { +namespace stuffit { + + + +// Initialize the algorithm. +void Rle90Method::Initialize() +{ + count = 0; + byte = 0; +} + + + +// Read the next byte. +uint8_t Rle90Method::ReadNextByte() +{ + // We have a byte to repeat. + if (count != 0) + return (count--, byte); + + // Read the next byte from the stream. + uint8_t next = *(data++); + if (next != 0x90) + return (byte = next, byte); + + // The previous byte need to be repeated (if not 0x0). + next = *(data++); + if (next == 0x0) + return (byte = next, byte); + + count = next - 2; + return next; +} + + + +// Read the next bytes. +int32_t Rle90Method::ReadBytes(uint8_t *buffer, uint32_t length) +{ + if (data == end && count == 0) + return -1; + + uint8_t *start = buffer; + uint8_t *end_capacity = buffer + length; + + while ((data != end || count != 0) && buffer != end_capacity) + *(buffer++) = ReadNextByte(); + + return buffer - start; +} + + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/methods/rle90.h b/src/stuffit/methods/rle90.h new file mode 100644 index 0000000..73cad66 --- /dev/null +++ b/src/stuffit/methods/rle90.h @@ -0,0 +1,48 @@ +/* + +RLE 90 compression algorithm. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include "stuffit/methods.h" + +namespace maconv { +namespace stuffit { + + +// RLE 90 compression algorithm. +struct Rle90Method : CompressionMethod { + + // Initialize the algorithm. + void Initialize() override; + + // Read the next byte. + uint8_t ReadNextByte(); + + // Read the next bytes. + int32_t ReadBytes(uint8_t *data, uint32_t length) override; + + + uint32_t count; // Number of bytes to repeat. + uint8_t byte; // Byte to repeat. +}; + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/stuffit.cc b/src/stuffit/stuffit.cc new file mode 100644 index 0000000..b00d255 --- /dev/null +++ b/src/stuffit/stuffit.cc @@ -0,0 +1,137 @@ +/* + +Extract Stuffit archives. +See docs/stuffit/Stuffit.md for more information on this format. + +Copyright (C) 2019, Guillaume Gonnet + +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 "stuffit/stuffit.h" +#include "stuffit/methods.h" +#include "formats/formats.h" +#include "commands.h" + +#include +#include + +namespace maconv { +namespace stuffit { + + + +// Warn the user that a fork couldn't be extracted. +static void WarnForkError(StuffitEntry &ent, bool is_res, const char *msg, ...) +{ + va_list args; + fprintf(stderr, "\033[1m\033[33mWARNING: "); + fprintf(stderr, is_res ? "ressource fork " : "data fork "); + fprintf(stderr, "of '%s' couldn't be extracted (", ent.name.c_str()); + + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); + + fprintf(stderr, ")\033[0m\n"); +} + + + +// Extract a single fork. +static void ExtractFork(StuffitEntry &ent, bool is_res, fs::File &file, + uint8_t *data) +{ + const StuffitCompInfo &info = is_res ? ent.res : ent.data; + + // Select the compression handler and extract the fork. + auto ptr = GetCompressionMethod(info.method); + if (!ptr) + return (void)WarnForkError(ent, is_res, "compression method %u not supported", info.method); + + // Try extracting the fork. + try { + ptr->Extract(info, data, file.mem_pool); + } catch (ExtractException &e) { + return (void)WarnForkError(ent, is_res, e.what()); + } + + // Fill file information. + if (is_res) { + file.res = ptr->uncompressed; + file.res_size = ptr->total_size; + } else { + file.data_size = ptr->total_size; + file.data = ptr->uncompressed; + } +} + + + +// Extract a file. +static void ExtractFile(fs::FileReader &reader, StuffitEntry &ent, + const std::string &dest_folder) +{ + // Copy extracted data to file object. + fs::File file; + file.Reset(); + + file.type = ent.type; + file.creator = ent.type; + file.flags = ent.type; + file.creation_date = ent.type; + file.modif_date = ent.type; + file.filename = ent.name; + + // Log information to user. + std::string filename = dest_folder + "/" + GetFilenameFor(ent.name, prefered_conv); + LogDebug("Extracting %s ...", filename.c_str()); + + // Uncompress forks (if not empty). + if (ent.data.comp_size > 0) + ExtractFork(ent, false, file, reader.data); + if (ent.res.comp_size > 0) + ExtractFork(ent, true, file, reader.data); + + // Save the file. + PackLocalFile(file, filename, prefered_conv); +} + + + +// Extract a directory. +static void ExtractDirectory(StuffitEntry &ent, const std::string &dest_folder) +{ + std::string dirname = dest_folder + "/" + ent.name; + Path::makedirs(dirname); + + // TODO: set mofitication date. +} + + + +// Extract a Stuffit entry. +void ExtractStuffitEntry(fs::FileReader &reader, StuffitEntry &ent, + const std::string &dest_folder) +{ + if (ent.etype == StuffitEntryType::File) + ExtractFile(reader, ent, dest_folder); + else if (ent.etype == StuffitEntryType::Folder) + ExtractDirectory(ent, dest_folder); +} + + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/stuffit.h b/src/stuffit/stuffit.h new file mode 100644 index 0000000..2c3990b --- /dev/null +++ b/src/stuffit/stuffit.h @@ -0,0 +1,88 @@ +/* + +Extract Stuffit archives. +See docs/stuffit/Stuffit.md for more information on this format. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include "fs/file.h" +#include "fs/file_reader.h" +#include "fs/file_writer.h" + +#include + +namespace maconv { +namespace stuffit { + + +// Stuffit entry type. +enum class StuffitEntryType : uint8_t { + Folder, File, EndFolder, Unknown +}; + + +// Information about a compressed fork. +struct StuffitCompInfo { + uint8_t method; // The compression method. + uint32_t offset; // Compressed data offset in the archive. + uint32_t size; // Fork size (uncompressed). + uint32_t comp_size; // Fork size (compressed). +}; + + +// An entry in a Stuffit archive. +struct StuffitEntry { + StuffitEntryType etype; // Entry type (folder, file, end folder). + std::string name; // Name of the file/directory. + + uint32_t entity_off; // Entity offset. + uint32_t parent_off; // Parent offset. + uint16_t num_files; // Number of files (folder only). + + + time_t creation_date; // Creation date of the file (Unix time). + time_t modif_date; // Modification date of the file (Unix file). + + uint32_t type; // File type (4 chars). + uint32_t creator; // File creator (4 chars). + uint32_t flags; // Finder flags. + + + StuffitCompInfo data; // Info about data fork. + StuffitCompInfo res; // Info about res fork. +}; + + + +// Stuffit (v1) functions. +bool IsFileStuffit1(fs::FileReader &reader); +void ExtractStuffit1(fs::FileReader &reader, const std::string &output); + +// Stuffit (v5) functions. +bool IsFileStuffit5(fs::FileReader &reader); +void ExtractStuffit5(fs::FileReader &reader, const std::string &output); + + +// Extract a Stuffit entry. +void ExtractStuffitEntry(fs::FileReader &reader, StuffitEntry &ent, + const std::string &dest_folder); + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/stuffit_v1.cc b/src/stuffit/stuffit_v1.cc new file mode 100644 index 0000000..21d52a6 --- /dev/null +++ b/src/stuffit/stuffit_v1.cc @@ -0,0 +1,152 @@ +/* + +Extract files from Stuffit (v1) archives. +See docs/stuffit/Stuffit_v1.md for more information on this format. + +Copyright (C) 2019, Guillaume Gonnet + +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 "stuffit/stuffit.h" + +#include + +namespace maconv { +namespace stuffit { + + +// Constants. +constexpr uint8_t kTypeFolder = 32; +constexpr uint8_t kTypeEndFolder = 33; + + + +// Return true if a file is in Stuffit (v1) format. +bool IsFileStuffit1(fs::FileReader &reader) +{ + reader.Seek(0); + IS_COND(reader.file_size >= 22); + IS_COND(reader.ReadByte() == 'S'); + + uint8_t byte = reader.ReadByte(); + IS_COND(byte == 'T' || byte == 'I'); + + reader.Skip(8); + IS_COND(reader.ReadWordBE() == 0x724c6175); + + return true; +} + + + +// Read Stuffit (v1) header. +static uint32_t ReadHeader(fs::FileReader &reader) +{ + reader.Seek(0); + reader.Skip(6); // Skip magic number and number of files. + uint32_t total_size = reader.ReadWordBE(); + + reader.Skip(12); // Skip other bytes from header. + return total_size; +} + + + +// Read Stuffit (v1) file header. +static void ReadFileHeader(fs::FileReader &reader, StuffitEntry &ent) +{ + // Read compression methods. + ent.res.method = reader.ReadByte(); + ent.data.method = reader.ReadByte(); + + // Guess file type. + if (ent.data.method == kTypeFolder || ent.res.method == kTypeFolder) + ent.etype = StuffitEntryType::Folder; + else if (ent.data.method == kTypeEndFolder || ent.res.method == kTypeEndFolder) + ent.etype = StuffitEntryType::EndFolder; + else + ent.etype = StuffitEntryType::File; + + // If the entry type is "EndFolder": don't parse the rest. + if (ent.etype == StuffitEntryType::EndFolder) + return (void)reader.Skip(110); + + + // Read file name. + uint8_t filelength = reader.ReadByte(); + ent.name = reader.ReadString(filelength); + reader.Skip(63 - filelength); + + // Read file type, creator and Finder flags. + ent.type = reader.ReadWordBE(); + ent.creator = reader.ReadWordBE(); + ent.flags = reader.ReadHalfLE(); + + // Read creation and modification dates. + ent.creation_date = reader.ReadMacDate(); + ent.modif_date = reader.ReadMacDate(); + + + // If the entry type is "Folder": don't parse the rest. + if (ent.etype == StuffitEntryType::Folder) + return (void)reader.Skip(28); + + // Read data and res sizes and skip CRC-16. + ent.res.size = reader.ReadWordBE(); + ent.data.size = reader.ReadWordBE(); + ent.res.comp_size = reader.ReadWordBE(); + ent.data.comp_size = reader.ReadWordBE(); + reader.Skip(12); + + // Set ressource and data offsets. + ent.res.offset = reader.Tell(); + ent.data.offset = ent.res.offset + ent.res.comp_size; + + // Move to next entry. + reader.Seek(ent.data.offset + ent.data.comp_size); +} + + + +// Extract a Stuffit (v1) directory. +static void ExtractDirectory(fs::FileReader &reader, const std::string &dest_dir, + uint32_t total_size) +{ + StuffitEntry ent; + + while (reader.Tell() < total_size) { + ReadFileHeader(reader, ent); + ExtractStuffitEntry(reader, ent, dest_dir); + + if (ent.etype == StuffitEntryType::EndFolder) + break; + if (ent.etype == StuffitEntryType::Folder) + ExtractDirectory(reader, dest_dir + "/" + ent.name, total_size); + } +} + + + +// Extract a Stuffit (v1) archive. +void ExtractStuffit1(fs::FileReader &reader, const std::string &output) +{ + uint32_t total_size = ReadHeader(reader); + ExtractDirectory(reader, output, total_size); +} + + + +} // namespace maconv +} // namespace stuffit diff --git a/src/stuffit/stuffit_v5.cc b/src/stuffit/stuffit_v5.cc new file mode 100644 index 0000000..c3351d4 --- /dev/null +++ b/src/stuffit/stuffit_v5.cc @@ -0,0 +1,203 @@ +/* + +Extract files from Stuffit (v5) archives. +See docs/stuffit/Stuffit_v5.md for more information on this format. + +Copyright (C) 2019, Guillaume Gonnet + +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 "stuffit/stuffit.h" +#include "commands.h" + +#include + +namespace maconv { +namespace stuffit { + + +// Stuffit (v5) entity flags. +constexpr uint8_t kFlagDirectory = 0x40; +constexpr uint8_t kFlagHasRessource = 0x1; + + + +// Return true if a file is in Stuffit (v5) format. +bool IsFileStuffit5(fs::FileReader &reader) +{ + reader.Seek(0); + IS_COND(reader.file_size >= 100); + + auto first = reader.ReadString(16); + IS_COND(first == "StuffIt (c)1997-"); + + reader.Skip(4); + auto sec = reader.ReadString(60); + IS_COND(sec == " Aladdin Systems, Inc., http://www.aladdinsys.com/StuffIt/\x0d\x0a"); + + return true; +} + + + +// Read Stuffit (v5) header. +static uint16_t ReadHeader(fs::FileReader &reader) +{ + reader.Seek(0); + reader.Skip(83); // Skip magic string. + + // Make sure that the archive is not encrypted. + if (reader.ReadByte() & 0x80) + StopOnError("Stuffit archive is encrypted"); + + // Read entry offset and number of files. + reader.Skip(8); + uint16_t num_files = reader.ReadHalfBE(); + uint32_t offset = reader.ReadWordBE(); + + reader.Seek(offset); + return num_files; +} + + + +// Read Stuffit (v5) file header. +static void ReadFileHeader(fs::FileReader &reader, StuffitEntry &ent) +{ + reader.Skip(4); // Skip magic number. + + // Read version and header size. + uint8_t version = reader.ReadByte(); + reader.Skip(1); + uint16_t header_size = reader.ReadHalfBE(); + reader.Skip(1); + + // Read flags for knowing if entry is a file or a folder. + ent.etype = (reader.ReadByte() & kFlagDirectory) ? StuffitEntryType::Folder + : StuffitEntryType::File; + + // Read creation and modification dates. + ent.creation_date = reader.ReadMacDate(); + ent.modif_date = reader.ReadMacDate(); + reader.Skip(8); + + // Entity and parent offsets. + ent.entity_off = reader.Tell() - 26; + ent.parent_off = reader.ReadWordBE(); + + // Read name and data lengths. + uint16_t name_length = reader.ReadHalfBE(); + reader.Skip(2); + ent.data.size = reader.ReadWordBE(); + ent.data.comp_size = reader.ReadWordBE(); + reader.Skip(4); + + + // The entry is a folder: read the number of files. + if (ent.etype == StuffitEntryType::Folder) { + ent.num_files = reader.ReadHalfBE(); + + // This folder is not a real one and must be skipped. + if (ent.data.size == 0XFFFFFFFF) { + ReadFileHeader(reader, ent); + return (void)ent.num_files++; + } + } + + // The entry is a file: read compression method. + else { + ent.num_files = 0; + ent.data.method = reader.ReadByte(); + reader.Skip(1); + } + + + // Read file/folder name. + ent.name = reader.ReadString(name_length); + + // Skip comment (if exist). + if (reader.Tell() < ent.entity_off + header_size) { + uint16_t comment_length = reader.ReadHalfBE(); + reader.Skip(comment_length + 2); + } + + + // Read second flags: if 0x1, there is a ressource fork. + bool has_res = reader.ReadHalfBE() & kFlagHasRessource; + reader.Skip(2); + + // Read file type, creator and flags. + ent.type = reader.ReadWordBE(); + ent.creator = reader.ReadWordBE(); + ent.flags = reader.ReadHalfBE(); + reader.Skip(version == 0x1 ? 22 : 18); + + + // Read ressource data (if exists). + if (has_res) { + ent.res.size = reader.ReadWordBE(); + ent.res.comp_size = reader.ReadWordBE(); + reader.Skip(4); + ent.res.method = reader.ReadByte(); + reader.Skip(1); + } else { + ent.res.size = 0; + ent.res.comp_size = 0; + } + + // If the entry is a folder, return here. + if (ent.etype == StuffitEntryType::Folder) + return; + + + // Set data and res offsets. + ent.data.offset = reader.Tell(); + ent.res.offset = ent.data.offset + ent.data.comp_size; + + // Seek to the next entry (if it's a file). + reader.Seek(ent.res.offset + ent.res.comp_size); +} + + + +// Extract a directoty. +void ExtractStuffit5(fs::FileReader &reader, const std::string &output) +{ + uint16_t num_files = ReadHeader(reader); + std::unordered_map folders; + + StuffitEntry ent; + std::string dest_folder; + + for (uint16_t i = 0; i < num_files; i++) { + ReadFileHeader(reader, ent); + + // Get the parent folder of the file. + auto folder = folders.find(ent.parent_off); + dest_folder = (folder != folders.end()) ? folder->second : output; + + ExtractStuffitEntry(reader, ent, dest_folder); + num_files += ent.num_files; + + // Add this directory to folders map. + if (ent.etype == StuffitEntryType::Folder) + folders[ent.entity_off] = dest_folder + "/" + ent.name; + } +} + + + +} // namespace maconv +} // namespace stuffit diff --git a/src/stuffit/utils/bwt.cc b/src/stuffit/utils/bwt.cc new file mode 100644 index 0000000..f0cbbe9 --- /dev/null +++ b/src/stuffit/utils/bwt.cc @@ -0,0 +1,76 @@ +/* + +Burrows–Wheeler Transform. + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 "stuffit/utils/bwt.h" + +namespace maconv { +namespace stuffit { + + + +// Calculate inverse of BWT. +void CalculateInverseBWT(uint32_t *transform, uint8_t *block, int block_len) +{ + int counts[256] = {0}; + int cumulative_counts[256]; + + for (int i = 0; i < block_len; i++) + counts[block[i]]++; + + for (int i = 0, total = 0; i < 256; i++) { + cumulative_counts[i] = total; + total += counts[i]; + counts[i] = 0; + } + + for (int i = 0; i < block_len; i++) { + transform[cumulative_counts[block[i]] + counts[block[i]]] = i; + counts[block[i]]++; + } +} + + + +// Reset the decoder. +void MtfDecoder::ResetDecoder() +{ + for (int i = 0; i < 256; i++) + table[i] = i; +} + + +// Decode the next symbol. +int MtfDecoder::Decode(int symbol) +{ + int res = table[symbol]; + for (int i = symbol; i > 0; i--) + table[i] = table[i-1]; + + table[0] = res; + return res; +} + + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/utils/bwt.h b/src/stuffit/utils/bwt.h new file mode 100644 index 0000000..33fa27c --- /dev/null +++ b/src/stuffit/utils/bwt.h @@ -0,0 +1,51 @@ +/* + +Burrows–Wheeler Transform. + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include + +namespace maconv { +namespace stuffit { + + +// The MTF (Move-to-Front Transform) decoder. +struct MtfDecoder { + + // Reset the decoder. + void ResetDecoder(); + + // Decode the next symbol. + int Decode(int symbol); + + int table[256]; +}; + + + +// Calculate inverse of BWT. +void CalculateInverseBWT(uint32_t *transform, uint8_t *block, int block_len); + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/utils/crc.cc b/src/stuffit/utils/crc.cc new file mode 100644 index 0000000..5b881d1 --- /dev/null +++ b/src/stuffit/utils/crc.cc @@ -0,0 +1,78 @@ +/* + +Cyclic redundancy check (CRC). + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 "stuffit/utils/crc.h" + +namespace maconv { +namespace stuffit { + + + +// EDB88320 CRC table. +const uint32_t CRCTable_edb88320[256]= { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D +}; + + + +// Calculate CRC for the next byte. +uint32_t CalcCRC(uint32_t prevcrc, uint8_t byte, const uint32_t *table) +{ + return table[(prevcrc ^ byte) & 0xFF] ^ (prevcrc >> 8); +} + + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/utils/crc.h b/src/stuffit/utils/crc.h new file mode 100644 index 0000000..705d931 --- /dev/null +++ b/src/stuffit/utils/crc.h @@ -0,0 +1,41 @@ +/* + +Cyclic redundancy check (CRC). + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include + +namespace maconv { +namespace stuffit { + + +// CRC tables. +extern const uint32_t CRCTable_edb88320[256]; + + +// Calculate CRC for the next byte. +uint32_t CalcCRC(uint32_t prevcrc, uint8_t byte, const uint32_t *table); + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/utils/huffman.cc b/src/stuffit/utils/huffman.cc new file mode 100644 index 0000000..94407fd --- /dev/null +++ b/src/stuffit/utils/huffman.cc @@ -0,0 +1,275 @@ +/* + +Huffman decoder. + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 "stuffit/utils/huffman.h" +#include "stuffit/methods.h" + +#include +#include + +namespace maconv { +namespace stuffit { + + + +// Reverse a 32bits integer. +static uint32_t Reverse32(uint32_t val) +{ + val = ((val >> 1) & 0x55555555) | ((val & 0x55555555) << 1); + val = ((val >> 2) & 0x33333333) | ((val & 0x33333333) << 2); + val = ((val >> 4) & 0x0F0F0F0F) | ((val & 0x0F0F0F0F) << 4); + val = ((val >> 8) & 0x00FF00FF) | ((val & 0x00FF00FF) << 8); + return (val >> 16) | (val << 16); +} + + +// Reverse a Nbits integer. +static uint32_t ReverseN(uint32_t val, int length) +{ + return Reverse32(val) >> (32 - length); +} + + + +// Copy an Huffman decoder. +HuffmanDecoder &HuffmanDecoder::operator=(const HuffmanDecoder &other) +{ + tree = other.tree; + num_entries = other.num_entries; + min_length = other.min_length; + max_length = other.max_length; + return *this; +} + + +// Initialize the decoder. +void HuffmanDecoder::Initialize() +{ + NewNode(); + num_entries = 1; + + min_length = std::numeric_limits::max(); + max_length = std::numeric_limits::min(); +} + + +// Initialize the decoder (with some lengths). +void HuffmanDecoder::Initialize(const int *lengths, int numsymbols, int maxcodelength, + bool zeros) +{ + Initialize(); + int code = 0, symbolsleft = numsymbols; + + for (int length = 1; length <= maxcodelength; length++) { + for (int i = 0; i < numsymbols; i++) { + if (lengths[i] != length) continue; + + AddValue(i, zeros ? code : ~code, length); + code++; + + if (--symbolsleft == 0) return; // Early exit if all codes have been handled. + } + + code <<= 1; + } +} + + + +// Add a new value in the decoder. +void HuffmanDecoder::AddValue(int value, uint32_t code, int length) +{ + AddValue(value, code, length, length); +} + + +// Add a new value in the decoder. +void HuffmanDecoder::AddValue(int value, uint32_t code, int length, int repeat_pos) +{ + if (length > max_length) max_length = length; + if (length < min_length) min_length = length; + + repeat_pos = length - 1 - repeat_pos; + int last_node = 0; + + int codest = ((code >> (repeat_pos - 1)) & 3); + if (repeat_pos == 0 || (repeat_pos >= 0 && (codest == 0 || codest == 3))) + throw ExtractException("Huffman: invalid repeat position"); + + for (int bitpos = length - 1; bitpos >= 0; bitpos--) { + int bit = (code >> bitpos) & 1; + + if (IsLeafNode(last_node)) + throw ExtractException("Huffman: prefix already exists"); + + if (bitpos == repeat_pos) { + if (!IsOpenBranch(last_node, bit)) + throw ExtractException("Huffman: invalid repeating code"); + + int repeat_node = NewNode(); + int next_node = NewNode(); + + SetBranch(last_node, bit, repeat_node); + SetBranch(repeat_node, bit, repeat_node); + SetBranch(repeat_node, bit ^ 1, next_node); + last_node = next_node; + + bitpos++; // Terminating bit already handled, skip it. + } + else { + if (IsOpenBranch(last_node, bit)) + SetBranch(last_node, bit, NewNode()); + last_node = Branch(last_node, bit); + } + } + + if (!IsEmptyNode(last_node)) + throw ExtractException("Huffman: prefix already exists"); + SetLeafValue(last_node, value); +} + + + +// Add a new value in the decoder (low bit first). +void HuffmanDecoder::AddValueLF(int value, uint32_t code, int length) +{ + AddValue(value, ReverseN(code, length), length, length); +} + + +// Add a new value in the decoder (low bit first). +void HuffmanDecoder::AddValueLF(int value, uint32_t code, int length, int repeat_pos) +{ + AddValue(value, ReverseN(code, length), length, repeat_pos); +} + + + +// Get the next symbol from a bit reader. +int HuffmanDecoder::NextSymbol(utils::BitReader &input) +{ + if (!table) + throw ExtractException("Huffman: search table not built"); + + int bits = input.ReadWord(table_size, false); + int length = table[bits].length; + int value = table[bits].value; + + if (length < 0) + throw ExtractException("Huffman: invalid prefix code when getting next symbol [length]"); + + if (length <= table_size) { + input.SkipBits(length); + return value; + } + + input.SkipBits(table_size); + int node = value; + + for (int bit; !IsLeafNode(node); node = Branch(node, bit)) { + bit = input.ReadBit(); + if (IsOpenBranch(node, bit)) + throw ExtractException("Huffman: invalid prefix code when getting next symbol [code]"); + } + + return LeafValue(node); +} + + + +// Make search table (Little Endian). +void HuffmanDecoder::MakeTableRecursLE(int node, HuffmanTableEntry *table, + int depth) +{ + int curr_table_size = (1 << (table_size - depth)); + int curr_stride = (1 << depth); + + if (IsLeafNode(node)) { + for (int i = 0; i < curr_table_size; i++) { + table[i * curr_stride].length = depth; + table[i * curr_stride].value = LeafValue(node); + } + } + else if (IsInvalidNode(node)) { + for (int i = 0; i < curr_table_size; i++) + table[i * curr_stride].length = -1; + } + else { + if (depth == table_size) { + table[0].length = table_size + 1; + table[0].value = node; + } else { + MakeTableRecursLE(LeftBranch(node), table, depth + 1); + MakeTableRecursLE(RightBranch(node), table + curr_stride, depth + 1); + } + } +} + + +// Make search table (Big Endian). +void HuffmanDecoder::MakeTableRecursBE(int node, HuffmanTableEntry *table, + int depth) +{ + int curr_table_size = (1 << (table_size - depth)); + + if (IsInvalidNode(node)) { + for (int i = 0; i < curr_table_size; i++) + table[i].length = -1; + } + else if (IsLeafNode(node)) { + for(int i = 0; i < curr_table_size; i++) { + table[i].length = depth; + table[i].value = LeafValue(node); + } + } + else { + if (depth == table_size) { + table[0].length = table_size + 1; + table[0].value = node; + } else { + MakeTableRecursBE(LeftBranch(node), table, depth + 1); + MakeTableRecursBE(RightBranch(node), table + curr_table_size/2, depth + 1); + } + } +} + + +// Make the search table. +void HuffmanDecoder::MakeTable(bool is_LE) +{ + constexpr int kMaxTableSize = 10; + + if (max_length < min_length) table_size = kMaxTableSize; + else if (max_length >= kMaxTableSize) table_size = kMaxTableSize; + else table_size = max_length; + + table = std::make_unique(1 << table_size); + + if (is_LE) MakeTableRecursLE(0, table.get(), 0); + else MakeTableRecursBE(0, table.get(), 0); +} + + + +} // namespace stuffit +} // namespace maconv diff --git a/src/stuffit/utils/huffman.h b/src/stuffit/utils/huffman.h new file mode 100644 index 0000000..669968e --- /dev/null +++ b/src/stuffit/utils/huffman.h @@ -0,0 +1,122 @@ +/* + +Huffman decoder. + +The code in this file is based on TheUnarchiver. +See README.md and docs/licenses/TheUnarchiver.txt for more information. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include "utils/bit_reader.h" + +#include +#include + +namespace maconv { +namespace stuffit { + + +// A node in the Huffman tree. +struct HuffmanTreeNode { + int branches[2]; +}; + +struct HuffmanTableEntry { + uint32_t length; + int32_t value; +}; + + + +// Huffman code decoder. +struct HuffmanDecoder { + + // Copy an Huffman decoder. + HuffmanDecoder &operator=(const HuffmanDecoder &); + + // Initialize the decoder. + void Initialize(); + void Initialize(const int *lengths, int numsymbols, int maxcodelength, bool zeros); + + + // Add a new value in the decoder (high bit first). + void AddValue(int value, uint32_t code, int length); + void AddValue(int value, uint32_t code, int length, int repeat_pos); + + // Add a new value in the decoder (low bit first). + void AddValueLF(int value, uint32_t code, int length); + void AddValueLF(int value, uint32_t code, int length, int repeat_pos); + + + // Get the next symbol from a bit reader. + int NextSymbol(utils::BitReader &input); + + // Make the search table. + void MakeTable(bool is_LE); + +protected: + + // Get a pointer on a node. + HuffmanTreeNode *NodePtr(int node) { return &tree[node]; } + + // Get/set node ID on branch |bit| of |node|. + int Branch(int node, int bit) { return NodePtr(node)->branches[bit]; } + void SetBranch(int node, int bit, int next) { NodePtr(node)->branches[bit] = next; } + + // Get/set left branch of |node|. + int LeftBranch(int node) { return Branch(node, 0); } + void SetLeftBranch(int node, int next) { SetBranch(node, 0, next); } + + // Get/set right branch of |node|. + int RightBranch(int node) { return Branch(node, 1); } + void SetRightBranch(int node, int next) { SetBranch(node, 1, next); } + + // Get/set leaf value. + int LeafValue(int node) { return LeftBranch(node); } + void SetLeafValue(int node, int v) { SetLeftBranch(node, v); SetRightBranch(node, v); } + + // Set/get whether a node is empty or not. + void SetEmptyNode(int node) { SetLeftBranch(node, -1); SetRightBranch(node, -2); } + bool IsEmptyNode(int node) { return LeftBranch(node) == -1 && RightBranch(node) == -2; } + + // Get information on a node. + bool IsInvalidNode(int node) { return node < 0; } + bool IsOpenBranch(int node, int bit) { return IsInvalidNode(Branch(node, bit)); } + bool IsLeafNode(int node) { return LeftBranch(node) == RightBranch(node); } + + // Create a new node. + int NewNode() { tree.push_back({0}); SetEmptyNode(tree.size()-1); + return tree.size()-1; } + + + // Make search table. + void MakeTableRecursLE(int node, HuffmanTableEntry *table, int depth); + void MakeTableRecursBE(int node, HuffmanTableEntry *table, int depth); + + + std::vector tree; + int num_entries, min_length, max_length; + + int table_size; + std::unique_ptr table; +}; + + +} // namespace stuffit +} // namespace maconv diff --git a/src/utils/bit_reader.cc b/src/utils/bit_reader.cc new file mode 100644 index 0000000..b11a02e --- /dev/null +++ b/src/utils/bit_reader.cc @@ -0,0 +1,128 @@ +/* + +Read a buffer by group of bits (not necessarily multiple of 8). + +Copyright (C) 2019, Guillaume Gonnet + +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 "utils/bit_reader.h" + +#include + +namespace maconv { +namespace utils { + + + +// Load a buffer of |length| in the reader. +void BitReader::Load(uint8_t *data, int length) +{ + this->data = data; + this->end = data + length; +} + + +// Ignore a number of bits (n can be > 32). +void BitReader::IgnoreBits(int n) +{ + for (; n > 0; n -= 24) + ReadWord(std::min(n, 24)); +} + + + +// Refill the bit cache. +void BitReaderBE::FillBitCache() +{ + int num_bytes = std::min((32 - num_bits) / 8, (int)(end - data)); + num_bits += (8 * num_bytes); + + for (int i = 0; i < num_bytes; i++) + bits = (bits << 8) | *(data++); +} + + +// Read a word (i.e. n <= 32 bits). +uint32_t BitReaderBE::ReadWord(int n, bool skip) +{ + if (n > num_bits) + FillBitCache(); + + uint32_t ret = (bits >> (num_bits - n)) & ((1 << n) - 1); + if (skip) SkipBits(n); + return ret; +} + + +// Read a long word (n can be > 25 bits). +uint32_t BitReaderBE::ReadLongWord(int n, bool skip) +{ + if (n <= 25) return ReadWord(n, skip); + int bits = ReadWord(25, skip) << (n - 25); + return bits | ReadWord(n - 25, skip); +} + + +// Skip some bits (that has been readed). +void BitReaderBE::SkipBits(int n) +{ + num_bits -= n; +} + + + +// Refill the bit cache. +void BitReaderLE::FillBitCache() +{ + int num_bytes = std::min((32 - num_bits) / 8, (int)(end - data)); + + for (int i = 0; i < num_bytes; i++, num_bits += 8) + bits |= *(data++) << num_bits; +} + + +// Read a word (i.e. n <= 32 bits). +uint32_t BitReaderLE::ReadWord(int n, bool skip) +{ + if (n > num_bits) + FillBitCache(); + + uint32_t ret = bits & ((1 << n) - 1); + if (skip) SkipBits(n); + return ret; +} + + +// Read a long word (n can be > 25 bits). +uint32_t BitReaderLE::ReadLongWord(int n, bool skip) +{ + if (n <= 25) return ReadWord(n, skip); + int bits = ReadWord(25, skip); + return (ReadWord(n - 25, skip) << 25) | bits; +} + + +// Skip some bits (that has been readed). +void BitReaderLE::SkipBits(int n) +{ + bits >>= n; + num_bits -= n; +} + + + +} // namespace utils +} // namespace maconv diff --git a/src/utils/bit_reader.h b/src/utils/bit_reader.h new file mode 100644 index 0000000..b0447db --- /dev/null +++ b/src/utils/bit_reader.h @@ -0,0 +1,100 @@ +/* + +Read a buffer by group of bits (not necessarily multiple of 8). + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include + +namespace maconv { +namespace utils { + + +// Read a buffer by group of bits (not necessarily multiple of 8). +struct BitReader { + + // Load a buffer of |length| in the reader. + void Load(uint8_t *data, int length); + + + // Is the reader at end? + bool HasEnded(int n = 0) { return data == end && num_bits <= n; } + + // Ignore a number of bits (n can be > 32). + void IgnoreBits(int n); + + + // Read a single bit. + virtual uint8_t ReadBit() { return ReadWord(1); }; + + // Read a word (i.e. n <= 25 bits). + virtual uint32_t ReadWord(int n, bool skip = true) = 0; + + // Read a long word (n can be > 25). + virtual uint32_t ReadLongWord(int n, bool skip = true) = 0; + + // Skip some bits (that has been readed). + virtual void SkipBits(int n) = 0; + + + uint8_t *data; // Current pointer on data. + uint8_t *end; // Length of the buffer. + + uint32_t bits = 0; // Bit cache. + int num_bits = 0; // Number of bits in the cache. +}; + + + +// BitReader that reads Big Endian integers. +struct BitReaderBE : BitReader { + + // Read a word (i.e. <= 32 bits). + uint32_t ReadWord(int n, bool skip = true) override; + + // Read a long word (n can be > 25 bits). + uint32_t ReadLongWord(int n, bool skip = true) override; + + // Skip some bits (that has been readed). + void SkipBits(int n) override; + + // Refill the bit cache. + void FillBitCache(); +}; + + +// BitReader that reads Little Endian integers. +struct BitReaderLE : BitReader { + + // Read a word (i.e. <= 25 bits). + uint32_t ReadWord(int n, bool skip = true) override; + + // Read a long word (n can be > 25 bits). + uint32_t ReadLongWord(int n, bool skip = true) override; + + // Skip some bits (that has been readed). + void SkipBits(int n) override; + + // Refill the bit cache. + void FillBitCache(); +}; + + +} // namespace utils +} // namespace maconv diff --git a/src/utils/buffer_stream.cc b/src/utils/buffer_stream.cc new file mode 100644 index 0000000..416b03d --- /dev/null +++ b/src/utils/buffer_stream.cc @@ -0,0 +1,119 @@ +/* + +Create a stream from a buffer. + +Copyright (C) 2019, Guillaume Gonnet + +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 "utils/buffer_stream.h" + +namespace maconv { +namespace utils { + + + +// "RawDataStreamBuf" constructor. +RawDataStreamBuf::RawDataStreamBuf(uint8_t *data, uint32_t length) +{ + SetData(data, length); +} + + +// Set the data and length for this stream buffer. +void RawDataStreamBuf::SetData(uint8_t *data, uint32_t length) +{ + char *start = reinterpret_cast(data); + char *end = reinterpret_cast(data + length); + setg(start, start, end); + setp(start, end); +} + + + +// Set the position indicator relative to some other position. +auto RawDataStreamBuf::seekoff(off_type off, std::ios_base::seekdir dir, + std::ios_base::openmode which) -> pos_type +{ + // If dir is beginning or end: set an absolute position directly. + switch (dir) { + case std::ios_base::beg: + return seekpos(off, which); + case std::ios_base::end: + return seekpos(off + (egptr() - eback()), which); + } + + // Set position from cursor (return twice if both in and out are set). + if (which & std::ios_base::in) + seekpos(off + (gptr() - eback()), std::ios_base::in); + if (which & std::ios_base::out) + seekpos(off + (pptr() - eback()), std::ios_base::out); + return ((which & std::ios_base::in) ? gptr() : pptr()) - eback(); +} + + +// Set the position indicator to an absolute position. +auto RawDataStreamBuf::seekpos(pos_type pos, std::ios_base::openmode which) + -> pos_type +{ + if (which & std::ios_base::in) + setg(eback(), eback() + pos, egptr()); + if (which & std::ios_base::out) + pbump(pos - (pptr() - eback())); + + return pos; +} + + + +// Get the number of characters available for input. +std::streamsize RawDataStreamBuf::showmanyc() +{ + return egptr() - gptr(); +} + + +// Reads count characters from the input sequence. +std::streamsize RawDataStreamBuf::xsgetn(char *p, std::streamsize n) +{ + std::streamsize length = std::min(n, egptr() - gptr()); + std::copy(gptr(), gptr() + length, p); + gbump(length); + return length; +} + + +// Write count characters to the output sequence. +std::streamsize RawDataStreamBuf::xsputn(const char *p, std::streamsize n) +{ + std::streamsize length = std::min(n, epptr() - pptr()); + std::copy(p, p + length, pptr()); + pbump(length); + return length; +} + + + + +// "BufferStreamBuf" constructor. +BufferStreamBuf::BufferStreamBuf(uint32_t length) + : buffer{new uint8_t[length]} +{ + SetData(buffer.get(), length); +} + + +} // namespace utils +} // namespace maconv diff --git a/src/utils/buffer_stream.h b/src/utils/buffer_stream.h new file mode 100644 index 0000000..7c0047e --- /dev/null +++ b/src/utils/buffer_stream.h @@ -0,0 +1,69 @@ +/* + +Create a stream from a buffer. + +Copyright (C) 2019, Guillaume Gonnet + +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 + +#include +#include + +namespace maconv { +namespace utils { + + +// A stream buffer from raw memory data. +struct RawDataStreamBuf : std::streambuf { + + RawDataStreamBuf() = default; + RawDataStreamBuf(uint8_t *data, uint32_t length); + + // Set the data and length for this stream buffer. + void SetData(uint8_t *data, uint32_t length); + +protected: + + // Get the number of characters available for input. + std::streamsize showmanyc() override; + + // Set the position indicator relative to some other position. + pos_type seekoff(off_type off, std::ios_base::seekdir dir, + std::ios_base::openmode which) override; + + // Set the position indicator to an absolute position. + pos_type seekpos(pos_type pos, std::ios_base::openmode which) override; + + // Read count characters from the input sequence. + std::streamsize xsgetn(char *p, std::streamsize n) override; + + // Write count characters to the output sequence. + std::streamsize xsputn(const char *s, std::streamsize n) override; +}; + + +// A stream buffer from a buffer. +struct BufferStreamBuf : RawDataStreamBuf { + + BufferStreamBuf(uint32_t length); + + std::unique_ptr buffer; +}; + + +} // namespace utils +} // namespace maconv diff --git a/vendors/CLI11.hpp b/vendors/CLI11.hpp new file mode 100644 index 0000000..51a23dc --- /dev/null +++ b/vendors/CLI11.hpp @@ -0,0 +1,4113 @@ +#pragma once + +// CLI11: Version 1.6.2 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v1.6.2 +// +// From LICENSE: +// +// CLI11 1.6 Copyright (c) 2017-2018 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, 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 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 the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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. + + +// Standard combined includes: + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Verbatim copy from CLI/Version.hpp: + + +#define CLI11_VERSION_MAJOR 1 +#define CLI11_VERSION_MINOR 6 +#define CLI11_VERSION_PATCH 2 +#define CLI11_VERSION "1.6.2" + + + + +// Verbatim copy from CLI/Macros.hpp: + + +// The following version macro is very similar to the one in PyBind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if __MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + + + + +// Verbatim copy from CLI/Optional.hpp: + +#ifdef __has_include + +// You can explicitly enable or disable support +// by defining these to 1 or 0. +#if defined(CLI11_CPP17) && __has_include() && \ + !defined(CLI11_STD_OPTIONAL) +#define CLI11_STD_OPTIONAL 1 +#endif + +#if defined(CLI11_CPP14) && __has_include() && \ + !defined(CLI11_EXPERIMENTAL_OPTIONAL) \ + && (!defined(CLI11_STD_OPTIONAL) || CLI11_STD_OPTIONAL == 0) +#define CLI11_EXPERIMENTAL_OPTIONAL 1 +#endif + +#if __has_include() && !defined(CLI11_BOOST_OPTIONAL) +#include +#if BOOST_VERSION >= 105800 +#define CLI11_BOOST_OPTIONAL 1 +#endif +#endif + +#endif + +#if CLI11_STD_OPTIONAL +#include +#endif +#if CLI11_EXPERIMENTAL_OPTIONAL +#include +#endif +#if CLI11_BOOST_OPTIONAL +#include +#endif + + +// From CLI/Version.hpp: + + + +// From CLI/Macros.hpp: + + + +// From CLI/Optional.hpp: + +namespace CLI { + +#if CLI11_STD_OPTIONAL +template std::istream &operator>>(std::istream &in, std::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#if CLI11_EXPERIMENTAL_OPTIONAL +template std::istream &operator>>(std::istream &in, std::experimental::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#if CLI11_BOOST_OPTIONAL +template std::istream &operator>>(std::istream &in, boost::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +// Export the best optional to the CLI namespace +#if CLI11_STD_OPTIONAL +using std::optional; +#elif CLI11_EXPERIMENTAL_OPTIONAL +using std::experimental::optional; +#elif CLI11_BOOST_OPTIONAL +using boost::optional; +#endif + +// This is true if any optional is found +#if CLI11_STD_OPTIONAL || CLI11_EXPERIMENTAL_OPTIONAL || CLI11_BOOST_OPTIONAL +#define CLI11_OPTIONAL 1 +#endif + +} // namespace CLI + +// From CLI/StringTools.hpp: + +namespace CLI { +namespace detail { + +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +inline std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) + elems.emplace_back(""); + else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + size_t start = 0; + for(const auto &i : v) { + if(start++ > 0) + s << delim; + s << i; + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +inline std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +/// Trim anything from left of string +inline std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +/// Trim whitespace from right of string +inline std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim anything from right of string +inline std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast(wid)) << ""; + out << description; + } + out << "\n"; + return out; +} + +/// Verify the first character of an option +template bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; } + +/// Verify following characters of an option +template bool valid_later_char(T c) { + return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-'; +} + +/// Verify an option name +inline bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) + return false; + for(auto c : str.substr(1)) + if(!valid_later_char(c)) + return false; + return true; +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// Split a string '"one two" "three"' into 'one two', 'three' +inline std::vector split_up(std::string str) { + + std::vector delims = {'\'', '\"'}; + auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; + trim(str); + + std::vector output; + + while(!str.empty()) { + if(str[0] == '\'') { + auto end = str.find('\'', 1); + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + str = str.substr(end + 1); + } else { + output.push_back(str.substr(1)); + str = ""; + } + } else if(str[0] == '\"') { + auto end = str.find('\"', 1); + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + str = str.substr(end + 1); + } else { + output.push_back(str.substr(1)); + str = ""; + } + + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it, str.end()); + } else { + output.push_back(str); + str = ""; + } + } + trim(str); + } + + return output; +} + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +inline std::string fix_newlines(std::string leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +/// Find and replace a subtring with another substring +inline std::string find_and_replace(std::string str, std::string from, std::string to) { + + size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +} // namespace detail +} // namespace CLI + +// From CLI/Error.hpp: + +namespace CLI { + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string name, std::string msg, int exit_code) : parent(std::move(name), std::move(msg), exit_code) {} \ + name(std::string name, std::string msg, ExitCodes exit_code) \ + : parent(std::move(name), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int exit_code; + std::string name{"Error"}; + + public: + int get_exit_code() const { return exit_code; } + + std::string get_name() const { return name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), exit_code(exit_code), name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually somethign like --help-all on command line +class CallForAllHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(size_t min_subcom) { + if(min_subcom == 1) + return RequiredError("A subcommand"); + else + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, size_t recieved) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(recieved)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(recieved)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + +} // namespace CLI + +// From CLI/TypeTools.hpp: + +namespace CLI { + +// Type tools + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. + +template using enable_if_t = typename std::enable_if::type; + +/// Check to see if something is a vector (fail check by default) +template struct is_vector { static const bool value = false; }; + +/// Check to see if something is a vector (true if actually a vector) +template struct is_vector> { static bool const value = true; }; + +/// Check to see if something is bool (fail check by default) +template struct is_bool { static const bool value = false; }; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool { static bool const value = true; }; + +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value && std::is_signed::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// This one should not be used, since vector types print the internal type +template ::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "VECTOR"; +} + +template ::value && !std::is_integral::value && !is_vector::value, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} + +// Lexical cast + +/// Signed integers / enums +template ::value && std::is_signed::value), detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + long long output_ll = std::stoll(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Unsigned integers +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + if(!input.empty() && input.front() == '-') + return false; // std::stoull happily converts negative values to junk without any errors. + + try { + size_t n = 0; + unsigned long long output_ll = std::stoull(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Floats +template ::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + output = static_cast(std::stold(input, &n)); + return n == input.size(); + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// String and similar +template ::value && !std::is_integral::value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + output = input; + return true; +} + +/// Non-string parsable +template ::value && !std::is_integral::value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + std::istringstream is; + + is.str(input); + is >> output; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +} // namespace detail +} // namespace CLI + +// From CLI/Split.hpp: + +namespace CLI { +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } else + return false; +} + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { + auto loc = current.find("="); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } else + return false; +} + +// Splits a string into multiple long and short names +inline std::vector split_names(std::string current) { + std::vector output; + size_t val; + while((val = current.find(",")) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +/// Get a vector of short names, one of long names, and a single name +inline std::tuple, std::vector, std::string> +get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + + for(std::string name : input) { + if(name.length() == 0) + continue; + else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(pos_name.length() > 0) + throw BadNameString::MultiPositionalNames(name); + pos_name = name; + } + } + + return std::tuple, std::vector, std::string>( + short_names, long_names, pos_name); +} + +} // namespace detail +} // namespace CLI + +// From CLI/ConfigFwd.hpp: + +namespace CLI { + +class App; + +namespace detail { + +/// Comma separated join, adds quotes if needed +inline std::string ini_join(std::vector args) { + std::ostringstream s; + size_t start = 0; + for(const auto &arg : args) { + if(start++ > 0) + s << " "; + + auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace(ch, std::locale()); }); + if(it == arg.end()) + s << arg; + else if(arg.find(R"(")") == std::string::npos) + s << R"(")" << arg << R"(")"; + else + s << R"(')" << arg << R"(')"; + } + + return s.str(); +} + +} // namespace detail + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents; + + /// This is the name + std::string name; + + /// Listing of inputs + std::vector inputs; + + /// The list of parents and name joined by "." + std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Convert a flag to a bool + virtual std::vector to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + std::string val = item.inputs.at(0); + val = detail::to_lower(val); + + if(val == "true" || val == "on" || val == "yes") { + return std::vector(1); + } else if(val == "false" || val == "off" || val == "no") { + return std::vector(); + } else { + try { + size_t ui = std::stoul(val); + return std::vector(ui); + } catch(const std::invalid_argument &) { + throw ConversionError::TrueFalse(item.fullname()); + } + } + } else { + throw ConversionError::TooManyInputsFlag(item.fullname()); + } + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + std::vector from_file(const std::string &name) { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI files +class ConfigINI : public Config { + public: + std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; + + std::vector from_config(std::istream &input) const override { + std::string line; + std::string section = "default"; + + std::vector output; + + while(getline(input, line)) { + std::vector items_buffer; + + detail::trim(line); + size_t len = line.length(); + if(len > 1 && line[0] == '[' && line[len - 1] == ']') { + section = line.substr(1, len - 2); + } else if(len > 0 && line[0] != ';') { + output.emplace_back(); + ConfigItem &out = output.back(); + + // Find = in string, split and recombine + auto pos = line.find('='); + if(pos != std::string::npos) { + out.name = detail::trim_copy(line.substr(0, pos)); + std::string item = detail::trim_copy(line.substr(pos + 1)); + items_buffer = detail::split_up(item); + } else { + out.name = detail::trim_copy(line); + items_buffer = {"ON"}; + } + + if(detail::to_lower(section) != "default") { + out.parents = {section}; + } + + if(out.name.find('.') != std::string::npos) { + std::vector plist = detail::split(out.name, '.'); + out.name = plist.back(); + plist.pop_back(); + out.parents.insert(out.parents.end(), plist.begin(), plist.end()); + } + + out.inputs.insert(std::end(out.inputs), std::begin(items_buffer), std::end(items_buffer)); + } + } + return output; + } +}; + +} // namespace CLI + +// From CLI/Validators.hpp: + +namespace CLI { + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +struct Validator { + /// This is the type name, if empty the type name will not be changed + std::string tname; + + /// This it the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func; + + /// This is the required operator for a validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { return func(str); }; + + /// Combining validators is a new validator + Validator operator&(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func; + const std::function &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(!s1.empty() && !s2.empty()) + return s1 + " & " + s2; + else + return s1 + s2; + }; + return newval; + } + + /// Combining validators is a new validator + Validator operator|(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func; + const std::function &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(s1.empty() || s2.empty()) + return std::string(); + else + return s1 + " & " + s2; + }; + return newval; + } +}; + +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// Check for an existing file (returns error message if check fails) +struct ExistingFileValidator : public Validator { + ExistingFileValidator() { + tname = "FILE"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "File does not exist: " + filename; + } else if(is_dir) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing directory (returns error message if check fails) +struct ExistingDirectoryValidator : public Validator { + ExistingDirectoryValidator() { + tname = "DIR"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "Directory does not exist: " + filename; + } else if(!is_dir) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing path +struct ExistingPathValidator : public Validator { + ExistingPathValidator() { + tname = "PATH"; + func = [](const std::string &filename) { + struct stat buffer; + bool const exist = stat(filename.c_str(), &buffer) == 0; + if(!exist) { + return "Path does not exist: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an non-existing path +struct NonexistentPathValidator : public Validator { + NonexistentPathValidator() { + tname = "PATH"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + if(exist) { + return "Path already exists: " + filename; + } + return std::string(); + }; + } +}; +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Produce a range (factory). Min and max are inclusive. +struct Range : public Validator { + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Range(T min, T max) { + std::stringstream out; + out << detail::type_name() << " in [" << min << " - " << max << "]"; + + tname = out.str(); + func = [min, max](std::string input) { + T val; + detail::lexical_cast(input, val); + if(val < min || val > max) + return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); + + return std::string(); + }; + } + + /// Range of one value is 0 to value + template explicit Range(T max) : Range(static_cast(0), max) {} +}; + +/// @} + +} // namespace CLI + +// From CLI/FormatterFwd.hpp: + +namespace CLI { + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, //< The normal, detailed help + All, //< A fully expanded help + Sub, //< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the first column + size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + virtual ~FormatterBase() = default; + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + else + return labels_.at(key); + } + + /// Get the current column width + size_t get_column_width() const { return column_width_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + virtual std::string make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App *, std::string, AppFormatMode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + +} // namespace CLI + +// From CLI/Option.hpp: + +namespace CLI { + +using results_t = std::vector; +using callback_t = std::function; + +class Option; +class App; + +using Option_p = std::unique_ptr