Add exdump utility

Add tool for dumping the contents of .ex files, which contain various
structured data used by the game (tables, lists, trees).

The dump format uses a C-like syntax which should be relatively easy to
parse back in.
This commit is contained in:
Nunuhara Cabbage
2020-02-17 19:51:25 -08:00
parent c9dcf6e104
commit 38048ea9e6
5 changed files with 904 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
*/
#ifndef SYSTEM4_EX_H
#define SYSTEM4_EX_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
enum ex_value_type {
EX_INT = 1,
EX_FLOAT = 2,
EX_STRING = 3,
EX_TABLE = 4,
EX_LIST = 5,
EX_TREE = 6
};
struct ex_value {
enum ex_value_type type;
union {
int32_t i;
float f;
struct string *s;
struct ex_table *t;
struct ex_list *list;
struct ex_tree *tree;
};
};
struct ex_field {
enum ex_value_type type;
struct string *name;
int32_t uk0;
int32_t uk1;
int32_t uk2;
uint32_t nr_subfields;
struct ex_field *subfields;
};
struct ex_table {
uint32_t nr_fields;
struct ex_field *fields;
uint32_t nr_columns;
uint32_t nr_rows;
struct ex_value **rows;
};
struct ex_list_item {
uint32_t size;
struct ex_value value;
};
struct ex_list {
uint32_t nr_items;
struct ex_list_item *items;
};
struct ex_leaf {
uint32_t size;
struct string *name;
struct ex_value value;
};
struct ex_tree {
struct string *name;
bool is_leaf;
union {
struct {
uint32_t nr_children;
struct ex_tree *children;
};
struct ex_leaf leaf;
};
};
struct ex_block {
size_t size;
struct string *name;
struct ex_value val;
};
struct ex {
uint32_t nr_blocks;
struct ex_block *blocks;
};
struct ex *ex_read(const char *path);
void ex_free(struct ex *ex);
#endif /* SYSTE4_EX_H */
+503
View File
@@ -0,0 +1,503 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <zlib.h>
#include "little_endian.h"
#include "system4.h"
#include "system4/ex.h"
#include "system4/string.h"
uint8_t ex_decode_table[256];
uint8_t ex_decode_table_inv[256];
struct buf_reader {
uint8_t *buf;
size_t size;
size_t index;
};
static void buf_init(struct buf_reader *r, uint8_t *buf, size_t size)
{
r->buf = buf;
r->size = size;
r->index = 0;
}
static int32_t buf_read_int32(struct buf_reader *r)
{
if (r->index + 4 > r->size)
ERROR("Out of bounds buffer read");
int32_t v = LittleEndian_getDW(r->buf, r->index);
r->index += 4;
return v;
}
static float buf_read_float(struct buf_reader *r)
{
union { int32_t i; float f; } v;
v.i = buf_read_int32(r);
return v.f;
}
static struct string *buf_read_pascal_string(struct buf_reader *r)
{
int32_t len = buf_read_int32(r);
if (len < 0)
ERROR("Invalid string length: %d", len);
struct string *s = make_string((char*)r->buf+r->index, len);
r->index += len;
return s;
}
static void buf_skip(struct buf_reader *r, size_t off)
{
r->index = min(r->index + off, r->size);
}
static size_t buf_remaining(struct buf_reader *r)
{
return r->size - r->index;
}
static char *buf_strdata(struct buf_reader *r)
{
return (char*)r->buf + r->index;
}
static struct string *ex_read_string(struct buf_reader *r)
{
struct string *s = buf_read_pascal_string(r);
// TODO: at most 3 bytes padding, no need for full strlen
s->size = strlen(s->text);
// TODO: validate?
return s;
}
static uint8_t *ex_decode(uint8_t *data, size_t *len, uint32_t *nr_blocks)
{
struct buf_reader r;
uint32_t compressed_size;
unsigned long uncompressed_size;
// initialize table
for (int i = 0; i < 256; i++) {
int tmpfor = i;
int tmp = tmpfor;
tmp = (tmp & 0x55) + ((tmp >> 1) & 0x55);
tmp = (tmp & 0x33) + ((tmp >> 2) & 0x33);
tmp = (tmp & 0x0F) + ((tmp >> 4) & 0x0F);
if ((tmp & 0x01) == 0) {
tmpfor = ((tmpfor << (8 - tmp)) | (tmpfor >> tmp)) & 0xFF;
} else {
tmpfor = ((tmpfor >> (8 - tmp)) | (tmpfor << tmp)) & 0xFF;
}
ex_decode_table[i] = tmpfor;
}
// initialize inverse table
for (int i = 0; i < 256; i++) {
ex_decode_table_inv[ex_decode_table[i]] = i;
}
buf_init(&r, data, *len);
if (strncmp(buf_strdata(&r), "HEAD", 4))
ERROR("Missing HEAD section marker");
buf_skip(&r, 8);
if (strncmp(buf_strdata(&r), "EXTF", 4))
ERROR("Missing EXTF section marker");
buf_skip(&r, 8);
*nr_blocks = buf_read_int32(&r);
if (strncmp(buf_strdata(&r), "DATA", 4)) {
ERROR("Missing DATA section marker");
}
buf_skip(&r, 4);
compressed_size = buf_read_int32(&r);
uncompressed_size = buf_read_int32(&r);
// decode compressed data
for (size_t i = 0; i < compressed_size; i++) {
data[r.index+i] = ex_decode_table[data[r.index+i]];
}
uint8_t *out = xmalloc(uncompressed_size);
int rv = uncompress(out, &uncompressed_size, (uint8_t*)buf_strdata(&r), compressed_size);
switch (rv) {
case Z_BUF_ERROR: ERROR("Uncompress failed: Z_BUF_ERROR");
case Z_MEM_ERROR: ERROR("Uncompress failed: Z_MEM_ERROR");
case Z_DATA_ERROR: ERROR("Uncompress failed: Z_DATA_ERROR");
case Z_OK: break;
default: ERROR("Uncompress failed: %d", rv);
}
// decompress
*len = uncompressed_size;
return out;
}
static void ex_read_fields(struct buf_reader *r, struct ex_table *table);
static void ex_read_table(struct buf_reader *r, struct ex_table *table, struct ex_field *fields, uint32_t nr_fields);
static void ex_read_list(struct buf_reader *r, struct ex_list *list);
static void _ex_read_value(struct buf_reader *r, struct ex_value *value, struct ex_field *fields, uint32_t nr_fields)
{
switch (value->type) {
case EX_INT:
value->i = buf_read_int32(r);
break;
case EX_FLOAT:
value->f = buf_read_float(r);
break;
case EX_STRING:
value->s = buf_read_pascal_string(r);
break;
case EX_TABLE:
value->t = xcalloc(1, sizeof(struct ex_table));
// XXX: if nr_fields is zero, we are NOT a sub-table and therefore need to read fields
if (!nr_fields) {
ex_read_fields(r, value->t);
ex_read_table(r, value->t, value->t->fields, value->t->nr_fields);
} else {
ex_read_table(r, value->t, fields, nr_fields);
}
break;
case EX_LIST:
value->list = xcalloc(1, sizeof(struct ex_list));
ex_read_list(r, value->list);
break;
default:
ERROR("Unhandled value type: %d", value->type);
}
}
static void ex_read_value(struct buf_reader *r, struct ex_value *value, struct ex_field *fields, uint32_t nr_fields)
{
value->type = buf_read_int32(r);
_ex_read_value(r, value, fields, nr_fields);
}
static void ex_read_field(struct buf_reader *r, struct ex_field *field)
{
field->type = buf_read_int32(r);
if (field->type < EX_INT || field->type > EX_TABLE)
ERROR("Unknown/invalid field type: %d", field->type);
field->name = ex_read_string(r);
field->uk0 = buf_read_int32(r);
field->uk1 = buf_read_int32(r);
if (field->uk0)
field->uk2 = buf_read_int32(r);
if (field->type == EX_TABLE) {
field->nr_subfields = buf_read_int32(r);
if (field->nr_subfields > 255)
ERROR("Too many subfields: %u", field->nr_subfields);
field->subfields = xcalloc(field->nr_subfields, sizeof(struct ex_field));
for (uint32_t i = 0; i < field->nr_subfields; i++) {
ex_read_field(r, &field->subfields[i]);
}
}
}
static void ex_read_fields(struct buf_reader *r, struct ex_table *table)
{
table->nr_fields = buf_read_int32(r);
table->fields = xcalloc(table->nr_fields, sizeof(struct ex_field));
for (uint32_t i = 0; i < table->nr_fields; i++) {
ex_read_field(r, &table->fields[i]);
}
}
enum table_layout {
TABLE_DEFAULT,
ROW_MAJOR,
COLUMN_MAJOR
};
static enum table_layout table_layout = TABLE_DEFAULT;
static bool read_table = false;
static void ex_read_table(struct buf_reader *r, struct ex_table *table, struct ex_field *fields, uint32_t nr_fields)
{
// NOTE: Starting in Evenicle, the rows/columns are reversed
if (table_layout == COLUMN_MAJOR) {
table->nr_rows = buf_read_int32(r);
table->nr_columns = buf_read_int32(r);
} else {
table->nr_columns = buf_read_int32(r);
table->nr_rows = buf_read_int32(r);
}
// try switching to column-major order if fields don't make sense
if (table_layout == TABLE_DEFAULT && table->nr_columns != nr_fields) {
if (read_table) {
ERROR("Can't switch to column-major order because a table was already read");
} else if (table->nr_rows == nr_fields) {
WARNING("Switching to column-major order");
uint32_t tmp = table->nr_columns;
table->nr_columns = table->nr_rows;
table->nr_rows = tmp;
table_layout = COLUMN_MAJOR;
} else {
ERROR("Number of fields doesn't match number of columns: %u, %u", table->nr_columns, nr_fields);
}
} else if (table->nr_columns != nr_fields) {
ERROR("Number of fields doesn't match number of columns: %u, %u", table->nr_columns, nr_fields);
}
read_table = true;
table->rows = xcalloc(table->nr_rows, sizeof(struct ex_value*));
for (uint32_t i = 0; i < table->nr_rows; i++) {
table->rows[i] = xcalloc(table->nr_columns, sizeof(struct ex_value));
for (uint32_t j = 0; j < table->nr_columns; j++) {
ex_read_value(r, &table->rows[i][j], fields[j].subfields, fields[j].nr_subfields);
if (table->rows[i][j].type != fields[j].type)
ERROR("Column type doesn't match field type: %d/%d", table->rows[i][j], fields[j].type);
}
}
}
static void ex_read_list(struct buf_reader *r, struct ex_list *list)
{
list->nr_items = buf_read_int32(r);
list->items = xcalloc(list->nr_items, sizeof(struct ex_list_item));
for (uint32_t i = 0; i < list->nr_items; i++) {
list->items[i].value.type = buf_read_int32(r);
list->items[i].size = buf_read_int32(r);
_ex_read_value(r, &list->items[i].value, NULL, 0);
// TODO: check size
}
}
static void ex_read_tree(struct buf_reader *r, struct ex_tree *tree)
{
tree->name = ex_read_string(r);
uint32_t is_leaf = buf_read_int32(r);
if (is_leaf > 1)
ERROR("tree->is_leaf is not a boolean: %u", is_leaf);
tree->is_leaf = is_leaf;
if (!tree->is_leaf) {
tree->nr_children = buf_read_int32(r);
tree->children = xcalloc(tree->nr_children, sizeof(struct ex_tree));
for (uint32_t i = 0; i < tree->nr_children; i++) {
ex_read_tree(r, &tree->children[i]);
}
} else {
tree->leaf.value.type = buf_read_int32(r);
tree->leaf.size = buf_read_int32(r);
tree->leaf.name = ex_read_string(r);
_ex_read_value(r, &tree->leaf.value, NULL, 0);
int32_t zero = buf_read_int32(r);
if (zero) {
ERROR("Expected 0 after leaf node: 0x%x at 0x%zx", zero, r->index);
}
// TODO: check size
}
}
static void ex_read_block(struct buf_reader *r, struct ex_block *block)
{
block->val.type = buf_read_int32(r);
if (block->val.type < EX_INT || block->val.type > EX_TREE)
ERROR("Unknown/invalid block type: %d", block->val.type);
block->size = buf_read_int32(r);
if (block->size > buf_remaining(r))
ERROR("Block size extends past end of file: %" SIZE_T_FMT "u", block->size);
block->name = ex_read_string(r);
switch (block->val.type) {
case EX_INT:
block->val.i = buf_read_int32(r);
break;
case EX_FLOAT:
block->val.f = buf_read_float(r);
break;
case EX_STRING:
block->val.s = buf_read_pascal_string(r);
break;
case EX_TABLE:
block->val.t = xcalloc(1, sizeof(struct ex_table));
ex_read_fields(r, block->val.t);
ex_read_table(r, block->val.t, block->val.t->fields, block->val.t->nr_fields);
break;
case EX_LIST:
block->val.list = xcalloc(1, sizeof(struct ex_list));
ex_read_list(r, block->val.list);
break;
case EX_TREE:
block->val.tree = xcalloc(1, sizeof(struct ex_tree));
ex_read_tree(r, block->val.tree);
break;
}
}
struct ex *ex_read(const char *path)
{
FILE *fp;
long len;
uint8_t *buf = NULL;
if (!(fp = fopen(path, "rb")))
ERROR("fopen failed: %s", strerror(errno));
fseek(fp, 0, SEEK_END);
len = ftell(fp);
fseek(fp, 0, SEEK_SET);
buf = xmalloc(len+1);
if (fread(buf, len, 1, fp) != 1)
ERROR("fread failed: %s", strerror(errno));
if (fclose(fp))
ERROR("fclose failed: %s", strerror(errno));
buf[len] = '\0';
size_t decoded_len;
uint32_t nr_blocks;
uint8_t *decoded = ex_decode(buf, &decoded_len, &nr_blocks);
FILE *out = fopen("rance3.dex", "wb");
fwrite(decoded, decoded_len, 1, out);
fclose(out);
struct buf_reader r;
buf_init(&r, decoded, decoded_len);
struct ex *ex = xmalloc(sizeof(struct ex));
ex->nr_blocks = nr_blocks;
ex->blocks = xcalloc(nr_blocks, sizeof(struct ex_block));
for (size_t i = 0; i < nr_blocks; i++) {
ex_read_block(&r, &ex->blocks[i]);
}
free(buf);
free(decoded);
return ex;
}
static void ex_free_table(struct ex_table *table);
static void ex_free_list(struct ex_list *list);
static void ex_free_tree(struct ex_tree *tree);
static void ex_free_value(struct ex_value *value)
{
switch (value->type) {
case EX_STRING:
free_string(value->s);
break;
case EX_TABLE:
ex_free_table(value->t);
break;
case EX_LIST:
ex_free_list(value->list);
break;
case EX_TREE:
ex_free_tree(value->tree);
free(value->tree);
break;
default:
break;
}
}
static void ex_free_values(struct ex_value *values, uint32_t n)
{
for (uint32_t i = 0; i < n; i++) {
ex_free_value(&values[i]);
}
free(values);
}
static void ex_free_fields(struct ex_field *fields, uint32_t n)
{
for (uint32_t i = 0; i < n; i++) {
free_string(fields[i].name);
ex_free_fields(fields[i].subfields, fields[i].nr_subfields);
}
free(fields);
}
static void ex_free_table(struct ex_table *table)
{
ex_free_fields(table->fields, table->nr_fields);
for (uint32_t i = 0; i < table->nr_rows; i++) {
ex_free_values(table->rows[i], table->nr_columns);
}
free(table->rows);
free(table);
}
static void ex_free_list(struct ex_list *list)
{
for (uint32_t i = 0; i < list->nr_items; i++) {
ex_free_value(&list->items[i].value);
}
free(list->items);
free(list);
}
static void ex_free_tree(struct ex_tree *tree)
{
free_string(tree->name);
if (tree->is_leaf) {
free_string(tree->leaf.name);
ex_free_value(&tree->leaf.value);
return;
}
for (uint32_t i = 0; i < tree->nr_children; i++) {
ex_free_tree(&tree->children[i]);
}
free(tree->children);
}
void ex_free(struct ex *ex)
{
for (uint32_t i = 0; i < ex->nr_blocks; i++) {
struct ex_block *block = &ex->blocks[i];
free_string(block->name);
switch (block->val.type) {
case EX_STRING:
free_string(block->val.s);
break;
case EX_TABLE:
ex_free_table(block->val.t);
break;
case EX_LIST:
ex_free_list(block->val.list);
break;
case EX_TREE:
ex_free_tree(block->val.tree);
free(block->val.tree);
break;
default:
break;
}
}
}
+286
View File
@@ -0,0 +1,286 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <zlib.h>
#include "little_endian.h"
#include "system4.h"
#include "system4/ex.h"
#include "system4/string.h"
#include "system4/utfsjis.h"
int indent_level = 0;
static void indent(FILE *out)
{
for (int i = 0; i < indent_level; i++) {
fputc('\t', out);
}
}
static void print_string(FILE *out, struct string *s)
{
char *u = sjis2utf(s->text, s->size);
fprintf(out, "%s", u);
free(u);
}
static const char *ex_strtype(enum ex_value_type type)
{
switch (type) {
case EX_INT: return "int";
case EX_FLOAT: return "float";
case EX_STRING: return "string";
case EX_TABLE: return "table";
case EX_LIST: return "list";
case EX_TREE: return "tree";
default: return "unknown_type";
}
}
static char *_escape_string(const char *str, const char *escape_chars, const char *replace_chars)
{
int escapes = 0;
char *u = sjis2utf(str, strlen(str));
// count number of required escapes
for (int i = 0; u[i]; i++) {
if (u[i] & 0x80)
continue;
for (int j = 0; escape_chars[j]; j++) {
if (u[i] == escape_chars[j]) {
escapes++;
break;
}
}
}
// add backslash escapes
if (escapes > 0) {
int dst = 0;
int len = strlen(u);
char *tmp = xmalloc(len + escapes + 1);
for (int i = 0; u[i]; i++) {
bool escaped = false;
for (int j = 0; escape_chars[j]; j++) {
if (u[i] == escape_chars[j]) {
tmp[dst++] = '\\';
tmp[dst++] = replace_chars[j];
escaped = true;
break;
}
}
if (!escaped)
tmp[dst++] = u[i];
}
tmp[len+escapes] = '\0';
free(u);
u = tmp;
}
return u;
}
char *escape_string(const char *str)
{
const char escape_chars[] = { '\\', '\"', '\n', 0 };
const char replace_chars[] = { '\\', '\"', 'n', 0 };
return _escape_string(str, escape_chars, replace_chars);
}
static void ex_dump_string(FILE *out, struct string *str)
{
char *u = escape_string(str->text);
fprintf(out, "\"%s\"", u);
free(u);
}
static void ex_dump_field(FILE *out, struct ex_field *field)
{
fprintf(out, "%s ", ex_strtype(field->type));
print_string(out, field->name);
if (field->uk0 || field->uk1) {
if (field->uk0)
fprintf(out, "[%d,%d,%d]", field->uk0, field->uk1, field->uk2);
else
fprintf(out, "[%d,%d]", field->uk0, field->uk1);
}
if (field->nr_subfields) {
fprintf(out, " { ");
for (uint32_t i = 0; i < field->nr_subfields; i++) {
ex_dump_field(out, &field->subfields[i]);
if (i+1 < field->nr_subfields)
fprintf(out, ", ");
}
fprintf(out, " }");
}
}
static void ex_dump_table(FILE *out, struct ex_table *table);
static void ex_dump_list(FILE *out, struct ex_list *list, bool in_line);
static void ex_dump_value(FILE *out, struct ex_value *val)
{
switch (val->type) {
case EX_INT: fprintf(out, "%d", val->i); break;
case EX_FLOAT: fprintf(out, "%f", val->f); break;
case EX_STRING: ex_dump_string(out, val->s); break;
case EX_TABLE: ex_dump_table(out, val->t); break;
case EX_LIST: ex_dump_list(out, val->list, true); break;
case EX_TREE: ERROR("TODO DUMP TREE"); break;
}
}
static void ex_dump_row(FILE *out, struct ex_value *row, uint32_t nr_columns)
{
fprintf(out, "{ ");
for (uint32_t i = 0; i < nr_columns; i++) {
ex_dump_value(out, &row[i]);
if (i+1 < nr_columns)
fprintf(out, ", ");
}
fprintf(out, " }");
}
static void ex_dump_table(FILE *out, struct ex_table *table)
{
bool toplevel = !!table->nr_fields;
fputc('{', out);
if (toplevel)
fputc('\n', out);
indent_level++;
if (table->nr_fields) {
indent(out);
fprintf(out, "{ ");
for (uint32_t i = 0; i < table->nr_fields; i++) {
ex_dump_field(out, &table->fields[i]);
if (i+1 < table->nr_fields)
fprintf(out, ", ");
}
fprintf(out, " }\n");
}
for (uint32_t i = 0; i < table->nr_rows; i++) {
if (toplevel)
indent(out);
else
fputc(' ', out);
ex_dump_row(out, table->rows[i], table->nr_columns);
if (i+1 < table->nr_rows)
fputc(',', out);
else if (!toplevel)
fputc(' ', out);
if (toplevel)
fputc('\n', out);
}
indent_level--;
indent(out);
fputc('}', out);
}
static void ex_dump_list(FILE *out, struct ex_list *list, bool in_line)
{
fputc('{', out);
fputc(in_line ? ' ' : '\n', out);
indent_level++;
for (uint32_t i = 0; i < list->nr_items; i++) {
if (!in_line)
indent(out);
ex_dump_value(out, &list->items[i].value);
if (i+1 < list->nr_items)
fputc(',', out);
fputc(in_line ? ' ' : '\n', out);
}
indent_level--;
fputc('}', out);
}
static void ex_dump_tree(FILE *out, struct ex_tree *tree, int level)
{
if (tree->is_leaf) {
if (tree->leaf.value.type == EX_TABLE)
fprintf(out, "(table) ");
else if (tree->leaf.value.type == EX_LIST)
fprintf(out, "(list) ");
else if (tree->leaf.value.type == EX_TREE)
fprintf(out, "(tree) "); // shouldn't happen?
ex_dump_value(out, &tree->leaf.value);
return;
}
fprintf(out, "{\n");
indent_level++;
for (uint32_t i = 0; i < tree->nr_children; i++) {
indent(out);
print_string(out, tree->children[i].name);
fprintf(out, " = ");
ex_dump_tree(out, &tree->children[i], level+1);
fprintf(out, ",\n");
}
indent_level--;
indent(out);
fprintf(out, "}");
}
void ex_dump(FILE *out, struct ex *ex)
{
indent_level = 0;
for (uint32_t i = 0; i < ex->nr_blocks; i++) {
struct ex_block *block = &ex->blocks[i];
// type name =
fprintf(out, "%s ", ex_strtype(block->val.type));
print_string(out, block->name);
fprintf(out, " = ");
// rvalue
switch (block->val.type) {
case EX_INT: fprintf(out, "%d", block->val.i); break;
case EX_FLOAT: fprintf(out, "%f", block->val.f); break;
case EX_STRING: ex_dump_string(out, block->val.s); break;
case EX_TABLE: ex_dump_table(out, block->val.t); break;
case EX_LIST: ex_dump_list(out, block->val.list, false); break;
case EX_TREE: ex_dump_tree(out, block->val.tree, 0); break;
}
fputc(';', out);
if (i+1 < ex->nr_blocks)
fprintf(out, "\n\n");
}
}
int main(int argc, char *argv[])
{
if (argc != 2)
ERROR("Wrong number of arguments.");
struct ex *ex = ex_read(argv[1]);
ex_dump(stdout, ex);
ex_free(ex);
return 0;
}
+8
View File
@@ -0,0 +1,8 @@
exdump = ['../cJSON.c',
'exdump.c',
]
executable('exdump', exdump,
dependencies : [libm, zlib],
include_directories : incdir,
link_with : libsys4)
+2
View File
@@ -2,6 +2,7 @@
system4 = ['ain.c',
'ald.c',
'cg.c',
'ex.c',
'instructions.c',
'qnt.c',
'string.c',
@@ -68,3 +69,4 @@ executable('xsystem4', xsystem4,
subdir('aindump')
subdir('ainedit')
subdir('exdump')