Subversion Repositories ipe_artfile_utils

Compare Revisions

No changes between revisions

Regard whitespace Rev 1 → Rev HEAD

/trunk/LICENSE
0,0 → 1,202
 
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
 
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 
1. Definitions.
 
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
 
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
 
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
 
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
 
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
 
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
 
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
 
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
 
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
 
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
 
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
 
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
 
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
 
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
 
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
 
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
 
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
 
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
 
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
 
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
 
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
 
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
 
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
 
END OF TERMS AND CONDITIONS
 
APPENDIX: How to apply the Apache License to your work.
 
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
 
Copyright 2018 Daniel Marschall, ViaThinkSoft
 
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
/trunk/Makefile.linux
0,0 → 1,36
 
# On Linux, run:
# make -f Makefile.linux clean && make -f Makefile.linux
 
all: ipe_artfile_unpacker ipe_artfile_packer
 
ipe_artfile_unpacker: ipe_artfile_unpacker.c ipe_artfile_unpacker_ipe16.c ipe_artfile_unpacker_ipe32.c ipe16_lzw_decoder.c ipe32_lzw_decoder.c ipe16_bmpexport.c ipe32_bmpexport.c utils.c
gcc -std=c99 -Wall -c ipe_artfile_unpacker.c -o ipe_artfile_unpacker.o
gcc -std=c99 -Wall -c ipe_artfile_unpacker_ipe16.c -o ipe_artfile_unpacker_ipe16.o
gcc -std=c99 -Wall -c ipe_artfile_unpacker_ipe32.c -o ipe_artfile_unpacker_ipe32.o
gcc -std=c99 -Wall -c ipe16_lzw_decoder.c -o ipe16_lzw_decoder.o
gcc -std=c99 -Wall -c ipe32_lzw_decoder.c -o ipe32_lzw_decoder.o
gcc -std=c99 -Wall -c ipe16_bmpexport.c -o ipe16_bmpexport.o
gcc -std=c99 -Wall -c ipe32_bmpexport.c -o ipe32_bmpexport.o
gcc -std=c99 -Wall -c utils.c -o utils.o
gcc -o ipe_artfile_unpacker ipe_artfile_unpacker.o ipe_artfile_unpacker_ipe16.o ipe_artfile_unpacker_ipe32.o ipe16_lzw_decoder.o ipe32_lzw_decoder.o ipe16_bmpexport.o ipe32_bmpexport.o utils.o
rm *.o
 
ipe_artfile_packer: ipe_artfile_packer.c ipe_artfile_packer_ipe16_ba.c ipe_artfile_packer_ipe16_pip.c ipe_artfile_packer_ipe32.c ipe16_lzw_encoder.c ipe32_lzw_encoder.c ipe16_bmpimport.c ipe32_bmpimport.c utils.c
gcc -std=c99 -Wall -c ipe_artfile_packer.c -o ipe_artfile_packer.o
gcc -std=c99 -Wall -c ipe_artfile_packer_ipe16_ba.c -o ipe_artfile_packer_ipe16_ba.o
gcc -std=c99 -Wall -c ipe_artfile_packer_ipe16_pip.c -o ipe_artfile_packer_ipe16_pip.o
gcc -std=c99 -Wall -c ipe_artfile_packer_ipe32.c -o ipe_artfile_packer_ipe32.o
gcc -std=c99 -Wall -c ipe16_lzw_encoder.c -o ipe16_lzw_encoder.o
gcc -std=c99 -Wall -c ipe32_lzw_encoder.c -o ipe32_lzw_encoder.o
gcc -std=c99 -Wall -c ipe16_bmpimport.c -o ipe16_bmpimport.o
gcc -std=c99 -Wall -c ipe32_bmpimport.c -o ipe32_bmpimport.o
gcc -std=c99 -Wall -c utils.c -o utils.o
gcc -o ipe_artfile_packer ipe_artfile_packer.o ipe_artfile_packer_ipe16_ba.o ipe_artfile_packer_ipe16_pip.o ipe_artfile_packer_ipe32.o ipe16_lzw_encoder.c ipe32_lzw_encoder.o ipe16_bmpimport.o ipe32_bmpimport.o utils.o -lm
rm *.o
 
clean:
rm -f *.o
# TODO: if [ -f ... ] then rm
rm ipe_artfile_packer
rm ipe_artfile_unpacker
/trunk/Makefile.win
0,0 → 1,40
 
all: ipe_artfile_unpacker ipe_artfile_packer ipma_frame_extractor
 
ipe_artfile_unpacker: ipe_artfile_unpacker.c ipe_artfile_unpacker_ipe16.c ipe_artfile_unpacker_ipe32.c ipe16_lzw_decoder.c ipe32_lzw_decoder.c ipe16_bmpexport.c ipe32_bmpexport.c utils.c
gcc -std=c99 -Wall -c ipe_artfile_unpacker.c -o ipe_artfile_unpacker.o
gcc -std=c99 -Wall -c ipe_artfile_unpacker_ipe16.c -o ipe_artfile_unpacker_ipe16.o
gcc -std=c99 -Wall -c ipe_artfile_unpacker_ipe32.c -o ipe_artfile_unpacker_ipe32.o
gcc -std=c99 -Wall -c ipe16_lzw_decoder.c -o ipe16_lzw_decoder.o
gcc -std=c99 -Wall -c ipe32_lzw_decoder.c -o ipe32_lzw_decoder.o
gcc -std=c99 -Wall -c ipe16_bmpexport.c -o ipe16_bmpexport.o
gcc -std=c99 -Wall -c ipe32_bmpexport.c -o ipe32_bmpexport.o
gcc -std=c99 -Wall -c utils.c -o utils.o
gcc -o ipe_artfile_unpacker ipe_artfile_unpacker.o ipe_artfile_unpacker_ipe16.o ipe_artfile_unpacker_ipe32.o ipe16_lzw_decoder.o ipe32_lzw_decoder.o ipe16_bmpexport.o ipe32_bmpexport.o utils.o
del *.o
 
ipe_artfile_packer: ipe_artfile_packer.c ipe_artfile_packer_ipe16_ba.c ipe_artfile_packer_ipe16_pip.c ipe_artfile_packer_ipe32.c ipe16_lzw_encoder.c ipe32_lzw_encoder.c ipe16_bmpimport.c ipe32_bmpimport.c utils.c
gcc -std=c99 -Wall -c ipe_artfile_packer.c -o ipe_artfile_packer.o
gcc -std=c99 -Wall -c ipe_artfile_packer_ipe16_ba.c -o ipe_artfile_packer_ipe16_ba.o
gcc -std=c99 -Wall -c ipe_artfile_packer_ipe16_pip.c -o ipe_artfile_packer_ipe16_pip.o
gcc -std=c99 -Wall -c ipe_artfile_packer_ipe32.c -o ipe_artfile_packer_ipe32.o
gcc -std=c99 -Wall -c ipe16_lzw_encoder.c -o ipe16_lzw_encoder.o
gcc -std=c99 -Wall -c ipe32_lzw_encoder.c -o ipe32_lzw_encoder.o
gcc -std=c99 -Wall -c ipe16_bmpimport.c -o ipe16_bmpimport.o
gcc -std=c99 -Wall -c ipe32_bmpimport.c -o ipe32_bmpimport.o
gcc -std=c99 -Wall -c utils.c -o utils.o
gcc -lm -o ipe_artfile_packer ipe_artfile_packer.o ipe_artfile_packer_ipe16_ba.o ipe_artfile_packer_ipe16_pip.o ipe_artfile_packer_ipe32.o ipe16_lzw_encoder.c ipe32_lzw_encoder.o ipe16_bmpimport.o ipe32_bmpimport.o utils.o
del *.o
 
# Can only be compiled for Windows, because it requires "Video for Windows"!
ipma_frame_extractor: ipma_frame_extractor.c
gcc -std=c99 -Wall -c ipma_frame_extractor.c -o ipma_frame_extractor.o
gcc -lm -o ipma_frame_extractor ipma_frame_extractor.o -lVfw32 -lOle32
del *.o
 
clean:
del *.o
# TODO: if [ -f ... ] then rm
del ipe_artfile_packer.exe
del ipe_artfile_unpacker.exe
del ipma_frame_extractor.exe
/trunk/README.md
0,0 → 1,73
# Imagination Pilots ART-File packer/unpacker
 
The IPE Artfile Packer/Unpacker tool let you pack and unpack ART files of games by Imagination Pilots, so that you can extract and modify the graphics of the game.
 
The following games are supported:
- "Blown Away" (1994)
- "Panic in the Park" (1995)
- "Where's Waldo? At the Circus" (1995)
- "Where's Waldo? Exploring Geography" (1996)
- "Eraser Turnabout" (1997)
- "Virtual K'Nex" (1998)
 
Tested with Operating Systems
- Linux
- Windows
 
## Unpacker syntax
 
Example:
 
ipe_artfile_unpacker -v -i INPUT.ART -o outputFolder
 
Arguments:
 
-i Input Art file
 
-v Output verbose information (-vv more verbose)
 
-o Output folder (must exist)
 
## Packer syntax
 
Example:
 
ipe_artfile_packer -v -t pip -i inputFolder -o OUTPUT.ART
 
Arguments:
 
-i Input folder
 
-v Output verbose information (-vv more verbose)
 
-o Output ART files
 
-t Game type (ba, pip, waldo, waldo2, eraser or knex)
 
 
 
# Imagination Pilots Transparent Video Frame Extractor
 
Extracts video frames from `IPMA` and `IP20` coded AVI files into Bitmap files
 
The following games are supported:
- "Blown Away" (1994)
- "Panic in the Park" (1995)
- "Where's Waldo? At the Circus" (1995)
- "Where's Waldo? Exploring Geography" (1996)
 
This tool is only available on Windows, since it requires the "Video for Windows" API.
 
More information about the codecs can be found here: https://misc.daniel-marschall.de/spiele/blown_away/ipma_codec/
 
## Syntax
 
Example:
 
ipma_frame_extractor -i inputfile -o outputdir
 
Arguments:
 
-i Input file (AVI)
 
-o Output directory (will be created if it does not exist)
/trunk/TODO
0,0 → 1,6
 
Future: Allow several input arguments (e.g. *.ART)
 
Packer: Like the unpacker, implement a simulation mode (so that no ART file is written)
 
Please see also : grep -r "// TODO"
/trunk/artfile_overview.txt
0,0 → 1,56
 
ART FILES BY IMAGINATION PILOTS
Technical overview by Daniel Marschall 15 Feb 2018
 
 
VERSION 1.x (16-bit era, "IPE16")
=================================
 
Version 1.0 For game: "Blown Away" (1994)
* File signature "Art" + 20 NUL bytes
* Image names up to 23 characters
(unsure if the 23th byte must be a zero terminator)
* LZW implementation: GIF standard implementation
- variable code length between 9 and 12 bits
- clear code (256)
- end code (257)
* Pictures can only be 8-bit
* Each picture can either be LZW compressed (code "P")
or uncompressed (code "p", lower case)
* Decompressed/raw data: Top-down pixel data
* Attached: Optional palette (R,G,B), uncompressed.
(Sprites usually do not have a palette, so the game
will use the palette of the parent picture)
 
Version 1.1 For games: "Panic in the Park" (1995)
"Where's Waldo? At the Circus" (1995)
* Codes for compression/raw are now "Q" and "q" instead of
"P" and "p"
* Each picture has now a relative X- and Y-offsets for 'fine-tuning'.
These offsets cannot be negative, though.
 
VERSION 2.x (32-bit era, "IPE32")
=================================
 
Version 2.0 For games: "Where's Waldo? Exploring Geography" (1996)
"Eraser Turnabout" (1997)
"Virtual K'Nex" (1998)
* File signature "ART_DATA"
* Image names up to 8 characters;
no zero termination required after the 8 characters.
* LZW implementation by Mark R. Nelson and Shawn M. Regan
- variable code length between 9 and 13 bits
- clear code (256)
- end code (257)
* Pictures are divided into chunks. Each chunk can be compressed or
uncompressed. Limitations:
- Each compressed chunk (except the last one) must decode into
exactly 16382 (0x3FFE) bytes of uncompressed data.
- The size of the compressed data of a chunk must not exceed the
size of the original uncompressed data.
* The size of each chunk (raw or compressed) is limited
to max. 32767 Bytes (0x7FFF).
* Decompressed/raw data: Windows Bitmap (without file header);
therefore, theoretically every color depth/compression/etc. can be used.
For 8-bit pictures, the palette is included in the Bitmap.
* Re-using of parent palette is not possible.
Property changes:
Added: svn:mime-type
+text/plain
\ No newline at end of property
/trunk/artfile_structure_10.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes:
Added: svn:mime-type
+image/png
\ No newline at end of property
/trunk/artfile_structure_11.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes:
Added: svn:mime-type
+image/png
\ No newline at end of property
/trunk/artfile_structure_20.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes:
Added: svn:mime-type
+image/png
\ No newline at end of property
/trunk/bitmap.h
0,0 → 1,51
#ifndef __inc__bitmap
#define __inc__bitmap
 
#include <stdint.h>
 
// "BM"
#define BI_SIGNATURE 0x4D42
 
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751%28v=vs.85%29.aspx
#define BYTE uint8_t
#define WORD uint16_t
#define DWORD uint32_t
#define LONG int32_t
 
// These parts were extracted from WinGDI.h and WinDef.h
 
#define BI_RGB 0
 
#pragma pack(push,2)
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER,*LPBITMAPFILEHEADER,*PBITMAPFILEHEADER;
#pragma pack(pop)
 
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER,*LPBITMAPINFOHEADER,*PBITMAPINFOHEADER;
 
typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD,*LPRGBQUAD;
 
#endif // #ifndef __inc__bitmap
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe16_artfile.h
0,0 → 1,85
/**
* ART files for Imagination Pilots Entertainment 16-bit games (IPE16)
* - Blown Away - The Interactive Game by Imagination Pilots (BA)
* - Panic in the Park - The Interactive Game by Imagination Pilots (PiP)
* - Where's Waldo? At the Circus (Waldo1)
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe16_artfile
#define __inc__ipe16_artfile
 
#include <stdint.h>
 
#define BA_COMPRESSIONTYPE_LZW 'P'
#define BA_COMPRESSIONTYPE_NONE 'p'
 
// PANIC.EXE offset 0x44365: Choice between "Q" and "q"
#define PIP_COMPRESSIONTYPE_LZW 'Q'
#define PIP_COMPRESSIONTYPE_NONE 'q'
 
#define IPE16_PALETTETYPE_ATTACHED 'X'
 
// Pictures with the type 'C' do not have a palette.
// They use the palette of their parent picture where they are embedded in
#define IPE16_PALETTETYPE_PARENT 'C'
 
#define IPE16_NAME_SIZE 23
 
#define IPE16_MAGIC_ART "Art"
#define IPE16_MAGIC_DUMMY '?'
 
#pragma pack(push, 1)
 
typedef struct tagIpe16FileHeader {
char magic[IPE16_NAME_SIZE]; // always "Art\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" (ignored by the game)
char dummy; // always '?' (ignored by the game)
uint32_t numHeaderEntries; // number of headers including this file header (number of follow-up Ipe16PictureEntryHeader entries plus one)
uint32_t totalFileSize; // total file size including this header (ignored by the game)
} Ipe16FileHeader;
 
typedef struct tagIpe16PictureEntryHeader {
char name[IPE16_NAME_SIZE]; // zero terminated string. case sensitive
char paletteType; // 'X' (0x58) = RGB palette attached
// 'C' (0x43) = no palette attached (for embedded picture, use palette of parent picture)
uint32_t offset; // offset to the picture (PictureHeader)
uint32_t size; // size of the picture (PictureHeader + picture data + optional palette)
} Ipe16PictureEntryHeader;
 
typedef struct tagBAPictureHeader {
char compressionType; // Compression type of the follow-up data (top down pixel data; the palette won't be compressed)
// 'P' (0x50, upper case) = LZW compression, more precisely:
// The LZW variant of the GIF specification,
// but without splitting the output data into chunks
// 'p' (0x70, lower case) = No compression
uint16_t width; // width of the picture
uint16_t height; // height of the picture
} BAPictureHeader;
 
typedef struct tagPipPictureHeader {
char compressionType; // Compression type of the follow-up data (top down pixel data; the palette won't be compressed)
// 'Q' (0x51, upper case) = LZW compression, more precisely:
// The LZW variant of the GIF specification,
// but without splitting the output data into chunks
// 'q' (0x71, lower case) = No compression
uint16_t offsetX; // Additional offsets for 'fine-tuning'
uint16_t offsetY;
uint16_t width; // width of the picture
uint16_t height; // height of the picture
} PipPictureHeader;
 
typedef struct tagIpe16ColorTableEntry {
uint8_t r;
uint8_t g;
uint8_t b;
} Ipe16ColorTableEntry;
 
typedef struct tagIpe16ColorTable {
Ipe16ColorTableEntry colors[256];
} Ipe16ColorTable;
 
#pragma pack(pop)
 
#endif // #ifndef __inc__ipe16_artfile
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe16_bmpexport.c
0,0 → 1,106
/**
* Bitmap Export for Imagination Pilots Entertainment 16-bit games (IPE16)
* - Blown Away - The Interactive Game by Imagination Pilots (BA)
* - Panic in the Park - The Interactive Game by Imagination Pilots (PiP)
* - Where's Waldo? At the Circus (Waldo1)
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
**/
 
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
 
#include "ipe16_bmpexport.h"
 
#define BMP_LINE_PADDING 4
 
// Windows 98 does not support top-down bitmaps, so we need to flip everything
#define USE_BOTTOMUP
 
RGBQUAD ipe16_rgb_to_rgbquad(Ipe16ColorTableEntry cte) {
RGBQUAD ret;
ret.rgbRed = cte.r;
ret.rgbGreen = cte.g;
ret.rgbBlue = cte.b;
ret.rgbReserved = 0;
return ret;
}
 
