diff options
author | Jari Vetoniemi <mailroxas@gmail.com> | 2017-04-20 16:49:35 +0300 |
---|---|---|
committer | Jari Vetoniemi <mailroxas@gmail.com> | 2017-05-01 22:58:22 +0300 |
commit | 29086b1d12a2c28cffdbfbf0b3990a7bd75506b9 (patch) | |
tree | 8acd48bc30932812744c0adb102d7a7add494357 /src/bin | |
parent | 76b8c9e03c97b16d9ff97f3b79c0ecbff0f5e7f2 (diff) |
work in progress
Diffstat (limited to 'src/bin')
-rw-r--r-- | src/bin/fw/uneaf.c | 208 | ||||
-rw-r--r-- | src/bin/misc/dec2bin.c | 40 | ||||
-rw-r--r-- | src/bin/xi/xi2path.c | 16 | ||||
-rw-r--r-- | src/bin/xi/xi2path.h | 35 | ||||
-rw-r--r-- | src/bin/xi/xidec.c | 136 | ||||
-rw-r--r-- | src/bin/xi/xifile.c | 170 | ||||
-rw-r--r-- | src/bin/xi/xils.c | 94 |
7 files changed, 699 insertions, 0 deletions
diff --git a/src/bin/fw/uneaf.c b/src/bin/fw/uneaf.c new file mode 100644 index 0000000..f23c6d6 --- /dev/null +++ b/src/bin/fw/uneaf.c @@ -0,0 +1,208 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> +#include <err.h> +#include <sys/stat.h> +#include <zlib.h> + +static const char *stdin_name = "/dev/stdin"; + +int ZEXPORT uncompress2 (dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong *sourceLen; +{ + z_stream stream; + int err; + const uInt max = (uInt)-1; + uLong len, left; + Byte buf[1]; /* for detection of incomplete stream when *destLen == 0 */ + + len = *sourceLen; + if (*destLen) { + left = *destLen; + *destLen = 0; + } + else { + left = 1; + dest = buf; + } + + stream.next_in = (z_const Bytef *)source; + stream.avail_in = 0; + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + err = inflateInit2(&stream, -15); + if (err != Z_OK) return err; + + stream.next_out = dest; + stream.avail_out = 0; + + do { + if (stream.avail_out == 0) { + stream.avail_out = left > (uLong)max ? max : (uInt)left; + left -= stream.avail_out; + } + if (stream.avail_in == 0) { + stream.avail_in = len > (uLong)max ? max : (uInt)len; + len -= stream.avail_in; + } + err = inflate(&stream, Z_NO_FLUSH); + } while (err == Z_OK); + + *sourceLen -= len + stream.avail_in; + if (dest != buf) + *destLen = stream.total_out; + else if (stream.total_out && err == Z_BUF_ERROR) + left = 1; + + inflateEnd(&stream); + return err == Z_STREAM_END ? Z_OK : + err == Z_NEED_DICT ? Z_DATA_ERROR : + err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR : + err; +} + +static void +zdeflate(const uint8_t *buf, const size_t buf_sz, uint8_t **out_dec, size_t *inout_dec_sz) +{ + uLongf dsize = (*inout_dec_sz ? *inout_dec_sz : buf_sz * 2), bsize; + int ret = Z_OK; + + do { + if (!(*out_dec = realloc(*out_dec, (bsize = dsize)))) + err(EXIT_FAILURE, "realloc(%zu)", dsize); + dsize *= 2; + } while ((ret = uncompress(*out_dec, &bsize, buf, buf_sz)) == Z_BUF_ERROR && !*inout_dec_sz); + + if (ret != Z_OK) + errx(EXIT_FAILURE, "uncompress(%zu, %zu) == %d", (size_t)(dsize / 2), buf_sz, ret); + + *inout_dec_sz = bsize; +} + +static FILE* +fopen_or_die(const char *path, const char *mode) +{ + assert(path && mode); + + FILE *f; + if (!(f = fopen(path, mode))) + err(EXIT_FAILURE, "fopen(%s, %s)", path, mode); + + return f; +} + +static void +mkdirp(const char *path) +{ + assert(path); + for (const char *s = path; *s; ++s) { + if (*s != '/') + continue; + + *(char*)s = 0; + mkdir(path, 0755); + *(char*)s = '/'; + } +} + +static void +write_data_to(const uint8_t *data, const size_t size, const char *path) +{ + assert(data && path); + mkdirp(path); + FILE *f = fopen_or_die(path, "wb"); + + struct header { + uint8_t magic[4]; + uint32_t unknown; + uint32_t size; + uint32_t offset; + } __attribute__((packed)) header; + + memcpy(&header, data, sizeof(header)); + warnx("%s", path); + + if (!memcmp(header.magic, "#EMZ", sizeof(header.magic))) { + uint8_t *buf = NULL; + size_t dec_size = header.size; + zdeflate(data + header.offset, size - header.offset, &buf, &dec_size); + fwrite(buf, 1, dec_size, f); + free(buf); + } else { + fwrite(data, 1, size, f); + } + + fclose(f); +} + +static void +unpack(const char *path, const char *outdir) +{ + assert(path); + const char *name = (!strcmp(path, "-") ? stdin_name : path); + FILE *f = (name == stdin_name ? stdin : fopen_or_die(name, "rb")); + + struct header { + uint8_t magic[4]; + uint16_t major, minor; + uint64_t size; + uint32_t count; + uint64_t unknown; + uint8_t padding[100]; + } __attribute__((packed)) header; + + if (fread(&header, 1, sizeof(header), f) != sizeof(header)) + err(EXIT_FAILURE, "fread(%zu)", sizeof(header)); + + if (memcmp(header.magic, "#EAF", sizeof(header.magic))) + errx(EXIT_FAILURE, "'%s' is not a #EAF file", name); + + for (size_t i = 0; i < header.count; ++i) { + struct file { + char path[256]; + uint64_t offset, size; + uint8_t padding[16]; + } __attribute__((packed)) file; + + if (fread(&file, 1, sizeof(file), f) != sizeof(file)) + err(EXIT_FAILURE, "fread(%zu)", sizeof(file)); + + fpos_t pos; + fgetpos(f, &pos); + + uint8_t *data; + if (!(data = malloc(file.size))) + err(EXIT_FAILURE, "malloc(%zu)", file.size); + + fseek(f, file.offset, SEEK_SET); + if (fread(data, 1, file.size, f) != file.size) + err(EXIT_FAILURE, "fread(%zu)", file.size); + + char path[4096]; + snprintf(path, sizeof(path), "%s/%s", outdir, file.path); + write_data_to(data, file.size, path); + free(data); + fsetpos(f, &pos); + } + + fclose(f); +} + +int +main(int argc, char *argv[]) +{ + if (argc < 3) + errx(EXIT_FAILURE, "usage: %s outdir file ...", argv[0]); + + for (int i = 2; i < argc; ++i) + unpack(argv[i], argv[1]); + + return EXIT_SUCCESS; +} diff --git a/src/bin/misc/dec2bin.c b/src/bin/misc/dec2bin.c new file mode 100644 index 0000000..be1dd5e --- /dev/null +++ b/src/bin/misc/dec2bin.c @@ -0,0 +1,40 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <err.h> + +int +main(int argc, char *argv[]) +{ + if (argc < 3) + errx(EXIT_FAILURE, "usage: %s <u8|u16|u32|u64> number", argv[0]); + + const struct { + const char *t; + size_t sz; + } map[] = { + { .t = "u8", .sz = sizeof(uint8_t) }, + { .t = "u16", .sz = sizeof(uint16_t) }, + { .t = "u32", .sz = sizeof(uint32_t) }, + { .t = "u64", .sz = sizeof(uint64_t) }, + }; + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + + size_t sz = 0; + for (size_t i = 0; i < ARRAY_SIZE(map); ++i) { + if (strcmp(argv[1], map[i].t)) + continue; + + sz = map[i].sz; + break; + } + + if (!sz) + errx(EXIT_FAILURE, "unknown type: %s", argv[1]); + + const uint64_t v = strtoll(argv[2], NULL, 10); + fwrite(&v, sz, 1, stdout); + return EXIT_SUCCESS; +} diff --git a/src/bin/xi/xi2path.c b/src/bin/xi/xi2path.c new file mode 100644 index 0000000..4b4c519 --- /dev/null +++ b/src/bin/xi/xi2path.c @@ -0,0 +1,16 @@ +#include <stdlib.h> +#include <err.h> + +#include "xi2path.h" + +int +main(int argc, char *argv[]) +{ + if (argc < 2) + errx(EXIT_FAILURE, "usage: %s id", argv[0]); + + char path[12]; + xi2path(path, strtol(argv[1], NULL, 10)); + printf("%s\n", path); + return EXIT_SUCCESS; +} diff --git a/src/bin/xi/xi2path.h b/src/bin/xi/xi2path.h new file mode 100644 index 0000000..954c554 --- /dev/null +++ b/src/bin/xi/xi2path.h @@ -0,0 +1,35 @@ +#pragma once + +#include <stdio.h> +#include <assert.h> +#include <stdint.h> + +static inline void +xi2path(char out[12], const uint16_t id) +{ + // Forms path like: section/id.DAT, from 16bit id. + // First 7 bits are used for DAT and rest of the bits for ROM section. + // + // e.g. ID of 1000 would look like 11101000 00000011 in binary. + // RDDDDDDD RRRRRRRR + // + // In the above graph, 'R' bits form the ROM section (7) and 'D' bits form the DAT (104). + // Thus maximum DAT and ROM section IDs are 127 and 511 respectively (65535 as decimal). + + snprintf(out, 12, "%u/%u.DAT", id >> 7, id & 0x7F); +} + +static inline void +xi2rompath(char out[18], const uint8_t rom, const uint16_t id) +{ + assert(rom <= 9); + + char path[12]; + xi2path(path, id); + + if (rom > 1) { + snprintf(out, 18, "ROM%u/%s", rom, path); + } else { + snprintf(out, 18, "ROM/%s", path); + } +} diff --git a/src/bin/xi/xidec.c b/src/bin/xi/xidec.c new file mode 100644 index 0000000..cb4c1bb --- /dev/null +++ b/src/bin/xi/xidec.c @@ -0,0 +1,136 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> +#include <string.h> +#include <assert.h> +#include <err.h> + +static uint8_t +rotate_right(uint8_t b, const uint8_t count) +{ + for (size_t i = 0; i < count; ++i) { + const bool drop = ((b & 0x01) == 0x01); + b = (b >> 1) | (drop ? 0x80 : 0); + } + return b; +} + +static void +decode(uint8_t *data, const size_t size, const uint8_t count) +{ + assert(data); + + for (size_t i = 0; i < size; ++i) + data[i] = rotate_right(data[i], count); +} + +static size_t +count_bits(const uint8_t byte) +{ + static const uint8_t lut[16] = { + 0, 1, 1, 2, 1, 2, 2, 3, + 1, 2, 2, 3, 2, 3, 3, 4 + }; + + return lut[byte & 0x0F] + lut[byte >> 4]; +} + +static uint8_t +text_rotation(const uint8_t *data, const size_t size) +{ + assert(data); + + if (size < 2 || (data[0] == 0 && data[1] == 0)) + return 0; + + const int seed = count_bits(data[1]) - count_bits(data[0]); + switch (abs(seed) % 5) { + case 0: return 1; + case 1: return 7; + case 2: return 2; + case 3: return 6; + case 4: return 3; + default:break; + } + + assert(0 && "failed to detect rotation"); + return 0; +} + +static uint8_t +other_rotation(const uint8_t *data, const size_t size) +{ + assert(data); + + if (size < 13) + return 0; + + const int seed = count_bits(data[2]) - count_bits(data[11]) + count_bits(data[12]); + switch (abs(seed) % 5) { + case 0: return 7; + case 1: return 1; + case 2: return 6; + case 3: return 2; + case 4: return 5; + default:break; + } + + assert(0 && "failed to detect rotation"); + return 0; +} + +static uint8_t +item_rotation(const uint8_t *data, const size_t size) +{ + assert(data); + (void)data, (void)size; + return 5; +} + +int +main(int argc, char *argv[]) +{ + if (argc < 2) + errx(EXIT_FAILURE, "usage: %s (name | ability | spell | item | text) < data", argv[0]); + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + + const struct info { + const char *name; + size_t chunk; + uint8_t (*rotation)(const uint8_t *data, const size_t size); + } map[] = { + { .name = "name", .chunk = 32 }, + { .name = "ability", .chunk = 1024, .rotation = other_rotation }, + { .name = "spell", .chunk = 1024, .rotation = other_rotation }, + { .name = "item", .chunk = 3072, .rotation = item_rotation }, + { .name = "text", .chunk = 255, .rotation = text_rotation }, + }; + + const struct info *info = NULL; + for (size_t i = 0; i < ARRAY_SIZE(map); ++i) { + if (strcmp(map[i].name, argv[1])) + continue; + + info = &map[i]; + break; + } + + if (!info) + errx(EXIT_FAILURE, "unknown file type '%s'", argv[1]); + + uint8_t buf[4096]; + assert(sizeof(buf) >= info->chunk); + + size_t bytes; + while ((bytes = fread(buf, 1, info->chunk, stdin)) > 0) { + if (info->rotation) + decode(buf, bytes, info->rotation(buf, bytes)); + + fwrite(buf, 1, bytes, stdout); + } + + return EXIT_SUCCESS; +} diff --git a/src/bin/xi/xifile.c b/src/bin/xi/xifile.c new file mode 100644 index 0000000..fb7f2f7 --- /dev/null +++ b/src/bin/xi/xifile.c @@ -0,0 +1,170 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <err.h> + +static const char *stdin_name = "/dev/stdin"; + +static FILE* +fopen_or_die(const char *path, const char *mode) +{ + assert(path && mode); + + FILE *f; + if (!(f = fopen(path, mode))) + err(EXIT_FAILURE, "fopen(%s, %s)", path, mode); + + return f; +} + +static void +detect(const char *path) +{ + assert(path); + + uint8_t buf[32] = {0}; + const char *name = (!strcmp(path, "-") ? stdin_name : path); + FILE *f = (name == stdin_name ? stdin : fopen_or_die(name, "rb")); + fread(buf, 1, sizeof(buf), f); + + const struct info { + const char *name; + const uint8_t *header; + size_t chunk; + } map[] = { +#define HDR(...) .header = (const uint8_t[]){__VA_ARGS__}, .chunk = sizeof((const uint8_t[]){__VA_ARGS__}) + { + .name = "BGMStream", + HDR('B', 'G', 'M', 'S', 't', 'r', 'e', 'a', 'm') + }, { + .name = "SeWave", + HDR('S', 'e', 'W', 'a', 'v', 'e') + }, { + .name = "PMUS", + HDR('P', 'M', 'U', 'S') + }, { + .name = "RIFF/WAVE", + HDR('R', 'I', 'F', 'F', 0x24, 0xB3, 0xCF, 0x04, 'W', 'A', 'V', 'E', 'f', 'm', 't') + }, { + .name = "RIFF/ACON", + HDR('R', 'I', 'F', 'F', 0x58, 0x23, 0, 0, 'A', 'C', 'O', 'N', 'a', 'n', 'i', 'h') + }, { + .name = "name", + HDR('n', 'o', 'n', 'e', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + }, { + .name = "syst", + HDR('s', 'y', 's', 't') + }, { + .name = "menu", + HDR('m', 'e', 'n', 'u') + }, { + .name = "lobb", + HDR('l', 'o', 'b', 'b') + }, { + .name = "wave", + HDR('w', 'a', 'v', 'e') + }, { + .name = "ability", + HDR(0, 0, 0, 0x17, 0, 0, 0, 0, 0x80) + }, { + .name = "spell", + HDR(0, 0, 0, 0, 0x03, 0, 0x9F, 0, 0x10) + }, { + .name = "mgc_", + HDR('m', 'g', 'c', '_') + }, { + .name = "win0", + HDR('w', 'i', 'n', '0') + }, { + .name = "titl", + HDR('t', 'i', 't', 'l') + }, { + .name = "sel_", + HDR('s', 'e', 'l', '_') + }, { + .name = "damv", + HDR('d', 'a', 'm', 'v') + }, { + .name = "XISTRING", + HDR('X', 'I', 'S', 'T', 'R', 'I', 'N', 'G') + }, { + .name = "prvd", + HDR('p', 'r', 'v', 'd') + }, { + .name = "selp", + HDR('s', 'e', 'l', 'p') + }, { + .name = "dun", + HDR('d', 'u', 'n') + }, { + .name = "town", + HDR('t', 'o', 'w', 'n') + }, { + .name = "dese", + HDR('d', 'e', 's', 'e') + }, { + .name = "fore", + HDR('f', 'o', 'r', 'e') + }, { + .name = "tree", + HDR('t', 'r', 'e', 'e') + }, { + .name = "unka", + HDR('u', 'n', 'k', 'a') + }, { + .name = "moun", + HDR('m', 'o', 'u', 'n') + }, { + .name = "cast", + HDR('c', 'a', 's', 't') + }, { + .name = "fuji", + HDR('f', 'u', 'j', 'i') + }, { + .name = "view", + HDR('v', 'i', 'e', 'w') + } +#undef HDR + }; + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + + const struct info *info = NULL; + for (size_t i = 0; i < ARRAY_SIZE(map); ++i) { + assert(map[i].chunk <= sizeof(buf)); + if (memcmp(buf, map[i].header, map[i].chunk)) + continue; + + info = &map[i]; + break; + } + + if (info) { + printf("%s: %s\n", name, info->name); + } else { + int i; + for (i = 0; i < 32 && isprint(buf[i]); ++i); + if (i > 0) { + printf("%s: unknown (%.*s)\n", name, i, buf); + } else { + printf("%s: unknown\n", name); + } + } + + fclose(f); +} + +int +main(int argc, char *argv[]) +{ + if (argc < 2) + errx(EXIT_FAILURE, "usage: %s file ...", argv[0]); + + for (int i = 1; i < argc; ++i) + detect(argv[i]); + + return EXIT_SUCCESS; +} diff --git a/src/bin/xi/xils.c b/src/bin/xi/xils.c new file mode 100644 index 0000000..b29b54b --- /dev/null +++ b/src/bin/xi/xils.c @@ -0,0 +1,94 @@ +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <assert.h> +#include <err.h> + +#include "xi2path.h" + +static FILE* +fopen_or_die(const char *gamedir, const char *file, const char *mode) +{ + assert(gamedir && file && mode); + + char path[4096]; + snprintf(path, sizeof(path), "%s/%s", gamedir, file); + + FILE *f; + if (!(f = fopen(path, mode))) + err(EXIT_FAILURE, "fopen(%s, %s)", path, mode); + + return f; +} + +static void +dump_tables(const char *dir, const char *names[2], const uint8_t rom, const bool print_all, const bool verbose) +{ + assert(dir && names); + + // FTABLE.DAT contains list of IDs. + // VTABLE.DAT contains number of ROM for each entry or 0 if the entry is not used. + FILE *f = fopen_or_die(dir, names[0], "rb"); + FILE *v = fopen_or_die(dir, names[1], "rb"); + +#define ELEM_SIZE(x) (sizeof(x[0])) +#define ARRAY_SIZE(x) (sizeof(x) / ELEM_SIZE(x)) + + { + size_t read[2]; + uint16_t id[255]; + uint8_t exist[255]; + while ((read[0] = fread(id, ELEM_SIZE(id), ARRAY_SIZE(id), f)) > 0 && + (read[1] = fread(exist, ELEM_SIZE(exist), ARRAY_SIZE(exist), v)) > 0) { + assert(read[0] == read[1]); + + for (size_t i = 0; i < read[0]; ++i) { + if (!print_all && !exist[i]) + continue; + + if (verbose) + printf("%u: ", exist[i]); + + char path[18]; + xi2rompath(path, rom, id[i]); + printf("%s\n", path); + } + } + } + + fclose(v); + fclose(f); +} + +int +main(int argc, char *argv[]) +{ + bool verbose = false; + bool print_all = false; + const char *gamedir = NULL; + for (int i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "-a")) { + print_all = true; + } else if (!strcmp(argv[i], "-v")) { + verbose = true; + } else { + gamedir = argv[i]; + break; + } + } + + if (!gamedir) + errx(EXIT_FAILURE, "usage: %s [-a|-v] gamedir", argv[0]); + + dump_tables(gamedir, (const char*[]){ "FTABLE.DAT", "VTABLE.DAT" }, 1, print_all, verbose); + + for (uint8_t i = 2; i <= 9; ++i) { + char dir[4096], f[12], v[12]; + snprintf(dir, sizeof(dir), "%s/ROM%u", gamedir, i); + snprintf(f, sizeof(f), "FTABLE%u.DAT", i); + snprintf(v, sizeof(v), "VTABLE%u.DAT", i); + dump_tables(dir, (const char*[]){ f, v }, i, print_all, verbose); + } + + return EXIT_SUCCESS; +} |