summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjakob <jakob@memeware.net>2017-01-08 20:22:46 -0500
committerjakob <jakob@memeware.net>2017-01-08 20:22:46 -0500
commit7d141c7e79bb9688a475b88722f6101ad01ef782 (patch)
treec6afc8951270e57461ce25d32a32665ab52d7d6c /src
parentb3560b1005d3bc78a4f00a2279eb791209801a98 (diff)
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.
Diffstat (limited to 'src')
-rw-r--r--src/cli.c27
-rw-r--r--src/cli.h2
-rw-r--r--src/decompress.c109
-rw-r--r--src/decompress.h26
-rw-r--r--src/defs.h1
-rw-r--r--src/extract.c134
-rw-r--r--src/extract.h39
-rw-r--r--src/file.c132
-rw-r--r--src/file.h29
-rw-r--r--src/main.c35
-rw-r--r--src/parse.c264
-rw-r--r--src/write.c82
-rw-r--r--src/write.h (renamed from src/parse.h)9
13 files changed, 592 insertions, 297 deletions
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 <getopt.h>
#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. <http://tsar-fox.com/>\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 <http://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <zlib.h>
+
+#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 <http://www.gnu.org/licenses/>. */
+
+#pragma once
+
+#include <zlib.h>
+
+#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 <http://www.gnu.org/licenses/>. */
+#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 <http://www.gnu.org/licenses/>. */
+
+#include <inttypes.h> // Needed for debugging at this point.
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <iconv.h>
+#include <zlib.h>
+
+#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 <http://www.gnu.org/licenses/>. */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <zlib.h>
+
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#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(&timestamp, &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 <http://www.gnu.org/licenses/>. */
+
+#pragma once
+
+#include <stdint.h>
+
+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 <http://www.gnu.org/licenses/>. */
+#include <errno.h>
#include <inttypes.h> // Needed for debugging at this point.
#include <stdint.h>
#include <stdio.h>
@@ -22,7 +23,7 @@
#include <string.h>
#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 <http://www.gnu.org/licenses/>. */
-
-#include <inttypes.h> // Needed for debugging at this point.
-#include <stdio.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <iconv.h>
-#include <zlib.h>
-
-#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/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 <http://www.gnu.org/licenses/>. */
+
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#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/parse.h b/src/write.h
index c258711..4b8c717 100644
--- a/src/parse.h
+++ b/src/write.h
@@ -15,5 +15,10 @@
You should have received a copy of the GNU General Public License
along with Nekopack. If not, see <http://www.gnu.org/licenses/>. */
-
-void extract(FILE *archive, uint64_t table_offset);
+/* 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;