From ab921e50597d24a01e7efc6051da55237d80421d Mon Sep 17 00:00:00 2001 From: William Miles Date: Mon, 5 Sep 2022 19:52:06 +1000 Subject: [PATCH] Flehsed out esp walker with callback interface. Implemented header printer using walker. --- NavmeshList/main.c | 6 +- espReader/ESPReader.h | 85 ++++++++++++++----------- espReader/Reader.c | 141 +++++++++++++++++++++++------------------- 3 files changed, 127 insertions(+), 105 deletions(-) diff --git a/NavmeshList/main.c b/NavmeshList/main.c index 2fb7e19..3a955c7 100644 --- a/NavmeshList/main.c +++ b/NavmeshList/main.c @@ -5,7 +5,7 @@ #undef NDEBUG #include -int main() { +int main(void) { FILE *fp; errno_t ret = fopen_s(&fp, "Skyrim.esm", "rb"); @@ -13,7 +13,7 @@ int main() { return ret; fseek(fp, 0L, SEEK_END); - long size = ftell(fp); + size_t size = ftell(fp); rewind(fp); char *buffer = malloc(size); @@ -23,7 +23,7 @@ int main() { size_t read = fread(buffer, sizeof(char), size, fp); assert(read == size); - walk(buffer, size); + espr_print(buffer, size); return 0; } diff --git a/espReader/ESPReader.h b/espReader/ESPReader.h index dc86f87..2188fa6 100644 --- a/espReader/ESPReader.h +++ b/espReader/ESPReader.h @@ -45,9 +45,9 @@ extern "C" { typedef union type4 Type4; typedef struct timestamp Timestamp; - typedef const struct group Group; - typedef const struct record Record; - typedef const struct field Field; + typedef struct group Group; + typedef struct record Record; + typedef struct field Field; // // === SIMPLE TYPES === @@ -73,7 +73,6 @@ extern "C" { enum node_type { // NT_ prefix NT_GROUP, NT_RECORD, - NT_FIELD }; // Record type enum @@ -158,16 +157,15 @@ extern "C" { // // Generic node - typedef const struct node Node; + typedef struct node Node; struct node { - const union { - Group *const group; - Record *const record; - Field *const field; - }; - const char *const data; - const enum node_type type; - const uint32_t _pad; + union { + Group *group; + Record *record; + } header; + char *const data; + enum node_type type; + uint32_t _pad; }; // calculated timestamp @@ -185,36 +183,36 @@ extern "C" { // Group header overlay struct group { - const Type4 grup; // always RT_GRUP - const uint32_t size; // uncludes the 24 byte group header - const union { - const Type4 type; // this may be mangled and should not be relied on - const formid formid; - const int32_t number; - const int16_t coord[2]; + Type4 grup; // always RT_GRUP + uint32_t size; // uncludes the 24 byte group header + union { + Type4 type; // this may be mangled and should not be relied on + formid formid; + int32_t number; + int16_t coord[2]; } label; // access determined by the `type` below - const int32_t type; // group_type enum - const uint16_t timestamp; - const uint16_t vcinfo; - const uint32_t unknown; + int32_t type; // group_type enum + uint16_t timestamp; + uint16_t vcinfo; + uint32_t unknown; }; // Record header overlay struct record { - const Type4 type; - const uint32_t size; - const uint32_t flags; - const uint32_t formid; - const uint16_t timestamp; - const uint16_t vcinfo; - const uint16_t version; - const uint16_t unknown; + Type4 type; + uint32_t size; + uint32_t flags; + uint32_t formid; + uint16_t timestamp; + uint16_t vcinfo; + uint16_t version; + uint16_t unknown; }; // Field header overlay struct field { - const Type4 type; - const uint16_t size; + Type4 type; + uint16_t size; }; #pragma pack(pop) @@ -253,10 +251,23 @@ extern "C" { return uint32_t_msh(type, RT_HASH_BITS, RT_HASH_SEED); } - // Walks the nodes of the esp/esm structured data at `data` - void walk(const char *data, size_t size); + /* `espr_walk` walks through the tree structure of the esp/esm binary data + * starting at `data` of `size` bytes. + * + * `cb` is a callback that takes a `Node` to process. `pt` is a pointer to + * arbitrary data that is passed on to `cb` whenever it is called. + * + * Data is walked sequentially. Nodes passed to `cb` will be strictly increasing + * in terms of memory location within the buffer. + */ + void espr_walk(char *data, size_t size, void (*cb)(Node n, void *pt), void *pt); - // End C++ guard + /* `espr_print` prints the header of every group and record in the given + * esp/esm binary data. + */ + void espr_print(char *data, size_t size); + +// End C++ guard #ifdef __cplusplus } #endif diff --git a/espReader/Reader.c b/espReader/Reader.c index 4f45bb6..4439cbc 100644 --- a/espReader/Reader.c +++ b/espReader/Reader.c @@ -1,4 +1,3 @@ -#undef NDEBUG #include #include #include @@ -23,34 +22,33 @@ const int year_offset = 9; // === FORWARD DECLARATIONS === // -void dynamic_asserts(void); +void asserts(void); -// tree walkers -const char *walk_group(const char *data); -const char *walk_unknown_data(const char *start, size_t size); -const char *walk_record(const char *data); -const char *walk_field(const char *data); +// Tree walkers +char *walk_concat(char *data, size_t size, void (*cb)(Node n, void *pt), void *pt); +char *walk_group(char *data, void (*cb)(Node n, void *pt), void *pt); +char *walk_record(char *data, void (*cb)(Node n, void *pt), void *pt); -// header printers -void print_group_header(Group *const header); -void print_record_header(Record *const header); -void print_field_header(Field *const header); +// Header printers +void print_group_header(Group *header); +void print_record_header(Record *header); -// print helpers -void print_group_label(Group *const header); -void print_record_flags(Record *const header); +// Printer helpers +void print_group_label(Group *header); +void print_record_flags(Record *header); void print_timestamp(uint16_t ts); void print_type(Type4 type); void print_type4(Type4 val); -// utilities +// Utilities Timestamp convert_ts(uint16_t ts); +void print_callback(Node n, void *_); // // === FUNCTIONS === // -void dynamic_asserts(void) { +void asserts(void) { // binary overlay size checks assert(sizeof(Record) == 24); // Record struct incorrect size assert(sizeof(Group) == 24); // Group struct incorrect size @@ -58,83 +56,102 @@ void dynamic_asserts(void) { assert(sizeof(Field) == 6); // Field struct incorrect size } -void walk(const char *data, size_t size) { +void espr_walk(char *data, size_t size, void (*cb)(Node n, void *pt), void *pt) { // check assertions that cannot be checked at compile time - dynamic_asserts(); + asserts(); + + char *data_start = data; // check that we are at the start of the file const Type4 type = *(const Type4 *)data; assert(type.uint == rt[TES4]); - const char *start = data; - // walk the TES4 record - data = walk_record(data); - - data = walk_unknown_data(data, size - (size_t)(data - start)); + data = walk_concat(data, size, cb, pt); + assert(data == data_start + size); } -const char *walk_unknown_data(const char *data, size_t size) { +void espr_print(char *data, size_t size) { + espr_walk(data, size, print_callback, NULL); +} +void print_callback(Node n, void *pt) { + (void)pt; + switch (n.type) { + case NT_GROUP: + print_group_header(n.header.group); + break; + case NT_RECORD: + print_record_header(n.header.record); + break; + default: + assert(false); // invalid node type + } +} + +/* Unknown data will be some concatenation of groups and records. + * + * `walk_concat` will call the appropriate walking function + * for each segment of unknown data in this concatenation. + */ +char *walk_concat(char *data, size_t size, void (*cb)(Node n, void *pt), void *pt) { const char *end = data + size; while (data != end) { - if (data >= end) { - fflush(stdout); - assert(false); - } + assert(data < end); const Type4 *type = (Type4 *)data; // check valid type assert(rt[rth2rt[rt_hash(type->uint)]] == type->uint); + // only need to distinguish between groups and records if (type->uint == rt[GRUP]) - data = walk_group(data); + data = walk_group(data, cb, pt); else - data = walk_record(data); + data = walk_record(data, cb, pt); } - return data; } -const char *walk_group(const char *data) { +/* Walk a group record. Group records are containers for any other type of record, + * including other group records. + * + * This function will also call `cb` with the node constructed from this group record. + */ +char *walk_group(char *data, void (*cb)(Node n, void *pt), void *pt) { Group *const header = (Group *const)data; - print_group_header(header); - // NB: group sizes include the header + // The size in the group header includes the size of the header + char *data_start = data + sizeof(Group); + char *data_end = data + header->size; + size_t data_size = data_end - data_start; - const char *const start = data + sizeof(Group); - const char *const end = data + header->size; - size_t size = header->size - sizeof(Group); + // Callback + Node n = { .header.group = header, .data = data_start, .type = NT_GROUP }; + cb(n, pt); - // walk through the groups/records inside the group - data = walk_unknown_data(start, size); - assert(data == end); + // Walk through the concatenation of data inside the group. + data = walk_concat(data_start, data_size, cb, pt); + assert(data == data_end); return data; } -const char *walk_record(const char *data) { - Record *const header = (Record *const)data; +char *walk_record(char *data, void (*cb)(Node n, void *pt), void *pt) { + Record *header = (Record *)data; assert(header->type.uint != rt[GRUP]); - print_record_header(header); - // update data location to next node and return - // record sizes do not include the header + char *data_start = data + sizeof(Record); + + // Callback + Node n = { .header.record = header, .data = data_start, .type = NT_RECORD }; + cb(n, pt); + + // Update data ptr based on record size. data += sizeof(Record) + header->size; return data; } -const char *walk_field(const char *data) { - Field *const header = (Field *const)data; - print_field_header(header); - - // update data location to next node and return - // field sizes do not include the header - data += sizeof(Field) + header->size; - return data; -} - -void print_group_header(Group *const header) { +void print_group_header(Group *header) { printf("--- HEADER: GROUP ---\n"); print_type(header->grup); printf("Size: %u\n", header->size); @@ -146,7 +163,7 @@ void print_group_header(Group *const header) { printf("Unknown: %08x\n", header->unknown); } -void print_record_header(Record *const header) { +void print_record_header(Record *header) { printf("--- HEADER: RECORD ---\n"); print_type(header->type); print_record_flags(header); @@ -157,13 +174,7 @@ void print_record_header(Record *const header) { printf("Unknown: %08x\n", header->unknown); } -void print_field_header(Field *const header) { - printf("--- HEADER: FIELD ---\n"); - print_type(header->type); - printf("Size: %u\n", header->size); -} - -void print_group_label(Group *const header) { +void print_group_label(Group *header) { printf("Label: "); switch (header->type) { case GT_TOP: @@ -188,7 +199,7 @@ void print_group_label(Group *const header) { printf("\n"); } -void print_record_flags(Record *const header) { +void print_record_flags(Record *header) { printf("Flags:\n"); uint32_t flags = header->flags;