Flehsed out esp walker with callback interface. Implemented header printer using walker.
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user