/* table.c -- Code for handling the archive's table section. Copyright (C) 2017 Jakob Kreuze, 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 #include #include "encoding.h" #include "io.h" #include "table.h" #define ELIF_MAGIC 0x46696c65 #define FILE_MAGIC 0x656c6946 #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)); } } /* Dumps the segm segment for `cur` into the table at `s`. */ static void dump_segm(struct stream *s, struct table_entry *cur) { struct segment *segm; uint32_t magic = SEGM_MAGIC; uint64_t entry_size = cur->segment_count * 28; stream_write(s, &magic, sizeof(uint32_t)); stream_write(s, &entry_size, sizeof(uint64_t)); for (uint64_t i = 0; i < cur->segment_count; i++) { segm = cur->segments[i]; stream_write(s, &segm->compressed, sizeof(uint32_t)); stream_write(s, &segm->offset, sizeof(uint64_t)); stream_write(s, &segm->decompressed_size, sizeof(uint64_t)); stream_write(s, &segm->compressed_size, 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)); } /* Dumps the adlr segment for `key` into the table at `s`. */ static void dump_adlr(struct stream *s, uint32_t key) { uint32_t magic = ADLR_MAGIC; uint64_t entry_size = sizeof(uint32_t); stream_write(s, &magic, sizeof(uint32_t)); stream_write(s, &entry_size, sizeof(uint64_t)); stream_write(s, &key, 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)); } /* Dumps the time segment for `timestamp` into the table at `s`. */ static void dump_time(struct stream *s, uint64_t timestamp) { uint32_t magic = TIME_MAGIC; uint64_t entry_size = sizeof(uint64_t); stream_write(s, &magic, sizeof(uint32_t)); stream_write(s, &entry_size, sizeof(uint64_t)); stream_write(s, ×tamp, sizeof(uint64_t)); } /* 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); if (tmp == NULL) return; 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); if (cur == NULL) { free(tmp); return; } cur->segment_count = tmp->segment_count; cur->segments = tmp->segments; cur->ctime = tmp->ctime; free(tmp); } /* Dumps the File entry for `cur` into the table at `s`. */ static void dump_file(struct stream *s, struct table_entry *cur) { uint32_t magic = FILE_MAGIC; uint64_t bytes_written = 28 * cur->segment_count + 48; stream_write(s, &magic, sizeof(uint32_t)); stream_write(s, &bytes_written, sizeof(uint64_t)); dump_adlr(s, cur->key); dump_time(s, cur->ctime); dump_segm(s, cur); } /* 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 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, *tmp; uint16_t name_len; uint32_t key; struct table_entry *cur; stream_read(&key, s, sizeof(uint32_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_len = name_len * 2 + 2; cur = get_node(root, key); if (cur == NULL || cur->filename != NULL) { return; } if (name_len < 0x100) { buf = malloc(name_len); name = malloc(name_len); if (buf == NULL || name == NULL) return; stream_read(buf, s, name_len); utf16le_decode(buf, name, name_len); free(buf); tmp = realloc(name, strlen(name) + 1); if (tmp == NULL) { free(name); return; } name = tmp; } else { name = malloc(14); if (name == NULL) return; strncpy(name, "COPYRIGHT.txt", 14); stream_seek(s, name_len, SEEK_CUR); } cur->filename = name; } /* Dumps the eliF entry for `cur` into the table at `s`. */ static void dump_elif(struct stream *s, struct table_entry *cur) { uint16_t name_len = strlen(cur->filename); char *encoded = malloc(name_len * 2 + 2); if (encoded == NULL) return; utf16le_encode(cur->filename, encoded, name_len); uint32_t magic = ELIF_MAGIC; uint64_t entry_size = name_len * 2 + 8; stream_write(s, &magic, sizeof(uint32_t)); stream_write(s, &entry_size, sizeof(uint64_t)); stream_write(s, &cur->key, sizeof(uint32_t)); stream_write(s, &name_len, sizeof(uint16_t)); stream_write(s, encoded, name_len * 2 + 2); } /* Returns the root of a linked list containing all of the files listed in the archive's table section. */ struct table_entry *read_table(struct stream *s) { struct table_entry *root = calloc(sizeof(struct table_entry), 1); if (root == NULL) return NULL; bool ended = false; uint32_t magic; uint64_t size; FILE *fp = fopen("/tmp/dump.bin", "wb+"); stream_dump(fp, s, s->len); fclose(fp); do { stream_read(&magic, s, sizeof(uint32_t)); stream_read(&size, s, sizeof(uint64_t)); switch (magic) { case ELIF_MAGIC: 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; } /* Dumps the XP3 table specified by `root` into `s`. */ void dump_table(struct stream *s, struct table_entry *root) { struct table_entry *cur; for (cur = root->next; cur != NULL; cur = cur->next) { dump_elif(s, cur); dump_file(s, cur); } } /* Inserts the file specified by `path` into the table linked list specified by `root`. */ struct table_entry *add_file(struct table_entry *root, char *path) { struct table_entry *new = calloc(sizeof(struct table_entry), 1); if (new == NULL) return NULL; size_t name_len = strlen(path); new->filename = malloc(name_len + 1); if (new->filename == NULL) return NULL; strncpy(new->filename, path, name_len + 1); /* TODO: Replace this with something more deterministic. */ new->key = rand(); new->ctime = 0; new->segment_count = 1; entry_append(root, new); return new; } /* 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; for (cur = root; cur->next != NULL; cur = cur->next); cur->next = e; } /* Frees every entry in the linked list specified by `cur`. */ 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); }