Flehsed out esp walker with callback interface. Implemented header printer using walker.

This commit is contained in:
2022-09-05 19:52:06 +10:00
parent 20d29ea721
commit ab921e5059
3 changed files with 127 additions and 105 deletions

View File

@@ -5,7 +5,7 @@
#undef NDEBUG #undef NDEBUG
#include <assert.h> #include <assert.h>
int main() { int main(void) {
FILE *fp; FILE *fp;
errno_t ret = fopen_s(&fp, "Skyrim.esm", "rb"); errno_t ret = fopen_s(&fp, "Skyrim.esm", "rb");
@@ -13,7 +13,7 @@ int main() {
return ret; return ret;
fseek(fp, 0L, SEEK_END); fseek(fp, 0L, SEEK_END);
long size = ftell(fp); size_t size = ftell(fp);
rewind(fp); rewind(fp);
char *buffer = malloc(size); char *buffer = malloc(size);
@@ -23,7 +23,7 @@ int main() {
size_t read = fread(buffer, sizeof(char), size, fp); size_t read = fread(buffer, sizeof(char), size, fp);
assert(read == size); assert(read == size);
walk(buffer, size); espr_print(buffer, size);
return 0; return 0;
} }

View File

@@ -45,9 +45,9 @@ extern "C" {
typedef union type4 Type4; typedef union type4 Type4;
typedef struct timestamp Timestamp; typedef struct timestamp Timestamp;
typedef const struct group Group; typedef struct group Group;
typedef const struct record Record; typedef struct record Record;
typedef const struct field Field; typedef struct field Field;
// //
// === SIMPLE TYPES === // === SIMPLE TYPES ===
@@ -73,7 +73,6 @@ extern "C" {
enum node_type { // NT_ prefix enum node_type { // NT_ prefix
NT_GROUP, NT_GROUP,
NT_RECORD, NT_RECORD,
NT_FIELD
}; };
// Record type enum // Record type enum
@@ -158,16 +157,15 @@ extern "C" {
// //
// Generic node // Generic node
typedef const struct node Node; typedef struct node Node;
struct node { struct node {
const union { union {
Group *const group; Group *group;
Record *const record; Record *record;
Field *const field; } header;
}; char *const data;
const char *const data; enum node_type type;
const enum node_type type; uint32_t _pad;
const uint32_t _pad;
}; };
// calculated timestamp // calculated timestamp
@@ -185,36 +183,36 @@ extern "C" {
// Group header overlay // Group header overlay
struct group { struct group {
const Type4 grup; // always RT_GRUP Type4 grup; // always RT_GRUP
const uint32_t size; // uncludes the 24 byte group header uint32_t size; // uncludes the 24 byte group header
const union { union {
const Type4 type; // this may be mangled and should not be relied on Type4 type; // this may be mangled and should not be relied on
const formid formid; formid formid;
const int32_t number; int32_t number;
const int16_t coord[2]; int16_t coord[2];
} label; // access determined by the `type` below } label; // access determined by the `type` below
const int32_t type; // group_type enum int32_t type; // group_type enum
const uint16_t timestamp; uint16_t timestamp;
const uint16_t vcinfo; uint16_t vcinfo;
const uint32_t unknown; uint32_t unknown;
}; };
// Record header overlay // Record header overlay
struct record { struct record {
const Type4 type; Type4 type;
const uint32_t size; uint32_t size;
const uint32_t flags; uint32_t flags;
const uint32_t formid; uint32_t formid;
const uint16_t timestamp; uint16_t timestamp;
const uint16_t vcinfo; uint16_t vcinfo;
const uint16_t version; uint16_t version;
const uint16_t unknown; uint16_t unknown;
}; };
// Field header overlay // Field header overlay
struct field { struct field {
const Type4 type; Type4 type;
const uint16_t size; uint16_t size;
}; };
#pragma pack(pop) #pragma pack(pop)
@@ -253,10 +251,23 @@ extern "C" {
return uint32_t_msh(type, RT_HASH_BITS, RT_HASH_SEED); return uint32_t_msh(type, RT_HASH_BITS, RT_HASH_SEED);
} }
// Walks the nodes of the esp/esm structured data at `data` /* `espr_walk` walks through the tree structure of the esp/esm binary data
void walk(const char *data, size_t size); * 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 #ifdef __cplusplus
} }
#endif #endif

View File

@@ -1,4 +1,3 @@
#undef NDEBUG
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
@@ -23,34 +22,33 @@ const int year_offset = 9;
// === FORWARD DECLARATIONS === // === FORWARD DECLARATIONS ===
// //
void dynamic_asserts(void); void asserts(void);
// tree walkers // Tree walkers
const char *walk_group(const char *data); char *walk_concat(char *data, size_t size, void (*cb)(Node n, void *pt), void *pt);
const char *walk_unknown_data(const char *start, size_t size); char *walk_group(char *data, void (*cb)(Node n, void *pt), void *pt);
const char *walk_record(const char *data); char *walk_record(char *data, void (*cb)(Node n, void *pt), void *pt);
const char *walk_field(const char *data);
// header printers // Header printers
void print_group_header(Group *const header); void print_group_header(Group *header);
void print_record_header(Record *const header); void print_record_header(Record *header);
void print_field_header(Field *const header);
// print helpers // Printer helpers
void print_group_label(Group *const header); void print_group_label(Group *header);
void print_record_flags(Record *const header); void print_record_flags(Record *header);
void print_timestamp(uint16_t ts); void print_timestamp(uint16_t ts);
void print_type(Type4 type); void print_type(Type4 type);
void print_type4(Type4 val); void print_type4(Type4 val);
// utilities // Utilities
Timestamp convert_ts(uint16_t ts); Timestamp convert_ts(uint16_t ts);
void print_callback(Node n, void *_);
// //
// === FUNCTIONS === // === FUNCTIONS ===
// //
void dynamic_asserts(void) { void asserts(void) {
// binary overlay size checks // binary overlay size checks
assert(sizeof(Record) == 24); // Record struct incorrect size assert(sizeof(Record) == 24); // Record struct incorrect size
assert(sizeof(Group) == 24); // Group 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 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 // 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 // check that we are at the start of the file
const Type4 type = *(const Type4 *)data; const Type4 type = *(const Type4 *)data;
assert(type.uint == rt[TES4]); assert(type.uint == rt[TES4]);
const char *start = data; data = walk_concat(data, size, cb, pt);
// walk the TES4 record assert(data == data_start + size);
data = walk_record(data);
data = walk_unknown_data(data, size - (size_t)(data - start));
} }
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; const char *end = data + size;
while (data != end) { while (data != end) {
if (data >= end) { assert(data < end);
fflush(stdout);
assert(false);
}
const Type4 *type = (Type4 *)data; const Type4 *type = (Type4 *)data;
// check valid type // check valid type
assert(rt[rth2rt[rt_hash(type->uint)]] == type->uint); assert(rt[rth2rt[rt_hash(type->uint)]] == type->uint);
// only need to distinguish between groups and records
if (type->uint == rt[GRUP]) if (type->uint == rt[GRUP])
data = walk_group(data); data = walk_group(data, cb, pt);
else else
data = walk_record(data); data = walk_record(data, cb, pt);
} }
return data; 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; 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); // Callback
const char *const end = data + header->size; Node n = { .header.group = header, .data = data_start, .type = NT_GROUP };
size_t size = header->size - sizeof(Group); cb(n, pt);
// walk through the groups/records inside the group // Walk through the concatenation of data inside the group.
data = walk_unknown_data(start, size); data = walk_concat(data_start, data_size, cb, pt);
assert(data == end); assert(data == data_end);
return data; return data;
} }
const char *walk_record(const char *data) { char *walk_record(char *data, void (*cb)(Node n, void *pt), void *pt) {
Record *const header = (Record *const)data; Record *header = (Record *)data;
assert(header->type.uint != rt[GRUP]); assert(header->type.uint != rt[GRUP]);
print_record_header(header);
// update data location to next node and return char *data_start = data + sizeof(Record);
// record sizes do not include the header
// 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; data += sizeof(Record) + header->size;
return data; return data;
} }
const char *walk_field(const char *data) { void print_group_header(Group *header) {
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) {
printf("--- HEADER: GROUP ---\n"); printf("--- HEADER: GROUP ---\n");
print_type(header->grup); print_type(header->grup);
printf("Size: %u\n", header->size); printf("Size: %u\n", header->size);
@@ -146,7 +163,7 @@ void print_group_header(Group *const header) {
printf("Unknown: %08x\n", header->unknown); printf("Unknown: %08x\n", header->unknown);
} }
void print_record_header(Record *const header) { void print_record_header(Record *header) {
printf("--- HEADER: RECORD ---\n"); printf("--- HEADER: RECORD ---\n");
print_type(header->type); print_type(header->type);
print_record_flags(header); print_record_flags(header);
@@ -157,13 +174,7 @@ void print_record_header(Record *const header) {
printf("Unknown: %08x\n", header->unknown); printf("Unknown: %08x\n", header->unknown);
} }
void print_field_header(Field *const header) { void print_group_label(Group *header) {
printf("--- HEADER: FIELD ---\n");
print_type(header->type);
printf("Size: %u\n", header->size);
}
void print_group_label(Group *const header) {
printf("Label: "); printf("Label: ");
switch (header->type) { switch (header->type) {
case GT_TOP: case GT_TOP:
@@ -188,7 +199,7 @@ void print_group_label(Group *const header) {
printf("\n"); printf("\n");
} }
void print_record_flags(Record *const header) { void print_record_flags(Record *header) {
printf("Flags:\n"); printf("Flags:\n");
uint32_t flags = header->flags; uint32_t flags = header->flags;