/* 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));
}
}
/* 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 *read_table(struct stream *s) {
struct table_entry *root = calloc(sizeof(struct table_entry), 1);
bool ended = false;
uint32_t magic;
uint64_t size;
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;
}
/* 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);
}
/* 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->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 {
/* 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;
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);
}