1888 lines
54 KiB
C
1888 lines
54 KiB
C
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
|
|
typedef enum {
|
|
false,
|
|
true
|
|
} bool;
|
|
|
|
#define VERIFY_COST_MODEL 0
|
|
#define DEFAULT_LENGTHS "3/6/8/10:4/7/10/13"
|
|
#define BITFIRE_WITH_MOFF 0
|
|
|
|
#define OUTPUT_NONE 0
|
|
#define OUTPUT_SFX 2
|
|
#define OUTPUT_LEVEL 3
|
|
#define OUTPUT_BITFIRE 4
|
|
|
|
enum {
|
|
RUN_LIMIT = 0x100,
|
|
OFFSET_LENGTH_LIMIT = 15
|
|
};
|
|
|
|
enum {
|
|
INFINITE_WINDOW = (unsigned) INT_MIN
|
|
};
|
|
|
|
static const char decruncher[] = {
|
|
0x01,0x08,0x1c,0x08,0x00,0x00,0x9e,0x32,0x30,0x37,0x38,0x3a,0x22,0x14,0x14,0x14,
|
|
0x14,0x14,0x14,0x14,0x14,0x14,0x42,0x49,0x54,0x4e,0x41,0x58,0x00,0x00,0x00,0x78,
|
|
0xe6,0x01,0xa2,0x00,0xbd,0x46,0x08,0x9d,0x20,0x00,0xe8,0xd0,0xf7,0xa0,0x01,0xca,
|
|
0xbd,0x2c,0x08,0x9d,0x00,0xff,0x8a,0xd0,0xf6,0xce,0x31,0x08,0xce,0x34,0x08,0x88,
|
|
0xd0,0xed,0xa2,0x1a,0x4c,0x20,0x00,0x38,0x20,0xef,0x00,0x90,0x3b,0xf0,0xf9,0xa9,
|
|
0x00,0x2a,0x06,0xee,0xd0,0x03,0x20,0xef,0x00,0x90,0x09,0x06,0xee,0xd0,0xf2,0x20,
|
|
0xef,0x00,0xd0,0xed,0x85,0x4f,0xa0,0x00,0xbd,0x00,0x00,0xe8,0xd0,0x03,0x20,0xf9,
|
|
0x00,0x99,0x00,0x38,0xc8,0xc0,0x00,0xd0,0xef,0x98,0xd0,0x03,0x4c,0xe7,0x00,0x18,
|
|
0x65,0x4b,0x85,0x4b,0x90,0x02,0xe6,0x4c,0xa9,0x01,0x06,0xee,0xd0,0x03,0x20,0xef,
|
|
0x00,0xb0,0x17,0x06,0xee,0xd0,0x03,0x20,0xef,0x00,0x90,0x0c,0x06,0xee,0xd0,0x03,
|
|
0x20,0xef,0x00,0x2a,0x90,0xed,0xb0,0x7f,0x69,0x01,0x85,0xdd,0xa9,0x20,0x2a,0x06,
|
|
0xee,0xd0,0x03,0x20,0xef,0x00,0x2a,0x90,0xf6,0xa8,0xb9,0x08,0x01,0xf0,0x10,0x06,
|
|
0xee,0xd0,0x07,0x84,0xa2,0x20,0xef,0x00,0xa0,0x00,0x2a,0x90,0xf2,0x30,0x1f,0x85,
|
|
0xc0,0xbd,0x00,0x00,0xe8,0xd0,0x03,0x20,0xf9,0x00,0x79,0x10,0x01,0xb0,0x03,0xc6,
|
|
0xc0,0x38,0x65,0x4b,0x85,0xd8,0xa9,0x00,0x79,0x18,0x01,0x38,0xb0,0x09,0x79,0x10,
|
|
0x01,0x65,0x4b,0x85,0xd8,0xa9,0xff,0x65,0x4c,0x85,0xd9,0xa0,0xff,0xc8,0xb9,0x00,
|
|
0x10,0x91,0x4b,0xc0,0xff,0xd0,0xf6,0x98,0x65,0x4b,0x85,0x4b,0x90,0x02,0xe6,0x4c,
|
|
0x06,0xee,0x4c,0x24,0x00,0x00,0xbc,0x00,0x00,0x84,0xee,0x26,0xee,0xe8,0xd0,0x06,
|
|
0xe6,0xf1,0xe6,0x43,0xe6,0xac,0x60,0xc6,0x01,0x58,0x85,0x98,0x4c
|
|
};
|
|
|
|
// Some definitions for compiler independence
|
|
#ifdef _MSC_VER
|
|
# include <malloc.h>
|
|
# include <io.h>
|
|
#else
|
|
# include <sys/stat.h>
|
|
#endif
|
|
|
|
#undef min
|
|
#undef max
|
|
#define remainder remainder_
|
|
|
|
// The main crunching structure
|
|
typedef struct {
|
|
signed short match_length;
|
|
unsigned short match_offset;
|
|
|
|
union {
|
|
signed hash_link;
|
|
unsigned cumulative_cost;
|
|
};
|
|
} lz_info;
|
|
|
|
typedef struct {
|
|
unsigned char *src_data;
|
|
unsigned src_begin;
|
|
unsigned src_end;
|
|
unsigned output_begin;
|
|
unsigned output_end;
|
|
bool full_dict;
|
|
signed margin;
|
|
|
|
FILE *dst_file;
|
|
unsigned dst_bits;
|
|
unsigned dst_used;
|
|
|
|
unsigned file_size;
|
|
|
|
lz_info *info;
|
|
|
|
signed hash_table[0x100];
|
|
//be graceful, only two max literals is a bit low, as tere can be uncompressibel data rows even bigger than that
|
|
unsigned char dst_literals[65536];
|
|
unsigned output_type;
|
|
unsigned end_pos;
|
|
unsigned header_size;
|
|
signed load_addr;
|
|
signed depack_to;
|
|
unsigned cut_addr_first;
|
|
unsigned cut_addr_last;
|
|
signed start_addr;
|
|
signed relocate_to;
|
|
|
|
const char *input_name;
|
|
char *output_name;
|
|
const char *emit_offset_tables;
|
|
unsigned iterations;
|
|
|
|
bool offset_lengths;
|
|
bool write_tables;
|
|
bool show_stats;
|
|
bool is_cbm;
|
|
bool show_trace;
|
|
bool overlap;
|
|
bool output;
|
|
bool checksum;
|
|
bool exit_on_warn;
|
|
|
|
// Some informational counters
|
|
struct {
|
|
unsigned output_size;
|
|
unsigned short_freq[4];
|
|
unsigned long_freq[4];
|
|
unsigned literal_bytes;
|
|
unsigned literal_runs;
|
|
unsigned match_bytes;
|
|
unsigned match_count;
|
|
unsigned offset_distance;
|
|
unsigned max_offset;
|
|
unsigned len_freq[259];
|
|
unsigned llen_freq[259];
|
|
} stats;
|
|
} lz_context;
|
|
|
|
// A bit of global configuration data
|
|
typedef struct {
|
|
unsigned bits;
|
|
unsigned base;
|
|
signed limit;
|
|
} offset_length_t;
|
|
|
|
static offset_length_t cfg_short_offset[4];
|
|
static offset_length_t cfg_long_offset[4];
|
|
#define cfg_short_limit (cfg_short_offset[3].limit)
|
|
#define cfg_long_limit (cfg_long_offset[3].limit)
|
|
|
|
/******************************************************************************
|
|
* Various utility functions and bithacks
|
|
******************************************************************************/
|
|
#define countof(n) (sizeof(n) / sizeof *(n))
|
|
|
|
unsigned _log2(unsigned value) {
|
|
# ifdef __GNUC__
|
|
enum { WORD_BITS = sizeof(unsigned) * CHAR_BIT };
|
|
|
|
return (WORD_BITS - 1) ^ __builtin_clz(value);
|
|
# else
|
|
signed bits = -1;
|
|
|
|
do
|
|
++bits;
|
|
while(value >>= 1);
|
|
|
|
return bits;
|
|
# endif
|
|
}
|
|
|
|
bool wraps(unsigned cursor, unsigned length, unsigned limit) {
|
|
return ((cursor + length) ^ cursor) >= limit;
|
|
}
|
|
|
|
unsigned remainder(signed cursor, signed window) {
|
|
return -(cursor | -window);
|
|
}
|
|
|
|
unsigned min(unsigned a, unsigned b) {
|
|
return (a < b) ? a : b;
|
|
}
|
|
|
|
unsigned max(unsigned a, unsigned b) {
|
|
return (a < b) ? b : a;
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
__declspec(noreturn)
|
|
#elif defined(__GNUC__)
|
|
__attribute__((noreturn))
|
|
__attribute__((format(printf, 1, 2)))
|
|
#endif
|
|
static void
|
|
#ifdef _MSC_VER
|
|
__cdecl
|
|
#endif
|
|
|
|
fatal(const char *format, ...) {
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
fputs("error: ", stderr);
|
|
vfprintf(stderr, format, args);
|
|
fputc('\n', stderr);
|
|
va_end(args);
|
|
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Manage the output stream
|
|
******************************************************************************/
|
|
void _output_doflush(lz_context *ctx) {
|
|
putc(ctx->dst_bits, ctx->dst_file);
|
|
fwrite(ctx->dst_literals, ctx->dst_used, 1, ctx->dst_file);
|
|
|
|
ctx->dst_bits = 1;
|
|
ctx->dst_used = 0;
|
|
}
|
|
|
|
void output_open(lz_context *ctx, const char *name) {
|
|
if(ctx->dst_file = fopen(name, "wb"), !ctx->dst_file)
|
|
fatal("cannot create '%s'", name);
|
|
|
|
ctx->dst_bits = 1;
|
|
ctx->dst_used = 0;
|
|
}
|
|
|
|
void output_close(lz_context *ctx) {
|
|
if(ctx->dst_bits != 1) {
|
|
while(ctx->dst_bits < 0x100)
|
|
ctx->dst_bits <<= 1;
|
|
|
|
putc(ctx->dst_bits, ctx->dst_file);
|
|
}
|
|
|
|
fwrite(ctx->dst_literals, ctx->dst_used, 1, ctx->dst_file);
|
|
ctx->stats.output_size = ftell(ctx->dst_file);
|
|
fclose(ctx->dst_file);
|
|
}
|
|
|
|
void output_bit(lz_context *ctx, unsigned bit) {
|
|
ctx->file_size++;
|
|
if (ctx->output) {
|
|
if(ctx->dst_bits >= 0x100)
|
|
_output_doflush(ctx);
|
|
|
|
if (ctx->show_trace) printf("%d", bit & 1);
|
|
ctx->dst_bits <<= 1;
|
|
ctx->dst_bits += bit & 1;
|
|
}
|
|
}
|
|
|
|
void output_literal(lz_context *ctx, unsigned value) {
|
|
ctx->file_size += 8;
|
|
if (ctx->output) {
|
|
ctx->dst_literals[ctx->dst_used++] = value;
|
|
}
|
|
}
|
|
|
|
unsigned output_bitsize(lz_context *ctx) {
|
|
unsigned total;
|
|
unsigned bitbuffer;
|
|
|
|
total = ftell(ctx->dst_file);
|
|
total += ctx->dst_used;
|
|
total <<= 3;
|
|
|
|
for(bitbuffer = ctx->dst_bits; bitbuffer > 1; bitbuffer >>= 1)
|
|
++total;
|
|
|
|
return total;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Read file into memory and allocate per-byte buffers
|
|
******************************************************************************/
|
|
void read_input(lz_context *ctx, const char *name, bool is_cbm) {
|
|
FILE *file;
|
|
signed length;
|
|
unsigned origin;
|
|
|
|
if(file = fopen(name, "rb"), !file)
|
|
fatal("unable to open '%s'", name);
|
|
|
|
# ifdef _MSC_VER
|
|
length = _filelength(_fileno(file));
|
|
# else
|
|
{
|
|
struct stat stat;
|
|
stat.st_size = 0;
|
|
fstat(fileno(file), &stat);
|
|
length = stat.st_size;
|
|
}
|
|
# endif
|
|
|
|
if(length <= 0)
|
|
fatal("cannot determine length of '%s'", name);
|
|
|
|
{
|
|
// Give us a sentinel for the info structure and prevent two-byte
|
|
// hashing from overrunning the buffer
|
|
unsigned count = length + 1;
|
|
|
|
ctx->info = malloc(count *
|
|
(sizeof *ctx->info + sizeof *ctx->src_data));
|
|
ctx->src_data = (void *) &ctx->info[count];
|
|
|
|
if(!ctx->info)
|
|
fatal("cannot allocate memory buffer");
|
|
|
|
if(fread(ctx->src_data, length, 1, file) != 1)
|
|
fatal("cannot read '%s'", name);
|
|
}
|
|
|
|
// Deal with the PRG file header. We don't write the loading
|
|
// address back out to compressed file, however we *do* need to
|
|
// consider the decompression address when deciding whether a
|
|
// run crosses a page or window boundary
|
|
origin = 0;
|
|
|
|
if(is_cbm) {
|
|
length -= 2;
|
|
|
|
if(length < 0) {
|
|
fatal("CBM .prg file is too short to "
|
|
"fit a 2-byte load address");
|
|
}
|
|
|
|
origin = *ctx->src_data++;
|
|
origin += *ctx->src_data++ << 8;
|
|
}
|
|
|
|
ctx->info -= origin;
|
|
ctx->src_data -= origin;
|
|
ctx->src_begin = origin;
|
|
ctx->src_end = origin + length;
|
|
}
|
|
|
|
void cut_input_addr(lz_context *ctx, unsigned first, unsigned last, bool full_dict) {
|
|
ctx->output_begin = max(ctx->src_begin, first);
|
|
ctx->output_end = min(ctx->src_end, last);
|
|
//if dict shall be restricted to current range only, src end/begin is set to output end/begin
|
|
if (!full_dict) {
|
|
ctx->src_begin = ctx->output_begin;
|
|
ctx->src_end = ctx->output_end;
|
|
}
|
|
if(ctx->src_begin > ctx->src_end)
|
|
fatal("no data in address range $%04x $%04x", ctx->src_begin, ctx->src_end);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Try to figure out what matches would be the most beneficial
|
|
******************************************************************************/
|
|
|
|
unsigned costof_run_8class(unsigned run) {
|
|
return _log2(run) * 2 + 1;
|
|
}
|
|
|
|
unsigned costof_run(unsigned run) {
|
|
unsigned bits;
|
|
run--;
|
|
if (run == 0) return 1;
|
|
if (run == 1) return 2;
|
|
bits = _log2(run);
|
|
return bits * 2 + 2;
|
|
//return _log2(run) * 2 + 1;
|
|
}
|
|
|
|
unsigned costof_literals_8class(unsigned address, unsigned length) {
|
|
unsigned cost;
|
|
|
|
cost = length * 8;
|
|
cost += costof_run_8class(length);
|
|
|
|
// A type bit is still always needed after maximum length
|
|
// run since another run may follow
|
|
if(length == RUN_LIMIT) cost++;
|
|
return cost;
|
|
}
|
|
|
|
unsigned costof_literals(unsigned address, unsigned length) {
|
|
unsigned cost;
|
|
|
|
cost = length * 8;
|
|
cost += _log2(length) * 2 + 1; //costof_run(length);
|
|
|
|
// A type bit is still always needed after maximum length
|
|
// run since another run may follow
|
|
if(length == RUN_LIMIT) cost++;
|
|
return cost;
|
|
}
|
|
|
|
unsigned costof_match_8class(const offset_length_t *class, signed offset, unsigned length) {
|
|
unsigned cost = 3;
|
|
|
|
while(offset > class->limit)
|
|
++class;
|
|
cost += class->bits;
|
|
|
|
return cost + costof_run_8class(length - 1);
|
|
}
|
|
|
|
unsigned costof_match(const offset_length_t *class, signed offset, unsigned length) {
|
|
unsigned cost = 3;
|
|
|
|
while(offset > class->limit)
|
|
++class;
|
|
cost += class->bits;
|
|
|
|
return cost + costof_run(length - 1);
|
|
}
|
|
|
|
lz_info optimal_parsing_literal(lz_context *ctx, const lz_info *info, unsigned cursor) {
|
|
signed length;
|
|
unsigned cost;
|
|
lz_info result;
|
|
|
|
length = -info[cursor + 1].match_length;
|
|
|
|
if(length > 0 && length < RUN_LIMIT)
|
|
cost = info[cursor + ++length].cumulative_cost;
|
|
else {
|
|
cost = info[cursor + 1].cumulative_cost;
|
|
length = 1;
|
|
}
|
|
|
|
if (ctx->output_type == OUTPUT_BITFIRE) {
|
|
cost += costof_literals_8class(cursor, length);
|
|
} else {
|
|
cost += costof_literals(cursor, length);
|
|
}
|
|
|
|
result.match_length = -length;
|
|
result.cumulative_cost = cost;
|
|
|
|
return result;
|
|
}
|
|
|
|
lz_info optimal_parsing (lz_context *ctx, const lz_info *info, unsigned cursor, signed match_offset, unsigned match_length, unsigned match_limit, lz_info best_match) {
|
|
unsigned cost;
|
|
|
|
if(match_length == 2) {
|
|
if(match_offset <= cfg_short_limit) {
|
|
if (ctx->output_type == OUTPUT_BITFIRE) {
|
|
cost = costof_match_8class(cfg_short_offset, match_offset, match_length);
|
|
} else {
|
|
cost = costof_match(cfg_short_offset, match_offset, match_length);
|
|
}
|
|
goto try_short_match;
|
|
} else if(++match_length > match_limit)
|
|
return best_match;
|
|
}
|
|
|
|
do {
|
|
if (ctx->output_type == OUTPUT_BITFIRE) {
|
|
cost = costof_match_8class(cfg_long_offset, match_offset, match_length);
|
|
} else {
|
|
cost = costof_match(cfg_long_offset, match_offset, match_length);
|
|
}
|
|
try_short_match:
|
|
cost += info[cursor + match_length].cumulative_cost;
|
|
|
|
if(cost < best_match.cumulative_cost) {
|
|
best_match.match_offset = match_offset;
|
|
best_match.match_length = match_length;
|
|
best_match.cumulative_cost = cost;
|
|
}
|
|
} while(++match_length <= match_limit);
|
|
|
|
return best_match;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Determine the longest match for every position of the file
|
|
******************************************************************************/
|
|
signed *hashof(lz_context *ctx, unsigned a, unsigned b) {
|
|
static const unsigned char random[] = {
|
|
0x17, 0x80, 0x95, 0x4f, 0xc7, 0xd1, 0x15, 0x13,
|
|
0x91, 0x57, 0x0f, 0x47, 0xd0, 0x59, 0xab, 0xf0,
|
|
0xa7, 0xf5, 0x36, 0xc0, 0x24, 0x9c, 0xed, 0xfd,
|
|
0xd4, 0xf3, 0x51, 0xb4, 0x8c, 0x97, 0xa3, 0x58,
|
|
0xcb, 0x61, 0x78, 0xb1, 0x3e, 0x7e, 0xfb, 0x41,
|
|
0x39, 0xa6, 0x8e, 0x10, 0xa1, 0xba, 0x62, 0xcd,
|
|
0x94, 0x02, 0x0d, 0x2b, 0xdb, 0xd7, 0x44, 0x16,
|
|
0x29, 0x4d, 0x68, 0x0a, 0x6b, 0x6c, 0xa2, 0xf8,
|
|
0xc8, 0x9f, 0x25, 0xca, 0xbd, 0x4a, 0xc2, 0x35,
|
|
0x53, 0x1c, 0x40, 0x04, 0x76, 0x43, 0xa9, 0xbc,
|
|
0x46, 0xeb, 0x99, 0xe9, 0xf6, 0x5e, 0x8f, 0x8a,
|
|
0xf1, 0x5d, 0x21, 0x33, 0x0b, 0x82, 0xdf, 0x52,
|
|
0xea, 0x27, 0x22, 0x9a, 0x6f, 0xad, 0xe5, 0x83,
|
|
0x11, 0xbe, 0xa4, 0x85, 0x1d, 0xb3, 0x77, 0xf4,
|
|
0xef, 0xb7, 0xf2, 0x03, 0x64, 0x6d, 0x1b, 0xee,
|
|
0x72, 0x08, 0x66, 0xc6, 0xc1, 0x06, 0x56, 0x81,
|
|
0x55, 0x60, 0x70, 0x8d, 0x23, 0xb2, 0x65, 0x5b,
|
|
0xff, 0x4c, 0xb9, 0x7a, 0xd6, 0xe6, 0x19, 0x9b,
|
|
0xb5, 0x49, 0x7d, 0xd8, 0x45, 0x1a, 0x84, 0x32,
|
|
0xdd, 0xbf, 0x9e, 0x2f, 0xd2, 0xec, 0x92, 0x0e,
|
|
0xe8, 0x7c, 0x7f, 0x00, 0x86, 0xde, 0xb6, 0xcf,
|
|
0x05, 0x69, 0xd5, 0x37, 0xe4, 0x30, 0x3c, 0xe1,
|
|
0x4b, 0xaa, 0x3b, 0x2d, 0xda, 0x5c, 0xcc, 0x67,
|
|
0x20, 0xb0, 0x6a, 0x1f, 0xf9, 0x01, 0xac, 0x2e,
|
|
0x71, 0xf7, 0xfc, 0x3f, 0x42, 0xd3, 0xbb, 0xa8,
|
|
0x38, 0xce, 0x12, 0x96, 0xe2, 0x14, 0x87, 0x4e,
|
|
0x63, 0x07, 0xae, 0xdc, 0xa5, 0xc9, 0x0c, 0x90,
|
|
0xe7, 0xd9, 0x09, 0x2a, 0xc4, 0x3d, 0x5a, 0x34,
|
|
0x8b, 0x88, 0x98, 0x48, 0xfa, 0xc3, 0x26, 0x75,
|
|
0xfe, 0xa0, 0x7b, 0x50, 0x2c, 0x89, 0x18, 0x9d,
|
|
0x3a, 0x73, 0x6e, 0x5f, 0xc5, 0xaf, 0xb8, 0x74,
|
|
0x93, 0xe3, 0x79, 0x28, 0xe0, 0x1e, 0x54, 0x31
|
|
};
|
|
|
|
size_t bucket = random[a] ^ b;
|
|
return &ctx->hash_table[bucket];
|
|
}
|
|
|
|
void generate_hash_table(lz_context *ctx) {
|
|
unsigned cursor;
|
|
|
|
const unsigned src_end = ctx->src_end;
|
|
const unsigned char *src_data = ctx->src_data;
|
|
lz_info *info = ctx->info;
|
|
|
|
for(cursor = 0; cursor < countof(ctx->hash_table); ++cursor)
|
|
ctx->hash_table[cursor] = INT_MIN;
|
|
|
|
for(cursor = ctx->src_begin; cursor != src_end; ++cursor) {
|
|
signed *hash_bucket = hashof (
|
|
ctx,
|
|
src_data[cursor + 0],
|
|
src_data[cursor + 1]
|
|
);
|
|
|
|
info[cursor].hash_link = *hash_bucket;
|
|
*hash_bucket = cursor;
|
|
}
|
|
}
|
|
|
|
unsigned find_matches(lz_context *ctx) {
|
|
const unsigned src_begin = ctx->src_begin;
|
|
const unsigned src_end = ctx->src_end;
|
|
const unsigned char *src_data = ctx->src_data;
|
|
lz_info *info = ctx->info;
|
|
|
|
unsigned offset_limit = min(INFINITE_WINDOW, cfg_long_limit);
|
|
unsigned cursor = ctx->src_end;
|
|
|
|
info[cursor].cumulative_cost = 0;
|
|
|
|
while(cursor != src_begin) {
|
|
unsigned match_length;
|
|
signed cursor_limit;
|
|
unsigned length_limit;
|
|
signed *hash_bucket;
|
|
signed hash_link;
|
|
lz_info best_match;
|
|
|
|
--cursor;
|
|
|
|
match_length = 1;
|
|
cursor_limit = cursor - offset_limit;
|
|
|
|
length_limit = RUN_LIMIT;
|
|
length_limit = min(length_limit, remainder(cursor, INFINITE_WINDOW));
|
|
length_limit = min(length_limit, src_end - cursor);
|
|
|
|
hash_bucket = hashof (
|
|
ctx,
|
|
src_data[cursor + 0],
|
|
src_data[cursor + 1]
|
|
);
|
|
|
|
assert((unsigned) *hash_bucket == cursor);
|
|
hash_link = info[cursor].hash_link;
|
|
*hash_bucket = hash_link;
|
|
|
|
best_match = optimal_parsing_literal(ctx, info, cursor);
|
|
|
|
while(hash_link >= cursor_limit) {
|
|
unsigned match_limit = remainder(hash_link, INFINITE_WINDOW);
|
|
match_limit = min(match_limit, length_limit);
|
|
|
|
if(match_length != match_limit) {
|
|
unsigned i = match_length + 1;
|
|
|
|
if(!memcmp(&src_data[cursor], &src_data[hash_link], i)) {
|
|
for(; i != match_limit; ++i) {
|
|
if(src_data[cursor + i] != src_data[hash_link + i])
|
|
break;
|
|
}
|
|
|
|
assert(i <= match_limit);
|
|
|
|
best_match = optimal_parsing (
|
|
ctx,
|
|
info,
|
|
cursor,
|
|
cursor - hash_link,
|
|
match_length + 1,
|
|
i,
|
|
best_match
|
|
);
|
|
|
|
match_length = i;
|
|
|
|
if(match_length == RUN_LIMIT)
|
|
break;
|
|
}
|
|
}
|
|
|
|
hash_link = info[hash_link].hash_link;
|
|
}
|
|
|
|
info[cursor] = best_match;
|
|
}
|
|
return info[src_begin].cumulative_cost;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Write the generated matches and literal runs
|
|
******************************************************************************/
|
|
|
|
// burps out remaining literals as a plain bnary blob
|
|
void encode_literals_plain (lz_context *ctx, unsigned cursor, unsigned length) {
|
|
const unsigned char *data;
|
|
unsigned start = length;
|
|
|
|
if(ctx->show_trace) {
|
|
printf ("plain literal(%u bytes)\n",length);
|
|
}
|
|
|
|
ctx->stats.literal_bytes += length;
|
|
++ctx->stats.literal_runs;
|
|
++ctx->stats.llen_freq[length];
|
|
|
|
data = &ctx->src_data[cursor];
|
|
do
|
|
output_literal(ctx, data[start - length--]);
|
|
while(length);
|
|
}
|
|
|
|
void encode_literals_8class (lz_context *ctx, unsigned cursor, unsigned length) {
|
|
signed bit;
|
|
const unsigned char *data;
|
|
unsigned start = length;
|
|
|
|
if(ctx->show_trace) {
|
|
printf ("literal(%u bytes)\n",length);
|
|
}
|
|
ctx->stats.literal_bytes += length;
|
|
++ctx->stats.literal_runs;
|
|
++ctx->stats.llen_freq[length];
|
|
|
|
bit = _log2(length);
|
|
while(--bit >= 0) {
|
|
output_bit(ctx, 1);
|
|
output_bit(ctx, length >> bit);
|
|
}
|
|
|
|
output_bit(ctx, 0);
|
|
|
|
data = &ctx->src_data[cursor];
|
|
do
|
|
output_literal(ctx, data[start - length--]);
|
|
while(length);
|
|
}
|
|
|
|
void encode_literals (lz_context *ctx, unsigned cursor, unsigned length) {
|
|
signed bit;
|
|
const unsigned char *data;
|
|
unsigned start = length;
|
|
|
|
ctx->stats.literal_bytes += length;
|
|
++ctx->stats.literal_runs;
|
|
++ctx->stats.llen_freq[length];
|
|
|
|
if(ctx->show_trace) printf ("length: ");
|
|
bit = _log2(length);
|
|
while(--bit >= 0) {
|
|
output_bit(ctx, 1);
|
|
output_bit(ctx, length >> bit);
|
|
if (ctx->show_trace) printf(" ");
|
|
}
|
|
|
|
output_bit(ctx, 0);
|
|
|
|
data = &ctx->src_data[cursor];
|
|
do
|
|
output_literal(ctx, data[start - length--]);
|
|
while(length);
|
|
if (ctx->show_trace) printf("\n");
|
|
}
|
|
|
|
void encode_match_8class (lz_context *ctx, signed offset, unsigned length) {
|
|
unsigned offset_bits;
|
|
unsigned offset_prefix;
|
|
const offset_length_t *offset_class;
|
|
signed length_bit;
|
|
#if BITFIRE_WITH_MOFF
|
|
const unsigned prefix_order[] = {0,1,2,3,7,6,5,4};
|
|
#else
|
|
const unsigned prefix_order[] = {1,2,0,3,4,5,6,7};
|
|
#endif
|
|
|
|
if(ctx->show_trace) {
|
|
printf("match(-%u, %u bytes)\n",offset,length);
|
|
}
|
|
|
|
++ctx->stats.match_count;
|
|
ctx->stats.match_bytes += length;
|
|
ctx->stats.offset_distance += offset;
|
|
|
|
// Write length
|
|
length_bit = _log2(--length) - 1;
|
|
|
|
// Write offset prefix
|
|
if(length == 2 - 1) {
|
|
offset_prefix = 0;
|
|
assert(offset <= cfg_short_limit);
|
|
offset_class = cfg_short_offset;
|
|
|
|
while(offset > offset_class->limit) {
|
|
++offset_class;
|
|
++offset_prefix;
|
|
}
|
|
|
|
offset_prefix = prefix_order[offset_prefix];
|
|
|
|
++ctx->stats.short_freq[offset_prefix & 3];
|
|
} else {
|
|
offset_prefix = 4;
|
|
assert(offset <= cfg_long_limit);
|
|
offset_class = cfg_long_offset;
|
|
|
|
while(offset > offset_class->limit) {
|
|
++offset_class;
|
|
++offset_prefix;
|
|
}
|
|
|
|
offset_prefix = prefix_order[offset_prefix];
|
|
|
|
++ctx->stats.long_freq[offset_prefix & 3];
|
|
}
|
|
|
|
output_bit(ctx, offset_prefix >> 2);
|
|
|
|
while(length_bit >= 0) {
|
|
output_bit(ctx, length >> length_bit);
|
|
output_bit(ctx, --length_bit < 0);
|
|
}
|
|
|
|
output_bit(ctx, offset_prefix >> 1);
|
|
output_bit(ctx, offset_prefix >> 0);
|
|
|
|
// Write offset payload
|
|
offset -= offset_class->base;
|
|
|
|
offset_bits = offset_class->bits;
|
|
if (offset_bits > 7) {
|
|
while(offset_bits & 7) output_bit(ctx, offset >> --offset_bits);
|
|
} else {
|
|
while(offset_bits & 7) output_bit(ctx, ~offset >> --offset_bits);
|
|
}
|
|
if(offset_bits) output_literal(ctx, ~offset);
|
|
++ctx->stats.len_freq[length + 2];
|
|
}
|
|
|
|
void encode_match (lz_context *ctx, signed offset, unsigned length) {
|
|
unsigned offset_bits;
|
|
unsigned offset_prefix;
|
|
const offset_length_t *offset_class;
|
|
signed length_bit;
|
|
unsigned init_bit;
|
|
|
|
if (offset == 0) length = RUN_LIMIT + 2;
|
|
++ctx->stats.match_count;
|
|
ctx->stats.match_bytes += length;
|
|
ctx->stats.offset_distance += offset;
|
|
if (ctx->stats.max_offset < offset) ctx->stats.max_offset = offset;
|
|
|
|
if(ctx->show_trace) printf ("length: ");
|
|
// Write initial length bit
|
|
length_bit = _log2(--length);
|
|
init_bit = --length_bit < 0;
|
|
output_bit(ctx, init_bit);
|
|
|
|
length--;
|
|
|
|
//nothing to do if length == 1, as the only needed bit is already written
|
|
if (length == 0) {
|
|
} else if (length == 1) {
|
|
//special case, no additional bits will follow after 0 as payload is 0
|
|
output_bit(ctx, 0);
|
|
//now handle lengths > 2
|
|
} else {
|
|
output_bit(ctx, 1);
|
|
length_bit = _log2(length);
|
|
while(--length_bit >= 0) {
|
|
if (ctx->show_trace) printf(" ");
|
|
output_bit(ctx, length >> length_bit);
|
|
//if (ctx->show_trace) printf(" ");
|
|
if (length >= 255) {
|
|
//skip stop bit if number has 8 bits
|
|
if (length_bit != 0) {
|
|
output_bit(ctx, length_bit != 0);
|
|
} else {
|
|
if (ctx->show_trace) printf("s");
|
|
}
|
|
} else {
|
|
output_bit(ctx, length_bit != 0);
|
|
}
|
|
}
|
|
}
|
|
if (ctx->show_trace) printf("\n");
|
|
|
|
if (length >= RUN_LIMIT) return;
|
|
|
|
// Write offset prefix
|
|
if(length == 0) {
|
|
assert(offset <= cfg_short_limit);
|
|
offset_prefix = 0;
|
|
offset_class = cfg_short_offset;
|
|
|
|
while(offset > offset_class->limit) {
|
|
++offset_class;
|
|
++offset_prefix;
|
|
}
|
|
|
|
++ctx->stats.short_freq[offset_prefix];
|
|
++ctx->stats.len_freq[length + 2];
|
|
|
|
// Note that the encoding for short matches is reversed relative to
|
|
// the long ones in order to expose holes in the decruncher's offset
|
|
// tables
|
|
offset_prefix = ~offset_prefix;
|
|
} else {
|
|
assert(offset <= cfg_long_limit);
|
|
offset_prefix = 0;
|
|
offset_class = cfg_long_offset;
|
|
|
|
while(offset > offset_class->limit) {
|
|
++offset_class;
|
|
++offset_prefix;
|
|
}
|
|
|
|
++ctx->stats.long_freq[offset_prefix];
|
|
++ctx->stats.len_freq[length + 2];
|
|
}
|
|
|
|
if(ctx->show_trace) printf("offset: (%d)", init_bit);
|
|
output_bit(ctx, offset_prefix >> 1);
|
|
output_bit(ctx, offset_prefix >> 0);
|
|
if(ctx->show_trace) printf("-");
|
|
|
|
// Write offset payload
|
|
offset -= offset_class->base;
|
|
offset = ~offset;
|
|
|
|
offset_bits = offset_class->bits;
|
|
while(offset_bits & 7)
|
|
output_bit(ctx, offset >> --offset_bits);
|
|
|
|
if(offset_bits) {
|
|
output_literal(ctx, offset);
|
|
if (ctx->show_trace) printf("$%02x", offset & 0xff);
|
|
}
|
|
|
|
if (ctx->show_trace) printf("\n");
|
|
}
|
|
|
|
void render_output(lz_context *ctx) {
|
|
unsigned cursor;
|
|
|
|
bool implicit_match = false;
|
|
|
|
// unsigned output_end = ctx->output_end;
|
|
signed dest_pos;
|
|
signed stream_pos;
|
|
|
|
lz_info *info = ctx->info;
|
|
signed length;
|
|
signed length_bit;
|
|
signed packed_size;
|
|
signed last_match;
|
|
|
|
bool update = true;
|
|
|
|
bool sentinel_needed = false;
|
|
|
|
ctx->margin = 0;
|
|
ctx->end_pos = 0;
|
|
|
|
packed_size = ctx->file_size;
|
|
last_match = 0;
|
|
|
|
ctx->file_size = 0;
|
|
length = 0;
|
|
|
|
for(cursor = ctx->output_begin; cursor < ctx->output_end; cursor += length) {
|
|
length = info[cursor].match_length;
|
|
|
|
//check for normal overlap
|
|
if (ctx->output) {
|
|
//save pointer before new match/literal is written onto stream
|
|
stream_pos = (packed_size + 7) / 8 - (ctx->file_size + 7) / 8;
|
|
//distance to end from current destination
|
|
dest_pos = ctx->output_end - cursor;
|
|
if(stream_pos > dest_pos && stream_pos - dest_pos > ctx->margin) {
|
|
ctx->margin = stream_pos - dest_pos;
|
|
}
|
|
//only allow unencoded last literal if zero overlap and if load_addr && depack_to is yet unset (--load-addr/--dpeack-to not set)
|
|
if(ctx->load_addr < 0 && ctx->depack_to < 0 && !ctx->overlap && stream_pos > dest_pos) {
|
|
if(update) {
|
|
//last sane position, now switch to literal output only
|
|
ctx->end_pos = cursor;
|
|
update = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(length > 0) {
|
|
|
|
unsigned offset = info[cursor].match_offset;
|
|
sentinel_needed = false;
|
|
|
|
if(!implicit_match) {
|
|
if(ctx->show_trace) printf ("type_bit ");
|
|
if(update) output_bit(ctx, 0);
|
|
if(ctx->show_trace) printf ("\n");
|
|
}
|
|
|
|
if (ctx->output_type == OUTPUT_BITFIRE) {
|
|
if (!update) {
|
|
encode_literals_plain(ctx, cursor, length);
|
|
} else {
|
|
encode_match_8class(ctx, offset, length);
|
|
}
|
|
} else {
|
|
encode_match(ctx, offset, length);
|
|
}
|
|
if(ctx->show_trace) printf ("\n");
|
|
|
|
implicit_match = false;
|
|
last_match = cursor + length;
|
|
} else {
|
|
sentinel_needed = true;
|
|
length = -length;
|
|
|
|
if(ctx->show_trace) printf ("type_bit ");
|
|
if(update) output_bit(ctx, 1);
|
|
if(ctx->show_trace) printf ("\n");
|
|
|
|
// Normally a match implicitly follows a literal run except for the
|
|
// case of a maximum length literal run
|
|
implicit_match = length < RUN_LIMIT;
|
|
|
|
// The parser may generate a short run followed by one or more maximum
|
|
// length runs for split literals. This needs to be avoided manually
|
|
// by reversing the order
|
|
if(implicit_match) {
|
|
signed next_length = -info[cursor + length].match_length;
|
|
//is the next run a literal too? is the length of the current literal smaller then the run limit and is the next element a literal of maximum runlength? If so, swap both, and if things continue, do so also on upcoming turns.
|
|
if(next_length > 0 && next_length == RUN_LIMIT && length < RUN_LIMIT) {
|
|
//if(next_length > 0) {
|
|
info[cursor].match_length = -next_length;
|
|
info[cursor + next_length].match_length = -length;
|
|
|
|
//check that first element is < RUN_LIMIT if we swap elements
|
|
assert(length < RUN_LIMIT);
|
|
|
|
length = next_length;
|
|
implicit_match = false;
|
|
}
|
|
}
|
|
|
|
if (ctx->output_type == OUTPUT_BITFIRE) {
|
|
if (!update) {
|
|
encode_literals_plain(ctx, cursor, length);
|
|
} else {
|
|
encode_literals_8class(ctx, cursor, length);
|
|
}
|
|
}
|
|
else {
|
|
encode_literals(ctx, cursor, length);
|
|
}
|
|
if(ctx->show_trace) printf ("\n");
|
|
}
|
|
}
|
|
|
|
// If still no end_position is determined, do it now
|
|
if (update) ctx->end_pos = last_match;
|
|
|
|
# if VERIFY_COST_MODEL
|
|
{
|
|
unsigned expected = info[ctx->output_begin].cumulative_cost;
|
|
unsigned actual = ctx->file_size;
|
|
|
|
if(expected != actual) {
|
|
printf (
|
|
"expected: %u\n"
|
|
"actual: %u\n",
|
|
expected,
|
|
actual
|
|
);
|
|
}
|
|
}
|
|
# endif
|
|
|
|
//check for normal overlap
|
|
if (ctx->output) {
|
|
//save pointer before new match/literal is written onto stream
|
|
stream_pos = (packed_size + 7) / 8 - (ctx->file_size + 7) / 8;
|
|
//distance to end from current destination
|
|
dest_pos = ctx->output_end - cursor;
|
|
if(stream_pos > dest_pos && stream_pos - dest_pos > ctx->margin) {
|
|
ctx->margin = stream_pos - dest_pos;
|
|
}
|
|
}
|
|
|
|
// The sentinel is a maximum-length match
|
|
if(ctx->show_trace) printf("EOF\n");
|
|
|
|
// We encode the EOF whenever we do compress with overlap or to an alternative location where in place depacking can't happen
|
|
if(ctx->overlap || ctx->load_addr >= 0 || ctx->depack_to >= 0) {
|
|
// In that case take end of data as end_pos to disable the end_pos check by letting it hit too late during decoding
|
|
// XXX TODO can still be omitted if last action was a match
|
|
ctx->end_pos = ctx->output_end;
|
|
|
|
// only add a sentinael if last action was a literal
|
|
if (sentinel_needed) {
|
|
if(!implicit_match) {
|
|
if(ctx->show_trace) printf ("type_bit ");
|
|
output_bit(ctx, 0);
|
|
if(ctx->show_trace) printf ("\n");
|
|
}
|
|
|
|
// printf("%d bits saved\n", saved);
|
|
|
|
if (ctx->output_type == OUTPUT_BITFIRE) {
|
|
length_bit = _log2(RUN_LIMIT);
|
|
output_bit(ctx, --length_bit >= 0);
|
|
|
|
while(length_bit >= 0) {
|
|
output_bit(ctx, RUN_LIMIT >> length_bit);
|
|
output_bit(ctx, --length_bit < 0);
|
|
}
|
|
} else {
|
|
encode_match(ctx, 1, RUN_LIMIT+3);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Parse out the set of offset bit lengths from a descriptor string
|
|
******************************************************************************/
|
|
|
|
//XXX TODO basically the same, except table->base = 0 and base is unused?
|
|
static void prepare_offset_lengths_8class(offset_length_t *table, size_t count) {
|
|
unsigned limit = 0;
|
|
unsigned previous = 0;
|
|
|
|
do {
|
|
unsigned int bits = table->bits;
|
|
|
|
if(bits <= previous)
|
|
fatal("offset lengths must be listed in ascending order");
|
|
previous = bits;
|
|
if(bits > OFFSET_LENGTH_LIMIT)
|
|
fatal("offset lengths cannot be wider than %u bits", OFFSET_LENGTH_LIMIT);
|
|
|
|
limit = 1 << bits;
|
|
table->base = 1;
|
|
table->limit = limit;
|
|
++table;
|
|
} while(--count);
|
|
}
|
|
|
|
static void prepare_offset_lengths(offset_length_t *table, size_t count) {
|
|
unsigned base;
|
|
unsigned limit = 0;
|
|
unsigned previous = 0;
|
|
|
|
do {
|
|
unsigned int bits = table->bits;
|
|
|
|
if(bits <= previous)
|
|
fatal("offset lengths must be listed in ascending order");
|
|
previous = bits;
|
|
if(bits > OFFSET_LENGTH_LIMIT)
|
|
fatal("offset lengths cannot be wider than %u bits", OFFSET_LENGTH_LIMIT);
|
|
|
|
base = limit + 1;
|
|
limit += 1 << bits;
|
|
table->base = base;
|
|
table->limit = limit;
|
|
++table;
|
|
} while(--count);
|
|
}
|
|
|
|
bool parse_offset_lengths(lz_context* ctx, const char *text) {
|
|
if(sscanf(text, "%u/%u/%u/%u:%u/%u/%u/%u",
|
|
&cfg_short_offset[0].bits, &cfg_short_offset[1].bits,
|
|
&cfg_short_offset[2].bits, &cfg_short_offset[3].bits,
|
|
&cfg_long_offset[0].bits, &cfg_long_offset[1].bits,
|
|
&cfg_long_offset[2].bits, &cfg_long_offset[3].bits) != 8) {
|
|
return false;
|
|
}
|
|
if (ctx->output_type == OUTPUT_BITFIRE && !BITFIRE_WITH_MOFF) {
|
|
prepare_offset_lengths_8class(cfg_short_offset, 4);
|
|
prepare_offset_lengths_8class(cfg_long_offset, 4);
|
|
} else {
|
|
prepare_offset_lengths(cfg_short_offset, 4);
|
|
prepare_offset_lengths(cfg_long_offset, 4);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Generate decruncher the tables corresponding to a particular offset length
|
|
* sequence.
|
|
* These are admittedly rather convoluted and tied tightly to how matches are
|
|
* handled in the implementation. See the source for details
|
|
******************************************************************************/
|
|
|
|
static char single_offset (const offset_length_t *class, unsigned shift) {
|
|
signed offset = class->base + 1;
|
|
if(class->bits > 8)
|
|
offset += 0x8000;
|
|
else if(class->bits == 8)
|
|
offset += 0x0100;
|
|
offset = -offset;
|
|
offset >>= shift;
|
|
offset &= 0xFF;
|
|
|
|
return offset;
|
|
}
|
|
|
|
static void write_single_offset (FILE *file, const offset_length_t *class, unsigned shift) {
|
|
char binary[9];
|
|
|
|
signed offset = class->base + 1;
|
|
if(class->bits > 8)
|
|
offset += 0x8000;
|
|
else if(class->bits == 8)
|
|
offset += 0x0100;
|
|
offset = -offset;
|
|
offset >>= shift;
|
|
offset &= 0xFF;
|
|
|
|
{
|
|
char *digit = &binary[8];
|
|
*digit = '\0';
|
|
do {
|
|
*--digit = (offset & 1)["01"];
|
|
offset >>= 1;
|
|
} while(digit != binary);
|
|
}
|
|
|
|
fprintf (
|
|
file,
|
|
"%s\t\t.byte %%%s\t\t;%u-%u%s\n",
|
|
class->bits < shift ? ";" : "",
|
|
binary,
|
|
class->base,
|
|
class->limit,
|
|
class->bits < shift ? " (unreferenced)" : ""
|
|
);
|
|
}
|
|
|
|
void write_offsets(FILE* file) {
|
|
static const char length_codes[] = {
|
|
0,
|
|
0xff,
|
|
0x7f,
|
|
0x3f,
|
|
0x1f,
|
|
0x0f,
|
|
0x07,
|
|
0x03,
|
|
0,
|
|
0xbf,
|
|
0x5f,
|
|
0x2f,
|
|
0x17,
|
|
0x0b,
|
|
0x05,
|
|
0x02
|
|
};
|
|
|
|
ptrdiff_t i;
|
|
|
|
for(i = 0; i <= 3; ++i) {
|
|
unsigned bits = cfg_long_offset[i].bits;
|
|
fputc(length_codes[bits], file);
|
|
}
|
|
for(i = 3; i >= 0; --i) {
|
|
unsigned bits = cfg_short_offset[i].bits;
|
|
fputc(length_codes[bits], file);
|
|
}
|
|
for(i = 0; i <= 3; ++i)
|
|
fputc(single_offset(&cfg_long_offset[i], 0), file);
|
|
for(i = 3; i >= 0; --i)
|
|
fputc(single_offset(&cfg_short_offset[i], 0), file);
|
|
// MSB of base offsets
|
|
for(i = 0; i <= 3; ++i)
|
|
fputc(single_offset(&cfg_long_offset[i], 8), file);
|
|
for(i = 3; i >= 0; --i)
|
|
fputc(single_offset(&cfg_short_offset[i], 8), file);
|
|
}
|
|
|
|
void write_offset_tables(const char *name) {
|
|
static const char long_title[] = "\t\t;Long (>2 byte matches)\n";
|
|
static const char short_title[] = "\t\t;Short (2 byte matches)\n";
|
|
|
|
static const char *const length_codes[] = {
|
|
NULL,
|
|
"11111111", // 1-bits
|
|
"01111111", // 2-bits
|
|
"00111111", // 3-bits
|
|
"00011111", // 4-bits
|
|
"00001111", // 5-bits
|
|
"00000111", // 6-bits
|
|
"00000011", // 7-bits
|
|
"00000000", // 8-bits: This needs a bit of special-processing
|
|
"10111111", // 9-bits
|
|
"01011111", // 10-bits
|
|
"00101111", // 11-bits
|
|
"00010111", // 12-bits
|
|
"00001011", // 13-bits
|
|
"00000101", // 14-bits
|
|
"00000010" // 15-bits
|
|
};
|
|
|
|
ptrdiff_t i;
|
|
unsigned near_longs;
|
|
|
|
// Open the target file
|
|
FILE *file = fopen(name, "w");
|
|
if(!file)
|
|
fatal("cannot create '%s'", name);
|
|
|
|
// Bit lengths
|
|
fprintf(file, "_lz_moff_length\n");
|
|
fprintf(file, long_title);
|
|
near_longs = 0;
|
|
for(i = 0; i <= 3; ++i) {
|
|
unsigned bits = cfg_long_offset[i].bits;
|
|
fprintf(file, "\t\t.byte %%%s\t\t;%u bits\n",
|
|
length_codes[bits], bits);
|
|
if(bits < 8)
|
|
++near_longs;
|
|
}
|
|
fprintf(file, short_title);
|
|
for(i = 3; i >= 0; --i) {
|
|
unsigned bits = cfg_short_offset[i].bits;
|
|
fprintf(file, "\t\t.byte %%%s\t\t;%u bits\n",
|
|
length_codes[bits], bits);
|
|
}
|
|
// LSB of base offsets
|
|
fprintf(file, "_lz_moff_adjust_lo\n");
|
|
fprintf(file, long_title);
|
|
for(i = 0; i <= 3; ++i)
|
|
write_single_offset(file, &cfg_long_offset[i], 0);
|
|
fprintf(file, short_title);
|
|
for(i = 3; i >= 0; --i)
|
|
write_single_offset(file, &cfg_short_offset[i], 0);
|
|
// MSB of base offsets
|
|
fprintf(file, "_lz_moff_adjust_hi = *-%u\n", near_longs);
|
|
fprintf(file, long_title);
|
|
for(i = 0; i <= 3; ++i)
|
|
write_single_offset(file, &cfg_long_offset[i], 8);
|
|
fprintf(file, short_title);
|
|
for(i = 3; i >= 0; --i)
|
|
write_single_offset(file, &cfg_short_offset[i], 8);
|
|
|
|
fclose(file);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Print some basic statistics about the encoding of the file
|
|
******************************************************************************/
|
|
void print_statistics(const lz_context *ctx, FILE *file) {
|
|
unsigned input_size = ctx->src_end - ctx->src_begin;
|
|
unsigned i;
|
|
|
|
printf (
|
|
"input file:\t" "%u bytes\n"
|
|
"output file:\t" "%u bytes, %u bits (%.2f%% ratio)\n"
|
|
"short offsets:\t" "{ %u-%u: %u, %u-%u: %u, %u-%u: %u, %u-%u: %u }\n"
|
|
"long offsets:\t" "{ %u-%u: %u, %u-%u: %u, %u-%u: %u, %u-%u: %u }\n"
|
|
"%u matches:\t" "%u bytes, %f avg, %d max\n"
|
|
"%u literals:\t" "%u bytes, %f avg\n"
|
|
"avg offset:\t" "%f bytes\n",
|
|
|
|
input_size,
|
|
ctx->stats.output_size,
|
|
ctx->info[ctx->src_begin].cumulative_cost,
|
|
100.0 * ctx->stats.output_size / input_size,
|
|
|
|
cfg_short_offset[0].base,
|
|
cfg_short_offset[0].limit,
|
|
ctx->stats.short_freq[0],
|
|
cfg_short_offset[1].base,
|
|
cfg_short_offset[1].limit,
|
|
ctx->stats.short_freq[1],
|
|
cfg_short_offset[2].base,
|
|
cfg_short_offset[2].limit,
|
|
ctx->stats.short_freq[2],
|
|
cfg_short_offset[3].base,
|
|
cfg_short_offset[3].limit,
|
|
ctx->stats.short_freq[3],
|
|
cfg_long_offset[0].base,
|
|
cfg_long_offset[0].limit,
|
|
ctx->stats.long_freq[0],
|
|
cfg_long_offset[1].base,
|
|
cfg_long_offset[1].limit,
|
|
ctx->stats.long_freq[1],
|
|
cfg_long_offset[2].base,
|
|
cfg_long_offset[2].limit,
|
|
ctx->stats.long_freq[2],
|
|
cfg_long_offset[3].base,
|
|
cfg_long_offset[3].limit,
|
|
ctx->stats.long_freq[3],
|
|
|
|
ctx->stats.match_count,
|
|
ctx->stats.match_bytes,
|
|
(double) ctx->stats.match_bytes / ctx->stats.match_count,
|
|
ctx->stats.max_offset,
|
|
|
|
ctx->stats.literal_runs,
|
|
ctx->stats.literal_bytes,
|
|
(double) ctx->stats.literal_bytes / ctx->stats.literal_runs,
|
|
|
|
(double) ctx->stats.offset_distance / ctx->stats.match_count
|
|
);
|
|
for (i = 0; i < 256; i++) {
|
|
printf("% 6d % 6d\n", ctx->stats.llen_freq[i], ctx->stats.len_freq[i]);
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Helper functions
|
|
******************************************************************************/
|
|
signed read_number(char* arg) {
|
|
if(arg[0] == '$') return strtoul(arg + 1, NULL, 16);
|
|
else if(arg[0] == '0' && arg[1] == 'x') return strtoul(arg + 2, NULL, 16);
|
|
return strtoul(arg, NULL, 10);
|
|
}
|
|
|
|
unsigned compress(lz_context* ctx, char* output_name) {
|
|
generate_hash_table(ctx);
|
|
return find_matches(ctx);
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
******************************************************************************/
|
|
|
|
void iterate(lz_context* ctx) {
|
|
unsigned packed_size;
|
|
|
|
unsigned temp;
|
|
signed j;
|
|
unsigned smin, smax;
|
|
unsigned lmin, lmax;
|
|
unsigned from, to;
|
|
|
|
char lengths[24];
|
|
unsigned lbits[6], sbits[6];
|
|
unsigned lbest, sbest;
|
|
|
|
lbits[0] = 3;
|
|
lbits[1] = 7;
|
|
lbits[2] = 10;
|
|
lbits[3] = 13;
|
|
|
|
setbuf(stdout, NULL);
|
|
|
|
packed_size = 0;
|
|
smin = 1;
|
|
smax = 15;
|
|
|
|
lmin = 1;
|
|
lmax = 15;
|
|
|
|
ctx->iterations = 1;
|
|
while (ctx->iterations--) {
|
|
for (j = smin; j <= smax - 3; j++) {
|
|
sbits[0] = j;
|
|
sbits[3] = smax;
|
|
sbits[1] = j + (smax - j) / 3;
|
|
sbits[2] = j + (smax - j) / 3 * 2;
|
|
sprintf(lengths,"%u/%u/%u/%u:%u/%u/%u/%u",sbits[0],sbits[1],sbits[2],sbits[3],lbits[0],lbits[1],lbits[2],lbits[3]);
|
|
parse_offset_lengths(ctx, lengths);
|
|
temp = compress(ctx, ctx->output_name);
|
|
if (packed_size == 0 || temp < packed_size) {
|
|
printf("best bitlengths: %s \r", lengths);
|
|
smin = j; packed_size = temp;
|
|
}
|
|
}
|
|
for (j = smax; j > smin + 3; j--) {
|
|
sbits[0] = smin;
|
|
sbits[3] = j;
|
|
sbits[1] = smin + (j - smin) / 3;
|
|
sbits[2] = smin + (j - smin) / 3 * 2;
|
|
sprintf(lengths,"%u/%u/%u/%u:%u/%u/%u/%u",sbits[0],sbits[1],sbits[2],sbits[3],lbits[0],lbits[1],lbits[2],lbits[3]);
|
|
parse_offset_lengths(ctx, lengths);
|
|
temp = compress(ctx, ctx->output_name);
|
|
if (packed_size == 0 || temp < packed_size) {
|
|
printf("best bitlengths: %s \r", lengths);
|
|
smax = j; packed_size = temp;
|
|
}
|
|
|
|
}
|
|
sbits[0] = smin;
|
|
sbits[3] = smax;
|
|
sbits[1] = smin + (smax - smin) / 3;
|
|
sbits[2] = smin + (smax - smin) / 3 * 2;
|
|
|
|
for (j = lmin; j <= lmax - 3; j++) {
|
|
lbits[0] = j;
|
|
lbits[3] = lmax;
|
|
lbits[1] = j + (lmax - j) / 3;
|
|
lbits[2] = j + (lmax - j) / 3 * 2;
|
|
sprintf(lengths,"%u/%u/%u/%u:%u/%u/%u/%u",sbits[0],sbits[1],sbits[2],sbits[3],lbits[0],lbits[1],lbits[2],lbits[3]);
|
|
parse_offset_lengths(ctx, lengths);
|
|
temp = compress(ctx, ctx->output_name);
|
|
if (packed_size == 0 || temp < packed_size) {
|
|
printf("best bitlengths: %s \r", lengths);
|
|
lmin = j; packed_size = temp;
|
|
}
|
|
}
|
|
for (j = lmax; j > lmin + 3; j--) {
|
|
lbits[0] = lmin;
|
|
lbits[3] = j;
|
|
lbits[1] = lmin + (j - lmin) / 3;
|
|
lbits[2] = lmin + (j - lmin) / 3 * 2;
|
|
sprintf(lengths,"%u/%u/%u/%u:%u/%u/%u/%u",sbits[0],sbits[1],sbits[2],sbits[3],lbits[0],lbits[1],lbits[2],lbits[3]);
|
|
parse_offset_lengths(ctx, lengths);
|
|
temp = compress(ctx, ctx->output_name);
|
|
if (packed_size == 0 || temp < packed_size) {
|
|
printf("best bitlengths: %s \r", lengths);
|
|
lmax = j; packed_size = temp;
|
|
}
|
|
|
|
}
|
|
lbits[0] = lmin;
|
|
lbits[3] = lmax;
|
|
lbits[1] = lmin + (lmax - lmin) / 3;
|
|
lbits[2] = lmin + (lmax - lmin) / 3 * 2;
|
|
|
|
|
|
}
|
|
|
|
packed_size = 0;
|
|
ctx->iterations = 2;
|
|
|
|
while (ctx->iterations--) {
|
|
|
|
for (j = 0; j < 4; j++) {
|
|
lbest = lbits[j];
|
|
if (j == 0) from = 1;
|
|
else from = lbits[j-1] + 1;
|
|
|
|
if (j == 3) to = 16;
|
|
else to = lbits[j+1] - 1;
|
|
|
|
for (lbits[j] = from; lbits[j] < to; lbits[j]++) {
|
|
sprintf(lengths,"%u/%u/%u/%u:%u/%u/%u/%u",sbits[0],sbits[1],sbits[2],sbits[3],lbits[0],lbits[1],lbits[2],lbits[3]);
|
|
parse_offset_lengths(ctx, lengths);
|
|
temp = compress(ctx, ctx->output_name);
|
|
if (packed_size == 0 || temp < packed_size) {
|
|
printf("best bitlengths: %s \r", lengths);
|
|
lbest = lbits[j]; packed_size = temp;
|
|
}
|
|
}
|
|
lbits[j] = lbest;
|
|
}
|
|
for (j = 0; j < 4; j++) {
|
|
sbest = sbits[j];
|
|
if (j == 0) from = 1;
|
|
else from = sbits[j-1] + 1;
|
|
|
|
if (j == 3) to = 16;
|
|
else to = sbits[j+1] - 1;
|
|
|
|
for (sbits[j] = from; sbits[j] < to; sbits[j]++) {
|
|
sprintf(lengths,"%u/%u/%u/%u:%u/%u/%u/%u",sbits[0],sbits[1],sbits[2],sbits[3],lbits[0],lbits[1],lbits[2],lbits[3]);
|
|
parse_offset_lengths(ctx, lengths);
|
|
temp = compress(ctx, ctx->output_name);
|
|
if (packed_size == 0 || temp < packed_size) {
|
|
printf("best bitlengths: %s \r", lengths);
|
|
sbest = sbits[j]; packed_size = temp;
|
|
}
|
|
}
|
|
sbits[j] = sbest;
|
|
}
|
|
}
|
|
|
|
sprintf(lengths,"%u/%u/%u/%u:%u/%u/%u/%u",sbits[0],sbits[1],sbits[2],sbits[3],lbits[0],lbits[1],lbits[2],lbits[3]);
|
|
parse_offset_lengths(ctx, lengths);
|
|
printf("best bitlengths: %s \n", lengths);
|
|
}
|
|
|
|
int crunch(lz_context* ctx) {
|
|
unsigned packed_size;
|
|
unsigned source_size;
|
|
unsigned decruncher_size = sizeof decruncher;
|
|
unsigned data_addr = 0;
|
|
int i;
|
|
|
|
unsigned char checksum;
|
|
|
|
read_input(ctx, ctx->input_name, ctx->is_cbm);
|
|
cut_input_addr(ctx, ctx->cut_addr_first, ctx->cut_addr_last, ctx->full_dict);
|
|
|
|
source_size = ctx->src_end - ctx->src_begin;
|
|
|
|
if (ctx->iterations > 0) iterate(ctx);
|
|
|
|
// Do the compression
|
|
generate_hash_table(ctx);
|
|
find_matches(ctx);
|
|
|
|
output_open(ctx, ctx->output_name);
|
|
|
|
if(ctx->output_type == OUTPUT_LEVEL || ctx->output_type == OUTPUT_BITFIRE) {
|
|
// Add 2 blank bytes here first, as the address can not be calculated yet
|
|
fputc(0, ctx->dst_file);
|
|
fputc(0, ctx->dst_file);
|
|
|
|
// Add depack address
|
|
fputc(0, ctx->dst_file);
|
|
fputc(0, ctx->dst_file);
|
|
|
|
if (!ctx->overlap) {
|
|
// Add 2 blank bytes here first, as the end_addr can not be calculated yet
|
|
fputc(0, ctx->dst_file);
|
|
fputc(0, ctx->dst_file);
|
|
}
|
|
}
|
|
|
|
if(ctx->output_type == OUTPUT_SFX) {
|
|
// Output decruncher with loadadress 0x801
|
|
fwrite(decruncher, decruncher_size, 1, ctx->dst_file);
|
|
|
|
// Add depack address
|
|
fputc(ctx->start_addr & 0xff, ctx->dst_file);
|
|
fputc(ctx->start_addr >> 8, ctx->dst_file);
|
|
}
|
|
|
|
if(ctx->write_tables) write_offsets(ctx->dst_file);
|
|
|
|
// Do a dry run with rendering output to find out packed_filesize
|
|
ctx->output = false;
|
|
render_output(ctx);
|
|
|
|
// Now go live and find perfect safety margin, and write compressed data to file
|
|
ctx->output = true;
|
|
render_output(ctx);
|
|
|
|
output_close(ctx);
|
|
|
|
// Now open compressed file and manipulate all kind of header data
|
|
if(ctx->dst_file = fopen(ctx->output_name, "rb+"), !ctx->dst_file) fatal("cannot create '%s'", ctx->output_name);
|
|
|
|
// Packed size including all the header bytes
|
|
fseek(ctx->dst_file, 0L, SEEK_END);
|
|
packed_size = ftell(ctx->dst_file);
|
|
|
|
// Ignore margin in case of zero overlap
|
|
if (!ctx->overlap) ctx->margin = 0;
|
|
|
|
// Adapt size for different output types
|
|
if(ctx->output_type == OUTPUT_LEVEL || ctx->output_type == OUTPUT_BITFIRE) {
|
|
// File contains a load address, subtract
|
|
packed_size -= 2;
|
|
} else if(ctx->output_type == OUTPUT_SFX) {
|
|
// Includes already the load address
|
|
packed_size -= decruncher_size;
|
|
}
|
|
|
|
// Emit margin only when necessary
|
|
if (ctx->margin > 0 && ctx->load_addr < 0 && ctx->depack_to < 0 && ctx->output_type != OUTPUT_SFX) {
|
|
printf("overlap: %d bytes\n", ctx->margin);
|
|
}
|
|
|
|
// Some stats and info
|
|
printf("source size: $%04x (%d)\n", source_size, source_size);
|
|
printf("packed size: $%04x (%d) %s ratio: %.1f%%\n", packed_size - ctx->write_tables * 24, packed_size - ctx->write_tables * 24, ctx->write_tables ? "(+24 byte tables)" : "", ((packed_size - ctx->write_tables * 24) * 100.0 / (ctx->output_end - ctx->output_begin)));
|
|
|
|
// Print more info and calc addresses
|
|
if(ctx->output_type == OUTPUT_LEVEL || ctx->output_type == OUTPUT_BITFIRE) {
|
|
//find perfect loading address
|
|
if (ctx->load_addr < 0) ctx->load_addr = ctx->output_end - packed_size + ctx->margin;
|
|
if (ctx->relocate_to >= 0 && ctx->load_addr >= 0) {
|
|
ctx->load_addr = ctx->load_addr + (ctx->relocate_to - ctx->output_begin);
|
|
ctx->end_pos = ctx->end_pos + (ctx->relocate_to - ctx->output_begin);
|
|
}
|
|
|
|
printf("source load: $%04x-$%04x\n", ctx->output_begin, ctx->output_end);
|
|
printf("packed load: $%04x-$%04x\n", ctx->load_addr, ctx->load_addr + packed_size);
|
|
|
|
// Fix optimal load address to file
|
|
fseek(ctx->dst_file, 0, SEEK_SET);
|
|
fputc(ctx->load_addr & 0xff, ctx->dst_file);
|
|
fputc(ctx->load_addr >> 8, ctx->dst_file);
|
|
// Add depack address
|
|
if (ctx->depack_to >=0) {
|
|
fputc(ctx->depack_to & 0xff, ctx->dst_file);
|
|
fputc(ctx->depack_to >> 8, ctx->dst_file);
|
|
} else {
|
|
if (ctx->relocate_to >= 0) {
|
|
fputc(ctx->relocate_to & 0xff, ctx->dst_file);
|
|
fputc(ctx->relocate_to >> 8, ctx->dst_file);
|
|
} else {
|
|
fputc(ctx->output_begin & 0xff, ctx->dst_file);
|
|
fputc(ctx->output_begin >> 8, ctx->dst_file);
|
|
}
|
|
}
|
|
if (!ctx->overlap) {
|
|
// Add end address
|
|
fputc(ctx->end_pos & 0xff, ctx->dst_file);
|
|
fputc(ctx->end_pos >> 8, ctx->dst_file);
|
|
}
|
|
|
|
// Do some sanity checks
|
|
if (ctx->load_addr + packed_size + ctx->margin < ctx->output_end) {
|
|
if (ctx->load_addr + packed_size + ctx->margin >= ctx->output_begin) {
|
|
fprintf(stderr,"WARNING: packed file location collides with depacked data location\n");
|
|
if (ctx->exit_on_warn) exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
if (packed_size > ctx->output_end - ctx->output_begin) {
|
|
fprintf(stderr,"WARNING: compressed file is bigger than original\n");
|
|
if (ctx->exit_on_warn) exit(EXIT_FAILURE);
|
|
}
|
|
if ((ctx->load_addr > 0xd000 && ctx->load_addr < 0xe000) || (ctx->load_addr + packed_size > 0xd000 && ctx->load_addr + packed_size < 0xe000)) {
|
|
fprintf(stderr,"WARNING: resulting file has parts in IO-range $d000-$dfff\n");
|
|
if (ctx->exit_on_warn) exit(EXIT_FAILURE);
|
|
}
|
|
if (ctx->output_type == OUTPUT_BITFIRE) {
|
|
printf("filetype: bitfire\n");
|
|
} else {
|
|
printf("filetype: level\n");
|
|
}
|
|
} else if(ctx->output_type == OUTPUT_SFX) {
|
|
packed_size -= 26;
|
|
data_addr = decruncher_size + packed_size - 0xff + 0x800 + 24;
|
|
|
|
// Set up depacker values
|
|
fseek(ctx->dst_file, 0x2e, SEEK_SET);
|
|
fputc((packed_size >> 8) + 1, ctx->dst_file);
|
|
|
|
fseek(ctx->dst_file, 0x31, SEEK_SET);
|
|
fputc(data_addr & 0xff, ctx->dst_file);
|
|
fputc(data_addr >> 8, ctx->dst_file);
|
|
|
|
fseek(ctx->dst_file, 0x43, SEEK_SET);
|
|
fputc((0x10000 - packed_size) & 0xff, ctx->dst_file);
|
|
|
|
fseek(ctx->dst_file, 0x6a, SEEK_SET);
|
|
fputc((0x10000 - packed_size) >> 8, ctx->dst_file);
|
|
fseek(ctx->dst_file, 0xd3, SEEK_SET);
|
|
fputc((0x10000 - packed_size) >> 8, ctx->dst_file);
|
|
fseek(ctx->dst_file, 0x118, SEEK_SET);
|
|
fputc((0x10000 - packed_size) >> 8, ctx->dst_file);
|
|
|
|
fseek(ctx->dst_file, 0x72, SEEK_SET);
|
|
fputc(ctx->output_begin & 0xff, ctx->dst_file);
|
|
fputc(ctx->output_begin >> 8, ctx->dst_file);
|
|
|
|
printf("start address: $%04x (%d)\n", ctx->start_addr, ctx->start_addr);
|
|
printf("final size: $%04x (%d)\n", packed_size + decruncher_size, packed_size + decruncher_size);
|
|
printf("filetype: sfx\n");
|
|
} else {
|
|
}
|
|
|
|
fclose(ctx->dst_file);
|
|
|
|
if (ctx->checksum) {
|
|
checksum = 0;
|
|
for (i = ctx->output_begin; i < ctx->output_end; i++) {
|
|
checksum ^= ctx->src_data[i];
|
|
}
|
|
printf("checksum: $%02x\n", checksum);
|
|
}
|
|
|
|
// Display some statistics gathered in the process
|
|
if(ctx->show_stats) print_statistics(ctx, stdout);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* The main function
|
|
******************************************************************************/
|
|
int
|
|
#ifdef _MSC_VER
|
|
__cdecl
|
|
#endif
|
|
main(int argc, char *argv[]) {
|
|
const char *program_name;
|
|
unsigned name_length;
|
|
lz_context ctx;
|
|
|
|
memset(&ctx.stats, 0, sizeof ctx.stats);
|
|
// Parse the command line
|
|
program_name = *argv;
|
|
|
|
ctx.iterations = 0;
|
|
ctx.output_name = NULL;
|
|
ctx.cut_addr_first = 0;
|
|
ctx.cut_addr_last = INT_MAX;
|
|
ctx.emit_offset_tables = NULL;
|
|
ctx.show_stats = false;
|
|
|
|
ctx.is_cbm = true;
|
|
ctx.show_trace = false;
|
|
ctx.output_type = OUTPUT_LEVEL;
|
|
ctx.full_dict = false;
|
|
ctx.overlap = false;
|
|
ctx.header_size = 0;
|
|
ctx.checksum = false;
|
|
ctx.exit_on_warn = false;
|
|
ctx.load_addr = -1;
|
|
ctx.depack_to = -1;
|
|
ctx.start_addr = -1;
|
|
ctx.relocate_to = -1;
|
|
ctx.write_tables = false;
|
|
ctx.offset_lengths = false;
|
|
|
|
while(++argv, --argc) {
|
|
if(argc >= 2 && !strcmp(*argv, "-o")) {
|
|
ctx.output_name = *++argv;
|
|
--argc;
|
|
} else if(argc >= 2 && !strcmp(*argv, "--sfx")) {
|
|
ctx.start_addr = read_number(*++argv);
|
|
ctx.output_type = OUTPUT_SFX;
|
|
ctx.write_tables = true;
|
|
--argc;
|
|
} else if(argc >= 2 && !strcmp(*argv, "--load-addr")) {
|
|
ctx.load_addr = read_number(*++argv);
|
|
--argc;
|
|
} else if(argc >= 2 && !strcmp(*argv, "--depack-to")) {
|
|
ctx.depack_to = read_number(*++argv);
|
|
--argc;
|
|
} else if(argc >= 2 && !strcmp(*argv, "--relocate-to")) {
|
|
ctx.relocate_to = read_number(*++argv);
|
|
--argc;
|
|
} else if(!strcmp(*argv, "--level")) {
|
|
ctx.output_type = OUTPUT_LEVEL;
|
|
} else if(!strcmp(*argv, "--bitfire")) {
|
|
ctx.output_type = OUTPUT_BITFIRE;
|
|
} else if(argc >= 3 && !strcmp(*argv, "--cut-input-addr")) {
|
|
ctx.cut_addr_first = read_number(*++argv);
|
|
ctx.cut_addr_last = read_number(*++argv);
|
|
if(ctx.cut_addr_first > ctx.cut_addr_last)
|
|
fatal("last must be larger than first");
|
|
argc -= 2;
|
|
} else if(!strcmp(*argv, "--full-dict")) {
|
|
ctx.full_dict = true;
|
|
} else if(!strcmp(*argv, "--overlap")) {
|
|
ctx.overlap = true;
|
|
} else if(!strcmp(*argv, "--best-offset-tables")) {
|
|
ctx.iterations = 1;
|
|
} else if(argc >= 2 && !strcmp(*argv, "--offset-lengths")) {
|
|
ctx.offset_lengths = true;
|
|
if(!parse_offset_lengths(&ctx, *++argv))
|
|
break;
|
|
--argc;
|
|
} else if(argc >= 2 && !strcmp(*argv, "--emit-offset-tables")) {
|
|
ctx.emit_offset_tables = *++argv;
|
|
--argc;
|
|
} else if(!strcmp(*argv, "--include-tables")) {
|
|
ctx.write_tables = true;
|
|
} else if(!strcmp(*argv, "--statistics")) {
|
|
ctx.show_stats = true;
|
|
} else if(!strcmp(*argv, "--trace-coding")) {
|
|
ctx.show_trace = true;
|
|
} else if(!strcmp(*argv, "--binfile")) {
|
|
ctx.is_cbm = false;
|
|
} else if(!strcmp(*argv, "--checksum")) {
|
|
ctx.checksum = true;
|
|
} else if(!strcmp(*argv, "--exit-on-warn")) {
|
|
ctx.exit_on_warn = true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ctx.offset_lengths) parse_offset_lengths(&ctx, DEFAULT_LENGTHS);
|
|
|
|
if(ctx.emit_offset_tables) {
|
|
write_offset_tables(ctx.emit_offset_tables);
|
|
// It allowed to just generate the offset tables
|
|
if(!argc)
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
if(argc != 1) {
|
|
fprintf (
|
|
stderr,
|
|
"syntax: %s\n"
|
|
"\t[-o output.lz selects output file\n"
|
|
"\t[--overlap] allow overlap and disable in place depacking\n"
|
|
"\t[--sfx startaddr] create a sfx with given startaddress\n"
|
|
"\t[--level] create a level packed file\n"
|
|
"\t[--bitfire] create a file compatible with bitfire\n"
|
|
"\t[--full-dict] allow matches over whole file\n"
|
|
"\t[--cut-input-addr first last] only pack selected part of file\n"
|
|
"\t[--load-addr addr] change load address of packed file\n"
|
|
"\t[--depack-to addr] change depack address of file\n"
|
|
"\t[--relocate-to addr] change load address of file, prior to packing\n"
|
|
"\t[--binfile] file has no 2 byte load-address (--relocate-to can help)\n"
|
|
"\t[--checksum] print eor checksum over file\n"
|
|
"\t[--exit_on_warn] exit with error instead of giving a warning\n"
|
|
"\t[--best-offset-tables] evaluate best offset tables, slow\n"
|
|
"\t[--include-tables] add own tables to packed sfx\n"
|
|
"\t[--offset-lengths s1/s2/s3/s4:l1/l2/l3/l4] use alternative offset lengths\n"
|
|
"\t[--emit-offset-tables tables.asm] spit out offset table\n"
|
|
"\t[--statistics] print some stats\n"
|
|
"\t[--trace-coding] print more gibberish\n"
|
|
"\t{input-file} the input file\n",
|
|
program_name
|
|
);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
ctx.input_name = *argv;
|
|
|
|
if (!ctx.is_cbm) {
|
|
if (ctx.relocate_to < 0) {
|
|
fatal("a load address is needed via the --relocate-to switch");
|
|
}
|
|
}
|
|
|
|
if (ctx.output_type == OUTPUT_SFX) {
|
|
if (!ctx.overlap) {
|
|
ctx.overlap = true;
|
|
//fprintf(stderr,"ignoring --overlap option for filetype --sfx");
|
|
}
|
|
if (ctx.depack_to >= 0) {
|
|
fatal("option --depack-to not supported with --sfx");
|
|
}
|
|
if (ctx.load_addr >= 0) {
|
|
fatal("option --load-addr not supported with --sfx");
|
|
}
|
|
if (ctx.relocate_to >= 0) {
|
|
fatal("option --relocate-to not supported with --sfx");
|
|
}
|
|
}
|
|
|
|
if (ctx.output_type == OUTPUT_BITFIRE) {
|
|
if (ctx.offset_lengths) {
|
|
fatal("option --offset_lengths not supported with --bitfire");
|
|
}
|
|
if (ctx.write_tables) {
|
|
fatal("option --write_tables not supported with --bitfire");
|
|
}
|
|
if (ctx.iterations > 0) {
|
|
fatal("option --best_offset_tables not supported with --bitfire");
|
|
}
|
|
}
|
|
|
|
// If necessary generate output file by substituting the
|
|
// extension for .lz
|
|
if(!ctx.output_name) {
|
|
static const char extension[] = ".lz";
|
|
name_length = strlen(ctx.input_name);
|
|
|
|
ctx.output_name = malloc(name_length + 1 + sizeof extension);
|
|
|
|
memcpy(ctx.output_name, ctx.input_name, name_length);
|
|
memcpy(ctx.output_name + name_length, extension, sizeof extension);
|
|
}
|
|
|
|
return crunch(&ctx);
|
|
}
|