From 7d141c7e79bb9688a475b88722f6101ad01ef782 Mon Sep 17 00:00:00 2001 From: jakob Date: Sun, 8 Jan 2017 20:22:46 -0500 Subject: Large commit. Filenames are now converted from UTF-16LE and placed into a linked list. Parsers also exist for every entry in the XP3 format. --- .gitignore | 1 + Nekopack_Logo.png | Bin 0 -> 1491576 bytes README.md | 4 +- src/cli.c | 27 ++++-- src/cli.h | 2 +- src/decompress.c | 109 ++++++++++++++++++++++ src/decompress.h | 26 ++++++ src/defs.h | 1 + src/extract.c | 134 +++++++++++++++++++++++++++ src/extract.h | 39 ++++++++ src/file.c | 132 +++++++++++++++++++++++++++ src/file.h | 29 ++++++ src/main.c | 35 +++----- src/parse.c | 264 ------------------------------------------------------ src/parse.h | 19 ---- src/write.c | 82 +++++++++++++++++ src/write.h | 24 +++++ 17 files changed, 613 insertions(+), 315 deletions(-) create mode 100644 Nekopack_Logo.png create mode 100644 src/decompress.c create mode 100644 src/decompress.h create mode 100644 src/extract.c create mode 100644 src/extract.h create mode 100644 src/file.c create mode 100644 src/file.h delete mode 100644 src/parse.c delete mode 100644 src/parse.h create mode 100644 src/write.c create mode 100644 src/write.h diff --git a/.gitignore b/.gitignore index 5958ffc..a0f403f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ bin/ obj/ dump.bin +README.md.new diff --git a/Nekopack_Logo.png b/Nekopack_Logo.png new file mode 100644 index 0000000..8100278 Binary files /dev/null and b/Nekopack_Logo.png differ diff --git a/README.md b/README.md index bf8f173..428b002 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -Nekopack +![Nekopack](https://raw.github.com/TsarFox/nekopack/master/Nekopack_Logo.png "Nekopack") ======== ## Software programmed and maintained by [Jakob.](http://jakob.space/) Nekopack is a work-in-progress attempt at reverse engineering the archive format used by Nekopara. +Please use this software responsibly. I chose to develop and release this because I see it as a very valuable teaching tool in accompaniment to my writeup, and I think that people who legally purchased Nekopara should have the right to view the game's content. I do not condone piracy or the redistribution of copyrighted materials in any way, shape, or form. + Nekopack is free software, licensed under the [GNU General Public License.](http://gnu.org/licenses/gpl.html) diff --git a/src/cli.c b/src/cli.c index dd3c263..481aaea 100644 --- a/src/cli.c +++ b/src/cli.c @@ -22,6 +22,7 @@ #include #include "cli.h" +#include "defs.h" #define VERSION "0.1.0" #define HELP_TEXT "A tool for decompressing the XP3 archives used by " \ @@ -35,8 +36,8 @@ /* General subroutine for parsing command-line arguments. Returns a "configuration" structure containing everything parsed from argv. */ struct configuration parse_args(int argc, char *argv[]) { - if (argc != 2) { - fprintf(stderr, "Usage: %s [OPTIONS] (ARCHIVE PATH)\n", argv[0]); + if (argc < 2) { + fprintf(stderr, "Usage: %s (ARCHIVE PATH) [OPTIONS]\n", argv[0]); exit(1); } @@ -45,25 +46,39 @@ struct configuration parse_args(int argc, char *argv[]) { struct configuration parsed; memset(&parsed, 0, sizeof(parsed)); - int current = 0, option_index = 0; + /* count represents the number of options parsed, regardless of + whether or not they were "long." This is kept track of so that + the user can supply an archive path as a positional argument. */ + int current = 0, option_index = 0, count = 0; static struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {NULL, 0, NULL, 0} }; - while (current >= 0) { + do { current = getopt_long(argc, argv, "hv", long_options, &option_index); switch (current) { case 'h': printf("Usage: %s [OPTIONS] (ARCHIVE PATH)\n\n", argv[0]); printf("%s\n", HELP_TEXT); - exit(0); + exit(EXIT_SUCCESS); case 'v': printf("Nekopack, version %s\nProgrammed by " "Jakob. \n", VERSION); - exit(0); + exit(EXIT_SUCCESS); } + count++; + } while (current >= 0); + + /* getopt "sorts" the argument array such that all of the flags come + first. argv[count] is the first positional argument encountered. */ + if (parsed.archive_path == NULL) { + if (argv[count] == NULL) { + fprintf(stderr, "No archive path given.\n"); + exit(EXIT_FAILURE); + } + parsed.archive_path = argv[count]; } return parsed; diff --git a/src/cli.h b/src/cli.h index 80b4f01..0008d63 100644 --- a/src/cli.h +++ b/src/cli.h @@ -19,7 +19,7 @@ /* Binary structure for storing command-line options. */ struct configuration { - long long padding; // This is temporary. + const char *archive_path; /* Path to archive to extract. */ }; /* General subroutine for parsing command-line arguments. Returns a diff --git a/src/decompress.c b/src/decompress.c new file mode 100644 index 0000000..dba637f --- /dev/null +++ b/src/decompress.c @@ -0,0 +1,109 @@ +/* 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 +#include + +#include + +#include "defs.h" +#include "extract.h" + + +/* Document and put in header. */ +Bytef *inflate_chunk(Bytef *chunk, uint64_t chunk_size, + uint64_t decompressed_size) { + Bytef *decompressed_data = malloc(decompressed_size); + + if (decompressed_data == NULL) { + fprintf(stderr, "Insufficient memory to decompress archive.\n"); + free(decompressed_data); + return NULL; + } + + 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(decompressed_data); + return NULL; + } + + int status_code; + do { + data_stream.avail_in = chunk_size; + if (data_stream.avail_in == 0) + break; + data_stream.next_in = chunk; + // 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); + + inflateEnd(&data_stream); + + return decompressed_data; +} + + +/* Wrapper for inflate_chunk which operates on FILE pointers. The file + pointer's contents are inflated into a memory stream and returned. */ +memory_stream decompress_file(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); + + /* This is a pretty shitty way of handling it, though. */ + if (compressed_data == NULL) { + fprintf(stderr, "Insufficient memory to decompress archive.\n"); + free(compressed_data); + fclose(archive); + exit(EXIT_FAILURE); + } + + fread(compressed_data, compressed_size, 1, archive); + /* if (ferror(archive)) { */ + /* fprintf(stderr, "File corrupt.\n"); */ + /* inflateEnd(&data_stream); */ + /* free(compressed_data); */ + /* free(decompressed_data); */ + /* exit(EXIT_FAILURE); */ + /* } */ + Bytef *decompressed_data = inflate_chunk(compressed_data, compressed_size, + decompressed_size); + + /* The compressed data is irrelevant at this point. */ + free(compressed_data); + if (decompressed_data == NULL) + exit(EXIT_FAILURE); + + return (memory_stream) {decompressed_size, + decompressed_data, + decompressed_data}; +} diff --git a/src/decompress.h b/src/decompress.h new file mode 100644 index 0000000..184b0f8 --- /dev/null +++ b/src/decompress.h @@ -0,0 +1,26 @@ +/* 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 . */ + +#pragma once + +#include + +#include "extract.h" + +/* Wrapper for inflate_chunk which operates on FILE pointers. The file + pointer's contents are inflated into a memory stream and returned. */ +memory_stream decompress_file(FILE *archive, uint64_t sizes_offset); diff --git a/src/defs.h b/src/defs.h index cafc102..f6dd535 100644 --- a/src/defs.h +++ b/src/defs.h @@ -15,6 +15,7 @@ You should have received a copy of the GNU General Public License along with Nekopack. If not, see . */ +#pragma once #define EXIT_SUCCESS 0 #define EXIT_FAILURE 1 diff --git a/src/extract.c b/src/extract.c new file mode 100644 index 0000000..8a1eec0 --- /dev/null +++ b/src/extract.c @@ -0,0 +1,134 @@ +/* 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" +#include "decompress.h" +#include "extract.h" +#include "file.h" +#include "write.h" + +#define ELIF_MAGIC 0x46696c65 +#define FILE_MAGIC 0x656c6946 +#define HNFN_MAGIC 0x6e666e68 + +void read_stream(void *destination, Bytef **source, size_t size); +void read_file_entry(memory_stream *data_stream, Bytef *section_end); +node *read_elif_entry(memory_stream *data_stream); + + +/* Handles decompression of the archive, as well as + parsing, decrypting and writing the table entries. */ +void extract(FILE *archive, uint64_t table_offset) { + /* Filenames are stored in a linked list as they're seen. + calloc is used to prevent the next pointer from being junk. */ + node *root = calloc(sizeof(node), 1); + + uint8_t compressed; + fseek(archive, table_offset, SEEK_SET); + fread(&compressed, sizeof(uint8_t), 1, archive); + + int stream_ended = 0; + memory_stream data_stream; + if (compressed) { + data_stream = decompress_file(archive, ftell(archive)); + } else { + fprintf(stderr, "This archive is not 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; + /* Implement "last filename" variable, which gets freed + and reallocated every time an elif entry is read. */ + do { + 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); + + switch (entry_magic) { + /* hnfn and eliF entries are identical. The size given by + the entry header doesn't match the actual entry size, + so it isn't passed to the function. */ + case HNFN_MAGIC: + case ELIF_MAGIC: + defer_node(read_elif_entry(&data_stream), root); + break; + case FILE_MAGIC: + read_file_entry(&data_stream, data_stream.data + entry_size); + break; + default: + printf("End of archive reached.\n"); + stream_ended = 1; + } + } while (!stream_ended); + + test_linked_list(root); + free_node(root); + + free(data_stream.start); +} + + +/* 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 */ +node *read_elif_entry(memory_stream *data_stream) { + uint16_t name_size; + uint32_t name_hash; + read_stream(&name_hash, &data_stream->data, sizeof(uint32_t)); + read_stream(&name_size, &data_stream->data, sizeof(uint16_t)); + + /* Strings are terminated by null bytes, + which aren't counted in the name size. */ + char *input_buffer = malloc(name_size * 2 + 2); + char *file_name = malloc(name_size + 1); + read_stream(input_buffer, &data_stream->data, name_size * 2 + 2); + + /* iconv is the non-portable glibc way of doing it. */ + char *in_start = input_buffer, *out_start = file_name; + size_t in_size = name_size * 2 + 2, out_size = name_size + 1; + iconv_t conversion = iconv_open("UTF-8", "UTF-16LE"); + iconv(conversion, &in_start, &in_size, &out_start, &out_size); + iconv_close(conversion); + + node *current = malloc(sizeof(node)); + current->key = name_hash; + current->file_name = file_name; + current->next = NULL; + + free(input_buffer); + + return current; +} diff --git a/src/extract.h b/src/extract.h new file mode 100644 index 0000000..1972af2 --- /dev/null +++ b/src/extract.h @@ -0,0 +1,39 @@ +/* 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 . */ + +#pragma once + +#include + +#include + +/* Structure representing a "stream" in memory. A pointer to the + start of the memory region is kept for freeing purposes. */ +typedef struct { + uint64_t stream_length; + Bytef *start; + Bytef *data; +} memory_stream; + +/* Handles decompression of the archive, as well as + parsing, decrypting and writing the table entries. */ +void extract(FILE *archive, uint64_t table_offset); + +/* 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); + diff --git a/src/file.c b/src/file.c new file mode 100644 index 0000000..1d4dc74 --- /dev/null +++ b/src/file.c @@ -0,0 +1,132 @@ +/* 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 +#include +#include +#include + +#include "extract.h" + +#define ADLR_MAGIC 0x726c6461 +#define SEGM_MAGIC 0x6d676573 +#define INFO_MAGIC 0x6f666e69 +#define TIME_MAGIC 0x656d6974 + +void read_info_chunk(memory_stream *data_stream); +void read_segm_chunk(memory_stream *data_stream); +void read_adlr_chunk(memory_stream *data_stream); +void read_time_chunk(memory_stream *data_stream); + + +/* Document and update documentation in header file. */ +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); + read_adlr_chunk(data_stream); + break; + case SEGM_MAGIC: + printf("[SEGM found at 0x%lx]\n", data_stream->data - data_stream->start); + read_segm_chunk(data_stream); + break; + case INFO_MAGIC: + printf("[INFO found at 0x%lx]\n", data_stream->data - data_stream->start); + read_info_chunk(data_stream); + break; + case TIME_MAGIC: + printf("[TIME found at 0x%lx]\n", data_stream->data - data_stream->start); + read_time_chunk(data_stream); + break; + default: + printf("New magic discovered: %" PRIx32 " size: %" PRIx64 "\n", entry_magic, entry_size); // Debug. + } + } + printf("\n\n\n\n"); // Debug. +} + + +/* 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("COMPRESSED_SIZE: %" PRIx64 "\n", compressed_size); + printf("DECOMPRESSED_SIZE: %" PRIx64 "\n", decompressed_size); + printf("MD5: "); + 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; +} + + +/* Document */ +void read_segm_chunk(memory_stream *data_stream) { + uint32_t flags; + uint64_t offset, compressed_size, decompressed_size; + read_stream(&flags, &data_stream->data, sizeof(uint32_t)); + read_stream(&offset, &data_stream->data, sizeof(uint64_t)); + read_stream(&compressed_size, &data_stream->data, sizeof(uint64_t)); + read_stream(&decompressed_size, &data_stream->data, sizeof(uint64_t)); + printf("\nSEGM SEGMENT\n"); + printf("------------\n"); + printf("FLAGS: %" PRIx32 "\n", flags); + printf("MEM_OFFSET: %" PRIx64 "\n", offset); + printf("DECOMPRESSED_SIZE: %" PRIx64 "\n", decompressed_size); + printf("COMPRESSED_SIZE: %" PRIx64 "\n\n", compressed_size); + /* Entries can be between the segment chunk and the actual file + data, so processing the file has to be deferred for later. */ +} + + +/* Document */ +void read_adlr_chunk(memory_stream *data_stream) { + uint32_t key; + read_stream(&key, &data_stream->data, sizeof(uint32_t)); + printf("\nADLR SEGMENT\n"); + printf("------------\n"); + printf("KEY: %" PRIx32 "\n\n", key); +} + + +/* Document */ +void read_time_chunk(memory_stream *data_stream) { + uint64_t timestamp; + read_stream(×tamp, &data_stream->data, sizeof(uint64_t)); + printf("\nTIME SEGMENT\n"); + printf("------------\n"); + printf("TIMESTAMP: %" PRIx64 "\n\n", timestamp); +} diff --git a/src/file.h b/src/file.h new file mode 100644 index 0000000..c8dcc1d --- /dev/null +++ b/src/file.h @@ -0,0 +1,29 @@ +/* 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 . */ + +#pragma once + +#include + +typedef struct { + uint32_t filename_key; + uint64_t timestamp, compressed_size, decompressed_size, offset; +} file_entry; +// Decompressed/compressed size same in info/segm? + +/* Document and update documentation in header file. */ +void read_file_entry(memory_stream *data_stream, Bytef *section_end); diff --git a/src/main.c b/src/main.c index 2665b2f..efda3bc 100644 --- a/src/main.c +++ b/src/main.c @@ -15,6 +15,7 @@ You should have received a copy of the GNU General Public License along with Nekopack. If not, see . */ +#include #include // Needed for debugging at this point. #include #include @@ -22,7 +23,7 @@ #include #include "cli.h" -#include "parse.h" +#include "extract.h" #define XP3_MAGIC "XP3\x0d\x0a\x20\x0a\x1a\x8b\x67\x01" #define XP3_TABLE_OFFSET 11 @@ -32,14 +33,16 @@ int is_xp3_archive(FILE *archive); int get_archive_version(FILE *archive); uint64_t get_table_offset(FILE *archive, uint8_t archive_version); +/* Global instance of the command-line configuration structure. */ +struct configuration arguments; + int main(int argc, char *argv[]) { - struct configuration arguments = parse_args(argc, argv); + arguments = parse_args(argc, argv); - FILE *archive = fopen(argv[1], "rb"); + FILE *archive = fopen(arguments.archive_path, "rb"); if (archive == NULL) { - // perror? - fprintf(stderr, "Cannot open file.\n"); + perror(arguments.archive_path); exit(EXIT_FAILURE); } else if (!is_xp3_archive(archive)) { fprintf(stderr, "File is not an XP3 archive.\n"); @@ -50,21 +53,6 @@ int main(int argc, char *argv[]) { int archive_version = get_archive_version(archive); uint64_t table_offset = get_table_offset(archive, archive_version); extract(archive, table_offset); - - /* After the table_is_compressed byte is 8 bytes containing the - compressed size followed by 8 bytes containing the original size - (if the archive is compressed). Use zlib's inflate function to - decompress the compressed size worth of chunks if it's compressed. - - The decompressed data will contain a four-byte magic number, - followed by 8 bytes containing the file size, followed by the - file size worth of data. - - See the following link for magic numbers and entry handling - procedures. - - https://github.com/vn-tools/arc_unpacker/blob/master/src/dec - /kirikiri/xp3_archive_decoder.cc */ fclose(archive); return 0; } @@ -74,8 +62,6 @@ int main(int argc, char *argv[]) { whether or not it represents a valid XP3 archive. */ int is_xp3_archive(FILE *archive) { char* magic_buffer = malloc(11); - /* The magic number is at the very beginning of the file, so - rewind must be called on the archive's file pointer. */ rewind(archive); fread(magic_buffer, 11, 1, archive); if (memcmp(magic_buffer, XP3_MAGIC, 11)) { @@ -100,12 +86,12 @@ int get_archive_version(FILE *archive) { /* Subroutine for finding the archive's table offset. If the minor_version is invalid, the program will exit. */ uint64_t get_table_offset(FILE *archive, uint8_t archive_version) { - fseek(archive, XP3_TABLE_OFFSET, SEEK_SET); uint64_t table_offset; + fseek(archive, XP3_TABLE_OFFSET, SEEK_SET); fread(&table_offset, sizeof(uint64_t), 1, archive); if (archive_version == 1) return table_offset; - /* Version 2 of XP3 contains a minor version field. */ + /* The minor version is only present in XP3 version 2. */ uint32_t minor_version; fread(&minor_version, sizeof(uint32_t), 1, archive); if (minor_version != 1) { @@ -115,6 +101,7 @@ uint64_t get_table_offset(FILE *archive, uint8_t archive_version) { /* The read table_offset is actually an offset to the real table offset. XP3 Version 2 is a little strange. */ fseek(archive, table_offset, SEEK_SET); + /* Flags and size of table are ignored in the parsing process. */ fseek(archive, sizeof(uint8_t) + sizeof(uint64_t), SEEK_CUR); fread(&table_offset, sizeof(uint64_t), 1, archive); return table_offset; diff --git a/src/parse.c b/src/parse.c deleted file mode 100644 index c53a627..0000000 --- a/src/parse.c +++ /dev/null @@ -1,264 +0,0 @@ -/* 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; -} diff --git a/src/parse.h b/src/parse.h deleted file mode 100644 index c258711..0000000 --- a/src/parse.h +++ /dev/null @@ -1,19 +0,0 @@ -/* 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 . */ - - -void extract(FILE *archive, uint64_t table_offset); diff --git a/src/write.c b/src/write.c new file mode 100644 index 0000000..3cf04ca --- /dev/null +++ b/src/write.c @@ -0,0 +1,82 @@ +/* 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 +#include +#include +#include + +#include "write.h" + + +/* Document */ +char *pop_file_name(uint32_t key, node *root) { + /* The root node shouldn't contain anything. */ + if (root->next == NULL) + return NULL; + + const char *file_name = NULL; + node *current = root->next, *last = root; + for (;;) { + if (current->key == key) { + file_name = current->file_name; + last->next = current->next; + free(current->file_name); + free(current); + break; + } else if (current->next == NULL) { + break; + } else { + last = current; + current = current->next; + } + } + return file_name; +} + + +/* Document */ +void defer_node(node *new, node *root) { + node *current = root; + for (;;) { + if (current->next == NULL) { + current->next = new; + break; + } else { + current = current->next; + } + } +} + + +void test_linked_list(node *root) { + for (node *current = root; current != NULL; current = current->next) { + printf("NODE\n----\n"); + printf("KEY: %" PRIx32 "\n", current->key); + printf("FILE_NAME: %s\n\n", current->file_name); + } +} + + +/* Document */ +void free_node(node *base) { + if (base->next != NULL) { + free_node(base->next); + return; + } + free(base); +} diff --git a/src/write.h b/src/write.h new file mode 100644 index 0000000..4b8c717 --- /dev/null +++ b/src/write.h @@ -0,0 +1,24 @@ +/* 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 . */ + +/* Because File entries won't necessarily follow the associated + eliF entry, filenames and hashes are stored in a linked list. */ +typedef struct node { + uint32_t key; + char *file_name; + struct node *next; +} node; -- cgit v1.3