You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
3.7 KiB
149 lines
3.7 KiB
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
// This is a LZ77-esque compression algorithm.
|
|
// However, unlike LZ77, it decompresses itself in-place.
|
|
// This is done by storing and reading the compressed data _backwards_,
|
|
// and writing the decompressed data far beyond the end of the compressed blob.
|
|
// Of course, that's not a *requirement* of any sort.
|
|
void nitro_decompress(void *header)
|
|
{
|
|
if (!header) return;
|
|
|
|
unsigned dest_offset = *((uint32_t *)header - 1);
|
|
unsigned data_offset = *((uint32_t *)header - 2) >> 24;
|
|
unsigned end_offset = *((uint32_t *)header - 2) & 0xFFFFFF;
|
|
|
|
// Compressed size: end_offset
|
|
// Header + padding size: data_offset
|
|
// Decompressed size: dest_offset + end_offset + 1
|
|
|
|
uint8_t *dest = (uint8_t *)header + dest_offset;
|
|
uint8_t *data = (uint8_t *)header - data_offset;
|
|
uint8_t *end = (uint8_t *)header - end_offset;
|
|
|
|
while (data > end) {
|
|
uint8_t flags = *--data;
|
|
|
|
for (int i = 8; i > 0; i--) {
|
|
if (flags & 0x80) {
|
|
unsigned length = ((*(data - 1) & 0xF0) >> 4) + 3;
|
|
unsigned offset = (((*(data - 1) & 0x0F) << 8) | *(data - 2)) + 2;
|
|
data -= 2;
|
|
|
|
for (unsigned x = length; x > 0; x--) {
|
|
char d = *(dest + offset);
|
|
*--dest = d;
|
|
}
|
|
} else {
|
|
*--dest = *--data;
|
|
}
|
|
flags <<= 1;
|
|
if (data <= end) break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void usage(const char *prgname)
|
|
{
|
|
fprintf(stderr, "Usage: %s [-o offset] [--] <in> <out>\n", prgname);
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
FILE *f;
|
|
int argi = 1;
|
|
|
|
int offset = 0;
|
|
|
|
for (;;) {
|
|
if (argc <= argi) usage(argv[0]);
|
|
if (argv[argi][0] != '-') break;
|
|
|
|
if (strcmp(argv[argi], "--") == 0) {
|
|
argi++;
|
|
break;
|
|
} else if (strcmp(argv[argi], "-o") == 0) {
|
|
if (argc <= argi + 1) {
|
|
fprintf(stderr, "-o: Missing offset\n");
|
|
usage(argv[0]);
|
|
}
|
|
offset = strtol(argv[argi + 1], NULL, 0);
|
|
argi += 2;
|
|
} else {
|
|
fprintf(stderr, "Unrecognized option: '%s'\n", argv[argi]);
|
|
usage(argv[0]);
|
|
}
|
|
}
|
|
|
|
if (argc < argi + 2) {
|
|
fprintf(stderr, "Missing parameters\n");
|
|
usage(argv[0]);
|
|
}
|
|
|
|
f = fopen(argv[argi + 0], "r");
|
|
if (!f) {
|
|
perror("fopen");
|
|
return 1;
|
|
}
|
|
|
|
// Find the "header" in the file
|
|
if (!offset) {
|
|
fseek(f, 0, SEEK_END);
|
|
} else if (offset < 0) {
|
|
fseek(f, offset, SEEK_END);
|
|
} else if (offset > 0) {
|
|
fseek(f, offset, SEEK_SET);
|
|
}
|
|
|
|
// Figure out the length and decompressed length of the file
|
|
uint32_t f_len;
|
|
uint32_t f_declen;
|
|
fseek(f, -(sizeof(f_declen) + sizeof(f_len)), SEEK_CUR);
|
|
if (fread(&f_len, sizeof(f_len), 1, f) != 1) {
|
|
perror("fread");
|
|
return 1;
|
|
}
|
|
if (fread(&f_declen, sizeof(f_declen), 1, f) != 1) {
|
|
perror("fread");
|
|
return 1;
|
|
}
|
|
f_len = f_len & 0xFFFFFF;
|
|
if (f_len % 4 != 0) f_len += 4 - (f_len % 4);
|
|
f_declen += f_len;
|
|
if (fseek(f, -(size_t)f_len, SEEK_CUR)) {
|
|
perror("fseek");
|
|
return 1;
|
|
}
|
|
|
|
// Read the file
|
|
void *file = malloc(f_declen);
|
|
if (!file) {
|
|
perror("malloc");
|
|
return 1;
|
|
}
|
|
if (fread(file, 1, f_len, f) != f_len) {
|
|
perror("fread");
|
|
return 1;
|
|
}
|
|
fclose(f);
|
|
|
|
nitro_decompress((char *)file + f_len);
|
|
|
|
// Save the output
|
|
f = fopen(argv[argi + 1], "w");
|
|
if (!f) {
|
|
perror("fopen");
|
|
return 1;
|
|
}
|
|
fwrite(file, f_declen, 1, f);
|
|
fclose(f);
|
|
free(file);
|
|
|
|
return 0;
|
|
}
|
|
|