diff options
Diffstat (limited to 'src/bin/xi')
-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 |
5 files changed, 451 insertions, 0 deletions
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; +} |