void ipe16_write_bmp(FILE* output, unsigned int width, unsigned int height, unsigned char* imagedata, size_t imagedata_len, Ipe16ColorTable ct) {
 
#ifdef USE_BOTTOMUP
const size_t bmpDataSize = width*height;
unsigned char* bmpData = (unsigned char*)malloc(bmpDataSize);
assert(bmpData != NULL);
int h;
for (h=0; h<height; ++h) {
int idx_dest = (height-1)-h;
int idx_src = h;
assert(idx_src*width >= 0);
assert(idx_dest*width >= 0);
memcpy(bmpData+idx_dest*width, imagedata+idx_src*width, width);
}
imagedata = bmpData;
#endif
 
// Each line must be padded to a multiple of 4
int pad = (BMP_LINE_PADDING - (width % BMP_LINE_PADDING)) % BMP_LINE_PADDING;
int newwidth = width+pad;
int newsize = newwidth * height;
unsigned char* padded_imagedata = (unsigned char*)malloc(newsize);
int i;
for (i=0; i<height; ++i) {
int offset = newwidth*i;
memcpy(&padded_imagedata[offset], imagedata, width);
memset(&padded_imagedata[offset+width], 0, pad);
imagedata += width;
}
 
// Color table in a bitmap is BGR0, while Blown Away uses RGB
const unsigned int NUM_COLORS = sizeof(ct.colors)/sizeof(ct.colors[0]);
RGBQUAD rgba_colortable[NUM_COLORS];
for (i=0; i<NUM_COLORS; ++i) {
rgba_colortable[i] = ipe16_rgb_to_rgbquad(ct.colors[i]);
}
 
BITMAPFILEHEADER bfh;
bfh.bfType = BI_SIGNATURE;
bfh.bfSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+sizeof(rgba_colortable)+newsize;
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+sizeof(rgba_colortable);
fwrite(&bfh, sizeof(bfh), 1, output);
 
BITMAPINFOHEADER bih;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = width;
#ifdef USE_BOTTOMUP
bih.biHeight = height; // (positive = "bottom-up"-Bitmap)
#else
bih.biHeight = -height; // (negative = "top-down"-Bitmap)
#endif
bih.biPlanes = 1;
bih.biBitCount = 8;
bih.biCompression = BI_RGB;
bih.biSizeImage = 0;
bih.biXPelsPerMeter = 0;
bih.biYPelsPerMeter = 0;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
fwrite(&bih, sizeof(bih), 1, output);
 
// Color table
fwrite(rgba_colortable, sizeof(rgba_colortable), 1, output);
 
// Image data
fwrite(padded_imagedata, newsize, 1, output);
 
free(padded_imagedata);
#ifdef USE_BOTTOMUP
free(bmpData);
#endif
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe16_bmpexport.h
0,0 → 1,23
/**
* Bitmap Export for Imagination Pilots Entertainment 16-bit games (IPE16)
* - Blown Away - The Interactive Game by Imagination Pilots (BA)
* - Panic in the Park - The Interactive Game by Imagination Pilots (PiP)
* - Where's Waldo? At the Circus (Waldo1)
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe16_bmpexport
#define __inc__ipe16_bmpexport
 
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
 
#include "bitmap.h"
#include "ipe16_artfile.h"
 
void ipe16_write_bmp(FILE* output, unsigned int width, unsigned int height, unsigned char* imagedata, size_t imagedata_len, Ipe16ColorTable ct);
 
#endif // #ifndef __inc__ipe16_bmpexport
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe16_bmpimport.c
0,0 → 1,108
/**
* Bitmap Import for Imagination Pilots Entertainment 16-bit games (IPE16)
* - Blown Away - The Interactive Game by Imagination Pilots (BA)
* - Panic in the Park - The Interactive Game by Imagination Pilots (PiP)
* - Where's Waldo? At the Circus (Waldo1)
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
**/
 
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
 
#include "bitmap.h"
#include "ipe16_bmpimport.h"
 
Ipe16ColorTableEntry rgbquad_to_ipe16_rgb(RGBQUAD rq) {
Ipe16ColorTableEntry cte;
cte.r = rq.rgbRed;
cte.g = rq.rgbGreen;
cte.b = rq.rgbBlue;
return cte;
}
 
bool ipe16_bmp_import(FILE* fibBitmap, Ipe16BmpImportData* result) {
BITMAPFILEHEADER bitmapFileHeader;
BITMAPINFOHEADER bitmapInfoHeader;
 
#define EXIT_ERROR(msg) { sprintf(result->error, msg); return false; }
 
fseek(fibBitmap, 0, SEEK_SET);
 
if (!fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, fibBitmap) ||
(bitmapFileHeader.bfType != BI_SIGNATURE) ||
!fread(&bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, fibBitmap)) {
EXIT_ERROR("Not a bitmap file");
}
 
if (bitmapInfoHeader.biCompression != BI_RGB) {
EXIT_ERROR("At the moment, only uncompressed files can be read.");
}
 
if (bitmapInfoHeader.biBitCount != 8) {
EXIT_ERROR("The color depth has to be 8 bpp.");
}
 
Ipe16ColorTable* ct = malloc(sizeof(Ipe16ColorTable));
#define NUM_COLORS 256
RGBQUAD rgba_colortable[NUM_COLORS];
if (!fread(&rgba_colortable, sizeof(rgba_colortable), 1, fibBitmap)) {
EXIT_ERROR("Error reading color table.");
}
int i;
for (i=0; i<NUM_COLORS; ++i) {
Ipe16ColorTableEntry x;
x = rgbquad_to_ipe16_rgb(rgba_colortable[i]);
ct->colors[i] = x;
}
 
fseek(fibBitmap, bitmapFileHeader.bfOffBits, SEEK_SET);
 
const uint32_t realwidth = bitmapInfoHeader.biWidth;
const uint32_t realheight = abs(bitmapInfoHeader.biHeight);
const size_t bmpDataSize = realwidth*realheight;
unsigned char* bmpData = (unsigned char*)malloc(bmpDataSize);
assert(bmpData != NULL);
const int padded_width = (bitmapInfoHeader.biWidth + 3) & ~0x03; // http://stackoverflow.com/a/2022194/3544341
unsigned char* bmpLine = (unsigned char*)malloc(padded_width);
assert(bmpLine != NULL);
int h;
for (h=0; h<realheight; ++h) {
if (fread(bmpLine, padded_width, 1, fibBitmap) != 1) {
free(bmpLine);
free(bmpData);
free(ct);
EXIT_ERROR("Error while reading pixel data.");
}
int idx;
if (bitmapInfoHeader.biHeight > 0) {
// "bottom-up" (most usual)
// Convert to "top-down":
idx = (realheight-1)-h;
} else {
// "top-down"
idx = h;
}
assert(idx*realwidth >= 0);
memcpy(bmpData+idx*realwidth, bmpLine, realwidth);
}
free(bmpLine);
 
result->colorTable = ct;
result->bmpData = bmpData;
result->bmpDataSize = bmpDataSize;
result->width = realwidth;
result->height = realheight;
result->error[0] = 0;
return true;
}
 
