mirror of https://github.com/pret/pokecrystal.git
142 lines
6.6 KiB
C
142 lines
6.6 KiB
C
|
#include "proto.h"
|
||
|
|
||
|
/*
|
||
|
Single-pass compressor: attempts to compress the data in a single pass, selecting the best command at each
|
||
|
position within some constraints.
|
||
|
Methods defined: 72
|
||
|
Flags values:
|
||
|
Bit fields (will trigger alternate behavior if set):
|
||
|
1: prefer repetition commands over copy commands of equal count
|
||
|
2: don't emit a copy or repetition with a count equal to its size when the previous command is a literal (0) that
|
||
|
is not at maximum size (32 or 1024)
|
||
|
4: don't emit long copy commands
|
||
|
Selector values (pick one from each group and add them to the bit fields):
|
||
|
- Scan delay: number of bytes that are forced into literal (0) commands after each non-literal command:
|
||
|
0: 0 bytes
|
||
|
8: 1 byte
|
||
|
16: 2 bytes
|
||
|
- Copy command preference (when the command counts are tied), in order from most to least:
|
||
|
0: normal (4), reversed (6), flipped (5)
|
||
|
24: reversed (6), flipped (5), normal (4)
|
||
|
48: flipped (5), reversed (6), normal (4)
|
||
|
*/
|
||
|
|
||
|
struct command * try_compress_single_pass (const unsigned char * data, const unsigned char * bitflipped, unsigned short * length, unsigned flags) {
|
||
|
struct command * commands = malloc(sizeof(struct command) * *length);
|
||
|
memset(commands, -1, sizeof(struct command) * *length);
|
||
|
struct command * current_command = commands;
|
||
|
unsigned short position = 0, previous_data = 0;
|
||
|
unsigned char scan_delay = 0, scan_delay_flag = (flags >> 3) % 3;
|
||
|
struct command copy, repetition;
|
||
|
while (position < *length) {
|
||
|
copy = find_best_copy(data, position, *length, bitflipped, flags);
|
||
|
repetition = find_best_repetition(data, position, *length);
|
||
|
if (flags & 1)
|
||
|
*current_command = pick_best_command(2, repetition, copy);
|
||
|
else
|
||
|
*current_command = pick_best_command(2, copy, repetition);
|
||
|
*current_command = pick_best_command(2, (struct command) {.command = 0, .count = 1, .value = position}, *current_command);
|
||
|
if ((flags & 2) && (command_size(*current_command) == current_command -> count))
|
||
|
if (previous_data && (previous_data != SHORT_COMMAND_COUNT) && (previous_data != MAX_COMMAND_COUNT))
|
||
|
*current_command = (struct command) {.command = 0, .count = 1, .value = position};
|
||
|
if (scan_delay_flag) {
|
||
|
if (scan_delay >= scan_delay_flag)
|
||
|
scan_delay = 0;
|
||
|
else if (current_command -> command) {
|
||
|
scan_delay ++;
|
||
|
*current_command = (struct command) {.command = 0, .count = 1, .value = position};
|
||
|
}
|
||
|
}
|
||
|
if (current_command -> command)
|
||
|
previous_data = 0;
|
||
|
else
|
||
|
previous_data += current_command -> count;
|
||
|
position += (current_command ++) -> count;
|
||
|
}
|
||
|
optimize(commands, current_command - commands);
|
||
|
repack(&commands, length);
|
||
|
return commands;
|
||
|
}
|
||
|
|
||
|
struct command find_best_copy (const unsigned char * data, unsigned short position, unsigned short length, const unsigned char * bitflipped, unsigned flags) {
|
||
|
struct command simple = {.command = 7};
|
||
|
struct command flipped = simple, backwards = simple;
|
||
|
short count, offset;
|
||
|
if ((count = scan_forwards(data + position, length - position, data, position, &offset)))
|
||
|
simple = (struct command) {.command = 4, .count = count, .value = offset};
|
||
|
if ((count = scan_forwards(data + position, length - position, bitflipped, position, &offset)))
|
||
|
flipped = (struct command) {.command = 5, .count = count, .value = offset};
|
||
|
if ((count = scan_backwards(data, length - position, position, &offset)))
|
||
|
backwards = (struct command) {.command = 6, .count = count, .value = offset};
|
||
|
struct command command;
|
||
|
switch (flags / 24) {
|
||
|
case 0: command = pick_best_command(3, simple, backwards, flipped); break;
|
||
|
case 1: command = pick_best_command(3, backwards, flipped, simple); break;
|
||
|
case 2: command = pick_best_command(3, flipped, backwards, simple);
|
||
|
}
|
||
|
if ((flags & 4) && (command.count > SHORT_COMMAND_COUNT)) command.count = SHORT_COMMAND_COUNT;
|
||
|
return command;
|
||
|
}
|
||
|
|
||
|
unsigned short scan_forwards (const unsigned char * target, unsigned short limit, const unsigned char * source, unsigned short real_position, short * offset) {
|
||
|
unsigned short best_match, best_length = 0;
|
||
|
unsigned short current_length;
|
||
|
unsigned short position;
|
||
|
for (position = 0; position < real_position; position ++) {
|
||
|
if (source[position] != *target) continue;
|
||
|
for (current_length = 0; (current_length < limit) && (source[position + current_length] == target[current_length]); current_length ++);
|
||
|
if (current_length > MAX_COMMAND_COUNT) current_length = MAX_COMMAND_COUNT;
|
||
|
if (current_length < best_length) continue;
|
||
|
best_match = position;
|
||
|
best_length = current_length;
|
||
|
}
|
||
|
if (!best_length) return 0;
|
||
|
if ((best_match + LOOKBACK_LIMIT) >= real_position)
|
||
|
*offset = best_match - real_position;
|
||
|
else
|
||
|
*offset = best_match;
|
||
|
return best_length;
|
||
|
}
|
||
|
|
||
|
unsigned short scan_backwards (const unsigned char * data, unsigned short limit, unsigned short real_position, short * offset) {
|
||
|
if (real_position < limit) limit = real_position;
|
||
|
unsigned short best_match, best_length = 0;
|
||
|
unsigned short current_length;
|
||
|
unsigned short position;
|
||
|
for (position = 0; position < real_position; position ++) {
|
||
|
if (data[position] != data[real_position]) continue;
|
||
|
for (current_length = 0; (current_length <= position) && (current_length < limit) &&
|
||
|
(data[position - current_length] == data[real_position + current_length]); current_length ++);
|
||
|
if (current_length > MAX_COMMAND_COUNT) current_length = MAX_COMMAND_COUNT;
|
||
|
if (current_length < best_length) continue;
|
||
|
best_match = position;
|
||
|
best_length = current_length;
|
||
|
}
|
||
|
if (!best_length) return 0;
|
||
|
if ((best_match + LOOKBACK_LIMIT) >= real_position)
|
||
|
*offset = best_match - real_position;
|
||
|
else
|
||
|
*offset = best_match;
|
||
|
return best_length;
|
||
|
}
|
||
|
|
||
|
struct command find_best_repetition (const unsigned char * data, unsigned short position, unsigned short length) {
|
||
|
if ((position + 1) >= length) return data[position] ? ((struct command) {.command = 7}) : ((struct command) {.command = 3, .count = 1});
|
||
|
unsigned char value[2] = {data[position], data[position + 1]};
|
||
|
unsigned repcount, limit = length - position;
|
||
|
if (limit > MAX_COMMAND_COUNT) limit = MAX_COMMAND_COUNT;
|
||
|
for (repcount = 2; (repcount < limit) && (data[position + repcount] == value[repcount & 1]); repcount ++);
|
||
|
struct command result;
|
||
|
result.count = repcount;
|
||
|
if (*value != value[1]) {
|
||
|
if (!*value && (repcount < 3)) return (struct command) {.command = 3, .count = 1};
|
||
|
result.command = 2;
|
||
|
result.value = ((unsigned) (*value)) | (((unsigned) (value[1])) << 8);
|
||
|
} else if (*value) {
|
||
|
result.command = 1;
|
||
|
result.value = *value;
|
||
|
} else
|
||
|
result.command = 3;
|
||
|
return result;
|
||
|
}
|