From 01d185f31ff7194773342881fa0e0a550b5d5c5a Mon Sep 17 00:00:00 2001 From: jakob Date: Mon, 13 Feb 2017 16:53:11 -0500 Subject: Began inevetable rewrite. --- GNUmakefile | 45 ------------- TODO.md | 1 - src/cli.c | 141 -------------------------------------- src/cli.h | 50 -------------- src/crypto.c | 77 --------------------- src/crypto.h | 39 ----------- src/decompress.c | 98 --------------------------- src/decompress.h | 33 --------- src/defs.h | 27 -------- src/extract.c | 188 --------------------------------------------------- src/extract.h | 42 ------------ src/file.c | 137 ------------------------------------- src/file.h | 42 ------------ src/io.c | 69 +++++++++++++++++++ src/io.h | 57 ++++++++++++++++ src/main.c | 137 +------------------------------------ src/write.c | 201 ------------------------------------------------------- src/write.h | 42 ------------ test/minunit.h | 3 + test/test_io.c | 52 ++++++++++++++ 20 files changed, 184 insertions(+), 1297 deletions(-) delete mode 100644 GNUmakefile delete mode 100644 src/cli.c delete mode 100644 src/cli.h delete mode 100644 src/crypto.c delete mode 100644 src/crypto.h delete mode 100644 src/decompress.c delete mode 100644 src/decompress.h delete mode 100644 src/defs.h delete mode 100644 src/extract.c delete mode 100644 src/extract.h delete mode 100644 src/file.c delete mode 100644 src/file.h create mode 100644 src/io.c create mode 100644 src/io.h delete mode 100644 src/write.c delete mode 100644 src/write.h create mode 100644 test/minunit.h create mode 100644 test/test_io.c diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index 0238712..0000000 --- a/GNUmakefile +++ /dev/null @@ -1,45 +0,0 @@ -SHELL = /bin/sh - -.SUFFIXES = -.SUFFIXES = .c .o - -CC := gcc -LD := gcc -CFLAGS := -Wall -Wextra -Os -funroll-all-loops -std=gnu99 -pedantic -LDFLAGS := -lz -STRIPARGS := -s -p - -SRCDIR = src -OBJDIR = obj -BINDIR = bin - -SOURCES := $(wildcard $(SRCDIR)/*.c) -OBJECTS := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) - - -all: $(BINDIR)/nekopack - - -$(BINDIR)/nekopack: $(OBJECTS) - @mkdir -p $(BINDIR) - @$(LD) $(LDFLAGS) -o $@ $(OBJECTS) - - -$(OBJDIR)/%.o: $(SRCDIR)/%.c - @mkdir -p $(OBJDIR) - @$(CC) $(CFLAGS) -o $@ -c $< - - -.PHONY: strip -strip: - @strip $(STRIPARGS) $(BINDIR)/nekopack - - -.PHONY: clean -clean: - @rm -rf $(OBJECTS) - - -.PHONY: remove -remove: clean - @rm -rf $(BINDIR)/$(TARGET) diff --git a/TODO.md b/TODO.md index 9612929..74e5cc0 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,2 @@ TODO ==== -* Write a 100% compatible repacker to aid in translation efforts. diff --git a/src/cli.c b/src/cli.c deleted file mode 100644 index 3891032..0000000 --- a/src/cli.c +++ /dev/null @@ -1,141 +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 -#include -#include - -#include - -#include "cli.h" -#include "defs.h" - -#define VERSION "1.0.0.rc1" -#define HELP_TEXT "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." -#define GAME_CONSTANTS "none, nekopara_volume_0, nekopara_volume_0_steam, " \ - "nekopara_volume_1, nekopara_volume_1_steam" - - -/* 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]); - exit(EXIT_FAILURE); - } - - /* The struct has to be zeroed out, otherwise argument parsing - becomes irrelevant and the program will not act as expected. */ - struct configuration parsed; - memset(&parsed, 0, sizeof(parsed)); - - /* 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'}, - {"quiet", no_argument, NULL, 'q'}, - {"extract", no_argument, NULL, 'e'}, - {"list", no_argument, NULL, 'l'}, - {"output", required_argument, NULL, 'o'}, - {"game", required_argument, NULL, 'g'}, - {NULL, 0, NULL, 0} - }; - - do { - current = getopt_long(argc, argv, "hvelqo:g:", long_options, - &option_index); - switch (current) { - case 'h': - printf("Usage: %s [OPTIONS] (ARCHIVE PATH)\n\n", argv[0]); - printf("%s\n\n", HELP_TEXT); - printf("Supported Game Crypto:\n%s\n", GAME_CONSTANTS); - exit(EXIT_SUCCESS); - case 'v': - printf("Nekopack, version %s\nProgrammed by " - "Jakob. \n", VERSION); - exit(EXIT_SUCCESS); - case 'q': - parsed.quiet = 1; - break; - case 'o': - count++; - size_t path_size = strlen(optarg); - parsed.output = malloc(path_size + 1); - strcpy(parsed.output, optarg); - if (parsed.output[path_size - 1] != PATH_DELIMITER) - parsed.output[path_size] = PATH_DELIMITER; - break; - case 'g': - count++; - /* Out of concern for efficiency, string comparisons are - done here and the source game is stored in an enum. */ - if (!strcmp(optarg, "nekopara_volume_0")) - parsed.game = NEKOPARA_VOLUME_0; - else if (!strcmp(optarg, "nekopara_volume_0_steam")) - parsed.game = NEKOPARA_VOLUME_0_STEAM; - else if (!strcmp(optarg, "nekopara_volume_1")) - parsed.game = NEKOPARA_VOLUME_1; - else if (!strcmp(optarg, "nekopara_volume_1_steam")) - parsed.game = NEKOPARA_VOLUME_1_STEAM; - else - parsed.game = NO_CRYPTO; - break; - case 'e': - /* Despite being the default, '-e' is - still parsed to provide consisency. */ - parsed.mode = EXTRACT; - break; - case 'l': - parsed.mode = LIST; - } - count++; - } while (current >= 0); - - /* getopt pushes non-flag arguments to the end of argv, and `count` - is only incremented when a flag argument is parsed. */ - if (count == argc) { - fprintf(stderr, "No archive path provided.\n"); - exit(EXIT_FAILURE); - } - parsed.vararg_index = count; - - /* The output path is allocated on the heap to simplify freeing. */ - if (parsed.output == NULL) { - if ((parsed.output = strdup(".")) == NULL) { - fprintf(stderr, "Insufficient memory.\n"); - exit(EXIT_FAILURE); - } - } - - return parsed; -} diff --git a/src/cli.h b/src/cli.h deleted file mode 100644 index d16d4a7..0000000 --- a/src/cli.h +++ /dev/null @@ -1,50 +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 . */ - -#pragma once - -/* Enumerable type representing Nekopara games, as they have different - encryption keys. It's stored as an enum because multiple strcmp calls - to figure out how to decrypt is a waste of processor cycles. */ -typedef enum game_type { - NO_CRYPTO, - NEKOPARA_VOLUME_0, - NEKOPARA_VOLUME_0_STEAM, - NEKOPARA_VOLUME_1, - NEKOPARA_VOLUME_1_STEAM, -} game_type; - -/* Enumerable type for mode of operation. Used in the main function - to decide what to do after the initial XP3 sanity checks. */ -typedef enum mode_type { - EXTRACT, - LIST, -} mode_type; - -/* Binary structure for storing command-line options. */ -struct configuration { - int quiet; /* Level of output verbosity. */ - int vararg_index; /* Index of where a variadic list of paths starts. */ - game_type game; /* Which decryption key to use. */ - mode_type mode; /* What to do after initial sanity checks. */ - char *output; /* Path to extract files to. */ - const char *archive; /* Path to archive to extract. */ -}; - -/* General subroutine for parsing command-line arguments. Returns a - "configuration" structure containing everything parsed from argv. */ -struct configuration parse_args(int argc, char *argv[]); diff --git a/src/crypto.c b/src/crypto.c deleted file mode 100644 index 1172644..0000000 --- a/src/crypto.c +++ /dev/null @@ -1,77 +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 "cli.h" -#include "crypto.h" - - -/* Returns the encryption keys for a given game value. */ -key get_encryption_key(game_type game) { - key encryption_key; - switch (game) { - case NEKOPARA_VOLUME_0: - encryption_key.master_key = 0x1548e29c; - encryption_key.initial_fallback_key = 0x9c; - encryption_key.primary_fallback_key = 0xd7; - encryption_key.uses_initial_key = 1; - break; - case NEKOPARA_VOLUME_0_STEAM: - encryption_key.master_key = 0x44528b87; - encryption_key.initial_fallback_key = 0x87; - encryption_key.primary_fallback_key = 0x23; - encryption_key.uses_initial_key = 1; - break; - case NEKOPARA_VOLUME_1: - encryption_key.master_key = 0x1548e29c; - encryption_key.initial_fallback_key = 0x00; - encryption_key.primary_fallback_key = 0xd7; - encryption_key.uses_initial_key = 0; - break; - case NEKOPARA_VOLUME_1_STEAM: - encryption_key.master_key = 0x44528b87; - encryption_key.initial_fallback_key = 0x00; - encryption_key.primary_fallback_key = 0x23; - encryption_key.uses_initial_key = 0; - break; - default: - encryption_key.master_key = 0x00000000; - encryption_key.initial_fallback_key = 0x00; - encryption_key.primary_fallback_key = 0x00; - encryption_key.uses_initial_key = 0; - } - return encryption_key; -} - - -/* Decrypts the contents of a buffer according to a file key. */ -void decrypt_buffer(Bytef *encrypted_buffer, uint64_t buffer_length, - key encryption_key, uint32_t file_key) { - uint32_t xor_key = file_key ^ encryption_key.master_key; - uint8_t initial_key = xor_key & 0xff; - uint8_t primary_key = (xor_key >> 24 ^ xor_key >> 16 ^ \ - xor_key >> 8 ^ xor_key) & 0xff; - if (primary_key == 0) - primary_key = encryption_key.primary_fallback_key; - if (encryption_key.uses_initial_key) { - if (initial_key == 0) - initial_key = encryption_key.initial_fallback_key; - encrypted_buffer[0] ^= initial_key; - } - for (uint64_t i = 0; i < buffer_length; i++) { - encrypted_buffer[i] ^= primary_key; - } -} diff --git a/src/crypto.h b/src/crypto.h deleted file mode 100644 index f8d5d86..0000000 --- a/src/crypto.h +++ /dev/null @@ -1,39 +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 . */ - -#pragma once - -#include - -#include - -#include "cli.h" - -/* Struct for a game's encryption keys. */ -typedef struct { - int uses_initial_key; /* Set if first byte uses a different key. */ - uint32_t master_key; /* Master key used to derive a key. */ - uint8_t initial_fallback_key; /* Fallback for the initial key. */ - uint8_t primary_fallback_key; /* Fallback for the primary key. */ -} key; - -/* Returns the encryption keys for a given game value. */ -key get_encryption_key(game_type game); - -/* Decrypts the contents of a buffer according to a file key. */ -void decrypt_buffer(Bytef *encrypted_buffer, uint64_t buffer_length, - key encryption_key, uint32_t file_key); diff --git a/src/decompress.c b/src/decompress.c deleted file mode 100644 index 9a8ec64..0000000 --- a/src/decompress.c +++ /dev/null @@ -1,98 +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 -#include - -#include - -#include "defs.h" -#include "extract.h" - - -/* Inflates a chunk into new space in memory. NULL is returned if not - enough memory can be allocated to contain the decompressed data. */ -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; - 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; -} - - -/* Decompresses the contents of a memory_stream data structure. - Program exits if not enough memory can be allocated. */ -memory_stream decompress_stream(memory_stream compressed_data, uint64_t size) { - uint64_t chunk_size = compressed_data.stream_length; - Bytef *decompressed_data, *chunk = compressed_data.start; - - decompressed_data = inflate_chunk(chunk, chunk_size, size); - if (decompressed_data == NULL) { - free(chunk); - exit(EXIT_FAILURE); - } - - return (memory_stream) {size, decompressed_data, decompressed_data}; -} - - -/* Reads a FILE pointer into a memory_stream data structure. */ -memory_stream read_to_stream(FILE *archive, uint64_t buffer_size) { - Bytef *buffer = malloc(buffer_size); - /* This is a pretty shitty way of handling it. */ - if (buffer == NULL) { - fprintf(stderr, "Insufficient memory to load archive.\n"); - free(buffer); - fclose(archive); - exit(EXIT_FAILURE); - } - fread(buffer, buffer_size, 1, archive); - return (memory_stream) {buffer_size, buffer, buffer}; -} diff --git a/src/decompress.h b/src/decompress.h deleted file mode 100644 index dcb985b..0000000 --- a/src/decompress.h +++ /dev/null @@ -1,33 +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 . */ - -#pragma once - -#include - -#include "extract.h" - -/* Decompresses a memory_stream data structure into a new one. */ -memory_stream decompress_stream(memory_stream compressed_data, uint64_t size); - -/* Reads a FILE pointer into a memory_stream data structure. */ -memory_stream read_to_stream(FILE *archive, uint64_t buffer_size); - -/* Returns a pointer to a buffer containing the - inflated contents of a given memory chunk. */ -Bytef *inflate_chunk(Bytef *chunk, uint64_t chunk_size, - uint64_t decompressed_size); diff --git a/src/defs.h b/src/defs.h deleted file mode 100644 index 15ce5ee..0000000 --- a/src/defs.h +++ /dev/null @@ -1,27 +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 . */ - -#pragma once - -#define EXIT_SUCCESS 0 -#define EXIT_FAILURE 1 - -#ifdef WINDOWS -#define PATH_DELIMITER '\\' -#else -#define PATH_DELIMITER '/' -#endif diff --git a/src/extract.c b/src/extract.c deleted file mode 100644 index e6476ce..0000000 --- a/src/extract.c +++ /dev/null @@ -1,188 +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 -#include -#include -#include - -#include -#include - -#include "cli.h" -#include "defs.h" -#include "extract.h" -#include "file.h" -#include "write.h" - -#define ELIF_MAGIC 0x46696c65 -#define FILE_MAGIC 0x656c6946 -#define HNFN_MAGIC 0x6e666e68 -#define NEKO_MAGIC 0x6f6b656e - -elif_node *read_elif_entry(memory_stream *data_stream); -void read_stream(void *destination, Bytef **source, size_t size); - - -/* Decompresses, decrypts, and writes files in the XP3 archive to disk - according to entries in the table. Will return prematurely if there - isn't enough memory to store the linked lists. */ -void extract(memory_stream data_stream, FILE *archive) { - /* eliF and File entries are stored in a linked list as they're - seen because the order of entries in XP3 archives is not - guaranteed to be chronological. calloc is used to prevent the - "next" pointer from being garbage and causing a segfault. */ - elif_node *elif_new, *elif_root = calloc(sizeof(elif_node), 1); - file_node *file_new, *file_root = calloc(sizeof(file_node), 1); - - int stream_ended = 0; - uint32_t entry_magic; - uint64_t entry_size; - do { - read_stream(&entry_magic, &data_stream.data, sizeof(uint32_t)); - read_stream(&entry_size, &data_stream.data, sizeof(uint64_t)); - - switch (entry_magic) { - /* hnfn, neko, and eliF entries are all identical. - - The size given by the entry header doesn't match the - actual entry size, so it isn't passed to the function. */ - case ELIF_MAGIC: - case HNFN_MAGIC: - case NEKO_MAGIC: - elif_new = read_elif_entry(&data_stream); - if (elif_new == NULL) { - fprintf(stderr, "Insufficient memory.\n"); - free_elif_nodes(elif_root); - free_file_nodes(file_root); - return; - } - defer_elif_node(elif_new, elif_root); - break; - case FILE_MAGIC: - file_new = read_file_entry(&data_stream, - data_stream.data + entry_size); - if (file_new == NULL) { - fprintf(stderr, "Insufficient memory.\n"); - free_elif_nodes(elif_root); - free_file_nodes(file_root); - return; - } - defer_file_node(file_new, file_root); - break; - default: - stream_ended = 1; - } - } while (!stream_ended); - - write_files(file_root, elif_root, archive); - free_elif_nodes(elif_root); - free_file_nodes(file_root); -} - - -/* Writes the associated filename for each eliF entry in the table. */ -void list(memory_stream data_stream) { - int stream_ended = 0; - uint32_t entry_magic; - uint64_t entry_size; - elif_node *temporary; - do { - read_stream(&entry_magic, &data_stream.data, sizeof(uint32_t)); - read_stream(&entry_size, &data_stream.data, sizeof(uint64_t)); - - switch (entry_magic) { - case ELIF_MAGIC: - case HNFN_MAGIC: - case NEKO_MAGIC: - temporary = read_elif_entry(&data_stream); - printf("%s\n", temporary->file_name); - free(temporary->file_name); - free(temporary); - break; - case FILE_MAGIC: - data_stream.data += entry_size; - break; - default: - stream_ended = 1; - } - } while (!stream_ended); -} - - -/* 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; -} - - -/* Creates a node for an eliF entry, which can be deferred in a linked - list. NULL will be returned if there isn't enough memory to contain - the node structure or the filenames. */ -elif_node *read_elif_entry(memory_stream *data_stream) { - uint32_t file_key; - uint16_t name_size; - read_stream(&file_key, &data_stream->data, sizeof(uint32_t)); - read_stream(&name_size, &data_stream->data, sizeof(uint16_t)); - - char *file_name; - if (name_size < 0x100) { - /* The filenames are terminated by null bytes, - but that isn't counted in the name size. */ - char *input_buffer = malloc(name_size * 2 + 2); - read_stream(input_buffer, &data_stream->data, name_size * 2 + 2); - if (input_buffer == NULL) - return NULL; - - /* name_size + 1 is unreliable since we're going back to UTF-8 - and some characters (especially Japanese ones) can be wide. - To compensate we just allocate a buffer that could fit the - UTF-16LE data and utilize null-bytes to terminate names. */ - file_name = malloc(name_size * 2 + 2); - if (file_name == NULL) { - free(input_buffer); - return NULL; - } - - /* iconv is the less-portable glibc way of doing it. It seems to - be in the OpenBSD manpages, though, so I'm not worried. */ - size_t in_size = name_size * 2 + 2, out_size = name_size * 2 + 2; - char *in_start = input_buffer, *out_start = file_name; - iconv_t conversion = iconv_open("UTF-8", "UTF-16LE"); - iconv(conversion, &in_start, &in_size, &out_start, &out_size); - iconv_close(conversion); - - free(input_buffer); - } else { - /* It's safe to assume that a filename that - large is the flashy copyright notice. */ - data_stream->data += name_size * 2 + 2; - file_name = strdup("COPYING.txt"); - } - - elif_node *parsed = malloc(sizeof(elif_node)); - if (parsed == NULL) { - free(file_name); - return NULL; - } - parsed->key = file_key; - parsed->file_name = file_name; - parsed->next = NULL; - return parsed; -} diff --git a/src/extract.h b/src/extract.h deleted file mode 100644 index 10965a5..0000000 --- a/src/extract.h +++ /dev/null @@ -1,42 +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 . */ - -#pragma once - -#include -#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; - -/* Decrypts and writes files in the XP3 archive - to disk according to table entries. */ -void extract(memory_stream data_stream, FILE *archive); - -/* Simply lists the contents of an archive, ignoring File entries. */ -void list(memory_stream data_stream); - -/* 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 deleted file mode 100644 index d2a9b72..0000000 --- a/src/file.c +++ /dev/null @@ -1,137 +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 -#include -#include -#include - -#include "extract.h" -#include "file.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, file_node *parsed); -void read_adlr_chunk(memory_stream *data_stream, file_node *parsed); -void read_time_chunk(memory_stream *data_stream, file_node *parsed); -void read_segm_chunk(memory_stream *data_stream, file_node *parsed, - uint64_t segment_count); - - -/* Creates a node for a File entry, which can be deferred in a linked - list. NULL will be returned if there isn't enough memory to contain - the node structure. */ -file_node *read_file_entry(memory_stream *data_stream, Bytef *section_end) { - uint32_t entry_magic; - uint64_t entry_size; - file_node *parsed = calloc(sizeof(file_node), 1); - if (parsed == NULL) - return NULL; - 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: - read_adlr_chunk(data_stream, parsed); - break; - case SEGM_MAGIC: - /* The segment count is not included in the table and - has to be calculated. Segments are 28 bytes each. */ - read_segm_chunk(data_stream, parsed, entry_size / 28); - /* There can't really be a check for this - in read_segm_chunk, so it's done here. */ - if (parsed->segments == NULL) { - free(parsed); - return NULL; - } - break; - case INFO_MAGIC: - read_info_chunk(data_stream, parsed); - break; - case TIME_MAGIC: - read_time_chunk(data_stream, parsed); - break; - default: - printf("Unknown magic: %" PRIx32 "\n", entry_magic); - } - } - return parsed; -} - - -/* Reads the contents of an info chunk into a file node. */ -void read_info_chunk(memory_stream *data_stream, file_node *parsed) { - uint32_t encrypted; - uint64_t decompressed_size, compressed_size; - uint16_t file_name_size; - read_stream(&encrypted, &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)); - parsed->encrypted = encrypted; - - char *file_name = malloc(file_name_size * 2 + 2); - read_stream(file_name, &data_stream->data, file_name_size * 2 + 2); - free(file_name); -} - - -/* Reads the contents of a segm chunk into a file node. */ -void read_segm_chunk(memory_stream *data_stream, file_node *parsed, - uint64_t segment_count) { - /* Segments are stored in an array of segment pointers. */ - segment **segments = malloc(sizeof(segment *) * segment_count); - if (segments == NULL) - return; - - for (uint64_t i = 0; i < segment_count; i++) { - segments[i] = malloc(sizeof(segment)); - read_stream(&segments[i]->compressed, - &data_stream->data, - sizeof(uint32_t)); - read_stream(&segments[i]->offset, - &data_stream->data, - sizeof(uint64_t)); - read_stream(&segments[i]->decompressed_size, - &data_stream->data, - sizeof(uint64_t)); - read_stream(&segments[i]->compressed_size, - &data_stream->data, - sizeof(uint64_t)); - parsed->file_size += segments[i]->decompressed_size; - } - parsed->segment_count = segment_count; - parsed->segments = segments; -} - - -/* Reads the contents of an adlr chunk into a file_node. */ -void read_adlr_chunk(memory_stream *data_stream, file_node *parsed) { - uint32_t key; - read_stream(&key, &data_stream->data, sizeof(uint32_t)); - parsed->key = key; -} - - -/* Reads the contents of a time chunk into a file_node. */ -void read_time_chunk(memory_stream *data_stream, file_node *parsed) { - uint64_t timestamp; - read_stream(×tamp, &data_stream->data, sizeof(uint64_t)); -} diff --git a/src/file.h b/src/file.h deleted file mode 100644 index 46b6dc0..0000000 --- a/src/file.h +++ /dev/null @@ -1,42 +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 . */ - -#pragma once - -#include - -/* Structure representing a segment in a file. */ -typedef struct { - uint32_t compressed; /* Whether or not the chunk is compressed. */ - uint64_t offset; /* Chunk's position in the file as an offset. */ - uint64_t compressed_size; /* Chunk's compressed size. */ - uint64_t decompressed_size; /* Chunk's decompressed size. */ -} segment; - -/* Node in a linked list of file entries to write to disk. */ -typedef struct file_node { - int encrypted; /* Whether or not the entry is encrypted. */ - int compressed; /* Whether or not the entry is compressed. */ - uint32_t key; /* Key associated with matching eliF entry. */ - uint64_t file_size; /* Total size of decompressed file. */ - uint64_t segment_count; /* Number of segments in File entry. */ - segment **segments; /* Data segments associated with the entry. */ - struct file_node *next; /* Pointer to the next node. */ -} file_node; - -/* Creates a file node by parsing a file entry. */ -file_node *read_file_entry(memory_stream *data_stream, Bytef *section_end); diff --git a/src/io.c b/src/io.c new file mode 100644 index 0000000..2762180 --- /dev/null +++ b/src/io.c @@ -0,0 +1,69 @@ +/* io.c -- Code for file and memory I/O. + + 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 . */ + +#include +#include + +#include "io.h" + + +/* Allocates `len` bytes of memory and returns a new stream pointing to + it. The memory provided has not zeroed. */ +struct stream *stream_new(size_t len) { + stream *new = malloc(sizeof(stream)); + new->len = len; + new->_start = malloc(len); + new->_cur = new->_start; + new->_location = HEAP; +} + + +/* 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) { + switch (s->_loc) { + case HEAP: + free(s->_start); + } + free(s); +} + + +/* Copies `n` bytes from the given stream into the memory area specified + by `dest`. The stream is advanced appropriately. */ +void stream_read(void *dest, struct stream *s, size_t n) { + memcpy(dest, s->_cur, n); + (char *) s->_cur += n; +} + + +/* Copies `n` bytes into the given stream from the memory area specified + by `src`. The stream is advanced appropriately. */ +void stream_write(struct stream *s, void *src, size_t n) { + if (s->cur + n > s->start + s->len) { + ptrdiff_t dist = (uintptr_t) s->_cur - (uintptr_t) s->_start; + switch(s->_loc) { + case HEAP: + s->_start = realloc(s->_start, s->len * 2); + s->_cur = s->_start + dist; + } + } + memcpy(s->cur, src, n); + s->cur += n; +} diff --git a/src/io.h b/src/io.h new file mode 100644 index 0000000..f07a421 --- /dev/null +++ b/src/io.h @@ -0,0 +1,57 @@ +/* io.h -- Code for file and memory I/O. + + 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 . */ + +#pragma once + +#include +#include + +/* Internal enumerable type for representing the location of a memory + segment. Used for freeing and unmapping purposes, if necessary. */ +enum _location { + HEAP, +} + +/* Structure to emulate a stream of data that maintains a current + position. The structure additionally maintains a `length` variable + containing the stream's length. */ +struct stream { + size_t len; /* Length of the stream. */ + void *_start; /* Pointer to the stream's start. */ + void *_cur; /* Pointer to the current position. */ + enum _location _loc; /* Location of the stream in memory. */ +} + +/* Allocates `len` bytes of memory and returns a new stream pointing to + it. The memory provided has not zeroed. */ +struct stream *stream_new(size_t len); + +/* 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); + + +/* Copies `n` bytes from the given stream into the memory area specified + by `dest`. The stream is advanced appropriately. */ +void stream_read(void *dest, struct stream *s, size_t n); + + +/* Copies `n` bytes into the given stream from the memory area specified + by `src`. The stream is advanced appropriately. */ +void stream_write(struct stream *s, void *src, size_t n); diff --git a/src/main.c b/src/main.c index 2ba65db..892958e 100644 --- a/src/main.c +++ b/src/main.c @@ -1,7 +1,9 @@ -/* This file is part of Nekopack. +/* main.c -- Entry point to Nekopack. 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 @@ -14,136 +16,3 @@ You should have received a copy of the GNU General Public License along with Nekopack. If not, see . */ - -#include -#include -#include -#include -#include - -#include "cli.h" -#include "decompress.h" -#include "extract.h" - -#define XP3_MAGIC "XP3\x0d\x0a\x20\x0a\x1a\x8b\x67\x01" -#define XP3_TABLE_OFFSET 11 -#define XP3_VERSION_OFFSET 19 - -void handle_archive(char *path); -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[]) { - arguments = parse_args(argc, argv); - for (int i = arguments.vararg_index; i < argc; i++) - handle_archive(argv[i]); - free(arguments.output); - return 0; -} - - -/* Extracts the archive at the specified path. */ -void handle_archive(char *path) { - FILE *archive = fopen(path, "rb"); - if (archive == NULL) { - /* Including the expanded path in the error message is - much more useful to the user than `perror("fopen")`. */ - perror(path); - exit(EXIT_FAILURE); - } else if (!is_xp3_archive(archive)) { - fprintf(stderr, "%s is not an XP3 archive.\n", path); - fclose(archive); - exit(EXIT_FAILURE); - } - - int archive_version = get_archive_version(archive); - uint64_t table_offset = get_table_offset(archive, archive_version); - - uint8_t compressed; - uint64_t compressed_size, decompressed_size; - fseek(archive, table_offset, SEEK_SET); - fread(&compressed, sizeof(uint8_t), 1, archive); - fread(&compressed_size, sizeof(uint64_t), 1, archive); - fread(&decompressed_size, sizeof(uint64_t), 1, archive); - - /* Every task that hasn't already been handled needs a - decompressed instance of the archive table. Decompression - done in memory because it's $CURRENT_YEAR. */ - memory_stream data_stream; - memory_stream compressed_data = read_to_stream(archive, compressed_size); - if (compressed) { - data_stream = decompress_stream(compressed_data, decompressed_size); - free(compressed_data.start); - } else { - data_stream = compressed_data; - } - - switch (arguments.mode) { - case EXTRACT: - extract(data_stream, archive); - break; - case LIST: - list(data_stream); - } - - free(data_stream.start); - fclose(archive); -} - - -/* Check of the file's magic number to decide - whether or not the file a valid XP3 archive. */ -int is_xp3_archive(FILE *archive) { - int ret = 1; - char* buffer = malloc(11); - rewind(archive); - fread(buffer, 11, 1, archive); - if (memcmp(buffer, XP3_MAGIC, 11)) - ret = 0; - free(buffer); - return ret; -} - - -/* Returns the version of XP3 used to pack the archive. */ -int get_archive_version(FILE *archive) { - uint32_t version; - fseek(archive, XP3_VERSION_OFFSET, SEEK_SET); - fread(&version, sizeof(uint32_t), 1, archive); - /* 0x00 indicates version 1, and 0x01 indicates version 2. */ - return version == 1 ? 2 : 1; -} - - -/* 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) { - 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; - - /* 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) { - fprintf(stderr, "Archive version not implemented.\n"); - fclose(archive); - exit(EXIT_FAILURE); - } - - /* The value initally read as table_offset is actually an offset to - the real table offset. (Try saying that five times fast.) */ - fseek(archive, table_offset, SEEK_SET); - /* Table flags and size are ignored in the parsing process, - but are read anyway to advance the FILE pointer. */ - 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/write.c b/src/write.c deleted file mode 100644 index 7a0d90e..0000000 --- a/src/write.c +++ /dev/null @@ -1,201 +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 -#include -#include -#include -#include -#include - -#include "crypto.h" -#include "decompress.h" -#include "defs.h" -#include "extract.h" -#include "file.h" -#include "write.h" - -/* Global instance of the command-line configuration structure. */ -extern struct configuration arguments; - - -/* Finds an eliF entry with the given key in a linked list and returns - the associated filename, removing it from the linked list. */ -char *pop_file_name(uint32_t key, elif_node *root) { - char *file_name = NULL; - elif_node *current = root->next, *last = root; - for (;;) { - if (current->key == key) { - file_name = current->file_name; - last->next = current->next; - free(current); - break; - } else if (current->next == NULL) { - break; - } else { - last = current; - current = current->next; - } - } - return file_name; -} - - -/* Creates all of the paths in a filename. */ -void make_paths(char *file_name) { - /* This structure is only used for the stat call. */ - struct stat temp = {0}; - - /* Max filename size. */ - char *buffer = calloc(0x100, 1); - char *buffer_start = buffer; - for (int i = 0; i < 0x100 && file_name[i] != '\0'; i++) { - if (file_name[i] == '/') { - *buffer++ = PATH_DELIMITER; - *buffer = '\0'; - if (stat(buffer_start, &temp) == -1) { - if (!arguments.quiet) - printf("Creating directory %s\n", buffer_start); - mkdir(buffer_start, 0777); - } - } else { - *buffer++ = file_name[i]; - } - } - free(buffer_start); -} - - -/* Iterates through the linked list of file entries and writes every - entry to disk, according to information specified by the node. */ -void write_files(file_node *file_root, elif_node *elif_root, FILE *archive) { - file_node *current; - key encryption_key = get_encryption_key(arguments.game); - Bytef *compressed_buffer, *decompressed_buffer, *out_buffer, *out_start; - for (current = file_root->next; current != NULL; current = current->next) { - char *file_name = pop_file_name(current->key, elif_root); - if (file_name == NULL) - continue; - - /* strcpy is used rather than strcat to prevent the file - name from being appended after garbage data. */ - size_t file_name_size = strlen(file_name); - size_t path_size = strlen(arguments.output); - char *output_path = malloc(path_size + file_name_size + 1); - if (output_path == NULL) { - fprintf(stderr, "Insufficient memory.\n"); - return; - } - strcpy(output_path, arguments.output); - strcpy(output_path + path_size, file_name); - - if (!arguments.quiet) { - printf("Inflating %s (%" PRIu64 " bytes)\n", - file_name, current->file_size); - } - - out_buffer = malloc(current->file_size); - if (out_buffer == NULL) { - fprintf(stderr, "Insufficient memory to decompress.\n"); - return; - } - - /* out_start is kept so out_buffer can be incremented. */ - out_start = out_buffer; - for (uint64_t i = 0; i < current->segment_count; i++) { - segment *chunk = current->segments[i]; - fseek(archive, chunk->offset, SEEK_SET); - compressed_buffer = malloc(chunk->compressed_size); - fread(compressed_buffer, chunk->compressed_size, 1, archive); - - if (chunk->compressed) { - decompressed_buffer = inflate_chunk(compressed_buffer, - chunk->compressed_size, - chunk->decompressed_size); - free(compressed_buffer); - } else { - decompressed_buffer = compressed_buffer; - } - memcpy(out_buffer, decompressed_buffer, chunk->decompressed_size); - out_buffer += chunk->decompressed_size; - free(decompressed_buffer); - } - - if (arguments.game != NO_CRYPTO) { - decrypt_buffer(out_start, current->file_size, - encryption_key, current->key); - } - - make_paths(output_path); - FILE *output = fopen(output_path, "wb+"); - if (output == NULL) { - perror(output_path); - break; - } - fwrite(out_start, current->file_size, 1, output); - fclose(output); - free(output_path); - free(file_name); - free(out_start); - } -} - - -/* Inserts an eliF entry at the end of a linked list. */ -void defer_elif_node(elif_node *new, elif_node *root) { - elif_node *current = root; - for (;;) { - if (current->next == NULL) { - current->next = new; - break; - } else { - current = current->next; - } - } -} - - -/* Inserts a File entry node at the end of a linked list. */ -void defer_file_node(file_node *new, file_node *root) { - file_node *current = root; - for (;;) { - if (current->next == NULL) { - current->next = new; - break; - } else { - current = current->next; - } - } -} - - -/* Iterates through the linked list and frees all entries. */ -void free_elif_nodes(elif_node *base) { - if (base->next != NULL) { - free_elif_nodes(base->next); - } - free(base->file_name); - free(base); -} - - -/* Iterates through the linked list and frees all entries. */ -void free_file_nodes(file_node *base) { - if (base->next != NULL) - free_file_nodes(base->next); - free(base); -} diff --git a/src/write.h b/src/write.h deleted file mode 100644 index 51a847e..0000000 --- a/src/write.h +++ /dev/null @@ -1,42 +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 . */ - -#pragma once - -/* Because File entries won't necessarily follow the associated - eliF entry, filenames and hashes are stored in a linked list. */ -typedef struct elif_node { - uint32_t key; /* Key associated with matching File entry. */ - char *file_name; /* Name of file. */ - struct elif_node *next; /* Pointer to the next node. */ -} elif_node; - -/* Iterates through the linked list of file entries and writes every - entry to disk, according to information specified by the node. */ -void write_files(file_node *file_root, elif_node *elif_root, FILE *archive); - -/* Inserts an eliF entry at the end of a linked list. */ -void defer_elif_node(elif_node *new, elif_node *root); - -/* Inserts a File entry node at the end of a linked list. */ -void defer_file_node(file_node *new, file_node *root); - -/* Iterates through the linked list and frees all entries. */ -void free_elif_nodes(elif_node *base); - -/* Iterates through the linked list and frees all entries. */ -void free_file_nodes(file_node *base); diff --git a/test/minunit.h b/test/minunit.h new file mode 100644 index 0000000..7422998 --- /dev/null +++ b/test/minunit.h @@ -0,0 +1,3 @@ +#define mu_assert(message, test) do { if (!(test)) return message; } while (0) +#define mu_run_test(test) do { char *message = test(); tests_run++; if (message) return message; } while (0) +extern int tests_run; diff --git a/test/test_io.c b/test/test_io.c new file mode 100644 index 0000000..8bf7593 --- /dev/null +++ b/test/test_io.c @@ -0,0 +1,52 @@ +/* test_io.c -- MinUnit test cases for I/O related functions. + + 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 . */ + +#include +#include + +#include "minunit.h" + +#include "io.h" + +int tests_run = 0; + + +static char *test_stream_obj(void) { + struct stream *s_1 = stream_new(0x10); + struct stream *s_2 = stream_new(0x20); + struct stream *s_3 = stream_new(0x40); + mu_assert("Incorrect size for `s_1`", s_1.len == 0x10); + mu_assert("Incorrect size for `s_2`", s_1.len == 0x20); + mu_assert("Incorrect size for `s_3`", s_1.len == 0x40); + stream_free(s_1); + stream_free(s_2); + stream_free(s_3); +} + + +static char *test_stream_rw(void) { + char dest[4]; + struct stream *s_1 = stream_new(0x4); + stream_write(s_1, "\x00\x01\x02\x03", 4); + mu_assert("Write failure", !memcmp(s_1->_start, "\x00\x01\x02\x03", 4)); + mu_assert("Cursor not incremented", s_1->cur - s_1->start == 4); + stream_read(dest, s, 4); + mu_assert("Read failure", !memcmp(dest, "\x00\x01\x02\x03", 4)); + stream_free(s_1); +} -- cgit v1.3