void ipe16_free_bmpimport_result(Ipe16BmpImportData *res) {
if (res->colorTable) free(res->colorTable);
if (res->bmpData) free(res->bmpData);
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe16_bmpimport.h
0,0 → 1,34
/**
* Bitmap Import for Imagination Pilots Entertainment 16-bit games (IPE16)
* - Blown Away - The Interactive Game by Imagination Pilots (BA)
* - Panic in the Park - The Interactive Game by Imagination Pilots (PiP)
* - Where's Waldo? At the Circus (Waldo1)
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe16_bmpimport
#define __inc__ipe16_bmpimport
 
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
 
#include "bitmap.h"
#include "ipe16_artfile.h"
 
typedef struct tagIpe16BmpImportData {
Ipe16ColorTable* colorTable;
unsigned char* bmpData;
size_t bmpDataSize;
unsigned int width;
unsigned int height;
char error[255];
} Ipe16BmpImportData;
 
bool ipe16_bmp_import(FILE* fibBitmap, Ipe16BmpImportData* result);
void ipe16_free_bmpimport_result(Ipe16BmpImportData *res);
 
#endif // #ifndef __inc__ipe16_bmpimport
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe16_lzw_decoder.c
0,0 → 1,179
/**
* LZW Decoder for Imagination Pilots Entertainment 16-bit games (IPE16)
* - Blown Away - The Interactive Game by Imagination Pilots (BA)
* - Panic in the Park - The Interactive Game by Imagination Pilots (PiP)
* - Where's Waldo? At the Circus (Waldo1)
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
*
* The code is based on "Cross platform GIF source code" (c) L. Patrick
* http://www.cs.usyd.edu.au/~graphapp/package/src/libgif/gif.c
* It was simplified and modified to encode IPE16-LZW instead of GIF-LZW.
* The game uses exactly the compressed stream as defined in the GIF standard,
* but the compressed stream is not divided into chunks.
**/
 
#include "ipe16_lzw_decoder.h"
#include "utils.h"
 
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
 
Ipe16LZWDecoder* new_ipe16lzw_decoder(void) {
return (Ipe16LZWDecoder*)app_zero_alloc(sizeof(Ipe16LZWDecoder));
}
 
void del_ipe16lzw_decoder(Ipe16LZWDecoder* decoder) {
free(decoder);
}
 
void ipe16lzw_init_decoder(Ipe16LZWDecoder* decoder) {
decoder->running_code = FIRST_CODE;
decoder->running_bits = LZ_MIN_BITS;
decoder->max_code_plus_one = 1 << decoder->running_bits;
decoder->shift_state = 0;
decoder->shift_data = 0;
 
int i;
for (i = 0; i <= LZ_MAX_CODE; i++) {
decoder->prefix[i] = NO_SUCH_CODE;
}
}
 
int ipe16lzw_read_code(FILE* inFile, Ipe16LZWDecoder* decoder) {
int code;
unsigned char next_byte;
static int code_masks[] = {
0x0000, 0x0001, 0x0003, 0x0007,
0x000f, 0x001f, 0x003f, 0x007f,
0x00ff, 0x01ff, 0x03ff, 0x07ff,
0x0fff
};
 
while (decoder->shift_state < decoder->running_bits) {
next_byte = read_byte(inFile);
decoder->shift_data |=
((unsigned long) next_byte) << decoder->shift_state;
decoder->shift_state += 8;
}
 
code = decoder->shift_data & code_masks[decoder->running_bits];
 
decoder->shift_data >>= decoder->running_bits;
decoder->shift_state -= decoder->running_bits;
 
if (++decoder->running_code > decoder->max_code_plus_one
&& decoder->running_bits < LZ_MAX_BITS) {
decoder->max_code_plus_one <<= 1;
decoder->running_bits++;
}
 
return code;
}
 
static int ipe16lzw_trace_prefix(unsigned int* prefix, int code, int clear_code) {
int i = 0;
 
while (code > clear_code && i++ <= LZ_MAX_CODE) {
code = prefix[code];
}
return code;
}
 
// We don't do unsigned, because we want to have <0 as error result
/*unsigned*/ int ipe16lzw_decode(FILE* inFile, Ipe16LZWDecoder* decoder, unsigned char* output, int outputLength) {
int i = 0, j;
int current_code;
int current_prefix;
int stack_ptr = 0;
int prev_code = NO_SUCH_CODE;
unsigned char* stack;
unsigned int* prefix;
unsigned int* suffix;
unsigned int bytes_written = 0;
 
ipe16lzw_init_decoder(decoder);
 
prefix = decoder->prefix;
suffix = decoder->suffix;
stack = decoder->stack;
 
/* Pop the stack */
while (stack_ptr != 0 && i < outputLength) {
output[i++] = stack[--stack_ptr];
//if (i > bytes_written) bytes_written = i;
++bytes_written;
}
 
while (i < outputLength) {
current_code = ipe16lzw_read_code(inFile, decoder);
 
if (current_code == END_CODE) {
if (i != outputLength - 1) // || decoder->pixel_count != 0
return -1; /* unexpected eof */
i++;
} else if (current_code == CLEAR_CODE) {
for (j = 0; j <= LZ_MAX_CODE; j++) {
prefix[j] = NO_SUCH_CODE;
}
decoder->running_code = FIRST_CODE;
decoder->running_bits = LZ_MIN_BITS;
decoder->max_code_plus_one = 1 << decoder->running_bits;
prev_code = NO_SUCH_CODE;
} else {
if (current_code < CLEAR_CODE) {
output[i++] = current_code;
//if (i > bytes_written) bytes_written = i;
++bytes_written;
} else {
if ((current_code < 0) || (current_code > LZ_MAX_CODE))
return -2; /* image defect */
if (prefix[current_code] == NO_SUCH_CODE) {
if (current_code == decoder->running_code - 2) {
current_prefix = prev_code;
suffix[decoder->running_code - 2]
= stack[stack_ptr++]
= ipe16lzw_trace_prefix(prefix, prev_code, CLEAR_CODE);
} else {
return -3; /* image defect */
}
} else {
current_prefix = current_code;
}
j = 0;
while (j++ <= LZ_MAX_CODE && current_prefix > CLEAR_CODE && current_prefix <= LZ_MAX_CODE) {
stack[stack_ptr++] = suffix[current_prefix];
current_prefix = prefix[current_prefix];
}
if (j >= LZ_MAX_CODE || current_prefix > LZ_MAX_CODE)
return -4; /* image defect */
 
stack[stack_ptr++] = current_prefix;
 
while (stack_ptr != 0 && i < outputLength) {
output[i++] = stack[--stack_ptr];
//if (i > bytes_written) bytes_written = i;
++bytes_written;
}
}
if (prev_code != NO_SUCH_CODE) {
if ((decoder->running_code < 2) ||
(decoder->running_code > LZ_MAX_CODE+2))
return -5; /* image defect */
prefix[decoder->running_code - 2] = prev_code;
 
if (current_code == decoder->running_code - 2) {
suffix[decoder->running_code - 2]
= ipe16lzw_trace_prefix(prefix, prev_code, CLEAR_CODE);
} else {
suffix[decoder->running_code - 2]
= ipe16lzw_trace_prefix(prefix, current_code, CLEAR_CODE);
}
}
prev_code = current_code;
}
}
 
return bytes_written;
}
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe16_lzw_decoder.h
0,0 → 1,48
/**
* LZW Decoder for Imagination Pilots Entertainment 16-bit games (IPE16)
* - Blown Away - The Interactive Game by Imagination Pilots (BA)
* - Panic in the Park - The Interactive Game by Imagination Pilots (PiP)
* - Where's Waldo? At the Circus (Waldo1)
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
*
* The code is based on "Cross platform GIF source code" (c) L. Patrick
* http://www.cs.usyd.edu.au/~graphapp/package/src/libgif/gif.c
* It was simplified and modified to encode IPE16-LZW instead of GIF-LZW.
* The game uses exactly the compressed stream as defined in the GIF standard,
* but the compressed stream is not divided into chunks.
**/
 
#ifndef __inc__ipe16_lzw_decoder
#define __inc__ipe16_lzw_decoder
 
#include <stdio.h>
#include <stdbool.h>
 
#define LZ_MIN_BITS 9
#define LZ_MAX_BITS 12
 
#define LZ_MAX_CODE 4095 /* Largest 12 bit code */
#define NO_SUCH_CODE 4098 /* Impossible code = empty */
 
#define CLEAR_CODE 256
#define END_CODE 257
#define FIRST_CODE 258
 
typedef struct tagIpe16LZWDecoder {
int running_code;
int running_bits;
int max_code_plus_one;
int shift_state;
unsigned long shift_data;
unsigned char stack[LZ_MAX_CODE+1];
unsigned int suffix[LZ_MAX_CODE+1];
unsigned int prefix[LZ_MAX_CODE+1];
} Ipe16LZWDecoder;
 
Ipe16LZWDecoder* new_ipe16lzw_decoder(void);
void del_ipe16lzw_decoder(Ipe16LZWDecoder* decoder);
/*unsigned*/ int ipe16lzw_decode(FILE* outFile, Ipe16LZWDecoder *decoder, unsigned char *input, int inputLength);
 
#endif // #ifndef __inc__ipe16_lzw_decoder
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe16_lzw_encoder.c
0,0 → 1,140
/**
* LZW Encoder for Imagination Pilots Entertainment 16-bit games (IPE16)
* - Blown Away - The Interactive Game by Imagination Pilots (BA)
* - Panic in the Park - The Interactive Game by Imagination Pilots (PiP)
* - Where's Waldo? At the Circus (Waldo1)
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
*
* The code is based on "Cross platform GIF source code" (c) L. Patrick
* http://www.cs.usyd.edu.au/~graphapp/package/src/libgif/gif.c
* It was simplified and modified to encode IPE16-LZW instead of GIF-LZW.
* The game uses exactly the compressed stream as defined in the GIF standard,
* but the compressed stream is not divided into chunks.
**/
 
#include "ipe16_lzw_encoder.h"
#include "utils.h"
 
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
 
Ipe16LZWEncoder* new_ipe16lzw_encoder(void) {
return (Ipe16LZWEncoder*)app_zero_alloc(sizeof(Ipe16LZWEncoder));
}
 
void del_ipe16lzw_encoder(Ipe16LZWEncoder* encoder) {
free(encoder);
}
 
void ipe16lzw_write_code(FILE* outFile, Ipe16LZWEncoder* encoder, int code) {
if (code == FLUSH_OUTPUT) {
/* write all remaining data */
while (encoder->shift_state > 0) {
fputc(encoder->shift_data & 0xff, outFile);
encoder->shift_data >>= 8;
encoder->shift_state -= 8;
}
encoder->shift_state = 0;
} else {
encoder->shift_data |= ((long) code) << encoder->shift_state;
encoder->shift_state += encoder->running_bits;
 
while (encoder->shift_state >= 8) {
/* write full bytes */
fputc(encoder->shift_data & 0xff, outFile);
encoder->shift_data >>= 8;
encoder->shift_state -= 8;
}
}
 
if (encoder->running_code >= encoder->max_code_plus_one && code <= LZ_MAX_CODE) {
encoder->max_code_plus_one = 1 << ++encoder->running_bits;
}
}
 
static void ipe16lzw_clear_hash_table(unsigned long* hash_table) {
int i;
for (i=0; i<HT_SIZE; i++) {
hash_table[i] = 0xFFFFFFFFL;
}
}
 
void ipe16lzw_init_encoder(Ipe16LZWEncoder* encoder) {
encoder->running_code = FIRST_CODE;
encoder->running_bits = LZ_MIN_BITS;
encoder->max_code_plus_one = 1 << encoder->running_bits;
encoder->shift_state = 0;
encoder->shift_data = 0;
}
 
static int ipe16lzw_hash_key(unsigned long key) {
return ((key >> 12) ^ key) & HT_KEY_MASK;
}
 
static int ipe16lzw_lookup_hash(unsigned long* hash_table, unsigned long key) {
int hkey = ipe16lzw_hash_key(key);
unsigned long htkey;
 
while ((htkey = HT_GET_KEY(hash_table[hkey])) != 0xFFFFFL) {
if (key == htkey) {
return HT_GET_CODE(hash_table[hkey]);
}
hkey = (hkey + 1) & HT_KEY_MASK;
}
 
return -1;
}
 
static void ipe16lzw_add_hash_entry(unsigned long* hash_table, unsigned long key, int code) {
int hkey = ipe16lzw_hash_key(key);
 
while (HT_GET_KEY(hash_table[hkey]) != 0xFFFFFL) {
hkey = (hkey + 1) & HT_KEY_MASK;
}
hash_table[hkey] = HT_PUT_KEY(key) | HT_PUT_CODE(code);
}
 
void ipe16lzw_encode(FILE* outFile, Ipe16LZWEncoder* encoder, unsigned char* input, int inputLength) {
int i = 0, current_code, new_code;
unsigned long new_key;
unsigned char pixval;
/* Init stuff */
ipe16lzw_init_encoder(encoder);
ipe16lzw_clear_hash_table(encoder->hash_table);
ipe16lzw_write_code(outFile, encoder, CLEAR_CODE);
 
if (inputLength == 0) return;
current_code = input[i++];
 
while (i < inputLength) {
pixval = input[i++]; /* Fetch next pixel from stream */
 
new_key = (((unsigned long) current_code) << 8) + pixval;
if ((new_code = ipe16lzw_lookup_hash(encoder->hash_table, new_key)) >= 0) {
current_code = new_code;
} else {
ipe16lzw_write_code(outFile, encoder, current_code);
current_code = pixval;
 
if (encoder->running_code >= LZ_MAX_CODE) {
ipe16lzw_write_code(outFile, encoder, CLEAR_CODE);
encoder->running_code = FIRST_CODE;
encoder->running_bits = LZ_MIN_BITS;
encoder->max_code_plus_one = 1 << encoder->running_bits;
ipe16lzw_clear_hash_table(encoder->hash_table);
} else {
/* Put this unique key with its relative code in hash table */
ipe16lzw_add_hash_entry(encoder->hash_table, new_key, encoder->running_code++);
}
}
}
 
/* Flush */
ipe16lzw_write_code(outFile, encoder, current_code);
ipe16lzw_write_code(outFile, encoder, END_CODE);
ipe16lzw_write_code(outFile, encoder, FLUSH_OUTPUT);
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe16_lzw_encoder.h
0,0 → 1,53
/**
* LZW Encoder for Imagination Pilots Entertainment 16-bit games (IPE16)
* - Blown Away - The Interactive Game by Imagination Pilots (BA)
* - Panic in the Park - The Interactive Game by Imagination Pilots (PiP)
* - Where's Waldo? At the Circus (Waldo1)
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
*
* The code is based on "Cross platform GIF source code" (c) L. Patrick
* http://www.cs.usyd.edu.au/~graphapp/package/src/libgif/gif.c
* It was simplified and modified to encode IPE16-LZW instead of GIF-LZW.
* The game uses exactly the compressed stream as defined in the GIF standard,
* but the compressed stream is not divided into chunks.
**/
 
#ifndef __inc__ipe16_lzw_encoder
#define __inc__ipe16_lzw_encoder
 
#include <stdio.h>
#include <stdbool.h>
 
#define LZ_MIN_BITS 9
 
#define LZ_MAX_CODE 4095 /* Largest 12 bit code */
#define FLUSH_OUTPUT 4096 /* Impossible code = flush */
 
#define HT_SIZE 8192 /* 13 bit hash table size */
#define HT_KEY_MASK 0x1FFF /* 13 bit key mask */
 
#define CLEAR_CODE 256
#define END_CODE 257
#define FIRST_CODE 258
 
#define HT_GET_KEY(x) ((x) >> 12)
#define HT_GET_CODE(x) ((x) & 0x0FFF)
#define HT_PUT_KEY(x) ((x) << 12)
#define HT_PUT_CODE(x) ((x) & 0x0FFF)
 
typedef struct tagIpe16LZWEncoder {
int running_code;
int running_bits;
int max_code_plus_one;
int shift_state;
unsigned long shift_data;
unsigned long hash_table[HT_SIZE];
} Ipe16LZWEncoder;
 
Ipe16LZWEncoder* new_ipe16lzw_encoder(void);
void del_ipe16lzw_encoder(Ipe16LZWEncoder* encoder);
void ipe16lzw_encode(FILE* outFile, Ipe16LZWEncoder* encoder, unsigned char* input, int inputLength);
 
#endif // #ifndef __inc__ipe16_lzw_encoder
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe32_artfile.h
0,0 → 1,49
/**
* ART files for Imagination Pilots Entertainment 32-bit games (IPE32)
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2018
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe32_artfile
#define __inc__ipe32_artfile
 
#include <stdint.h>
 
#define IPE32_COMPRESSIONTYPE_LZW 0
#define IPE32_COMPRESSIONTYPE_NONE 1
 
#define IPE32_NAME_SIZE 8
 
#define IPE32_MAGIC_ART "ART_DATA"
 
#pragma pack(push, 1)
 
typedef struct tagIpe32FileHeader {
char magic[IPE32_NAME_SIZE]; // always "ART_DATA"
uint32_t totalHeaderSize; // size of all headers (file header and all picture headers). headerSize/16 = numberPictures
uint32_t reserved; // always 0
} Ipe32FileHeader;
 
typedef struct tagIpe32PictureEntryHeader {
char name[IPE32_NAME_SIZE];
uint32_t offset; // offset to the picture (Ipe32PictureHeader)
uint32_t uncompressedSize; // size of the picture (picture data + palette)
} Ipe32PictureEntryHeader;
 
/*
typedef struct tagIpe32PictureChunk {
unsigned compressionType : 1; // Compression type of the follow-up data
// 0 = LZW-like compression (special implementation)
// 1 = None
unsigned chunkDataSize : 15; // size of the chunk data
//char data[];
} Ipe32PictureChunk;
*/
 
#pragma pack(pop)
 
#endif // #ifndef __inc__ipe32_artfile
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe32_bmpexport.c
0,0 → 1,29
/**
* Bitmap Export for Imagination Pilots Entertainment 32-bit games (IPE32)
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
**/
 
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
 
#include "bitmap.h"
#include "ipe32_bmpexport.h"
 
void ipe32_write_bmp(FILE* output, unsigned char* imagedata, size_t imagedata_len) {
BITMAPFILEHEADER bh={0};
bh.bfType = BI_SIGNATURE;
bh.bfSize = sizeof(bh) + imagedata_len;
bh.bfReserved1 = 0;
bh.bfReserved2 = 0;
bh.bfOffBits = 0x436;
 
fwrite(&bh, 1, sizeof(bh), output);
fwrite(imagedata, 1, imagedata_len, output);
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe32_bmpexport.h
0,0 → 1,20
/**
* Bitmap Export for Imagination Pilots Entertainment 32-bit games (IPE32)
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe32_bmpexport
#define __inc__ipe32_bmpexport
 
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
 
void ipe32_write_bmp(FILE* output, unsigned char* imagedata, size_t imagedata_len);
 
#endif // #ifndef __inc__ipe32_bmpexport
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe32_bmpimport.c
0,0 → 1,83
/**
* Bitmap Import for Imagination Pilots Entertainment 32-bit games (IPE32)
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
**/
 
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
 
#include "bitmap.h"
#include "utils.h"
#include "ipe32_bmpimport.h"
 
#define BI_BITFIELDS 3
 
bool bmp_has_gap1(BITMAPFILEHEADER bitmapFileHeader, BITMAPINFOHEADER bitmapInfoHeader) {
int expected = 0;
expected = sizeof(bitmapFileHeader) + bitmapInfoHeader.biSize;
 
if (bitmapInfoHeader.biCompression == BI_BITFIELDS) {
expected += 3*sizeof(DWORD);
}
 
int numColorEntries;
if (bitmapInfoHeader.biClrUsed == 0) {
if ((bitmapInfoHeader.biBitCount == 1) || (bitmapInfoHeader.biBitCount == 4) || (bitmapInfoHeader.biBitCount == 8)) {
numColorEntries = pow(2, bitmapInfoHeader.biBitCount);
} else {
numColorEntries = 0;
}
} else {
numColorEntries = bitmapInfoHeader.biClrUsed;
}
expected += sizeof(RGBQUAD)*numColorEntries;
 
return bitmapFileHeader.bfOffBits != expected;
}
 
bool ipe32_bmp_import(FILE* fibBitmap, Ipe32BmpImportData* result) {
BITMAPFILEHEADER bitmapFileHeader;
BITMAPINFOHEADER bitmapInfoHeader;
 
#define EXIT_ERROR(msg) { sprintf(result->error, msg); return false; }
 
fseek(fibBitmap, 0, SEEK_SET);
 
if (!fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, fibBitmap) ||
(bitmapFileHeader.bfType != BI_SIGNATURE) ||
!fread(&bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, fibBitmap)) {
EXIT_ERROR("Not a bitmap file");
}
 
if (bmp_has_gap1(bitmapFileHeader, bitmapInfoHeader)) {
EXIT_ERROR("Picture may not have a gap between header and bitmap data");
}
 
// TODO: Find out if the game has limitations in regards to compression, top-down/bottom-up, Bitmap version etc.
/*
if (bitmapInfoHeader.biCompression != BI_RGB) {
EXIT_ERROR("At the moment, only uncompressed files can be read.");
}
 
if (bitmapInfoHeader.biBitCount != 8) {
EXIT_ERROR("The color depth has to be 8 bpp.");
}
*/
 
fseek(fibBitmap, sizeof(bitmapFileHeader), SEEK_SET);
 
result->error[0] = 0;
result->dataSize = file_size(fibBitmap)-sizeof(bitmapFileHeader);
return true;
}
 
void ipe32_free_bmpimport_result(Ipe32BmpImportData *res) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe32_bmpimport.h
0,0 → 1,27
/**
* Bitmap Import for Imagination Pilots Entertainment 32-bit games (IPE32)
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe32_bmpimport
#define __inc__ipe32_bmpimport
 
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
 
typedef struct tagIpe32BmpImportData {
size_t dataSize;
char error[255];
} Ipe32BmpImportData;
 
bool ipe32_bmp_import(FILE* fibBitmap, Ipe32BmpImportData* result);
void ipe32_free_bmpimport_result(Ipe32BmpImportData *res);
 
#endif // #ifndef __inc__ipe32_bmpimport
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe32_lzw_decoder.c
0,0 → 1,159
/**
* LZW Decoder for Imagination Pilots Entertainment 32-bit games (IPE32)
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2018
* Revision: 2018-02-15
*
* Based on : Basic LZW Data Compression program published in DDJ October 1989 issue.
* by Mark R. Nelson
* http://collaboration.cmc.ec.gc.ca/science/rpn/biblio/ddj/Website/articles/DDJ/1989/8910/8910b/8910b.htm
* Updated by: Shawn M. Regan, January 1990
* http://mirror.bagelwood.com/textfiles/computers/regan.lst
* Updated by: Daniel Marschall, 11 February 2018
* https://misc.daniel-marschall.de/code/c/lzw.c
* Changed for IPE32: - Simplified
* - Thread safe
* - MAX_BITS = 13
**/
 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
 
#include "utils.h"
#include "ipe32_lzw_decoder.h"
 
#define INIT_BITS 9
#define MAX_BITS 13 /* Do not exceed 14 with this program */
 
#if MAX_BITS == 14 /* Set the table size. Must be a prime */
#define TABLE_SIZE 18041 /* number somewhat larger than 2^MAX_BITS.*/
#elif MAX_BITS == 13
#define TABLE_SIZE 9029
#else
#define TABLE_SIZE 5021
#endif
 
#define CLEAR_TABLE 256 /* Code to flush the string table */
#define TERMINATOR 257 /* To mark EOF Condition, instead of MAX_VALUE */
#define FIRST_CODE 258 /* First available code for code_value table */
 
#define MAXVAL(n) (( 1 <<( n )) -1) /* max_value formula macro */
 
unsigned char* decode_string(Ipe32LZWDecoder *decoder, unsigned char *buffer, unsigned int code) {
int i=0;
 
while (code > 255) {
*buffer++ = decoder->append_character[code];
code = decoder->prefix_code[code];
if (i++ >= 4000) {
// printf("Error during code expansion\n");
return NULL;
}
}
*buffer=code;
return(buffer);
}
 
unsigned input_code(Ipe32LZWDecoder *decoder, unsigned char* lzwInputBuffer, int* inputBufferPos) {
unsigned int return_value;
 
while (decoder->input_bit_count <= 24) {
decoder->input_bit_buffer |= lzwInputBuffer[(*inputBufferPos)] << (24 - decoder->input_bit_count);
(*inputBufferPos)++;
decoder->input_bit_count += 8;
}
return_value=decoder->input_bit_buffer >> (32-decoder->num_bits);
decoder->input_bit_buffer <<= decoder->num_bits;
decoder->input_bit_count -= decoder->num_bits;
return(return_value);
}
 
void reset_input_buffer(Ipe32LZWDecoder *decoder) {
decoder->input_bit_count = 0;
decoder->input_bit_buffer = 0;
}
 
void ipe32lzw_reset_decoder(Ipe32LZWDecoder *decoder) {
decoder->num_bits = INIT_BITS;
decoder->max_code = MAXVAL(decoder->num_bits); /* Initialize max_value & max_code */
 
// Make sure the input buffer is correctly flushed
reset_input_buffer(decoder);
}
 
void ipe32lzw_init_decoder(Ipe32LZWDecoder *decoder) {
decoder->prefix_code = malloc(TABLE_SIZE*sizeof(unsigned int));
decoder->append_character = malloc(TABLE_SIZE*sizeof(unsigned char));
ipe32lzw_reset_decoder(decoder);
}
 
// Returns: Bytes written or -1 when an error occurs
int ipe32lzw_decode(Ipe32LZWDecoder *decoder, unsigned char* outputBuffer, const size_t outpufBufferSize, unsigned char* lzwInputBuffer, const size_t maxReadBytes) {
unsigned int next_code=FIRST_CODE;
unsigned int new_code;
unsigned int old_code;
int character;
int clear_flag=1; /* Need to clear the code value array */
unsigned char *string;
 
int inputBufferPos = 0;
int outputBufferPos = 0;
#define OUTPUT(code) { if (outputBufferPos == outpufBufferSize) return -1; outputBuffer[outputBufferPos++] = code; }
 
ipe32lzw_reset_decoder(decoder);
 
while (1) {
if (inputBufferPos == maxReadBytes) return -1;
if ((new_code=input_code(decoder, lzwInputBuffer, &inputBufferPos)) == TERMINATOR) break;
 
if (clear_flag) { /* Initialize or Re-Initialize */
clear_flag=0;
old_code=new_code; /* The next three lines have been moved */
character=old_code; /* from the original */
OUTPUT(old_code);
continue;
}
if (new_code == CLEAR_TABLE) { /* Clear string table */
clear_flag=1;
decoder->num_bits=INIT_BITS;
next_code=FIRST_CODE;
decoder->max_code = MAXVAL(decoder->num_bits);
continue;
}
if (new_code >= next_code) { /* Check for string+char+string */
*decoder->decode_stack=character;
string = decode_string(decoder, decoder->decode_stack+1,old_code);
} else {
string = decode_string(decoder, decoder->decode_stack,new_code);
}
if (string == NULL) return -1;
 
character = *string; /* Output decoded string in reverse */
while (string >= decoder->decode_stack) {
OUTPUT(*string--);
}
 
if (next_code <= decoder->max_code) { /* Add to string table if not full */
decoder->prefix_code[next_code]=old_code;
decoder->append_character[next_code++]=character;
if (next_code == decoder->max_code && decoder->num_bits < MAX_BITS) {
decoder->max_code = MAXVAL(++decoder->num_bits);
}
}
old_code=new_code;
}
 
return outputBufferPos;
}
 
void ipe32lzw_free_decoder(Ipe32LZWDecoder *decoder) {
free(decoder->prefix_code);
free(decoder->append_character);
}
 
Ipe32LZWDecoder* new_ipe32lzw_decoder(void) {
return (Ipe32LZWDecoder*)app_zero_alloc(sizeof(Ipe32LZWDecoder));
}
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe32_lzw_decoder.h
0,0 → 1,38
/**
* LZW Decoder for Imagination Pilots Entertainment 32-bit games (IPE32)
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2018
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe32_lzw_decoder
#define __inc__ipe32_lzw_decoder
 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
 
typedef struct tagIpe32LZWDecoder {
unsigned int *prefix_code; /* This array holds the prefix codes */
unsigned char *append_character; /* This array holds the appended chars */
unsigned char decode_stack[4000]; /* This array holds the decoded string */
 
int num_bits; /* Starting with 9 bit codes */
int max_code; /* old MAX_CODE */
 
int input_bit_count;
uint32_t input_bit_buffer;
} Ipe32LZWDecoder;
 
// Returns: Bytes written or -1 when an error occurs
int ipe32lzw_decode(Ipe32LZWDecoder *decoder, unsigned char* outputBuffer, const size_t outpufBufferSize, unsigned char* lzwInputBuffer, const size_t maxReadBytes);
 
Ipe32LZWDecoder* new_ipe32lzw_decoder(void);
void ipe32lzw_init_decoder(Ipe32LZWDecoder *decoder);
void ipe32lzw_free_decoder(Ipe32LZWDecoder *decoder);
 
#endif // #ifndef __inc__ipe32_lzw_decoder
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe32_lzw_decoder.old
Cannot display: file marked as a binary type.
svn:mime-type = application/x-trash
Property changes:
Added: svn:mime-type
+application/x-trash
\ No newline at end of property
/trunk/ipe32_lzw_encoder.c
0,0 → 1,192
/**
* LZW Encoder for Imagination Pilots Entertainment 32-bit games (IPE32)
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2018
* Revision: 2018-02-15
*
* Based on : Basic LZW Data Compression program published in DDJ October 1989 issue.
* by Mark R. Nelson
* http://collaboration.cmc.ec.gc.ca/science/rpn/biblio/ddj/Website/articles/DDJ/1989/8910/8910b/8910b.htm
* Updated by: Shawn M. Regan, January 1990
* http://mirror.bagelwood.com/textfiles/computers/regan.lst
* Updated by: Daniel Marschall, 11 February 2018
* https://misc.daniel-marschall.de/code/c/lzw.c
* Changed for IPE32: - Simplified
* - Thread safe
* - MAX_BITS = 13
**/
 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
 
#include "utils.h"
#include "ipe32_lzw_encoder.h"
 
#define INIT_BITS 9
#define MAX_BITS 13 /* Do not exceed 14 with this program */
#define HASHING_SHIFT (MAX_BITS - 8)
 
#if MAX_BITS == 14 /* Set the table size. Must be a prime */
#define TABLE_SIZE 18041 /* number somewhat larger than 2^MAX_BITS.*/
#elif MAX_BITS == 13
#define TABLE_SIZE 9029
#else
#define TABLE_SIZE 5021
#endif
 
#define CLEAR_TABLE 256 /* Code to flush the string table */
#define TERMINATOR 257 /* To mark EOF Condition, instead of MAX_VALUE */
#define FIRST_CODE 258 /* First available code for code_value table */
#define CHECK_TIME 100 /* Check comp ratio every CHECK_TIME chars input */
 
#define MAXVAL(n) (( 1 <<( n )) -1) /* max_value formula macro */
 
unsigned int find_match(Ipe32LZWEncoder *encoder, int hash_prefix, unsigned int hash_character) {
int index, offset;
 
index = (hash_character << HASHING_SHIFT) ^ hash_prefix;
 
offset = (index == 0) ? 1 : TABLE_SIZE - index;
 
while (1) {
if (encoder->code_value[index] == -1) {
return(index);
}
if (encoder->prefix_code[index] == hash_prefix && encoder->append_character[index] == hash_character) {
return(index);
}
index -= offset;
if (index < 0) {
index += TABLE_SIZE;
}
}
}
 
void output_code(Ipe32LZWEncoder *encoder, unsigned int code, unsigned char* outBuf, size_t* compressedPos) {
encoder->output_bit_buffer |= (uint32_t) code << (32 - encoder->num_bits - encoder->output_bit_count);
encoder->output_bit_count += encoder->num_bits;
while (encoder->output_bit_count >= 8) {
outBuf[(*compressedPos)] = encoder->output_bit_buffer >> 24; // putc(output_bit_buffer >> 24, output);
(*compressedPos)++;
 
encoder->output_bit_buffer <<= 8;
encoder->output_bit_count -= 8;
encoder->bytes_out++;
}
}
 
void reset_output_buffer(Ipe32LZWEncoder *encoder) {
encoder->output_bit_count = 0;
encoder->output_bit_buffer = 0;
}
 
void ipe32lzw_reset_encoder(Ipe32LZWEncoder *encoder) {
encoder->num_bits = INIT_BITS;
encoder->max_code = MAXVAL(encoder->num_bits); /* Initialize max_value & max_code */
encoder->bytes_in = 0;
encoder->bytes_out = 0;
encoder->checkpoint = CHECK_TIME;
 
// For some reason, the output buffer doesn't get correctly flushed when
// a new compression is started. So I made the static symbols global
// and clear them here.
reset_output_buffer(encoder);
}
 
// Returns: Bytes written, or -1 if compression failed
int ipe32lzw_encode(Ipe32LZWEncoder *encoder, unsigned char* compressedData, const size_t compressedBufLen, unsigned char* uncompressedData, const size_t uncompressedSize) {
unsigned int next_code=FIRST_CODE;
unsigned int index;
int i, /* All purpose integer */
ratio_new, /* New compression ratio as a percentage */
ratio_old=100; /* Original ratio at 100% */
 
ipe32lzw_reset_encoder(encoder);
 
for (i=0; i<TABLE_SIZE; ++i) { /* Initialize the string table first */
encoder->code_value[i]=-1;
}
 
/* Get the first code */
if (compressedBufLen == 0) return -1;
unsigned int string_code = uncompressedData[0]; //string_code=getc(input);
size_t uncompressedPos = 1;
size_t compressedPos = 0;
#define OUTPUT(code) { if (compressedPos == compressedBufLen) return -1; output_code(encoder,code,compressedData,&compressedPos); }
 
/* This is the main compression loop. Notice when the table is full we try
* to increment the code size. Only when num_bits == MAX_BITS and the code
* value table is full do we start to monitor the compression ratio.
*/
while (uncompressedPos < uncompressedSize) { // while((character=getc(input)) != (unsigned)EOF) {
unsigned int character = uncompressedData[uncompressedPos++];
 
++encoder->bytes_in;
index=find_match(encoder,string_code,character);
if (encoder->code_value[index] != -1) {
string_code=encoder->code_value[index];
} else {
if (next_code <= encoder->max_code) {
encoder->code_value[index]=next_code++;
encoder->prefix_code[index]=string_code;
encoder->append_character[index]=character;
}
OUTPUT(string_code); /* Send out current code */
string_code=character;
if (next_code > encoder->max_code) { /* Is table Full? */
if (encoder->num_bits < MAX_BITS) { /* Any more bits? */
encoder->max_code = MAXVAL(++encoder->num_bits); /* Increment code size then */
} else if (encoder->bytes_in > encoder->checkpoint) { /* At checkpoint? */
if (encoder->num_bits == MAX_BITS) {
ratio_new = encoder->bytes_out*100/encoder->bytes_in; /* New compression ratio */
if (ratio_new > ratio_old) { /* Has ratio degraded? */
OUTPUT(CLEAR_TABLE); /* YES,flush string table */
encoder->num_bits=INIT_BITS;
next_code=FIRST_CODE; /* Reset to FIRST_CODE */
encoder->max_code = MAXVAL(encoder->num_bits); /* Re-Initialize this stuff */
encoder->bytes_in = encoder->bytes_out = 0;
ratio_old = 100; /* Reset compression ratio */
for (i=0; i<TABLE_SIZE; ++i) { /* Reset code value array */
encoder->code_value[i]=-1;
}
} else { /* NO, then save new */
ratio_old = ratio_new; /* compression ratio */
}
}
encoder->checkpoint = encoder->bytes_in + CHECK_TIME; /* Set new checkpoint */
}
}
}
}
OUTPUT(string_code); /* Output the last code */
if (next_code == encoder->max_code) { /* Handles special case for bit */
++encoder->num_bits; /* increment on EOF */
}
OUTPUT(TERMINATOR); /* Output the end of buffer code */
OUTPUT(0); /* Flush the output buffer */
OUTPUT(0);
OUTPUT(0);
 
return compressedPos;
}
 
void ipe32lzw_init_encoder(Ipe32LZWEncoder *encoder) {
/* The three buffers for the compression phase. */
encoder->code_value=malloc(TABLE_SIZE*sizeof(unsigned int));
encoder->prefix_code=malloc(TABLE_SIZE*sizeof(unsigned int));
encoder->append_character=malloc(TABLE_SIZE*sizeof(unsigned char));
ipe32lzw_reset_encoder(encoder);
}
 
void ipe32lzw_free_encoder(Ipe32LZWEncoder *encoder) {
free(encoder->code_value); /* Needed only for compression */
free(encoder->prefix_code);
free(encoder->append_character);
}
 
Ipe32LZWEncoder* new_ipe32lzw_encoder(void) {
return (Ipe32LZWEncoder*)app_zero_alloc(sizeof(Ipe32LZWEncoder));
}
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe32_lzw_encoder.h
0,0 → 1,39
/**
* LZW Encoder for Imagination Pilots Entertainment 32-bit games (IPE32)
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* ART file packer and unpacker by Daniel Marschall, ViaThinkSoft (C) 2018
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe32_lzw_encoder
#define __inc__ipe32_lzw_encoder
 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
 
typedef struct tagIpe32LZWEncoder {
int *code_value; /* This is the code value array */
unsigned int *prefix_code; /* This array holds the prefix codes */
unsigned char *append_character; /* This array holds the appended chars */
 
int num_bits; /* Starting with 9 bit codes */
uint32_t bytes_in,bytes_out; /* Used to monitor compression ratio */
int max_code; /* old MAX_CODE */
uint32_t checkpoint; /* For compression ratio monitoring */
 
int output_bit_count;
uint32_t output_bit_buffer;
} Ipe32LZWEncoder;
 
