From 263fb12bd90843e6235dce27f4c215460788fa4f Mon Sep 17 00:00:00 2001 From: decafcode Date: Wed, 25 Oct 2023 19:22:20 -0400 Subject: [PATCH] feat: initial ifsdump and xmldump --- .gitignore | 2 + .vscode/extensions.json | 8 + .vscode/settings.json | 18 + 573file/ifs.c | 356 ++++++++++++++++++++ 573file/ifs.h | 20 ++ 573file/meson.build | 17 + 573file/prop-binary-reader.c | 459 ++++++++++++++++++++++++++ 573file/prop-binary-reader.h | 7 + 573file/prop-type.c | 43 +++ 573file/prop-type.h | 28 ++ 573file/prop-xml-writer.c | 617 +++++++++++++++++++++++++++++++++++ 573file/prop-xml-writer.h | 5 + 573file/prop.c | 385 ++++++++++++++++++++++ 573file/prop.h | 32 ++ LICENSE | 24 ++ README.md | 13 + ifsdump/main.c | 242 ++++++++++++++ ifsdump/meson.build | 12 + meson.build | 9 + precompiled.h | 12 + util/fs.c | 231 +++++++++++++ util/fs.h | 15 + util/iobuf.c | 336 +++++++++++++++++++ util/iobuf.h | 39 +++ util/list.c | 46 +++ util/list.h | 15 + util/log.c | 25 ++ util/log.h | 10 + util/macro.h | 14 + util/meson.build | 18 + util/str.c | 123 +++++++ util/str.h | 25 ++ xmldump/main.c | 96 ++++++ xmldump/meson.build | 12 + 34 files changed, 3314 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 573file/ifs.c create mode 100644 573file/ifs.h create mode 100644 573file/meson.build create mode 100644 573file/prop-binary-reader.c create mode 100644 573file/prop-binary-reader.h create mode 100644 573file/prop-type.c create mode 100644 573file/prop-type.h create mode 100644 573file/prop-xml-writer.c create mode 100644 573file/prop-xml-writer.h create mode 100644 573file/prop.c create mode 100644 573file/prop.h create mode 100644 LICENSE create mode 100644 README.md create mode 100644 ifsdump/main.c create mode 100644 ifsdump/meson.build create mode 100644 meson.build create mode 100644 precompiled.h create mode 100644 util/fs.c create mode 100644 util/fs.h create mode 100644 util/iobuf.c create mode 100644 util/iobuf.h create mode 100644 util/list.c create mode 100644 util/list.h create mode 100644 util/log.c create mode 100644 util/log.h create mode 100644 util/macro.h create mode 100644 util/meson.build create mode 100644 util/str.c create mode 100644 util/str.h create mode 100644 xmldump/main.c create mode 100644 xmldump/meson.build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5b4f34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.cache/ +.vscode/launch.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..7d73736 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "llvm-vs-code-extensions.vscode-clangd", + "mesonbuild.mesonbuild", + "webfreak.debug", + "xaver.clang-format", + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cc7b64f --- /dev/null +++ b/.vscode/settings.json @@ -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", +} diff --git a/573file/ifs.c b/573file/ifs.c new file mode 100644 index 0000000..ec8db34 --- /dev/null +++ b/573file/ifs.c @@ -0,0 +1,356 @@ +#include +#include +#include +#include +#include + +#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; +} diff --git a/573file/ifs.h b/573file/ifs.h new file mode 100644 index 0000000..b03ac58 --- /dev/null +++ b/573file/ifs.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#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); diff --git a/573file/meson.build b/573file/meson.build new file mode 100644 index 0000000..caed79c --- /dev/null +++ b/573file/meson.build @@ -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', + ] +) diff --git a/573file/prop-binary-reader.c b/573file/prop-binary-reader.c new file mode 100644 index 0000000..bcfd6be --- /dev/null +++ b/573file/prop-binary-reader.c @@ -0,0 +1,459 @@ +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/573file/prop-binary-reader.h b/573file/prop-binary-reader.h new file mode 100644 index 0000000..71ee821 --- /dev/null +++ b/573file/prop-binary-reader.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "573file/prop.h" + +int prop_binary_parse(struct prop **p, const void *bytes, size_t nbytes); diff --git a/573file/prop-type.c b/573file/prop-type.c new file mode 100644 index 0000000..edc3c98 --- /dev/null +++ b/573file/prop-type.c @@ -0,0 +1,43 @@ +#include +#include + +#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]; +} diff --git a/573file/prop-type.h b/573file/prop-type.h new file mode 100644 index 0000000..3c7ea9e --- /dev/null +++ b/573file/prop-type.h @@ -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); diff --git a/573file/prop-xml-writer.c b/573file/prop-xml-writer.c new file mode 100644 index 0000000..9924442 --- /dev/null +++ b/573file/prop-xml-writer.c @@ -0,0 +1,617 @@ +#include + +#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, "\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, "\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, "\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, "<"); + + break; + + case '>': + strbuf_puts(dest, ">"); + + break; + + case '&': + strbuf_puts(dest, "&"); + + break; + + case '\'': + if (ctx == PROP_XML_ESCAPE_ATTR) { + strbuf_puts(dest, "'"); + } else { + strbuf_putc(dest, c); + } + + break; + + case '"': + if (ctx == PROP_XML_ESCAPE_ATTR) { + strbuf_puts(dest, """); + } 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); + } +} diff --git a/573file/prop-xml-writer.h b/573file/prop-xml-writer.h new file mode 100644 index 0000000..8103b54 --- /dev/null +++ b/573file/prop-xml-writer.h @@ -0,0 +1,5 @@ +#pragma once + +#include "573file/prop.h" + +int prop_xml_write(const struct prop *p, char **out); diff --git a/573file/prop.c b/573file/prop.c new file mode 100644 index 0000000..f0da770 --- /dev/null +++ b/573file/prop.c @@ -0,0 +1,385 @@ +#include +#include +#include +#include +#include + +#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; +} diff --git a/573file/prop.h b/573file/prop.h new file mode 100644 index 0000000..1fd9d3c --- /dev/null +++ b/573file/prop.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#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); diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/LICENSE @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d4c861 --- /dev/null +++ b/README.md @@ -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). diff --git a/ifsdump/main.c b/ifsdump/main.c new file mode 100644 index 0000000..8d1efa3 --- /dev/null +++ b/ifsdump/main.c @@ -0,0 +1,242 @@ +#include +#include +#include +#include + +#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; +} diff --git a/ifsdump/meson.build b/ifsdump/meson.build new file mode 100644 index 0000000..23ed106 --- /dev/null +++ b/ifsdump/meson.build @@ -0,0 +1,12 @@ +executable( + 'ifsdump', + include_directories: inc, + c_pch: '../precompiled.h', + link_with: [ + _573file_lib, + util_lib + ], + sources: [ + 'main.c' + ] +) diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..65736b3 --- /dev/null +++ b/meson.build @@ -0,0 +1,9 @@ +project('saltytools', 'c', version: '0.1.0') + +inc = include_directories('.') + +subdir('573file') +subdir('util') + +subdir('ifsdump') +subdir('xmldump') diff --git a/precompiled.h b/precompiled.h new file mode 100644 index 0000000..6b1d2be --- /dev/null +++ b/precompiled.h @@ -0,0 +1,12 @@ +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep + +#include // IWYU pragma: keep +#include // IWYU pragma: keep diff --git a/util/fs.c b/util/fs.c new file mode 100644 index 0000000..5eb721d --- /dev/null +++ b/util/fs.c @@ -0,0 +1,231 @@ +#include +#include +#include +#include + +#include +#include + +#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; +} diff --git a/util/fs.h b/util/fs.h new file mode 100644 index 0000000..ef2313b --- /dev/null +++ b/util/fs.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#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); diff --git a/util/iobuf.c b/util/iobuf.c new file mode 100644 index 0000000..cf8a5db --- /dev/null +++ b/util/iobuf.c @@ -0,0 +1,336 @@ +#include +#include +#include + +#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; +} diff --git a/util/iobuf.h b/util/iobuf.h new file mode 100644 index 0000000..c74049f --- /dev/null +++ b/util/iobuf.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +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); diff --git a/util/list.c b/util/list.c new file mode 100644 index 0000000..b942d70 --- /dev/null +++ b/util/list.c @@ -0,0 +1,46 @@ +#include +#include +#include + +#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; + } + } +} diff --git a/util/list.h b/util/list.h new file mode 100644 index 0000000..8b9fbe6 --- /dev/null +++ b/util/list.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +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); diff --git a/util/log.c b/util/log.c new file mode 100644 index 0000000..82bf6c5 --- /dev/null +++ b/util/log.c @@ -0,0 +1,25 @@ +#include +#include +#include + +#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); +} diff --git a/util/log.h b/util/log.h new file mode 100644 index 0000000..9bb738d --- /dev/null +++ b/util/log.h @@ -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); diff --git a/util/macro.h b/util/macro.h new file mode 100644 index 0000000..6e283e8 --- /dev/null +++ b/util/macro.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#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])) diff --git a/util/meson.build b/util/meson.build new file mode 100644 index 0000000..b119a33 --- /dev/null +++ b/util/meson.build @@ -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', + ] +) diff --git a/util/str.c b/util/str.c new file mode 100644 index 0000000..a5777cd --- /dev/null +++ b/util/str.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include + +#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; +} diff --git a/util/str.h b/util/str.h new file mode 100644 index 0000000..1112b7a --- /dev/null +++ b/util/str.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +#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); diff --git a/xmldump/main.c b/xmldump/main.c new file mode 100644 index 0000000..05f036b --- /dev/null +++ b/xmldump/main.c @@ -0,0 +1,96 @@ +#include +#include + +#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] \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; +} diff --git a/xmldump/meson.build b/xmldump/meson.build new file mode 100644 index 0000000..fa7477c --- /dev/null +++ b/xmldump/meson.build @@ -0,0 +1,12 @@ +executable( + 'xmldump', + include_directories: inc, + c_pch: '../precompiled.h', + link_with: [ + _573file_lib, + util_lib + ], + sources: [ + 'main.c' + ] +)