mk64/tools/libtkmk00.c

640 lines
17 KiB
C

#include <stdint.h>
#include <string.h>
#include "libtkmk00.h"
#include "utils.h"
#if defined(_WIN32) || defined(_WIN64)
#include <io.h>
#include <fcntl.h>
#endif
#define TKMK00_VERSION "0.1"
static int32_t t1, t7, t8, t9, s0, s1, s3, s4, s6, s7, v0, v1;
// 0x400 allocated on stack
static uint16_t rgba_buf[0x40]; // SP[000]-SP[07F] - buffer of 32 RGBA colors
static uint16_t buffer80_u16[0x3F]; // SP[080]-SP[0FD]
static uint16_t bufferFE_u16[0x3F]; // SP[0FE]-SP[17B]
// SP[17C]-SP[19F] - preserved registers
static uint8_t byte_buffer[0x20]; // SP[1A0]-SP[1BF] - byte buffer
static uint8_t *some_ptrs[8]; // SP[1C0]-SP[1DF] - 8 pointers to A0 data read from starting at offset 0xC
static uint16_t some_u16s[8]; // SP[1E0]-SP[1EF] - 8 u16s, related to some_ptrs
static uint32_t some_u32s[0x80]; // SP[200]-SP[3F0] - indexes used to initialize buffer80 and bufferFE
static int32_t header6;
static int some_offset;
static int some_flags;
static const uint8_t *in_ptr;
static void proc_80040A60(void);
static void proc_80040AC8(void);
static void proc_80040BC0(uint32_t, uint32_t*);
static void proc_80040C54(void);
static void proc_80040C94(void);
// this is needed to perform logical shifts on signed data
static int32_t SRL(int32_t val, int amount)
{
uint32_t vU = (uint32_t)val;
vU >>= amount;
return (int32_t)vU;
}
// a0[in]: pointer to TKMK00 data
// a1[out]: pointer to output (1 byte per pixel)
// a2[out]: pointer to output (RGBA16, 2 bytes per pixel)
// a3[in]: RGBA color to set alpha to 0, values observed: 0x01, 0xBE
void tkmk00_decode(uint8_t *tkmk, uint8_t *tmp_buf, uint8_t *rgba16, int32_t alpha_color) // 800405D0/0411D0
{
unsigned offset;
unsigned test_bits;
int width, height;
int col, row;
int pixels;
int alpha;
unsigned i;
uint16_t rgba0;
uint16_t rgba1;
uint8_t red0, red1, green0, green1, blue0, blue1;
width = read_u16_be(&tkmk[0x8]);
height = read_u16_be(&tkmk[0xA]);
alpha = alpha_color;
header6 = tkmk[0x6];
pixels = width * height;
memset(rgba_buf, 0xFF, sizeof(rgba_buf));
memset(rgba16, 0x0, 2 * pixels);
memset(tmp_buf, 0x0, pixels);
for (i = 0; i < 8; i++) {
offset = read_u32_be(&tkmk[0xC + i*4]);
if (0 == (header6 & (0x1 << i))) {
offset -= 4;
}
some_ptrs[i] = tkmk + offset;
}
memset(some_u16s, 0, sizeof(some_u16s));
some_offset = 0x0; // no idea, used in proc_80040A60
some_flags = read_u32_be(&tkmk[0x2C]); // used in proc_80040A60
in_ptr = &tkmk[0x30];
uint32_t val = 0x20;
proc_80040BC0(DIM(some_u32s)-4, &val); // recursive
t1 = v0;
t7 = 0;
for (row = 0; row != height; row++) {
for (col = 0; col != width; col++) {
t9 = read_u16_be(rgba16);
if (t9 != 0) {
s3 = t9 & 0xFFFE;
t7 = t9;
if (alpha == s3) {
write_u16_be(rgba16, s3);
t7 = s3;
}
} else {
v1 = tmp_buf[0];
v1 += 1;
proc_80040AC8();
if (v0 == 0) {
write_u16_be(rgba16, t7);
} else {
v1 = 1;
proc_80040A60();
if (v0 != 0) {
proc_80040C54();
s0 = s4;
proc_80040C54();
s1 = s4;
proc_80040C54();
rgba0 = 0;
rgba1 = 0;
if (row != 0) {
rgba0 = read_u16_be(rgba16 - (width * 2));
rgba1 = read_u16_be(rgba16 - 2);
} else {
if (col != 0) {
rgba1 = read_u16_be(rgba16 - 2);
}
}
red0 = (rgba0 & 0x7C0) >> 6;
red1 = (rgba1 & 0x7C0) >> 6;
t8 = (red0 + red1) / 2;
t9 = s0;
proc_80040C94();
s0 = t9;
v1 = t9 - t8;
green0 = (rgba0 & 0xF800) >> 11;
green1 = (rgba1 & 0xF800) >> 11;
t8 = v1 + (green0 + green1) / 2;
if (t8 >= 0x20) {
t8 = 0x1F;
} else if (t8 < 0) {
t8 = 0;
}
t9 = s1;
proc_80040C94();
s1 = t9;
blue0 = (rgba0 & 0x3E) >> 1;
blue1 = (rgba1 & 0x3E) >> 1;
t8 = v1 + (blue0 + blue1) / 2;
if (t8 >= 0x20) {
t8 = 0x1F;
} else if (t8 < 0) {
t8 = 0;
}
t9 = s4;
proc_80040C94();
t7 = (s1 << 11) | (s0 << 6) | (t9 << 1);
if (t7 != alpha) {
t7 |= 0x1;
}
// insert new value by shifting others to right
for (i = DIM(rgba_buf) - 1; i > 0; i--) {
rgba_buf[i] = rgba_buf[i - 1];
}
rgba_buf[0] = t7;
} else {
v1 = 6;
proc_80040A60();
t7 = rgba_buf[v0];
if (v0 != 0) {
for (i = v0; i > 0; i--) {
rgba_buf[i] = rgba_buf[i - 1];
}
rgba_buf[0] = t7;
}
}
write_u16_be(rgba16, t7);
test_bits = 0;
if (col != 0) {
test_bits |= 0x01;
}
if (col < (width - 1)) {
test_bits |= 0x02;
}
if (col < (width - 2)) {
test_bits |= 0x04;
}
if (row < (height - 1)) {
test_bits |= 0x08;
}
if (row < (height - 2)) {
test_bits |= 0x10;
}
if (0x2 == (test_bits & 0x2)) {
tmp_buf[1]++;
}
if (0x4 == (test_bits & 0x4)) {
tmp_buf[2]++;
}
if (0x9 == (test_bits & 0x9)) {
tmp_buf[width - 1]++;
}
if (0x8 == (test_bits & 0x8)) {
tmp_buf[width]++;
}
if (0xA == (test_bits & 0xA)) {
tmp_buf[width + 1]++;
}
if (0x10 == (test_bits & 0x10)) {
tmp_buf[2*width]++;
}
v1 = 1;
proc_80040A60();
if (v0 != 0) {
uint8_t *out = rgba16;
s0 = width * 2;
s3 = t7 | 0x1;
do {
v1 = 2;
proc_80040A60();
if (v0 == 0) {
v1 = 1;
proc_80040A60();
if (v0 == 0) {
break;
} else {
v1 = 1;
proc_80040A60();
out += 4;
if (v0 == 0) {
out -= 8;
}
}
} else if (v0 == 1) {
out -= 2;
} else if (v0 == 3) {
out += 2;
}
out += s0;
write_u16_be(out, s3);
} while (1);
}
}
}
tmp_buf += 1;
rgba16 += 2;
}
}
}
// inputs: a0, a3, v1, t0
// outputs: a0, a3, t0, t8, t9, v0
static void proc_80040A60(void) // 80040A60/041660
{
unsigned this_offset;
this_offset = some_offset + v1;
t8 = 0x20 - v1;
v0 = SRL(some_flags, t8); // v0 = t0 >> t8;
if (this_offset < 0x21) {
if (this_offset != 0x20) {
some_flags <<= v1;
some_offset += v1;
} else {
some_flags = read_u32_be(in_ptr);
some_offset = 0;
in_ptr += 4;
}
} else {
this_offset = 0x40;
some_flags = read_u32_be(in_ptr);
this_offset -= v1;
this_offset -= some_offset;
some_offset -= t8;
t8 = SRL(some_flags, this_offset); // t8 = t0 >> t9;
v0 |= t8;
in_ptr += 4;
some_flags <<= some_offset;
}
}
// inputs: t2, v1
// outputs: t8, t9, s6, s7, v0
static void proc_80040AC8(void) // 80040AC8/0416C8
{
uint8_t *s6ptr;
t8 = SRL(header6, v1); // t8 = t2 >> v1;
t9 = t8 & 0x1;
s7 = some_u16s[v1];
if (t9 == 0) {
s6ptr = some_ptrs[v1];
if (s7 == 0) {
s6ptr += 4;
s7 = 0x20;
some_ptrs[v1] = s6ptr;
}
t9 = read_u32_be(s6ptr);
s7 -= 1;
some_u16s[v1] = s7;
v0 = SRL(t9, s7); // v0 = t9 >> s7;
v0 &= 0x1;
} else {
s6ptr = some_ptrs[v1];
if (s7 == 0) {
s7 = *s6ptr;
v0 = 0x100;
v0 <<= v1;
if ((s7 & 0x80) == 0x00) { // if (s7 >= 0) {
v0 = ~v0;
s7 += 3;
header6 &= v0;
} else {
s7 &= 0x7F;
s7 += 1;
header6 |= v0;
}
v0 = s6ptr[1];
s6ptr += 2;
s7 <<= 3;
byte_buffer[v1] = v0;
some_ptrs[v1] = s6ptr;
}
v0 = byte_buffer[v1];
s7 -= 1;
some_u16s[v1] = s7;
t8 = s7 & 0x7;
v0 = SRL(v0, t8); // v0 >>= t8;
v0 &= 0x1;
if (t8 == 0 && s7 != 0) {
t8 = 0x100;
s7 = t8 << v1;
s7 &= header6;
if (s7 != 0) {
s7 = s6ptr[0];
s6ptr += 1;
byte_buffer[v1] = s7;
some_ptrs[v1] = s6ptr;
}
}
}
}
// inputs: s3, s4
// outputs: v0, v1, s0, s1, s3, s4
static void proc_80040BC0(uint32_t u32idx, uint32_t *val) // 80040BC0/0417C0
{
u32idx--;
v1 = 0;
proc_80040AC8();
if (v0 != 0) {
uint32_t idx;
some_u32s[u32idx] = *val;
(*val)++;
proc_80040BC0(u32idx, val);
idx = some_u32s[u32idx];
buffer80_u16[idx] = v0;
proc_80040BC0(u32idx, val);
idx = some_u32s[u32idx];
u32idx++;
s6 = idx;
bufferFE_u16[idx] = v0;
v0 = s6;
} else {
s0 = 0;
for (s1 = 5; s1 != 0; s1--) {
v1 = 0;
proc_80040AC8();
s0 = v0 + s0 * 2;
}
u32idx++;
v0 = s0;
}
}
// inputs: t1
// outputs: s4, v0
static void proc_80040C54(void) // 80040C54/041854
{
s4 = t1;
while (s4 >= 0x20) {
v1 = 0;
proc_80040AC8();
if (v0 == 0) {
s4 = buffer80_u16[s4];
} else {
s4 = bufferFE_u16[s4];
}
}
}
// inputs: t8, t9
// outputs: v0, t9
static void proc_80040C94(void) // 80040C94/041894
{
if (t8 >= 0x10) {
v0 = (0x1F - t8) * 2;
if (v0 < t9) {
v0 = 0x1F;
t9 = v0 - t9;
} else {
v0 = t9 & 0x1;
t9 = SRL(t9, 1); // t9 >>= 1;
if (v0 != 0) {
t9 += t8 + 1;
} else {
t9 = t8 - t9;
}
}
} else {
v0 = t8 << 1;
if (v0 >= t9) {
v0 = t9 & 0x1;
t9 = SRL(t9, 1); // t9 >>= 1;
if (v0 != 0) {
t9 += t8 + 1;
} else {
t9 = t8 - t9;
}
}
}
}
// TKMK00 standalone executable
#ifdef TKMK00_STANDALONE
#include <stdlib.h>
typedef struct
{
char *in_filename;
char *out_filename;
char *tmp_filename;
unsigned int offset;
int compress;
uint32_t alpha_color;
} arg_config;
static arg_config default_config =
{
.in_filename = NULL,
.out_filename = NULL,
.tmp_filename = NULL,
.offset = 0x0,
.compress = 0,
.alpha_color = 0x01
};
static void print_usage(void)
{
ERROR("Usage: tkmk00 [-c / -d] [-o OFFSET] FILE [OUTPUT]\n"
"\n"
"tkmk00 v" TKMK00_VERSION ": TKMK00 compression and decompression tool\n"
"\n"
"Optional arguments:\n"
" -a color to use for alpha (default: 0x%02X)\n"
" -c compress raw RGBA16 data into TKMK00 (Not functional)\n"
" -d decompress TKMK00 into RGBA16 raw data (default: decompress)\n"
" -o OFFSET starting offset in FILE (default: 0x%X)\n"
" -t TMP_FILE save temp buffer data to TMP_FILE (default: do not save)\n"
"\n"
"File arguments:\n"
" FILE input file\n"
" [OUTPUT] output file (default: FILE.offset.bin)\n",
default_config.alpha_color,
default_config.offset);
exit(1);
}
// parse command line arguments
static void parse_arguments(int argc, char *argv[], arg_config *config)
{
int file_count = 0;
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-' && argv[i][1] != '\0') {
switch (argv[i][1]) {
case 'a':
if (++i >= argc) {
print_usage();
}
config->alpha_color = strtoul(argv[i], NULL, 0);
break;
case 'c':
config->compress = 1;
break;
case 'd':
config->compress = 0;
break;
case 'o':
if (++i >= argc) {
print_usage();
}
config->offset = strtoul(argv[i], NULL, 0);
break;
case 't':
if (++i >= argc) {
print_usage();
}
config->tmp_filename = argv[i];
break;
default:
print_usage();
break;
}
} else {
switch (file_count) {
case 0:
config->in_filename = argv[i];
break;
case 1:
config->out_filename = argv[i];
break;
default: // too many
print_usage();
break;
}
file_count++;
}
}
if (file_count < 1) {
print_usage();
}
}
static FILE *open_out_file(const char *out_file) {
if (strcmp(out_file, "-") == 0) {
#if defined(_WIN32) || defined(_WIN64)
_setmode(_fileno(stdout), _O_BINARY);
#endif
return stdout;
} else {
return fopen(out_file, "wb");
}
}
static int extract_tkmk00(const char *in_filename, const char *out_filename, const char *tmp_filename, uint32_t offset, uint32_t alpha_color)
{
FILE *out;
uint8_t *in_buf = NULL;
uint8_t *tmp_buf = NULL;
uint8_t *out_buf = NULL;
size_t bytes_written;
int w, h;
size_t tmp_size;
size_t out_size;
long in_size;
int ret_val = EXIT_SUCCESS;
in_size = read_file(in_filename, &in_buf);
if (in_size < 0) {
ERROR("Error: reading input file \"%s\"\n", in_filename);
return EXIT_FAILURE;
}
// verify input
if (memcmp(&in_buf[offset], "TKMK00", 6)) {
ERROR("Error: offset 0x%X does not begin with \"TKMK00\"\n", offset);
return EXIT_FAILURE;
}
w = read_u16_be(&in_buf[offset + 0x8]);
h = read_u16_be(&in_buf[offset + 0xA]);
// allocate output memory
out_size = 2 * w * h;
tmp_buf = calloc(1, out_size);
out_buf = calloc(1, out_size);
// run decoder
tkmk00_decode(&in_buf[offset], tmp_buf, out_buf, alpha_color);
// optionally write tmp file
if (tmp_filename) {
tmp_size = w * h;
bytes_written = write_file(tmp_filename, tmp_buf, tmp_size);
if (bytes_written < tmp_size) {
ERROR("Error writing to temp file \"%s\"\n", tmp_filename);
return EXIT_FAILURE;
}
}
out = open_out_file(out_filename);
if (out == NULL) {
ret_val = EXIT_FAILURE;
goto free_all;
}
// write to output files
bytes_written = fwrite(out_buf, 1, out_size, out);
if (bytes_written != out_size) {
ERROR("Error writing to output file \"%s\"\n", out_filename);
ret_val = EXIT_FAILURE;
}
// clean up
if (out != stdout) {
fclose(out);
}
free_all:
free(tmp_buf);
free(out_buf);
free(in_buf);
return ret_val;
}
int main(int argc, char *argv[])
{
char out_filename[FILENAME_MAX];
arg_config config;
int ret_val;
config = default_config;
parse_arguments(argc, argv, &config);
if (config.out_filename == NULL) {
config.out_filename = out_filename;
sprintf(config.out_filename, "%s.%06X.bin", config.in_filename, config.offset);
}
if (config.compress) {
ERROR("TKMK00 compression not implemented yet.\n");
ret_val = EXIT_FAILURE;
} else {
ret_val = extract_tkmk00(config.in_filename, config.out_filename, config.tmp_filename, config.offset, config.alpha_color);
}
return ret_val;
}
#endif // TKMK00_STANDALONE