// Returns: Bytes written, or -1 if compression failed
int ipe32lzw_encode(Ipe32LZWEncoder *encoder, unsigned char* compressedData, const size_t compressedBufLen, unsigned char* uncompressedData, const size_t uncompressedSize);
 
Ipe32LZWEncoder* new_ipe32lzw_encoder(void);
void ipe32lzw_init_encoder(Ipe32LZWEncoder *encoder);
void ipe32lzw_free_encoder(Ipe32LZWEncoder *encoder);
 
#endif // #ifndef __inc__ipe32_lzw_encoder
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe_artfile_packer
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes:
Added: svn:executable
+*
\ No newline at end of property
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/ipe_artfile_packer.c
0,0 → 1,107
/**
* ART file packer by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Blown Away - The Interactive Game by Imagination Pilots
* - Panic in the Park - The Interactive Game by Imagination Pilots
* - Where's Waldo? At the Circus
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* Revision: 2018-02-21
**/
 
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
#include <getopt.h>
 
#include "ipe_artfile_packer_ipe16_ba.h"
#include "ipe_artfile_packer_ipe16_pip.h"
#include "ipe_artfile_packer_ipe32.h"
 
#define VERSION "2018-02-21"
 
void print_syntax() {
fprintf(stderr, "Syntax: [-v] -t <type> -i <input dir> -o <output artfile>\n");
fprintf(stderr, " -t : ba (Blown Away)\n");
fprintf(stderr, " pip (Panic in the Park)\n");
fprintf(stderr, " waldo (Where's Waldo? At the Circus)\n");
fprintf(stderr, " waldo2 (Where's Waldo? Exploring Geography)\n");
fprintf(stderr, " eraser (Eraser Turnabout)\n");
fprintf(stderr, " knex (Virtual K'Nex)\n");
fprintf(stderr, " -v : verbose output\n");
}
 
#define GAME_UNKNOWN 0
#define GAME_BA 1
#define GAME_PIP 2
#define GAME_WALDO_CIRCUS 3
#define GAME_WALDO_GEOGRAPHY 4
#define GAME_ERASER 5
#define GAME_KNEX 6
 
int main(int argc, char *argv[]) {
int verbosity = 0;
char* szSrcFolder = "";
char* szArtFile = "";
int c;
 
#define PRINT_SYNTAX { print_syntax(); return 0; }
 
int game = GAME_UNKNOWN;
 
while ((c = getopt(argc, argv, "Vvi:o:t:")) != -1) {
switch (c) {
case 't':
if (strcmp(optarg, "ba") == 0) game = GAME_BA;
if (strcmp(optarg, "pip") == 0) game = GAME_PIP;
if (strcmp(optarg, "waldo") == 0) game = GAME_WALDO_CIRCUS;
if (strcmp(optarg, "waldo2") == 0) game = GAME_WALDO_GEOGRAPHY;
if (strcmp(optarg, "eraser") == 0) game = GAME_ERASER;
if (strcmp(optarg, "knex") == 0) game = GAME_KNEX;
break;
case 'v':
verbosity++;
break;
case 'V':
fprintf(stdout, "IPE artfile packer, revision %s\n", VERSION);
return 0;
case 'i':
szSrcFolder = optarg;
break;
case 'o':
szArtFile = optarg;
break;
case '?':
PRINT_SYNTAX;
break;
}
}
if (optind < argc) PRINT_SYNTAX;
if (game == GAME_UNKNOWN) {
fprintf(stderr, "Please specify the game\n");
PRINT_SYNTAX;
}
 
if (strlen(szArtFile) == 0) PRINT_SYNTAX;
if (strlen(szSrcFolder) == 0) PRINT_SYNTAX;
 
FILE* fobArt = fopen(szArtFile, "wb");
 
switch (game) {
case GAME_BA:
return ba_pack_art(szSrcFolder, fobArt, verbosity) ? 0 : 1;
break;
case GAME_PIP:
case GAME_WALDO_CIRCUS:
return pip_pack_art(szSrcFolder, fobArt, verbosity) ? 0 : 1;
break;
case GAME_WALDO_GEOGRAPHY:
case GAME_ERASER:
case GAME_KNEX:
return ipe32_pack_art(szSrcFolder, fobArt, verbosity) ? 0 : 1;
break;
}
}
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe_artfile_packer.dev
0,0 → 1,182
[Project]
FileName=ipe_artfile_packer.dev
Name=Artfile Packer
UnitCount=12
Type=1
Ver=2
ObjFiles=
Includes=
Libs=
PrivateResource=
ResourceIncludes=
MakeIncludes=
Compiler=
CppCompiler=
Linker=
IsCpp=0
Icon=
ExeOutput=
ObjectOutput=
OverrideOutput=0
OverrideOutputName=ipe_artfile_packer.exe
HostApplication=
Folders=
CommandLine=
UseCustomMakefile=0
CustomMakefile=
IncludeVersionInfo=0
SupportXPThemes=0
CompilerSet=0
CompilerSettings=0000000000000000000000000
LogOutput=
LogOutputEnabled=0
 
[VersionInfo]
Major=0
Minor=1
Release=1
Build=1
LanguageID=1033
CharsetID=1252
CompanyName=
FileVersion=0.1.1.1
FileDescription=Developed using the Dev-C++ IDE
InternalName=
LegalCopyright=
LegalTrademarks=
OriginalFilename=
ProductName=
ProductVersion=
AutoIncBuildNr=0
SyncProduct=0
 
[Unit1]
FileName=ipe_artfile_packer.c
CompileCpp=0
Folder=BA Artfile Packer
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit2]
FileName=ipe16_artfile.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit3]
FileName=ipe32_artfile.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit4]
FileName=bitmap.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit5]
FileName=ipe32_lzw_encoder.c
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit7]
FileName=ipe16_bmpimport.c
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit8]
FileName=utils.c
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit9]
FileName=ipe32_bmpimport.c
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit9]
FileName=ipe_artfile_packer_ipe16_ba.c
CompileCpp=0
Folder=BA Artfile Packer
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit10]
FileName=ipe_artfile_packer_ipe16_pip.c
CompileCpp=0
Folder=BA Artfile Packer
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit11]
FileName=ipe_artfile_packer_ipe32.c
CompileCpp=0
Folder=BA Artfile Packer
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit12]
FileName=ipe32_bmpimport.c
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit6]
FileName=ipe16_lzw_encoder.c
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
/trunk/ipe_artfile_packer.exe
Cannot display: file marked as a binary type.
svn:mime-type = application/x-msdos-program
Property changes:
Added: svn:mime-type
+application/x-msdos-program
\ No newline at end of property
/trunk/ipe_artfile_packer.layout
0,0 → 1,48
[Editor_0]
CursorCol=13
CursorRow=3
TopLine=1
LeftChar=1
[Editor_2]
CursorCol=1
CursorRow=47
TopLine=16
LeftChar=1
[Editor_1]
CursorCol=20
CursorRow=69
TopLine=1
LeftChar=1
[Editor_6]
CursorCol=39
CursorRow=83
TopLine=64
LeftChar=1
[Editor_7]
CursorCol=7
CursorRow=17
TopLine=1
LeftChar=1
[Editor_3]
CursorCol=21
CursorRow=19
TopLine=1
LeftChar=1
[Editors]
Order=0
Focused=0
[Editor_5]
CursorCol=1
CursorRow=8
TopLine=83
LeftChar=1
[Editor_8]
CursorCol=42
CursorRow=97
TopLine=120
LeftChar=1
[Editor_4]
CursorCol=13
CursorRow=144
TopLine=120
LeftChar=1
/trunk/ipe_artfile_packer_ipe16_ba.c
0,0 → 1,167
/**
* ART file packer by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Blown Away - The Interactive Game by Imagination Pilots
* Revision: 2018-02-15
**/
 
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
 
#include "ipe_artfile_packer_ipe16_ba.h"
 
#include "ipe16_artfile.h"
#include "ipe16_bmpimport.h"
#include "ipe16_lzw_encoder.h"
 
#define MAX_FILE 256
 
