diff options
-rw-r--r-- | Makefile | 42 | ||||
-rw-r--r-- | spec/ability.fspec | 11 | ||||
-rw-r--r-- | spec/ftable.fspec | 3 | ||||
-rw-r--r-- | spec/item.fspec | 28 | ||||
-rw-r--r-- | spec/model.fspec | 25 | ||||
-rw-r--r-- | spec/name.fspec | 5 | ||||
-rw-r--r-- | spec/spell.fspec | 18 | ||||
-rw-r--r-- | spec/vtable.fspec | 3 | ||||
-rw-r--r-- | src/dump.c | 310 | ||||
-rw-r--r-- | src/ragel/fspec.h | 77 | ||||
-rw-r--r-- | src/ragel/fspec.rl | 329 | ||||
-rw-r--r-- | src/ragel/ragel.h | 236 | ||||
-rw-r--r-- | src/utils/dec2bin.c | 40 | ||||
-rw-r--r-- | src/xi/xi2path.c | 16 | ||||
-rw-r--r-- | src/xi/xi2path.h | 35 | ||||
-rw-r--r-- | src/xi/xidec.c | 136 | ||||
-rw-r--r-- | src/xi/xifile.c | 169 | ||||
-rw-r--r-- | src/xi/xils.c | 94 | ||||
-rw-r--r-- | vim/filespec.vim | 56 |
19 files changed, 1633 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0a8aaf8 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +PREFIX ?= /usr/local +bindir ?= /bin + +WARNINGS := -Wall -Wextra -Wformat=2 -Winit-self -Wfloat-equal -Wcast-align -Wpointer-arith +CFLAGS += -std=c11 $(WARNINGS) + +all: fspec-dump dec2bin xidec xi2path xils xifile + +%.c: %.rl + ragel $^ + +fspec-dump: src/ragel/ragel.h src/ragel/fspec.h src/ragel/fspec.c src/dump.c + $(LINK.c) $(filter %.c,$^) $(LDLIBS) -o $@ + +dec2bin: src/utils/dec2bin.c + $(LINK.c) $(filter %.c,$^) $(LDLIBS) -o $@ + +xidec: src/xi/xidec.c + $(LINK.c) $(filter %.c,$^) $(LDLIBS) -o $@ + +xi2path: src/xi/xi2path.c + $(LINK.c) $(filter %.c,$^) $(LDLIBS) -o $@ + +xils: src/xi/xils.c + $(LINK.c) $(filter %.c,$^) $(LDLIBS) -o $@ + +xifile: src/xi/xifile.c + $(LINK.c) $(filter %.c,$^) $(LDLIBS) -o $@ + +install: + install -Dm755 $(DESTDIR)$(PREFIX)$(bindir)/fspec-dump + install -Dm755 $(DESTDIR)$(PREFIX)$(bindir)/dec2bin + install -Dm755 $(DESTDIR)$(PREFIX)$(bindir)/xidec + install -Dm755 $(DESTDIR)$(PREFIX)$(bindir)/xi2path + install -Dm755 $(DESTDIR)$(PREFIX)$(bindir)/xils + install -Dm755 $(DESTDIR)$(PREFIX)$(bindir)/xifile + +clean: + $(RM) src/ragel/fspec.c + $(RM) fspec-dump dec2bin xidec xi2path xils xifile + +.PHONY: all clean install diff --git a/spec/ability.fspec b/spec/ability.fspec new file mode 100644 index 0000000..3c2c890 --- /dev/null +++ b/spec/ability.fspec @@ -0,0 +1,11 @@ +// Abilities +struct ability { + u16 index; + u16 icon_id; + u16 mp_cost; + u16 unknown; + u16 targets; + u8 name[32] = sjis; // The kind actually depends on ROM section + u8 description[256] = sjis; // ^ Ditto, we probably can't express this + u8 padding[726] = pad; +}; diff --git a/spec/ftable.fspec b/spec/ftable.fspec new file mode 100644 index 0000000..615b7b3 --- /dev/null +++ b/spec/ftable.fspec @@ -0,0 +1,3 @@ +struct ftable { + u16 id; +}; diff --git a/spec/item.fspec b/spec/item.fspec new file mode 100644 index 0000000..c4d1767 --- /dev/null +++ b/spec/item.fspec @@ -0,0 +1,28 @@ +struct string_info { + u32 offset; + u32 flags; +}; + +struct strings { + u32 nmemb; + struct string_info info[nmemb]; +}; + +struct item { + u32 id; + u16 flags; + u16 stack; + u16 type; + u16 resource; + u16 targets; + + union data (type) { + 4 => struct weapon weapon; + 5 => struct armor armor; + 7 => struct usable usable; + 12 => struct puppet puppet; + * => struct general general; + }; + + struct strings strings; +}; diff --git a/spec/model.fspec b/spec/model.fspec new file mode 100644 index 0000000..afa8281 --- /dev/null +++ b/spec/model.fspec @@ -0,0 +1,25 @@ +struct texture { + u8 type; + u8 name[16] = ascii; + u32 version; + u32 width; + u32 height; + u32 unknown[6]; + u32 bits_per_pal_clr; +}; + +struct geometry { + u16 unknown; + u16 vert_and_bone_ref_flag; + u16 mirror; + u32 draw_data_offset; + u16 draw_data_size; + u32 bone_ref_offset; + u16 bone_ref_count; + u32 weighted_vert_count_offset; + u16 max_weights_per_vertex; + u32 weighted_data_offset; + u16 weighted_data_count; + u32 vertex_data_offset; + u16 vertex_data_size; +}; diff --git a/spec/name.fspec b/spec/name.fspec new file mode 100644 index 0000000..69f75de --- /dev/null +++ b/spec/name.fspec @@ -0,0 +1,5 @@ +// NPC IDs +struct name = { + u8 name[28] = ascii; // The kind actually depends on ROM section + u32 id; +}; diff --git a/spec/spell.fspec b/spec/spell.fspec new file mode 100644 index 0000000..f65b5ad --- /dev/null +++ b/spec/spell.fspec @@ -0,0 +1,18 @@ +struct spell { + u16 index; + u16 type; // 1-6 for White/Black/Summon/Ninja/Bard/Blue + u16 element; + u16 targets; + u16 skill; + u16 mp_cost; + u8 casting_time; // in quarter of seconds + u8 recast_delay; // in quarter of seconds + u8 level[24]; // 1 byte per job, 0xxFF if not learnable, first slot is NONE job so always 0xFF + u16 id; // 0 for "unused" spells; often, but not always, equal to index + u8 unknown; + u8 jp_name[20] = sjis; + u8 en_name[20] = ascii; + u8 jp_description[128] = sjis; + u8 en_description[128] = ascii; + u8 padding[687] = pad; +}; diff --git a/spec/vtable.fspec b/spec/vtable.fspec new file mode 100644 index 0000000..0fc8701 --- /dev/null +++ b/spec/vtable.fspec @@ -0,0 +1,3 @@ +struct vtable { + u8 exist; +}; diff --git a/src/dump.c b/src/dump.c new file mode 100644 index 0000000..641bb55 --- /dev/null +++ b/src/dump.c @@ -0,0 +1,310 @@ +#include <stdlib.h> +#include <stdio.h> +#include <limits.h> +#include <string.h> +#include <assert.h> +#include <err.h> + +#include <iconv.h> +#include <locale.h> +#include <langinfo.h> + +#include "ragel/fspec.h" + +static size_t +to_hex(const char *buf, const size_t buf_sz, char *out, const size_t out_sz, const bool reverse) +{ + assert(out); + const char nibble[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + const uint8_t nbs = sizeof(nibble) - 1; + + size_t w = 0; + for (size_t i = 0; i < buf_sz && out_sz > 2 && w < out_sz - 2; ++i) { + for (uint8_t c = 0; c < CHAR_BIT / 8 && w < out_sz; ++c) { + const size_t idx = (reverse ? (buf_sz - 1) - i : i); + const uint8_t hi = (buf[idx] >> (4 * (c + 1))) & nbs; + const uint8_t lo = (buf[idx] >> (8 * c)) & nbs; + out[w++] = nibble[hi]; + out[w++] = nibble[lo]; + } + } + + assert(w < out_sz); + out[w] = 0; + return w; +} + +static void +print_decimal(const char *buf, const bool is_signed, const size_t size, const size_t nmemb) +{ + if (nmemb > 1) + printf("{ "); + + for (size_t n = 0; n < nmemb; ++n) { + char hex[2 * sizeof(uint64_t) + 1]; + to_hex(buf + size * n, size, hex, sizeof(hex), true); + const char *delim = (nmemb > 1 && n + 1 < nmemb ? ", " : ""); + + if (is_signed) { + printf("%ld%s", (int64_t)strtoll(hex, NULL, 16), delim); + } else { + printf("%lu%s", (uint64_t)strtoull(hex, NULL, 16), delim); + } + } + + printf("%s\n", (nmemb > 1 ? " }" : "")); +} + +static void +print_hex(const char *buf, const size_t size, const size_t nmemb) +{ + if (nmemb > 1) + printf("{ "); + + for (size_t n = 0; n < nmemb; ++n) { + char hex[2 * sizeof(uint64_t) + 1]; + to_hex(buf + size * n, size, hex, sizeof(hex), false); + printf("%s%s", hex, (nmemb > 1 && n + 1 < nmemb ? ", " : "")); + } + + printf("%s\n", (nmemb > 1 ? " }" : "")); +} + +static void +print_chars(const char *buf, const size_t size, const size_t nmemb) +{ + assert(size == sizeof(char)); + + for (size_t n = 0; n < nmemb && buf[n] != 0; ++n) + printf("%c", buf[n]); +} + +static void +print_encoded(const char *buf, const char *from, const char *to, const size_t size, const size_t nmemb) +{ + assert(from && size == sizeof(char)); + + if (!to) { + static const char *sys_encoding; + if (!sys_encoding) { + setlocale(LC_ALL, ""); + sys_encoding = nl_langinfo(CODESET); + } + + to = sys_encoding; + } + + iconv_t iv; + if ((iv = iconv_open(to, from)) == (iconv_t)-1) + err(EXIT_FAILURE, "iconv_open(%s, %s)", to, from); + + const char *in = buf; + size_t in_left = nmemb; + do { + char enc[1024], *out = enc; + size_t out_left = sizeof(enc); + + if (iconv(iv, (char**)&in, &in_left, &out, &out_left) == (size_t)-1) + err(EXIT_FAILURE, "iconv(%s, %s)", to, from); + + print_chars(enc, 1, sizeof(enc) - out_left); + } while (in_left > 0); + + iconv_close(iv); + puts(""); +} + +struct container; +struct field { + struct fspec_field f; + struct container *c, *link; + uint64_t value; +}; + +struct container { + struct fspec_container c; + struct field fields[255]; + size_t num_fields; +}; + +static size_t +field_get_buffer(const struct field *field, FILE *f, char **buf) +{ + assert(field && f && buf); + + switch (field->f.array.type) { + case FSPEC_ARRAY_FIXED: + if (!(*buf = calloc(field->f.array.nmemb, field->f.type.size))) + err(EXIT_FAILURE, "calloc(%zu, %zu)", field->f.array.nmemb, field->f.type.size); + + if (fread(*buf, field->f.type.size, field->f.array.nmemb, f) != field->f.array.nmemb) + return 0; + + return field->f.array.nmemb; + + case FSPEC_ARRAY_MATCH: + { + size_t off = 0; + const size_t msz = field->f.array.match.size; + for (size_t len = 0;; ++off) { + if (off >= (len ? len - 1 : len) && !(*buf = realloc(*buf, (len += 1024)))) + err(EXIT_FAILURE, "realloc(%zu)", len); + + assert(off < len); + if (fread(*buf + off, 1, 1, f) != 1) + return 0; + + if (off >= msz && !memcmp(field->f.array.match.data, *buf + off - msz, msz)) + break; + } + + (*buf)[off] = 0; + return off; + } + break; + + case FSPEC_ARRAY_VAR: + for (size_t i = 0; i < field->c->num_fields; ++i) { + if (!strcmp(field->c->fields[i].f.name, field->f.array.var)) + return field->c->fields[i].value; + } + break; + } + + return 0; +} + +static void +container_process(struct container *container, FILE *f); + +static void +field_process(struct field *field, FILE *f) +{ + assert(field && f); + + char *buf = NULL; + const size_t nmemb = field_get_buffer(field, f, &buf); + + if (field->link) { + for (size_t i = 0; i < nmemb; ++i) + container_process(field->link, f); + } else { + printf("%s(%zu) %s[%zu] = ", field->f.type.name, field->f.type.size, field->f.name, nmemb); + + if (field->f.kind.flags & FSPEC_KIND_IGNORE) { + puts("..."); + } else if (field->f.kind.flags & FSPEC_KIND_ENCODING) { + print_encoded(buf, field->f.kind.name, NULL, field->f.type.size, nmemb); + } else if (field->f.kind.flags & FSPEC_KIND_HEXADECIMAL) { + print_hex(buf, field->f.type.size, nmemb); + } else { + print_decimal(buf, (field->f.type.flags & FSPEC_TYPE_SIGNED), field->f.type.size, nmemb); + } + + if (nmemb == 1) { + char hex[2 * sizeof(uint64_t) + 1]; + to_hex(buf, field->f.type.size, hex, sizeof(hex), true); + field->value = strtoull(hex, NULL, 16); + } + } + + free(buf); +} + +static void +container_process(struct container *container, FILE *f) +{ + assert(container && f); + + for (size_t i = 0; i < container->num_fields; ++i) + field_process(&container->fields[i], f); +} + +#define container_of(ptr, type, member) ((type *)((char *)(1 ? (ptr) : &((type *)0)->member) - offsetof(type, member))) + +struct fspec_file { + // TODO: Rethink container/field + // I think I want just flat structure of key / value pairs in the end + // Especially if I want to express members of struct members (e.g. struct a { struct b b; u8 c[b.x]; };) + struct container containers[32]; + struct fspec fspec; + FILE *handle; + size_t num_containers; +}; + +static void +fspec_field(struct fspec *fspec, const struct fspec_container *container, const struct fspec_field *field) +{ + assert(fspec && container); + struct fspec_file *f = container_of(fspec, struct fspec_file, fspec); + + if (!f->num_containers || memcmp(container, &f->containers[f->num_containers - 1].c, sizeof(*container))) + f->containers[f->num_containers++].c = *container; + + struct container *c = &f->containers[f->num_containers - 1]; + + if (field->type.flags & FSPEC_TYPE_CONTAINER) { + for (size_t i = 0; i < f->num_containers - 1; ++i) { + if (strcmp(field->type.name, f->containers[i].c.name)) + continue; + + c->fields[c->num_fields].link = &f->containers[i]; + break; + } + } + + c->fields[c->num_fields].c = c; + c->fields[c->num_fields++].f = *field; +} + +static size_t +fspec_read(struct fspec *fspec, char *buf, const size_t size, const size_t nmemb) +{ + assert(fspec && buf); + struct fspec_file *f = container_of(fspec, struct fspec_file, fspec); + return fread(buf, size, nmemb, f->handle); +} + +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; +} + +int +main(int argc, const char *argv[]) +{ + if (argc < 2) + errx(EXIT_FAILURE, "usage: %s file.spec < data\n", argv[0]); + + uint8_t data[4096] = {0}; + + struct fspec_file file = { + .fspec = { + .ops = { + .read = fspec_read, + .field = fspec_field, + }, + .mem = { + .data = data, + .size = sizeof(data), + }, + }, + .handle = fopen_or_die(argv[1], "rb"), + }; + + fspec_parse(&file.fspec); + + if (!file.num_containers) + errx(EXIT_FAILURE, "'%s' contains no containers", argv[1]); + + container_process(&file.containers[file.num_containers - 1], stdin); + fclose(file.handle); + return EXIT_SUCCESS; +} diff --git a/src/ragel/fspec.h b/src/ragel/fspec.h new file mode 100644 index 0000000..68998f4 --- /dev/null +++ b/src/ragel/fspec.h @@ -0,0 +1,77 @@ +#pragma once + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +struct fspec_bytes { + const uint8_t *data; + size_t size; +}; + +enum fspec_kind_bits { + FSPEC_KIND_IGNORE = 1<<0, + FSPEC_KIND_HEXADECIMAL = 1<<1, + FSPEC_KIND_ENCODING = 1<<2, +}; + +struct fspec_kind { + const char *name; + uint32_t flags; +}; + +enum fspec_array_type { + FSPEC_ARRAY_FIXED, + FSPEC_ARRAY_MATCH, + FSPEC_ARRAY_VAR, +}; + +struct fspec_array { + enum fspec_array_type type; + + union { + struct fspec_bytes match; + const char *var; + size_t nmemb; + }; +}; + +enum fspec_type_bits { + FSPEC_TYPE_SIGNED = 1<<0, + FSPEC_TYPE_CONTAINER = 1<<1, +}; + +struct fspec_type { + const char *name; + size_t size; + uint32_t flags; +}; + +struct fspec_field { + struct fspec_type type; + struct fspec_array array; + struct fspec_kind kind; + const char *name; +}; + +struct fspec_container { + const char *name; +}; + +struct fspec; +struct fspec { + struct { + void (*field)(struct fspec *fspec, const struct fspec_container *container, const struct fspec_field *field); + size_t (*read)(struct fspec *fspec, char *buf, const size_t size, const size_t nmemb); + } ops; + + struct { + // XXX: replace with ops.alloc, ops.free + // on dump.c we can then just provide implementation that still uses reasonable amount of static memory + // but we don't limit the code from working with regular dynamic memory + uint8_t *data; + size_t size; + } mem; +}; + +void fspec_parse(struct fspec *fspec); diff --git a/src/ragel/fspec.rl b/src/ragel/fspec.rl new file mode 100644 index 0000000..8493cf1 --- /dev/null +++ b/src/ragel/fspec.rl @@ -0,0 +1,329 @@ +#include "fspec.h" +#include "ragel.h" + +// It's pretty good base so far. +// ragel_search_str for typechecking variable delcaration is hack. +// State should have hashmap for fields/containers. +// +// XXX: Maybe drop whole container thing and just give field const char *parent; that points to keypath of container. +// Then we would have flat structure like, "foo, foo.var, foo.b, ..." + +static const struct fspec_container default_container = {0}; +static const struct fspec_field default_field = { .array.nmemb = 1 }; + +enum stack_type { + STACK_VAR, + STACK_STR, + STACK_NUM, +}; + +struct stack { + enum stack_type type; + + union { + struct fspec_bytes str; + const char *var; + uint64_t num; + }; +}; + +struct state { + struct ragel ragel; + struct stack stack; + struct fspec_field field; + struct fspec_container container; + size_t container_data_offset; +}; + +static const char* +stack_type_to_str(const enum stack_type type) +{ + switch (type) { + case STACK_VAR: return "var"; + case STACK_STR: return "str"; + case STACK_NUM: return "num"; + }; + + assert(0 && "should not happen"); + return "unknown"; +} + +static void +stack_check_type(const struct ragel *ragel, const struct stack *stack, const enum stack_type type) +{ + assert(ragel && stack); + + if (stack->type != type) + ragel_throw_error(ragel, "tried to get '%s' from stack, but the last pushed type was '%s'", stack_type_to_str(type), stack_type_to_str(stack->type)); +} + +static const char* +stack_get_var(const struct ragel *ragel, const struct stack *stack) +{ + assert(ragel && stack); + stack_check_type(ragel, stack, STACK_VAR); + return stack->var; +} + +static const struct fspec_bytes* +stack_get_str(const struct ragel *ragel, const struct stack *stack) +{ + assert(ragel && stack); + stack_check_type(ragel, stack, STACK_STR); + return &stack->str; +} + +static uint64_t +stack_get_num(const struct ragel *ragel, const struct stack *stack) +{ + assert(ragel && stack); + stack_check_type(ragel, stack, STACK_NUM); + return stack->num; +} + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +static void +fspec_type_from_str(const struct ragel *ragel, const char *str, struct fspec_type *out_type) +{ + assert(ragel && str); + + const struct fspec_type types[] = { + { .name = "u8", .size = sizeof(uint8_t) }, + { .name = "u16", .size = sizeof(uint16_t) }, + { .name = "u32", .size = sizeof(uint32_t) }, + { .name = "u64", .size = sizeof(uint64_t) }, + { .name = "s8", .size = sizeof(int8_t), .flags = FSPEC_TYPE_SIGNED }, + { .name = "s16", .size = sizeof(int16_t), .flags = FSPEC_TYPE_SIGNED }, + { .name = "s32", .size = sizeof(int32_t), .flags = FSPEC_TYPE_SIGNED }, + { .name = "s64", .size = sizeof(int64_t), .flags = FSPEC_TYPE_SIGNED }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(types); ++i) { + if (strcmp(str, types[i].name)) + continue; + + *out_type = types[i]; + return; + } + + if (ragel_search_str(ragel, 0, str)) { + *out_type = (struct fspec_type){ .name = str, .flags = FSPEC_TYPE_CONTAINER }; + return; + } + + ragel_throw_error(ragel, "invalid type"); +} + +static void +fspec_kind_from_str(const struct ragel *ragel, const char *str, struct fspec_kind *out_kind) +{ + assert(ragel && str); + + const struct fspec_kind kinds[] = { + { .name = "pad", .flags = FSPEC_KIND_IGNORE }, + { .name = "hex", .flags = FSPEC_KIND_HEXADECIMAL }, + { .name = "ascii", .flags = FSPEC_KIND_ENCODING }, + { .name = "utf8", .flags = FSPEC_KIND_ENCODING }, + { .name = "sjis", .flags = FSPEC_KIND_ENCODING }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(kinds); ++i) { + if (strcmp(str, kinds[i].name)) + continue; + + *out_kind = kinds[i]; + return; + } + + ragel_throw_error(ragel, "invalid kind"); +} + +static void +check_field_kind(const struct ragel *ragel, const struct fspec_field *field) +{ + assert(ragel && field); + + if ((field->kind.flags & FSPEC_KIND_ENCODING) && field->type.size != sizeof(uint8_t)) + ragel_throw_error(ragel, "invalid kind: %s kind only allowed for u8 and s8 types", field->kind.name); +} + +%%{ + # File specification parser. + + machine fspec; + variable p state.ragel.p; + variable pe state.ragel.pe; + variable eof state.ragel.eof; + write data noerror nofinal; + + action field { + fspec->ops.field(fspec, &state.container, &state.field); + } + + action field_kind { + fspec_kind_from_str(&state.ragel, stack_get_var(&state.ragel, &state.stack), &state.field.kind); + check_field_kind(&state.ragel, &state.field); + } + + action field_array { + switch (state.stack.type) { + case STACK_NUM: + state.field.array.type = FSPEC_ARRAY_FIXED; + state.field.array.nmemb = stack_get_num(&state.ragel, &state.stack); + break; + + case STACK_STR: + state.field.array.type = FSPEC_ARRAY_MATCH; + state.field.array.match = *stack_get_str(&state.ragel, &state.stack); + break; + + case STACK_VAR: + state.field.array.type = FSPEC_ARRAY_VAR; + state.field.array.var = stack_get_var(&state.ragel, &state.stack); + + if (!ragel_search_str(&state.ragel, state.container_data_offset, state.field.array.var)) + ragel_throw_error(&state.ragel, "undeclared variable '%s'", state.field.array.var); + break; + + default: + ragel_throw_error(&state.ragel, "array can't contain the stack type of '%s'", stack_type_to_str(state.stack.type)); + break; + } + } + + action field_name { + state.field.name = stack_get_var(&state.ragel, &state.stack); + } + + action field_type { + state.field = default_field; + fspec_type_from_str(&state.ragel, stack_get_var(&state.ragel, &state.stack), &state.field.type); + } + + action container_name { + state.container = default_container; + state.container.name = stack_get_var(&state.ragel, &state.stack); + state.container_data_offset = state.ragel.mem.cur - state.ragel.mem.data; + } + + action push_var { + state.stack.type = STACK_VAR; + state.stack.var = (char*)state.ragel.mem.cur; + } + + action push_hex { + state.stack.type = STACK_NUM; + state.stack.num = strtoll((char*)state.ragel.mem.cur, NULL, 16); + } + + action push_dec { + state.stack.type = STACK_NUM; + state.stack.num = strtoll((char*)state.ragel.mem.cur, NULL, 10); + } + + action push_str { + state.stack.type = STACK_STR; + state.stack.str.data = state.ragel.mem.cur; + state.stack.str.size = (state.ragel.mem.data + state.ragel.mem.written) - state.ragel.mem.cur; + } + + action convert_escape { + ragel_convert_escape(&state.ragel); + } + + action remove { + ragel_remove_last_data(&state.ragel); + } + + action finish { + ragel_finish_data(&state.ragel); + } + + action store { + ragel_store_data(&state.ragel); + } + + action begin { + ragel_begin_data(&state.ragel); + } + + action invalid_kind { + ragel_throw_error(&state.ragel, "invalid kind"); + } + + action invalid_type { + ragel_throw_error(&state.ragel, "invalid type"); + } + + action error { + ragel_throw_error(&state.ragel, "malformed input (machine failed here or in previous or next expression)"); + } + + action line { + ragel_advance_line(&state.ragel); + } + + # Semantic + ws = space; + valid = ^cntrl; + es = '\\'; + delim = ';'; + quote = ['"]; + bopen = '{'; + bclose = '}'; + newline = '\n'; + octal = [0-7]; + hex = '0x' <: xdigit+; + decimal = ([1-9] <: digit*) | '0'; + comment = '//' <: valid* :>> newline; + escape = es <: ('x' <: xdigit+ | [abfnrtv\\'"e] | octal{1,3}); + type = 'u8' | 'u16' | 'u32' | 'u64' | 's8' | 's16' | 's32' | 's64'; + kind = 'ascii' | 'utf8' | 'sjis' | 'hex' | 'pad'; + reserved = 'struct' | type | kind; + var = ((alpha | '_') <: (alnum | '_')*) - reserved; + + # Catchers + catch_var = var >begin $store %finish %push_var; + catch_struct = ('struct' $store ws+ >store <: var $store) >begin %finish %push_var; + catch_type = (catch_struct | type >begin $store %push_var %remove) $!invalid_type; + catch_hex = hex >begin $store %push_hex %remove; + catch_decimal = decimal >begin $store %push_dec %remove; + catch_string = quote <: (escape %convert_escape | print)* >begin $store %finish %push_str :>> quote; + catch_array = '[' <: (catch_hex | catch_decimal | catch_string | catch_var) :>> ']'; + catch_kind = '=' ws* <: kind >begin $store %push_var %remove $!invalid_kind; + + # Actions + field = catch_type %field_type ws+ <: catch_var %field_name ws* <: (catch_array %field_array ws*)? <: (catch_kind %field_kind ws*)? :>> delim %field; + container = catch_struct %container_name ws* :>> bopen <: (ws | comment | field)* :>> bclose ws* delim; + line = valid* :>> newline @line; + main := (ws | comment | container)* & line* $!error; +}%% + +void +fspec_parse(struct fspec *fspec) +{ + int cs; + %% write init; + + (void)fspec_en_main; + assert(fspec); + assert(fspec->ops.read); + assert(fspec->ops.field); + + struct state state = { + .ragel = { + .lineno = 1, + .mem = { + .data = fspec->mem.data, + .size = fspec->mem.size, + }, + }, + }; + + for (bool ok = true; ok;) { + const size_t bytes = fspec->ops.read(fspec, state.ragel.buf, 1, sizeof(state.ragel.buf)); + ok = ragel_confirm_input(&state.ragel, bytes); + %% write exec; + } +} diff --git a/src/ragel/ragel.h b/src/ragel/ragel.h new file mode 100644 index 0000000..af06f4a --- /dev/null +++ b/src/ragel/ragel.h @@ -0,0 +1,236 @@ +#pragma once + +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <limits.h> +#include <err.h> + +struct ragel { + struct { + uint8_t *data; // data\0another_data\0 + const uint8_t *cur; // data\0another_data\0cursor + size_t written, size; // amount of data written / size of data + } mem; + + char buf[4096]; // block of input data + const char *p, *pe, *eof; // see ragel doc + size_t lineno; // current line +}; + +static inline void +ragel_get_current_line(const struct ragel *ragel, size_t *out_lineno, size_t *out_ls, size_t *out_le, size_t *out_ws, size_t *out_we) +{ + assert(out_ls && out_le && out_ws && out_we); + assert(ragel->p >= ragel->buf && ragel->pe >= ragel->p); + + size_t ls, le, ws, we; + size_t off = ragel->p - ragel->buf; + size_t lineno = ragel->lineno; + const size_t end = ragel->pe - ragel->buf; + + // rewind to first non-space + for (; off > 0 && (isspace(ragel->buf[off]) || !ragel->buf[off]); --off) { + if (lineno > 0 && ragel->buf[off] == '\n') + --lineno; + } + + for (ls = off; ls > 0 && ragel->buf[ls] != '\n'; --ls); // beginning of line + for (le = off; le < end && ragel->buf[le] != '\n'; ++le); // end of line + for (; ls < le && isspace(ragel->buf[ls]); ++ls); // strip leading whitespace + for (ws = off; ws > ls && isspace(ragel->buf[ws]); --ws); // rewind to first non-space + for (; ws > 0 && ws > ls && !isspace(ragel->buf[ws - 1]); --ws); // find word start + for (we = ws; we < le && !isspace(ragel->buf[we]); ++we); // find word ending + + assert(we >= ws && ws >= ls && le >= ls && le >= we); + *out_lineno = lineno; + *out_ls = ls; + *out_le = le; + *out_ws = ws; + *out_we = we; +} + +__attribute__((format(printf, 2, 3))) +static inline void +ragel_throw_error(const struct ragel *ragel, const char *fmt, ...) +{ + assert(ragel && fmt); + + size_t lineno, ls, le, ws, we; + ragel_get_current_line(ragel, &lineno, &ls, &le, &ws, &we); + assert(le - ls <= INT_MAX && ws - ls <= INT_MAX); + + char msg[255]; + va_list args; + va_start(args, fmt); + vsnprintf(msg, sizeof(msg), fmt, args); + va_end(args); + + const int indent = 8; + const size_t mark = (we - ws ? we - ws : 1), cur = (ragel->p - ragel->buf) - ws; + warnx("\x1b[37m%zu: \x1b[31merror: \x1b[0m%s\n%*s%.*s", lineno, msg, indent, "", (int)(le - ls), ragel->buf + ls); + fprintf(stderr, "%*s%*s\x1b[31m", indent, "", (int)(ws - ls), ""); + for (size_t i = 0; i < mark; ++i) fputs((i == cur ? "^" : "~"), stderr); + fputs("\x1b[0m\n", stderr); + + exit(EXIT_FAILURE); +} + +static inline void +ragel_bounds_check_data(const struct ragel *ragel, const size_t nmemb) +{ + assert(ragel); + + if (ragel->mem.size < nmemb || ragel->mem.written >= ragel->mem.size - nmemb) + ragel_throw_error(ragel, "data storage limit exceeded: %zu bytes exceeds the maximum store size of %zu bytes", ragel->mem.written, ragel->mem.size); +} + +static inline void +ragel_replace_data(struct ragel *ragel, const size_t nmemb, char replacement) +{ + assert(ragel); + + if (ragel->mem.written < nmemb) + ragel_throw_error(ragel, "parse error: received escape conversion with mem.written of %zu, expected >= %zu", ragel->mem.written, nmemb); + + ragel->mem.data[(ragel->mem.written -= nmemb)] = replacement; + ragel->mem.data[++ragel->mem.written] = 0; +} + +static inline void +ragel_convert_escape(struct ragel *ragel) +{ + assert(ragel); + + if (ragel->mem.written < 2) + ragel_throw_error(ragel, "parse error: received escape conversion with mem.written of %zu, expected >= 2", ragel->mem.written); + + const struct { + const char *e; + const char v, b; + } map[] = { + { .e = "\\a", .v = '\a' }, + { .e = "\\b", .v = '\b' }, + { .e = "\\f", .v = '\f' }, + { .e = "\\n", .v = '\n' }, + { .e = "\\r", .v = '\r' }, + { .e = "\\t", .v = '\t' }, + { .e = "\\v", .v = '\v' }, + { .e = "\\\\", .v = '\\' }, + { .e = "\\'", .v = '\'' }, + { .e = "\\\"", .v = '"' }, + { .e = "\\e", .v = '\e' }, + { .e = "\\x", .b = 16 }, + { .e = "\\", .b = 8 }, + }; + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + const char *cur = (char*)ragel->mem.cur; + const size_t cur_sz = strlen(cur); + for (size_t i = 0; i < ARRAY_SIZE(map); ++i) { + if (!strncmp(cur, map[i].e, strlen(map[i].e))) { + const char v = (!map[i].b ? map[i].v : strtol(cur + strlen(map[i].e), NULL, map[i].b)); + assert((map[i].b == 8 && cur_sz >= 2) || (map[i].b == 16 && cur_sz >= 2) || (map[i].b == 0 && cur_sz == 2)); + assert(map[i].b != 8 || isdigit(cur[1])); + ragel_replace_data(ragel, cur_sz, v); + return; + } + } +#undef ARRAY_SIZE + + ragel_throw_error(ragel, "parse error: received unknown escape conversion"); +} + +static inline void +ragel_dump_data(struct ragel *ragel, const size_t offset) +{ + const uint8_t *end = ragel->mem.data + ragel->mem.written; + for (const uint8_t *p = ragel->mem.data + offset; p && p < end; p = (uint8_t*)memchr(p, 0, end - p), p += !!p) + printf("%s\n", p); +} + +static inline const uint8_t* +ragel_search_data(const struct ragel *ragel, const size_t offset, const uint8_t *data, const size_t size) +{ + assert(ragel && data); + + const uint8_t *end = ragel->mem.data + ragel->mem.written; + for (const uint8_t *p = ragel->mem.data + offset; p && p < end && (size_t)(end - p) >= size; p = (uint8_t*)memchr(p, 0, end - p), p += !!p) { + if (!memcmp(data, p, size)) + return p; + } + + return NULL; +} + +static inline const uint8_t* +ragel_search_str(const struct ragel *ragel, const size_t offset, const char *str) +{ + return ragel_search_data(ragel, offset, (const uint8_t*)str, strlen(str) + 1); +} + +static inline void +ragel_remove_last_data(struct ragel *ragel) +{ + assert(ragel); + const uint8_t *end = ragel->mem.data + ragel->mem.written; + const size_t size = end - ragel->mem.cur + 1; + assert(ragel->mem.written >= size); + ragel->mem.written -= size; + ragel->mem.data[ragel->mem.written] = 0; +} + +static inline void +ragel_finish_data(struct ragel *ragel) +{ + assert(ragel); + + const uint8_t *end = ragel->mem.data + ragel->mem.written, *p; + if ((p = ragel_search_data(ragel, 0, ragel->mem.cur, end - ragel->mem.cur + 1))) { + ragel_remove_last_data(ragel); + ragel->mem.cur = p; + } +} + +static inline void +ragel_store_data(struct ragel *ragel) +{ + ragel_bounds_check_data(ragel, 1); + ragel->mem.data[ragel->mem.written++] = *ragel->p; + ragel->mem.data[ragel->mem.written] = 0; +} + +static inline void +ragel_begin_data(struct ragel *ragel) +{ + ragel_bounds_check_data(ragel, 1); + ragel->mem.written += (ragel->mem.written > 0); + ragel->mem.cur = ragel->mem.data + ragel->mem.written; +} + +static inline void +ragel_advance_line(struct ragel *ragel) +{ + assert(ragel); + ++ragel->lineno; +} + +static inline bool +ragel_confirm_input(struct ragel *ragel, const size_t bytes) +{ + assert(ragel); + + if (bytes > sizeof(ragel->buf)) + errx(EXIT_FAILURE, "%s: gave larger buffer than %zu", __func__, sizeof(ragel->buf)); + + const bool in_eof = (bytes < sizeof(ragel->buf)); + ragel->p = ragel->buf; + ragel->pe = ragel->p + bytes; + ragel->eof = (in_eof ? ragel->pe : NULL); + return !in_eof; +} diff --git a/src/utils/dec2bin.c b/src/utils/dec2bin.c new file mode 100644 index 0000000..97e59bb --- /dev/null +++ b/src/utils/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\n", 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/xi/xi2path.c b/src/xi/xi2path.c new file mode 100644 index 0000000..bd9c702 --- /dev/null +++ b/src/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\n", argv[0]); + + char path[12]; + xi2path(path, strtol(argv[1], NULL, 10)); + printf("%s\n", path); + return EXIT_SUCCESS; +} diff --git a/src/xi/xi2path.h b/src/xi/xi2path.h new file mode 100644 index 0000000..954c554 --- /dev/null +++ b/src/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/xi/xidec.c b/src/xi/xidec.c new file mode 100644 index 0000000..3df917f --- /dev/null +++ b/src/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\n", 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/xi/xifile.c b/src/xi/xifile.c new file mode 100644 index 0000000..f1b2111 --- /dev/null +++ b/src/xi/xifile.c @@ -0,0 +1,169 @@ +#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\n", argv[0]); + + for (int i = 1; i < argc; ++i) + detect(argv[i]); + + return EXIT_SUCCESS; +} diff --git a/src/xi/xils.c b/src/xi/xils.c new file mode 100644 index 0000000..9c9a75e --- /dev/null +++ b/src/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\n", 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; +} diff --git a/vim/filespec.vim b/vim/filespec.vim new file mode 100644 index 0000000..5e43fc5 --- /dev/null +++ b/vim/filespec.vim @@ -0,0 +1,56 @@ +" Vim syntax file +" Language: Filespec +" Author: Jari Vetoniemi + +syntax clear + +syn match fsBadContinuation contained "\\\s\+$" +syn keyword fsTodo contained TODO FIXME XXX +syn cluster fsCommentGroup contains=fsTodo,fsBadContinuation +syn region fsComment start="//" skip="\\$" end="$" keepend contains=@fsCommentGroup,@Spell + +syn keyword fsStructure struct union +syn keyword fsType s8 s16 s32 s64 +syn keyword fsType u8 u16 u32 u64 +syn keyword fsKind ascii utf8 sjis pad hex + +syn case ignore +syn match fsNumbers display transparent "\<\d\|\.\d" contains=fsNumber,fsFloat,fsOctalError,fsOctal +syn match fsNumbersCom display contained transparent "\<\d\|\.\d" contains=fsNumber,fsFloat,fsOctal +syn match fsNumber display contained "\d\+\(u\=l\{0,2}\|ll\=u\)\>" +syn match fsNumber display contained "0x\x\+\(u\=l\{0,2}\|ll\=u\)\>" +syn match fsOctal display contained "0\o\+\(u\=l\{0,2}\|ll\=u\)\>" contains=fsOctalZero +syn match fsOctalZero display contained "\<0" +syn match fsFloat display contained "\d\+f" +syn match fsFloat display contained "\d\+\.\d*\(e[-+]\=\d\+\)\=[fl]\=" +syn match fsFloat display contained "\.\d\+\(e[-+]\=\d\+\)\=[fl]\=\>" +syn match fsFloat display contained "\d\+e[-+]\=\d\+[fl]\=\>" +syn match fsOctalError display contained "0\o*[89]\d*" +syn case match + +syn match fsSpecial display contained "\\\(x\x\+\|\o\{1,3}\|.\|$\)" +syn region fsString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=fsSpecial,@Spell extend +syn match fsCharacter "'[^']*'" contains=fsSpecial + +syn match fsBlock "[{}]" +syn match fsBracket "[\[\]]" +syn match fsOperator display "[-+&|<>=!*\/~.,;:%&^?()]" contains=fsComment,fsKind + +" Define the default highlighting. +" Only used when an item doesn't have highlighting yet +hi def link fsTodo Todo +hi def link fsComment Comment +hi def link fsStructure Structure +hi def link fsType Type +hi def link fsKind Constant +hi def link fsNumber Number +hi def link fsOctal Number +hi def link fsOctalZero PreProc +hi def link fsFloat Float +hi def link fsOctalError Error +hi def link fsString Constant +hi def link fsCharacter Character +hi def link fsSpecial SpecialChar +hi def link fsBlock Constant +hi def link fsBracket Constant +hi def link fsOperator Operator |