From 5ac05edbb64b89741728ba006b0ce681a5d51595 Mon Sep 17 00:00:00 2001 From: jakob Date: Sat, 1 Apr 2017 15:47:28 -0400 Subject: [PATCH] Fixed segmentation fault on invalid paths, cleaned up code --- .gitignore | 4 +++- src/cli.c | 50 +++++++++++++++++++++++++-------------- src/cli.h | 10 ++++---- src/crypto.h | 4 ++-- src/header.h | 10 ++++---- src/io.h | 6 ++--- src/main.c | 67 ++++++++++++++++++++++++++-------------------------- src/table.h | 16 ++++++------- 8 files changed, 91 insertions(+), 76 deletions(-) diff --git a/.gitignore b/.gitignore index 34be812..d82166a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ bin/ obj/ -TAGS +GPATH +GRTAGS +GTAGS diff --git a/src/cli.c b/src/cli.c index feb9718..b60454a 100644 --- a/src/cli.c +++ b/src/cli.c @@ -23,19 +23,46 @@ #include #include "cli.h" -#include "crypto.h" /* New, maybe clean up the section on game crypto? */ +#include "crypto.h" + + +/* Copies `optarg` into the out field of `p` and ensures that it ends in + a trailing path delimiter. */ +static void parse_output_path(const char *optarg, struct params *p) { + 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] = '/'; + p->out_len += 1; + } +} + + +/* Updates the game field of `p` to properly reflect the game ID + specified by the string, `optarg`. */ +static void parse_game_id(const char *optarg, struct params *p) { + 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; +} /* 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'}, @@ -63,24 +90,11 @@ struct params parse_args(int argc, char **argv) { 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] = '/'; - p.out_len += 1; - } + parse_output_path(optarg, &p); 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; + parse_game_id(optarg, &p); case 'e': p.mode = EXTRACT; break; diff --git a/src/cli.h b/src/cli.h index 8e16020..cf7a535 100644 --- a/src/cli.h +++ b/src/cli.h @@ -33,12 +33,12 @@ enum { /* 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 verbose; /* Whether or not to output progress messages. */ + int mode; /* Current mode of operation. */ + int game; /* Which encryption keys to use. */ + bool verbose; /* Whether or not to output 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. */ + char *out; /* Path to extract files to. */ + size_t out_len; /* Length of the output path string. */ }; /* Returns a params structure parsed from `argv`. */ diff --git a/src/crypto.h b/src/crypto.h index 11b0fc5..e80531d 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -33,8 +33,8 @@ enum { /* 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. */ + 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. */ }; diff --git a/src/header.h b/src/header.h index d45d4b0..19f7fcd 100644 --- a/src/header.h +++ b/src/header.h @@ -27,11 +27,11 @@ /* Structure representing the header section of an XP3 archive. */ 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. */ - uint64_t table_size; /* The size of the table section. (?) */ - uint8_t flags; /* A flags variable for the archive. (?) */ + char magic[11]; /* Identifier for the archive. */ + uint64_t info_offset; /* Offset to `table_size`. */ + uint32_t version; /* Raw value containing the archive version. */ + uint64_t table_size; /* The size of the table section. (?) */ + uint8_t flags; /* A flags variable for the archive. (?) */ uint64_t table_offset; /* Offset to the archive table. */ }; diff --git a/src/io.h b/src/io.h index 3caaa95..b37ec51 100644 --- a/src/io.h +++ b/src/io.h @@ -33,10 +33,10 @@ enum _location { position. The structure additionally maintains a `length` variable containing the stream's length. */ struct stream { - size_t len; /* Length of the stream. */ + size_t len; /* Length of the stream. */ char *_start; /* Pointer to the stream's start. */ - char *_cur; /* Pointer to the current position. */ - enum _location _loc; /* Location of the stream in memory. */ + char *_cur; /* Pointer to the current position. */ + enum _location _loc; /* Location of the stream in memory. */ }; /* Allocates `len` bytes of non-zeroed memory and returns a new stream diff --git a/src/main.c b/src/main.c index 9b24fe6..95b4ae3 100644 --- a/src/main.c +++ b/src/main.c @@ -1,4 +1,4 @@ -/* main.c -- Entry point to the program. +/* main.c -- Command-line entry point to the Nekopack. Copyright (C) 2017 Jakob Kreuze, All Rights Reserved. @@ -33,12 +33,7 @@ #define EXIT_FAILURE 1 #define EXIT_SUCCESS 0 -#define VERSION_STR "2.0.0a2" - - -/* 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); +#define VERSION_STR "2.0.0" /* Writes usage information to stderr. */ @@ -65,7 +60,7 @@ static void print_help(void) { " -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" + "decryption.\n" " -q, --quiet\t\tDon't display information about extracted " "files.\n\n" "Supported games: nekopara_volume_0, nekopara_volume_0_steam,\n" @@ -88,10 +83,9 @@ static struct stream *load_table(struct stream *s) { /* 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 = calloc(0x100, 1); char *buf_start = buf; - + struct stat tmp = {0}; for (int i = 0; i < 0x100 && path[i] != '\0'; i++) { if (path[i] == '/') { *buf++ = '/'; @@ -116,26 +110,20 @@ static char *get_path(struct params p, char *name) { } -/* Basic mapfn for printing the filename of each entry. */ -static void list(struct stream *s, struct table_entry *e, struct params p) { - if (e->filename == NULL) { - fprintf(stderr, "Found File entry without matching eliF. Archive may " - "be corrupted.\n"); +/* Prints the filename of the entry specified by `e` to stdout. */ +static void list(struct table_entry *e) { + if (e->filename == NULL || e->segments == NULL) { + fprintf(stderr, "Inconsistency detected. Archive may be corrupted.\n"); return; } printf("%s\n", e->filename); } -/* Mapfn for extracting the contents of the archive. */ +/* Extracts the archive entry specified by `e` to disk. */ static void extract(struct stream *s, struct table_entry *e, struct params p) { - if (e->filename == NULL) { - fprintf(stderr, "Found File entry without matching eliF. Archive may " - "be corrupted.\n"); - return; - } else if (e->segments == NULL) { - fprintf(stderr, "Found eliF entry without matching File. Archive may " - "be corrupted.\n"); + if (e->filename == NULL || e->segments == NULL) { + fprintf(stderr, "Inconsistency detected. Archive may be corrupted.\n"); return; } @@ -177,17 +165,30 @@ static void extract(struct stream *s, struct table_entry *e, struct params p) { } -/* Maps `fn` to every table entry found in the archive at `path`. */ -static void map_entries(char *path, struct params p, mapfn fn) { +/* Loads an XP3 archive and maps the function associated to the current + mode of operation to every entry. */ +static void map_entries(char *path, struct params p) { struct stream *archive = stream_from_file(path); - struct header *h = read_header(archive); + if (archive == NULL) { + perror(path); + return; + } + + 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); + for (struct table_entry *cur = root->next; cur != NULL; cur = cur->next) { + switch (p.mode) { + case LIST: + list(cur); + break; + case EXTRACT: + extract(archive, cur, p); + } + } entry_free(root); stream_free(table); @@ -201,7 +202,8 @@ int main(int argc, char **argv) { switch (p.mode) { case USAGE: print_usage(argv[0]); - return 1; + params_free(p); + return EXIT_FAILURE; case VERSION: print_version(); break; @@ -209,12 +211,9 @@ int main(int argc, char **argv) { 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); + map_entries(argv[i], p); } params_free(p); return EXIT_SUCCESS; diff --git a/src/table.h b/src/table.h index 1f40390..80ad099 100644 --- a/src/table.h +++ b/src/table.h @@ -27,20 +27,20 @@ /* Structure representing one segment associated with a File entry. */ struct segment { - bool compressed; /* Whether or not the segment is compressed. */ - uint64_t offset; /* Offset to the segment's beginning. */ - uint64_t compressed_size; /* Size of compressed segment. */ + bool compressed; /* Whether or not the segment is compressed. */ + uint64_t offset; /* Offset to the segment's beginning. */ + uint64_t compressed_size; /* Size of compressed segment. */ uint64_t decompressed_size; /* Size of decompressed segment. */ }; /* Structure representing an entry in the archive's table section. */ struct table_entry { - uint32_t key; /* File-specific key for encryption. */ - uint64_t ctime; /* Timestamp of creation time. */ + 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. */ + char *filename; /* String containing file's name. */ + struct segment **segments; /* Array of associated segments. */ + struct table_entry *next; /* Pointer to the next entry. */ };