bool ba_pack_art(const char* szSrcFolder, FILE* fobArt, const int verbosity) {
bool bEverythingOK = true;
 
char szIndexFilename[MAX_FILE];
sprintf(szIndexFilename, "%s/index.txt", szSrcFolder);
FILE* fitIndex = fopen(szIndexFilename, "rt");
if (!fitIndex) {
fprintf(stderr, "Cannot open %s\n", szIndexFilename);
return false;
}
 
#define MAX_LINE 1024
char line[MAX_LINE];
int cItems = 0;
while (fgets(line, sizeof(line), fitIndex)) {
if (strlen(line) == 0) continue;
++cItems;
}
if (verbosity >= 1) printf("%s contains %d entries\n", szIndexFilename, cItems); // TODO: don't print double /
 
Ipe16FileHeader bfh;
memset(&bfh, 0x00, sizeof(bfh));
strcpy(bfh.magic, IPE16_MAGIC_ART);
bfh.dummy = IPE16_MAGIC_DUMMY;
bfh.numHeaderEntries = cItems+1;
 
Ipe16PictureEntryHeader peh[cItems];
memset(&peh, 0x00, sizeof(peh));
 
BAPictureHeader ph[cItems];
memset(&ph, 0x00, sizeof(ph));
 
// We need to write the (still empty) headers, so we can use ftell() to determine the offsets correctly
// These headers are currently just dummies. They will be rewritten after all pictures are processed
fwrite(&bfh, sizeof(bfh), 1, fobArt);
fwrite(&peh, sizeof(peh), 1, fobArt);
 
fseek(fitIndex, 0, SEEK_SET);
int curItem = 0;
Ipe16LZWEncoder* lzwEncoder = NULL;
while (fgets(line, sizeof(line), fitIndex)) {
// If something fails, we discard the item, but continue in building the file!
#define FAIL_CONTINUE { memset(&peh[curItem], 0x00, sizeof(peh[curItem])); bEverythingOK=false; continue; }
 
const char* szDelimiters = " \t\r\n";
char* szPaletteType = strtok(&line[0], szDelimiters);
char* szCompressionType = strtok(NULL, szDelimiters);
char* szName = strtok(NULL, szDelimiters);
char* szFilename = strtok(NULL, szDelimiters);
 
if (strlen(szPaletteType) != 1) {
fprintf(stderr, "ERROR: Palette type (argument 1) at line %d is not valid (must be 1 char)\n", curItem+1);
FAIL_CONTINUE;
}
const char chPaletteType = *szPaletteType;
 
if ((chPaletteType != IPE16_PALETTETYPE_ATTACHED) && (chPaletteType != IPE16_PALETTETYPE_PARENT)) {
fprintf(stderr, "ERROR: Unknown palette type '%c' at line %d\n", chPaletteType, curItem+1);
FAIL_CONTINUE;
}
 
if (strlen(szCompressionType) != 1) {
fprintf(stderr, "ERROR: Compression type (argument 2) at line %d is not valid (must be 1 char)\n", curItem+1);
FAIL_CONTINUE;
}
const char chCompressionType = *szCompressionType;
 
if (strlen(szName) > IPE16_NAME_SIZE) {
fprintf(stderr, "ERROR: Name %s is too long (max %d chars allowed)\n", szName, IPE16_NAME_SIZE);
FAIL_CONTINUE;
}
 
strcpy(peh[curItem].name, szName);
peh[curItem].paletteType = chPaletteType;
peh[curItem].offset = ftell(fobArt);
peh[curItem].size = 0; // will be increased later
 
if (verbosity >= 1) printf("Process %s at offset %x\n", szName, peh[curItem].offset);
 
// Read bitmap
 
const bool colorTableExisting = (chPaletteType == IPE16_PALETTETYPE_ATTACHED);
 
char szBitmapFilename[MAX_FILE];
sprintf(szBitmapFilename, "%s/%s", szSrcFolder, szFilename);
FILE* fibBitmap = fopen(szBitmapFilename, "rb");
if (!fibBitmap) {
fprintf(stderr, "ERROR: cannot open '%s'\n", szFilename);
FAIL_CONTINUE;
}
 
Ipe16BmpImportData result={0};
if (!ipe16_bmp_import(fibBitmap, &result)) {
fprintf(stderr, "Error at %s: %s\n", szFilename, result.error);
fclose(fibBitmap);
ipe16_free_bmpimport_result(&result);
FAIL_CONTINUE;
}
 
ph[curItem].compressionType = chCompressionType;
ph[curItem].width = result.width;
ph[curItem].height = result.height;
fwrite(&ph[curItem], sizeof(ph[curItem]), 1, fobArt);
peh[curItem].size += sizeof(ph[curItem]);
 
// Write picture data
 
size_t tmpBefore = ftell(fobArt);
if (chCompressionType == BA_COMPRESSIONTYPE_LZW) {
if (!lzwEncoder) lzwEncoder = new_ipe16lzw_encoder();
ipe16lzw_encode(fobArt, lzwEncoder, result.bmpData, result.bmpDataSize);
} else if (chCompressionType == BA_COMPRESSIONTYPE_NONE) {
fwrite(result.bmpData, result.bmpDataSize, 1, fobArt);
} else {
fprintf(stderr, "Unknown compression type '%c' at line %d\n", chCompressionType, curItem+1);
fclose(fibBitmap);
ipe16_free_bmpimport_result(&result);
FAIL_CONTINUE;
}
peh[curItem].size += ftell(fobArt)-tmpBefore;
 
if (colorTableExisting) {
fwrite(result.colorTable, sizeof(*result.colorTable), 1, fobArt);
peh[curItem].size += sizeof(*result.colorTable);
}
 
// Free and continue
 
fclose(fibBitmap);
ipe16_free_bmpimport_result(&result);
 
++curItem;
}
if (lzwEncoder) del_ipe16lzw_encoder(lzwEncoder);
fclose(fitIndex);
 
bfh.totalFileSize = ftell(fobArt);
 
fseek(fobArt, 0, SEEK_SET);
fwrite(&bfh, sizeof(bfh), 1, fobArt);
fwrite(&peh, sizeof(peh), 1, fobArt);
 
fclose(fobArt);
 
return bEverythingOK;
}
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe_artfile_packer_ipe16_ba.h
0,0 → 1,16
/**
* ART file packer by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Blown Away - The Interactive Game by Imagination Pilots
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe_artfile_packer_ipe16_ba
#define __inc__ipe_artfile_packer_ipe16_ba
 
#include <stdio.h>
#include <stdbool.h>
 
bool ba_pack_art(const char* szSrcFolder, FILE* fobArt, const int verbosity);
 
#endif // #ifndef __inc__ipe_artfile_packer_ipe16_ba
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe_artfile_packer_ipe16_pip.c
0,0 → 1,173
/**
* ART file packer by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Panic in the Park - The Interactive Game by Imagination Pilots
* - Where's Waldo? At the Circus (Waldo1)
* Revision: 2018-02-15
**/
 
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
 
#include "ipe_artfile_packer_ipe16_pip.h"
#include "ipe16_artfile.h"
#include "ipe16_bmpimport.h"
#include "ipe16_lzw_encoder.h"
 
#define MAX_FILE 256
 
bool pip_pack_art(const char* szSrcFolder, FILE* fobArt, const int verbosity) {
bool bEverythingOK = true;
 
char szIndexFilename[MAX_FILE];
sprintf(szIndexFilename, "%s/index.txt", szSrcFolder);
FILE* fitIndex = fopen(szIndexFilename, "rt");
if (!fitIndex) {
fprintf(stderr, "Cannot open %s\n", szIndexFilename);
return false;
}
 
#define MAX_LINE 1024
char line[MAX_LINE];
int cItems = 0;
while (fgets(line, sizeof(line), fitIndex)) {
if (strlen(line) == 0) continue;
++cItems;
}
if (verbosity >= 1) printf("%s contains %d entries\n", szIndexFilename, cItems); // TODO: don't print double /
 
Ipe16FileHeader bfh;
memset(&bfh, 0x00, sizeof(bfh));
strcpy(bfh.magic, IPE16_MAGIC_ART);
bfh.dummy = IPE16_MAGIC_DUMMY;
bfh.numHeaderEntries = cItems+1;
 
Ipe16PictureEntryHeader peh[cItems];
memset(&peh, 0x00, sizeof(peh));
 
PipPictureHeader ph[cItems];
memset(&ph, 0x00, sizeof(ph));
 
// We need to write the (still empty) headers, so we can use ftell() to determine the offsets correctly
// These headers are currently just dummies. They will be rewritten after all pictures are processed
fwrite(&bfh, sizeof(bfh), 1, fobArt);
fwrite(&peh, sizeof(peh), 1, fobArt);
 
fseek(fitIndex, 0, SEEK_SET);
int curItem = 0;
Ipe16LZWEncoder* lzwEncoder = NULL;
while (fgets(line, sizeof(line), fitIndex)) {
// If something fails, we discard the item, but continue in building the file!
#define FAIL_CONTINUE { memset(&peh[curItem], 0x00, sizeof(peh[curItem])); bEverythingOK=false; continue; }
 
const char* szDelimiters = " \t\r\n";
char* szPaletteType = strtok(&line[0], szDelimiters);
char* szCompressionType = strtok(NULL, szDelimiters);
char* szName = strtok(NULL, szDelimiters);
char* szFilename = strtok(NULL, szDelimiters);
char* szOffsetX = strtok(NULL, szDelimiters);
int iOffsetX = (szOffsetX != NULL) ? atoi(szOffsetX) : 0;
char* szOffsetY = strtok(NULL, szDelimiters);
int iOffsetY = (szOffsetY != NULL) ? atoi(szOffsetY) : 0;
 
if (strlen(szPaletteType) != 1) {
fprintf(stderr, "ERROR: Palette type (argument 1) at line %d is not valid (must be 1 char)\n", curItem+1);
FAIL_CONTINUE;
}
const char chPaletteType = *szPaletteType;
 
if ((chPaletteType != IPE16_PALETTETYPE_ATTACHED) && (chPaletteType != IPE16_PALETTETYPE_PARENT)) {
fprintf(stderr, "ERROR: Unknown palette type '%c' at line %d\n", chPaletteType, curItem+1);
FAIL_CONTINUE;
}
 
if (strlen(szCompressionType) != 1) {
fprintf(stderr, "ERROR: Compression type (argument 2) at line %d is not valid (must be 1 char)\n", curItem+1);
FAIL_CONTINUE;
}
const char chCompressionType = *szCompressionType;
 
if (strlen(szName) > IPE16_NAME_SIZE) {
fprintf(stderr, "ERROR: Name %s is too long (max %d chars allowed)\n", szName, IPE16_NAME_SIZE);
FAIL_CONTINUE;
}
 
strcpy(peh[curItem].name, szName);
peh[curItem].paletteType = chPaletteType;
peh[curItem].offset = ftell(fobArt);
peh[curItem].size = 0; // will be increased later
 
if (verbosity >= 1) printf("Process %s at offset %x\n", szName, peh[curItem].offset);
 
// Read bitmap
 
const bool colorTableExisting = (chPaletteType == IPE16_PALETTETYPE_ATTACHED);
 
char szBitmapFilename[MAX_FILE];
sprintf(szBitmapFilename, "%s/%s", szSrcFolder, szFilename);
FILE* fibBitmap = fopen(szBitmapFilename, "rb");
if (!fibBitmap) {
fprintf(stderr, "ERROR: cannot open '%s'\n", szFilename);
FAIL_CONTINUE;
}
 
Ipe16BmpImportData result={0};
if (!ipe16_bmp_import(fibBitmap, &result)) {
fprintf(stderr, "Error at %s: %s\n", szFilename, result.error);
fclose(fibBitmap);
ipe16_free_bmpimport_result(&result);
FAIL_CONTINUE;
}
 
ph[curItem].compressionType = chCompressionType;
ph[curItem].offsetX = iOffsetX;
ph[curItem].offsetY = iOffsetY;
ph[curItem].width = result.width;
ph[curItem].height = result.height;
fwrite(&ph[curItem], sizeof(ph[curItem]), 1, fobArt);
peh[curItem].size += sizeof(ph[curItem]);
 
// Write picture data
 
size_t tmpBefore = ftell(fobArt);
if (chCompressionType == PIP_COMPRESSIONTYPE_LZW) {
if (!lzwEncoder) lzwEncoder = new_ipe16lzw_encoder();
ipe16lzw_encode(fobArt, lzwEncoder, result.bmpData, result.bmpDataSize);
} else if (chCompressionType == PIP_COMPRESSIONTYPE_NONE) {
fwrite(result.bmpData, result.bmpDataSize, 1, fobArt);
} else {
fprintf(stderr, "Unknown compression type '%c' at line %d\n", chCompressionType, curItem+1);
fclose(fibBitmap);
ipe16_free_bmpimport_result(&result);
FAIL_CONTINUE;
}
peh[curItem].size += ftell(fobArt)-tmpBefore;
 
if (colorTableExisting) {
fwrite(result.colorTable, sizeof(*result.colorTable), 1, fobArt);
peh[curItem].size += sizeof(*result.colorTable);
}
 
// Free and continue
 
fclose(fibBitmap);
ipe16_free_bmpimport_result(&result);
 
++curItem;
}
if (lzwEncoder) del_ipe16lzw_encoder(lzwEncoder);
fclose(fitIndex);
 
bfh.totalFileSize = ftell(fobArt);
 
fseek(fobArt, 0, SEEK_SET);
fwrite(&bfh, sizeof(bfh), 1, fobArt);
fwrite(&peh, sizeof(peh), 1, fobArt);
 
fclose(fobArt);
 
return bEverythingOK;
}
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe_artfile_packer_ipe16_pip.h
0,0 → 1,17
/**
* ART file packer by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Panic in the Park - The Interactive Game by Imagination Pilots
* - Where's Waldo? At the Circus (Waldo1)
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe_artfile_packer_ipe16_pip
#define __inc__ipe_artfile_packer_ipe16_pip
 
#include <stdio.h>
#include <stdbool.h>
 
bool pip_pack_art(const char* szSrcFolder, FILE* fobArt, const int verbosity);
 
#endif // #ifndef __inc__ipe_artfile_packer_ipe16_pip
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe_artfile_packer_ipe32.c
0,0 → 1,143
/**
* ART file packer by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* Revision: 2018-02-15
**/
 
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
 
#include "ipe_artfile_packer_ipe32.h"
#include "ipe32_artfile.h"
#include "ipe32_bmpimport.h"
#include "ipe32_lzw_encoder.h"
 
#define MAX_FILE 256
 
bool ipe32_pack_art(const char* szSrcFolder, FILE* fobArt, const int verbosity) {
bool bEverythingOK = true;
 
char szIndexFilename[MAX_FILE];
sprintf(szIndexFilename, "%s/index.txt", szSrcFolder);
FILE* fitIndex = fopen(szIndexFilename, "rt");
if (!fitIndex) {
fprintf(stderr, "Cannot open %s\n", szIndexFilename);
return false;
}
 
#define MAX_LINE 1024
char line[MAX_LINE];
int cItems = 0;
while (fgets(line, sizeof(line), fitIndex)) {
if (strlen(line) == 0) continue;
++cItems;
}
if (verbosity >= 1) printf("%s contains %d entries\n", szIndexFilename, cItems); // TODO: don't print double /
 
Ipe32FileHeader efh;
memset(&efh, 0x00, sizeof(efh));
strcpy(efh.magic, IPE32_MAGIC_ART);
efh.reserved = 0;
efh.totalHeaderSize = (cItems+1)*sizeof(efh);
 
Ipe32PictureEntryHeader peh[cItems];
memset(&peh, 0x00, sizeof(peh));
 
// These headers are currently just dummies. They will be rewritten after all pictures are processed
fwrite(&efh, sizeof(efh), 1, fobArt);
fwrite(&peh, sizeof(peh), 1, fobArt);
 
Ipe32LZWEncoder *encoder = new_ipe32lzw_encoder();
ipe32lzw_init_encoder(encoder);
fseek(fitIndex, 0, SEEK_SET);
int curItem = 0;
while (fgets(line, sizeof(line), fitIndex)) {
// If something fails, we discard the item, but continue in building the file!
#define FAIL_CONTINUE { memset(&peh[curItem], 0x00, sizeof(peh[curItem])); bEverythingOK=false; continue; }
 
const char* szDelimiters = " \t\r\n";
char* szName = strtok(&line[0], szDelimiters);
/* char* szNumCompressedChunks = */ strtok(NULL, szDelimiters);
/* char* szNumRawChunks = */ strtok(NULL, szDelimiters);
char* szFilename = strtok(NULL, szDelimiters);
 
if (strlen(szName) > IPE32_NAME_SIZE) {
fprintf(stderr, "ERROR: Name %s is too long (max %d chars allowed)\n", szName, IPE32_NAME_SIZE);
FAIL_CONTINUE;
}
 
char szBitmapFilename[MAX_FILE];
sprintf(szBitmapFilename, "%s/%s", szSrcFolder, szFilename);
FILE* fibBitmap = fopen(szBitmapFilename, "rb");
if (!fibBitmap) {
fprintf(stderr, "ERROR: cannot open '%s'\n", szFilename);
FAIL_CONTINUE;
}
 
Ipe32BmpImportData result={0};
if (!ipe32_bmp_import(fibBitmap, &result)) { // This function moves the file pointer to the bitmap info header
fprintf(stderr, "Error at %s: %s\n", szFilename, result.error);
fclose(fibBitmap);
ipe32_free_bmpimport_result(&result);
FAIL_CONTINUE;
}
 
strcpy(peh[curItem].name, szName);
peh[curItem].offset = ftell(fobArt);
peh[curItem].uncompressedSize = result.dataSize;
if (verbosity >= 1) printf("Process %s at offset %x\n", szName, peh[curItem].offset);
 
// Now write the chunks
 
int chunkNo = 0;
unsigned char uncompressedChunk[0x3FFE];
unsigned char compressedChunk[0x3FFE];
while (1) {
if (verbosity >= 2) fprintf(stdout, "Bitmap %s: Write chunk %d.\n", szFilename, chunkNo);
 
int uncompressedSize = fread(uncompressedChunk, 1, sizeof(uncompressedChunk), fibBitmap);
if (uncompressedSize == 0) break; // done
 
int compressedSize = ipe32lzw_encode(encoder, compressedChunk, sizeof(compressedChunk), uncompressedChunk, uncompressedSize);
 
uint16_t len;
 
if ((compressedSize == -1) || (compressedSize >= uncompressedSize)) {
// Choose uncompressed chunk
len = 0x8000 | uncompressedSize;
fwrite(&len, sizeof(len), 1, fobArt);
fwrite(uncompressedChunk, uncompressedSize, 1, fobArt);
} else {
// Choose compressed chunk
len = compressedSize;
fwrite(&len, sizeof(len), 1, fobArt);
fwrite(compressedChunk, compressedSize, 1, fobArt);
}
 
chunkNo++;
}
 
// Free and continue
 
fclose(fibBitmap);
ipe32_free_bmpimport_result(&result);
 
++curItem;
}
fclose(fitIndex);
ipe32lzw_free_encoder(encoder);
 
fseek(fobArt, 0, SEEK_SET);
fwrite(&efh, sizeof(efh), 1, fobArt);
fwrite(&peh, sizeof(peh), 1, fobArt);
 
fclose(fobArt);
 
return bEverythingOK;
}
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe_artfile_packer_ipe32.h
0,0 → 1,19
/**
* ART file packer by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe_artfile_packer_ipe32
#define __inc__ipe_artfile_packer_ipe32
 
#include <stdio.h>
#include <stdbool.h>
 
bool ipe32_pack_art(const char* szSrcFolder, FILE* fobArt, const int verbosity);
 
#endif // #ifndef __inc__ipe_artfile_packer_ipe32
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe_artfile_unpacker
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes:
Added: svn:executable
+*
\ No newline at end of property
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/ipe_artfile_unpacker.c
0,0 → 1,87
/**
* ART file unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Blown Away - The Interactive Game by Imagination Pilots
* - Panic in the Park - The Interactive Game by Imagination Pilots
* - Where's Waldo? At the Circus (Waldo1)
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* Revision: 2018-02-15
**/
 
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <getopt.h>
 
