diff --git a/ext/libsmacker/CMakeLists.txt b/ext/libsmacker/CMakeLists.txt new file mode 100644 index 000000000..08c19d7cd --- /dev/null +++ b/ext/libsmacker/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(smacker STATIC "smacker.c") +target_include_directories(smacker PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/ext/libsmacker/COPYING b/ext/libsmacker/COPYING new file mode 100644 index 000000000..4362b4915 --- /dev/null +++ b/ext/libsmacker/COPYING @@ -0,0 +1,502 @@ + 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/ext/libsmacker/INSTALL b/ext/libsmacker/INSTALL new file mode 100644 index 000000000..f91fcde4c --- /dev/null +++ b/ext/libsmacker/INSTALL @@ -0,0 +1,84 @@ +libsmacker +http://libsmacker.sourceforge.net + +Compilation and Install Notes +--- + +There are two ways to compile libsmacker for use in your own programs. + +1. DIRECT COMPILATION + +This method is preferred when you can't (or don't want to) build a system +library. Simply take the various smk_* and smacker.* files, add them to your +project, and compile them in as usual. + +All external functions (those you, a user, would care about) are contained in +smacker.h - so, in your main program, just + + #include "smacker.h" + +and you're ready to decode smk files. + +To ease the process of managing libsmacker compilation, and to keep your source +tree uncluttered, you may wish to use libsmacker as a compiled static library +instead. This allows you to redistribute it, or compile once / separately from +your project, but with the advantage that linking against it does not create a +run-time dependency. See the next section. + +2. LIBRARY COMPILATION + +libsmacker is distributed as a standard autotools-based package. The build +requires a GNU- or BSD-compatible "make" program. Optionally, you may need +autotools development packages installed to regenerate some distribution +files if you wish to provide your own distribution. + +From the root directory, run "./configure" - this will perform some checks on +your system to find the name of your C compiler, desired compilation flags, +etc. At the end of the configure process, there will be a generated Makefile. +Simply run "make" to build your files. + +Compilation via autotools results in these files - though depending on your +system, compilation output may go into a .libs/ subfolder: + +* libsmacker.a +* libsmacker.so (and several versioned symlinks) +* driver +* smk2avi + +The "libsmacker.a" is a static library file which can be compiled in with an +existing codebase: + + cc main.c extra.c libsmacker.a + +or (better) + + cc main.c -lsmacker + +The "libsmacker.so" is a shared object file. Compiling with this means it +needs to be distributed along with your application; this might be useful to +save space if you have multiple programs that use libsmacker. + +"driver" and "smk2avi" are example programs that show how to use libsmacker. +They can also test a successful build. + +3. INSTALLATION + +The makefile from "configure" can be used to install the .so and corresponding +.h file to correct locations - simply run "make install". This will copy the +files to $PREFIX and make them available system-wide to other developers or +applications. + +4. CONTROLLING BUILD PARAMETERS +Altering build parameters is done by different flags to "configure". The +default is to build both a static AND shared library, as well as the two demo +programs. Also by default, CFLAGS is set to "-O2 -g". To change this, for +example making a debug build, use something like: + + ./configure CFLAGS="-O0" + +or an optimized release build: + + ./configure CFLAGS="-march=i686 -O2 -DNDEBUG" + +Once reconfigured, running "make" will apply CFLAGS to calls to the C compiler, +passing your options on to the underlying build system. diff --git a/ext/libsmacker/Makefile.am b/ext/libsmacker/Makefile.am new file mode 100644 index 000000000..c99998716 --- /dev/null +++ b/ext/libsmacker/Makefile.am @@ -0,0 +1,17 @@ +ACLOCAL_AMFLAGS = -I m4 + +AM_CPPFLAGS = -Wall -Wextra + +lib_LTLIBRARIES = libsmacker.la +libsmacker_la_SOURCES = smacker.c +libsmacker_la_LDFLAGS = -version-info 1:2:0 + +noinst_PROGRAMS = driver smk2avi + +driver_SOURCES = driver.c +driver_LDADD = $(lib_LTLIBRARIES) +driver_DEPENDENCIES = $(lib_LTLIBRARIES) + +smk2avi_SOURCES = smk2avi.c +smk2avi_LDADD = $(lib_LTLIBRARIES) +smk2avi_DEPENDENCIES = $(lib_LTLIBRARIES) diff --git a/ext/libsmacker/README b/ext/libsmacker/README new file mode 100644 index 000000000..5f9840317 --- /dev/null +++ b/ext/libsmacker/README @@ -0,0 +1,55 @@ +libsmacker +A C library for decoding .smk Smacker Video files + +version 1.2.0 +2021-06-02 + +(c) Greg Kennedy 2013-2021 +http://libsmacker.sourceforge.net +---- + +--- +Introduction +--- +libsmacker is a cross-platform C library which can be used for decoding Smacker Video files produced by RAD Game Tools. Smacker Video was the king of video middleware in the 1990s, and its 256-color compressed video format was used in over 2600 software titles. + +libsmacker implements the minimum feature set required from smackw32.dll to get an smk off a disk and the frames / audio into a buffer in the correct order. + +--- +License +--- +libsmacker is released under the Lesser GNU Public License, v2.1. See the file COPYING for more information. + +--- +Usage +--- +See the webpage for sample code and function documentation. The source package additionally includes a pair of driver programs: +* driver.c - dumps all frames of a file to a bmp/ subdirectory, and all audio as raw streams to out_*.raw files in CWD +* smk2avi.c - converts smk file(s) to AVI files - uncompressed 24-bit color and PCM audio stream. + +Though the libraries are "bulletproofed" the sample apps are not: be cautious if you plan to implement in some critical environment. + +--- +Changelog +--- +1.2.0 +* Major refactor of data structures for performance +* Merge all files into a single unified .c source file +* Support audio-only .smk files +1.1.1 +* Re-license under LGPL 2.1 +1.1 +* Switch to autotools-based build +* Incorporates patches from Dalerank Slim, Gennady Trafimenkov, and Bianca van Schaik +* Performance improvements and code cleanup / safety. +1.0 +* Initial revision + +--- +Contact +--- +Questions/comments: +* by email: kennedy.greg@gmail.com +* by website: http://libsmacker.sourceforge.net + +Enjoy! diff --git a/ext/libsmacker/configure.ac b/ext/libsmacker/configure.ac new file mode 100644 index 000000000..192ca05ef --- /dev/null +++ b/ext/libsmacker/configure.ac @@ -0,0 +1,13 @@ +AC_INIT([libsmacker], [1.2.0], [kennedy.greg@gmail.com]) + +AC_CONFIG_MACRO_DIRS([m4]) + +AM_INIT_AUTOMAKE([foreign]) + +AC_PROG_CC +AC_HEADER_ASSERT + +AC_PROG_LIBTOOL + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/ext/libsmacker/driver.c b/ext/libsmacker/driver.c new file mode 100644 index 000000000..8f5cf5cdd --- /dev/null +++ b/ext/libsmacker/driver.c @@ -0,0 +1,170 @@ +/* + * libsmacker - A C library for decoding .smk Smacker Video files Copyright + * (C) 2012-2013 Greg Kennedy + * + * See smacker.h for more information. + * + * driver.c Driver program + */ + + +#include "smacker.h" + +#include + +void +dump_bmp(const unsigned char *pal, const unsigned char *image_data, unsigned int w, unsigned int h, unsigned int framenum) +{ + int i; + FILE *fp; + char filename [128]; + unsigned int temp; + sprintf(filename, "bmp/out_%04u.bmp", framenum); + fp = fopen(filename, "wb"); + if (!fp) { fprintf(stderr, "Failed to open %s for write\n", filename); return; } + fwrite("BM", 2, 1, fp); + temp = 1078 + (w * h); + fwrite(&temp, 4, 1, fp); + temp = 0; + fwrite(&temp, 4, 1, fp); + temp = 1078; + fwrite(&temp, 4, 1, fp); + temp = 40; + fwrite(&temp, 4, 1, fp); + fwrite(&w, 4, 1, fp); + fwrite(&h, 4, 1, fp); + temp = 1; + fwrite(&temp, 2, 1, fp); + temp = 8; + fwrite(&temp, 4, 1, fp); + temp = 0; + fwrite(&temp, 2, 1, fp); + temp = w * h; + fwrite(&temp, 4, 1, fp); + temp = 0; + fwrite(&temp, 4, 1, fp); + fwrite(&temp, 4, 1, fp); + temp = 256; + fwrite(&temp, 4, 1, fp); + temp = 256; + fwrite(&temp, 4, 1, fp); + temp = 0; + for (i = 0; i < 256; i++) + { + fwrite(&pal[(i * 3) + 2], 1, 1, fp); + fwrite(&pal[(i * 3) + 1], 1, 1, fp); + fwrite(&pal[(i * 3)], 1, 1, fp); + fwrite(&temp, 1, 1, fp); + } + + for (i = h - 1; i >= 0; i--) + { + fwrite(&image_data[i * w], w, 1, fp); + } + + fclose(fp); +} + +int +main(int argc, char *argv[]) +{ + unsigned long w, h, f; + double usf; + smk s; + + char filename [128]; + + FILE *fpo[7] = {NULL}; + + if (argc != 2) + { + printf("Usage: %s file.smk\n", argv[0]); + return -1; + } + /* s = smk_open(argv[1], SMK_MODE_DISK); */ + s = smk_open_file(argv[1], SMK_MODE_MEMORY); + if (s == NULL) + { + printf("Errors encountered opening %s, exiting.\n", argv[1]); + return -1; + } + /* print some info about the file */ + smk_info_all(s, NULL, &f, &usf); + smk_info_video(s, &w, &h, NULL); + + printf("Opened file %s\nWidth: %lu\nHeight: %lu\nFrames: %lu\nFPS: %f\n", argv[1], w, h, f, 1000000.0 / usf); + + unsigned char a_t, a_c[7], a_d[7]; + unsigned long a_r[7]; + + smk_info_audio(s, &a_t, a_c, a_d, a_r); + + int i; + for (i = 0; i < 7; i++) + { + printf("Audio track %d: %u bits, %u channels, %luhz\n", i, a_d[i], a_c[i], a_r[i]); + } + + /* Turn on decoding for palette, video, and audio track 0 */ + smk_enable_video(s, 1); + + for (i = 0; i < 7; i++) + { + if (a_t & (1 << i)) + { + smk_enable_audio(s, i, 1); + sprintf(filename, "out_%01d.raw", i); + fpo[i] = fopen(filename, "wb"); + } else + { + fpo[i] = NULL; + } + } + + //Get a pointer to first frame + + smk_first(s); + + unsigned long cur_frame; + + smk_info_all(s, &cur_frame, NULL, NULL); + dump_bmp(smk_get_palette(s), smk_get_video(s), w, h, cur_frame); + + for (i = 0; i < 7; i++) + { + if (fpo[i] != NULL) + { + fwrite(smk_get_audio(s, i), smk_get_audio_size(s, i), 1, fpo[i]); + } + } + printf(" -> Frame %lu\n", cur_frame); + + + for (cur_frame = 1; cur_frame < f; cur_frame ++) + { + smk_next(s); + /* smk_info_all(s, &cur_frame, NULL, NULL); */ + + dump_bmp(smk_get_palette(s), smk_get_video(s), w, h, cur_frame); + + for (i = 0; i < 7; i++) + { + if (fpo[i] != NULL) + { + fwrite(smk_get_audio(s, i), smk_get_audio_size(s, i), 1, fpo[i]); + } + } + fprintf(stderr," -> Frame %lu\n", cur_frame); + //Advance to next frame + + } + + for (i = 0; i < 7; i++) + if (fpo[i] != NULL) + { + fclose(fpo[i]); + } + smk_close(s); + + return 0; +} diff --git a/ext/libsmacker/smacker.c b/ext/libsmacker/smacker.c new file mode 100644 index 000000000..eef6e7b75 --- /dev/null +++ b/ext/libsmacker/smacker.c @@ -0,0 +1,1797 @@ +/** + libsmacker - A C library for decoding .smk Smacker Video files + Copyright (C) 2012-2021 Greg Kennedy + + See smacker.h for more information. + + smacker.c + Main implementation file of libsmacker. + Open, close, query, render, advance and seek an smk +*/ + +#include "smacker.h" + +#include "smk_malloc.h" + +#include +#include +#include +#include + +/* ************************************************************************* */ +/* BITSTREAM Structure */ +/* ************************************************************************* */ +/* Wraps a block of memory and adds functions to read 1 or 8 bits at a time */ +struct smk_bit_t { + const unsigned char * buffer, * end; + unsigned int bit_num; +}; + +/* ************************************************************************* */ +/* BITSTREAM Functions */ +/* ************************************************************************* */ +/** Initialize a bitstream wrapper */ +static void smk_bs_init(struct smk_bit_t * const bs, const unsigned char * const b, const size_t size) +{ + /* null check */ + assert(bs); + assert(b); + /* set up the pointer to bitstream start and end, and set the bit pointer to 0 */ + bs->buffer = b; + bs->end = b + size; + bs->bit_num = 0; +} + +/* Reads a bit + Returns -1 if error encountered */ +static int smk_bs_read_1(struct smk_bit_t * const bs) +{ + int ret; + /* null check */ + assert(bs); + + /* don't die when running out of bits, but signal */ + if (bs->buffer >= bs->end) { + fputs("libsmacker::smk_bs_read_1(): ERROR: bitstream exhausted.\n", stderr); + return -1; + } + + /* get next bit and store for return */ + ret = (*bs->buffer >> bs->bit_num) & 1; + + /* advance to next bit */ + if (bs->bit_num >= 7) { + /* Out of bits in this byte: next! */ + bs->buffer ++; + bs->bit_num = 0; + } else + bs->bit_num ++; + + return ret; +} + +/* Reads a byte + Returns -1 if error. */ +static int smk_bs_read_8(struct smk_bit_t * const bs) +{ + /* null check */ + assert(bs); + + /* don't die when running out of bits, but signal */ + if (bs->buffer + (bs->bit_num > 0) >= bs->end) { + fputs("libsmacker::smk_bs_read_8(): ERROR: bitstream exhausted.\n", stderr); + return -1; + } + + if (bs->bit_num) { + /* unaligned read */ + int ret = *bs->buffer >> bs->bit_num; + bs->buffer ++; + return ret | (*bs->buffer << (8 - bs->bit_num) & 0xFF); + } + + /* aligned read */ + return *bs->buffer++; +} + +/* ************************************************************************* */ +/* HUFF8 Structure */ +/* ************************************************************************* */ +#define SMK_HUFF8_BRANCH 0x8000 +#define SMK_HUFF8_LEAF_MASK 0x7FFF + +struct smk_huff8_t { + /* Unfortunately, smk files do not store the alloc size of a small tree. + 511 entries is the pessimistic case (N codes and N-1 branches, + with N=256 for 8 bits) */ + size_t size; + unsigned short tree[511]; +}; + +/* ************************************************************************* */ +/* HUFF8 Functions */ +/* ************************************************************************* */ +/* Recursive sub-func for building a tree into an array. */ +static int _smk_huff8_build_rec(struct smk_huff8_t * const t, struct smk_bit_t * const bs) +{ + int bit, value; + assert(t); + assert(bs); + + /* Make sure we aren't running out of bounds */ + if (t->size >= 511) { + fputs("libsmacker::_smk_huff8_build_rec() - ERROR: size exceeded\n", stderr); + return 0; + } + + /* Read the next bit */ + if ((bit = smk_bs_read_1(bs)) < 0) { + fputs("libsmacker::_smk_huff8_build_rec() - ERROR: get_bit returned -1\n", stderr); + return 0; + } + + if (bit) { + /* Bit set: this forms a Branch node. + what we have to do is build the left-hand branch, + assign the "jump" address, + then build the right hand branch from there. + */ + /* track the current index */ + value = t->size ++; + + /* go build the left branch */ + if (! _smk_huff8_build_rec(t, bs)) { + fputs("libsmacker::_smk_huff8_build_rec() - ERROR: failed to build left sub-tree\n", stderr); + return 0; + } + + /* now go back to our current location, and + mark our location as a "jump" */ + t->tree[value] = SMK_HUFF8_BRANCH | t->size; + + /* continue building the right side */ + if (! _smk_huff8_build_rec(t, bs)) { + fputs("libsmacker::_smk_huff8_build_rec() - ERROR: failed to build right sub-tree\n", stderr); + return 0; + } + } else { + /* Bit unset signifies a Leaf node. */ + /* Attempt to read value */ + if ((value = smk_bs_read_8(bs)) < 0) { + fputs("libsmacker::_smk_huff8_build_rec() - ERROR: get_byte returned -1\n", stderr); + return 0; + } + + /* store to tree */ + t->tree[t->size ++] = value; + } + + return 1; +} + +/** + Build an 8-bit Hufftree out of a Bitstream. +*/ +static int smk_huff8_build(struct smk_huff8_t * const t, struct smk_bit_t * const bs) +{ + int bit; + /* null check */ + assert(t); + assert(bs); + + /* Smacker huff trees begin with a set-bit. */ + if ((bit = smk_bs_read_1(bs)) < 0) { + fputs("libsmacker::smk_huff8_build() - ERROR: initial get_bit returned -1\n", stderr); + return 0; + } + + /* OK to fill out the struct now */ + t->size = 0; + + /* First bit indicates whether a tree is present or not. */ + /* Very small or audio-only files may have no tree. */ + if (bit) { + if (! _smk_huff8_build_rec(t, bs)) { + fputs("libsmacker::smk_huff8_build() - ERROR: tree build failed\n", stderr); + return 0; + } + } else + t->tree[0] = 0; + + /* huff trees end with an unset-bit */ + if ((bit = smk_bs_read_1(bs)) < 0) { + fputs("libsmacker::smk_huff8_build() - ERROR: final get_bit returned -1\n", stderr); + return 0; + } + + /* a 0 is expected here, a 1 generally indicates a problem! */ + if (bit) { + fputs("libsmacker::smk_huff8_build() - ERROR: final get_bit returned 1\n", stderr); + return 0; + } + + return 1; +} + +/* Look up an 8-bit value from a basic huff tree. + Return -1 on error. */ +static int smk_huff8_lookup(const struct smk_huff8_t * const t, struct smk_bit_t * const bs) +{ + int bit, index = 0; + /* null check */ + assert(t); + assert(bs); + + while (t->tree[index] & SMK_HUFF8_BRANCH) { + if ((bit = smk_bs_read_1(bs)) < 0) { + fputs("libsmacker::smk_huff8_lookup() - ERROR: get_bit returned -1\n", stderr); + return -1; + } + + if (bit) { + /* take the right branch */ + index = t->tree[index] & SMK_HUFF8_LEAF_MASK; + } else { + /* take the left branch */ + index ++; + } + } + + /* at leaf node. return the value at this point. */ + return t->tree[index]; +} + +/* ************************************************************************* */ +/* HUFF16 Structure */ +/* ************************************************************************* */ +#define SMK_HUFF16_BRANCH 0x80000000 +#define SMK_HUFF16_CACHE 0x40000000 +#define SMK_HUFF16_LEAF_MASK 0x3FFFFFFF + +struct smk_huff16_t { + unsigned int * tree; + size_t size; + + /* recently-used values cache */ + unsigned short cache[3]; +}; + +/* ************************************************************************* */ +/* HUFF16 Functions */ +/* ************************************************************************* */ +/* Recursive sub-func for building a tree into an array. */ +static int _smk_huff16_build_rec(struct smk_huff16_t * const t, struct smk_bit_t * const bs, const struct smk_huff8_t * const low8, const struct smk_huff8_t * const hi8, const size_t limit) +{ + int bit, value; + assert(t); + assert(bs); + assert(low8); + assert(hi8); + + /* Make sure we aren't running out of bounds */ + if (t->size >= limit) { + fputs("libsmacker::_smk_huff16_build_rec() - ERROR: size exceeded\n", stderr); + return 0; + } + + /* Read the first bit */ + if ((bit = smk_bs_read_1(bs)) < 0) { + fputs("libsmacker::_smk_huff16_build_rec() - ERROR: get_bit returned -1\n", stderr); + return 0; + } + + if (bit) { + /* See tree-in-array explanation for HUFF8 above */ + /* track the current index */ + value = t->size ++; + + /* go build the left branch */ + if (! _smk_huff16_build_rec(t, bs, low8, hi8, limit)) { + fputs("libsmacker::_smk_huff16_build_rec() - ERROR: failed to build left sub-tree\n", stderr); + return 0; + } + + /* now go back to our current location, and + mark our location as a "jump" */ + t->tree[value] = SMK_HUFF16_BRANCH | t->size; + + /* continue building the right side */ + if (! _smk_huff16_build_rec(t, bs, low8, hi8, limit)) { + fputs("libsmacker::_smk_huff16_build_rec() - ERROR: failed to build right sub-tree\n", stderr); + return 0; + } + } else { + /* Bit unset signifies a Leaf node. */ + /* Attempt to read LOW value */ + if ((value = smk_huff8_lookup(low8, bs)) < 0) { + fputs("libsmacker::_smk_huff16_build_rec() - ERROR: get LOW value returned -1\n", stderr); + return 0; + } + + t->tree[t->size] = value; + + /* now read HIGH value */ + if ((value = smk_huff8_lookup(hi8, bs)) < 0) { + fputs("libsmacker::_smk_huff16_build_rec() - ERROR: get HIGH value returned -1\n", stderr); + return 0; + } + + /* Looks OK: we got low and hi values. Return a new LEAF */ + t->tree[t->size] |= (value << 8); + + /* Last: when building the tree, some Values may correspond to cache positions. + Identify these values and set the Escape code byte accordingly. */ + if (t->tree[t->size] == t->cache[0]) + t->tree[t->size] = SMK_HUFF16_CACHE; + else if (t->tree[t->size] == t->cache[1]) + t->tree[t->size] = SMK_HUFF16_CACHE | 1; + else if (t->tree[t->size] == t->cache[2]) + t->tree[t->size] = SMK_HUFF16_CACHE | 2; + + t->size ++; + } + + return 1; +} + +/* Entry point for building a big 16-bit tree. */ +static int smk_huff16_build(struct smk_huff16_t * const t, struct smk_bit_t * const bs, const unsigned int alloc_size) +{ + struct smk_huff8_t low8, hi8; + size_t limit; + int value, i, bit; + /* null check */ + assert(t); + assert(bs); + + /* Smacker huff trees begin with a set-bit. */ + if ((bit = smk_bs_read_1(bs)) < 0) { + fputs("libsmacker::smk_huff16_build() - ERROR: initial get_bit returned -1\n", stderr); + return 0; + } + + t->size = 0; + + /* First bit indicates whether a tree is present or not. */ + /* Very small or audio-only files may have no tree. */ + if (bit) { + /* build low-8-bits tree */ + if (! smk_huff8_build(&low8, bs)) { + fputs("libsmacker::smk_huff16_build() - ERROR: failed to build LOW tree\n", stderr); + return 0; + } + + /* build hi-8-bits tree */ + if (! smk_huff8_build(&hi8, bs)) { + fputs("libsmacker::smk_huff16_build() - ERROR: failed to build HIGH tree\n", stderr); + return 0; + } + + /* Init the escape code cache. */ + for (i = 0; i < 3; i ++) { + if ((value = smk_bs_read_8(bs)) < 0) { + fprintf(stderr, "libsmacker::smk_huff16_build() - ERROR: get LOW value for cache %d returned -1\n", i); + return 0; + } + + t->cache[i] = value; + + /* now read HIGH value */ + if ((value = smk_bs_read_8(bs)) < 0) { + fprintf(stderr, "libsmacker::smk_huff16_build() - ERROR: get HIGH value for cache %d returned -1\n", i); + return 0; + } + + t->cache[i] |= (value << 8); + } + + /* Everything looks OK so far. Time to malloc structure. */ + if (alloc_size < 12 || alloc_size % 4) { + fprintf(stderr, "libsmacker::smk_huff16_build() - ERROR: illegal value %u for alloc_size\n", alloc_size); + return 0; + } + + limit = (alloc_size - 12) / 4; + + if ((t->tree = malloc(limit * sizeof(unsigned int))) == NULL) { + perror("libsmacker::smk_huff16_build() - ERROR: failed to malloc() huff16 tree"); + return 0; + } + + /* Finally, call recursive function to retrieve the Bigtree. */ + if (! _smk_huff16_build_rec(t, bs, &low8, &hi8, limit)) { + fputs("libsmacker::smk_huff16_build() - ERROR: failed to build huff16 tree\n", stderr); + free(t->tree); + t->tree = NULL; + return 0; + } + + /* check that we completely filled the tree */ + if (limit != t->size) { + fputs("libsmacker::smk_huff16_build() - ERROR: failed to completely decode huff16 tree\n", stderr); + free(t->tree); + t->tree = NULL; + return 0; + } + } else { + if ((t->tree = malloc(sizeof(unsigned int))) == NULL) { + perror("libsmacker::smk_huff16_build() - ERROR: failed to malloc() huff16 tree"); + return 0; + } + + t->tree[0] = 0; + //t->cache[0] = t->cache[1] = t->cache[2] = 0; + } + + /* Check final end tag. */ + if ((bit = smk_bs_read_1(bs)) < 0) { + fputs("libsmacker::smk_huff16_build() - ERROR: final get_bit returned -1\n", stderr); + free(t->tree); + t->tree = NULL; + return 0; + } + + /* a 0 is expected here, a 1 generally indicates a problem! */ + if (bit) { + fputs("libsmacker::smk_huff16_build() - ERROR: final get_bit returned 1\n", stderr); + free(t->tree); + t->tree = NULL; + return 0; + } + + return 1; +} + +/* Look up a 16-bit value from a large huff tree. + Return -1 on error. + Note that this also updates the recently-used-values cache. */ +static int smk_huff16_lookup(struct smk_huff16_t * const t, struct smk_bit_t * const bs) +{ + int bit, value, index = 0; + /* null check */ + assert(t); + assert(bs); + + while (t->tree[index] & SMK_HUFF16_BRANCH) { + if ((bit = smk_bs_read_1(bs)) < 0) { + fputs("libsmacker::smk_huff16_lookup() - ERROR: get_bit returned -1\n", stderr); + return -1; + } + + if (bit) { + /* take the right branch */ + index = t->tree[index] & SMK_HUFF16_LEAF_MASK; + } else { + /* take the left branch */ + index ++; + } + } + + /* Get the value at this point */ + value = t->tree[index]; + + if (value & SMK_HUFF16_CACHE) { + /* uses cached value instead of actual value */ + value = t->cache[value & SMK_HUFF16_LEAF_MASK]; + } + + if (t->cache[0] != value) { + /* Update the cache, by moving val to the front of the queue, + if it isn't already there. */ + t->cache[2] = t->cache[1]; + t->cache[1] = t->cache[0]; + t->cache[0] = value; + } + + return value; +} + +/* ************************************************************************* */ +/* SMACKER Structure */ +/* ************************************************************************* */ +/* tree processing order */ +#define SMK_TREE_MMAP 0 +#define SMK_TREE_MCLR 1 +#define SMK_TREE_FULL 2 +#define SMK_TREE_TYPE 3 + +struct smk_t { + /* meta-info */ + /* file mode: see flags, smacker.h */ + unsigned char mode; + + /* microsec per frame - stored as a double to handle scaling + (large positive millisec / frame values may overflow a ul) */ + double usf; + + /* total frames */ + unsigned long f; + /* does file have a ring frame? (in other words, does file loop?) */ + unsigned char ring_frame; + + /* Index of current frame */ + unsigned long cur_frame; + + /* SOURCE union. + Where the data is going to be read from (or be stored), + depending on the file mode. */ + union { + struct { + /* on-disk mode */ + FILE * fp; + unsigned long * chunk_offset; + } file; + + /* in-memory mode: unprocessed chunks */ + unsigned char ** chunk_data; + } source; + + /* shared array of "chunk sizes"*/ + unsigned long * chunk_size; + + /* Holds per-frame flags (i.e. 'keyframe') */ + unsigned char * keyframe; + /* Holds per-frame type mask (e.g. 'audio track 3, 2, and palette swap') */ + unsigned char * frame_type; + + /* video and audio structures */ + /* Video data type: enable/disable decode switch, + video info and flags, + pointer to last-decoded-palette */ + struct smk_video_t { + /* enable/disable decode switch */ + unsigned char enable; + + /* video info */ + unsigned long w; + unsigned long h; + /* Y scale mode (constants defined in smacker.h) + 0: unscaled + 1: doubled + 2: interlaced */ + unsigned char y_scale_mode; + + /* version ('2' or '4') */ + unsigned char v; + + /* Huffman trees */ + unsigned long tree_size[4]; + struct smk_huff16_t tree[4]; + + /* Palette data type: pointer to last-decoded-palette */ + unsigned char palette[256][3]; + /* Last-unpacked frame */ + unsigned char * frame; + } video; + + /* audio structure */ + struct smk_audio_t { + /* set if track exists in file */ + unsigned char exists; + + /* enable/disable switch (per track) */ + unsigned char enable; + + /* Info */ + unsigned char channels; + unsigned char bitdepth; + unsigned long rate; + long max_buffer; + + /* compression type + 0: raw PCM + 1: SMK DPCM + 2: Bink (Perceptual), unsupported */ + unsigned char compress; + + /* pointer to last-decoded-audio-buffer */ + void * buffer; + unsigned long buffer_size; + } audio[7]; +}; + +union smk_read_t { + FILE * file; + unsigned char * ram; +}; + +/* ************************************************************************* */ +/* SMACKER Functions */ +/* ************************************************************************* */ +/* An fread wrapper: consumes N bytes, or returns -1 + on failure (when size doesn't match expected) */ +static char smk_read_file(void * buf, const size_t size, FILE * fp) +{ + /* don't bother checking buf or fp, fread does it for us */ + size_t bytesRead = fread(buf, 1, size, fp); + + if (bytesRead != size) { + fprintf(stderr, "libsmacker::smk_read_file(buf,%lu,fp) - ERROR: Short read, %lu bytes returned\n", (unsigned long)size, (unsigned long)bytesRead); + perror("\tReason"); + return -1; + } + + return 0; +} + +/* A memcpy wrapper: consumes N bytes, or returns -1 + on failure (when size too low) */ +static char smk_read_memory(void * buf, const unsigned long size, unsigned char ** p, unsigned long * p_size) +{ + if (size > *p_size) { + fprintf(stderr, "libsmacker::smk_read_memory(buf,%lu,p,%lu) - ERROR: Short read\n", (unsigned long)size, (unsigned long)*p_size); + return -1; + } + + memcpy(buf, *p, size); + *p += size; + *p_size -= size; + return 0; +} + +/* Helper functions to do the reading, plus + byteswap from LE to host order */ +/* read n bytes from (source) into ret */ +#define smk_read(ret,n) \ +{ \ + if (m) \ + { \ + r = (smk_read_file(ret,n,fp.file)); \ + } \ + else \ + { \ + r = (smk_read_memory(ret,n,&fp.ram,&size)); \ + } \ + if (r < 0) \ + { \ + fprintf(stderr,"libsmacker::smk_read(...) - Errors encountered on read, bailing out (file: %s, line: %lu)\n", __FILE__, (unsigned long)__LINE__); \ + goto error; \ + } \ +} + +/* Calls smk_read, but returns a ul */ +#define smk_read_ul(p) \ +{ \ + smk_read(buf,4); \ + p = ((unsigned long) buf[3] << 24) | \ + ((unsigned long) buf[2] << 16) | \ + ((unsigned long) buf[1] << 8) | \ + ((unsigned long) buf[0]); \ +} + +/* PUBLIC FUNCTIONS */ +/* open an smk (from a generic Source) */ +static smk smk_open_generic(const unsigned char m, union smk_read_t fp, unsigned long size, const unsigned char process_mode) +{ + /* Smacker structure we intend to work on / return */ + smk s; + /* Temporary variables */ + long temp_l; + unsigned long temp_u; + /* r is used by macros above for return code */ + char r; + unsigned char buf[4] = {'\0'}; + /* video hufftrees are stored as a large chunk (bitstream) + these vars are used to load, then decode them */ + unsigned char * hufftree_chunk = NULL; + unsigned long tree_size; + /* a bitstream struct */ + struct smk_bit_t bs; + + /** **/ + /* safe malloc the structure */ + if ((s = calloc(1, sizeof(struct smk_t))) == NULL) { + perror("libsmacker::smk_open_generic() - ERROR: failed to malloc() smk structure"); + return NULL; + } + + /* Check for a valid signature */ + smk_read(buf, 3); + + if (buf[0] != 'S' || buf[1] != 'M' || buf[2] != 'K') { + fprintf(stderr, "libsmacker::smk_open_generic - ERROR: invalid SMKn signature (got: %s)\n", buf); + goto error; + } + + /* Read .smk file version */ + smk_read(&s->video.v, 1); + + if (s->video.v != '2' && s->video.v != '4') { + fprintf(stderr, "libsmacker::smk_open_generic - Warning: invalid SMK version %c (expected: 2 or 4)\n", s->video.v); + + /* take a guess */ + if (s->video.v < '4') + s->video.v = '2'; + else + s->video.v = '4'; + + fprintf(stderr, "\tProcessing will continue as type %c\n", s->video.v); + } + + /* width, height, total num frames */ + smk_read_ul(s->video.w); + smk_read_ul(s->video.h); + smk_read_ul(s->f); + /* frames per second calculation */ + smk_read_ul(temp_u); + temp_l = (int)temp_u; + + if (temp_l > 0) { + /* millisec per frame */ + s->usf = temp_l * 1000; + } else if (temp_l < 0) { + /* 10 microsec per frame */ + s->usf = temp_l * -10; + } else { + /* defaults to 10 usf (= 100000 microseconds) */ + s->usf = 100000; + } + + /* Video flags follow. + Ring frame is important to libsmacker. + Y scale / Y interlace go in the Video flags. + The user should scale appropriately. */ + smk_read_ul(temp_u); + + if (temp_u & 0x01) + s->ring_frame = 1; + + if (temp_u & 0x02) + s->video.y_scale_mode = SMK_FLAG_Y_DOUBLE; + + if (temp_u & 0x04) { + if (s->video.y_scale_mode == SMK_FLAG_Y_DOUBLE) + fputs("libsmacker::smk_open_generic - Warning: SMK file specifies both Y-Double AND Y-Interlace.\n", stderr); + + s->video.y_scale_mode = SMK_FLAG_Y_INTERLACE; + } + + /* Max buffer size for each audio track - used to pre-allocate buffers */ + for (temp_l = 0; temp_l < 7; temp_l ++) + smk_read_ul(s->audio[temp_l].max_buffer); + + /* Read size of "hufftree chunk" - save for later. */ + smk_read_ul(tree_size); + + /* "unpacked" sizes of each huff tree */ + for (temp_l = 0; temp_l < 4; temp_l ++) + smk_read_ul(s->video.tree_size[temp_l]); + + /* read audio rate data */ + for (temp_l = 0; temp_l < 7; temp_l ++) { + smk_read_ul(temp_u); + + if (temp_u & 0x40000000) { + /* Audio track specifies "exists" flag, malloc structure and copy components. */ + s->audio[temp_l].exists = 1; + /* and for all audio tracks */ + smk_malloc(s->audio[temp_l].buffer, s->audio[temp_l].max_buffer); + + if (temp_u & 0x80000000) + s->audio[temp_l].compress = 1; + + s->audio[temp_l].bitdepth = ((temp_u & 0x20000000) ? 16 : 8); + s->audio[temp_l].channels = ((temp_u & 0x10000000) ? 2 : 1); + + if (temp_u & 0x0c000000) { + fprintf(stderr, "libsmacker::smk_open_generic - Warning: audio track %ld is compressed with Bink (perceptual) Audio Codec: this is currently unsupported by libsmacker\n", temp_l); + s->audio[temp_l].compress = 2; + } + + /* Bits 25 & 24 are unused. */ + s->audio[temp_l].rate = (temp_u & 0x00FFFFFF); + } + } + + /* Skip over Dummy field */ + smk_read_ul(temp_u); + /* FrameSizes and Keyframe marker are stored together. */ + smk_malloc(s->keyframe, (s->f + s->ring_frame)); + smk_malloc(s->chunk_size, (s->f + s->ring_frame) * sizeof(unsigned long)); + + for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u ++) { + smk_read_ul(s->chunk_size[temp_u]); + + /* Set Keyframe */ + if (s->chunk_size[temp_u] & 0x01) + s->keyframe[temp_u] = 1; + + /* Bits 1 is used, but the purpose is unknown. */ + s->chunk_size[temp_u] &= 0xFFFFFFFC; + } + + /* That was easy... Now read FrameTypes! */ + smk_malloc(s->frame_type, (s->f + s->ring_frame)); + + for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u ++) + smk_read(&s->frame_type[temp_u], 1); + + /* HuffmanTrees + We know the sizes already: read and assemble into + something actually parse-able at run-time */ + smk_malloc(hufftree_chunk, tree_size); + smk_read(hufftree_chunk, tree_size); + /* set up a Bitstream */ + smk_bs_init(&bs, hufftree_chunk, tree_size); + + /* create some tables */ + for (temp_u = 0; temp_u < 4; temp_u ++) { + if (! smk_huff16_build(&s->video.tree[temp_u], &bs, s->video.tree_size[temp_u])) { + fprintf(stderr, "libsmacker::smk_open_generic - ERROR: failed to create huff16 tree %lu\n", temp_u); + goto error; + } + } + + /* clean up */ + smk_free(hufftree_chunk); + /* Go ahead and malloc storage for the video frame */ + smk_malloc(s->video.frame, s->video.w * s->video.h); + /* final processing: depending on ProcessMode, handle what to do with rest of file data */ + s->mode = process_mode; + + /* Handle the rest of the data. + For MODE_MEMORY, read the chunks and store */ + if (s->mode == SMK_MODE_MEMORY) { + smk_malloc(s->source.chunk_data, (s->f + s->ring_frame) * sizeof(unsigned char *)); + + for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u ++) { + smk_malloc(s->source.chunk_data[temp_u], s->chunk_size[temp_u]); + smk_read(s->source.chunk_data[temp_u], s->chunk_size[temp_u]); + } + } else { + /* MODE_STREAM: don't read anything now, just precompute offsets. + use fseek to verify that the file is "complete" */ + smk_malloc(s->source.file.chunk_offset, (s->f + s->ring_frame) * sizeof(unsigned long)); + + for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u ++) { + s->source.file.chunk_offset[temp_u] = ftell(fp.file); + + if (fseek(fp.file, s->chunk_size[temp_u], SEEK_CUR)) { + fprintf(stderr, "libsmacker::smk_open - ERROR: fseek to frame %lu not OK.\n", temp_u); + perror("\tError reported was"); + goto error; + } + } + } + + return s; +error: + smk_free(hufftree_chunk); + smk_close(s); + return NULL; +} + +/* open an smk (from a memory buffer) */ +smk smk_open_memory(const unsigned char * buffer, const unsigned long size) +{ + smk s = NULL; + union smk_read_t fp; + + if (buffer == NULL) { + fputs("libsmacker::smk_open_memory() - ERROR: buffer pointer is NULL\n", stderr); + return NULL; + } + + /* set up the read union for Memory mode */ + fp.ram = (unsigned char *)buffer; + + if (!(s = smk_open_generic(0, fp, size, SMK_MODE_MEMORY))) + fprintf(stderr, "libsmacker::smk_open_memory(buffer,%lu) - ERROR: Fatal error in smk_open_generic, returning NULL.\n", size); + + return s; +} + +/* open an smk (from a file) */ +smk smk_open_filepointer(FILE * file, const unsigned char mode) +{ + smk s = NULL; + union smk_read_t fp; + + if (file == NULL) { + fputs("libsmacker::smk_open_filepointer() - ERROR: file pointer is NULL\n", stderr); + return NULL; + } + + /* Copy file ptr to internal union */ + fp.file = file; + + if (!(s = smk_open_generic(1, fp, 0, mode))) { + fprintf(stderr, "libsmacker::smk_open_filepointer(file,%u) - ERROR: Fatal error in smk_open_generic, returning NULL.\n", mode); + fclose(fp.file); + goto error; + } + + if (mode == SMK_MODE_MEMORY) + fclose(fp.file); + else + s->source.file.fp = fp.file; + + /* fall through, return s or null */ +error: + return s; +} + +/* open an smk (from a file) */ +smk smk_open_file(const char * filename, const unsigned char mode) +{ + FILE * fp; + + if (filename == NULL) { + fputs("libsmacker::smk_open_file() - ERROR: filename is NULL\n", stderr); + return NULL; + } + + if (!(fp = fopen(filename, "rb"))) { + fprintf(stderr, "libsmacker::smk_open_file(%s,%u) - ERROR: could not open file\n", filename, mode); + perror("\tError reported was"); + goto error; + } + + /* kick processing to smk_open_filepointer */ + return smk_open_filepointer(fp, mode); + /* fall through, return s or null */ +error: + return NULL; +} + +/* close out an smk file and clean up memory */ +void smk_close(smk s) +{ + unsigned long u; + + if (s == NULL) { + fputs("libsmacker::smk_close() - ERROR: smk is NULL\n", stderr); + return; + } + + /* free video sub-components */ + for (u = 0; u < 4; u ++) { + if (s->video.tree[u].tree) free(s->video.tree[u].tree); + } + + smk_free(s->video.frame); + + /* free audio sub-components */ + for (u = 0; u < 7; u++) { + if (s->audio[u].buffer) + smk_free(s->audio[u].buffer); + } + + smk_free(s->keyframe); + smk_free(s->frame_type); + + if (s->mode == SMK_MODE_DISK) { + /* disk-mode */ + if (s->source.file.fp) + fclose(s->source.file.fp); + + smk_free(s->source.file.chunk_offset); + } else { + /* mem-mode */ + if (s->source.chunk_data != NULL) { + for (u = 0; u < (s->f + s->ring_frame); u++) + smk_free(s->source.chunk_data[u]); + + smk_free(s->source.chunk_data); + } + } + + smk_free(s->chunk_size); + smk_free(s); +} + +/* tell some info about the file */ +char smk_info_all(const smk object, unsigned long * frame, unsigned long * frame_count, double * usf) +{ + /* null check */ + if (object == NULL) { + fputs("libsmacker::smk_info_all() - ERROR: smk is NULL\n", stderr); + return -1; + } + + if (!frame && !frame_count && !usf) { + fputs("libsmacker::smk_info_all(object,frame,frame_count,usf) - ERROR: Request for info with all-NULL return references\n", stderr); + goto error; + } + + if (frame) + *frame = (object->cur_frame % object->f); + + if (frame_count) + *frame_count = object->f; + + if (usf) + *usf = object->usf; + + return 0; +error: + return -1; +} + +char smk_info_video(const smk object, unsigned long * w, unsigned long * h, unsigned char * y_scale_mode) +{ + /* null check */ + if (object == NULL) { + fputs("libsmacker::smk_info_video() - ERROR: smk is NULL\n", stderr); + return -1; + } + + if (!w && !h && !y_scale_mode) { + fputs("libsmacker::smk_info_all(object,w,h,y_scale_mode) - ERROR: Request for info with all-NULL return references\n", stderr); + return -1; + } + + if (w) + *w = object->video.w; + + if (h) + *h = object->video.h; + + if (y_scale_mode) + *y_scale_mode = object->video.y_scale_mode; + + return 0; +} + +char smk_info_audio(const smk object, unsigned char * track_mask, unsigned char channels[7], unsigned char bitdepth[7], unsigned long audio_rate[7]) +{ + unsigned char i; + + /* null check */ + if (object == NULL) { + fputs("libsmacker::smk_info_audio() - ERROR: smk is NULL\n", stderr); + return -1; + } + + if (!track_mask && !channels && !bitdepth && !audio_rate) { + fputs("libsmacker::smk_info_audio(object,track_mask,channels,bitdepth,audio_rate) - ERROR: Request for info with all-NULL return references\n", stderr); + return -1; + } + + if (track_mask) { + *track_mask = ((object->audio[0].exists) | + ((object->audio[1].exists) << 1) | + ((object->audio[2].exists) << 2) | + ((object->audio[3].exists) << 3) | + ((object->audio[4].exists) << 4) | + ((object->audio[5].exists) << 5) | + ((object->audio[6].exists) << 6)); + } + + if (channels) { + for (i = 0; i < 7; i ++) + channels[i] = object->audio[i].channels; + } + + if (bitdepth) { + for (i = 0; i < 7; i ++) + bitdepth[i] = object->audio[i].bitdepth; + } + + if (audio_rate) { + for (i = 0; i < 7; i ++) + audio_rate[i] = object->audio[i].rate; + } + + return 0; +} + +/* Enable-disable switches */ +char smk_enable_all(smk object, const unsigned char mask) +{ + unsigned char i; + + /* null check */ + if (object == NULL) { + fputs("libsmacker::smk_enable_all() - ERROR: smk is NULL\n", stderr); + return -1; + } + + /* set video-enable */ + object->video.enable = (mask & 0x80); + + for (i = 0; i < 7; i ++) { + if (object->audio[i].exists) + object->audio[i].enable = (mask & (1 << i)); + } + + return 0; +} + +char smk_enable_video(smk object, const unsigned char enable) +{ + /* null check */ + if (object == NULL) { + fputs("libsmacker::smk_enable_video() - ERROR: smk is NULL\n", stderr); + return -1; + } + + object->video.enable = enable; + return 0; +} + +char smk_enable_audio(smk object, const unsigned char track, const unsigned char enable) +{ + /* null check */ + if (object == NULL) { + fputs("libsmacker::smk_enable_audio() - ERROR: smk is NULL\n", stderr); + return -1; + } + + object->audio[track].enable = enable; + return 0; +} + +const unsigned char * smk_get_palette(const smk object) +{ + /* null check */ + if (object == NULL) { + fputs("libsmacker::smk_get_palette() - ERROR: smk is NULL\n", stderr); + return NULL; + } + + return (unsigned char *)object->video.palette; +} +const unsigned char * smk_get_video(const smk object) +{ + /* null check */ + if (object == NULL) { + fputs("libsmacker::smk_get_video() - ERROR: smk is NULL\n", stderr); + return NULL; + } + + return object->video.frame; +} +const unsigned char * smk_get_audio(const smk object, const unsigned char t) +{ + /* null check */ + if (object == NULL) { + fputs("libsmacker::smk_get_audio() - ERROR: smk is NULL\n", stderr); + return NULL; + } + + return object->audio[t].buffer; +} +unsigned long smk_get_audio_size(const smk object, const unsigned char t) +{ + /* null check */ + if (object == NULL) { + fputs("libsmacker::smk_get_audio_size() - ERROR: smk is NULL\n", stderr); + return 0; + } + + return object->audio[t].buffer_size; +} + +/* Decompresses a palette-frame. */ +static char smk_render_palette(struct smk_video_t * s, unsigned char * p, unsigned long size) +{ + /* Index into palette */ + unsigned short i = 0; + /* Helper variables */ + unsigned short count, src; + static unsigned char oldPalette[256][3]; + /* Smacker palette map: smk colors are 6-bit, this table expands them to 8. */ + const unsigned char palmap[64] = { + 0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, + 0x20, 0x24, 0x28, 0x2C, 0x30, 0x34, 0x38, 0x3C, + 0x41, 0x45, 0x49, 0x4D, 0x51, 0x55, 0x59, 0x5D, + 0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D, + 0x82, 0x86, 0x8A, 0x8E, 0x92, 0x96, 0x9A, 0x9E, + 0xA2, 0xA6, 0xAA, 0xAE, 0xB2, 0xB6, 0xBA, 0xBE, + 0xC3, 0xC7, 0xCB, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF, + 0xE3, 0xE7, 0xEB, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF + }; + /* null check */ + assert(s); + assert(p); + /* Copy palette to old palette */ + memcpy(oldPalette, s->palette, 256 * 3); + + /* Loop until palette is complete, or we are out of bytes to process */ + while ((i < 256) && (size > 0)) { + if ((*p) & 0x80) { + /* 0x80: Skip block + (preserve C+1 palette entries from previous palette) */ + count = ((*p) & 0x7F) + 1; + p ++; + size --; + + /* check for overflow condition */ + if (i + count > 256) { + fprintf(stderr, "libsmacker::palette_render(s,p,size) - ERROR: overflow, 0x80 attempt to skip %d entries from %d\n", count, i); + goto error; + } + + /* finally: advance the index. */ + i += count; + } else if ((*p) & 0x40) { + /* 0x40: Color-shift block + Copy (c + 1) color entries of the previous palette, + starting from entry (s), + to the next entries of the new palette. */ + if (size < 2) { + fputs("libsmacker::palette_render(s,p,size) - ERROR: 0x40 ran out of bytes for copy\n", stderr); + goto error; + } + + /* pick "count" items to copy */ + count = ((*p) & 0x3F) + 1; + p ++; + size --; + /* start offset of old palette */ + src = *p; + p ++; + size --; + + /* overflow: see if we write/read beyond 256colors, or overwrite own palette */ + if (i + count > 256 || src + count > 256 || + (src < i && src + count > i)) { + fprintf(stderr, "libsmacker::palette_render(s,p,size) - ERROR: overflow, 0x40 attempt to copy %d entries from %d to %d\n", count, src, i); + goto error; + } + + /* OK! Copy the color-palette entries. */ + memmove(&s->palette[i][0], &oldPalette[src][0], count * 3); + i += count; + } else { + /* 0x00: Set Color block + Direct-set the next 3 bytes for palette index */ + if (size < 3) { + fprintf(stderr, "libsmacker::palette_render - ERROR: 0x3F ran out of bytes for copy, size=%lu\n", size); + goto error; + } + + for (count = 0; count < 3; count ++) { + if (*p > 0x3F) { + fprintf(stderr, "libsmacker::palette_render - ERROR: palette index exceeds 0x3F (entry [%u][%u])\n", i, count); + goto error; + } + + s->palette[i][count] = palmap[*p]; + p++; + size --; + } + + i ++; + } + } + + if (i < 256) { + fprintf(stderr, "libsmacker::palette_render - ERROR: did not completely fill palette (idx=%u)\n", i); + goto error; + } + + return 0; +error: + /* Error, return -1 + The new palette probably has errors but is preferrable to a black screen */ + return -1; +} + +static char smk_render_video(struct smk_video_t * s, unsigned char * p, unsigned int size) +{ + unsigned char * t = s->frame; + unsigned char s1, s2; + unsigned short temp; + unsigned long i, j, k, row, col, skip; + /* used for video decoding */ + struct smk_bit_t bs; + /* results from a tree lookup */ + int unpack; + /* unpack, broken into pieces */ + unsigned char type; + unsigned char blocklen; + unsigned char typedata; + char bit; + const unsigned short sizetable[64] = { + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 128, 256, 512, 1024, 2048 + }; + /* null check */ + assert(s); + assert(p); + row = 0; + col = 0; + /* Set up a bitstream for video unpacking */ + smk_bs_init(&bs, p, size); + + /* Reset the cache on all bigtrees */ + for (i = 0; i < 4; i++) + memset(&s->tree[i].cache, 0, 3 * sizeof(unsigned short)); + + while (row < s->h) { + if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_TYPE], &bs)) < 0) { + fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from TYPE tree.\n", stderr); + return -1; + } + + type = ((unpack & 0x0003)); + blocklen = ((unpack & 0x00FC) >> 2); + typedata = ((unpack & 0xFF00) >> 8); + + /* support for v4 full-blocks */ + if (type == 1 && s->v == '4') { + bit = smk_bs_read_1(&bs); + + if (bit) + type = 4; + else { + bit = smk_bs_read_1(&bs); + + if (bit) + type = 5; + } + } + + for (j = 0; (j < sizetable[blocklen]) && (row < s->h); j ++) { + skip = (row * s->w) + col; + + switch (type) { + case 0: + if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_MCLR], &bs)) < 0) { + fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from MCLR tree.\n", stderr); + return -1; + } + + s1 = (unpack & 0xFF00) >> 8; + s2 = (unpack & 0x00FF); + + if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_MMAP], &bs)) < 0) { + fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from MMAP tree.\n", stderr); + return -1; + } + + temp = 0x01; + + for (k = 0; k < 4; k ++) { + for (i = 0; i < 4; i ++) { + if (unpack & temp) + t[skip + i] = s1; + else + t[skip + i] = s2; + + temp = temp << 1; + } + + skip += s->w; + } + + break; + + case 1: /* FULL BLOCK */ + for (k = 0; k < 4; k ++) { + if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_FULL], &bs)) < 0) { + fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from FULL tree.\n", stderr); + return -1; + } + + t[skip + 3] = ((unpack & 0xFF00) >> 8); + t[skip + 2] = (unpack & 0x00FF); + + if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_FULL], &bs)) < 0) { + fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from FULL tree.\n", stderr); + return -1; + } + + t[skip + 1] = ((unpack & 0xFF00) >> 8); + t[skip] = (unpack & 0x00FF); + skip += s->w; + } + + break; + + case 2: /* VOID BLOCK */ + /* break; + if (s->frame) + { + memcpy(&t[skip], &s->frame[skip], 4); + skip += s->w; + memcpy(&t[skip], &s->frame[skip], 4); + skip += s->w; + memcpy(&t[skip], &s->frame[skip], 4); + skip += s->w; + memcpy(&t[skip], &s->frame[skip], 4); + } */ + break; + + case 3: /* SOLID BLOCK */ + memset(&t[skip], typedata, 4); + skip += s->w; + memset(&t[skip], typedata, 4); + skip += s->w; + memset(&t[skip], typedata, 4); + skip += s->w; + memset(&t[skip], typedata, 4); + break; + + case 4: /* V4 DOUBLE BLOCK */ + for (k = 0; k < 2; k ++) { + if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_FULL], &bs)) < 0) { + fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from FULL tree.\n", stderr); + return -1; + } + + for (i = 0; i < 2; i ++) { + memset(&t[skip + 2], (unpack & 0xFF00) >> 8, 2); + memset(&t[skip], (unpack & 0x00FF), 2); + skip += s->w; + } + } + + break; + + case 5: /* V4 HALF BLOCK */ + for (k = 0; k < 2; k ++) { + if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_FULL], &bs)) < 0) { + fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from FULL tree.\n", stderr); + return -1; + } + + t[skip + 3] = ((unpack & 0xFF00) >> 8); + t[skip + 2] = (unpack & 0x00FF); + t[skip + s->w + 3] = ((unpack & 0xFF00) >> 8); + t[skip + s->w + 2] = (unpack & 0x00FF); + + if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_FULL], &bs)) < 0) { + fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from FULL tree.\n", stderr); + return -1; + } + + t[skip + 1] = ((unpack & 0xFF00) >> 8); + t[skip] = (unpack & 0x00FF); + t[skip + s->w + 1] = ((unpack & 0xFF00) >> 8); + t[skip + s->w] = (unpack & 0x00FF); + skip += (s->w << 1); + } + + break; + } + + col += 4; + + if (col >= s->w) { + col = 0; + row += 4; + } + } + } + + return 0; +} + +/* Decompress audio track i. */ +static char smk_render_audio(struct smk_audio_t * s, unsigned char * p, unsigned long size) +{ + unsigned int j, k; + unsigned char * t = s->buffer; + struct smk_bit_t bs; + char bit; + short unpack, unpack2; + /* used for audio decoding */ + struct smk_huff8_t aud_tree[4]; + /* null check */ + assert(s); + assert(p); + + if (!s->compress) { + /* Raw PCM data, update buffer size and perform copy */ + s->buffer_size = size; + memcpy(t, p, size); + } else if (s->compress == 1) { + /* SMACKER DPCM compression */ + /* need at least 4 bytes to process */ + if (size < 4) { + fputs("libsmacker::smk_render_audio() - ERROR: need 4 bytes to get unpacked output buffer size.\n", stderr); + goto error; + } + + /* chunk is compressed (huff-compressed dpcm), retrieve unpacked buffer size */ + s->buffer_size = ((unsigned int) p[3] << 24) | + ((unsigned int) p[2] << 16) | + ((unsigned int) p[1] << 8) | + ((unsigned int) p[0]); + p += 4; + size -= 4; + /* Compressed audio: must unpack here */ + /* Set up a bitstream */ + smk_bs_init(&bs, p, size); + bit = smk_bs_read_1(&bs); + + if (!bit) { + fputs("libsmacker::smk_render_audio - ERROR: initial get_bit returned 0\n", stderr); + goto error; + } + + bit = smk_bs_read_1(&bs); + + if (s->channels != (bit == 1 ? 2 : 1)) + fputs("libsmacker::smk_render - ERROR: mono/stereo mismatch\n", stderr); + + bit = smk_bs_read_1(&bs); + + if (s->bitdepth != (bit == 1 ? 16 : 8)) + fputs("libsmacker::smk_render - ERROR: 8-/16-bit mismatch\n", stderr); + + /* build the trees */ + smk_huff8_build(&aud_tree[0], &bs); + j = 1; + k = 1; + + if (s->bitdepth == 16) { + smk_huff8_build(&aud_tree[1], &bs); + k = 2; + } + + if (s->channels == 2) { + smk_huff8_build(&aud_tree[2], &bs); + j = 2; + k = 2; + + if (s->bitdepth == 16) { + smk_huff8_build(&aud_tree[3], &bs); + k = 4; + } + } + + /* read initial sound level */ + if (s->channels == 2) { + unpack = smk_bs_read_8(&bs); + + if (s->bitdepth == 16) { + ((short *)t)[1] = smk_bs_read_8(&bs); + ((short *)t)[1] |= (unpack << 8); + } else + ((unsigned char *)t)[1] = (unsigned char)unpack; + } + + unpack = smk_bs_read_8(&bs); + + if (s->bitdepth == 16) { + ((short *)t)[0] = smk_bs_read_8(&bs); + ((short *)t)[0] |= (unpack << 8); + } else + ((unsigned char *)t)[0] = (unsigned char)unpack; + + /* All set: let's read some DATA! */ + while (k < s->buffer_size) { + if (s->bitdepth == 8) { + unpack = smk_huff8_lookup(&aud_tree[0], &bs); + ((unsigned char *)t)[j] = (char)unpack + ((unsigned char *)t)[j - s->channels]; + j ++; + k++; + } else { + unpack = smk_huff8_lookup(&aud_tree[0], &bs); + unpack2 = smk_huff8_lookup(&aud_tree[1], &bs); + ((short *)t)[j] = (short)(unpack | (unpack2 << 8)) + ((short *)t)[j - s->channels]; + j ++; + k += 2; + } + + if (s->channels == 2) { + if (s->bitdepth == 8) { + unpack = smk_huff8_lookup(&aud_tree[2], &bs); + ((unsigned char *)t)[j] = (char)unpack + ((unsigned char *)t)[j - 2]; + j ++; + k++; + } else { + unpack = smk_huff8_lookup(&aud_tree[2], &bs); + unpack2 = smk_huff8_lookup(&aud_tree[3], &bs); + ((short *)t)[j] = (short)(unpack | (unpack2 << 8)) + ((short *)t)[j - 2]; + j ++; + k += 2; + } + } + } + } + + return 0; +error: + return -1; +} + +/* "Renders" (unpacks) the frame at cur_frame + Preps all the image and audio pointers */ +static char smk_render(smk s) +{ + unsigned long i, size; + unsigned char * buffer = NULL, * p, track; + /* null check */ + assert(s); + + /* Retrieve current chunk_size for this frame. */ + if (!(i = s->chunk_size[s->cur_frame])) { + fprintf(stderr, "libsmacker::smk_render(s) - Warning: frame %lu: chunk_size is 0.\n", s->cur_frame); + goto error; + } + + if (s->mode == SMK_MODE_DISK) { + /* Skip to frame in file */ + if (fseek(s->source.file.fp, s->source.file.chunk_offset[s->cur_frame], SEEK_SET)) { + fprintf(stderr, "libsmacker::smk_render(s) - ERROR: fseek to frame %lu (offset %lu) failed.\n", s->cur_frame, s->source.file.chunk_offset[s->cur_frame]); + perror("\tError reported was"); + goto error; + } + + /* In disk-streaming mode: make way for our incoming chunk buffer */ + if ((buffer = malloc(i)) == NULL) { + perror("libsmacker::smk_render() - ERROR: failed to malloc() buffer"); + return -1; + } + + /* Read into buffer */ + if (smk_read_file(buffer, s->chunk_size[s->cur_frame], s->source.file.fp) < 0) { + fprintf(stderr, "libsmacker::smk_render(s) - ERROR: frame %lu (offset %lu): smk_read had errors.\n", s->cur_frame, s->source.file.chunk_offset[s->cur_frame]); + goto error; + } + } else { + /* Just point buffer at the right place */ + if (!s->source.chunk_data[s->cur_frame]) { + fprintf(stderr, "libsmacker::smk_render(s) - ERROR: frame %lu: memory chunk is a NULL pointer.\n", s->cur_frame); + goto error; + } + + buffer = s->source.chunk_data[s->cur_frame]; + } + + p = buffer; + + /* Palette record first */ + if (s->frame_type[s->cur_frame] & 0x01) { + /* need at least 1 byte to process */ + if (!i) { + fprintf(stderr, "libsmacker::smk_render(s) - ERROR: frame %lu: insufficient data for a palette rec.\n", s->cur_frame); + goto error; + } + + /* Byte 1 in block, times 4, tells how many + subsequent bytes are present */ + size = 4 * (*p); + + /* If video rendering enabled, kick this off for decode. */ + if (s->video.enable) + smk_render_palette(&(s->video), p + 1, size - 1); + + p += size; + i -= size; + } + + /* Unpack audio chunks */ + for (track = 0; track < 7; track ++) { + if (s->frame_type[s->cur_frame] & (0x02 << track)) { + /* need at least 4 byte to process */ + if (i < 4) { + fprintf(stderr, "libsmacker::smk_render(s) - ERROR: frame %lu: insufficient data for audio[%u] rec.\n", s->cur_frame, track); + goto error; + } + + /* First 4 bytes in block tell how many + subsequent bytes are present */ + size = (((unsigned int) p[3] << 24) | + ((unsigned int) p[2] << 16) | + ((unsigned int) p[1] << 8) | + ((unsigned int) p[0])); + + /* If audio rendering enabled, kick this off for decode. */ + if (s->audio[track].enable) + smk_render_audio(&s->audio[track], p + 4, size - 4); + + p += size; + i -= size; + } else + s->audio[track].buffer_size = 0; + } + + /* Unpack video chunk */ + if (s->video.enable) { + if (smk_render_video(&(s->video), p, i) < 0) { + fprintf(stderr, "libsmacker::smk_render(s) - ERROR: frame %lu: failed to render video.\n", s->cur_frame); + goto error; + } + } + + if (s->mode == SMK_MODE_DISK) { + /* Remember that buffer we allocated? Trash it */ + smk_free(buffer); + } + + return 0; +error: + + if (s->mode == SMK_MODE_DISK) { + /* Remember that buffer we allocated? Trash it */ + smk_free(buffer); + } + + return -1; +} + +/* rewind to first frame and unpack */ +char smk_first(smk s) +{ + /* null check */ + if (s == NULL) { + fputs("libsmacker::smk_first() - ERROR: smk is NULL\n", stderr); + return -1; + } + + s->cur_frame = 0; + + if (smk_render(s) < 0) { + fprintf(stderr, "libsmacker::smk_first(s) - Warning: frame %lu: smk_render returned errors.\n", s->cur_frame); + return -1; + } + + if (s->f == 1) return SMK_LAST; + + return SMK_MORE; +} + +/* advance to next frame */ +char smk_next(smk s) +{ + /* null check */ + if (s == NULL) { + fputs("libsmacker::smk_next() - ERROR: smk is NULL\n", stderr); + return -1; + } + + if (s->cur_frame + 1 < (s->f + s->ring_frame)) { + s->cur_frame ++; + + if (smk_render(s) < 0) { + fprintf(stderr, "libsmacker::smk_next(s) - Warning: frame %lu: smk_render returned errors.\n", s->cur_frame); + return -1; + } + + if (s->cur_frame + 1 == (s->f + s->ring_frame)) + return SMK_LAST; + + return SMK_MORE; + } else if (s->ring_frame) { + s->cur_frame = 1; + + if (smk_render(s) < 0) { + fprintf(stderr, "libsmacker::smk_next(s) - Warning: frame %lu: smk_render returned errors.\n", s->cur_frame); + return -1; + } + + if (s->cur_frame + 1 == (s->f + s->ring_frame)) + return SMK_LAST; + + return SMK_MORE; + } + + return SMK_DONE; +} + +/* seek to a keyframe in an smk */ +char smk_seek_keyframe(smk s, unsigned long f) +{ + /* null check */ + if (s == NULL) { + fputs("libsmacker::smk_seek_keyframe() - ERROR: smk is NULL\n", stderr); + return -1; + } + + /* rewind (or fast forward!) exactly to f */ + s->cur_frame = f; + + /* roll back to previous keyframe in stream, or 0 if no keyframes exist */ + while (s->cur_frame > 0 && !(s->keyframe[s->cur_frame])) + s->cur_frame --; + + /* render the frame: we're ready */ + if (smk_render(s) < 0) { + fprintf(stderr, "libsmacker::smk_seek_keyframe(s,%lu) - Warning: frame %lu: smk_render returned errors.\n", f, s->cur_frame); + return -1; + } + + return 0; +} diff --git a/ext/libsmacker/smacker.h b/ext/libsmacker/smacker.h new file mode 100644 index 000000000..e79700416 --- /dev/null +++ b/ext/libsmacker/smacker.h @@ -0,0 +1,103 @@ +/** + libsmacker - A C library for decoding .smk Smacker Video files + Copyright (C) 2012-2020 Greg Kennedy + + libsmacker is a cross-platform C library which can be used for + decoding Smacker Video files produced by RAD Game Tools. + + This program 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 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef SMACKER_H +#define SMACKER_H + +/* includes - needed for FILE* here */ +#include + +/** forward-declaration for an struct */ +typedef struct smk_t * smk; + +/** a few defines as return codes from smk_next() */ +#define SMK_DONE 0x00 +#define SMK_MORE 0x01 +#define SMK_LAST 0x02 +#define SMK_ERROR -1 + +/** file-processing mode, pass to smk_open_file */ +#define SMK_MODE_DISK 0x00 +#define SMK_MODE_MEMORY 0x01 + +/** Y-scale meanings */ +#define SMK_FLAG_Y_NONE 0x00 +#define SMK_FLAG_Y_INTERLACE 0x01 +#define SMK_FLAG_Y_DOUBLE 0x02 + +/** track mask and enable bits */ +#define SMK_AUDIO_TRACK_0 0x01 +#define SMK_AUDIO_TRACK_1 0x02 +#define SMK_AUDIO_TRACK_2 0x04 +#define SMK_AUDIO_TRACK_3 0x08 +#define SMK_AUDIO_TRACK_4 0x10 +#define SMK_AUDIO_TRACK_5 0x20 +#define SMK_AUDIO_TRACK_6 0x40 +#define SMK_VIDEO_TRACK 0x80 + +/* PUBLIC FUNCTIONS */ +#ifdef __cplusplus +extern "C" { +#endif + +/* OPEN OPERATIONS */ +/** open an smk (from a file) */ +smk smk_open_file(const char * filename, unsigned char mode); +/** open an smk (from a file pointer) */ +smk smk_open_filepointer(FILE * file, unsigned char mode); +/** read an smk (from a memory buffer) */ +smk smk_open_memory(const unsigned char * buffer, unsigned long size); + +/* CLOSE OPERATIONS */ +/** close out an smk file and clean up memory */ +void smk_close(smk object); + +/* GET FILE INFO OPERATIONS */ +char smk_info_all(const smk object, unsigned long * frame, unsigned long * frame_count, double * usf); +char smk_info_video(const smk object, unsigned long * w, unsigned long * h, unsigned char * y_scale_mode); +char smk_info_audio(const smk object, unsigned char * track_mask, unsigned char channels[7], unsigned char bitdepth[7], unsigned long audio_rate[7]); + +/* ENABLE/DISABLE Switches */ +char smk_enable_all(smk object, unsigned char mask); +char smk_enable_video(smk object, unsigned char enable); +char smk_enable_audio(smk object, unsigned char track, unsigned char enable); + +/** Retrieve palette */ +const unsigned char * smk_get_palette(const smk object); +/** Retrieve video frame, as a buffer of size w*h */ +const unsigned char * smk_get_video(const smk object); +/** Retrieve decoded audio chunk, track N */ +const unsigned char * smk_get_audio(const smk object, unsigned char track); +/** Get size of currently pointed decoded audio chunk, track N */ +unsigned long smk_get_audio_size(const smk object, unsigned char track); + +/** rewind to first frame and unpack */ +char smk_first(smk object); +/** advance to next frame and unpack */ +char smk_next(smk object); +/** seek to first keyframe before/at N in an smk */ +char smk_seek_keyframe(smk object, unsigned long frame); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/libsmacker/smk2avi.c b/ext/libsmacker/smk2avi.c new file mode 100644 index 000000000..683e61d32 --- /dev/null +++ b/ext/libsmacker/smk2avi.c @@ -0,0 +1,327 @@ +#include +#include +#include + +#include "smacker.h" + +#define w(p,n) fwrite(p,1,n,fp) + +#define LIST w("LIST",4); + +#define lu(p) \ +{ \ + b[0] = (p & 0x000000FF); \ + b[1] = ((p & 0x0000FF00) >> 8); \ + b[2] = ((p & 0x00FF0000) >> 16); \ + b[3] = ((p & 0xFF000000) >> 24); \ + w(b,4); \ +} + +#define su(p) \ +{ \ + b[0] = (p & 0x00FF); \ + b[1] = ((p & 0xFF00) >> 8); \ + w(b,2); \ +} + +void process(const char *fn) +{ + FILE *fp = NULL; + smk s; + char outfile[256]; + unsigned char b[5]; + + unsigned int i,k; + int j; + unsigned long temp_u; + + /* all and video info */ + unsigned long w, h, f; + double usf; + unsigned long total_frame_size; + + /* audio info */ + unsigned char a_t, a_c[7], a_d[7]; + unsigned long a_r[7]; + + unsigned char num_tracks = 0; + unsigned char **audio_data[7]; + unsigned long *audio_size[7]; + unsigned long total_audio_size[7] = {0}; + unsigned long total_total_audio_size = 0; + + const unsigned char *pal,*frame; + + unsigned long cur_frame; + + printf("--------\nsmk2avi processing %s...\n",fn); + + /* open the smk file */ + s = smk_open_file(fn,SMK_MODE_DISK); + if (s == NULL) goto error; + + /* get some info about the file */ + smk_info_all(s, NULL, &f, &usf); + smk_info_video(s, &w, &h, NULL); + smk_info_audio(s, &a_t, a_c, a_d, a_r); + + printf("\t\t\twidth: %lu, height: %lu, usec/frame: %lf, frames: %lu\n",w,h,usf,f); + + total_frame_size = w * h * 3; + + /* make 2 passes through the file. + first one is to pull all the audio tracks only. */ + smk_enable_all(s,a_t); + for (i = 0; i < 7; i ++) + { + if (a_t & (1 << i)) + { + audio_size[i] = malloc(f * sizeof(unsigned long)); + audio_data[i] = malloc(f * sizeof(unsigned char*)); + num_tracks ++; + } else { + audio_size[i] = NULL; + audio_data[i] = NULL; + } + } + + printf("\tAudio processing frame: "); + smk_first(s); + for (cur_frame = 0; cur_frame < f; cur_frame ++) + { + printf("%lu... ",cur_frame); + fflush(stdout); + for (i = 0; i < 7; i ++) + { + if (audio_size[i] != NULL) + { + audio_size[i][cur_frame] = smk_get_audio_size(s,i); + total_audio_size[i] += smk_get_audio_size(s,i); + total_total_audio_size += smk_get_audio_size(s,i); + audio_data[i][cur_frame] = malloc(audio_size[i][cur_frame]); + memcpy(audio_data[i][cur_frame],smk_get_audio(s,i),audio_size[i][cur_frame]); + } + } + smk_next(s); + } + printf("done!\n"); + + smk_enable_all(s,SMK_VIDEO_TRACK); + + sprintf(outfile,"%s.avi",fn); + + fp = fopen(outfile,"wb"); + + printf("Writing AVI file...\n"); + + // riff header + w("RIFF",4); + temp_u = 4 + 4 + (8 + 64 + 124 + (num_tracks * 102)) + + 8 + (4 + ((total_frame_size + 8) * f) + ( (num_tracks * 8) + total_total_audio_size)); + lu(temp_u); + w("AVI ",4); + + { + // avi header list + LIST; + temp_u = 8 + 64 + 124 + (num_tracks * 102); + lu(temp_u); + w("hdrl",4); + + { + // avi header + w("avih",4); + lu(56); + { + lu( (unsigned long)usf ); // microsec per frame + temp_u = total_frame_size + total_total_audio_size; + lu( temp_u ); // max bytes per sec + lu( 1 ); // padding granularity + lu( 0 ); // flags + lu( f ); // total frames + lu( 0 ); // initial frames + temp_u = num_tracks + 1; + lu( temp_u ); // streams + temp_u = total_frame_size + total_total_audio_size; + lu( temp_u ); // suggested buf size + lu( w ); // width + lu( h ); // height + lu( 0 ); // reserved (0-3) + lu( 0 ); + lu( 0 ); + lu( 0 ); + } + + // stream list: video stream +puts("Video stream header list"); + LIST + lu(116); + w("strl",4); + + { + w("strh",4); + lu(56); + { + w("vids",4); // fourcc type + w("DIB ",4); // fourcc handler + lu(0); // flags + lu(0); // priority + language + lu(0); // init frames + lu ((unsigned long)usf); // scale + lu( 1000000 ); // rate + lu(0); // start + temp_u = (unsigned long) ((double)f * 100000.0 / usf); + lu( temp_u ); // length (time in seconds ?) + lu(total_frame_size); // suggested bufsize + lu(-1); // quality + lu(total_frame_size); // samplesize + lu(0); // rcFrame + su(w); su(h); // rcFrame: right, bottom + } + + w("strf",4); + lu(40); + { + lu(40); // size + lu(w); // width + lu(h); // height + su(1); // planes + su(24); // bpp + lu(0); // compression + lu(total_frame_size); // total image frame size + lu(0); // xpels/meter + lu(0); // ypels/meter + lu(0); // colors used + lu(0); // colors important + } + } + + // stream list: audio stream(s) + for (i = 0; i < 7; i++) + { + if (audio_size[i] != NULL) + { +printf("-> Audio header %d, %luhz, %d bits, %d channels\n",i,a_r[i],a_d[i],a_c[i]); + LIST + lu(94); + w("strl",4); + + w("strh",4); + lu(56); + { + w("auds",4); // fourcc + lu(0); // handler (pcm) + lu(0); // flags + lu(0); // priority + language + lu(0); // initial frames + lu( a_c[i]); // scale + temp_u = a_r[i] * a_c[i]; + lu( temp_u ); // framerate + lu(0); // start + temp_u = (unsigned long) ((double)f * 100000.0 / usf); + lu( temp_u ); // time in seconds + temp_u = total_audio_size[i] / a_c[i]; + lu( temp_u ); // sugg. buf size + lu(-1); // quality + temp_u = total_audio_size[i]; + lu(temp_u); // sample size + lu(0); // rect ?? + lu(0); // rect ?? + } + + w("strf",4); + lu(18); + { + su(1); // format + su(a_c[i]); // channels + lu(a_r[i]); // samples/sec + temp_u = a_c[i] * a_r[i] * (a_d[i] / 8); // avg bytes/sec + lu(temp_u); + temp_u = a_c[i] * (a_d[i] / 8); // avg bytes/sec + su(temp_u); + su(a_d[i]); + su(0); + } + } + } + } + + smk_first(s); + printf("\tVideo processing frame:\n"); + + // movie data + LIST + temp_u = 4 + ((total_frame_size + 8) * f) + ( (num_tracks * 8) + total_total_audio_size); + lu(temp_u); + w("movi",4); + + for (i = 0; i < f; i ++) + { + w("00db",4); + lu(total_frame_size); + + frame = smk_get_video(s); + pal = smk_get_palette(s); + + if (frame == NULL || pal == NULL) goto error; + + for (j = h - 1; j >= 0; j--) + { + for (k = 0; k < w; k++) + { + w(&pal[frame[(j * w) + k] * 3 + 2],1); + w(&pal[frame[(j * w) + k] * 3 + 1],1); + w(&pal[frame[(j * w) + k] * 3],1); + } + } + printf("%u...",i); + fflush(stdout); + + smk_next(s); + } + printf("done!\n"); + + k = 0; + for (i = 0; i < 7; i++) + { + if (audio_size[i] != NULL) + { + k ++; + sprintf((char *)b,"%02uwb",k); + w(b,4); + temp_u = total_audio_size[i]; + lu(temp_u); + + for (j = 0; j < (int)f; j++) + { + w(audio_data[i][j],audio_size[i][j]); + } + } + } + } + + fclose(fp); + + smk_close(s); + + printf("done.\n--------\n"); + return; + +error: + if (fp) fclose(fp); + + smk_close(s); + printf("!!HAD ERRORS!!\n--------\n"); + return; + +} + +int main (int argc, char *argv[]) +{ + int i; + for (i = 1; i < argc; i ++) + { + process(argv[i]); + } + return 0; +} diff --git a/ext/libsmacker/smk_malloc.h b/ext/libsmacker/smk_malloc.h new file mode 100644 index 000000000..cf944e7f9 --- /dev/null +++ b/ext/libsmacker/smk_malloc.h @@ -0,0 +1,56 @@ +/** + libsmacker - A C library for decoding .smk Smacker Video files + Copyright (C) 2012-2017 Greg Kennedy + + See smacker.h for more information. + + smk_malloc.h + "Safe" implementations of malloc and free. + Verbose implementation of assert. +*/ + +#ifndef SMK_MALLOC_H +#define SMK_MALLOC_H + +/* assert */ +#include +/* calloc */ +#include +/* fprintf */ +#include + +/* Error messages from calloc */ +#include +#include + +/** + Safe free: attempts to prevent double-free by setting pointer to NULL. + Optionally warns on attempts to free a NULL pointer. +*/ +#define smk_free(p) \ +{ \ + assert (p); \ + free(p); \ + p = NULL; \ +} + +/** + Safe malloc: exits if calloc() returns NULL. + Also initializes blocks to 0. + Optionally warns on attempts to malloc over an existing pointer. + Ideally, one should not exit() in a library. However, if you cannot + calloc(), you probably have bigger problems. +*/ +#define smk_malloc(p, x) \ +{ \ + assert (p == NULL); \ + p = calloc(1, x); \ + if (!p) \ + { \ + fprintf(stderr, "libsmacker::smk_malloc(" #p ", %lu) - ERROR: calloc() returned NULL (file: %s, line: %lu)\n\tReason: [%d] %s\n", \ + (unsigned long) (x), __FILE__, (unsigned long)__LINE__, errno, strerror(errno)); \ + exit(EXIT_FAILURE); \ + } \ +} + +#endif diff --git a/ext/versions.txt b/ext/versions.txt index f8862aefe..0880ca486 100644 --- a/ext/versions.txt +++ b/ext/versions.txt @@ -1,4 +1,5 @@ -7z(lzma) : 9.22 -utfcpp : 2.3.4 -libpng : 1.2.50 -zlib : 1.2.8 +7z(lzma) : 9.22 +utfcpp : 2.3.4 +libpng : 1.2.50 +libsmacker : 1.2.0 +zlib : 1.2.8