/* This file is part of Nekopack.
Copyright (C) 2017 Jakob Tsar-Fox, All Rights Reserved.
Nekopack is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
Nekopack is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with Nekopack. If not, see . */
#include // Needed for debugging at this point.
#include
#include
#include
#include
#include
#include
#include "defs.h"
#define ELIF_MAGIC 0x46696c65
#define FILE_MAGIC 0x656c6946
#define ADLR_MAGIC 0x726c6461
#define SEGM_MAGIC 0x6d676573
#define INFO_MAGIC 0x6f666e69
#define TIME_MAGIC 0x656d6974
/* A pointer to the start of the memory region
has to be kept for freeing purposes. */
typedef struct {
uint64_t stream_length;
Bytef *start;
Bytef *data;
} memory_stream;
/* typedef struct { */
/* uint64_t timestamp; */
/* uint32_t hash_key; */
/* } file_entry; */
memory_stream decompress_stream(FILE *archive, uint64_t sizes_offset);
void read_stream(void *destination, Bytef **source, size_t size);
void read_file_entry(memory_stream *data_stream, Bytef *section_end);
void read_elif_entry(memory_stream *data_stream);
void read_info_chunk(memory_stream *data_stream);
/* Parses and extracts entries from the archive. */
void extract(FILE *archive, uint64_t table_offset) {
/* An 8-bit unsigned integers at the table offset indicates
whether or not the archive has to be decompressed. */
uint8_t compressed;
fseek(archive, table_offset, SEEK_SET);
fread(&compressed, sizeof(uint8_t), 1, archive);
memory_stream data_stream;
if (compressed) {
data_stream = decompress_stream(archive, ftell(archive));
} else {
fprintf(stderr, "Uncompressed archives not yet supported.\n");
exit(EXIT_FAILURE);
}
/* The header for every entry in the XP3 archive format contains
a magic number, followed by the size of the entry. */
uint32_t entry_magic;
uint64_t entry_size;
for (;;) {
read_stream(&entry_magic, &data_stream.data, sizeof(uint32_t));
read_stream(&entry_size, &data_stream.data, sizeof(uint64_t));
printf("entry at 0x%lx\n", data_stream.data - data_stream.start - 12);
printf("Magic: %" PRIx32 " Size: %" PRIx64 "\n", entry_magic, entry_size); // Debug.
switch (entry_magic) {
case ELIF_MAGIC:
/* The size given by the entry header doesn't match the
actual entry size, so it isn't passed to the function. */
read_elif_entry(&data_stream);
break;
case FILE_MAGIC:
// printf("[File entry]\n");
// data_stream.data += entry_size;
read_file_entry(&data_stream, data_stream.data + entry_size);
break;
default: // Debug.
printf("Unknown magic: %x\n", entry_magic);
exit(EXIT_FAILURE);
}
}
free(data_stream.start);
}
/* Inflates the file pointer and returns a struct containing the
size and a pointer to the decompressed data in memory. */
memory_stream decompress_stream(FILE *archive, uint64_t sizes_offset) {
uint64_t compressed_size, decompressed_size;
fseek(archive, sizes_offset, SEEK_SET);
fread(&compressed_size, sizeof(uint64_t), 1, archive);
fread(&decompressed_size, sizeof(uint64_t), 1, archive);
/* Decompression is done in memory because it's $CURRENT_YEAR. */
Bytef *compressed_data = malloc(compressed_size);
Bytef *decompressed_data = malloc(decompressed_size);
/* This is a pretty shitty way of handling it, though. */
if (compressed_data == NULL || decompressed_data == NULL) {
if (compressed_data != NULL)
free(compressed_data);
fprintf(stderr, "Insufficient memory to decompress archive.\n");
fclose(archive);
exit(EXIT_FAILURE);
}
z_stream data_stream;
data_stream.zalloc = Z_NULL;
data_stream.zfree = Z_NULL;
data_stream.opaque = Z_NULL;
data_stream.avail_in = 0;
data_stream.next_in = Z_NULL;
if (inflateInit(&data_stream) != Z_OK) {
fprintf(stderr, "Could not initialize zlib.\n");
free(compressed_data);
free(decompressed_data);
fclose(archive);
exit(EXIT_FAILURE);
}
int status_code;
do {
fread(compressed_data, compressed_size, 1, archive);
data_stream.avail_in = compressed_size;
/* This really shouldn't happen. */
if (ferror(archive)) {
fprintf(stderr, "File corrupt.\n");
inflateEnd(&data_stream);
free(compressed_data);
free(decompressed_data);
exit(EXIT_FAILURE);
}
if (data_stream.avail_in == 0)
break;
data_stream.next_in = compressed_data;
// Flushing probably isn't required here.
do {
data_stream.avail_out = decompressed_size;
data_stream.next_out = decompressed_data;
status_code = inflate(&data_stream, Z_NO_FLUSH);
} while (data_stream.avail_out == 0);
} while (status_code != Z_STREAM_END);
/* The compressed data is irrelevant at this point. */
free(compressed_data);
return (memory_stream) {decompressed_size, decompressed_data,
decompressed_data};
}
/* Wrapper for memcpy which increments the source operand by
the amount of bytes read to simulate a file stream. */
void read_stream(void *destination, Bytef **source, size_t size) {
memcpy(destination, *source, size);
*source += size;
}
/* Document */
void read_file_entry(memory_stream *data_stream, Bytef *section_end) {
uint32_t entry_magic;
uint64_t entry_size;
while (data_stream->data < section_end) {
read_stream(&entry_magic, &data_stream->data, sizeof(uint32_t));
read_stream(&entry_size, &data_stream->data, sizeof(uint64_t));
switch (entry_magic) {
case ADLR_MAGIC:
printf("[ADLR found at 0x%lx]\n", data_stream->data - data_stream->start);
data_stream->data += entry_size;
break;
case SEGM_MAGIC:
printf("[SEGM found at 0x%lx]\n", data_stream->data - data_stream->start);
data_stream->data += entry_size;
break;
case INFO_MAGIC:
printf("[INFO found at 0x%lx]\n", data_stream->data - data_stream->start);
read_info_chunk(data_stream);
/* data_stream->data += entry_size; */
break;
case TIME_MAGIC:
printf("[TIME found at 0x%lx]\n", data_stream->data - data_stream->start);
data_stream->data += entry_size;
break;
default:
printf("New magic discovered: %" PRIx32 " size: %" PRIx64 "\n", entry_magic, entry_size); // Debug.
}
}
printf("\n\n\n\n"); // Debug.
}
/* Document */
void read_elif_entry(memory_stream *data_stream) {
uint16_t name_size;
/* The first part of an ELIF entry is a 32-bit file name hash. */
data_stream->data += sizeof(uint32_t);
read_stream(&name_size, &data_stream->data, sizeof(uint16_t));
char *input_buffer = malloc(name_size * 2);
char *file_name = malloc(name_size);
read_stream(input_buffer, &data_stream->data, name_size * 2);
/* There seems to be an extra UTF-16 byte at the end of the entry. */
data_stream->data += 2;
printf("Filename (ASCII): ");
for (int i = 0; i < name_size * 2; i++) {
if (input_buffer[i] >= 0x20 && input_buffer[i] < 0x7f)
printf("%c", input_buffer[i]);
}
printf("\n");
free(input_buffer);
free(file_name);
}
/* Document */
void read_info_chunk(memory_stream *data_stream) {
uint32_t flags;
uint64_t decompressed_size, compressed_size;
uint16_t file_name_size;
read_stream(&flags, &data_stream->data, sizeof(uint32_t));
read_stream(&decompressed_size, &data_stream->data, sizeof(uint64_t));
read_stream(&compressed_size, &data_stream->data, sizeof(uint64_t));
read_stream(&file_name_size, &data_stream->data, sizeof(uint16_t));
char *file_name = malloc(file_name_size * 2);
read_stream(file_name, &data_stream->data, file_name_size * 2);
printf("\nINFO SEGMENT\n");
printf("------------\n");
printf("FLAGS: %" PRIx32 "\n", flags);
printf("DECOMPRESSED_SIZE: %" PRIx64 "\n", decompressed_size);
printf("COMPRESSED_SIZE: %" PRIx64 "\n", compressed_size);
printf("FILENAME (HASH?): ");
for (int i = 0; i < file_name_size * 2; i++) {
if (file_name[i] >= 0x20 && file_name[i] < 0x7f)
printf("%c", file_name[i]);
}
printf("\n");
data_stream->data += 2;
}