#include "ipe16_artfile.h"
#include "ipe32_artfile.h"
 
#include "ipe_artfile_unpacker_ipe16.h"
#include "ipe_artfile_unpacker_ipe32.h"
 
#define VERSION "2018-02-15"
 
void print_syntax() {
fprintf(stderr, "Syntax: -v [-o <outputdir>] -i <artfile>\n");
fprintf(stderr, " -v : verbose output\n");
fprintf(stderr, "Runs in simulation mode if no output directory is defined.\n");
}
 
int main(int argc, char *argv[]) {
int verbosity = 0;
char* szOutputDir = "";
char* szArtFile = "";
int c;
 
#define PRINT_SYNTAX { print_syntax(); return 0; }
 
while ((c = getopt(argc, argv, "Vvi:o:")) != -1) {
switch (c) {
case 'v':
verbosity++;
break;
case 'V':
fprintf(stdout, "IPE Artfile unpacker, revision %s\n", VERSION);
return 0;
case 'i':
szArtFile = optarg;
break;
case 'o':
szOutputDir = optarg;
break;
case '?':
PRINT_SYNTAX;
break;
}
}
if (optind < argc) PRINT_SYNTAX;
 
if (strlen(szArtFile) == 0) PRINT_SYNTAX;
 
FILE* fibArt = fopen(szArtFile, "rb");
if (!fibArt) {
fprintf(stderr, "FATAL: Cannot open %s\n", szArtFile);
return 1;
}
 
char signature[9]={0};
if (fread(&signature, 8, 1, fibArt) != 1) {
fprintf(stderr, "FATAL: Cannot read signature of %s\n", szArtFile);
return 1;
}
if (strcmp(signature, IPE32_MAGIC_ART) == 0) {
if (verbosity >= 1) fprintf(stdout, "%s: Detected file as IPE32 (Waldo2/Eraser/K'Nex) art file\n", szArtFile);
return ipe32_extract_art_to_folder(fibArt, szOutputDir, verbosity) ? 0 : 1;
} else if (strcmp(signature, IPE16_MAGIC_ART) == 0) {
if (verbosity >= 1) fprintf(stdout, "%s: Detected file as IPE16 (BA/PiP/Waldo1) art file\n", szArtFile);
return ipe16_extract_art_to_folder(fibArt, szOutputDir, verbosity) ? 0 : 1;
} else {
fprintf(stderr, "FATAL: %s is not a valid ART file of Imagination Pilots!\n", szArtFile);
return 1;
}
 
fclose(fibArt);
}
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe_artfile_unpacker.dev
0,0 → 1,182
[Project]
FileName=ipe_artfile_unpacker.dev
Name=Artfile Unpacker
UnitCount=11
Type=1
Ver=2
ObjFiles=
Includes=
Libs=
PrivateResource=
ResourceIncludes=
MakeIncludes=
Compiler=
CppCompiler=
Linker=
IsCpp=0
Icon=
ExeOutput=
ObjectOutput=
OverrideOutput=0
OverrideOutputName=ipe_artfile_unpacker.exe
HostApplication=
Folders=
CommandLine=
UseCustomMakefile=0
CustomMakefile=
IncludeVersionInfo=0
SupportXPThemes=0
CompilerSet=0
CompilerSettings=0000000000000000000000000
LogOutput=
LogOutputEnabled=0
 
[VersionInfo]
Major=0
Minor=1
Release=1
Build=1
LanguageID=1033
CharsetID=1252
CompanyName=
FileVersion=0.1.1.1
FileDescription=Developed using the Dev-C++ IDE
InternalName=
LegalCopyright=
LegalTrademarks=
OriginalFilename=
ProductName=
ProductVersion=
AutoIncBuildNr=0
SyncProduct=0
 
[Unit1]
FileName=ipe_artfile_unpacker.c
CompileCpp=0
Folder=BA Artfile Unpacker
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit2]
FileName=ipe16_artfile.h
CompileCpp=0
Folder=BA Artfile Unpacker
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit3]
FileName=ipe16_bmpexport.c
CompileCpp=0
Folder=BA Artfile Unpacker
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit6]
FileName=ipe32_lzw_decoder.c
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit8]
FileName=utils.c
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit9]
FileName=ipe_artfile_unpacker_ipe16.c
CompileCpp=0
Folder=BA Artfile Unpacker
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit10]
FileName=ipe_artfile_unpacker_ipe32.c
CompileCpp=0
Folder=BA Artfile Unpacker
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit11]
FileName=ipe32_bmpexport.c
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit12]
FileName=ipe_artfile_unpacker_ipe16.c
CompileCpp=0
Folder=BA Artfile Unpacker
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit13]
FileName=ipe_artfile_unpacker_ipe32.c
CompileCpp=0
Folder=BA Artfile Unpacker
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit4]
FileName=ipe16_lzw_decoder.c
CompileCpp=0
Folder=BA Artfile Unpacker
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit5]
FileName=ipe32_artfile.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
[Unit7]
FileName=bitmap.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
 
/trunk/ipe_artfile_unpacker.exe
Cannot display: file marked as a binary type.
svn:mime-type = application/x-msdos-program
Property changes:
Added: svn:mime-type
+application/x-msdos-program
\ No newline at end of property
/trunk/ipe_artfile_unpacker.layout
0,0 → 1,53
[Editor_6]
CursorCol=29
CursorRow=8
TopLine=1
LeftChar=1
[Editor_7]
CursorCol=19
CursorRow=8
TopLine=1
LeftChar=1
[Editor_4]
CursorCol=35
CursorRow=9
TopLine=1
LeftChar=1
[Editor_5]
CursorCol=35
CursorRow=54
TopLine=44
LeftChar=1
[Editor_3]
CursorCol=1
CursorRow=9
TopLine=1
LeftChar=1
[Editor_2]
CursorCol=5
CursorRow=51
TopLine=50
LeftChar=1
[Editor_1]
CursorCol=20
CursorRow=13
TopLine=1
LeftChar=1
[Editor_0]
CursorCol=1
CursorRow=1
TopLine=1
LeftChar=1
[Editor_8]
CursorCol=1
CursorRow=8
TopLine=1
LeftChar=1
[Editor_9]
CursorCol=17
CursorRow=4
TopLine=1
LeftChar=1
[Editors]
Order=1
Focused=1
/trunk/ipe_artfile_unpacker_ipe16.c
0,0 → 1,299
/**
* ART file unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Blown Away - The Interactive Game by Imagination Pilots
* - Panic in the Park - The Interactive Game by Imagination Pilots
* - Where's Waldo? At the Circus (Waldo1)
* Revision: 2018-02-15
**/
 
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <getopt.h>
 
#include "ipe16_bmpexport.h"
#include "ipe16_artfile.h"
#include "ipe16_lzw_decoder.h"
 
#include "utils.h"
 
#define MAX_FILE 256
 
void ipe16_generate_gray_table(Ipe16ColorTable *ct) {
int i;
for (i=0; i<=0xFF; ++i) {
ct->colors[i].r = i;
ct->colors[i].g = i;
ct->colors[i].b = i;
}
}
 
bool ipe16_extract_art_to_folder(FILE* fibArt, const char* szDestFolder, const int verbosity) {
bool bEverythingOK = true;
 
fseek(fibArt, 0, SEEK_SET);
 
Ipe16FileHeader bfh;
if (fread(&bfh, sizeof(bfh), 1, fibArt) != 1) {
fprintf(stderr, "FATAL: Cannot read Ipe16FileHeader. It is probably not an art file.\n");
return false;
}
 
// The "super header" has some different meanings of the fields
// Name and Type are hardcoded
// startOffset is the number of header entries (including the super header)
// length is the complete file size
const size_t fileSize = file_size(fibArt);
if ((strcmp(bfh.magic, IPE16_MAGIC_ART) != 0) || // better memcpy over all 23 bytes?
(bfh.dummy != IPE16_MAGIC_DUMMY) ||
(bfh.totalFileSize != fileSize)) {
fprintf(stderr, "FATAL: Something does not seem to be correct with this art file's header. It is probably not an art file.\n");
return false;
}
 
Ipe16LZWDecoder* lzwDecoder = NULL;
 
FILE* fotIndex = NULL;
if (strlen(szDestFolder) > 0) {
char szIndexFilename[MAX_FILE];
sprintf(szIndexFilename, "%s/index.txt", szDestFolder);
fotIndex = fopen(szIndexFilename, "wt");
if (!fotIndex) {
fprintf(stderr, "FATAL: Cannot open %s for writing\n", szIndexFilename);
return false;
}
}
 
const int numPictures = bfh.numHeaderEntries - 1;
char knownNames[numPictures][IPE16_NAME_SIZE];
memset(&knownNames[0][0], 0, numPictures*IPE16_NAME_SIZE);
int iPicNo;
for (iPicNo=0; iPicNo<numPictures; ++iPicNo) {
Ipe16PictureEntryHeader peh;
if (fread(&peh, sizeof(peh), 1, fibArt) != 1) {
fprintf(stderr, "FATAL: Cannot read Ipe16PictureEntryHeader.\n");
return false;
}
 
// Begin duplicate check
memcpy(&knownNames[iPicNo][0], peh.name, IPE16_NAME_SIZE);
int iCopyNumber = 0;
int j;
for (j=0; j<=iPicNo; ++j) {
// TODO: should we rather use strcmp() in IPE16?
if (memcmp(&knownNames[j][0], peh.name, IPE16_NAME_SIZE) == 0) ++iCopyNumber;
}
assert(iCopyNumber > 0);
// End duplicate check
 
// in the English version of Blown Away (not the Special Edition), there is a header entry
// with the fields seh.name='', peh.paletteType=0x00, seh.offset[end of file], seh.size=0
// ignore it
if (strlen(peh.name) == 0) continue;
if (peh.size == 0) continue;
 
if (strlen(peh.name) > IPE16_NAME_SIZE-1) {
fprintf(stderr, "FATAL: szName at picture %d is breaking the boundaries. The file is probably corrupt.\n", iPicNo);
return false;
}
 
size_t headersPos = ftell(fibArt);
#define FAIL_CONTINUE { bEverythingOK = false; fseek(fibArt, headersPos, SEEK_SET); continue; }
 
if (fseek(fibArt, peh.offset, SEEK_SET) != 0) {
fprintf(stderr, "ERROR: Error jumping to offset defined for %s\n", peh.name);
FAIL_CONTINUE;
}
 
if (peh.offset+peh.size > fileSize) {
fprintf(stderr, "ERROR: Defined size of %s exceeds file size\n", peh.name);
FAIL_CONTINUE;
}
 
unsigned char compressionType;
int bakPos = ftell(fibArt);
fread(&compressionType, sizeof(compressionType), 1, fibArt);
fseek(fibArt, bakPos, SEEK_SET);
 
if ((compressionType == BA_COMPRESSIONTYPE_LZW) || (compressionType == BA_COMPRESSIONTYPE_NONE)) {
BAPictureHeader ph;
if (fread(&ph, sizeof(ph), 1, fibArt) != 1) {
fprintf(stderr, "ERROR: Cannot read BAPictureHeader of %s\n", peh.name);
FAIL_CONTINUE;
}
 
size_t imagedata_len = ph.width * ph.height;
unsigned char* imagedata = (unsigned char*)malloc(imagedata_len);
 
Ipe16ColorTable ct;
if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) {
if ((fseek(fibArt, peh.offset+peh.size-sizeof(ct), SEEK_SET) != 0) ||
(fread(&ct, sizeof(ct), 1, fibArt) != 1) ||
(fseek(fibArt, peh.offset+sizeof(ph), SEEK_SET) != 0)) {
fprintf(stderr, "ERROR: Cannot read palette of %s\n", peh.name);
FAIL_CONTINUE;
}
} else if (peh.paletteType == IPE16_PALETTETYPE_PARENT) {
ipe16_generate_gray_table(&ct);
} else {
fprintf(stderr, "ERROR: Unknown palette type 0x%x at %s\n", peh.paletteType, peh.name);
FAIL_CONTINUE;
}
 
unsigned int bytes_written, expected_uncompressed_len;
switch (ph.compressionType) {
case BA_COMPRESSIONTYPE_LZW:
if (!lzwDecoder) lzwDecoder = new_ipe16lzw_decoder();
bytes_written = ipe16lzw_decode(fibArt, lzwDecoder, imagedata, imagedata_len);
if (bytes_written < 0) {
fprintf(stderr, "ERROR: LZW decompression error at %s\n", peh.name);
FAIL_CONTINUE;
}
if (bytes_written != imagedata_len) {
fprintf(stderr, "ERROR: Image dimensions and decompressed data size does not match for %s\n", peh.name);
FAIL_CONTINUE;
}
 
break;
case BA_COMPRESSIONTYPE_NONE:
expected_uncompressed_len = imagedata_len;
if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) expected_uncompressed_len += sizeof(ct);
expected_uncompressed_len += sizeof(ph.compressionType)+sizeof(ph.width)+sizeof(ph.height);
if (expected_uncompressed_len != peh.size) {
fprintf(stderr, "ERROR: Image dimensions/palette (%d) and defined memory size (%d) does not match for %s\n", expected_uncompressed_len, peh.size, peh.name);
FAIL_CONTINUE;
}
fread(imagedata, imagedata_len, 1, fibArt); // no error checking, because filesize was already checked
break;
}
 
char szBitmapFilename[MAX_FILE];
if (iCopyNumber == 1) {
sprintf(szBitmapFilename, "%s.bmp", sanitize_filename(peh.name));
} else {
sprintf(szBitmapFilename, "%s__%d.bmp", sanitize_filename(peh.name), iCopyNumber);
}
 
if (strlen(szDestFolder) > 0) {
char szAbsoluteBitmapFilename[MAX_FILE+1];
sprintf(szAbsoluteBitmapFilename, "%s/%s", szDestFolder, szBitmapFilename);
FILE* fobBitmap = fopen(szAbsoluteBitmapFilename, "wb");
if (!fobBitmap) {
fprintf(stderr, "FATAL: Cannot open %s for writing\n", szAbsoluteBitmapFilename);
FAIL_CONTINUE;
}
ipe16_write_bmp(fobBitmap, ph.width, ph.height, imagedata, imagedata_len, ct);
fclose(fobBitmap);
}
 
if (fotIndex) {
// We require this index file for 2 reasons
// 1. Our packer tool can then know what to pack
// 2. The packer tool can know which picture would have a palette appended and which one does not
// The index file won't be written in simulation mode (when no output directory is defined)
fprintf(fotIndex, "%c %c %s %s\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename);
}
if (verbosity >= 1) {
fprintf(stdout, "%c %c %s %s\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename);
}
 
free(imagedata);
} else if ((compressionType == PIP_COMPRESSIONTYPE_LZW) || (compressionType == PIP_COMPRESSIONTYPE_NONE)) {
PipPictureHeader ph;
if (fread(&ph, sizeof(ph), 1, fibArt) != 1) {
fprintf(stderr, "ERROR: Cannot read PipPictureHeader of %s\n", peh.name);
FAIL_CONTINUE;
}
 
size_t imagedata_len = ph.width * ph.height;
unsigned char* imagedata = (unsigned char*)malloc(imagedata_len);
 
Ipe16ColorTable ct;
if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) {
if ((fseek(fibArt, peh.offset+peh.size-sizeof(ct), SEEK_SET) != 0) ||
(fread(&ct, sizeof(ct), 1, fibArt) != 1) ||
(fseek(fibArt, peh.offset+sizeof(ph), SEEK_SET) != 0)) {
fprintf(stderr, "ERROR: Cannot read palette of %s\n", peh.name);
FAIL_CONTINUE;
}
} else if (peh.paletteType == IPE16_PALETTETYPE_PARENT) {
ipe16_generate_gray_table(&ct);
} else {
fprintf(stderr, "ERROR: Unknown palette type 0x%x at %s\n", peh.paletteType, peh.name);
FAIL_CONTINUE;
}
 
unsigned int bytes_written, expected_uncompressed_len;
switch (ph.compressionType) {
case PIP_COMPRESSIONTYPE_LZW:
if (!lzwDecoder) lzwDecoder = new_ipe16lzw_decoder();
bytes_written = ipe16lzw_decode(fibArt, lzwDecoder, imagedata, imagedata_len);
if (bytes_written < 0) {
fprintf(stderr, "ERROR: LZW decompression error at %s\n", peh.name);
FAIL_CONTINUE;
}
if (bytes_written != imagedata_len) {
fprintf(stderr, "ERROR: Image dimensions and decompressed data size does not match for %s\n", peh.name);
FAIL_CONTINUE;
}
 
break;
case PIP_COMPRESSIONTYPE_NONE:
expected_uncompressed_len = imagedata_len;
if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) expected_uncompressed_len += sizeof(ct);
expected_uncompressed_len += sizeof(ph.compressionType)+sizeof(ph.offsetX)+sizeof(ph.offsetY)+sizeof(ph.width)+sizeof(ph.height);
if (expected_uncompressed_len != peh.size) {
fprintf(stderr, "ERROR: Image dimensions/palette (%d) and defined memory size (%d) does not match for %s\n", expected_uncompressed_len, peh.size, peh.name);
FAIL_CONTINUE;
}
fread(imagedata, imagedata_len, 1, fibArt); // no error checking, because filesize was already checked
break;
}
 
char szBitmapFilename[MAX_FILE];
if (iCopyNumber == 1) {
sprintf(szBitmapFilename, "%s.bmp", sanitize_filename(peh.name));
} else {
sprintf(szBitmapFilename, "%s__%d.bmp", sanitize_filename(peh.name), iCopyNumber);
}
 
if (strlen(szDestFolder) > 0) {
char szAbsoluteBitmapFilename[MAX_FILE+1];
sprintf(szAbsoluteBitmapFilename, "%s/%s", szDestFolder, szBitmapFilename);
FILE* fobBitmap = fopen(szAbsoluteBitmapFilename, "wb");
if (!fobBitmap) {
fprintf(stderr, "FATAL: Cannot open %s for writing\n", szAbsoluteBitmapFilename);
FAIL_CONTINUE;
}
ipe16_write_bmp(fobBitmap, ph.width, ph.height, imagedata, imagedata_len, ct);
fclose(fobBitmap);
}
 
if (fotIndex) {
// We require this index file for 2 reasons
// 1. Our packer tool can then know what to pack
// 2. The packer tool can know which picture would have a palette appended and which one does not
// The index file won't be written in simulation mode (when no output directory is defined)
fprintf(fotIndex, "%c %c %s %s %d %d\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename, ph.offsetX, ph.offsetY);
}
if (verbosity >= 1) {
fprintf(stdout, "%c %c %s %s %d %d\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename, ph.offsetX, ph.offsetY);
}
 
free(imagedata);
} else {
fprintf(stderr, "ERROR: Unknown compression type 0x%x at %s\n", compressionType, peh.name);
FAIL_CONTINUE;
}
 
fseek(fibArt, headersPos, SEEK_SET); // we were already there, so we don't need to check for errors
}
 
if (lzwDecoder) del_ipe16lzw_decoder(lzwDecoder);
 
if (strlen(szDestFolder) > 0) fclose(fotIndex);
 
return bEverythingOK;
}
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe_artfile_unpacker_ipe16.h
0,0 → 1,18
/**
* ART file unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Blown Away - The Interactive Game by Imagination Pilots
* - Panic in the Park - The Interactive Game by Imagination Pilots
* - Where's Waldo? At the Circus (Waldo1)
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe_artfile_packer_ipe16
#define __inc__ipe_artfile_packer_ipe16
 
#include <stdio.h>
#include <stdbool.h>
 
bool ipe16_extract_art_to_folder(FILE* fibArt, const char* szDestFolder, const int verbosity);
 
#endif // #ifndef __inc__ipe_artfile_packer_ipe16
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipe_artfile_unpacker_ipe32.c
0,0 → 1,197
/**
* ART file unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* Revision: 2018-02-15
**/
 
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <getopt.h>
 
