/* Copyright (C) 1999-2007 id Software, Inc. and contributors. For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "qdata.h" #include "inout.h" byte *soundtrack; char base[32]; /* =============================================================================== WAV loading =============================================================================== */ typedef struct { int rate; int width; int channels; int loopstart; int samples; int dataofs; // chunk starts this many bytes from file start } wavinfo_t; byte *data_p; byte *iff_end; byte *last_chunk; byte *iff_data; int iff_chunk_len; int samplecounts[0x10000]; wavinfo_t wavinfo; short GetLittleShort( void ){ short val = 0; val = *data_p; val = val + ( *( data_p + 1 ) << 8 ); data_p += 2; return val; } int GetLittleLong( void ){ int val = 0; val = *data_p; val = val + ( *( data_p + 1 ) << 8 ); val = val + ( *( data_p + 2 ) << 16 ); val = val + ( *( data_p + 3 ) << 24 ); data_p += 4; return val; } void FindNextChunk( char *name ){ while ( 1 ) { data_p = last_chunk; if ( data_p >= iff_end ) { // didn't find the chunk data_p = NULL; return; } data_p += 4; iff_chunk_len = GetLittleLong(); if ( iff_chunk_len < 0 ) { data_p = NULL; return; } // if (iff_chunk_len > 1024*1024) // Sys_Error ("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len); data_p -= 8; last_chunk = data_p + 8 + ( ( iff_chunk_len + 1 ) & ~1 ); if ( !strncmp( data_p, name, 4 ) ) { return; } } } void FindChunk( char *name ){ last_chunk = iff_data; FindNextChunk( name ); } void DumpChunks( void ){ char str[5]; str[4] = 0; data_p = iff_data; do { memcpy( str, data_p, 4 ); data_p += 4; iff_chunk_len = GetLittleLong(); printf( "0x%x : %s (%d)\n", (int)( data_p - 4 ), str, iff_chunk_len ); data_p += ( iff_chunk_len + 1 ) & ~1; } while ( data_p < iff_end ); } /* ============ GetWavinfo ============ */ wavinfo_t GetWavinfo( char *name, byte *wav, int wavlength ){ wavinfo_t info; int i; int format; int samples; memset( &info, 0, sizeof( info ) ); if ( !wav ) { return info; } iff_data = wav; iff_end = wav + wavlength; // find "RIFF" chunk FindChunk( "RIFF" ); if ( !( data_p && !strncmp( data_p + 8, "WAVE", 4 ) ) ) { printf( "Missing RIFF/WAVE chunks\n" ); return info; } // get "fmt " chunk iff_data = data_p + 12; // DumpChunks (); FindChunk( "fmt " ); if ( !data_p ) { printf( "Missing fmt chunk\n" ); return info; } data_p += 8; format = GetLittleShort(); if ( format != 1 ) { printf( "Microsoft PCM format only\n" ); return info; } info.channels = GetLittleShort(); info.rate = GetLittleLong(); data_p += 4 + 2; info.width = GetLittleShort() / 8; // get cue chunk FindChunk( "cue " ); if ( data_p ) { data_p += 32; info.loopstart = GetLittleLong(); // Com_Printf("loopstart=%d\n", sfx->loopstart); // if the next chunk is a LIST chunk, look for a cue length marker FindNextChunk( "LIST" ); if ( data_p ) { if ( !strncmp( data_p + 28, "mark", 4 ) ) { // this is not a proper parse, but it works with cooledit... data_p += 24; i = GetLittleLong(); // samples in loop info.samples = info.loopstart + i; } } } else{ info.loopstart = -1; } // find data chunk FindChunk( "data" ); if ( !data_p ) { printf( "Missing data chunk\n" ); return info; } data_p += 4; samples = GetLittleLong(); if ( info.samples ) { if ( samples < info.samples ) { Error( "Sound %s has a bad loop length", name ); } } else{ info.samples = samples; } info.dataofs = data_p - wav; return info; } //===================================================================== /* ============== LoadSoundtrack ============== */ void LoadSoundtrack( void ){ char name[1024]; FILE *f; int len; int i, val, j; soundtrack = NULL; sprintf( name, "%svideo/%s/%s.wav", gamedir, base, base ); printf( "%s\n", name ); f = fopen( name, "rb" ); if ( !f ) { printf( "no soundtrack for %s\n", base ); return; } len = Q_filelength( f ); soundtrack = malloc( len ); fread( soundtrack, 1, len, f ); fclose( f ); wavinfo = GetWavinfo( name, soundtrack, len ); // count samples for compression memset( samplecounts, 0, sizeof( samplecounts ) ); j = wavinfo.samples / 2; for ( i = 0 ; i < j ; i++ ) { val = ( (unsigned short *)( soundtrack + wavinfo.dataofs ) )[i]; samplecounts[val]++; } val = 0; for ( i = 0 ; i < 0x10000 ; i++ ) if ( samplecounts[i] ) { val++; } printf( "%i unique sample values\n", val ); } /* ================== WriteSound ================== */ void WriteSound( FILE *output, int frame ){ int start, end; int count; int empty = 0; int i; int sample; int width; width = wavinfo.width * wavinfo.channels; start = frame * wavinfo.rate / 14; end = ( frame + 1 ) * wavinfo.rate / 14; count = end - start; for ( i = 0 ; i < count ; i++ ) { sample = start + i; if ( sample > wavinfo.samples || !soundtrack ) { fwrite( &empty, 1, width, output ); } else{ fwrite( soundtrack + wavinfo.dataofs + sample * width, 1, width,output ); } } } //========================================================================== /* ================== MTF ================== */ cblock_t MTF( cblock_t in ){ int i, j, b, code; byte *out_p; int index[256]; cblock_t out; out_p = out.data = malloc( in.count + 4 ); // write count *out_p++ = in.count & 255; *out_p++ = ( in.count >> 8 ) & 255; *out_p++ = ( in.count >> 16 ) & 255; *out_p++ = ( in.count >> 24 ) & 255; for ( i = 0 ; i < 256 ; i++ ) index[i] = i; for ( i = 0 ; i < in.count ; i++ ) { b = in.data[i]; code = index[b]; *out_p++ = code; // shuffle b indexes to 0 for ( j = 0 ; j < 256 ; j++ ) if ( index[j] < code ) { index[j]++; } index[b] = 0; } out.count = out_p - out.data; return out; } //========================================================================== int bwt_size; byte *bwt_data; int bwtCompare( const void *elem1, const void *elem2 ){ int i; int i1, i2; int b1, b2; i1 = *(int *)elem1; i2 = *(int *)elem2; for ( i = 0 ; i < bwt_size ; i++ ) { b1 = bwt_data[i1]; b2 = bwt_data[i2]; if ( b1 < b2 ) { return -1; } if ( b1 > b2 ) { return 1; } if ( ++i1 == bwt_size ) { i1 = 0; } if ( ++i2 == bwt_size ) { i2 = 0; } } return 0; } /* ================== BWT ================== */ cblock_t BWT( cblock_t in ){ int *sorted; int i; byte *out_p; cblock_t out; bwt_size = in.count; bwt_data = in.data; sorted = malloc( in.count * sizeof( *sorted ) ); for ( i = 0 ; i < in.count ; i++ ) sorted[i] = i; qsort( sorted, in.count, sizeof( *sorted ), bwtCompare ); out_p = out.data = malloc( in.count + 8 ); // write count *out_p++ = in.count & 255; *out_p++ = ( in.count >> 8 ) & 255; *out_p++ = ( in.count >> 16 ) & 255; *out_p++ = ( in.count >> 24 ) & 255; // write head index for ( i = 0 ; i < in.count ; i++ ) if ( sorted[i] == 0 ) { break; } *out_p++ = i & 255; *out_p++ = ( i >> 8 ) & 255; *out_p++ = ( i >> 16 ) & 255; *out_p++ = ( i >> 24 ) & 255; // write the L column for ( i = 0 ; i < in.count ; i++ ) *out_p++ = in.data[( sorted[i] + in.count - 1 ) % in.count]; free( sorted ); out.count = out_p - out.data; return out; } //========================================================================== typedef struct hnode_s { int count; qboolean used; int children[2]; } hnode_t; int numhnodes; hnode_t hnodes[512]; unsigned charbits[256]; int charbitscount[256]; int SmallestNode( void ){ int i; int best, bestnode; best = 99999999; bestnode = -1; for ( i = 0 ; i < numhnodes ; i++ ) { if ( hnodes[i].used ) { continue; } if ( !hnodes[i].count ) { continue; } if ( hnodes[i].count < best ) { best = hnodes[i].count; bestnode = i; } } if ( bestnode == -1 ) { return -1; } hnodes[bestnode].used = true; return bestnode; } void BuildChars( int nodenum, unsigned bits, int bitcount ){ hnode_t *node; if ( nodenum < 256 ) { if ( bitcount > 32 ) { Error( "bitcount > 32" ); } charbits[nodenum] = bits; charbitscount[nodenum] = bitcount; return; } node = &hnodes[nodenum]; bits <<= 1; BuildChars( node->children[0], bits, bitcount + 1 ); bits |= 1; BuildChars( node->children[1], bits, bitcount + 1 ); } /* ================== Huffman ================== */ cblock_t Huffman( cblock_t in ){ int i; hnode_t *node; int outbits, c; unsigned bits; byte *out_p; cblock_t out; int max, maxchar; // count memset( hnodes, 0, sizeof( hnodes ) ); for ( i = 0 ; i < in.count ; i++ ) hnodes[in.data[i]].count++; // normalize counts max = 0; maxchar = 0; for ( i = 0 ; i < 256 ; i++ ) { if ( hnodes[i].count > max ) { max = hnodes[i].count; maxchar = i; } } if ( max == 0 ) { Error( "Huffman: max == 0" ); } for ( i = 0 ; i < 256 ; i++ ) { hnodes[i].count = ( hnodes[i].count * 255 + max - 1 ) / max; } // build the nodes numhnodes = 256; while ( numhnodes != 511 ) { node = &hnodes[numhnodes]; // pick two lowest counts node->children[0] = SmallestNode(); if ( node->children[0] == -1 ) { break; // no more } node->children[1] = SmallestNode(); if ( node->children[1] == -1 ) { if ( node->children[0] != numhnodes - 1 ) { Error( "Bad smallestnode" ); } break; } node->count = hnodes[node->children[0]].count + hnodes[node->children[1]].count; numhnodes++; } BuildChars( numhnodes - 1, 0, 0 ); out_p = out.data = malloc( in.count * 2 + 1024 ); memset( out_p, 0, in.count * 2 + 1024 ); // write count *out_p++ = in.count & 255; *out_p++ = ( in.count >> 8 ) & 255; *out_p++ = ( in.count >> 16 ) & 255; *out_p++ = ( in.count >> 24 ) & 255; // save out the 256 normalized counts so the tree can be recreated for ( i = 0 ; i < 256 ; i++ ) *out_p++ = hnodes[i].count; // write bits outbits = 0; for ( i = 0 ; i < in.count ; i++ ) { c = charbitscount[in.data[i]]; bits = charbits[in.data[i]]; while ( c ) { c--; if ( bits & ( 1 << c ) ) { out_p[outbits >> 3] |= 1 << ( outbits & 7 ); } outbits++; } } out_p += ( outbits + 7 ) >> 3; out.count = out_p - out.data; return out; } //========================================================================== /* ================== RLE ================== */ #define RLE_CODE 0xe8 #define RLE_TRIPPLE 0xe9 int rle_counts[256]; int rle_bytes[256]; cblock_t RLE( cblock_t in ){ int i; byte *out_p; int val; int repeat; cblock_t out; out_p = out.data = malloc( in.count * 2 ); // write count *out_p++ = in.count & 255; *out_p++ = ( in.count >> 8 ) & 255; *out_p++ = ( in.count >> 16 ) & 255; *out_p++ = ( in.count >> 24 ) & 255; for ( i = 0 ; i < in.count ; ) { val = in.data[i]; rle_bytes[val]++; repeat = 1; i++; while ( i < in.count && repeat < 255 && in.data[i] == val ) { repeat++; i++; } if ( repeat < 256 ) { rle_counts[repeat]++; } if ( repeat > 3 || val == RLE_CODE ) { *out_p++ = RLE_CODE; *out_p++ = val; *out_p++ = repeat; } else { while ( repeat-- ) *out_p++ = val; } } out.count = out_p - out.data; return out; } //========================================================================== unsigned lzss_head[256]; unsigned lzss_next[0x20000]; /* ================== LZSS ================== */ #define BACK_WINDOW 0x10000 #define BACK_BITS 16 #define FRONT_WINDOW 16 #define FRONT_BITS 4 cblock_t LZSS( cblock_t in ){ int i; byte *out_p; cblock_t out; int val; int j, start, max; int bestlength, beststart; int outbits; if ( in.count >= sizeof( lzss_next ) / 4 ) { Error( "LZSS: too big" ); } memset( lzss_head, -1, sizeof( lzss_head ) ); out_p = out.data = malloc( in.count * 2 ); memset( out.data, 0, in.count * 2 ); // write count *out_p++ = in.count & 255; *out_p++ = ( in.count >> 8 ) & 255; *out_p++ = ( in.count >> 16 ) & 255; *out_p++ = ( in.count >> 24 ) & 255; outbits = 0; for ( i = 0 ; i < in.count ; ) { val = in.data[i]; #if 1 // chained search bestlength = 0; beststart = 0; max = FRONT_WINDOW; if ( i + max > in.count ) { max = in.count - i; } start = lzss_head[val]; while ( start != -1 && start >= i - BACK_WINDOW ) { // count match length for ( j = 0 ; j < max ; j++ ) if ( in.data[start + j] != in.data[i + j] ) { break; } if ( j > bestlength ) { bestlength = j; beststart = start; } start = lzss_next[start]; } #else // slow simple search // search for a match max = FRONT_WINDOW; if ( i + max > in.count ) { max = in.count - i; } start = i - BACK_WINDOW; if ( start < 0 ) { start = 0; } bestlength = 0; beststart = 0; for ( ; start < i ; start++ ) { if ( in.data[start] != val ) { continue; } // count match length for ( j = 0 ; j < max ; j++ ) if ( in.data[start + j] != in.data[i + j] ) { break; } if ( j > bestlength ) { bestlength = j; beststart = start; } } #endif beststart = BACK_WINDOW - ( i - beststart ); if ( bestlength < 3 ) { // output a single char bestlength = 1; out_p[outbits >> 3] |= 1 << ( outbits & 7 ); // set bit to mark char outbits++; for ( j = 0 ; j < 8 ; j++, outbits++ ) if ( val & ( 1 << j ) ) { out_p[outbits >> 3] |= 1 << ( outbits & 7 ); } } else { // output a phrase outbits++; // leave a 0 bit to mark phrase for ( j = 0 ; j < BACK_BITS ; j++, outbits++ ) if ( beststart & ( 1 << j ) ) { out_p[outbits >> 3] |= 1 << ( outbits & 7 ); } for ( j = 0 ; j < FRONT_BITS ; j++, outbits++ ) if ( bestlength & ( 1 << j ) ) { out_p[outbits >> 3] |= 1 << ( outbits & 7 ); } } while ( bestlength-- ) { val = in.data[i]; lzss_next[i] = lzss_head[val]; lzss_head[val] = i; i++; } } out_p += ( outbits + 7 ) >> 3; out.count = out_p - out.data; return out; } //========================================================================== #define MIN_REPT 15 #define MAX_REPT 0 #define HUF_TOKENS ( 256 + MAX_REPT ) unsigned charbits1[256][HUF_TOKENS]; int charbitscount1[256][HUF_TOKENS]; hnode_t hnodes1[256][HUF_TOKENS * 2]; int numhnodes1[256]; int order0counts[256]; /* ================== SmallestNode1 ================== */ int SmallestNode1( hnode_t *hnodes, int numhnodes ){ int i; int best, bestnode; best = 99999999; bestnode = -1; for ( i = 0 ; i < numhnodes ; i++ ) { if ( hnodes[i].used ) { continue; } if ( !hnodes[i].count ) { continue; } if ( hnodes[i].count < best ) { best = hnodes[i].count; bestnode = i; } } if ( bestnode == -1 ) { return -1; } hnodes[bestnode].used = true; return bestnode; } /* ================== BuildChars1 ================== */ void BuildChars1( int prev, int nodenum, unsigned bits, int bitcount ){ hnode_t *node; if ( nodenum < HUF_TOKENS ) { if ( bitcount > 32 ) { Error( "bitcount > 32" ); } charbits1[prev][nodenum] = bits; charbitscount1[prev][nodenum] = bitcount; return; } node = &hnodes1[prev][nodenum]; bits <<= 1; BuildChars1( prev, node->children[0], bits, bitcount + 1 ); bits |= 1; BuildChars1( prev, node->children[1], bits, bitcount + 1 ); } /* ================== BuildTree1 ================== */ void BuildTree1( int prev ){ hnode_t *node, *nodebase; int numhnodes; // build the nodes numhnodes = HUF_TOKENS; nodebase = hnodes1[prev]; while ( 1 ) { node = &nodebase[numhnodes]; // pick two lowest counts node->children[0] = SmallestNode1( nodebase, numhnodes ); if ( node->children[0] == -1 ) { break; // no more } node->children[1] = SmallestNode1( nodebase, numhnodes ); if ( node->children[1] == -1 ) { break; } node->count = nodebase[node->children[0]].count + nodebase[node->children[1]].count; numhnodes++; } numhnodes1[prev] = numhnodes - 1; BuildChars1( prev, numhnodes - 1, 0, 0 ); } /* ================== Huffman1_Count ================== */ void Huffman1_Count( cblock_t in ){ int i; int prev; int v; int rept; prev = 0; for ( i = 0 ; i < in.count ; i++ ) { v = in.data[i]; order0counts[v]++; hnodes1[prev][v].count++; prev = v; #if 1 for ( rept = 1 ; i + rept < in.count && rept < MAX_REPT ; rept++ ) if ( in.data[i + rept] != v ) { break; } if ( rept > MIN_REPT ) { hnodes1[prev][255 + rept].count++; i += rept - 1; } #endif } } /* ================== Huffman1_Build ================== */ byte scaled[256][HUF_TOKENS]; void Huffman1_Build( FILE *f ){ int i, j, v; int max; int total; for ( i = 0 ; i < 256 ; i++ ) { // normalize and save the counts max = 0; for ( j = 0 ; j < HUF_TOKENS ; j++ ) { if ( hnodes1[i][j].count > max ) { max = hnodes1[i][j].count; } } if ( max == 0 ) { max = 1; } total = 0; for ( j = 0 ; j < HUF_TOKENS ; j++ ) { // easy to overflow 32 bits here! v = ( hnodes1[i][j].count * (double)255 + max - 1 ) / max; if ( v > 255 ) { Error( "v > 255" ); } scaled[i][j] = hnodes1[i][j].count = v; if ( v ) { total++; } } if ( total == 1 ) { // must have two tokens if ( !scaled[i][0] ) { scaled[i][0] = hnodes1[i][0].count = 1; } else{ scaled[i][1] = hnodes1[i][1].count = 1; } } BuildTree1( i ); } #if 0 // count up the total bits total = 0; for ( i = 0 ; i < 256 ; i++ ) for ( j = 0 ; j < 256 ; j++ ) total += charbitscount1[i][j] * hnodes1[i][j].count; total = ( total + 7 ) / 8; printf( "%i bytes huffman1 compressed\n", total ); #endif fwrite( scaled, 1, sizeof( scaled ), f ); } /* ================== Huffman1 Order 1 compression with pre-built table ================== */ cblock_t Huffman1( cblock_t in ){ int i; int outbits, c; unsigned bits; byte *out_p; cblock_t out; int prev; int v; int rept; out_p = out.data = malloc( in.count * 2 + 1024 ); memset( out_p, 0, in.count * 2 + 1024 ); // write count *out_p++ = in.count & 255; *out_p++ = ( in.count >> 8 ) & 255; *out_p++ = ( in.count >> 16 ) & 255; *out_p++ = ( in.count >> 24 ) & 255; // write bits outbits = 0; prev = 0; for ( i = 0 ; i < in.count ; i++ ) { v = in.data[i]; c = charbitscount1[prev][v]; bits = charbits1[prev][v]; if ( !c ) { Error( "!bits" ); } while ( c ) { c--; if ( bits & ( 1 << c ) ) { out_p[outbits >> 3] |= 1 << ( outbits & 7 ); } outbits++; } prev = v; #if 1 // check for repeat encodes for ( rept = 1 ; i + rept < in.count && rept < MAX_REPT ; rept++ ) if ( in.data[i + rept] != v ) { break; } if ( rept > MIN_REPT ) { c = charbitscount1[prev][255 + rept]; bits = charbits1[prev][255 + rept]; if ( !c ) { Error( "!bits" ); } while ( c ) { c--; if ( bits & ( 1 << c ) ) { out_p[outbits >> 3] |= 1 << ( outbits & 7 ); } outbits++; } i += rept - 1; } #endif } out_p += ( outbits + 7 ) >> 3; out.count = out_p - out.data; return out; } //========================================================================== /* =================== LoadFrame =================== */ cblock_t LoadFrame( char *base, int frame, int digits, byte **palette ){ int ten3, ten2, ten1, ten0; cblock_t in; int width, height; char name[1024]; FILE *f; in.data = NULL; in.count = -1; ten3 = frame / 1000; ten2 = ( frame - ten3 * 1000 ) / 100; ten1 = ( frame - ten3 * 1000 - ten2 * 100 ) / 10; ten0 = frame % 10; if ( digits == 4 ) { sprintf( name, "%svideo/%s/%s%i%i%i%i.pcx", gamedir, base, base, ten3, ten2, ten1, ten0 ); } else{ sprintf( name, "%svideo/%s/%s%i%i%i.pcx", gamedir, base, base, ten2, ten1, ten0 ); } f = fopen( name, "rb" ); if ( !f ) { in.data = NULL; return in; } fclose( f ); printf( "%s\n", name ); Load256Image( name, &in.data, palette, &width, &height ); in.count = width * height; // FIXME: map 0 and 255! #if 0 // rle compress rle = RLE( in ); free( in.data ); return rle; #endif return in; } /* =============== Cmd_Video video =============== */ void Cmd_Video( void ){ char savename[1024]; char name[1024]; FILE *output; int startframe, frame; byte *palette; int width, height; byte current_palette[768]; int command; int i; int digits; cblock_t in, huffman; int swap; GetToken( false ); strcpy( base, token ); if ( g_release ) { // sprintf (savename, "video/%s.cin", token); // ReleaseFile (savename); return; } GetToken( false ); digits = atoi( token ); // optionally skip frames if ( TokenAvailable() ) { GetToken( false ); startframe = atoi( token ); } else{ startframe = 0; } sprintf( savename, "%svideo/%s.cin", gamedir, base ); // clear stuff memset( charbits1, 0, sizeof( charbits1 ) ); memset( charbitscount1, 0, sizeof( charbitscount1 ) ); memset( hnodes1, 0, sizeof( hnodes1 ) ); memset( numhnodes1, 0, sizeof( numhnodes1 ) ); memset( order0counts, 0, sizeof( order0counts ) ); // load the entire sound wav file if present LoadSoundtrack(); if ( digits == 4 ) { sprintf( name, "%svideo/%s/%s0000.pcx", gamedir, base, base ); } else{ sprintf( name, "%svideo/%s/%s000.pcx", gamedir, base, base ); } printf( "%s\n", name ); Load256Image( name, NULL, &palette, &width, &height ); output = fopen( savename, "wb" ); if ( !output ) { Error( "Can't open %s", savename ); } // write header info i = LittleLong( width ); fwrite( &i, 4, 1, output ); i = LittleLong( height ); fwrite( &i, 4, 1, output ); i = LittleLong( wavinfo.rate ); fwrite( &i, 4, 1, output ); i = LittleLong( wavinfo.width ); fwrite( &i, 4, 1, output ); i = LittleLong( wavinfo.channels ); fwrite( &i, 4, 1, output ); // build the dictionary for ( frame = startframe ; ; frame++ ) { printf( "counting %i", frame ); in = LoadFrame( base, frame, digits, &palette ); if ( !in.data ) { break; } Huffman1_Count( in ); free( in.data ); } printf( "\n" ); // build nodes and write counts Huffman1_Build( output ); memset( current_palette, 0, sizeof( current_palette ) ); // compress it with the dictionary for ( frame = startframe ; ; frame++ ) { printf( "packing %i", frame ); in = LoadFrame( base, frame, digits, &palette ); if ( !in.data ) { break; } // see if the palette has changed for ( i = 0 ; i < 768 ; i++ ) if ( palette[i] != current_palette[i] ) { // write a palette change memcpy( current_palette, palette, sizeof( current_palette ) ); command = LittleLong( 1 ); fwrite( &command, 1, 4, output ); fwrite( current_palette, 1, sizeof( current_palette ), output ); break; } if ( i == 768 ) { command = 0; // no palette change fwrite( &command, 1, 4, output ); } // save the image huffman = Huffman1( in ); printf( "%5i bytes after huffman1\n", huffman.count ); swap = LittleLong( huffman.count ); fwrite( &swap, 1, sizeof( swap ), output ); fwrite( huffman.data, 1, huffman.count, output ); // save some sound samples WriteSound( output, frame ); free( palette ); free( in.data ); free( huffman.data ); } printf( "\n" ); // write end-of-file command command = 2; fwrite( &command, 1, 4, output ); printf( "Total size: %i\n", ftell( output ) ); fclose( output ); if ( soundtrack ) { free( soundtrack ); } }