mirror of
https://frontier.innolan.net/github/AmigaExamples.git
synced 2025-11-20 13:24:16 +00:00
362 lines
11 KiB
C++
362 lines
11 KiB
C++
// Copyright 1999-2015 Aske Simon Christensen. See LICENSE.txt for usage terms.
|
|
|
|
/*
|
|
|
|
Main file for the cruncher.
|
|
|
|
*/
|
|
|
|
//#define SHRINKLER_TITLE ("Shrinkler executable file compressor by Blueberry - version 4.4 (2015-01-18)\n\n")
|
|
|
|
#ifndef SHRINKLER_TITLE
|
|
#define SHRINKLER_TITLE ("Shrinkler executable file compressor by Blueberry - development version (built " __DATE__ " " __TIME__ ")\n\n")
|
|
#endif
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <sys/stat.h>
|
|
|
|
using std::string;
|
|
|
|
#include "HunkFile.h"
|
|
#include "DataFile.h"
|
|
|
|
void usage() {
|
|
printf("Usage: Shrinkler <options> <input executable> <output executable>\n");
|
|
printf("\n");
|
|
printf("Available options are (default values in parentheses):\n");
|
|
printf(" -d, --data Treat input as raw data, rather than executable\n");
|
|
printf(" -h, --hunkmerge Merge hunks of the same memory type\n");
|
|
printf(" -o, --overlap Overlap compressed and decompressed data to save memory\n");
|
|
printf(" -m, --mini Use a smaller, but more restricted decrunch header\n");
|
|
printf(" -i, --iterations Number of iterations for the compression (2)\n");
|
|
printf(" -l, --length-margin Number of shorter matches considered for each match (2)\n");
|
|
printf(" -a, --same-length Number of matches of the same length to consider (20)\n");
|
|
printf(" -e, --effort Perseverance in finding multiple matches (200)\n");
|
|
printf(" -s, --skip-length Minimum match length to accept greedily (2000)\n");
|
|
printf(" -r, --references Number of reference edges to keep in memory (100000)\n");
|
|
printf(" -t, --text Print a text, followed by a newline, before decrunching\n");
|
|
printf(" -T, --textfile Print the contents of the given file before decrunching\n");
|
|
printf(" -f, --flash Poke into a register (e.g. DFF180) during decrunching\n");
|
|
printf(" -p, --no-progress Do not print progress info: no ANSI codes in output\n");
|
|
printf("\n");
|
|
exit(0);
|
|
}
|
|
|
|
class Parameter {
|
|
public:
|
|
bool seen;
|
|
|
|
virtual ~Parameter() {}
|
|
protected:
|
|
void parse(const char *form1, const char *form2, const char *arg_kind, int argc, const char *argv[], vector<bool>& consumed) {
|
|
seen = false;
|
|
for (int i = 1 ; i < argc ; i++) {
|
|
if (strcmp(argv[i], form1) == 0 || strcmp(argv[i], form2) == 0) {
|
|
if (seen) {
|
|
printf("Error: %s specified multiple times.\n\n", argv[i]);
|
|
usage();
|
|
}
|
|
consumed[i] = true;
|
|
if (arg_kind) {
|
|
if (i+1 < argc && !consumed[i+1] && argv[i+1][0] != '-') {
|
|
seen = parseArg(argv[i], argv[i+1]);
|
|
}
|
|
if (!seen) {
|
|
printf("Error: %s requires a %s argument.\n\n", argv[i], arg_kind);
|
|
usage();
|
|
}
|
|
consumed[i+1] = true;
|
|
i = i+1;
|
|
} else {
|
|
seen = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual bool parseArg(const char *param, const char *arg) = 0;
|
|
};
|
|
|
|
class IntParameter : public Parameter {
|
|
int min_value;
|
|
int max_value;
|
|
public:
|
|
int value;
|
|
|
|
IntParameter(const char *form1, const char *form2, int min_value, int max_value, int default_value,
|
|
int argc, const char *argv[], vector<bool>& consumed)
|
|
: min_value(min_value), max_value(max_value), value(default_value)
|
|
{
|
|
parse(form1, form2, "numeric", argc, argv, consumed);
|
|
}
|
|
|
|
protected:
|
|
virtual bool parseArg(const char *param, const char *arg) {
|
|
char *endptr;
|
|
value = strtol(arg, &endptr, 10);
|
|
if (endptr == &arg[strlen(arg)]) {
|
|
if (value < min_value || value > max_value) {
|
|
printf("Error: Argument of %s must be between %d and %d.\n\n", param, min_value, max_value);
|
|
usage();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class HexParameter : public Parameter {
|
|
public:
|
|
unsigned value;
|
|
|
|
HexParameter(const char *form1, const char *form2, int default_value,
|
|
int argc, const char *argv[], vector<bool>& consumed)
|
|
: value(default_value)
|
|
{
|
|
parse(form1, form2, "hexadecimal", argc, argv, consumed);
|
|
}
|
|
|
|
protected:
|
|
virtual bool parseArg(const char *param, const char *arg) {
|
|
char *endptr;
|
|
value = strtol(arg, &endptr, 16);
|
|
if (endptr == &arg[strlen(arg)]) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class StringParameter : public Parameter {
|
|
public:
|
|
const char *value;
|
|
|
|
StringParameter(const char *form1, const char *form2, int argc, const char *argv[], vector<bool>& consumed)
|
|
: value(NULL)
|
|
{
|
|
parse(form1, form2, "string", argc, argv, consumed);
|
|
}
|
|
|
|
protected:
|
|
virtual bool parseArg(const char *param, const char *arg) {
|
|
value = arg;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class FlagParameter : public Parameter {
|
|
public:
|
|
FlagParameter(const char *form1, const char *form2, int argc, const char *argv[], vector<bool>& consumed)
|
|
{
|
|
parse(form1, form2, NULL, argc, argv, consumed);
|
|
}
|
|
|
|
protected:
|
|
virtual bool parseArg(const char *param, const char *arg) {
|
|
// Not used
|
|
return true;
|
|
}
|
|
};
|
|
|
|
int main2(int argc, const char *argv[]) {
|
|
printf(SHRINKLER_TITLE);
|
|
|
|
vector<bool> consumed(argc);
|
|
|
|
FlagParameter data ("-d", "--data", argc, argv, consumed);
|
|
FlagParameter hunkmerge ("-h", "--hunkmerge", argc, argv, consumed);
|
|
FlagParameter overlap ("-o", "--overlap", argc, argv, consumed);
|
|
FlagParameter mini ("-m", "--mini", argc, argv, consumed);
|
|
IntParameter iterations ("-i", "--iterations", 1, 9, 2, argc, argv, consumed);
|
|
IntParameter length_margin ("-l", "--length-margin", 0, 100, 2, argc, argv, consumed);
|
|
IntParameter same_length ("-a", "--same-length", 1, 100000, 20, argc, argv, consumed);
|
|
IntParameter effort ("-e", "--effort", 0, 100000, 200, argc, argv, consumed);
|
|
IntParameter skip_length ("-s", "--skip-length", 2, 100000, 2000, argc, argv, consumed);
|
|
IntParameter references ("-r", "--references", 1000, 10000000, 100000, argc, argv, consumed);
|
|
StringParameter text ("-t", "--text", argc, argv, consumed);
|
|
StringParameter textfile ("-T", "--textfile", argc, argv, consumed);
|
|
HexParameter flash ("-f", "--flash", 0, argc, argv, consumed);
|
|
FlagParameter no_progress ("-p", "--no-progress", argc, argv, consumed);
|
|
|
|
vector<const char*> files;
|
|
|
|
for (int i = 1 ; i < argc ; i++) {
|
|
if (!consumed[i]) {
|
|
if (argv[i][0] == '-') {
|
|
printf("Error: Unknown option %s\n\n", argv[i]);
|
|
usage();
|
|
}
|
|
files.push_back(argv[i]);
|
|
}
|
|
}
|
|
|
|
if (data.seen && (hunkmerge.seen || overlap.seen || mini.seen || text.seen || textfile.seen || flash.seen)) {
|
|
printf("Error: The data option cannot be used together with any of the\n");
|
|
printf("hunkmerge, overlap, mini, text, textfile or flash options.\n\n");
|
|
usage();
|
|
}
|
|
|
|
if (overlap.seen && mini.seen) {
|
|
printf("Error: The overlap and mini options cannot be used together.\n\n");
|
|
usage();
|
|
}
|
|
|
|
if (text.seen && textfile.seen) {
|
|
printf("Error: The text and textfile options cannot both be specified.\n\n");
|
|
usage();
|
|
}
|
|
|
|
if (mini.seen && (text.seen || textfile.seen)) {
|
|
printf("Error: The text and textfile options cannot be used in mini mode.\n\n");
|
|
usage();
|
|
}
|
|
|
|
if (files.size() == 0) {
|
|
printf("Error: No input file specified.\n\n");
|
|
usage();
|
|
}
|
|
if (files.size() == 1) {
|
|
printf("Error: No output file specified.\n\n");
|
|
usage();
|
|
}
|
|
if (files.size() > 2) {
|
|
printf("Error: Too many files specified.\n\n");
|
|
usage();
|
|
}
|
|
|
|
const char *infile = files[0];
|
|
const char *outfile = files[1];
|
|
|
|
PackParams params;
|
|
params.iterations = iterations.value;
|
|
params.length_margin = length_margin.value;
|
|
params.skip_length = skip_length.value;
|
|
params.match_patience = effort.value;
|
|
params.max_same_length = same_length.value;
|
|
|
|
string *decrunch_text_ptr = NULL;
|
|
string decrunch_text;
|
|
if (text.seen) {
|
|
decrunch_text = text.value;
|
|
decrunch_text.push_back('\n');
|
|
decrunch_text_ptr = &decrunch_text;
|
|
} else if (textfile.seen) {
|
|
FILE *decrunch_text_file = fopen(textfile.value, "r");
|
|
if (!decrunch_text_file) {
|
|
printf("Error: Could not open text file %s\n", textfile.value);
|
|
exit(1);
|
|
}
|
|
char c;
|
|
while ((c = fgetc(decrunch_text_file)) != EOF) {
|
|
decrunch_text.push_back(c);
|
|
}
|
|
fclose(decrunch_text_file);
|
|
decrunch_text_ptr = &decrunch_text;
|
|
}
|
|
|
|
if (data.seen) {
|
|
// Data file compression
|
|
printf("Loading file %s...\n\n", infile);
|
|
DataFile *orig = new DataFile;
|
|
orig->load(infile);
|
|
|
|
printf("Crunching...\n\n");
|
|
RefEdgeFactory edge_factory(references.value);
|
|
DataFile *crunched = orig->crunch(¶ms, &edge_factory, !no_progress.seen);
|
|
delete orig;
|
|
printf("References considered:%8d\n", edge_factory.max_edge_count);
|
|
printf("References discarded:%9d\n\n", edge_factory.max_cleaned_edges);
|
|
|
|
printf("Saving file %s...\n\n", outfile);
|
|
crunched->save(outfile);
|
|
|
|
printf("Final file size: %d\n\n", crunched->size());
|
|
delete crunched;
|
|
|
|
if (edge_factory.max_edge_count > references.value) {
|
|
printf("Note: compression may benefit from a larger reference buffer (-r option).\n\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Executable file compression
|
|
printf("Loading file %s...\n\n", infile);
|
|
HunkFile *orig = new HunkFile;
|
|
orig->load(infile);
|
|
if (!orig->analyze()) {
|
|
printf("\nError while analyzing input file!\n\n");
|
|
delete orig;
|
|
exit(1);
|
|
}
|
|
if (hunkmerge.seen) {
|
|
printf("Merging hunks...\n\n");
|
|
HunkFile *merged = orig->merge_hunks(orig->merged_hunklist());
|
|
delete orig;
|
|
if (!merged->analyze()) {
|
|
printf("\nError while analyzing merged file!\n\n");
|
|
delete merged;
|
|
internal_error();
|
|
}
|
|
orig = merged;
|
|
}
|
|
if (mini.seen && !orig->valid_mini()) {
|
|
printf("Input executable not suitable for mini crunching.\n"
|
|
"Must contain only one non-empty hunk and no relocations,\n"
|
|
"and the final file size must be less than 24k.\n\n");
|
|
delete orig;
|
|
exit(1);
|
|
}
|
|
int orig_mem = orig->memory_usage(true);
|
|
printf("Crunching...\n\n");
|
|
RefEdgeFactory edge_factory(references.value);
|
|
HunkFile *crunched = orig->crunch(¶ms, overlap.seen, mini.seen, decrunch_text_ptr, flash.value, &edge_factory, !no_progress.seen);
|
|
delete orig;
|
|
printf("References considered:%8d\n", edge_factory.max_edge_count);
|
|
printf("References discarded:%9d\n\n", edge_factory.max_cleaned_edges);
|
|
if (!crunched->analyze()) {
|
|
printf("\nError while analyzing crunched file!\n\n");
|
|
delete crunched;
|
|
internal_error();
|
|
}
|
|
int crunched_mem_during = crunched->memory_usage(true);
|
|
int crunched_mem_after = crunched->memory_usage(mini.seen || overlap.seen);
|
|
|
|
printf("Memory overhead during decrunching: %9d\n", crunched_mem_during - orig_mem);
|
|
printf("Memory overhead after decrunching: %9d\n\n", crunched_mem_after - orig_mem);
|
|
|
|
printf("Saving file %s...\n\n", outfile);
|
|
crunched->save(outfile);
|
|
#ifdef S_IRWXU // Is the POSIX file permission API available?
|
|
chmod(outfile, 0755); // Mark file executable
|
|
#endif
|
|
|
|
printf("Final file size: %d\n\n", crunched->size());
|
|
delete crunched;
|
|
|
|
if (edge_factory.max_edge_count > references.value) {
|
|
printf("Note: compression may benefit from a larger reference buffer (-r option).\n\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, const char *argv[]) {
|
|
try {
|
|
return main2(argc, argv);
|
|
} catch (std::bad_alloc& e) {
|
|
fflush(stdout);
|
|
fprintf(stderr,
|
|
"\n\nShrinkler ran out of memory.\n\n"
|
|
"Some things you can try:\n"
|
|
" - Free up some memory\n"
|
|
" - Run it on a machine with more memory\n"
|
|
" - Reduce the size of the reference buffer (-r option)\n"
|
|
" - Split up your biggest hunk into smaller ones\n\n");
|
|
fflush(stderr);
|
|
return 1;
|
|
}
|
|
}
|