feat: initial ifsdump and xmldump

This commit is contained in:
decafcode
2023-10-25 19:22:20 -04:00
commit 263fb12bd9
34 changed files with 3314 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.cache/
.vscode/launch.json

8
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"recommendations": [
"llvm-vs-code-extensions.vscode-clangd",
"mesonbuild.mesonbuild",
"webfreak.debug",
"xaver.clang-format",
]
}

18
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"[c]": {
"editor.defaultFormatter": "xaver.clang-format"
},
"[cpp]": {
"editor.defaultFormatter": "xaver.clang-format"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[meson]": {
"editor.formatOnSave": false
},
"editor.tabSize": 2,
"editor.formatOnSave": true,
"files.insertFinalNewline": true,
"mesonbuild.buildFolder": "build",
}

356
573file/ifs.c Normal file
View File

@@ -0,0 +1,356 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "573file/ifs.h"
#include "573file/prop-binary-reader.h"
#include "573file/prop.h"
#include "util/fs.h"
#include "util/iobuf.h"
#include "util/log.h"
#include "util/macro.h"
#include "util/str.h"
enum ifs_stat_entry {
IFS_STAT_ENTRY_OFFSET,
IFS_STAT_ENTRY_NBYTES,
IFS_STAT_ENTRY_TIMESTAMP,
IFS_STAT_LENGTH_
};
struct ifs {
FILE *f;
uint32_t body_start;
struct prop *root;
};
static int ifs_dirent_match(const struct prop *dirent, const char *path);
int ifs_open(struct ifs **out, const char *path) {
struct ifs *ifs;
struct iobuf dest;
struct const_iobuf src;
uint8_t header_bytes[0x24];
uint32_t header_words[9];
void *pp_bytes;
size_t pp_nbytes;
size_t i;
int r;
assert(out != NULL);
assert(path != NULL);
*out = NULL;
pp_bytes = NULL;
ifs = calloc(1, sizeof(*ifs));
if (ifs == NULL) {
r = -ENOMEM;
goto end;
}
r = fs_open(&ifs->f, path, "rb");
if (r < 0) {
log_write("%s: Error opening file: %s (%i)", path, strerror(-r), r);
goto end;
}
dest.bytes = header_bytes;
dest.nbytes = sizeof(header_bytes);
dest.pos = 0;
r = fs_read(ifs->f, &dest);
if (r < 0) {
log_write("%s: Error reading header: %s (%i)", path, strerror(-r), r);
goto end;
}
src.bytes = header_bytes;
src.nbytes = sizeof(header_bytes);
src.pos = 0;
for (i = 0; i < lengthof(header_words); i++) {
r = iobuf_read_be32(&src, &header_words[i]);
assert(r >= 0);
}
ifs->body_start = header_words[4];
pp_nbytes = ifs->body_start - sizeof(header_bytes);
pp_bytes = malloc(pp_nbytes);
if (pp_bytes == NULL) {
r = -ENOMEM;
goto end;
}
dest.bytes = pp_bytes;
dest.nbytes = pp_nbytes;
dest.pos = 0;
r = fs_read(ifs->f, &dest);
if (r < 0) {
log_write("%s: Error reading TOC: %s (%i)", path, strerror(-r), r);
goto end;
}
r = prop_binary_parse(&ifs->root, pp_bytes, pp_nbytes);
if (r < 0) {
goto end;
}
if (!ifs_dirent_is_dir(ifs->root)) {
log_write("%s: Root dirent is not a directory", path);
r = -EBADMSG;
goto end;
}
*out = ifs;
ifs = NULL;
r = 0;
end:
free(pp_bytes);
ifs_close(ifs);
return r;
}
void ifs_close(struct ifs *ifs) {
if (ifs == NULL) {
return;
}
if (ifs->f != NULL) {
fclose(ifs->f);
}
prop_free(ifs->root);
free(ifs);
}
const struct prop *ifs_get_root(struct ifs *ifs) {
assert(ifs != NULL);
return ifs->root;
}
int ifs_read_file(struct ifs *ifs, const struct prop *dirent, void *bytes,
size_t *nbytes_out) {
struct iobuf dest;
struct const_iobuf stat_buf;
uint32_t stat[IFS_STAT_LENGTH_];
size_t offset;
size_t nbytes;
size_t i;
int r;
assert(ifs != NULL);
assert(dirent != NULL);
assert(nbytes_out != NULL);
if (prop_get_type(dirent) != PROP_3S32) {
log_write("%s: Expected dirent to be PROP_3S32", prop_get_name(dirent));
r = -EINVAL;
return r;
}
prop_borrow_value(dirent, &stat_buf);
for (i = 0; i < lengthof(stat); i++) {
r = iobuf_read_be32(&stat_buf, &stat[i]);
assert(r >= 0);
}
nbytes = stat[IFS_STAT_ENTRY_NBYTES];
if (bytes != NULL) {
if (*nbytes_out < nbytes) {
return -ENOSPC;
}
offset = ifs->body_start + stat[IFS_STAT_ENTRY_OFFSET];
r = fs_seek_to(ifs->f, offset);
if (r < 0) {
log_write("%s: IFS seek failed (offset=%#lx): %s (%i)",
prop_get_name(dirent), offset, strerror(-r), r);
return r;
}
dest.bytes = bytes;
dest.nbytes = nbytes;
dest.pos = 0;
r = fs_read(ifs->f, &dest);
if (r < 0) {
log_write("%s: IFS read failed (nbytes=%#lx): %s (%i)",
prop_get_name(dirent), nbytes, strerror(-r), r);
return r;
}
}
*nbytes_out = nbytes;
return 0;
}
int ifs_dirent_lookup(const struct prop *dirent, const char *path,
const struct prop **out) {
const struct prop *pos;
int r;
assert(dirent != NULL);
assert(path != NULL);
assert(out != NULL);
*out = NULL;
for (pos = ifs_dirent_get_first_child(dirent); pos != NULL;
pos = ifs_dirent_get_next_sibling(pos)) {
r = ifs_dirent_match(pos, path);
if (r < 0) {
return r;
}
if (r > 0) {
*out = pos;
return r;
}
}
return 0;
}
static int ifs_dirent_match(const struct prop *dirent, const char *path) {
char *str;
int r;
assert(dirent != NULL);
assert(path != NULL);
str = NULL;
r = ifs_dirent_get_name(dirent, &str);
if (r < 0) {
goto end;
}
r = str_eq(str, path) ? 1 : 0;
end:
free(str);
return r;
}
const struct prop *ifs_dirent_get_first_child(const struct prop *dirent) {
const struct prop *pos;
assert(dirent != NULL);
pos = prop_get_first_child_const(dirent);
if (pos != NULL && str_eq(prop_get_name(pos), "_info_")) {
pos = prop_get_next_sibling_const(pos);
}
return pos;
}
const struct prop *ifs_dirent_get_next_sibling(const struct prop *dirent) {
assert(dirent != NULL);
return prop_get_next_sibling_const(dirent);
}
int ifs_dirent_get_name(const struct prop *dirent, char **out) {
bool escape;
uint32_t i;
uint32_t j;
const char *raw;
char *str;
assert(dirent != NULL);
assert(out != NULL);
*out = NULL;
raw = prop_get_name(dirent);
escape = false;
j = 0;
for (i = 0; raw[i] != '\0'; i++) {
if (escape) {
escape = false;
j++;
} else if (raw[i] == '_') {
escape = true;
} else {
j++;
}
}
str = malloc(j + 1); /* Count terminating NUL */
if (str == NULL) {
return -ENOMEM;
}
escape = false;
j = 0;
for (i = 0; raw[i] != '\0'; i++) {
if (escape) {
if (raw[i] == 'E') {
str[j++] = '.';
} else {
/* Probably some other escapes I don't know about ... */
str[j++] = raw[i];
}
escape = false;
} else if (raw[i] == '_') {
escape = true;
} else {
str[j++] = raw[i];
}
}
str[j] = '\0';
*out = str;
return 0;
}
bool ifs_dirent_is_dir(const struct prop *dirent) {
enum prop_type type;
assert(dirent != NULL);
type = prop_get_type(dirent);
return type == PROP_VOID || type == PROP_S32;
}

20
573file/ifs.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include <stdbool.h>
#include "573file/prop.h"
struct ifs;
int ifs_open(struct ifs **ifs, const char *path);
void ifs_close(struct ifs *ifs);
const struct prop *ifs_get_root(struct ifs *ifs);
int ifs_read_file(struct ifs *ifs, const struct prop *dirent, void *bytes,
size_t *nbytes);
int ifs_dirent_lookup(const struct prop *dirent, const char *path,
const struct prop **out);
const struct prop *ifs_dirent_get_first_child(const struct prop *dirent);
const struct prop *ifs_dirent_get_next_sibling(const struct prop *dirent);
int ifs_dirent_get_name(const struct prop *dirent, char **out);
bool ifs_dirent_is_dir(const struct prop *dirent);

17
573file/meson.build Normal file
View File

