mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-09-29 18:38:04 +03:00
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:
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
exdump = ['../cJSON.c',
|
||||
'exdump.c',
|
||||
]
|
||||
|
||||
executable('exdump', exdump,
|
||||
dependencies : [libm, zlib],
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user