A lot of formatting and an untested implementation of espr_create_tree.

This commit is contained in:
2022-09-08 12:23:16 +10:00
parent 4d7bdcf3cc
commit ff95aeafbf
4 changed files with 507 additions and 351 deletions

View File

@@ -34,13 +34,13 @@ int main(void) {
if (!decompressed) if (!decompressed)
return errno; return errno;
espr_print(buffer, size); // espr_print(buffer, size);
espr_decompress(buffer, size, decompressed, stats.decompressed_size); espr_decompress(buffer, size, decompressed, stats.decompressed_size);
free(buffer); free(buffer);
// espr_print(decompressed, stats.decompressed_size); espr_print(decompressed, stats.decompressed_size);
free(decompressed); free(decompressed);

View File

@@ -109,6 +109,10 @@ extern "C" {
char _pad[4]; char _pad[4];
}; };
struct sized_buf {
char *data;
size_t size;
};
// //
// === ENUMS === // === ENUMS ===
@@ -172,7 +176,7 @@ extern "C" {
Group *group; Group *group;
Record *record; Record *record;
} header; } header;
char *const data; char *data;
enum node_type type; enum node_type type;
uint32_t _pad; uint32_t _pad;
}; };
@@ -196,8 +200,8 @@ extern "C" {
* will be passed to pre and post when they are called. * will be passed to pre and post when they are called.
*/ */
struct walker_callbacks { struct walker_callbacks {
void (*pre)(Node n, void *data, void **carry_out); void (*pre)(Node n, void *data, void **carry_out, void *from_parent, void **to_children);
void (*post)(Node n, void *data, void **carry_in); void (*post)(Node n, void *data, void *carry_in);
void *data; void *data;
}; };
@@ -227,7 +231,8 @@ extern "C" {
struct meta_node { struct meta_node {
Node n; Node n;
MetaNode *parent; MetaNode *parent;
MetaNode *child; MetaNode *first_child;
MetaNode *last_child;
MetaNode *prev; MetaNode *prev;
MetaNode *next; MetaNode *next;
}; };
@@ -349,7 +354,7 @@ extern "C" {
*/ */
void espr_decompress(char *data, size_t size, char *buf, size_t buf_size); void espr_decompress(char *data, size_t size, char *buf, size_t buf_size);
MetaNode *espr_create_tree(struct sized_buf in, struct sized_buf tree);
// End C++ guard // End C++ guard
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -10,6 +10,8 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <intrin.h> #include <intrin.h>
#include <ammintrin.h>
#include <io.h> #include <io.h>
#include "zlib.h" #include "zlib.h"
@@ -36,9 +38,14 @@ const int year_offset = 9;
void asserts(void); void asserts(void);
// Tree walkers // Tree walkers
char *walk_concat(char *data, size_t size, struct walker_callbacks cb); char *walk_concat(
char *walk_group(char *data, struct walker_callbacks cb); char *data,
char *walk_record(char *data, struct walker_callbacks cb); size_t size,
struct walker_callbacks cb,
void *from_parent
);
char *walk_group(char *data, struct walker_callbacks cb, void *from_parent);
char *walk_record(char *data, struct walker_callbacks cb, void *from_parent);
// Header printers // Header printers
void print_group_header(Group *header); void print_group_header(Group *header);
@@ -55,28 +62,63 @@ void sb_write(int fp, struct str_buf sb_pre, struct str_buf sb_post);
// Utilities // Utilities
Timestamp convert_ts(uint16_t ts); Timestamp convert_ts(uint16_t ts);
void print_cb(Node n, void *data, void **carry_out);
void stats_cb(Node n, void *data, void **carry_out); // Callbacks
void decompress_pre(Node n, void *data, void **carry_out); void print_cb(
void decompress_post(Node n, void *data, void **carry_in); Node n,
void *data,
void **carry_out,
void *from_parent,
void **to_children
);
void stats_cb(
Node n,
void *data,
void **carry_out,
void *from_parent,
void **to_children
);
void decompress_pre(
Node n,
void *decom_ptr,
void **carry_out,
void *from_parent,
void **to_children
);
void decompress_post(
Node n,
void *data,
void **carry_in
);
void create_tree_cb(
Node n,
void *data,
void **carry_out,
void *from_parent,
void **to_children
);
// //
// === FUNCTIONS === // === FUNCTIONS ===
// //
void 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
assert(sizeof((Group) { 0 }.label) == 4); // Label union incorrect size assert(sizeof((Group) { 0 }.label) == 4); // Label union incorrect size
assert(sizeof(Field) == 6); // Field struct incorrect size assert(sizeof(Field) == 6); // Field struct incorrect size
assert(sizeof(MetaNode) == 64); // 1 cache line
// zlib compatability // zlib compatability
assert(sizeof(uLongf) == sizeof(uint32_t)); assert(sizeof(uLongf) == sizeof(uint32_t));
assert(sizeof(Bytef) == sizeof(char)); assert(sizeof(Bytef) == sizeof(char));
} }
void espr_walk(char *data, size_t size, struct walker_callbacks cb) { void espr_walk(char *data, size_t size, struct walker_callbacks cb)
{
// check assertions that cannot be checked at compile time // check assertions that cannot be checked at compile time
asserts(); asserts();
@@ -86,7 +128,7 @@ void espr_walk(char *data, size_t size, struct walker_callbacks cb) {
const Type4 type = *(const Type4 *)data; const Type4 type = *(const Type4 *)data;
assert(type.uint == rt[TES4]); assert(type.uint == rt[TES4]);
data = walk_concat(data, size, cb); data = walk_concat(data, size, cb, NULL);
assert(data == data_start + size); assert(data == data_start + size);
} }
@@ -95,7 +137,12 @@ void espr_walk(char *data, size_t size, struct walker_callbacks cb) {
* `walk_concat` will call the appropriate walking function * `walk_concat` will call the appropriate walking function
* for each segment of unknown data in this concatenation. * for each segment of unknown data in this concatenation.
*/ */
char *walk_concat(char *data, size_t size, struct walker_callbacks cb) { char *walk_concat(
char *data,
size_t size,
struct walker_callbacks cb,
void *from_parent
) {
const char *end = data + size; const char *end = data + size;
while (data != end) { while (data != end) {
assert(data < end); assert(data < end);
@@ -107,9 +154,9 @@ char *walk_concat(char *data, size_t size, struct walker_callbacks cb) {
// only need to distinguish between groups and records // only need to distinguish between groups and records
if (type->uint == rt[GRUP]) if (type->uint == rt[GRUP])
data = walk_group(data, cb); data = walk_group(data, cb, from_parent);
else else
data = walk_record(data, cb); data = walk_record(data, cb, from_parent);
} }
return data; return data;
} }
@@ -120,7 +167,8 @@ char *walk_concat(char *data, size_t size, struct walker_callbacks cb) {
* This function will also call `cb` with the node constructed from this group * This function will also call `cb` with the node constructed from this group
* record. * record.
*/ */
char *walk_group(char *data, struct walker_callbacks cb) { char *walk_group(char *data, struct walker_callbacks cb, void *from_parent)
{
Group *const header = (Group *const)data; Group *const header = (Group *const)data;
// The size in the group header includes the size of the header // The size in the group header includes the size of the header
@@ -128,60 +176,80 @@ char *walk_group(char *data, struct walker_callbacks cb) {
char *data_end = data + header->size; char *data_end = data + header->size;
size_t data_size = data_end - data_start; size_t data_size = data_end - data_start;
Node n = { .header.group = header, .data = data_start, .type = NT_GROUP }; Node n = {
void *carry; .header.group = header,
.data = data_start,
.type = NT_GROUP
};
void *carry = NULL;
void *to_children = NULL;
// Pre-walk callback // Pre-walk callback
if (cb.pre) if (cb.pre)
cb.pre(n, cb.data, &carry); cb.pre(n, cb.data, &carry, from_parent, &to_children);
// Walk through the concatenation of data inside the group. // Walk through the concatenation of data inside the group.
data = walk_concat(data_start, data_size, cb); data = walk_concat(data_start, data_size, cb, to_children);
assert(data == data_end); assert(data == data_end);
// Post-walk callback // Post-walk callback
if (cb.post) if (cb.post)
cb.post(n, cb.data, &carry); cb.post(n, cb.data, carry);
return data; return data;
} }
char *walk_record(char *data, struct walker_callbacks cb) { char *walk_record(char *data, struct walker_callbacks cb, void *from_parent)
{
Record *header = (Record *)data; Record *header = (Record *)data;
assert(header->type.uint != rt[GRUP]); assert(header->type.uint != rt[GRUP]);
char *data_start = data + sizeof(Record); char *data_start = data + sizeof(Record);
Node n = { .header.record = header, .data = data_start, .type = NT_RECORD }; Node n = {
void *carry; .header.record = header,
.data = data_start,
.type = NT_RECORD
};
void *carry = NULL;
void *to_children = NULL;
/* Pre and post walk callbacks make less sense for record walking as records /* Pre and post walk callbacks make less sense for record walking as
* are leaf-ish, will still call both here for now as field walking may be * records are leaf-ish, will still call both here for now as field
* added in the future. * walking may be added in the future.
*/ */
// Pre-walk callback // Pre-walk callback
if (cb.pre) if (cb.pre)
cb.pre(n, cb.data, &carry); cb.pre(n, cb.data, &carry, from_parent, &to_children);
// Update data ptr based on record size. // Update data ptr based on record size.
data += sizeof(Record) + header->size; data += sizeof(Record) + header->size;
// Post-walk callback // Post-walk callback
if (cb.post) if (cb.post)
cb.post(n, cb.data, &carry); cb.post(n, cb.data, carry);
return data; return data;
} }
void espr_print(char *data, size_t size) { void espr_print(char *data, size_t size)
{
struct walker_callbacks cb = { .pre = print_cb }; struct walker_callbacks cb = { .pre = print_cb };
espr_walk(data, size, cb); espr_walk(data, size, cb);
} }
void print_cb(Node n, void *data, void **carry_out) { void print_cb(
Node n, void *data,
void **carry_out,
void *from_parent,
void **to_children
) {
(void)data; (void)data;
(void)carry_out; (void)carry_out;
(void)from_parent;
(void)to_children;
switch (n.type) { switch (n.type) {
case NT_GROUP: case NT_GROUP:
print_group_header(n.header.group); print_group_header(n.header.group);
@@ -194,7 +262,8 @@ void print_cb(Node n, void *data, void **carry_out) {
} }
} }
struct esp_stats espr_stats(char *data, size_t size) { struct esp_stats espr_stats(char *data, size_t size)
{
struct esp_stats stats = { 0 }; struct esp_stats stats = { 0 };
struct walker_callbacks cb = { .pre = stats_cb, .data = &stats }; struct walker_callbacks cb = { .pre = stats_cb, .data = &stats };
espr_walk(data, size, cb); espr_walk(data, size, cb);
@@ -205,8 +274,17 @@ struct esp_stats espr_stats(char *data, size_t size) {
* only need their header size tallied as their data size will be handled by * only need their header size tallied as their data size will be handled by
* further walking of the tree. * further walking of the tree.
*/ */
void stats_cb(Node n, void *data, void **carry_out) { void stats_cb(
Node n,
void *data,
void **carry_out,
void *from_parent,
void **to_children
) {
(void)carry_out; (void)carry_out;
(void)from_parent;
(void)to_children;
struct esp_stats *stats = data; struct esp_stats *stats = data;
switch (n.type) { switch (n.type) {
case NT_GROUP: case NT_GROUP:
@@ -217,7 +295,8 @@ void stats_cb(Node n, void *data, void **carry_out) {
stats->record_count++; stats->record_count++;
stats->decompressed_size += sizeof(Record); stats->decompressed_size += sizeof(Record);
if (n.header.record->flags & COMPRESSED_FLAG) { if (n.header.record->flags & COMPRESSED_FLAG) {
// uncompressed size is stored in the first 4 bytes of data // uncompressed size is stored in the first 4 bytes of
// data
stats->decompressed_size += *((uint32_t *)n.data); stats->decompressed_size += *((uint32_t *)n.data);
} else { } else {
stats->decompressed_size += n.header.record->size; stats->decompressed_size += n.header.record->size;
@@ -233,10 +312,14 @@ struct decom {
size_t remaining; size_t remaining;
}; };
void espr_decompress(char *data, size_t size, char *buf, size_t buf_size) { void espr_decompress(char *data, size_t size, char *buf, size_t buf_size)
{
struct decom s = { .buf = buf, .remaining = buf_size }; struct decom s = { .buf = buf, .remaining = buf_size };
struct walker_callbacks cb = struct walker_callbacks cb = {
{ .pre = decompress_pre, .post = decompress_post, .data = &s }; .pre = decompress_pre,
.post = decompress_post,
.data = &s
};
espr_walk(data, size, cb); espr_walk(data, size, cb);
} }
@@ -252,7 +335,16 @@ void espr_decompress(char *data, size_t size, char *buf, size_t buf_size) {
* destination. For compressed records in copies the header first and then * destination. For compressed records in copies the header first and then
* directly decompresses the compressed record into the destination. * directly decompresses the compressed record into the destination.
*/ */
void decompress_pre(Node n, void *decom_ptr, void **carry_out) { void decompress_pre(
Node n,
void *decom_ptr,
void **carry_out,
void *from_parent,
void **to_children
) {
(void)from_parent;
(void)to_children;
struct decom *d = decom_ptr; struct decom *d = decom_ptr;
switch (n.type) { switch (n.type) {
@@ -273,7 +365,8 @@ void decompress_pre(Node n, void *decom_ptr, void **carry_out) {
// first 4 bytes are the decompressed size // first 4 bytes are the decompressed size
const uint32_t dc_size = *((uint32_t *)n.data); const uint32_t dc_size = *((uint32_t *)n.data);
uint32_t to_copy = dc_size; uint32_t to_copy = dc_size;
uint32_t cur_size = n.header.record->size - sizeof(uint32_t); uint32_t cur_size =
n.header.record->size - sizeof(uint32_t);
char *data_start = n.data + sizeof(uint32_t); char *data_start = n.data + sizeof(uint32_t);
int ret = uncompress( int ret = uncompress(
(Bytef *)d->buf, (Bytef *)d->buf,
@@ -293,8 +386,7 @@ void decompress_pre(Node n, void *decom_ptr, void **carry_out) {
// unset compressed flag // unset compressed flag
header->flags &= ~COMPRESSED_FLAG; header->flags &= ~COMPRESSED_FLAG;
} } else {
else {
// copy record // copy record
size_t record_size = sizeof(Record) + n.header.record->size; size_t record_size = sizeof(Record) + n.header.record->size;
memcpy(d->buf, n.header.record, record_size); memcpy(d->buf, n.header.record, record_size);
@@ -327,7 +419,8 @@ void decompress_pre(Node n, void *decom_ptr, void **carry_out) {
* based on the difference between the current destination pointer and the * based on the difference between the current destination pointer and the
* group header pointer. * group header pointer.
*/ */
void decompress_post(Node n, void *decom_ptr, void **carry_in) { void decompress_post(Node n, void *decom_ptr, void **carry_in)
{
struct decom *d = decom_ptr; struct decom *d = decom_ptr;
// only need to handle group resize // only need to handle group resize
@@ -338,8 +431,10 @@ void decompress_post(Node n, void *decom_ptr, void **carry_in) {
} }
} }
void print_group_header(Group *header) { void print_group_header(Group *header)
{
assert(header->type < GTS_SIZE); assert(header->type < GTS_SIZE);
// Guess at enough with significant margin // Guess at enough with significant margin
char buf[1024] = { 0 }; char buf[1024] = { 0 };
const struct str_buf sb_pre = { .buf = buf, .size = sizeof(buf) }; const struct str_buf sb_pre = { .buf = buf, .size = sizeof(buf) };
@@ -373,14 +468,16 @@ void print_group_header(Group *header) {
sb_write(STDOUT_FILENO, sb_pre, sb); sb_write(STDOUT_FILENO, sb_pre, sb);
} }
void litcopy(struct str_buf *sb, struct str_lit lit) { void litcopy(struct str_buf *sb, struct str_lit lit)
{
assert(sb->size >= lit.size); assert(sb->size >= lit.size);
memcpy(sb->buf, lit.lit, lit.size); memcpy(sb->buf, lit.lit, lit.size);
sb->size -= lit.size; sb->size -= lit.size;
sb->buf += lit.size; sb->buf += lit.size;
} }
void num_str(struct str_buf *sb, unsigned long num, int radix) { void num_str(struct str_buf *sb, unsigned long num, int radix)
{
errno_t ret = _ultoa_s(num, sb->buf, sb->size, radix); errno_t ret = _ultoa_s(num, sb->buf, sb->size, radix);
assert(ret == 0); assert(ret == 0);
int len = (int)strlen(sb->buf); int len = (int)strlen(sb->buf);
@@ -388,7 +485,8 @@ void num_str(struct str_buf *sb, unsigned long num, int radix) {
sb->buf += len; sb->buf += len;
} }
void type_str(struct str_buf *sb, Type4 type) { void type_str(struct str_buf *sb, Type4 type)
{
assert(sb->size >= 4); assert(sb->size >= 4);
for (size_t i = 0; i != 4; i++) for (size_t i = 0; i != 4; i++)
sb->buf[i] = type.bytes[i]; sb->buf[i] = type.bytes[i];
@@ -396,7 +494,8 @@ void type_str(struct str_buf *sb, Type4 type) {
sb->size -= 4; sb->size -= 4;
} }
void group_label_str(struct str_buf *sb, Group *header) { void group_label_str(struct str_buf *sb, Group *header)
{
switch (header->type) { switch (header->type) {
case GT_TOP: case GT_TOP:
type_str(sb, header->label.type); type_str(sb, header->label.type);
@@ -407,8 +506,9 @@ void group_label_str(struct str_buf *sb, Group *header) {
break; break;
case GT_EXTERIOR_CELL_BLOCK: case GT_EXTERIOR_CELL_BLOCK:
case GT_EXTERIOR_CELL_SUBBLOCK: case GT_EXTERIOR_CELL_SUBBLOCK:
litcopy(sb, LIT("X: ")); num_str(sb, header->label.coord[1], 10); uint16_t x = header->label.coord[1], y = header->label.coord[0];
litcopy(sb, LIT("Y: ")); num_str(sb, header->label.coord[0], 10); litcopy(sb, LIT("X: ")); num_str(sb, x, 10);
litcopy(sb, LIT("Y: ")); num_str(sb, y, 10);
break; break;
case GT_WORLD_CHILDREN: case GT_WORLD_CHILDREN:
case GT_CELL_CHILDREN: case GT_CELL_CHILDREN:
@@ -424,20 +524,27 @@ void group_label_str(struct str_buf *sb, Group *header) {
} }
} }
void timestamp_str(struct str_buf *sb, uint16_t timestamp) { void timestamp_str(struct str_buf *sb, uint16_t timestamp)
{
Timestamp ts = convert_ts(timestamp); Timestamp ts = convert_ts(timestamp);
litcopy(sb, LIT("20x")); num_str(sb, ts.year, 10); litcopy(sb, LIT("-")); litcopy(sb, LIT("20x"));
num_str(sb, ts.month, 10); litcopy(sb, LIT("-")); num_str(sb, ts.day, 10); num_str(sb, ts.year, 10);
litcopy(sb, LIT("-"));
num_str(sb, ts.month, 10);
litcopy(sb, LIT("-"));
num_str(sb, ts.day, 10);
} }
void sb_write(int fp, struct str_buf sb_pre, struct str_buf sb_post) { void sb_write(int fp, struct str_buf sb_pre, struct str_buf sb_post)
{
int size = sb_pre.size - sb_post.size; int size = sb_pre.size - sb_post.size;
assert(size >= 0); assert(size >= 0);
int ret = _write(fp, sb_pre.buf, size); int ret = _write(fp, sb_pre.buf, size);
assert(ret == size); assert(ret == size);
} }
void print_record_header(Record *header) { void print_record_header(Record *header)
{
char buf[1024] = { 0 }; char buf[1024] = { 0 };
const struct str_buf sb_pre = { .buf = buf, .size = sizeof(buf) }; const struct str_buf sb_pre = { .buf = buf, .size = sizeof(buf) };
struct str_buf sb = sb_pre; struct str_buf sb = sb_pre;
@@ -466,7 +573,8 @@ void print_record_header(Record *header) {
sb_write(STDOUT_FILENO, sb_pre, sb); sb_write(STDOUT_FILENO, sb_pre, sb);
} }
void record_flags_str(struct str_buf *sb, Record *header) { void record_flags_str(struct str_buf *sb, Record *header)
{
uint32_t flags = header->flags; uint32_t flags = header->flags;
const uint32_t type = header->type.uint; const uint32_t type = header->type.uint;
@@ -475,21 +583,26 @@ void record_flags_str(struct str_buf *sb, Record *header) {
// TODO // TODO
// REFR requires FormID lookup // REFR requires FormID lookup
flags = 0; flags = 0;
} } else {
else {
rfs_inner *const flag_lut = rfs[rt_hash(type)]; rfs_inner *const flag_lut = rfs[rt_hash(type)];
if (flag_lut) { if (flag_lut) {
while (flags != 0) { while (flags != 0) {
// get next flag, from lowest bit to highest
// will always be >= 0 as flags is not 0 // will always be >= 0 as flags is not 0
int highest = 31 - __lzcnt(flags); int lowest = _tzcnt_u32(flags);
assert(highest >= 0); assert(lowest < 32);
const struct str_lit lit = (*flag_lut)[highest];
if (lit.lit) { // get flag string
litcopy(sb, LIT("\n - ")); litcopy(sb, lit); const struct str_lit lit = (*flag_lut)[lowest];
flags -= ((uint32_t)1) << highest;
} // not a valid flag
else if (!lit.lit)
break; break;
// copy flag string
litcopy(sb, LIT("\n - ")); litcopy(sb, lit);
// remove flag from to be processed
flags ^= ((uint32_t)1) << lowest;
} }
} }
} }
@@ -508,7 +621,8 @@ void record_flags_str(struct str_buf *sb, Record *header) {
* This currently handles the timestamp format used in Skyrim.esm, but newer * This currently handles the timestamp format used in Skyrim.esm, but newer
* files apparently use a different format. This will need to be handled later. * files apparently use a different format. This will need to be handled later.
*/ */
Timestamp convert_ts(uint16_t ts) { Timestamp convert_ts(uint16_t ts)
{
/* /*
const uint8_t day = (uint8_t)(ts & day_mask); const uint8_t day = (uint8_t)(ts & day_mask);
const uint8_t month = (uint8_t)((ts >> month_offset) & month_mask); const uint8_t month = (uint8_t)((ts >> month_offset) & month_mask);
@@ -523,3 +637,40 @@ Timestamp convert_ts(uint16_t ts) {
return (Timestamp) { year, month, day }; return (Timestamp) { year, month, day };
} }
MetaNode *espr_create_tree(struct sized_buf in, struct sized_buf tree)
{
const struct sized_buf tree_pre = tree;
struct walker_callbacks cb = { .pre = create_tree_cb, .data = &tree };
espr_walk(in.data, in.size, cb);
return (MetaNode *)tree_pre.data;
}
void create_tree_cb(
const Node n,
void *data,
void **carry_out,
void *from_parent,
void **to_children
) {
(void)carry_out;
// add new metanode to tree
struct sized_buf *tree = data;
assert(tree->size >= sizeof(MetaNode));
MetaNode *m = (MetaNode *)tree->data;
tree->data += sizeof(MetaNode);
tree->size -= sizeof(MetaNode);
MetaNode *p = from_parent;
// construct new node
m->n = n;
m->parent = p;
m->prev = p->last_child;
p->last_child->next = m;
p->last_child = m;
// send self to children
*to_children = m;
}

View File

@@ -24,7 +24,7 @@
bool buf[NUM]; bool buf[NUM];
int main() { int main(void) {
uint32_t seed = 1; uint32_t seed = 1;
bool clash; bool clash;
size_t max = 0; size_t max = 0;