@@ -0,0 +1,17 @@
_573file_lib = static_library(
'573file',
c_pch: '../precompiled.h',
include_directories: [inc],
sources: [
'ifs.c',
'ifs.h',
'prop-binary-reader.c',
'prop-binary-reader.h',
'prop-type.c',
'prop-type.h',
'prop-xml-writer.c',
'prop-xml-writer.h',
'prop.c',
'prop.h',
]
)

View File

@@ -0,0 +1,459 @@
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "573file/prop-binary-reader.h"
#include "573file/prop-type.h"
#include "573file/prop.h"
#include "util/iobuf.h"
#include "util/log.h"
#include "util/macro.h"
#define ALIGN32(x) (((x) + 3) & ~3)
#define INVALID_OFFSET ((size_t)-1)
struct prop_binary_parser {
struct const_iobuf head;
struct const_iobuf body;
struct const_iobuf align_cave[2];
};
static const char prop_binary_name_chars[] =
"0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
static int prop_binary_read_node(struct prop_binary_parser *bp, uint8_t type,
struct prop **p);
int prop_binary_header_read_name(struct prop_binary_parser *bp, char **out);
static int prop_binary_read_attr(struct prop_binary_parser *bp, struct prop *p);
static int prop_binary_slice_value(struct prop_binary_parser *bp, uint8_t type,
struct const_iobuf *out);
static int prop_binary_body_slice_bytes(struct prop_binary_parser *bp,
size_t nbytes, struct const_iobuf *out);
static int prop_binary_body_slice_cave(struct prop_binary_parser *bp,
size_t nbytes, struct const_iobuf *out);
int prop_binary_parse(struct prop **out, const void *bytes, size_t nbytes) {
struct prop *p;
struct prop_binary_parser bp;
struct const_iobuf file;
uint32_t head_nbytes;
uint32_t body_nbytes;
uint8_t type;
int r;
assert(out != NULL);
assert(bytes != NULL);
*out = NULL;
p = NULL;
memset(&bp, 0, sizeof(bp));
file.bytes = bytes;
file.nbytes = nbytes;
file.pos = 4; /* Skip initial magic # */
r = iobuf_read_be32(&file, &head_nbytes);
if (r < 0) {
log_error(r);
goto end;
}
r = iobuf_slice(&bp.head, &file, head_nbytes);
if (r < 0) {
log_error(r);
goto end;
}
r = iobuf_align_read(&file, 4);
if (r < 0) {
log_error(r);
goto end;
}
r = iobuf_read_be32(&file, &body_nbytes);
if (r < 0) {
log_error(r);
goto end;
}
r = iobuf_slice(&bp.body, &file, body_nbytes);
if (r < 0) {
log_error(r);
goto end;
}
r = iobuf_read_8(&bp.head, &type);
if (r < 0) {
log_write("Failed to read root node type code");
goto end;
}
if (type == 0xFF) {
log_write("Binary prop has no root node");
r = -EBADMSG;
goto end;
}
r = prop_binary_read_node(&bp, type, &p);
if (r < 0) {
goto end;
}
r = iobuf_read_8(&bp.head, &type);
if (r < 0) {
goto end;
}
if (type != 0xFF) {
log_write("Expected EOF marker in header, got %#x", type);
r = -EBADMSG;
goto end;
}
*out = p;
p = NULL;
r = 0;
end:
prop_free(p);
return r;
}
static int prop_binary_read_node(struct prop_binary_parser *bp, uint8_t type,
struct prop **out) {
struct const_iobuf value;
struct prop *child;
struct prop *p;
uint8_t child_type;
char *name;
int r;
assert(bp != NULL);
assert(out != NULL);
*out = NULL;
name = NULL;
p = NULL;
r = prop_binary_header_read_name(bp, &name);
if (r < 0) {
log_write("Failed to read name");
goto end;
}
if (!prop_type_is_valid(type)) {
log_write("\"%s\": Unsupported type code %#x", name, type);
r = -ENOTSUP;
goto end;
}
r = prop_binary_slice_value(bp, type, &value);
if (r < 0) {
log_write("\"%s\": Failed to read value of type %s", name,
prop_type_to_string(type));
goto end;
}
r = prop_alloc(&p, name, type, value.bytes, value.nbytes);
if (r < 0) {
goto end;
}
for (;;) {
r = iobuf_read_8(&bp->head, &child_type);
if (r < 0) {
log_write("\"%s\": Failed to read next child's type code", name);
goto end;
}
if (child_type == 0xFE) {
break;
} else if (child_type == PROP_ATTR) {
r = prop_binary_read_attr(bp, p);
if (r < 0) {
goto end;
}
} else {
r = prop_binary_read_node(bp, child_type, &child);
if (r < 0) {
goto end;
}
prop_append(p, child);
}
}
*out = p;
p = NULL;
end:
free(name);
prop_free(p);
return r;
}
int prop_binary_header_read_name(struct prop_binary_parser *bp, char **out) {
uint8_t nchars;
uint8_t x;
uint8_t y;
uint8_t z;
char *name;
int i;
int index;
int r;
*out = NULL;
name = NULL;
r = iobuf_read_8(&bp->head, &nchars);
if (r < 0) {
log_error(r);
goto end;
}
name = malloc(nchars + 1);
if (name == NULL) {
r = -ENOMEM;
goto end;
}
x = 0;
y = 0;
z = 0;
for (i = 0; i < nchars; i++) {
switch (i % 4) {
case 0:
r = iobuf_read_8(&bp->head, &x);
if (r < 0) {
log_error(r);
goto end;
}
index = (x >> 2) & 0x3F;
break;
case 1:
r = iobuf_read_8(&bp->head, &y);
if (r < 0) {
log_error(r);
goto end;
}
index = ((x & 0x03) << 4) | ((y >> 4) & 0x0F);
break;
case 2:
r = iobuf_read_8(&bp->head, &z);
if (r < 0) {
log_error(r);
goto end;
}
index = ((y & 0x0F) << 2) | ((z >> 6) & 0x03);
break;
case 3:
index = z & 0x3F;
break;
}
assert(index < lengthof(prop_binary_name_chars));
name[i] = prop_binary_name_chars[index];
}
name[nchars] = '\0';
*out = name;
name = NULL;
r = 0;
end:
free(name);
return r;
}
static int prop_binary_read_attr(struct prop_binary_parser *bp,
struct prop *p) {
struct const_iobuf value;
char *name;
int r;
name = NULL;
r = prop_binary_header_read_name(bp, &name);
if (r < 0) {
goto end;
}
r = prop_binary_slice_value(bp, PROP_ATTR, &value);
if (r < 0) {
goto end;
}
if (value.nbytes == 0) {
log_write("Attr @%s has zero length", name);
r = -EBADMSG;
goto end;
}
if (value.bytes[value.nbytes - 1] != '\0') {
log_write("Attr @%s is not NUL terminated", name);
r = -EBADMSG;
goto end;
}
r = prop_set_attr(p, name, (const char *)value.bytes);
if (r < 0) {
goto end;
}
r = 0;
end:
free(name);
return r;
}
static int prop_binary_slice_value(struct prop_binary_parser *bp, uint8_t type,
struct const_iobuf *out) {
int orig_nbytes;
uint32_t nbytes;
bool is_variable;
int r;
assert(bp != NULL);
assert(out != NULL);
memset(out, 0, sizeof(*out));
r = iobuf_align_read(&bp->body, 4);
if (r < 0) {
log_error(r);
return r;
}
orig_nbytes = prop_type_to_size(type);
if (orig_nbytes < 0) {
is_variable = true;
r = iobuf_read_be32(&bp->body, &nbytes);
if (r < 0) {
log_error(r);
return r;
}
} else {
is_variable = false;
nbytes = orig_nbytes;
}
if (is_variable || nbytes >= 4) {
return prop_binary_body_slice_bytes(bp, nbytes, out);
} else if (nbytes > 0) {
return prop_binary_body_slice_cave(bp, nbytes, out);
}
return 0;
}
static int prop_binary_body_slice_bytes(struct prop_binary_parser *bp,
size_t nbytes,
struct const_iobuf *out) {
int r;
r = iobuf_slice(out, &bp->body, nbytes);
if (r < 0) {
log_error(r);
return r;
}
return 0;
}
static int prop_binary_body_slice_cave(struct prop_binary_parser *bp,
size_t nbytes, struct const_iobuf *out) {
struct const_iobuf *cave;
int r;
assert(bp != NULL);
assert(nbytes == 1 || nbytes == 2);
assert(out != NULL);
cave = &bp->align_cave[nbytes - 1];
if (cave->pos >= cave->nbytes) {
r = iobuf_slice(cave, &bp->body, 4);
if (r < 0) {
log_error(r);
return r;
}
}
r = iobuf_slice(out, cave, nbytes);
assert(r >= 0);
return 0;
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include <stddef.h>
#include "573file/prop.h"
int prop_binary_parse(struct prop **p, const void *bytes, size_t nbytes);

43
573file/prop-type.c Normal file
View File

@@ -0,0 +1,43 @@
#include <assert.h>
#include <stdbool.h>
#include "573file/prop-type.h"
#include "util/macro.h"
static const char *const prop_type_names[64] = {
[PROP_VOID] = "void", [PROP_S8] = "s8", [PROP_U8] = "u8",
[PROP_S16] = "s16", [PROP_U16] = "u16", [PROP_S32] = "s32",
[PROP_U32] = "u32", [PROP_S64] = "s64", [PROP_U64] = "u64",
[PROP_BIN] = "bin", [PROP_STR] = "str", [PROP_IP4] = "ip4",
[PROP_TIME] = "time", [PROP_2U16] = "2u16", [PROP_3S32] = "3s32",
[PROP_4U16] = "4u16", [PROP_BOOL] = "bool",
};
static const int prop_type_sizes[64] = {
[PROP_VOID] = 0, [PROP_S8] = 1, [PROP_U8] = 1, [PROP_S16] = 2,
[PROP_U16] = 2, [PROP_S32] = 4, [PROP_U32] = 4, [PROP_S64] = 8,
[PROP_U64] = 8, [PROP_BIN] = -1, [PROP_STR] = -1, [PROP_IP4] = 4,
[PROP_TIME] = 4, [PROP_2U16] = 4, [PROP_3S32] = 12, [PROP_4U16] = 8,
[PROP_ATTR] = -1, [PROP_BOOL] = 1,
};
bool prop_type_is_valid(enum prop_type type) {
if (type < 0 || type >= lengthof(prop_type_names)) {
return false;
}
return prop_type_names[type] != NULL;
}
const char *prop_type_to_string(enum prop_type type) {
assert(prop_type_is_valid(type));
return prop_type_names[type];
}
int prop_type_to_size(enum prop_type type) {
assert(type == PROP_ATTR /* hack */ || prop_type_is_valid(type));
return prop_type_sizes[type];
}

28
573file/prop-type.h Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
enum prop_type {
PROP_INVALID_TYPE = 0x00,
PROP_VOID = 0x01,
PROP_S8 = 0x02,
PROP_U8 = 0x03,
PROP_S16 = 0x04,
PROP_U16 = 0x05,
PROP_S32 = 0x06,
PROP_U32 = 0x07,
PROP_S64 = 0x08,
PROP_U64 = 0x09,
PROP_BIN = 0x0A,
PROP_STR = 0x0B,
PROP_IP4 = 0x0C,
PROP_TIME = 0x0D,
PROP_2U16 = 0x13,
PROP_3S32 = 0x1E,
PROP_4U16 = 0x27,
PROP_ATTR = 0x2E, /* Pseudo-type */
PROP_BOOL = 0x34,
PROP_ARRAY_FLAG = 0x40
};
bool prop_type_is_valid(enum prop_type type);
const char *prop_type_to_string(enum prop_type type);
int prop_type_to_size(enum prop_type type);

617
573file/prop-xml-writer.c Normal file
View File

@@ -0,0 +1,617 @@
#include <assert.h>
#include "573file/prop-xml-writer.h"
#include "573file/prop.h"
#include "util/iobuf.h"
#include "util/log.h"
#include "util/str.h"
enum prop_xml_escape {
PROP_XML_ESCAPE_ATTR,
PROP_XML_ESCAPE_TEXT,
};
static void prop_xml_write_attr_list(struct strbuf *dest, const struct prop *p);
static void prop_xml_write_children(struct strbuf *dest, const struct prop *p,
unsigned int indent);
static void prop_xml_write_escaped_string(struct strbuf *dest, const char *str,
enum prop_xml_escape ctx);
static void prop_xml_write_indent(struct strbuf *dest, unsigned int indent);
static void prop_xml_write_node(struct strbuf *dest, const struct prop *p,
unsigned int indent);
static void prop_xml_write_nonvoid(struct strbuf *dest, const struct prop *p,
unsigned int indent);
static void prop_xml_write_void(struct strbuf *dest, const struct prop *p,
unsigned int indent);
static void prop_xml_write_text(struct strbuf *dest, const struct prop *p,
enum prop_xml_escape ctx);
static void prop_xml_write_text_s8(struct strbuf *dest,
struct const_iobuf *src);
static void prop_xml_write_text_s16(struct strbuf *dest,
struct const_iobuf *src);
static void prop_xml_write_text_s32(struct strbuf *dest,
struct const_iobuf *src);
static void prop_xml_write_text_s64(struct strbuf *dest,
struct const_iobuf *src);
static void prop_xml_write_text_u8(struct strbuf *dest,
struct const_iobuf *src);
static void prop_xml_write_text_u16(struct strbuf *dest,
struct const_iobuf *src);
static void prop_xml_write_text_u32(struct strbuf *dest,
struct const_iobuf *src);
static void prop_xml_write_text_u64(struct strbuf *dest,
struct const_iobuf *src);
static void prop_xml_write_text_bin(struct strbuf *dest,
struct const_iobuf *src);
static void prop_xml_write_text_str(struct strbuf *dest,
struct const_iobuf *src,
enum prop_xml_escape ctx);
static void prop_xml_write_text_ip4(struct strbuf *dest,
struct const_iobuf *src);
static void prop_xml_write_text_bool(struct strbuf *dest,
struct const_iobuf *src);
static void prop_xml_write_text_s32_tuple(struct strbuf *dest,
struct const_iobuf *src,
size_t count);
static void prop_xml_write_text_u16_tuple(struct strbuf *dest,
struct const_iobuf *src,
size_t count);
int prop_xml_write(const struct prop *p, char **out) {
struct strbuf buf;
char *chars;
assert(p != NULL);
assert(out != NULL);
*out = NULL;
buf.chars = NULL;
buf.nchars = 0;
buf.pos = 0;
prop_xml_write_node(&buf, p, 0);
chars = malloc(buf.pos + 1);
if (chars == NULL) {
return -ENOMEM;
}
buf.chars = chars;
buf.nchars = buf.pos + 1;
buf.pos = 0;
prop_xml_write_node(&buf, p, 0);
assert(buf.pos + 1 == buf.nchars);
*out = chars;
return 0;
}
static void prop_xml_write_node(struct strbuf *dest, const struct prop *p,
unsigned int indent) {
enum prop_type type;
assert(dest != NULL);
assert(p != NULL);
type = prop_get_type(p);
if (type == PROP_VOID) {
prop_xml_write_void(dest, p, indent);
} else {
prop_xml_write_nonvoid(dest, p, indent);
}
}
static void prop_xml_write_void(struct strbuf *dest, const struct prop *p,
unsigned int indent) {
const struct prop *first_child;
const char *name;
assert(dest != NULL);
assert(p != NULL);
name = prop_get_name(p);
prop_xml_write_indent(dest, indent);
strbuf_printf(dest, "<%s", name);
prop_xml_write_attr_list(dest, p);
first_child = prop_get_first_child_const(p);
if (first_child != NULL) {
strbuf_puts(dest, ">\n");
prop_xml_write_children(dest, p, indent + 1);
prop_xml_write_indent(dest, indent);
strbuf_printf(dest, "</%s>\n", name);
} else {
strbuf_puts(dest, "/>\n");
}
}
/* AVS doesn't understand this __value attribute here, this is my own
invention, but generally we don't see any property pages with mixed content
nodes being stored in XML format to begin with. */
static void prop_xml_write_nonvoid(struct strbuf *dest, const struct prop *p,
unsigned int indent) {
const struct prop *first_child;
const char *name;
const char *type_str;
enum prop_type type;
assert(dest != NULL);
assert(p != NULL);
name = prop_get_name(p);
type = prop_get_type(p);
type_str = prop_type_to_string(type);
prop_xml_write_indent(dest, indent);
strbuf_printf(dest, "<%s __type=\"%s\"", name, type_str);
first_child = prop_get_first_child_const(p);
if (first_child != NULL) {
strbuf_puts(dest, " __value=\"");
prop_xml_write_text(dest, p, PROP_XML_ESCAPE_ATTR);
strbuf_putc(dest, '"');
prop_xml_write_attr_list(dest, p);
strbuf_puts(dest, ">\n");
prop_xml_write_children(dest, p, indent + 1);
prop_xml_write_indent(dest, indent);
strbuf_printf(dest, "</%s>\n", name);
} else {
prop_xml_write_attr_list(dest, p);
strbuf_putc(dest, '>');
prop_xml_write_text(dest, p, PROP_XML_ESCAPE_TEXT);
strbuf_printf(dest, "</%s>\n", name);
}
}
static void prop_xml_write_children(struct strbuf *dest, const struct prop *p,
unsigned int indent) {
const struct prop *child;
assert(dest != NULL);
assert(p != NULL);
for (child = prop_get_first_child_const(p); child != NULL;
child = prop_get_next_sibling_const(child)) {
prop_xml_write_node(dest, child, indent);
}
}
static void prop_xml_write_attr_list(struct strbuf *dest,
const struct prop *p) {
const struct attr *a;
const char *attr_key;
const char *attr_val;
assert(dest != NULL);
assert(p != NULL);
for (a = prop_get_first_attr(p); a != NULL; a = attr_get_next_sibling(a)) {
attr_key = attr_get_key(a);
attr_val = attr_get_val(a);
strbuf_printf(dest, " %s=\"", attr_key);
prop_xml_write_escaped_string(dest, attr_val, PROP_XML_ESCAPE_ATTR);
strbuf_putc(dest, '"');
}
}
static void prop_xml_write_escaped_string(struct strbuf *dest, const char *str,
enum prop_xml_escape ctx) {
const char *pos;
char c;
assert(dest != NULL);
assert(str != NULL);
for (pos = str; *pos != '\0'; pos++) {
c = *pos;
switch (c) {
case '<':
strbuf_puts(dest, "&lt;");
break;
case '>':
strbuf_puts(dest, "&gt;");
break;
case '&':
strbuf_puts(dest, "&amp;");
break;
case '\'':
if (ctx == PROP_XML_ESCAPE_ATTR) {
strbuf_puts(dest, "&apos;");
} else {
strbuf_putc(dest, c);
}
break;
case '"':
if (ctx == PROP_XML_ESCAPE_ATTR) {
strbuf_puts(dest, "&quot;");
} else {
strbuf_putc(dest, c);
}
break;
default:
strbuf_putc(dest, c);
break;
}
}
}
static void prop_xml_write_indent(struct strbuf *dest, unsigned int indent) {
unsigned int i;
assert(dest != NULL);
for (i = 0; i < indent; i++) {
strbuf_puts(dest, " ");
}
}
static void prop_xml_write_text(struct strbuf *dest, const struct prop *p,
enum prop_xml_escape ctx) {
struct const_iobuf src;
enum prop_type type;
type = prop_get_type(p);
prop_borrow_value(p, &src);
switch (type) {
case PROP_S8:
prop_xml_write_text_s8(dest, &src);
break;
case PROP_S16:
prop_xml_write_text_s16(dest, &src);
break;
case PROP_S32:
prop_xml_write_text_s32(dest, &src);
break;
case PROP_S64:
prop_xml_write_text_s64(dest, &src);
break;
case PROP_U8:
prop_xml_write_text_u8(dest, &src);
break;
case PROP_U16:
prop_xml_write_text_u16(dest, &src);
break;
case PROP_U32:
prop_xml_write_text_u32(dest, &src);
break;
case PROP_U64:
prop_xml_write_text_u64(dest, &src);
break;
case PROP_BIN:
prop_xml_write_text_bin(dest, &src);
break;
case PROP_STR:
prop_xml_write_text_str(dest, &src, ctx);
break;
case PROP_IP4:
prop_xml_write_text_ip4(dest, &src);
break;
case PROP_TIME:
/* Reuse the u32 writer for timestamps */
prop_xml_write_text_u32(dest, &src);
break;
case PROP_2U16:
prop_xml_write_text_u16_tuple(dest, &src, 2);
break;
case PROP_3S32:
prop_xml_write_text_s32_tuple(dest, &src, 3);
break;
case PROP_4U16:
prop_xml_write_text_u16_tuple(dest, &src, 4);
break;
case PROP_BOOL:
prop_xml_write_text_bool(dest, &src);
break;
default:
log_write("Unsupported type: %#x", type);
abort();
}
}
static void prop_xml_write_text_s8(struct strbuf *dest,
struct const_iobuf *src) {
uint8_t value;
int r;
assert(dest != NULL);
assert(src != NULL);
r = iobuf_read_8(src, &value);
assert(r >= 0);
strbuf_printf(dest, "%i", (int8_t)value);
}
static void prop_xml_write_text_s16(struct strbuf *dest,
struct const_iobuf *src) {
uint16_t value;
int r;
assert(dest != NULL);
assert(src != NULL);
r = iobuf_read_be16(src, &value);
assert(r >= 0);
strbuf_printf(dest, "%i", (int16_t)value);
}
static void prop_xml_write_text_s32(struct strbuf *dest,
struct const_iobuf *src) {
uint32_t value;
int r;
assert(dest != NULL);
assert(src != NULL);
r = iobuf_read_be32(src, &value);
assert(r >= 0);
strbuf_printf(dest, "%i", (int32_t)value);
}
static void prop_xml_write_text_s64(struct strbuf *dest,
struct const_iobuf *src) {
uint64_t value;
int r;
assert(dest != NULL);
assert(src != NULL);
r = iobuf_read_be64(src, &value);
assert(r >= 0);
strbuf_printf(dest, "%li", (int64_t)value);
}
static void prop_xml_write_text_u8(struct strbuf *dest,
struct const_iobuf *src) {
uint8_t value;
int r;
assert(dest != NULL);
assert(src != NULL);
r = iobuf_read_8(src, &value);
assert(r >= 0);
strbuf_printf(dest, "%i", value);
}
static void prop_xml_write_text_u16(struct strbuf *dest,
struct const_iobuf *src) {
uint16_t value;
int r;
assert(dest != NULL);
assert(src != NULL);
r = iobuf_read_be16(src, &value);
assert(r >= 0);
strbuf_printf(dest, "%u", value);
}
static void prop_xml_write_text_u32(struct strbuf *dest,
struct const_iobuf *src) {
uint32_t value;
int r;
assert(dest != NULL);
assert(src != NULL);
r = iobuf_read_be32(src, &value);
assert(r >= 0);
strbuf_printf(dest, "%u", value);
}
static void prop_xml_write_text_u64(struct strbuf *dest,
struct const_iobuf *src) {
uint64_t value;
int r;
assert(dest != NULL);
assert(src != NULL);
r = iobuf_read_be64(src, &value);
assert(r >= 0);
strbuf_printf(dest, "%lu", value);
}
static void prop_xml_write_text_bin(struct strbuf *dest,
struct const_iobuf *src) {
uint8_t byte;
uint8_t nibble;
uint8_t nibbles[2];
size_t i;
size_t j;
char c;
assert(dest != NULL);
assert(src != NULL);
for (i = src->pos; i < src->nbytes; i++) {
byte = src->bytes[i];
nibbles[0] = byte >> 4;
nibbles[1] = byte & 0x0f;
for (j = 0; j < 2; j++) {
nibble = nibbles[j];
if (nibble < 10) {
c = '0' + nibble;
} else {
c = 'a' + (nibble - 10);
}
strbuf_putc(dest, c);
}
}
src->pos = i;
}
static void prop_xml_write_text_str(struct strbuf *dest,
struct const_iobuf *src,
enum prop_xml_escape ctx) {
const char *chars;
size_t nchars;
assert(dest != NULL);
assert(src != NULL);
assert(src->pos <= src->nbytes);
chars = (const char *)&src->bytes[src->pos];
nchars = src->nbytes - src->pos;
assert(chars[nchars - 1] == '\0');
prop_xml_write_escaped_string(dest, chars, ctx);
src->pos += nchars;
}
static void prop_xml_write_text_ip4(struct strbuf *dest,
struct const_iobuf *src) {
const uint8_t *b;
assert(dest != NULL);
assert(src != NULL);
assert(src->pos + 4 <= src->nbytes);
b = src->bytes + src->pos;
strbuf_printf(dest, "%i.%i.%i.%i", b[0], b[1], b[2], b[3]);
}
static void prop_xml_write_text_bool(struct strbuf *dest,
struct const_iobuf *src) {
assert(dest != NULL);
assert(src != NULL);
assert(src->pos < src->nbytes);
if (src->bytes[src->pos]) {
strbuf_putc(dest, '1');
} else {
strbuf_putc(dest, '0');
}
}
/* I don't know how tuples are actually serialized to XML, the real format
might be different. */
static void prop_xml_write_text_s32_tuple(struct strbuf *dest,
struct const_iobuf *src,
size_t count) {
uint32_t value;
size_t i;
int r;
assert(dest != NULL);
assert(src != NULL);
for (i = 0; i < count; i++) {
if (i > 0) {
strbuf_putc(dest, ',');
}
r = iobuf_read_be32(src, &value);
assert(r >= 0);
strbuf_printf(dest, "%i", (int32_t)value);
}
}
static void prop_xml_write_text_u16_tuple(struct strbuf *dest,
struct const_iobuf *src,
size_t count) {
uint16_t value;
size_t i;
int r;
assert(dest != NULL);
assert(src != NULL);
for (i = 0; i < count; i++) {
if (i > 0) {
strbuf_putc(dest, ',');
}
r = iobuf_read_be16(src, &value);
assert(r >= 0);
strbuf_printf(dest, "%u", value);
}
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "573file/prop.h"
int prop_xml_write(const struct prop *p, char **out);

385
573file/prop.c Normal file
View File

@@ -0,0 +1,385 @@
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "573file/prop-type.h"
#include "573file/prop.h"
#include "util/list.h"
#include "util/log.h"
#include "util/macro.h"
#include "util/str.h"
struct attr {
struct list_node node;
char *key;
char *val;
};
struct prop {
struct list_node node;
struct prop *parent;
struct list attrs;
struct list children;
char *name;
uint32_t nbytes;
enum prop_type type;
uint8_t bytes[];
};
static int attr_set(struct attr *a, const char *val);
static int prop_validate(const char *name, enum prop_type type,
const void *bytes, uint32_t nbytes);
static int prop_validate(const char *name, enum prop_type type,
const void *bytes, uint32_t nbytes) {
const char *chars;
int expected_nbytes;
if (type & PROP_ARRAY_FLAG) {
log_write("\"%s\": Arrays are not currently supported", name);
return -ENOTSUP;
}
if (!prop_type_is_valid(type)) {
log_write("\"%s\": Unsupported type code %#x", name, type);
return -ENOTSUP;
}
expected_nbytes = prop_type_to_size(type);
if (expected_nbytes >= 0 && nbytes != expected_nbytes) {
log_write("\"%s\": Incorrect size %#x for type %s (expected %#x)", name,
nbytes, prop_type_to_string(type), expected_nbytes);
return -EINVAL;
}
if (type == PROP_STR) {
if (nbytes == 0) {
log_write("\"%s\": String node has zero length", name);
return -EBADMSG;
}
chars = bytes;
if (chars[nbytes - 1] != '\0') {
log_write("\"%s\": String node is not NUL terminated", name);
return -EBADMSG;
}
}
return 0;
}
int prop_alloc(struct prop **out, const char *name, enum prop_type type,
const void *bytes, uint32_t nbytes) {
struct prop *p;
int r;
assert(out != NULL);
assert(name != NULL);
assert(bytes != NULL || nbytes == 0);
*out = NULL;
p = NULL;
r = prop_validate(name, type, bytes, nbytes);
if (r < 0) {
goto end;
}
p = calloc(1, sizeof(*p) + nbytes);
if (p == NULL) {
r = -ENOMEM;
goto end;
}
p->name = strdup(name);
if (p->name == NULL) {
r = -ENOMEM;
goto end;
}
p->type = type;
p->nbytes = nbytes;
memcpy(p->bytes, bytes, nbytes);
*out = p;
p = NULL;
r = 0;
end:
prop_free(p);
return r;
}
void prop_free(struct prop *p) {
struct list_node *pos;
struct list_node *next;
struct attr *attr;
struct prop *child;
if (p == NULL) {
return;
}
for (pos = p->children.head; pos != NULL; pos = next) {
next = pos->next;
child = containerof(pos, struct prop, node);
prop_free(child);
}
for (pos = p->attrs.head; pos != NULL; pos = next) {
next = pos->next;
attr = containerof(pos, struct attr, node);
free(attr->key);
free(attr->val);
free(attr);
}
free(p->name);
free(p);
}
void prop_append(struct prop *p, struct prop *child) {
assert(p != NULL);
assert(child != NULL);
assert(child->parent == NULL);
list_append(&p->children, &child->node);
child->parent = p;
}
void prop_borrow_value(const struct prop *p, struct const_iobuf *out) {
assert(p != NULL);
assert(out != NULL);
out->bytes = p->bytes;
out->nbytes = p->nbytes;
out->pos = 0;
}
const char *prop_get_attr(const struct prop *p, const char *key) {
struct list_node *pos;
struct attr *a;
assert(p != NULL);
assert(key != NULL);
for (pos = p->attrs.head; pos != NULL; pos = pos->next) {
a = containerof(pos, struct attr, node);
if (str_eq(key, a->key)) {
return a->val;
}
}
return NULL;
}
const struct attr *prop_get_first_attr(const struct prop *p) {
struct list_node *attr;
assert(p != NULL);
attr = p->attrs.head;
if (attr == NULL) {
return NULL;
}
return containerof(attr, struct attr, node);
}
struct prop *prop_get_first_child(struct prop *p) {
struct list_node *child;
assert(p != NULL);
child = p->children.head;
if (child == NULL) {
return NULL;
}
return containerof(child, struct prop, node);
}
const struct prop *prop_get_first_child_const(const struct prop *p) {
const struct list_node *child;
assert(p != NULL);
child = p->children.head;
if (child == NULL) {
return NULL;
}
return containerof(child, struct prop, node);
}
const char *prop_get_name(const struct prop *p) {
assert(p != NULL);
return p->name;
}
struct prop *prop_get_next_sibling(struct prop *p) {
assert(p != NULL);
return containerof(p->node.next, struct prop, node);
}
const struct prop *prop_get_next_sibling_const(const struct prop *p) {
assert(p != NULL);
return containerof(p->node.next, struct prop, node);
}
struct prop *prop_get_parent(struct prop *p) {
assert(p != NULL);
return p->parent;
}
enum prop_type prop_get_type(const struct prop *p) {
assert(p != NULL);
return p->type;
}
const char *prop_get_value_str(const struct prop *p) {
assert(p != NULL);
assert(p->type == PROP_STR);
return (const char *)p->bytes;
}
struct prop *prop_search_child(struct prop *p, const char *name) {
struct list_node *pos;
struct prop *child;
assert(p != NULL);
assert(name != NULL);
for (pos = p->children.head; pos != NULL; pos = pos->next) {
child = containerof(pos, struct prop, node);
if (str_eq(child->name, name)) {
return child;
}
}
return NULL;
}
int prop_set_attr(struct prop *p, const char *key, const char *val) {
struct list_node *pos;
struct attr *a;
int r;
assert(p != NULL);
assert(key != NULL);
assert(val != NULL);
for (pos = p->attrs.head; pos != NULL; pos = pos->next) {
a = containerof(pos, struct attr, node);
if (str_eq(key, a->key) == 0) {
return attr_set(a, val);
}
}
a = calloc(1, sizeof(*a));
if (a == NULL) {
r = -ENOMEM;
goto end;
}
a->key = strdup(key);
if (a->key == NULL) {
r = -ENOMEM;
goto end;
}
a->val = strdup(val);
if (a->val == NULL) {
r = -ENOMEM;
goto end;
}
list_append(&p->attrs, &a->node);
a = NULL;
r = 0;
end:
free(a);
return r;
}
const struct attr *attr_get_next_sibling(const struct attr *a) {
const struct list_node *next;
assert(a != NULL);
next = a->node.next;
if (next == NULL) {
return NULL;
}
return containerof(next, struct attr, node);
}
const char *attr_get_key(const struct attr *a) {
assert(a != NULL);
return a->key;
}
const char *attr_get_val(const struct attr *a) {
assert(a != NULL);
return a->val;
}
static int attr_set(struct attr *a, const char *val) {
char *replace;
replace = strdup(val);
if (replace == NULL) {
return -ENOMEM;
}
free(a->val);
a->val = replace;
return 0;
}

32
573file/prop.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#include <stdint.h>
#include "573file/prop-type.h"
#include "util/iobuf.h"
struct attr;
struct prop;
int prop_alloc(struct prop **p, const char *name, enum prop_type type,
const void *bytes, uint32_t nbytes);
void prop_free(struct prop *p);
void prop_append(struct prop *p, struct prop *child);
void prop_borrow_value(const struct prop *p, struct const_iobuf *out);
const char *prop_get_attr(const struct prop *p, const char *key);
const struct attr *prop_get_first_attr(const struct prop *p);
struct prop *prop_get_first_child(struct prop *p);
const struct prop *prop_get_first_child_const(const struct prop *p);
const char *prop_get_name(const struct prop *p);
struct prop *prop_get_next_sibling(struct prop *p);
const struct prop *prop_get_next_sibling_const(const struct prop *p);
struct prop *prop_get_parent(struct prop *p);
enum prop_type prop_get_type(const struct prop *p);
const char *prop_get_value_str(const struct prop *p);
struct prop *prop_search_child(struct prop *p, const char *name);
int prop_set_attr(struct prop *p, const char *key, const char *val);
const struct attr *attr_get_next_sibling(const struct attr *a);
const char *attr_get_key(const struct attr *a);
const char *attr_get_val(const struct attr *a);

24
LICENSE Normal file
View File

@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

13
README.md Normal file
View File

@@ -0,0 +1,13 @@
# Saltytools
A toolkit for dumping and perhaps some day re-packing the proprietary file formats used by various Konami arcade games. Currently implemented using a very pedantic style of C99, these tools are small and UNIX-y, and are designed to be chained together into game-specific asset dumping pipelines by means of shell scripts.
# Similar projects
This toolkit is primarily intended to accommodate my own personal preferences and use cases, although maybe somebody else might find it useful as well. Similar tools with a richer feature set than this project can be found at the following location(s):
- https://github.com/DragonMinded/bemaniutils/
# License
The Unlicense (see LICENSE file for details).

242
ifsdump/main.c Normal file
View File

@@ -0,0 +1,242 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "573file/ifs.h"
#include "573file/prop-xml-writer.h"
#include "573file/prop.h"
#include "util/fs.h"
#include "util/log.h"
#include "util/str.h"
static int ifs_dump_child(struct ifs *ifs, const struct prop *child,
const char *parent_path);
static int ifs_dump_dir(struct ifs *ifs, const struct prop *dirent,
const char *path);
static int ifs_dump_file(struct ifs *ifs, const struct prop *child,
const char *path);
static int ifs_dump_toc(struct ifs *ifs, const char *dir);
int main(int argc, char **argv) {
const char *infile;
const char *outdir;
struct ifs *ifs;
int r;
if (argc != 3) {
fprintf(stderr, "Usage: %s [infile] [outdir]\n", argv[0]);
return EXIT_FAILURE;
}
infile = argv[1];
outdir = argv[2];
ifs = NULL;
r = ifs_open(&ifs, infile);
if (r < 0) {
goto end;
}
r = fs_mkdir(outdir);
if (r < 0) {
goto end;
}
r = ifs_dump_toc(ifs, outdir);
if (r < 0) {
goto end;
}
r = ifs_dump_child(ifs, ifs_get_root(ifs), outdir);
if (r < 0) {
goto end;
}
end:
ifs_close(ifs);
if (r < 0) {
log_write("%s (%i)", strerror(-r), r);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
static int ifs_dump_toc(struct ifs *ifs, const char *dir) {
const struct prop *toc;
struct const_iobuf buf;
char *path;
char *xml;
int r;
assert(ifs != NULL);
assert(dir != NULL);
path = NULL;
xml = NULL;
r = str_printf(&path, "%s/toc.xml", dir);
if (r < 0) {
goto end;
}
toc = ifs_get_root(ifs);
r = prop_xml_write(toc, &xml);
if (r < 0) {
goto end;
}
buf.bytes = (uint8_t *)xml;
buf.nbytes = strlen(xml);
buf.pos = 0;
r = fs_write_file(path, &buf);
if (r < 0) {
goto end;
}
end:
free(xml);
free(path);
return r;
}
static int ifs_dump_dir(struct ifs *ifs, const struct prop *parent,
const char *path) {
const struct prop *child;
int r;
assert(ifs != NULL);
assert(parent != NULL);
assert(path != NULL);
for (child = ifs_dirent_get_first_child(parent); child != NULL;
child = ifs_dirent_get_next_sibling(child)) {
r = ifs_dump_child(ifs, child, path);
if (r < 0) {
return r;
}
}
return 0;
}
static int ifs_dump_child(struct ifs *ifs, const struct prop *child,
const char *parent_path) {
char *name;
char *path;
int r;
assert(ifs != NULL);
assert(child != NULL);
assert(parent_path != NULL);
name = NULL;
path = NULL;
r = ifs_dirent_get_name(child, &name);
if (r < 0) {
goto end;
}
r = str_printf(&path, "%s/%s", parent_path, name);
if (r < 0) {
goto end;
}
if (ifs_dirent_is_dir(child)) {
r = fs_mkdir(path);
if (r < 0) {
goto end;
}
r = ifs_dump_dir(ifs, child, path);
} else {
r = ifs_dump_file(ifs, child, path);
}
end:
free(path);
free(name);
return r;
}
static int ifs_dump_file(struct ifs *ifs, const struct prop *child,
const char *path) {
struct const_iobuf src;
void *bytes;
size_t nbytes;
FILE *f;
int r;
assert(ifs != NULL);
assert(child != NULL);
assert(path != NULL);
bytes = NULL;
f = NULL;
r = ifs_read_file(ifs, child, NULL, &nbytes);
if (r < 0) {
goto end;
}
bytes = malloc(nbytes);
if (bytes == NULL) {
r = -ENOMEM;
goto end;
}
r = ifs_read_file(ifs, child, bytes, &nbytes);
if (r < 0) {
goto end;
}
r = fs_open(&f, path, "wb");
if (r < 0) {
log_write("fopen %s: %s (%i)", path, strerror(-r), r);
goto end;
}
src.bytes = bytes;
src.nbytes = nbytes;
src.pos = 0;
r = fs_write(f, &src);
if (r < 0) {
log_write("fwrite %s: %s (%i)", path, strerror(-r), r);
goto end;
}
end:
fs_close(f);
free(bytes);
return r;
}

12
ifsdump/meson.build Normal file
View File

@@ -0,0 +1,12 @@
executable(
'ifsdump',
include_directories: inc,
c_pch: '../precompiled.h',
link_with: [
_573file_lib,
util_lib
],
sources: [
'main.c'
]
)

9
meson.build Normal file
View File

@@ -0,0 +1,9 @@
project('saltytools', 'c', version: '0.1.0')
inc = include_directories('.')
subdir('573file')
subdir('util')
subdir('ifsdump')
subdir('xmldump')

12
precompiled.h Normal file
View File

@@ -0,0 +1,12 @@
#include <assert.h> // IWYU pragma: keep
#include <errno.h> // IWYU pragma: keep
#include <stdarg.h> // IWYU pragma: keep
#include <stdbool.h> // IWYU pragma: keep
#include <stddef.h> // IWYU pragma: keep
#include <stdint.h> // IWYU pragma: keep
#include <stdio.h> // IWYU pragma: keep
#include <stdlib.h> // IWYU pragma: keep
#include <string.h> // IWYU pragma: keep
#include <sys/stat.h> // IWYU pragma: keep
#include <unistd.h> // IWYU pragma: keep

231
util/fs.c Normal file
View File

@@ -0,0 +1,231 @@
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "util/fs.h"
#include "util/iobuf.h"
#include "util/log.h"
int fs_open(FILE **out, const char *path, const char *mode) {
FILE *f;
assert(out != NULL);
assert(path != NULL);
assert(mode != NULL);
*out = NULL;
f = fopen(path, mode);
if (f == NULL) {
return -errno;
}
*out = f;
return 0;
}
void fs_close(FILE *f) {
if (f != NULL) {
fclose(f);
}
}
int fs_seek_to(FILE *f, size_t off) {
int r;
assert(f != NULL);
r = fseek(f, off, SEEK_SET);
if (r != 0) {
r = -errno;
}
return r;
}
int fs_read(FILE *f, struct iobuf *buf) {
size_t nbytes;
size_t nread;
int r;
assert(f != NULL);
assert(buf != NULL);
assert(buf->pos <= buf->nbytes);
nbytes = buf->nbytes - buf->pos;
nread = fread(buf->bytes + buf->pos, 1, nbytes, f);
if (nread < nbytes) {
r = -errno;
if (r == 0) {
r = -ENODATA;
}
return r;
}
buf->pos += nbytes;
return 0;
}
int fs_write(FILE *f, struct const_iobuf *buf) {
size_t nbytes;
size_t nwrit;
int r;
assert(f != NULL);
assert(buf != NULL);
assert(buf->pos <= buf->nbytes);
nbytes = buf->nbytes - buf->pos;
nwrit = fwrite(buf->bytes + buf->pos, 1, nbytes, f);
if (nwrit < nbytes) {
r = -errno;
if (r != 0) {
log_write("Write failed: %s (%i)", strerror(-r), r);
} else {
log_write("Short write: %zu < %zu", nwrit, nbytes);
}
return r;
}
buf->pos += nbytes;
return 0;
}
int fs_read_file(const char *path, void **out_bytes, size_t *out_nbytes) {
FILE *f;
struct iobuf buf;
void *bytes;
long nbytes;
int r;
assert(path != NULL);
assert(out_bytes != NULL);
assert(out_nbytes != NULL);
bytes = NULL;
*out_bytes = NULL;
*out_nbytes = 0;
r = fs_open(&f, path, "rb");
if (r < 0) {
goto end;
}
r = fseek(f, 0, SEEK_END);
if (r != 0) {
r = -errno;
log_write("fseek(SEEK_END): %s (%i)", strerror(-r), r);
goto end;
}
nbytes = ftell(f);
r = fseek(f, 0, SEEK_SET);
if (r != 0) {
r = -errno;
log_write("fseek(SEEK_SET): %s (%i)", strerror(-r), r);
goto end;
}
bytes = malloc(nbytes);
if (bytes == NULL) {
r = -ENOMEM;
goto end;
}
buf.bytes = bytes;
buf.nbytes = nbytes;
buf.pos = 0;
r = fs_read(f, &buf);
if (r < 0) {
goto end;
}
*out_bytes = bytes;
*out_nbytes = nbytes;
bytes = NULL;
end:
free(bytes);
fs_close(f);
return r;
}
int fs_write_file(const char *path, struct const_iobuf *buf) {
FILE *f;
int r;
assert(path != NULL);
assert(buf != NULL);
f = NULL;
r = fs_open(&f, path, "wb");
if (r < 0) {
goto end;
}
r = fs_write(f, buf);
if (r < 0) {
goto end;
}
end:
fs_close(f);
return r;
}
int fs_mkdir(const char *path) {
struct stat s;
int r;
assert(path != NULL);
r = stat(path, &s);
if (r == 0) {
/* Early return on _success_, not failure. */
return 0;
}
r = -errno;
if (r != -ENOENT) {
log_write("stat(%s): %i (%s)", path, r, strerror(-r));
return r;
}
r = mkdir(path, 0755);
if (r != 0) {
r = -errno;
}
return r;
}

15
util/fs.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <stdio.h>
#include "util/iobuf.h"
int fs_open(FILE **out, const char *path, const char *mode);
void fs_close(FILE *f);
int fs_seek_to(FILE *f, size_t off);
int fs_read(FILE *f, struct iobuf *buf);
int fs_write(FILE *f, struct const_iobuf *buf);
int fs_read_file(const char *path, void **bytes, size_t *nbytes);
int fs_write_file(const char *path, struct const_iobuf *buf);
int fs_mkdir(const char *path);

336
util/iobuf.c Normal file
View File

@@ -0,0 +1,336 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "util/iobuf.h"
static int iobuf_bounds_check(const struct const_iobuf *buf, size_t nbytes);
static int iobuf_bounds_check(const struct const_iobuf *buf, size_t nbytes) {
assert(buf != NULL);
if (buf->pos + nbytes > buf->nbytes) {
return -ENODATA;
}
return 0;
}
int iobuf_slice(struct const_iobuf *dest, struct const_iobuf *src,
size_t nbytes) {
int r;
assert(dest != NULL);
assert(src != NULL);
r = iobuf_bounds_check(src, nbytes);
if (r < 0) {
return r;
}
dest->bytes = src->bytes + src->pos;
dest->nbytes = nbytes;
dest->pos = 0;
src->pos += nbytes;
return 0;
}
int iobuf_align_read(struct const_iobuf *buf, size_t alignment) {
size_t mask;
size_t new_pos;
assert(buf != NULL);
assert(alignment != 0);
mask = alignment - 1;
assert((alignment & mask) == 0);
new_pos = (buf->pos + mask) & ~mask;
if (new_pos > buf->nbytes) {
return -ENODATA;
}
buf->pos = new_pos;
return 0;
}
int iobuf_peek(struct const_iobuf *src, void *bytes, size_t nbytes) {
int r;
assert(src != NULL);
assert(bytes != NULL);
r = iobuf_bounds_check(src, nbytes);
if (r < 0) {
return r;
}
memcpy(bytes, &src->bytes[src->pos], nbytes);
return 0;
}
int iobuf_read(struct const_iobuf *src, void *bytes, size_t nbytes) {
int r;
assert(src != NULL);
assert(bytes != NULL);
r = iobuf_bounds_check(src, nbytes);
if (r < 0) {
return r;
}
memcpy(bytes, src->bytes + src->pos, nbytes);
src->pos += nbytes;
return 0;
}
int iobuf_read_8(struct const_iobuf *src, uint8_t *out) {
assert(src != NULL);
assert(out != NULL);
if (src->pos + sizeof(uint8_t) > src->nbytes) {
return -ENODATA;
}
*out = src->bytes[src->pos++];
return 0;
}
int iobuf_read_be16(struct const_iobuf *src, uint16_t *out) {
uint16_t value;
assert(src != NULL);
assert(out != NULL);
if (src->pos + sizeof(uint16_t) > src->nbytes) {
return -ENODATA;
}
value = src->bytes[src->pos++] << 8;
value |= src->bytes[src->pos++];
*out = value;
return 0;
}
int iobuf_read_be32(struct const_iobuf *src, uint32_t *out) {
uint32_t value;
assert(src != NULL);
assert(out != NULL);
if (src->pos + sizeof(uint32_t) > src->nbytes) {
return -ENODATA;
}
value = src->bytes[src->pos++] << 24;
value |= src->bytes[src->pos++] << 16;
value |= src->bytes[src->pos++] << 8;
value |= src->bytes[src->pos++];
*out = value;
return 0;
}
int iobuf_read_be64(struct const_iobuf *src, uint64_t *out) {
uint64_t value;
assert(src != NULL);
assert(out != NULL);
if (src->pos + sizeof(uint64_t) > src->nbytes) {
return -ENODATA;
}
value = ((uint64_t)src->bytes[src->pos++]) << 56;
value |= ((uint64_t)src->bytes[src->pos++]) << 48;
value |= ((uint64_t)src->bytes[src->pos++]) << 40;
value |= ((uint64_t)src->bytes[src->pos++]) << 32;
value |= ((uint64_t)src->bytes[src->pos++]) << 24;
value |= ((uint64_t)src->bytes[src->pos++]) << 16;
value |= ((uint64_t)src->bytes[src->pos++]) << 8;
value |= ((uint64_t)src->bytes[src->pos++]);
*out = value;
return 0;
}
int iobuf_read_le16(struct const_iobuf *src, uint16_t *out) {
uint16_t value;
assert(src != NULL);
assert(out != NULL);
if (src->pos + sizeof(uint16_t) > src->nbytes) {
return -ENODATA;
}
value = src->bytes[src->pos++];
value |= src->bytes[src->pos++] << 8;
*out = value;
return 0;
}
int iobuf_read_le32(struct const_iobuf *src, uint32_t *out) {
uint32_t value;
assert(src != NULL);
assert(out != NULL);
if (src->pos + sizeof(uint32_t) > src->nbytes) {
return -ENODATA;
}
value = src->bytes[src->pos++];
value |= src->bytes[src->pos++] << 8;
value |= src->bytes[src->pos++] << 16;
value |= src->bytes[src->pos++] << 24;
*out = value;
return 0;
}
int iobuf_read_le64(struct const_iobuf *src, uint64_t *out) {
uint64_t value;
assert(src != NULL);
assert(out != NULL);
if (src->pos + sizeof(uint64_t) > src->nbytes) {
return -ENODATA;
}
value = ((uint64_t)src->bytes[src->pos++]);
value |= ((uint64_t)src->bytes[src->pos++]) << 8;
value |= ((uint64_t)src->bytes[src->pos++]) << 16;
value |= ((uint64_t)src->bytes[src->pos++]) << 24;
value |= ((uint64_t)src->bytes[src->pos++]) << 32;
value |= ((uint64_t)src->bytes[src->pos++]) << 40;
value |= ((uint64_t)src->bytes[src->pos++]) << 48;
value |= ((uint64_t)src->bytes[src->pos++]) << 56;
*out = value;
return 0;
}
void iobuf_write(struct iobuf *dest, const void *bytes, size_t nbytes) {
assert(dest != NULL);
assert(bytes != NULL || nbytes == 0);
if (dest->pos + nbytes <= dest->nbytes) {
memcpy(dest->bytes + dest->pos, bytes, nbytes);
}
dest->pos += nbytes;
}
void iobuf_write_8(struct iobuf *dest, uint8_t value) {
assert(dest != NULL);
if (dest->pos + 1 <= dest->nbytes) {
dest->bytes[dest->pos] = value;
}
dest->pos += 1;
}
void iobuf_write_be16(struct iobuf *dest, uint16_t value) {
assert(dest != NULL);
if (dest->pos + 2 <= dest->nbytes) {
dest->bytes[dest->pos + 0] = value >> 8;
dest->bytes[dest->pos + 1] = value;
}
dest->pos += 2;
}
void iobuf_write_be32(struct iobuf *dest, uint32_t value) {
assert(dest != NULL);
if (dest->pos + 4 <= dest->nbytes) {
dest->bytes[dest->pos + 0] = value >> 24;
dest->bytes[dest->pos + 1] = value >> 16;
dest->bytes[dest->pos + 2] = value >> 8;
dest->bytes[dest->pos + 3] = value;
}
dest->pos += 4;
}
void iobuf_write_be64(struct iobuf *dest, uint64_t value) {
assert(dest != NULL);
if (dest->pos + 8 <= dest->nbytes) {
dest->bytes[dest->pos + 0] = value >> 56;
dest->bytes[dest->pos + 1] = value >> 48;
dest->bytes[dest->pos + 2] = value >> 40;
dest->bytes[dest->pos + 3] = value >> 32;
dest->bytes[dest->pos + 4] = value >> 24;
dest->bytes[dest->pos + 5] = value >> 16;
dest->bytes[dest->pos + 6] = value >> 8;
dest->bytes[dest->pos + 7] = value;
}
dest->pos += 8;
}
void iobuf_write_le16(struct iobuf *dest, uint16_t value) {
assert(dest != NULL);
if (dest->pos + 2 <= dest->nbytes) {
dest->bytes[dest->pos + 0] = value;
dest->bytes[dest->pos + 1] = value >> 8;
}
dest->pos += 2;
}
void iobuf_write_le32(struct iobuf *dest, uint32_t value) {
assert(dest != NULL);
if (dest->pos + 4 <= dest->nbytes) {
dest->bytes[dest->pos + 0] = value;
dest->bytes[dest->pos + 1] = value >> 8;
dest->bytes[dest->pos + 2] = value >> 16;
dest->bytes[dest->pos + 3] = value >> 24;
}
dest->pos += 4;
}
void iobuf_write_le64(struct iobuf *dest, uint64_t value) {
assert(dest != NULL);
if (dest->pos + 8 <= dest->nbytes) {
dest->bytes[dest->pos + 0] = value;
dest->bytes[dest->pos + 1] = value >> 8;
dest->bytes[dest->pos + 2] = value >> 16;
dest->bytes[dest->pos + 3] = value >> 24;
dest->bytes[dest->pos + 4] = value >> 32;
dest->bytes[dest->pos + 5] = value >> 40;
dest->bytes[dest->pos + 6] = value >> 48;
dest->bytes[dest->pos + 7] = value >> 56;
}
dest->pos += 8;
}

39
util/iobuf.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
struct iobuf {
uint8_t *bytes;
size_t nbytes;
size_t pos;
};
struct const_iobuf {
const uint8_t *bytes;
size_t nbytes;
size_t pos;
};
int iobuf_slice(struct const_iobuf *dest, struct const_iobuf *src,
size_t nbytes);
int iobuf_align_read(struct const_iobuf *buf, size_t alignment);
int iobuf_peek(struct const_iobuf *src, void *bytes, size_t nbytes);
int iobuf_read(struct const_iobuf *src, void *bytes, size_t nbytes);
int iobuf_read_8(struct const_iobuf *src, uint8_t *value);
int iobuf_read_be16(struct const_iobuf *src, uint16_t *value);
int iobuf_read_be32(struct const_iobuf *src, uint32_t *value);
int iobuf_read_be64(struct const_iobuf *src, uint64_t *value);
int iobuf_read_le16(struct const_iobuf *src, uint16_t *value);
int iobuf_read_le32(struct const_iobuf *src, uint32_t *value);
int iobuf_read_le64(struct const_iobuf *src, uint64_t *value);
void iobuf_write(struct iobuf *dest, const void *bytes, size_t nbytes);
void iobuf_write_8(struct iobuf *dest, uint8_t value);
void iobuf_write_be16(struct iobuf *dest, uint16_t value);
void iobuf_write_be32(struct iobuf *dest, uint32_t value);
void iobuf_write_be64(struct iobuf *dest, uint64_t value);
void iobuf_write_le16(struct iobuf *dest, uint16_t value);
void iobuf_write_le32(struct iobuf *dest, uint32_t value);
void iobuf_write_le64(struct iobuf *dest, uint64_t value);

46
util/list.c Normal file
View File

@@ -0,0 +1,46 @@
#include <assert.h>
#include <stddef.h>
#include <string.h>
#include "util/list.h"
void list_append(struct list *list, struct list_node *node) {
assert(list != NULL);
assert(node != NULL);
node->next = NULL;
if (list->tail != NULL) {
list->tail->next = node;
} else {
list->head = node;
}
list->tail = node;
}
void list_remove(struct list *list, struct list_node *node) {
struct list_node *pos;
struct list_node *prev;
assert(list != NULL);
for (prev = NULL, pos = list->head; pos != NULL;
prev = pos, pos = pos->next) {
if (pos == node) {
if (prev != NULL) {
prev->next = node->next;
} else {
list->head = node->next;
}
if (node->next == NULL) {
list->tail = prev;
}
node->next = NULL;
return;
}
}
}

15
util/list.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <stdbool.h>
struct list {
struct list_node *head;
struct list_node *tail;
};
struct list_node {
struct list_node *next;
};
void list_append(struct list *list, struct list_node *node);
void list_remove(struct list *list, struct list_node *node);

25
util/log.c Normal file
View File

@@ -0,0 +1,25 @@
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "util/log.h"
static void log_vwrite_(const char *func, const char *fmt, va_list ap);
void log_write_(const char *func, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
log_vwrite_(func, fmt, ap);
va_end(ap);
}
static void log_vwrite_(const char *func, const char *fmt, va_list ap) {
printf("%s: ", func);
vprintf(fmt, ap);
puts("");
}
void log_error_(const char *func, const char *file, int line, int r) {
log_write_(func, "%s:%i: %s (%i)", file, line, strerror(-r), r);
}

10
util/log.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include "util/macro.h"
#define log_write(...) log_write_(__func__, __VA_ARGS__)
#define log_error(r) log_error_(__func__, __FILE__, __LINE__, r);
void log_write_(const char *func, const char *fmt, ...)
gcc_attribute((format(printf, 2, 3)));
void log_error_(const char *func, const char *file, int line, int r);

14
util/macro.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <stddef.h>
#ifdef __GNUC__
#define gcc_attribute(x) __attribute__(x)
#else
#define gcc_attribute(x)
#endif
#define containerof(member, outer_type, outer_member) \
((outer_type *)(((char *)member) - offsetof(outer_type, outer_member)))
#define lengthof(x) (sizeof(x) / sizeof(x[0]))

18
util/meson.build Normal file
View File

@@ -0,0 +1,18 @@
util_lib = static_library(
'util',
include_directories: [inc],
c_pch: '../precompiled.h',
sources: [
'fs.c',
'fs.h',
'iobuf.c',
'iobuf.h',
'list.c',
'list.h',
'log.c',
'log.h',
'macro.h',
'str.c',
'str.h',
]
)

123
util/str.c Normal file
View File

@@ -0,0 +1,123 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "util/str.h"
bool str_eq(const char *lhs, const char *rhs) {
if (lhs == NULL || rhs == NULL) {
return lhs == rhs;
}
return strcmp(lhs, rhs) == 0;
}
int str_printf(char **out, const char *fmt, ...) {
va_list ap;
int r;
assert(out != NULL);
assert(fmt != NULL);
va_start(ap, fmt);
*out = NULL;
r = str_vprintf(out, fmt, ap);
va_end(ap);
return r;
}
int str_vprintf(char **out, const char *fmt, va_list ap) {
va_list ap2;
char *chars;
int len;
int len2;
*out = NULL;
va_copy(ap2, ap);
len = vsnprintf(NULL, 0, fmt, ap2);
va_end(ap2);
chars = malloc(len + 1);
if (chars == NULL) {
return -ENOMEM;
}
va_copy(ap2, ap);
len2 = vsnprintf(chars, len + 1, fmt, ap2);
va_end(ap2);
assert(len2 == len);
*out = chars;
return 0;
}
void strbuf_printf(struct strbuf *dest, const char *fmt, ...) {
va_list ap;
assert(dest != NULL);
assert(fmt != NULL);
va_start(ap, fmt);
strbuf_vprintf(dest, fmt, ap);
va_end(ap);
}
void strbuf_vprintf(struct strbuf *dest, const char *fmt, va_list ap) {
size_t avail;
char *chars;
int result;
assert(dest != NULL);
assert(dest->chars == NULL || dest->pos < dest->nchars);
assert(fmt != NULL);
if (dest->chars == NULL) {
avail = 0;
chars = NULL;
} else {
avail = dest->nchars - dest->pos;
chars = dest->chars + dest->pos;
}
result = vsnprintf(chars, avail, fmt, ap);
assert(dest->chars == NULL || result < avail);
dest->pos += result;
}
void strbuf_putc(struct strbuf *dest, char c) {
assert(dest != NULL);
if (dest->chars != NULL) {
assert(dest->pos + 1 < dest->nchars);
dest->chars[dest->pos + 0] = c;
dest->chars[dest->pos + 1] = '\0';
}
dest->pos++;
}
void strbuf_puts(struct strbuf *dest, const char *str) {
size_t len;
assert(dest != NULL);
assert(str != NULL);
len = strlen(str);
if (dest->chars != NULL) {
assert(dest->pos + len < dest->nchars);
memcpy(dest->chars + dest->pos, str, len + 1);
}
dest->pos += len;
}

25
util/str.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include "util/macro.h"
struct strbuf {
char *chars;
size_t nchars;
size_t pos;
};
bool str_eq(const char *lhs, const char *rhs);
int str_printf(char **out, const char *fmt, ...)
gcc_attribute((format(printf, 2, 3)));
int str_vprintf(char **out, const char *fmt, va_list ap);
void strbuf_printf(struct strbuf *dest, const char *fmt, ...)
gcc_attribute((format(printf, 2, 3)));
void strbuf_vprintf(struct strbuf *dest, const char *fmt, va_list ap);
void strbuf_putc(struct strbuf *dest, char c);
void strbuf_puts(struct strbuf *dest, const char *str);

96
xmldump/main.c Normal file
View File

@@ -0,0 +1,96 @@
#include <stdio.h>
#include <stdlib.h>
#include "573file/prop-binary-reader.h"
#include "573file/prop-xml-writer.h"
#include "573file/prop.h"
#include "util/fs.h"
#include "util/iobuf.h"
#include "util/log.h"
int main(int argc, char **argv) {
const char *infile;
const char *outfile;
struct const_iobuf buf;
struct prop *p;
void *bytes;
char *xml;
size_t nbytes;
FILE *f;
FILE *f_dest;
int r;
if (argc < 2 || argc > 3) {
fprintf(stderr, "Usage: %s [infile] <outfile>\n", argv[0]);
return EXIT_FAILURE;
}
f = NULL;
bytes = NULL;
xml = NULL;
p = NULL;
infile = argv[1];
if (argc > 2) {
outfile = argv[2];
} else {
outfile = NULL;
}
r = fs_read_file(infile, &bytes, &nbytes);
if (r < 0) {
goto end;
}
r = prop_binary_parse(&p, bytes, nbytes);
if (r < 0) {
goto end;
}
r = prop_xml_write(p, &xml);
if (r < 0) {
goto end;
}
if (outfile != NULL) {
r = fs_open(&f, outfile, "w");
if (r < 0) {
goto end;
}
f_dest = f;
} else {
f_dest = stdout;
}
buf.bytes = (uint8_t *)xml;
buf.nbytes = strlen(xml);
buf.pos = 0;
r = fs_write(f_dest, &buf);
if (r < 0) {
goto end;
}
end:
fs_close(f);
free(xml);
prop_free(p);
free(bytes);
if (r < 0) {
log_write("%s (%i)", strerror(-r), r);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

12
xmldump/meson.build Normal file
View File

@@ -0,0 +1,12 @@
executable(
'xmldump',
include_directories: inc,
c_pch: '../precompiled.h',
link_with: [
_573file_lib,
util_lib
],
sources: [
'main.c'
]
)