253 lines
6.2 KiB
C
253 lines
6.2 KiB
C
#undef NDEBUG
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <intrin.h>
|
|
|
|
#include "zlib.h"
|
|
|
|
#include "ESPReader.h"
|
|
|
|
//
|
|
// === CONSTANTS ===
|
|
//
|
|
|
|
// timestamp field access
|
|
const uint16_t day_mask = 0x1F;
|
|
const uint16_t month_mask = 0xF;
|
|
const uint16_t year_mask = 0x7F;
|
|
const int month_offset = 5;
|
|
const int year_offset = 9;
|
|
|
|
//
|
|
// === FORWARD DECLARATIONS ===
|
|
//
|
|
|
|
void dynamic_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);
|
|
|
|
// header printers
|
|
void print_group_header(Group *const header);
|
|
void print_record_header(Record *const header);
|
|
void print_field_header(Field *const header);
|
|
|
|
// print helpers
|
|
void print_group_label(Group *const header);
|
|
void print_record_flags(Record *const header);
|
|
void print_timestamp(uint16_t ts);
|
|
void print_type(Type4 type);
|
|
void print_type4(Type4 val);
|
|
|
|
// utilities
|
|
Timestamp convert_ts(uint16_t ts);
|
|
|
|
//
|
|
// === FUNCTIONS ===
|
|
//
|
|
|
|
void dynamic_asserts(void) {
|
|
// binary overlay size checks
|
|
assert(sizeof(Record) == 24); // Record struct incorrect size
|
|
assert(sizeof(Group) == 24); // Group struct incorrect size
|
|
assert(sizeof((Group) { 0 }.label) == 4); // Label union in group struct incorrect size
|
|
assert(sizeof(Field) == 6); // Field struct incorrect size
|
|
}
|
|
|
|
void walk(const char *data, size_t size) {
|
|
// check assertions that cannot be checked at compile time
|
|
dynamic_asserts();
|
|
|
|
// 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));
|
|
}
|
|
|
|
const char *walk_unknown_data(const char *data, size_t size) {
|
|
|
|
const char *end = data + size;
|
|
while (data != end) {
|
|
assert(data < end);
|
|
|
|
const Type4 *type = (Type4 *)data;
|
|
|
|
// check valid type
|
|
assert(rt[rth2rt[rt_hash(type->uint)]] == type->uint);
|
|
|
|
if (type->uint == rt[GRUP])
|
|
data = walk_group(data);
|
|
else
|
|
data = walk_record(data);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
const char *walk_group(const char *data) {
|
|
Group *const header = (Group *const)data;
|
|
print_group_header(header);
|
|
|
|
// NB: group sizes include the header
|
|
|
|
const char *const start = data + sizeof(Group);
|
|
const char *const end = data + header->size;
|
|
|
|
// walk through the groups/records inside the group
|
|
data = walk_unknown_data(start, header->size);
|
|
assert(data == end);
|
|
|
|
return data;
|
|
}
|
|
|
|
const char *walk_record(const char *data) {
|
|
Record *const header = (Record *const)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
|
|
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) {
|
|
printf("--- HEADER: GROUP ---\n");
|
|
print_type(header->grup);
|
|
printf("Size: %u\n", header->size);
|
|
print_group_label(header);
|
|
assert(header->type < GTS_SIZE);
|
|
printf("Group type: %s\n", group_type_strings[header->type]);
|
|
print_timestamp(header->timestamp);
|
|
printf("Version Control Info: %04x\n", header->vcinfo);
|
|
printf("Unknown: %08x\n", header->unknown);
|
|
}
|
|
|
|
void print_record_header(Record *const header) {
|
|
printf("--- HEADER: RECORD ---\n");
|
|
print_type(header->type);
|
|
print_record_flags(header);
|
|
printf("FormID: %x\n", header->formid);
|
|
print_timestamp(header->timestamp);
|
|
printf("Version Control Info: %04x\n", header->vcinfo);
|
|
printf("Version: %u\n", header->version);
|
|
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) {
|
|
printf("Label: ");
|
|
switch (header->type) {
|
|
case GT_TOP:
|
|
print_type4(header->label.type);
|
|
break;
|
|
case GT_INTERIOR_CELL_BLOCK:
|
|
case GT_INTERIOR_CELL_SUBBLOCK:
|
|
printf("%d", header->label.number);
|
|
case GT_EXTERIOR_CELL_BLOCK:
|
|
case GT_EXTERIOR_CELL_SUBBLOCK:
|
|
printf("X: %d, Y: %d", header->label.coord[1], header->label.coord[0]);
|
|
case GT_WORLD_CHILDREN:
|
|
case GT_CELL_CHILDREN:
|
|
case GT_TOPIC_CHILDREN:
|
|
case GT_CELL_PERSISTENT_CHILDREN:
|
|
case GT_CELL_TEMPORARY_CHILDREN:
|
|
printf("FormID[%x]", header->label.formid);
|
|
break;
|
|
default:
|
|
assert(false); // invalid group type
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
void print_record_flags(Record *const header) {
|
|
printf("Flags:\n");
|
|
|
|
uint32_t flags = header->flags;
|
|
const uint32_t type = header->type.uint;
|
|
|
|
// print flags
|
|
if (type == rt[REFR]) {
|
|
// TODO
|
|
// REFR requires FormID lookup
|
|
flags = 0;
|
|
} else {
|
|
rfs_inner *const flag_lut = rfs[rt_hash(type)];
|
|
if (flag_lut) {
|
|
while (flags != 0) {
|
|
// will always be >= 0 as flags is not 0
|
|
size_t highest = 31 - __lzcnt(flags);
|
|
const char *const str = (*flag_lut)[highest];
|
|
if (str) {
|
|
printf(" - %s\n", str);
|
|
flags -= ((uint32_t)1) << highest;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (flags != 0) {
|
|
printf("\n\nOriginal flags: %08x\n", header->flags);
|
|
printf("Unhandled flags: %08x\n", flags);
|
|
assert(false); // unhandled flags
|
|
}
|
|
}
|
|
|
|
// This is the Skyrim SE timestamp format
|
|
void print_timestamp(uint16_t _ts) {
|
|
Timestamp ts = convert_ts(_ts);
|
|
printf("Timestamp: 20x%u-%02u-%02u\n", ts.year, ts.month, ts.day);
|
|
}
|
|
|
|
void print_type(Type4 type) {
|
|
printf("Type: ");
|
|
print_type4(type);
|
|
printf("\n");
|
|
}
|
|
|
|
void print_type4(Type4 val) {
|
|
// invariant: printed i characters from val.bytes
|
|
for (size_t i = 0; i != 4; i++)
|
|
printf("%c", val.bytes[i]);
|
|
}
|
|
|
|
Timestamp convert_ts(uint16_t ts) {
|
|
/*
|
|
const uint8_t day = (uint8_t)(ts & day_mask);
|
|
const uint8_t month = (uint8_t)((ts >> month_offset) & month_mask);
|
|
const uint16_t year = (ts >> year_offset) & year_mask;
|
|
*/
|
|
|
|
const uint8_t day = ts & 0xff;
|
|
const uint8_t hb = (ts >> 8) & 0xff;
|
|
const uint8_t month = ((hb - 1) % 12) + 1;
|
|
const uint8_t year = ((hb - 1) / 12 + 3) % 10;
|
|
|
|
return (Timestamp){ year, month, day };
|
|
}
|
|
|