#include "ipe32_bmpexport.h"
#include "ipe32_artfile.h"
#include "ipe32_lzw_decoder.h"
 
#include "utils.h"
 
#define MAX_FILE 256
 
typedef struct tagIpe32ReadPictureResult {
uint32_t writtenBytes;
uint32_t numCompressedChunks;
uint32_t numRawChunks;
} Ipe32ReadPictureResult;
 
Ipe32ReadPictureResult ipe32_read_picture(FILE* hFile, unsigned char* outbuf, const int outputBufLength, bool bVerbose) {
unsigned char* lzwbuf = (unsigned char*)malloc(0x8000);
int availableOutputBytes = outputBufLength;
 
Ipe32ReadPictureResult res;
res.numCompressedChunks = 0;
res.numRawChunks = 0;
res.writtenBytes = 0;
 
Ipe32LZWDecoder *decoder = new_ipe32lzw_decoder();
ipe32lzw_init_decoder(decoder);
if (outputBufLength != 0) {
int chunkNo = 0;
do {
uint16_t len;
fread(&len, 1, 2, hFile);
 
uint16_t writtenBytes;
if (len < 0x8000) {
fread(lzwbuf, 1, len, hFile);
res.numCompressedChunks++;
if (bVerbose) fprintf(stdout, "Chunk %d (compressed, length: %d) ...\n", chunkNo, len);
 
// Requirement 1: Each chunk (except the last one) MUST have 0x3FFE of uncompressed data
uint16_t expectedOutputSize = availableOutputBytes > 0x3FFE ? 0x3FFE : availableOutputBytes;
 
// Requirement 2: The size of the uncompressed data must not exceed the size of the compressed data
size_t maxReadBytes = expectedOutputSize;
 
writtenBytes = ipe32lzw_decode(decoder, outbuf, outputBufLength, lzwbuf, maxReadBytes); // returns bytes written, or -1
 
if (writtenBytes == -1) {
fprintf(stderr, "ERROR: Fatal error during decompression of chunk %d!\n", chunkNo);
break;
}
 
if (writtenBytes != expectedOutputSize) {
fprintf(stderr, "ERROR: Chunk %d decompressed %d bytes, but %d bytes are expected!\n", chunkNo, writtenBytes, expectedOutputSize);
break;
}
} else {
len &= 0x7FFF;
res.numRawChunks++;
if (bVerbose) fprintf(stdout, "Chunk %d (raw, length: %d) ...\n", chunkNo, len);
fread(outbuf, 1, len, hFile);
writtenBytes = len;
}
outbuf += writtenBytes;
availableOutputBytes -= writtenBytes;
chunkNo++;
} while (availableOutputBytes != 0);
}
ipe32lzw_free_decoder(decoder);
 
free(lzwbuf);
res.writtenBytes = outputBufLength-availableOutputBytes;
return res;
}
 
bool ipe32_extract_art_to_folder(FILE* fibArt, const char* szDestFolder, const int verbosity) {
bool bEverythingOK = true;
 
fseek(fibArt, 0, SEEK_SET);
 
Ipe32FileHeader efh;
if (fread(&efh, sizeof(efh), 1, fibArt) != 1) {
fprintf(stderr, "FATAL: Cannot read Ipe32FileHeader. It is probably not an art file.\n");
return false;
}
 
// Check if the super header is correct
if ((memcmp(efh.magic, IPE32_MAGIC_ART, 8) != 0) || (efh.reserved != 0)) {
fprintf(stderr, "FATAL: Something does not seem to be correct with this art file's header. It is probably not an art file.\n");
return false;
}
 
FILE* fotIndex = NULL;
if (strlen(szDestFolder) > 0) {
char szIndexFilename[MAX_FILE];
sprintf(szIndexFilename, "%s/index.txt", szDestFolder);
fotIndex = fopen(szIndexFilename, "wt");
if (!fotIndex) {
fprintf(stderr, "FATAL: Cannot open %s for writing\n", szIndexFilename);
return false;
}
}
 
const int numPictures = efh.totalHeaderSize/sizeof(efh) - 1;
char knownNames[numPictures][IPE32_NAME_SIZE];
memset(knownNames, 0, numPictures*IPE32_NAME_SIZE);
int iPicNo;
for (iPicNo=0; iPicNo<numPictures; ++iPicNo) {
Ipe32PictureEntryHeader peh;
if (fread(&peh, sizeof(peh), 1, fibArt) != 1) {
fprintf(stderr, "FATAL: Cannot read Ipe32PictureEntryHeader.\n");
return false;
}
 
char szName[IPE32_NAME_SIZE+1]={0};
memcpy(szName, peh.name, IPE32_NAME_SIZE);
 
// Begin duplicate check
// In ERASER, there are a few pictures which have the same identifier, in the same ART file!
memcpy(&knownNames[iPicNo][0], peh.name, IPE32_NAME_SIZE);
int iCopyNumber = 0;
int j;
for (j=0; j<=iPicNo; ++j) {
if (memcmp(&knownNames[j][0], peh.name, IPE32_NAME_SIZE) == 0) ++iCopyNumber;
}
assert(iCopyNumber > 0);
// End duplicate check
 
if (verbosity >= 2) fprintf(stdout, "Extracting %s (expected file size: %d bytes) ...\n", szName, peh.uncompressedSize);
 
size_t headersPos = ftell(fibArt);
#define FAIL_CONTINUE { bEverythingOK = false; fseek(fibArt, headersPos, SEEK_SET); continue; }
 
if (fseek(fibArt, peh.offset, SEEK_SET) != 0) {
fprintf(stderr, "ERROR: Error jumping to offset defined for %s\n", szName);
FAIL_CONTINUE;
}
 
int outputBufLen = peh.uncompressedSize;
unsigned char* outputBuf = (unsigned char*)malloc(outputBufLen);
 
char szBitmapFilename[MAX_FILE];
if (iCopyNumber == 1) {
sprintf(szBitmapFilename, "%s.bmp", sanitize_filename(szName));
} else {
sprintf(szBitmapFilename, "%s__%d.bmp", sanitize_filename(szName), iCopyNumber);
}
 
Ipe32ReadPictureResult res = ipe32_read_picture(fibArt, outputBuf, outputBufLen, verbosity >= 2);
if (res.writtenBytes != outputBufLen) {
fprintf(stderr, "FATAL: Error reading picture %s (compression failure?)\n", szName);
FAIL_CONTINUE;
}
 
if (strlen(szDestFolder) > 0) {
char szAbsoluteBitmapFilename[MAX_FILE+1];
sprintf(szAbsoluteBitmapFilename, "%s/%s", szDestFolder, szBitmapFilename);
FILE* fobBitmap = fopen(szAbsoluteBitmapFilename, "wb");
if (!fobBitmap) {
fprintf(stderr, "FATAL: Cannot open %s for writing\n", szAbsoluteBitmapFilename);
FAIL_CONTINUE;
}
ipe32_write_bmp(fobBitmap, outputBuf, outputBufLen);
fclose(fobBitmap);
}
 
free(outputBuf);
 
if (fotIndex) {
// We require this index file so that our packer tool can know what to pack
// The index file won't be written in simulation mode (when no output directory is defined)
fprintf(fotIndex, "%s %d(C) %d(R) %s\n", szName, res.numCompressedChunks, res.numRawChunks, szBitmapFilename);
}
if (verbosity >= 1) {
fprintf(stdout, "%s %d(C) %d(R) %s\n", szName, res.numCompressedChunks, res.numRawChunks, szBitmapFilename);
}
 
fseek(fibArt, headersPos, SEEK_SET); // we were already there, so we don't need to check for errors
}
 
if (strlen(szDestFolder) > 0) fclose(fotIndex);
 
return bEverythingOK;
}
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/ipe_artfile_unpacker_ipe32.h
0,0 → 1,18
/**
* ART file unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* Revision: 2018-02-15
**/
 
#ifndef __inc__ipe_artfile_unpacker_ipe32
#define __inc__ipe_artfile_unpacker_ipe32
 
#include <stdio.h>
#include <stdbool.h>
 
bool ipe32_extract_art_to_folder(FILE* fibArt, const char* szDestFolder, const int verbosity);
 
#endif // #ifndef __inc__ipe_artfile_packer_ipe32
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/ipma_frame_extractor.c
0,0 → 1,493
/**
* IPMA video frame extractor by Daniel Marschall, ViaThinkSoft (C) 2022
* Supports codecs IPMA and IP20
* Revision: 2022-01-16
* License: Apache 2.0
**/
 
#define VERSION "2022-01-16"
 
#define _CRT_SECURE_NO_WARNINGS
// #define VISUAL_STUDIO_TEST
 
#include <windows.h>
#include <vfw.h>
#include <stdio.h>
#include <stdbool.h>
 
#ifndef VISUAL_STUDIO_TEST
#include <getopt.h>
#endif
 
#define LZ_MIN_BITS 9
#define LZ_MAX_BITS 12
 
#define LZ_MAX_CODE 4095 /* Largest 12 bit code */
#define NO_SUCH_CODE 4098 /* Impossible code = empty */
 
#define CLEAR_CODE 256
#define END_CODE 257
#define FIRST_CODE 258
 
bool dirExists(const char* dirName_in) {
DWORD ftyp = GetFileAttributesA(dirName_in);
if (ftyp == INVALID_FILE_ATTRIBUTES)
return false; //something is wrong with your path!
 
if (ftyp & FILE_ATTRIBUTE_DIRECTORY)
return true; // this is a directory!
 
return false; // this is not a directory!
}
 
typedef struct tagIpe16LZWDecoder {
int running_code;
int running_bits;
int max_code_plus_one;
int shift_state;
unsigned long shift_data;
unsigned char stack[LZ_MAX_CODE + 1];
unsigned int suffix[LZ_MAX_CODE + 1];
unsigned int prefix[LZ_MAX_CODE + 1];
} Ipe16LZWDecoder;
 
void ipe16lzw_init_decoder(Ipe16LZWDecoder* decoder) {
decoder->running_code = FIRST_CODE;
decoder->running_bits = LZ_MIN_BITS;
decoder->max_code_plus_one = 1 << decoder->running_bits;
decoder->shift_state = 0;
decoder->shift_data = 0;
 
int i;
for (i = 0; i <= LZ_MAX_CODE; i++) {
decoder->prefix[i] = NO_SUCH_CODE;
}
}
 
int ipe16lzw_read_code(unsigned char** inFile, Ipe16LZWDecoder* decoder) {
int code;
unsigned char next_byte;
static int code_masks[] = {
0x0000, 0x0001, 0x0003, 0x0007,
0x000f, 0x001f, 0x003f, 0x007f,
0x00ff, 0x01ff, 0x03ff, 0x07ff,
0x0fff
};
 
while (decoder->shift_state < decoder->running_bits) {
next_byte = **inFile;
*inFile = *inFile + 1;
decoder->shift_data |=
((unsigned long)next_byte) << decoder->shift_state;
decoder->shift_state += 8;
}
 
code = decoder->shift_data & code_masks[decoder->running_bits];
 
decoder->shift_data >>= decoder->running_bits;
decoder->shift_state -= decoder->running_bits;
 
if (++decoder->running_code > decoder->max_code_plus_one
&& decoder->running_bits < LZ_MAX_BITS) {
decoder->max_code_plus_one <<= 1;
decoder->running_bits++;
}
 
return code;
}
 
static int ipe16lzw_trace_prefix(unsigned int* prefix, int code, int clear_code) {
int i = 0;
 
while (code > clear_code && i++ <= LZ_MAX_CODE) {
code = prefix[code];
}
return code;
}
 
// Difference between ipma_lzw_decode and ipe16lzw_decode: At ipma_lzw_decode, inFile is "unsigned char**" and not "FILE*"
// We don't do unsigned, because we want to have <0 as error result
/*unsigned*/ int ipma_lzw_decode(unsigned char** inFile, Ipe16LZWDecoder* decoder, unsigned char* output, int outputLength) {
int i = 0, j;
int current_code;
int current_prefix;
int stack_ptr = 0;
int prev_code = NO_SUCH_CODE;
unsigned char* stack;
unsigned int* prefix;
unsigned int* suffix;
unsigned int bytes_written = 0;
 
ipe16lzw_init_decoder(decoder);
 
prefix = decoder->prefix;
suffix = decoder->suffix;
stack = decoder->stack;
 
/* Pop the stack */
while (stack_ptr != 0 && i < outputLength) {
output[i++] = stack[--stack_ptr];
//if (i > bytes_written) bytes_written = i;
++bytes_written;
}
 
while (i < outputLength) {
current_code = ipe16lzw_read_code(inFile, decoder);
 
if (current_code == END_CODE) {
if (i != outputLength - 1) // || decoder->pixel_count != 0
return -1; /* unexpected eof */
i++;
}
else if (current_code == CLEAR_CODE) {
for (j = 0; j <= LZ_MAX_CODE; j++) {
prefix[j] = NO_SUCH_CODE;
}
decoder->running_code = FIRST_CODE;
decoder->running_bits = LZ_MIN_BITS;
decoder->max_code_plus_one = 1 << decoder->running_bits;
prev_code = NO_SUCH_CODE;
}
else {
if (current_code < CLEAR_CODE) {
output[i++] = current_code;
//if (i > bytes_written) bytes_written = i;
++bytes_written;
}
else {
if ((current_code < 0) || (current_code > LZ_MAX_CODE))
return -2; /* image defect */
if (prefix[current_code] == NO_SUCH_CODE) {
if (current_code == decoder->running_code - 2) {
current_prefix = prev_code;
suffix[decoder->running_code - 2]
= stack[stack_ptr++]
= ipe16lzw_trace_prefix(prefix, prev_code, CLEAR_CODE);
}
else {
return -3; /* image defect */
}
}
else {
current_prefix = current_code;
}
j = 0;
while (j++ <= LZ_MAX_CODE && current_prefix > CLEAR_CODE && current_prefix <= LZ_MAX_CODE) {
stack[stack_ptr++] = suffix[current_prefix];
current_prefix = prefix[current_prefix];
}
if (j >= LZ_MAX_CODE || current_prefix > LZ_MAX_CODE)
return -4; /* image defect */
 
stack[stack_ptr++] = current_prefix;
 
while (stack_ptr != 0 && i < outputLength) {
output[i++] = stack[--stack_ptr];
//if (i > bytes_written) bytes_written = i;
++bytes_written;
}
}
if (prev_code != NO_SUCH_CODE) {
if ((decoder->running_code < 2) ||
(decoder->running_code > LZ_MAX_CODE + 2))
return -5; /* image defect */
prefix[decoder->running_code - 2] = prev_code;
 
if (current_code == decoder->running_code - 2) {
suffix[decoder->running_code - 2]
= ipe16lzw_trace_prefix(prefix, prev_code, CLEAR_CODE);
}
else {
suffix[decoder->running_code - 2]
= ipe16lzw_trace_prefix(prefix, current_code, CLEAR_CODE);
}
}
prev_code = current_code;
}
}
 
return bytes_written;
}
 
#define BMP_LINE_PADDING 4
#define BI_SIGNATURE 0x4D42
 
// Difference between ipma_write_bmp and ipe16_write_bmp: At ipma_write_bmp, the imagedata is bottom-down, and the palette is a RGBA-structure and not a RGB-structure
void ipma_write_bmp(FILE* output, unsigned int width, unsigned int height, unsigned char* imagedata, size_t imagedata_len, RGBQUAD *pal, int numColors) {
 
// Each line must be padded to a multiple of 4
int pad = (BMP_LINE_PADDING - (width % BMP_LINE_PADDING)) % BMP_LINE_PADDING;
int newwidth = width + pad;
int newsize = newwidth * height;
unsigned char* padded_imagedata = (unsigned char*)malloc(newsize);
if (padded_imagedata == NULL) return;
unsigned int i;
for (i = 0; i < height; ++i) {
int offset = newwidth * i;
memcpy(&padded_imagedata[offset], imagedata, width);
memset(&padded_imagedata[offset + width], 0, pad);
imagedata += width;
}
 
BITMAPFILEHEADER bfh;
bfh.bfType = BI_SIGNATURE;
bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * numColors + newsize;
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * numColors;
fwrite(&bfh, sizeof(bfh), 1, output);
 
BITMAPINFOHEADER bih;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = width;
bih.biHeight = height; // (positive = "bottom-up"-Bitmap)
bih.biPlanes = 1;
bih.biBitCount = 8;
bih.biCompression = BI_RGB;
bih.biSizeImage = 0;
bih.biXPelsPerMeter = 0;
bih.biYPelsPerMeter = 0;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
fwrite(&bih, sizeof(bih), 1, output);
 
// Color table
fwrite(pal, sizeof(RGBQUAD) * numColors, 1, output);
 
// Image data
fwrite(padded_imagedata, newsize, 1, output);
 
free(padded_imagedata);
}
 
 
typedef struct tagBitmapInfoAndPalette {
BITMAPINFOHEADER bi;
RGBQUAD pal[256];
} BitmapInfoAndPalette;
 
