diff options
| author | jakob <jakob@memeware.net> | 2017-03-01 19:03:20 -0500 |
|---|---|---|
| committer | jakob <jakob@memeware.net> | 2017-03-01 19:03:20 -0500 |
| commit | 0e67a05edcc3ba01221a6bfd14750b5b8090887f (patch) | |
| tree | 94729dcef273244164d6b84d72d17481818baa7f /src | |
| parent | d65a3ae8790ae28a90f0ebcf0bc4dc078c67dc35 (diff) | |
First working commit of the rewrite.
Diffstat (limited to 'src')
| -rw-r--r-- | src/cli.c | 109 | ||||
| -rw-r--r-- | src/cli.h | 48 | ||||
| -rw-r--r-- | src/compress.c | 11 | ||||
| -rw-r--r-- | src/compress.h | 5 | ||||
| -rw-r--r-- | src/crypto.c | 73 | ||||
| -rw-r--r-- | src/crypto.h | 50 | ||||
| -rw-r--r-- | src/header.c | 25 | ||||
| -rw-r--r-- | src/header.h | 6 | ||||
| -rw-r--r-- | src/io.c | 33 | ||||
| -rw-r--r-- | src/io.h | 9 | ||||
| -rw-r--r-- | src/main.c | 179 | ||||
| -rw-r--r-- | src/table.c | 140 | ||||
| -rw-r--r-- | src/table.h | 16 |
13 files changed, 659 insertions, 45 deletions
diff --git a/src/cli.c b/src/cli.c new file mode 100644 index 0000000..9f4a6bd --- /dev/null +++ b/src/cli.c @@ -0,0 +1,109 @@ +/* cli.c -- Code for providing a command-line interface. + + Copyright (C) 2017 Jakob Tsar-Fox, All Rights Reserved. + + This file is part of Nekopack. + + 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 <stdlib.h> +#include <string.h> + +#include <getopt.h> + +#include "cli.h" +#include "crypto.h" /* New, maybe clean up the section on game crypto? */ + + +/* Returns a params structure parsed from `argv`. */ +struct params parse_args(int argc, char **argv) { + struct params p = {0}; + + if (argc < 2) { + p.mode = USAGE; + return p; + } + p.mode = EXTRACT; + + int cur = 0, opt_index = 0, count = 0; + static struct option long_opts[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, + {"quiet", no_argument, NULL, 'q'}, + {"extract", no_argument, NULL, 'e'}, + {"list", no_argument, NULL, 'l'}, + {"output", no_argument, NULL, 'o'}, + {"game", no_argument, NULL, 'g'}, + {NULL, 0, NULL, 0} + }; + + do { + count++; + cur = getopt_long(argc, argv, "hvelqo:", long_opts, &opt_index); + switch (cur) { + case 'h': + p.mode = HELP; + return p; + case 'v': + p.mode = VERSION; + return p; + case 'q': + p.quiet = true; + break; + case 'o': + count++; + p.out_len = strlen(optarg); + p.out = malloc(p.out_len + 2); + strcpy(p.out, optarg); + if (p.out[p.out_len - 1] != '/') + p.out[p.out_len] = '/'; + break; + case 'g': + count++; + if (!strcmp(optarg, "nekopara_volume_0")) + p.game = NEKOPARA_VOLUME_0; + else if (!strcmp(optarg, "nekopara_volume_0_steam")) + p.game = NEKOPARA_VOLUME_0_STEAM; + else if (!strcmp(optarg, "nekopara_volume_1")) + p.game = NEKOPARA_VOLUME_1; + else if (!strcmp(optarg, "nekopara_volume_1_steam")) + p.game = NEKOPARA_VOLUME_1_STEAM; + case 'e': + p.mode = EXTRACT; + break; + case 'l': + p.mode = LIST; + } + } while (cur >= 0); + + if (count == argc) { + p.mode = USAGE; + return p; + } + p.vararg_index = count; + + if (p.out == NULL) { + p.out_len = 2; + p.out = malloc(3); + strcpy(p.out, "./"); + } + + return p; +} + + +/* Frees allocated memory associated with `p`. */ +void params_free(struct params p) { + free(p.out); +} diff --git a/src/cli.h b/src/cli.h new file mode 100644 index 0000000..6ccfd86 --- /dev/null +++ b/src/cli.h @@ -0,0 +1,48 @@ +/* cli.h -- Code for providing a command-line interface. + + Copyright (C) 2017 Jakob Tsar-Fox, All Rights Reserved. + + This file is part of Nekopack. + + 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 <stdbool.h> +#include <stddef.h> + +/* Identifiers for the mode of operation. */ +enum { + USAGE, + HELP, + VERSION, + LIST, + EXTRACT, +}; + +/* Structure for storing options set from the command-line. */ +struct params { + int mode; /* Current mode of operation. */ + int game; /* Which encryption keys to use. */ + bool quiet; /* Whether or not to suppress progress messages. */ + int vararg_index; /* Start index of paths in argv. */ + char *out; /* Path to extract files to. */ + size_t out_len; /* Length of the output path string. */ +}; + +/* Returns a params structure parsed from `argv`. */ +struct params parse_args(int argc, char **argv); + +/* Frees allocated memory associated with `p`. */ +void params_free(struct params p); diff --git a/src/compress.c b/src/compress.c index c27937b..0c81271 100644 --- a/src/compress.c +++ b/src/compress.c @@ -23,7 +23,8 @@ /* Inflates `s` into a newly allocated stream structure. */ -struct stream *inflate_stream(struct stream *s, size_t inflated_len) { +struct stream *stream_inflate(struct stream *s, size_t len, + size_t decompressed_len) { z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; @@ -35,16 +36,16 @@ struct stream *inflate_stream(struct stream *s, size_t inflated_len) { return NULL; int ret; - struct stream *n = stream_new(inflated_len); + struct stream *n = stream_new(decompressed_len); do { - strm.avail_in = s->len; + strm.avail_in = len; strm.next_in = (Bytef *) s->_cur; do { - strm.avail_out = inflated_len; + strm.avail_out = decompressed_len; strm.next_out = (Bytef *) n->_cur; ret = inflate(&strm, Z_NO_FLUSH); - if (ret != Z_OK) { + if (ret == Z_STREAM_ERROR) { stream_free(n); inflateEnd(&strm); return NULL; diff --git a/src/compress.h b/src/compress.h index 9d56e74..495a504 100644 --- a/src/compress.h +++ b/src/compress.h @@ -21,5 +21,6 @@ #include "io.h" -/* Decompresses `s` into a newly allocated stream structure. */ -struct stream *inflate_stream(struct stream *s, size_t inflated_len); +/* Inflates `s` into a newly allocated stream structure. */ +struct stream *stream_inflate(struct stream *s, size_t len, + size_t decompressed_len); diff --git a/src/crypto.c b/src/crypto.c new file mode 100644 index 0000000..1189882 --- /dev/null +++ b/src/crypto.c @@ -0,0 +1,73 @@ +/* crypto.c -- Code for deriving encryption keys. + + Copyright (C) 2017 Jakob Tsar-Fox, All Rights Reserved. + + This file is part of Nekopack. + + 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 <stdbool.h> +#include <stdint.h> + +#include "crypto.h" + + +/* Returns the game_key structure for a given game identifier, as + defined in crypto.h */ +struct game_key get_key(int game) { + struct game_key k = {0}; + switch (game) { + case NEKOPARA_VOLUME_0: + k.master = 0x1548e29c; + k.fallback_initial = 0x9c; + k.fallback_primary = 0xd7; + k.uses_initial = true; + break; + case NEKOPARA_VOLUME_0_STEAM: + k.master = 0x44528b87; + k.fallback_initial = 0x87; + k.fallback_primary = 0x23; + k.uses_initial = true; + break; + case NEKOPARA_VOLUME_1: + k.master = 0x1548e29c; + k.fallback_initial = 0x00; + k.fallback_primary = 0xd7; + k.uses_initial = false; + break; + case NEKOPARA_VOLUME_1_STEAM: + k.master = 0x44528b87; + k.fallback_initial = 0x00; + k.fallback_primary = 0x23; + k.uses_initial = false; + break; + } + return k; +} + + +/* Generates an initial key for the given key structure and file key. */ +uint8_t derive_initial(struct game_key k, uint64_t file_key) { + if (!k.uses_initial) return 0x00; + uint8_t initial = (k.master ^ file_key) & 0xff; + return initial == 0 ? k.fallback_initial : initial; +} + + +/* Generates a primary key for the given key structure and file key. */ +uint8_t derive_primary(struct game_key k, uint64_t file_key) { + uint32_t tmp = k.master ^ file_key; + uint8_t primary = (tmp >> 24 ^ tmp >> 16 ^ tmp >> 8 ^ tmp) & 0xff; + return primary == 0 ? k.fallback_primary : primary; +} diff --git a/src/crypto.h b/src/crypto.h new file mode 100644 index 0000000..0984cc7 --- /dev/null +++ b/src/crypto.h @@ -0,0 +1,50 @@ +/* crypto.h -- Code for deriving encryption keys. + + Copyright (C) 2017 Jakob Tsar-Fox, All Rights Reserved. + + This file is part of Nekopack. + + 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 <stdbool.h> +#include <stdint.h> + +/* Game identifiers for use with get_key. */ +enum { + NONE, + NEKOPARA_VOLUME_0, + NEKOPARA_VOLUME_0_STEAM, + NEKOPARA_VOLUME_1, + NEKOPARA_VOLUME_1_STEAM, +}; + +/* Structure containing game-specific encryption information. */ +struct game_key { + bool uses_initial; /* Whether or not to use an initial key. */ + uint32_t master; /* Master key specific to the game. */ + uint8_t fallback_initial; /* Fallback if initial key is 0. */ + uint8_t fallback_primary; /* Fallback if primary key is 0. */ +}; + +/* Returns the game_key structure for a given game identifier, as + defined in crypto.h */ +struct game_key get_key(int game); + +/* Generates an initial key for the given key structure and file key. */ +uint8_t derive_initial(struct game_key k, uint64_t file_key); + +/* Generates a primary key for the given key structure and file key. */ +uint8_t derive_primary(struct game_key k, uint64_t file_key); diff --git a/src/header.c b/src/header.c index 0161775..d451149 100644 --- a/src/header.c +++ b/src/header.c @@ -24,15 +24,15 @@ #include "header.h" #include "io.h" -static bool is_xp3(struct xp3_header *h); -static bool is_supported(struct xp3_header *h); +static bool is_xp3(struct header *h); +static bool is_supported(struct header *h); -/* Reads from the given stream into a newly allocated xp3_header - structure. NULL is returned if the header contains an invalid magic - number, or if the archive's version is not supported. */ -struct xp3_header *read_header(struct stream *s) { - struct xp3_header *h = malloc(sizeof(struct xp3_header)); +/* Reads from the given stream into a newly allocated header structure. + NULL is returned if the header contains an invalid magic number, or + if the archive's version is not supported. */ +struct header *read_header(struct stream *s) { + struct header *h = malloc(sizeof(struct header)); if (h == NULL) return NULL; /* The header structure can't be read into directly because of @@ -40,8 +40,8 @@ struct xp3_header *read_header(struct stream *s) { stream_read(h->magic, s, 11); stream_read(&h->info_offset, s, sizeof(uint64_t)); stream_read(&h->version, s, sizeof(uint32_t)); - stream_read(&h->table_size, s, sizeof(uint64_t)); stream_read(&h->flags, s, sizeof(uint8_t)); + stream_read(&h->table_size, s, sizeof(uint64_t)); stream_read(&h->table_offset, s, sizeof(uint64_t)); if (!is_xp3(h) || !is_supported(h)) { @@ -53,12 +53,13 @@ struct xp3_header *read_header(struct stream *s) { /* Checks that the header contains the correct magic number. */ -static bool is_xp3(struct xp3_header *h) { +static bool is_xp3(struct header *h) { return !memcmp(h->magic, XP3_MAGIC, 11); } -/* Checks that the archive's version is supported. */ -static bool is_supported(struct xp3_header *h) { - return h->version == 1; +/* Checks that the archive's version is supported, and that it is marked + as compatible with the KiriKiriZ engine. */ +static bool is_supported(struct header *h) { + return h->version == 1 && h->flags & 0x80; } diff --git a/src/header.h b/src/header.h index 7119f23..529e801 100644 --- a/src/header.h +++ b/src/header.h @@ -26,7 +26,7 @@ #define XP3_MAGIC "XP3\x0d\x0a\x20\x0a\x1a\x8b\x67\x01" /* Structure representing the header section of an XP3 archive. */ -struct xp3_header { +struct header { char magic[11]; /* Identifier for the archive. */ uint64_t info_offset; /* Offset to `table_size`. */ uint32_t version; /* Raw value containing the archive version. */ @@ -35,8 +35,8 @@ struct xp3_header { uint64_t table_offset; /* Offset to the archive table. */ }; -/* Reads data from the given stream into a newly allocated xp3_header +/* Reads data from the given stream into a newly allocated header structure. The stream is assumed to be at the header's beginning. NULL is returned if the header contains an invalid magic number, or if the archive's version is not supported. */ -struct xp3_header *read_header(struct stream *s); +struct header *read_header(struct stream *s); @@ -37,6 +37,33 @@ struct stream *stream_new(size_t len) { } +/* Copies `n` bytes from `s` into a new stream structure. */ +struct stream *stream_clone(struct stream *s, size_t n) { + struct stream *new = stream_new(n); + stream_write(new, s->_cur, n); + stream_rewind(new); /* New! This fixes the only bug we've had so far! */ + /* Look for other shit like this! */ + return new; +} + + +/* Maps the file at the given `path` into a stream structure. */ +struct stream *stream_from_file(char *path) { + FILE *fp = fopen(path, "rb"); + if (fp == NULL) return NULL; + struct stream *new = malloc(sizeof(struct stream)); + fseek(fp, 0, SEEK_END); + new->len = ftell(fp); + fseek(fp, 0, SEEK_SET); + new->_start = malloc(new->len); + new->_cur = new->_start; + fread(new->_start, new->len, 1, fp); + new->_loc = HEAP; + fclose(fp); + return new; +} + + /* Called to free or unmap the memory chunk associated with the given stream, as well as the stream structure itself. */ void stream_free(struct stream *s) { @@ -56,6 +83,12 @@ void stream_read(void *dest, struct stream *s, size_t n) { } +/* Dumps the contents of `s` into the file specified by `fp`. */ +void stream_dump(FILE *fp, struct stream *s, size_t n) { + fwrite(s->_cur, n, 1, fp); +} + + /* Copies `n` bytes into the given stream from the memory area specified by `src`. The stream's cursor is advanced appropriately. */ void stream_write(struct stream *s, void *src, size_t n) { @@ -43,6 +43,12 @@ struct stream { structure pointing to it. */ struct stream *stream_new(size_t len); +/* Copies `n` bytes from `s` into a new stream structure. */ +struct stream *stream_clone(struct stream *s, size_t n); + +/* Maps the file at the given `path` into a stream structure. */ +struct stream *stream_from_file(char *path); + /* Called to free or unmap the memory chunk associated with the given stream, as well as the stream structure itself. */ void stream_free(struct stream *s); @@ -55,6 +61,9 @@ void stream_read(void *dest, struct stream *s, size_t n); by `src`. The stream's cursor is advanced appropriately. */ void stream_write(struct stream *s, void *src, size_t n); +/* Dumps the contents of `s` into the file specified by `fp`. */ +void stream_dump(FILE *fp, struct stream *s, size_t n); + /* Applies an initial and primary key to the given stream, effectively encrypting or decrypting it. */ void stream_xor(struct stream *s, uint8_t initial, uint8_t primary); @@ -1,4 +1,4 @@ -/* main.c -- Entry point to Nekopack. +/* main.c -- Entry point to the program. Copyright (C) 2017 Jakob Tsar-Fox, All Rights Reserved. @@ -16,3 +16,180 @@ You should have received a copy of the GNU General Public License along with Nekopack. If not, see <http://www.gnu.org/licenses/>. */ + +#include <sys/stat.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "cli.h" +#include "compress.h" +#include "crypto.h" +#include "header.h" +#include "io.h" +#include "table.h" + +#define VERSION_STR "2.0.0b1" + +/* Pointer to a function to be mapped to entries in the table. */ +typedef void (*mapfn)(struct stream *archive, struct table_entry *e, + struct params p); + + +/* Writes usage information to stderr. */ +static void print_usage(char *prog_name) { + fprintf(stderr, "Usage: %s [OPTIONS] (ARCHIVES) [PATHS]\n", prog_name); +} + + +/* Writes versioning information to stdout. */ +static void print_version(void) { + printf("Nekopack version %s\nProgrammed by Jakob. " + "<http://jakob.space>\n", VERSION_STR); +} + + +/* Writes help information to stdout. */ +static void print_help(void) { + printf("A tool for decompressing the XP3 archives used by Nekopara.\n\n" + " -h, --help\t\tDisplay this help page and exit.\n" + " -v, --version\tDisplay the currently installed version and " + "exit.\n\n" + " -e, --extract\tExtract the contents of the archive. This is " + "the default.\n" + " -l, --list\t\tList the contents of the archive.\n\n" + " -o, --output\t\tPath to extract files to.\n" + " -g, --game\t\tGame the archive is from. Required for file " + "decryption\n" + " -q, --quiet\t\tDon't display information about extracted " + "files.\n\n" + "Supported games: nekopara_volume_0, nekopara_volume_0_steam,\n" + "nekopara_volume_1, nekopara_volume_1_steam\n"); +} + + +/* Inflates the table according to information in the header. */ +static struct stream *load_table(struct stream *s) { + uint8_t compressed; + uint64_t len, decompressed_len; + stream_read(&compressed, s, sizeof(uint8_t)); + stream_read(&len, s, sizeof(uint64_t)); + stream_read(&decompressed_len, s, sizeof(uint64_t)); + if (compressed) + return stream_inflate(s, len, decompressed_len); + return stream_clone(s, len); +} + + +/* Creates any directories that do not already exist in `path`. */ +void make_dirs(char *path) { + struct stat tmp = {0}; + char *buf = calloc(0x100, 1); + char *buf_start = buf; + + for (int i = 0; i < 0x100 && path[i] != '\0'; i++) { + if (path[i] == '/') { + *buf++ = '/'; + *buf = '\0'; + if (stat(buf_start, &tmp) == -1) + mkdir(buf_start, 0777); + } else { + *buf++ = path[i]; + } + } + free(buf_start); +} + + +/* Concatenates `name` and the output path specified in `p`. */ +static char *get_path(struct params p, char *name) { + size_t name_len = strlen(name); + char *path = malloc(p.out_len + name_len + 2); + strcpy(path, p.out); + strcpy(path + p.out_len + 1, name); + return path; +} + + +/* Basic mapfn for printing the filename of each entry. */ +static void list(struct stream *s, struct table_entry *e, struct params p) { + printf("%s\n", e->filename); +} + + +/* Mapfn for extracting the contents of the archive. */ +static void extract(struct stream *s, struct table_entry *e, struct params p) { + char *path = get_path(p, e->filename); + make_dirs(path); + + FILE *fp = fopen(path, "wb+"); + struct stream *segm_data; + struct segment *segm; + + for (uint64_t i = 0; i < e->segment_count; i++) { + segm = e->segments[i]; + stream_seek(s, segm->offset, SEEK_SET); + if (e->segments[i]->compressed) { + segm_data = stream_inflate(s, segm->compressed_size, + segm->decompressed_size); + } else { + segm_data = stream_clone(s, segm->compressed_size); + } + + struct game_key k = get_key(NEKOPARA_VOLUME_1); + uint8_t initial = derive_initial(k, e->key); + uint8_t primary = derive_primary(k, e->key); + stream_xor(segm_data, initial, primary); + + stream_dump(fp, segm_data, segm_data->len); + stream_free(segm_data); + } + + fclose(fp); + free(path); +} + + +/* Maps `fn` to every table entry found in the archive at `path`. */ +static void map_entries(char *path, struct params p, mapfn fn) { + struct stream *archive = stream_from_file(path); + struct header *h = read_header(archive); + stream_seek(archive, h->table_offset, SEEK_SET); + + struct stream *table = load_table(archive); + struct table_entry *root = read_table(table); + + for (struct table_entry *cur = root->next; cur != NULL; cur = cur->next) + fn(archive, cur, p); + + entry_free(root); + stream_free(table); + free(h); + stream_free(archive); +} + + +int main(int argc, char **argv) { + struct params p = parse_args(argc, argv); + switch (p.mode) { + case USAGE: + print_usage(argv[0]); + return 1; + case VERSION: + print_version(); + break; + case HELP: + print_help(); + break; + case LIST: + for (int i = p.vararg_index; i < argc; i++) + map_entries(argv[i], p, list); + break; + case EXTRACT: + for (int i = p.vararg_index; i < argc; i++) + map_entries(argv[i], p, extract); + } + params_free(p); + return 0; +} diff --git a/src/table.c b/src/table.c index a541ab9..37c6e81 100644 --- a/src/table.c +++ b/src/table.c @@ -31,11 +31,45 @@ #define HNFN_MAGIC 0x6e666e68 #define NEKO_MAGIC 0x6f6b656e +#define ADLR_MAGIC 0x726c6461 +#define SEGM_MAGIC 0x6d676573 +#define INFO_MAGIC 0x6f666e69 +#define TIME_MAGIC 0x656d6974 + + +/* Reads a segm chunk into the table_entry specified by `tmp`. */ +static void read_segm(struct stream *s, struct table_entry *tmp, uint64_t count) { + tmp->segment_count = count; + tmp->segments = malloc(sizeof(struct segment *) * count); + if (tmp->segments == NULL) return; + + for (uint64_t i = 0; i < count; i++) { + tmp->segments[i] = malloc(sizeof(struct segment)); + if (tmp->segments[i] == NULL) return; + stream_read(&tmp->segments[i]->compressed, s, sizeof(uint32_t)); + stream_read(&tmp->segments[i]->offset, s, sizeof(uint64_t)); + stream_read(&tmp->segments[i]->decompressed_size, s, sizeof(uint64_t)); + stream_read(&tmp->segments[i]->compressed_size, s, sizeof(uint64_t)); + } +} + + +/* Reads an adlr chunk into the table_entry specified by `tmp`. */ +static void read_adlr(struct stream *s, struct table_entry *tmp) { + stream_read(&tmp->key, s, sizeof(uint32_t)); +} + + +/* Reads a time chunk into the table_entry specified by `tmp`. */ +static void read_time(struct stream *s, struct table_entry *tmp) { + stream_read(&tmp->ctime, s, sizeof(uint64_t)); +} + /* Returns the root of a linked list containing all of the files listed in the archive's table section. */ -struct table_entry *parse_table(struct stream *s) { - struct table_entry *cur, *root = calloc(sizeof(struct table_entry), 1); +struct table_entry *read_table(struct stream *s) { + struct table_entry *root = calloc(sizeof(struct table_entry), 1); bool ended = false; uint32_t magic; @@ -50,12 +84,57 @@ struct table_entry *parse_table(struct stream *s) { case HNFN_MAGIC: case NEKO_MAGIC: read_elif(s, root); + break; case FILE_MAGIC: + read_file(s, root); break; default: ended = 1; } } while (!ended); + + return root; +} + + +/* Reads the contents of a File chunk. If there is an entry with a + matching key in the linked list specified by `root`, that structure + will be modified. Otherwise, a new entry will be created and appended + to the linked list. */ +void read_file(struct stream *s, struct table_entry *root) { + bool ended = false; + uint32_t magic; + uint64_t size; + struct table_entry *cur, *tmp = calloc(sizeof(struct table_entry), 1); + + do { + stream_read(&magic, s, sizeof(uint32_t)); + stream_read(&size, s, sizeof(uint64_t)); + + switch (magic) { + case ADLR_MAGIC: + read_adlr(s, tmp); + break; + case SEGM_MAGIC: + read_segm(s, tmp, size / 28); + break; + case INFO_MAGIC: + stream_seek(s, size, SEEK_CUR); + break; + case TIME_MAGIC: + read_time(s, tmp); + break; + default: + ended = true; + stream_seek(s, -sizeof(uint32_t) - sizeof(uint64_t), SEEK_CUR); + } + } while (!ended); + + cur = get_node(root, tmp->key); + cur->segment_count = tmp->segment_count; + cur->segments = tmp->segments; + cur->ctime = tmp->ctime; + free(tmp); } @@ -64,46 +143,64 @@ struct table_entry *parse_table(struct stream *s) { will be modified. Otherwise, a new entry will be created and appended to the linked list. */ void read_elif(struct stream *s, struct table_entry *root) { - char *name, *buf; - uint16_t name_size; + char *name, *buf, *tmp; + uint16_t name_len; uint32_t key; struct table_entry *cur; stream_read(&key, s, sizeof(uint32_t)); - stream_read(&name_size, s, sizeof(uint16_t)); + stream_read(&name_len, s, sizeof(uint16_t)); /* The value provided by the archive represents the number of UTF-16LE characters, not the number of bytes in the string. */ - name_size = name_size * 2 + 2; + name_len = name_len * 2 + 2; - for (cur = root; cur != NULL && cur->key != key; cur = cur->next); - if (cur == NULL) { - cur = calloc(sizeof(struct table_entry), 1); - if (cur == NULL) return; - entry_append(root, cur); + cur = get_node(root, key); + if (cur->filename != NULL) { + return; } - if (name_size < 0x100) { - buf = malloc(name_size); - name = malloc(name_size); + if (name_len < 0x100) { + buf = malloc(name_len); + name = malloc(name_len); if (buf == NULL || name == NULL) return; - stream_read(buf, s, name_size); - utf16le_decode(buf, name, name_size); + stream_read(buf, s, name_len); + utf16le_decode(buf, name, name_len); free(buf); - name = realloc(name, strlen(name) + 1); - if (name == NULL) return; + tmp = realloc(name, strlen(name) + 1); + if (tmp == NULL) { + free(name); + return; + } + name = tmp; } else { /* strdup isn't defined in ISO/IEC 9899:1999 C. */ name = malloc(14); if (name == NULL) return; strncpy(name, "COPYRIGHT.txt", 14); + stream_seek(s, name_len, SEEK_CUR); } cur->filename = name; } +/* Traverses `root` for a node with the given key. If the linked list + lacks a node with the key, a new node is created and appended. */ +struct table_entry *get_node(struct table_entry *root, uint32_t key) { + struct table_entry *cur; + for (cur = root; cur != NULL && cur->key != key; cur = cur->next); + if (cur == NULL) { + cur = calloc(sizeof(struct table_entry), 1); + if (cur == NULL) return NULL; + entry_append(root, cur); + cur->key = key; + } + return cur; +} + + /* Inserts `e` to the end of the linked list specified by `root`. */ void entry_append(struct table_entry *root, struct table_entry *e) { struct table_entry *cur; @@ -116,5 +213,12 @@ void entry_append(struct table_entry *root, struct table_entry *e) { void entry_free(struct table_entry *cur) { if (cur->next != NULL) entry_free(cur->next); + if (cur->filename != NULL) + free(cur->filename); + if (cur->segments != NULL) { + for (uint64_t i = 0; i < cur->segment_count; i++) + free(cur->segments[i]); + free(cur->segments); + } free(cur); } diff --git a/src/table.h b/src/table.h index c7c85bb..50124fe 100644 --- a/src/table.h +++ b/src/table.h @@ -35,20 +35,18 @@ struct segment { /* Structure representing an entry in the archive's table section. */ struct table_entry { - bool encrypted; /* Whether or not it's encrypted. */ - bool compressed; /* Whether or not it's compressed. */ - char *filename; /* String containing file's name. */ uint32_t key; /* File-specific key for encryption. */ uint64_t ctime; /* Timestamp of creation time. */ uint64_t segment_count; /* Number of segments. */ struct segment **segments; /* Array of associated segments. */ + char *filename; /* String containing file's name. */ struct table_entry *next; /* Pointer to the next entry. */ }; /* Returns the root of a linked list containing all of the files listed in the archive's table section. */ -struct table_entry *parse_table(struct stream *s); +struct table_entry *read_table(struct stream *s); /* Reads the contents of an eliF chunk. If there is an entry with a matching key in the linked list specified by `root`, that structure @@ -56,6 +54,16 @@ struct table_entry *parse_table(struct stream *s); to the linked list. */ void read_elif(struct stream *s, struct table_entry *root); +/* Reads the contents of a File chunk. If there is an entry with a + matching key in the linked list specified by `root`, that structure + will be modified. Otherwise, a new entry will be created and appended + to the linked list. */ +void read_file(struct stream *s, struct table_entry *root); + +/* Traverses `root` for a node with the given key. If the linked list + lacks a node with the key, a new node is created and appended. */ +struct table_entry *get_node(struct table_entry *root, uint32_t key); + /* Inserts `e` to the end of the linked list specified by `root`. */ void entry_append(struct table_entry *root, struct table_entry *e); |