pokered/tools/pkmncompress.c

365 lines
8.3 KiB
C
Raw Permalink Normal View History

#define PROGRAM_NAME "pkmncompress"
#define USAGE_OPTS "[-h|--help] [-u|--uncompress] infile.2bpp outfile.pic"
#include "common.h"
void parse_args(int argc, char *argv[], bool *uncomp) {
struct option long_options[] = {
{"uncompress", no_argument, 0, 'u'},
{"help", no_argument, 0, 'h'},
{0}
};
for (int opt; (opt = getopt_long(argc, argv, "uh", long_options)) != -1;) {
switch (opt) {
case 'u':
*uncomp = true;
break;
case 'h':
usage_exit(0);
break;
default:
usage_exit(1);
}
}
}
uint8_t output[15 * 15 * 0x10];
int cur_bit;
int cur_byte;
void write_bit(int bit) {
if (++cur_bit == 8) {
cur_byte++;
cur_bit = 0;
}
output[cur_byte] |= bit << (7 - cur_bit);
}
int read_bit(uint8_t *data) {
if (cur_bit == -1) {
cur_byte++;
cur_bit = 7;
}
return (data[cur_byte] >> cur_bit--) & 1;
}
void transpose_tiles(uint8_t *data, int width) {
int size = width * width;
for (int i = 0; i < size; i++) {
int j = (i * width + i / width) % size;
if (i < j) {
uint8_t tmp[0x10];
uint8_t *p = data + i * COUNTOF(tmp);
uint8_t *q = data + j * COUNTOF(tmp);
memcpy(tmp, p, COUNTOF(tmp));
memcpy(p, q, COUNTOF(tmp));
memcpy(q, tmp, COUNTOF(tmp));
}
}
}
void compress_plane(uint8_t *plane, int width) {
2022-10-14 20:21:42 +00:00
static int gray_codes[2][0x10] = {
{0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4, 0xC, 0xD, 0xF, 0xE, 0xA, 0xB, 0x9, 0x8},
{0x8, 0x9, 0xB, 0xA, 0xE, 0xF, 0xD, 0xC, 0x4, 0x5, 0x7, 0x6, 0x2, 0x3, 0x1, 0x0},
};
int ram_size = width * width * 8;
for (int i = 0, nybble_lo = 0; i < ram_size; i++) {
int m = i % width;
if (!m) {
nybble_lo = 0;
}
int j = i / width + m * width * 8;
int nybble_hi = (plane[j] >> 4) & 0xF;
2022-10-14 20:21:42 +00:00
int code_hi = gray_codes[nybble_lo & 1][nybble_hi];
nybble_lo = plane[j] & 0xF;
2022-10-14 20:21:42 +00:00
int code_lo = gray_codes[nybble_hi & 1][nybble_lo];
plane[j] = (code_hi << 4) | code_lo;
}
}
void rle_encode_number(int n) {
int bit_count = -1;
int v = ++n;
v++;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v -= v >> 1;
v--;
int number = n - v;
while (v) {
v >>= 1;
bit_count++;
}
for (int j = 0; j < bit_count; j++) {
write_bit(1);
}
write_bit(0);
for (int j = bit_count; j >= 0; j--) {
write_bit((number >> j) & 1);
}
}
void write_data_packet(uint8_t *bit_groups, int n) {
for (int i = 0; i < n; i++) {
write_bit((bit_groups[i] >> 1) & 1);
write_bit(bit_groups[i] & 1);
}
}
int interpret_compress(uint8_t *planes[2], int mode, int order, int width) {
int ram_size = width * width * 8;
uint8_t *rams[2] = {xmalloc(ram_size), xmalloc(ram_size)};
memcpy(rams[0], planes[order], ram_size);
memcpy(rams[1], planes[order ^ 1], ram_size);
if (mode != 0) {
for (int i = 0; i < ram_size; i++) {
rams[1][i] ^= rams[0][i];
}
}
compress_plane(rams[0], width);
if (mode != 1) {
compress_plane(rams[1], width);
}
cur_bit = 7;
cur_byte = 0;
memset(output, 0, COUNTOF(output));
output[0] = (width << 4) | width;
write_bit(order);
uint8_t bit_groups[15 * 4 * 15 * 8] = {0};
int index = 0;
for (int plane = 0; plane < 2; plane++) {
int type = 0;
int nums = 0;
2022-10-14 20:21:42 +00:00
memset(bit_groups, 0, COUNTOF(bit_groups));
for (int x = 0; x < width; x++) {
for (int bit = 0; bit < 8; bit += 2) {
for (int y = 0, byte = x * width * 8; y < width * 8; y++, byte++) {
int bit_group = (rams[plane][byte] >> (6 - bit)) & 3;
if (bit_group) {
if (type == 0) {
write_bit(1);
} else if (type == 1) {
rle_encode_number(nums);
}
type = 2;
bit_groups[index++] = bit_group;
nums = 0;
} else {
if (type == 0) {
write_bit(0);
} else if (type == 1) {
nums++;
} else {
write_data_packet(bit_groups, index);
write_bit(0);
write_bit(0);
}
type = 1;
2022-10-14 20:21:42 +00:00
memset(bit_groups, 0, COUNTOF(bit_groups));
index = 0;
}
}
}
}
if (type == 1) {
rle_encode_number(nums);
} else {
write_data_packet(bit_groups, index);
}
if (!plane) {
if (mode == 0) {
write_bit(0);
} else {
write_bit(1);
write_bit(mode - 1);
}
}
}
free(rams[0]);
free(rams[1]);
return (cur_byte + 1) * 8 + cur_bit;
}
int get_width(long filesize) {
int width = 0;
for (int w = 1; w < 16; w++) {
if (filesize == w * w * 0x10) {
width = w;
break;
}
}
if (!width) {
error_exit("Image is not a square, or is larger than 15x15 tiles");
}
return width;
}
int compress(uint8_t *data, long filesize) {
int width = get_width(filesize);
int ram_size = width * width * 8;
uint8_t *planes[2] = {xmalloc(ram_size), xmalloc(ram_size)};
transpose_tiles(data, width);
for (int i = 0; i < ram_size; i++) {
planes[0][i] = data[i * 2];
planes[1][i] = data[i * 2 + 1];
}
uint8_t current[COUNTOF(output)] = {0};
int compressed_size = -1;
for (int mode = 0; mode < 3; mode++) {
for (int order = 0; order < 2; order++) {
if (mode == 0 && order == 0) {
continue;
}
int new_size = interpret_compress(planes, mode, order, width);
if (compressed_size == -1 || new_size < compressed_size) {
compressed_size = new_size;
2022-10-14 20:21:42 +00:00
memset(current, 0, COUNTOF(current));
memcpy(current, output, compressed_size / 8);
}
}
}
memset(output, 0, COUNTOF(output));
memcpy(output, current, compressed_size / 8);
free(planes[0]);
free(planes[1]);
return compressed_size / 8;
}
int read_int(uint8_t *data, int count) {
int n = 0;
while (count--) {
n = (n << 1) | read_bit(data);
}
return n;
}
uint8_t *fill_plane(uint8_t *data, int width) {
static int table[0x10] = {
0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF,
};
int mode = read_bit(data);
int size = width * width * 0x20;
uint8_t *plane = xmalloc(size);
int len = 0;
while (len < size) {
if (mode) {
while (len < size) {
int bit_group = read_int(data, 2);
if (!bit_group) {
break;
}
plane[len++] = bit_group;
}
} else {
size_t w = 0;
while (read_bit(data)) {
w++;
}
if (w >= COUNTOF(table)) {
error_exit("Invalid compressed data");
}
int n = table[w] + read_int(data, w + 1);
while (len < size && n--) {
plane[len++] = 0;
}
}
mode ^= 1;
}
if (len > size) {
error_exit("Invalid compressed data");
}
uint8_t *ram = xmalloc(size);
len = 0;
for (int y = 0; y < width; y++) {
for (int x = 0; x < width * 8; x++) {
for (int i = 0; i < 4; i++) {
ram[len++] = plane[(y * 4 + i) * width * 8 + x];
}
}
}
for (int i = 0; i < size - 3; i += 4) {
ram[i / 4] = (ram[i] << 6) | (ram[i + 1] << 4) | (ram[i + 2] << 2) | ram[i + 3];
}
free(plane);
return ram;
}
void uncompress_plane(uint8_t *plane, int width) {
static int codes[2][0x10] = {
{0x0, 0x1, 0x3, 0x2, 0x7, 0x6, 0x4, 0x5, 0xF, 0xE, 0xC, 0xD, 0x8, 0x9, 0xB, 0xA},
{0xF, 0xE, 0xC, 0xD, 0x8, 0x9, 0xB, 0xA, 0x0, 0x1, 0x3, 0x2, 0x7, 0x6, 0x4, 0x5},
};
for (int x = 0; x < width * 8; x++) {
int bit = 0;
for (int y = 0; y < width; y++) {
int i = y * width * 8 + x;
int nybble_hi = (plane[i] >> 4) & 0xF;
int code_hi = codes[bit][nybble_hi];
bit = code_hi & 1;
int nybble_lo = plane[i] & 0xF;
int code_lo = codes[bit][nybble_lo];
bit = code_lo & 1;
plane[i] = (code_hi << 4) | code_lo;
}
}
}
int uncompress(uint8_t *data) {
cur_bit = 7;
int width = read_int(data, 4);
if (read_int(data, 4) != width) {
error_exit("Image is not a square");
}
int size = width * width * 8;
uint8_t *rams[2];
int order = read_bit(data);
rams[order] = fill_plane(data, width);
int mode = read_bit(data);
if (mode) {
mode += read_bit(data);
}
rams[order ^ 1] = fill_plane(data, width);
uncompress_plane(rams[order], width);
if (mode != 1) {
uncompress_plane(rams[order ^ 1], width);
}
if (mode != 0) {
for (int i = 0; i < size; i++) {
rams[order ^ 1][i] ^= rams[order][i];
}
}
for (int i = 0; i < size; i++) {
output[i * 2] = rams[0][i];
output[i * 2 + 1] = rams[1][i];
}
transpose_tiles(output, width);
free(rams[0]);
free(rams[1]);
return size * 2;
}
int main(int argc, char *argv[]) {
bool uncomp = false;
parse_args(argc, argv, &uncomp);
argc -= optind;
argv += optind;
if (argc < 1) {
usage_exit(1);
}
long filesize;
uint8_t *data = read_u8(argv[0], &filesize);
int output_size = uncomp ? uncompress(data) : compress(data, filesize);
write_u8(argv[1], output, output_size);
free(data);
return 0;
}