bool ipma_export_frames_bmp(char* filename, char* outdir)
{
PAVIFILE pFile;
int res;
PAVISTREAM pStream1;
AVISTREAMINFOA asi1;
 
// Try creating the directory
CreateDirectoryA(outdir, NULL);
 
if (!dirExists(outdir)) {
fprintf(stderr, "ERROR: Directory couldn't be created! %s\n", outdir);
return 1;
}
 
res = AVIFileOpenA(&pFile, filename, OF_SHARE_DENY_WRITE, 0L);
if (res == AVIERR_FILEOPEN) {
fprintf(stderr, "ERROR: AVIFileOpenA(%s) returns AVIERR_FILEOPEN. Does the file exist?\n", filename);
return false;
}
if (res != 0) {
fprintf(stderr, "ERROR: AVIFileOpenA(%s) returns %d\n", filename, res);
return false;
}
 
res = AVIFileGetStream(pFile, &pStream1, streamtypeVIDEO, 0);
if (res == AVIERR_NODATA) {
fprintf(stderr, "ERROR: AVIFileGetStream returns AVIERR_NODATA\n");
AVIFileRelease(pFile);
return false;
}
if (res == AVIERR_MEMORY) {
fprintf(stderr, "ERROR: AVIFileGetStream returns AVIERR_MEMORY\n");
AVIFileRelease(pFile);
return false;
}
if (res != 0) {
fprintf(stderr, "ERROR: AVIFileGetStream returns %d\n", res);
AVIFileRelease(pFile);
return false;
}
 
 
res = AVIStreamInfoA(pStream1, &asi1, sizeof(asi1));
if (res != 0) {
fprintf(stderr, "ERROR: AVIStreamInfoA returns %d\n", res);
AVIStreamRelease(pStream1);
AVIFileRelease(pFile);
return false;
}
 
 
// The official handler name is "ipma", but some AVI files also use "IPMA"
int ipmaVersion = 0;
//if (asi1.fccHandler == mmioFOURCC('i', 'p', 'm', 'a')) ipmaVersion = 1;
if ((tolower(((asi1.fccHandler >> 0) & 0xFF)) == 'i') &&
(tolower(((asi1.fccHandler >> 8) & 0xFF)) == 'p') &&
(tolower(((asi1.fccHandler >> 16) & 0xFF)) == 'm') &&
(tolower(((asi1.fccHandler >> 24) & 0xFF)) == 'a'))
{
ipmaVersion = 1;
}
 
// The official handler name is "IP20", but all AVI files use "ip20"
//if (asi1.fccHandler == mmioFOURCC('I', 'P', '2', '0')) ipmaVersion = 2;
if ((tolower(((asi1.fccHandler >> 0) & 0xFF)) == 'i') &&
(tolower(((asi1.fccHandler >> 8) & 0xFF)) == 'p') &&
(tolower(((asi1.fccHandler >> 16) & 0xFF)) == '2') &&
(tolower(((asi1.fccHandler >> 24) & 0xFF)) == '0'))
{
ipmaVersion = 2;
}
 
if (ipmaVersion == 0) {
fprintf(stderr, "ERROR: Not an IPMA or IP20 AVI file!\n");
AVIStreamRelease(pStream1);
AVIFileRelease(pFile);
return false;
}
 
 
int framesWritten = 0;
for (int i = 0; 1; i++) {
BitmapInfoAndPalette* pstrf = (BitmapInfoAndPalette*)malloc(sizeof(BitmapInfoAndPalette));
if (pstrf == NULL) return false;
LONG strf_siz = sizeof(BitmapInfoAndPalette);
ZeroMemory(pstrf, strf_siz);
 
//res = pStream1->ReadFormat(i, (LPVOID)pstrf, &strf_siz);
res = AVIStreamReadFormat(pStream1, i, (LPVOID)pstrf, &strf_siz);
if (res != 0) {
fprintf(stderr, "ERROR: Read format info failed\n");
AVIStreamRelease(pStream1);
AVIFileRelease(pFile);
return false;
}
 
 
if (((ipmaVersion == 1) && (asi1.fccHandler != mmioFOURCC('I', 'p', 'm', 'a'))) &&
((ipmaVersion == 2) && (asi1.fccHandler != mmioFOURCC('I', 'p', '2', '0'))))
{
// biCompression is case-sensitive and must be "Ipma" or "Ip20"
if (ipmaVersion == 1) fprintf(stderr, "ERROR: biCompression is not Ipma!\n");
if (ipmaVersion == 2) fprintf(stderr, "ERROR: biCompression is not Ip20!\n");
AVIStreamRelease(pStream1);
AVIFileRelease(pFile);
return false;
}
 
// Note that for 2 files, bi.biSizeImage is wrong (much too small!)
// LB05M08.AVI: biSizeImage (10598) != rectWidth * rectHeight (27492)
// TY06M12.AVI: biSizeImage (1274) != rectWidth * rectHeight (8058)
//int bufsiz_uncompressed = pstrf->bi.biSizeImage;
int bufsiz_uncompressed = (asi1.rcFrame.right - asi1.rcFrame.left) * (asi1.rcFrame.bottom - asi1.rcFrame.top);
// theoretically, compressed can sometimes be larger than uncompressed, so we multiply by 10
int bufsiz_compressed = bufsiz_uncompressed * 10;
 
unsigned char* buffer_uncompressed = (unsigned char*)malloc(bufsiz_uncompressed);
if (buffer_uncompressed == NULL) return false;
unsigned char* buffer_compressed = (unsigned char*)malloc(bufsiz_compressed);
if (buffer_compressed == NULL) return false;
 
 
LONG plBytes = 0;
LONG plSamples = 0;
//res = pStream1->Read(i, 1, buffer_compressed, bufsiz_compressed, &plBytes, &plSamples);
res = AVIStreamRead(pStream1, i, 1, buffer_compressed, bufsiz_compressed, &plBytes, &plSamples);
if ((res != 0) || (plSamples == 0)) break;
 
int plBytesUncompressed;
if ((plBytes == 0) && (plSamples > 0)) {
// In "Panic in the Park", frames at the end of some videos have 0 bytes data.
// This should be interpreted as "repeat frame" (still image). So we fill it with palette #0 (transparent)
plBytesUncompressed = bufsiz_uncompressed;
ZeroMemory(buffer_uncompressed, bufsiz_uncompressed);
} else {
Ipe16LZWDecoder* pdecoder = (Ipe16LZWDecoder*)malloc(sizeof(Ipe16LZWDecoder));
if (pdecoder == NULL) return false;
ZeroMemory(pdecoder, sizeof(Ipe16LZWDecoder));
unsigned char* work_buffer_compressed = buffer_compressed;
plBytesUncompressed = ipma_lzw_decode(&work_buffer_compressed, pdecoder, buffer_uncompressed, bufsiz_uncompressed);
free(pdecoder);
}
if (plBytesUncompressed < 0) fprintf(stderr, "WARNING: LZW Error %d at frame %d\n", plBytesUncompressed, i);
if (plBytesUncompressed != bufsiz_uncompressed) fprintf(stderr, "WARNING: piBytesUncompressed != bufsiz_uncompressed\n");
if (plBytesUncompressed > 0) {
char filnam[MAX_PATH];
if (AVIStreamIsKeyFrame(pStream1, i)) {
sprintf(filnam, "%s\\frame_%05d_key.bmp", outdir, i);
} else {
sprintf(filnam, "%s\\frame_%05d.bmp", outdir, i);
}
FILE* fh2 = fopen(filnam, "wb+");
ipma_write_bmp(fh2, pstrf->bi.biWidth, pstrf->bi.biHeight, buffer_uncompressed, plBytesUncompressed, &pstrf->pal[0], 256);
fclose(fh2);
framesWritten++;
}
 
free(pstrf);
free(buffer_compressed);
free(buffer_uncompressed);
}
 
fprintf(stdout, "%s: %d frames written to %s\n", filename, framesWritten, outdir);
 
AVIStreamRelease(pStream1);
AVIFileRelease(pFile);
return true;
}
 
void print_syntax() {
fprintf(stderr, "Syntax: -o <outputdir> -i <avifile>\n");
}
 
int main(int argc, char* argv[]) {
char filename[MAX_PATH];
char outdir[MAX_PATH];
 
#ifndef VISUAL_STUDIO_TEST
int c;
 
#define PRINT_SYNTAX { print_syntax(); return 0; }
 
while ((c = getopt(argc, argv, "Vi:o:")) != -1) {
switch (c) {
case 'V':
fprintf(stdout, "IPMA video frame extractor, revision %s\n", VERSION);
return 0;
case 'i':
strcpy(filename, optarg);
break;
case 'o':
strcpy(outdir, optarg);
break;
case '?':
PRINT_SYNTAX;
break;
}
}
if (optind < argc) PRINT_SYNTAX;
 
if (strlen(filename) == 0) PRINT_SYNTAX;
if (strlen(outdir) == 0) PRINT_SYNTAX;
 
FILE* fhTest = fopen(filename, "rb");
if (!fhTest) {
fprintf(stderr, "FATAL: Cannot open %s\n", filename);
return 1;
}
fclose(fhTest);
 
#else
strcpy(filename, "D:\\test\\AVI_TEST.avi");
strcpy(outdir, "D:\\test\\AVI_TEST");
#endif
 
if (CoInitialize(NULL) != 0) return 1;
 
bool res = ipma_export_frames_bmp(filename, outdir);
 
CoUninitialize();
 
return res ? 0 : 1;
}
/trunk/ipma_frame_extractor.exe
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/test/ba_test/MENU.bmp
Cannot display: file marked as a binary type.
svn:mime-type = image/x-ms-bmp
Property changes:
Added: svn:mime-type
+image/x-ms-bmp
\ No newline at end of property
/trunk/test/ba_test/index.txt
0,0 → 1,0
X P MENU MENU.bmp
Property changes:
Added: svn:mime-type
+text/plain
\ No newline at end of property
/trunk/test/eraser_test/CHRBDOSS.bmp
Cannot display: file marked as a binary type.
svn:mime-type = image/x-ms-bmp
Property changes:
Added: svn:mime-type
+image/x-ms-bmp
\ No newline at end of property
/trunk/test/eraser_test/index.txt
0,0 → 1,0
CHRBDOSS 19(C) 0(R) CHRBDOSS.bmp
Property changes:
Added: svn:mime-type
+text/plain
\ No newline at end of property
/trunk/test/pip_test/CCES2S.bmp
Cannot display: file marked as a binary type.
svn:mime-type = image/x-ms-bmp
Property changes:
Added: svn:mime-type
+image/x-ms-bmp
\ No newline at end of property
/trunk/test/pip_test/index.txt
0,0 → 1,0
X Q CCES2S CCES2S.bmp 0 0
Property changes:
Added: svn:mime-type
+text/plain
\ No newline at end of property
/trunk/test/test.sh
0,0 → 1,72
#!/bin/bash
 
DIR=$( dirname "$0" )
cd "$DIR"
 
if [ -d out_test ]; then
rm -Rf out_test
fi
if [ -f eraser_test.art ]; then
rm -f eraser_test.art
fi
 
# ---
 
if [ -f ba_test.art ]; then
rm -f ba_test.art
fi
../ipe_artfile_packer -v -i ba_test -o ba_test.art -t ba
if [ -f ba_test.art ]; then
mkdir out_test
../ipe_artfile_unpacker -v -i ba_test.art -o out_test
fi
diff ba_test/MENU.bmp out_test/MENU.bmp
RES=$?
#if [ $RES -ne 0 ]; then
# exit 1
#fi
echo "DIFF Result (BA): $RES"
if [ -d out_test ]; then
rm -Rf out_test
fi
if [ -f ba_test.art ]; then
rm -f ba_test.art
fi
echo "------------------------"
 
../ipe_artfile_packer -v -i pip_test -o pip_test.art -t pip
if [ -f pip_test.art ]; then
mkdir out_test
../ipe_artfile_unpacker -v -i pip_test.art -o out_test
fi
diff pip_test/CCES2S.bmp out_test/CCES2S.bmp
RES=$?
#if [ $RES -ne 0 ]; then
# exit
#fi
echo "DIFF Result (PiP): $RES"
if [ -d out_test ]; then
rm -Rf out_test
fi
if [ -f pip_test.art ]; then
rm -f pip_test.art
fi
echo "------------------------"
 
../ipe_artfile_packer -v -i eraser_test -o eraser_test.art -t eraser
if [ -f eraser_test.art ]; then
mkdir out_test
../ipe_artfile_unpacker -v -i eraser_test.art -o out_test
fi
diff eraser_test/CHRBDOSS.bmp out_test/CHRBDOSS.bmp
RES=$?
#if [ $RES -ne 0 ]; then
# exit
#fi
echo "DIFF Result (Eraser): $RES"
if [ -d out_test ]; then
rm -Rf out_test
fi
if [ -f eraser_test.art ]; then
rm -f eraser_test.art
fi
Property changes:
Added: svn:executable
+*
\ No newline at end of property
Added: svn:mime-type
+text/x-sh
\ No newline at end of property
/trunk/test_h/INFO
0,0 → 1,2
 
These tests test if the header files can be compiled standalone
/trunk/test_h/test.sh
0,0 → 1,25
#!/bin/bash
 
DIR=$( dirname "$0" )
cd "$DIR"
 
gcc --std=c99 test_bitmap.c
gcc --std=c99 test_utils.c
gcc --std=c99 test_ipe16_artfile.c
gcc --std=c99 test_ipe16_bmpexport.c
gcc --std=c99 test_ipe16_bmpimport.c
gcc --std=c99 test_ipe16_lzw_encoder.c
gcc --std=c99 test_ipe16_lzw_decoder.c
gcc --std=c99 test_ipe32_artfile.c
gcc --std=c99 test_ipe32_bmpexport.c
gcc --std=c99 test_ipe32_bmpimport.c
gcc --std=c99 test_ipe32_lzw_encoder.c
gcc --std=c99 test_ipe32_lzw_decoder.c
gcc --std=c99 test_ipe_artfile_packer_ipe16_ba.c
gcc --std=c99 test_ipe_artfile_packer_ipe16_pip.c
gcc --std=c99 test_ipe_artfile_packer_ipe32.c
gcc --std=c99 test_ipe_artfile_unpacker_ipe16.c
gcc --std=c99 test_ipe_artfile_unpacker_ipe32.c
 
rm a.out
 
Property changes:
Added: svn:executable
+*
\ No newline at end of property
Added: svn:mime-type
+text/x-sh
\ No newline at end of property
/trunk/test_h/test_bitmap.c
0,0 → 1,5
#include "../bitmap.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe16_artfile.c
0,0 → 1,5
#include "../ipe16_artfile.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe16_bmpexport.c
0,0 → 1,5
#include "../ipe16_bmpexport.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe16_bmpimport.c
0,0 → 1,5
#include "../ipe16_bmpimport.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe16_lzw_decoder.c
0,0 → 1,5
#include "../ipe16_lzw_decoder.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe16_lzw_encoder.c
0,0 → 1,5
#include "../ipe16_lzw_encoder.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe32_artfile.c
0,0 → 1,5
#include "../ipe32_artfile.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe32_bmpexport.c
0,0 → 1,5
#include "../ipe32_bmpexport.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe32_bmpimport.c
0,0 → 1,5
#include "../ipe32_bmpimport.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe32_lzw_decoder.c
0,0 → 1,5
#include "../ipe32_lzw_decoder.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe32_lzw_encoder.c
0,0 → 1,5
#include "../ipe32_lzw_encoder.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe_artfile_packer_ipe16_ba.c
0,0 → 1,5
#include "../ipe_artfile_packer_ipe16_ba.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe_artfile_packer_ipe16_pip.c
0,0 → 1,5
#include "../ipe_artfile_packer_ipe16_pip.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe_artfile_packer_ipe32.c
0,0 → 1,5
#include "../ipe_artfile_packer_ipe32.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe_artfile_unpacker_ipe16.c
0,0 → 1,5
#include "../ipe_artfile_unpacker_ipe16.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_ipe_artfile_unpacker_ipe32.c
0,0 → 1,5
#include "../ipe_artfile_unpacker_ipe32.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/test_h/test_utils.c
0,0 → 1,5
#include "../utils.h"
 
int main(int argc, char *argv[]) {
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/utils.c
0,0 → 1,33
#include <stdlib.h>
#include <stdio.h>
 
#include "utils.h"
 
size_t file_size(FILE* fp) {
size_t pos = ftell(fp);
fseek(fp, 0, SEEK_END);
size_t len = ftell(fp);
fseek(fp, pos, SEEK_SET);
return len;
}
 
char* sanitize_filename(char* picname) {
// Remove invalid windows characters
// TODO: not yet implemented. not necessary for the regular ART files, though
return picname;
}
 
void* app_zero_alloc(long bytes) {
char* ptr = (char*)malloc(bytes);
int i;
for (i=0; i<bytes; ++i) ptr[i] = 0;
return (void*)ptr;
}
 
unsigned char read_byte(FILE *file) {
int ch = getc(file);
if (ch == EOF)
ch = 0;
return ch;
}
 
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property
/trunk/utils.h
0,0 → 1,12
#ifndef __inc__utils
#define __inc__utils
 
#include <stdio.h>
 
size_t file_size(FILE* fp);
char* sanitize_filename(char* picname);
void* app_zero_alloc(long bytes);
unsigned char read_byte(FILE *file);
 
#endif // #ifndef __inc__utils
 
Property changes:
Added: svn:mime-type
+text/x-chdr
\ No newline at end of property
/trunk/variable_prefixes.txt
0,0 → 1,7
My variable prefixes:
 
fit = text file input handle ("rt")
fib = binary file input handle ("rb")
 
fot = text file output handle ("ot")
fob = binary file output handle ("ob")
Property changes:
Added: svn:mime-type
+text/plain
\ No newline at end of property