Reformatting.

This commit is contained in:
2022-09-06 18:45:31 +10:00
parent 7063f4b763
commit 5471bcf102
5 changed files with 960 additions and 955 deletions

View File

@@ -11,38 +11,38 @@
#include <assert.h> #include <assert.h>
int main(void) { 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");
if (ret || !fp) if (ret || !fp)
return ret; return ret;
fseek(fp, 0L, SEEK_END); fseek(fp, 0L, SEEK_END);
size_t size = ftell(fp); size_t size = ftell(fp);
rewind(fp); rewind(fp);
char *buffer = malloc(size); char *buffer = malloc(size);
if (!buffer) if (!buffer)
return errno; return errno;
size_t read = fread(buffer, sizeof(char), size, fp); size_t read = fread(buffer, sizeof(char), size, fp);
assert(read == size); assert(read == size);
size_t dc_size = espr_decompressed_size(buffer, size); size_t dc_size = espr_decompressed_size(buffer, size);
char *decompressed = malloc(dc_size);
if (!decompressed)
return errno;
espr_decompress(buffer, size, decompressed, dc_size); char *decompressed = malloc(dc_size);
if (!decompressed)
return errno;
free(buffer); espr_decompress(buffer, size, decompressed, dc_size);
size_t formid_count = espr_formid_count(decompressed, dc_size); free(buffer);
printf("FormID Count: %zu\n", formid_count); size_t formid_count = espr_formid_count(decompressed, dc_size);
free(decompressed); printf("FormID Count: %zu\n", formid_count);
return 0; free(decompressed);
return 0;
} }

View File

@@ -26,7 +26,7 @@
#include <stdint.h> #include <stdint.h>
#include "msh.h" #include "msh.h"
// Guards for C++ usage // Guards for C++ usage
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@@ -38,7 +38,7 @@ extern "C" {
/* RT hash seed was externally calculated s.t. the fourcc codes perfectly hash into /* RT hash seed was externally calculated s.t. the fourcc codes perfectly hash into
* indices between 0 and 511. That is, there are no hashing collisions. This allows * indices between 0 and 511. That is, there are no hashing collisions. This allows
* for hard coded lookup tables for the fourcc codes in a relatively small space. * for hard coded lookup tables for the fourcc codes in a relatively small space.
* *
* A minimal perfect hash is also possible with an intermediate seed table, though * A minimal perfect hash is also possible with an intermediate seed table, though
* I'm not sure which is faster, if it's worth trying to speed this up, etc. * I'm not sure which is faster, if it's worth trying to speed this up, etc.
*/ */
@@ -52,219 +52,216 @@ extern "C" {
#define GTS_SIZE 10 #define GTS_SIZE 10
// //
// === FORWARD DEFS === // === FORWARD DEFS ===
// //
typedef union type4 Type4; typedef union type4 Type4;
typedef struct timestamp Timestamp; typedef struct timestamp Timestamp;
typedef struct group Group; typedef struct group Group;
typedef struct record Record; typedef struct record Record;
typedef struct field Field; typedef struct field Field;
// //
// === SIMPLE TYPES === // === SIMPLE TYPES ===
// //
// Basic types typedef uint32_t formid;
typedef uint32_t formid;
// char[4] with uint32_t access // char[4] with uint32_t access
union type4 { union type4 {
char bytes[4]; char bytes[4];
uint32_t uint; uint32_t uint;
}; };
// indexed by flag bit // indexed by flag bit
typedef const char *const rfs_inner[RFS_INNER_SIZE]; typedef const char *const rfs_inner[RFS_INNER_SIZE];
// //
// === ENUMS === // === ENUMS ===
// //
// Tag for generic node tagged union // Tag for generic node tagged union
enum node_type { // NT_ prefix enum node_type { // NT_ prefix
NT_GROUP, NT_GROUP,
NT_RECORD, NT_RECORD,
}; };
// Record type enum // Record type enum
enum record_type { enum record_type {
_NONE, AACT, ACHR, ACTI, ADDN, ALCH, AMMO,
AACT, ACHR, ACTI, ADDN, ALCH, AMMO, ANIO, APPA, ARMA, ARMO, ARTO, ASPC,
ANIO, APPA, ARMA, ARMO, ARTO, ASPC, ASTP, AVIF, BOOK, BPTD, CAMS, CELL,
ASTP, AVIF, BOOK, BPTD, CAMS, CELL, CLAS, CLDC, CLFM, CLMT, COBJ, COLL,
CLAS, CLDC, CLFM, CLMT, COBJ, COLL, CONT, CPTH, CSTY, DEBR, DIAL, DLBR,
CONT, CPTH, CSTY, DEBR, DIAL, DLBR, DLVW, DOBJ, DOOR, DUAL, ECZN, EFSH,
DLVW, DOBJ, DOOR, DUAL, ECZN, EFSH, ENCH, EQUP, EXPL, EYES, FACT, FLOR,
ENCH, EQUP, EXPL, EYES, FACT, FLOR, FLST, FSTP, FSTS, FURN, GLOB, GMST,
FLST, FSTP, FSTS, FURN, GLOB, GMST, GRAS, GRUP, HAIR, HAZD, HDPT, IDLE,
GRAS, GRUP, HAIR, HAZD, HDPT, IDLE, IDLM, IMAD, IMGS, INFO, INGR, IPCT,
IDLM, IMAD, IMGS, INFO, INGR, IPCT, IPDS, KEYM, KYWD, LAND, LCRT, LCTN,
IPDS, KEYM, KYWD, LAND, LCRT, LCTN, LGTM, LIGH, LSCR, LTEX, LVLI, LVLN,
LGTM, LIGH, LSCR, LTEX, LVLI, LVLN, LVSP, MATO, MATT, MESG, MGEF, MISC,
LVSP, MATO, MATT, MESG, MGEF, MISC, MOVT, MSTT, MUSC, MUST, NAVI, NAVM,
MOVT, MSTT, MUSC, MUST, NAVI, NAVM, NOTE, NPC_, OTFT, PACK, PERK, PGRE,
NOTE, NPC_, OTFT, PACK, PERK, PGRE, PHZD, PROJ, PWAT, QUST, RACE, REFR,
PHZD, PROJ, PWAT, QUST, RACE, REFR, REGN, RELA, REVB, RFCT, RGDL, SCEN,
REGN, RELA, REVB, RFCT, RGDL, SCEN, SCOL, SCPT, SCRL, SHOU, SLGM, SMBN,
SCOL, SCPT, SCRL, SHOU, SLGM, SMBN, SMEN, SMQN, SNCT, SNDR, SOPM, SOUN,
SMEN, SMQN, SNCT, SNDR, SOPM, SOUN, SPEL, SPGD, STAT, TACT, TES4, TREE,
SPEL, SPGD, STAT, TACT, TES4, TREE, TXST, VTYP, WATR, WEAP, WOOP, WRLD,
TXST, VTYP, WATR, WEAP, WOOP, WRLD, WTHR,
WTHR, };
};
// GRUP type values // GRUP type values
enum group_type { // GT_ prefix enum group_type { // GT_ prefix
GT_TOP = 0, GT_TOP = 0,
GT_WORLD_CHILDREN = 1, GT_WORLD_CHILDREN = 1,
GT_INTERIOR_CELL_BLOCK = 2, GT_INTERIOR_CELL_BLOCK = 2,
GT_INTERIOR_CELL_SUBBLOCK = 3, GT_INTERIOR_CELL_SUBBLOCK = 3,
GT_EXTERIOR_CELL_BLOCK = 4, GT_EXTERIOR_CELL_BLOCK = 4,
GT_EXTERIOR_CELL_SUBBLOCK = 5, GT_EXTERIOR_CELL_SUBBLOCK = 5,
GT_CELL_CHILDREN = 6, GT_CELL_CHILDREN = 6,
GT_TOPIC_CHILDREN = 7, GT_TOPIC_CHILDREN = 7,
GT_CELL_PERSISTENT_CHILDREN = 8, GT_CELL_PERSISTENT_CHILDREN = 8,
GT_CELL_TEMPORARY_CHILDREN = 9, GT_CELL_TEMPORARY_CHILDREN = 9,
}; };
// //
// === COMPOSITE TYPES === // === COMPOSITE TYPES ===
// //
// Generic node // Generic node
typedef struct node Node; typedef struct node Node;
struct node { struct node {
union { union {
Group *group; Group *group;
Record *record; Record *record;
} header; } header;
char *const data; char *const data;
enum node_type type; enum node_type type;
uint32_t _pad; uint32_t _pad;
}; };
// calculated timestamp // calculated timestamp
struct timestamp { struct timestamp {
uint16_t year; uint16_t year;
uint8_t month; uint8_t month;
uint8_t day; uint8_t day;
}; };
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 (*post)(Node n, void *data, void **carry_in); void (*post)(Node n, void *data, void **carry_in);
void *data; void *data;
}; };
// //
// === BINARY DATA OVERLAYS === // === BINARY DATA OVERLAYS ===
// //
#pragma pack(push, 1) #pragma pack(push, 1)
// Group header overlay // Group header overlay
struct group { struct group {
Type4 grup; // always RT_GRUP Type4 grup; // always RT_GRUP
uint32_t size; // uncludes the 24 byte group header uint32_t size; // uncludes the 24 byte group header
union { union {
Type4 type; // this may be mangled and should not be relied on Type4 type; // this may be mangled and should not be relied on
formid formid; formid formid;
int32_t number; int32_t number;
int16_t coord[2]; int16_t coord[2];
} label; // access determined by the `type` below } label; // access determined by the `type` below
int32_t type; // group_type enum int32_t type; // group_type enum
uint16_t timestamp; uint16_t timestamp;
uint16_t vcinfo; uint16_t vcinfo;
uint32_t unknown; uint32_t unknown;
}; };
// Record header overlay // Record header overlay
struct record { struct record {
Type4 type; Type4 type;
uint32_t size; uint32_t size;
uint32_t flags; uint32_t flags;
uint32_t formid; uint32_t formid;
uint16_t timestamp; uint16_t timestamp;
uint16_t vcinfo; uint16_t vcinfo;
uint16_t version; uint16_t version;
uint16_t unknown; uint16_t unknown;
}; };
// Field header overlay // Field header overlay
struct field { struct field {
Type4 type; Type4 type;
uint16_t size; uint16_t size;
}; };
#pragma pack(pop) #pragma pack(pop)
// //
// === LUTs === // === LUTs ===
// //
// record type enum to fourcc value // record type enum to fourcc value
extern const uint32_t rt[RT_SIZE]; extern const uint32_t rt[RT_SIZE];
// for converting between record_type and record_type_hash enums // for converting between record_type and record_type_hash enums
extern const uint16_t rt2rth[RT_SIZE]; extern const uint16_t rt2rth[RT_SIZE];
extern const uint8_t rth2rt[RT_HASH_SIZE]; extern const uint8_t rth2rt[RT_HASH_SIZE];
// type -> flag mappings // type -> flag mappings
// NULL table pointers indicate no flags // NULL table pointers indicate no flags
// NULL string pointers indicate invalid flag // NULL string pointers indicate invalid flag
extern rfs_inner *const rfs[RT_HASH_SIZE]; extern rfs_inner *const rfs[RT_HASH_SIZE];
extern rfs_inner *const rfs_refr[RT_HASH_SIZE]; extern rfs_inner *const rfs_refr[RT_HASH_SIZE];
// Expected (probably) order of top level groups in an esp/esm // Expected (probably) order of top level groups in an esp/esm
extern const enum record_type group_order[GO_SIZE]; extern const enum record_type group_order[GO_SIZE];
// Printable strings for group types // Printable strings for group types
extern const char *const group_type_strings[GTS_SIZE]; extern const char *const group_type_strings[GTS_SIZE];
//
// === FUNCTIONS ===
//
// // hashes type value into RT_ hash value
// === FUNCTIONS === inline uint32_t rt_hash(uint32_t type) {
// return uint32_t_msh(type, RT_HASH_BITS, RT_HASH_SEED);
}
// hashes type value into RT_ hash value /* `espr_walk` walks through the tree structure of the esp/esm binary data
inline uint32_t rt_hash(uint32_t type) { * starting at `data` of `size` bytes.
return uint32_t_msh(type, RT_HASH_BITS, RT_HASH_SEED); *
} * `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, struct walker_callbacks cb);
/* `espr_walk` walks through the tree structure of the esp/esm binary data /* `espr_print` prints the header of every group and record in the given
* starting at `data` of `size` bytes. * esp/esm binary data.
* */
* `cb` is a callback that takes a `Node` to process. `pt` is a pointer to void espr_print(char *data, size_t size);
* 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, struct walker_callbacks cb);
/* `espr_print` prints the header of every group and record in the given /* Calculates the size of the esp data if all of the compressed records are
* esp/esm binary data. * decompressed.
*/ */
void espr_print(char *data, size_t size); size_t espr_decompressed_size(char *data, size_t size);
/* Calculates the size of the esp data if all of the compressed records are /* Counts the number of formids present in the esp/esm data. This should be
* decompressed. * equal to the number of records.
*/ */
size_t espr_decompressed_size(char *data, size_t size); size_t espr_formid_count(char *data, size_t size);
/* Counts the number of formids present in the esp/esm data. This should be /* Copies the data from `data` to `buf` decompressing compressed fields as
* equal to the number of records. * it does so. buf_size should be the value returned from `espr_decompressed_size`,
*/ * and `buf` should be at least of that size.
size_t espr_formid_count(char *data, size_t size); */
void espr_decompress(char *data, size_t size, char *buf, size_t buf_size);
/* Copies the data from `data` to `buf` decompressing compressed fields as
* it does so. buf_size should be the value returned from `espr_decompressed_size`,
* and `buf` should be at least of that size.
*/
void espr_decompress(char *data, size_t size, char *buf, size_t buf_size);
// End C++ guard // End C++ guard
#ifdef __cplusplus #ifdef __cplusplus

File diff suppressed because it is too large Load Diff

View File

@@ -60,83 +60,87 @@ void decompress_post(Node n, void *data, void **carry_in);
// //
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 in 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 assert(sizeof(Field) == 6); // Field struct incorrect size
assert(sizeof(uLongf) == sizeof(uint32_t)); // zlib compatability
// zlib compatability
assert(sizeof(uLongf) == sizeof(uint32_t));
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();
char *data_start = data; 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]);
data = walk_concat(data, size, cb); data = walk_concat(data, size, cb);
assert(data == data_start + size); assert(data == data_start + size);
} }
void espr_print(char *data, size_t size) { void espr_print(char *data, size_t size) {
struct walker_callbacks cb = { .pre = print_callback }; struct walker_callbacks cb = { .pre = print_callback };
espr_walk(data, size, cb); espr_walk(data, size, cb);
} }
void print_callback(Node n, void *data, void **carry_out) { void print_callback(Node n, void *data, void **carry_out) {
(void)data; (void)data;
(void)carry_out; (void)carry_out;
switch (n.type) { switch (n.type) {
case NT_GROUP: case NT_GROUP:
print_group_header(n.header.group); print_group_header(n.header.group);
break; break;
case NT_RECORD: case NT_RECORD:
print_record_header(n.header.record); print_record_header(n.header.record);
break; break;
default: default:
assert(false); // invalid node type assert(false); // invalid node type
} }
} }
size_t espr_decompressed_size(char *data, size_t size) { size_t espr_decompressed_size(char *data, size_t size) {
size_t dc_size = 0; size_t dc_size = 0;
struct walker_callbacks cb = { .pre = dc_size_cb, .data = &dc_size }; struct walker_callbacks cb = { .pre = dc_size_cb, .data = &dc_size };
espr_walk(data, size, cb); espr_walk(data, size, cb);
return dc_size; return dc_size;
} }
// Adds the size of every node up, reading decompressed size from compressed records. // Adds the size of every node up, reading decompressed size from compressed records.
void dc_size_cb(Node n, void *data, void **carry_out) { void dc_size_cb(Node n, void *data, void **carry_out) {
(void)carry_out; (void)carry_out;
size_t *dc_size = data; size_t *dc_size = data;
switch (n.type) { switch (n.type) {
case NT_GROUP: case NT_GROUP:
// Only add header size for groups, internals will be walked // Only add header size for groups, internals will be walked
*dc_size += sizeof(Group); *dc_size += sizeof(Group);
break; break;
case NT_RECORD: case NT_RECORD:
// Add the whole record and header, records are leaf-ish // Add the whole record and header, records are leaf-ish
*dc_size += sizeof(Record); *dc_size += sizeof(Record);
if (n.header.record->flags & COMPRESSED_FLAG) { if (n.header.record->flags & COMPRESSED_FLAG) {
// Read decompressed size // Read decompressed size
*dc_size += *((uint32_t *)n.data); *dc_size += *((uint32_t *)n.data);
} else }
*dc_size += n.header.record->size; else
break; *dc_size += n.header.record->size;
default: break;
assert(false); // invalid node type default:
} assert(false); // invalid node type
}
} }
size_t espr_formid_count(char *data, size_t size) { size_t espr_formid_count(char *data, size_t size) {
size_t count = 0; size_t count = 0;
struct walker_callbacks cb = { .pre = formid_count_cb, .data = &count }; struct walker_callbacks cb = { .pre = formid_count_cb, .data = &count };
espr_walk(data, size, cb); espr_walk(data, size, cb);
return count; return count;
} }
/* FormID <-> Record relationship should be bijective. I do not believe /* FormID <-> Record relationship should be bijective. I do not believe
@@ -144,97 +148,97 @@ size_t espr_formid_count(char *data, size_t size) {
* otherwise there would be clashes in the id space. * otherwise there would be clashes in the id space.
*/ */
void formid_count_cb(Node n, void *data, void **carry_out) { void formid_count_cb(Node n, void *data, void **carry_out) {
(void)carry_out; (void)carry_out;
size_t *count = data; size_t *count = data;
if (n.type == NT_RECORD) { if (n.type == NT_RECORD) {
(*count)++; (*count)++;
} }
} }
struct decom { struct decom {
char *buf; char *buf;
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 = { .pre = decompress_pre, .post = decompress_post, .data = &s }; struct walker_callbacks cb = { .pre = decompress_pre, .post = decompress_post, .data = &s };
espr_walk(data, size, cb); espr_walk(data, size, cb);
} }
void decompress_pre(Node n, void *decom_ptr, void **carry_out) { void decompress_pre(Node n, void *decom_ptr, void **carry_out) {
struct decom *d = decom_ptr; struct decom *d = decom_ptr;
switch (n.type) { switch (n.type) {
case NT_RECORD: case NT_RECORD:
// compressed record // compressed record
if (n.header.record->flags & COMPRESSED_FLAG) { if (n.header.record->flags & COMPRESSED_FLAG) {
// copy header // copy header
memcpy(d->buf, n.header.record, sizeof(Record)); memcpy(d->buf, n.header.record, sizeof(Record));
// copied header reference // copied header reference
Record *header = (Record *)d->buf; Record *header = (Record *)d->buf;
// update decom struct // update decom struct
d->remaining -= sizeof(Record); d->remaining -= sizeof(Record);
d->buf += sizeof(Record); d->buf += sizeof(Record);
// decompress directly into buffer // decompress directly into buffer
// 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((Bytef *)d->buf, (uLongf *)&to_copy, (Bytef *)data_start, (uLong)cur_size); int ret = uncompress((Bytef *)d->buf, (uLongf *)&to_copy, (Bytef *)data_start, (uLong)cur_size);
assert(ret == Z_OK); assert(ret == Z_OK);
assert(to_copy == dc_size); assert(to_copy == dc_size);
// update decom struct // update decom struct
d->remaining -= dc_size; d->remaining -= dc_size;
d->buf += dc_size; d->buf += dc_size;
// update header data size // update header data size
header->size = dc_size; header->size = dc_size;
// 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);
// update decom // update decom
d->remaining -= record_size; d->remaining -= record_size;
d->buf += record_size; d->buf += record_size;
} }
break; break;
case NT_GROUP: case NT_GROUP:
// copy header, contents will be copied while walking // copy header, contents will be copied while walking
memcpy(d->buf, n.header.group, sizeof(Group)); memcpy(d->buf, n.header.group, sizeof(Group));
// save copied header location for post-walk group size recalc // save copied header location for post-walk group size recalc
*carry_out = (void *)d->buf; *carry_out = (void *)d->buf;
// update decom // update decom
d->buf += sizeof(Group); d->buf += sizeof(Group);
d->remaining -= sizeof(Group); d->remaining -= sizeof(Group);
break; break;
default: default:
assert(false); // invalid node type assert(false); // invalid node type
} }
} }
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
if (n.type == NT_GROUP) { if (n.type == NT_GROUP) {
Group *g = (Group *)(*carry_in); Group *g = (Group *)(*carry_in);
uint32_t new_size = (uint32_t)((char *)d->buf - (char *)g); uint32_t new_size = (uint32_t)((char *)d->buf - (char *)g);
g->size = new_size; g->size = new_size;
} }
} }
/* Unknown data will be some concatenation of groups and records. /* Unknown data will be some concatenation of groups and records.
@@ -243,196 +247,198 @@ void decompress_post(Node n, void *decom_ptr, void **carry_in) {
* 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) {
const char *end = data + size; const char *end = data + size;
while (data != end) { while (data != end) {
assert(data < end); assert(data < end);
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 // 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);
else else
data = walk_record(data, cb); data = walk_record(data, cb);
} }
return data; return data;
} }
/* Walk a group record. Group records are containers for any other type of record, /* Walk a group record. Group records are containers for any other type of record,
* including other group records. * including other group records.
* *
* This function will also call `cb` with the node constructed from this group record. * This function will also call `cb` with the node constructed from this group record.
*/ */
char *walk_group(char *data, struct walker_callbacks cb) { char *walk_group(char *data, struct walker_callbacks cb) {
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
char *data_start = data + sizeof(Group); char *data_start = data + sizeof(Group);
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 = { .header.group = header, .data = data_start, .type = NT_GROUP };
void *carry; void *carry;
// Pre-walk callback // Pre-walk callback
if (cb.pre) if (cb.pre)
cb.pre(n, cb.data, &carry); cb.pre(n, cb.data, &carry);
// 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);
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) {
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 = { .header.record = header, .data = data_start, .type = NT_RECORD };
void *carry; void *carry;
/* 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 records
* are leaf-ish, will still call both here for now as field walking may be * are leaf-ish, will still call both here for now as field walking may be
* added in the future. * 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);
// 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 print_group_header(Group *header) { void print_group_header(Group *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);
print_group_label(header); print_group_label(header);
assert(header->type < GTS_SIZE); assert(header->type < GTS_SIZE);
printf("Group type: %s\n", group_type_strings[header->type]); printf("Group type: %s\n", group_type_strings[header->type]);
print_timestamp(header->timestamp); print_timestamp(header->timestamp);
printf("Version Control Info: %04x\n", header->vcinfo); printf("Version Control Info: %04x\n", header->vcinfo);
printf("Unknown: %08x\n", header->unknown); printf("Unknown: %08x\n", header->unknown);
} }
void print_record_header(Record *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);
printf("FormID: %x\n", header->formid); printf("FormID: %x\n", header->formid);
print_timestamp(header->timestamp); print_timestamp(header->timestamp);
printf("Version Control Info: %04x\n", header->vcinfo); printf("Version Control Info: %04x\n", header->vcinfo);
printf("Version: %u\n", header->version); printf("Version: %u\n", header->version);
printf("Unknown: %08x\n", header->unknown); printf("Unknown: %08x\n", header->unknown);
} }
void print_group_label(Group *header) { void print_group_label(Group *header) {
printf("Label: "); printf("Label: ");
switch (header->type) { switch (header->type) {
case GT_TOP: case GT_TOP:
print_type4(header->label.type); print_type4(header->label.type);
break; break;
case GT_INTERIOR_CELL_BLOCK: case GT_INTERIOR_CELL_BLOCK:
case GT_INTERIOR_CELL_SUBBLOCK: case GT_INTERIOR_CELL_SUBBLOCK:
printf("%d", header->label.number); printf("%d", header->label.number);
case GT_EXTERIOR_CELL_BLOCK: case GT_EXTERIOR_CELL_BLOCK:
case GT_EXTERIOR_CELL_SUBBLOCK: case GT_EXTERIOR_CELL_SUBBLOCK:
printf("X: %d, Y: %d", header->label.coord[1], header->label.coord[0]); printf("X: %d, Y: %d", header->label.coord[1], header->label.coord[0]);
case GT_WORLD_CHILDREN: case GT_WORLD_CHILDREN:
case GT_CELL_CHILDREN: case GT_CELL_CHILDREN:
case GT_TOPIC_CHILDREN: case GT_TOPIC_CHILDREN:
case GT_CELL_PERSISTENT_CHILDREN: case GT_CELL_PERSISTENT_CHILDREN:
case GT_CELL_TEMPORARY_CHILDREN: case GT_CELL_TEMPORARY_CHILDREN:
printf("FormID[%x]", header->label.formid); printf("FormID[%x]", header->label.formid);
break; break;
default: default:
assert(false); // invalid group type assert(false); // invalid group type
} }
printf("\n"); printf("\n");
} }
void print_record_flags(Record *header) { void print_record_flags(Record *header) {
printf("Flags:\n"); printf("Flags:\n");
uint32_t flags = header->flags; uint32_t flags = header->flags;
const uint32_t type = header->type.uint; const uint32_t type = header->type.uint;
// print flags // print flags
if (type == rt[REFR]) { if (type == rt[REFR]) {
// TODO // TODO
// REFR requires FormID lookup // REFR requires FormID lookup
flags = 0; flags = 0;
} else { }
rfs_inner *const flag_lut = rfs[rt_hash(type)]; else {
if (flag_lut) { rfs_inner *const flag_lut = rfs[rt_hash(type)];
while (flags != 0) { if (flag_lut) {
// will always be >= 0 as flags is not 0 while (flags != 0) {
int highest = 31 - __lzcnt(flags); // will always be >= 0 as flags is not 0
assert(highest >= 0); int highest = 31 - __lzcnt(flags);
const char *const str = (*flag_lut)[highest]; assert(highest >= 0);
if (str) { const char *const str = (*flag_lut)[highest];
printf(" - %s\n", str); if (str) {
flags -= ((uint32_t)1) << highest; printf(" - %s\n", str);
} else flags -= ((uint32_t)1) << highest;
break; }
} else
} break;
} }
}
}
if (flags != 0) { if (flags != 0) {
printf("\n\nOriginal flags: %08x\n", header->flags); printf("\n\nOriginal flags: %08x\n", header->flags);
printf("Unhandled flags: %08x\n", flags); printf("Unhandled flags: %08x\n", flags);
assert(false); // unhandled flags assert(false); // unhandled flags
} }
} }
// This is the Skyrim SE timestamp format // This is the Skyrim SE timestamp format
void print_timestamp(uint16_t _ts) { void print_timestamp(uint16_t _ts) {
Timestamp ts = convert_ts(_ts); Timestamp ts = convert_ts(_ts);
printf("Timestamp: 20x%u-%02u-%02u\n", ts.year, ts.month, ts.day); printf("Timestamp: 20x%u-%02u-%02u\n", ts.year, ts.month, ts.day);
} }
void print_type(Type4 type) { void print_type(Type4 type) {
printf("Type: "); printf("Type: ");
print_type4(type); print_type4(type);
printf("\n"); printf("\n");
} }
void print_type4(Type4 val) { void print_type4(Type4 val) {
// invariant: printed i characters from val.bytes // invariant: printed i characters from val.bytes
for (size_t i = 0; i != 4; i++) for (size_t i = 0; i != 4; i++)
printf("%c", val.bytes[i]); printf("%c", val.bytes[i]);
} }
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);
const uint16_t year = (ts >> year_offset) & year_mask; const uint16_t year = (ts >> year_offset) & year_mask;
*/ */
const uint8_t day = ts & 0xff; const uint8_t day = ts & 0xff;
const uint8_t hb = (ts >> 8) & 0xff; const uint8_t hb = (ts >> 8) & 0xff;
const uint8_t month = ((hb - 1) % 12) + 1; const uint8_t month = ((hb - 1) % 12) + 1;
const uint8_t year = ((hb - 1) / 12 + 3) % 10; const uint8_t year = ((hb - 1) / 12 + 3) % 10;
return (Timestamp){ year, month, day }; return (Timestamp) { year, month, day };
} }

View File

@@ -25,58 +25,60 @@
bool buf[NUM]; bool buf[NUM];
int main() { int main() {
uint32_t seed = 1; uint32_t seed = 1;
bool clash; bool clash;
size_t max = 0; size_t max = 0;
do { do {
if ((seed - 1) % 1000000 == 0) if ((seed - 1) % 1000000 == 0)
printf("Checked up to: %u\r", seed - 1); printf("Checked up to: %u\r", seed - 1);
// reset for test // reset for test
memset(buf, 0, sizeof(bool) * NUM); memset(buf, 0, sizeof(bool) * NUM);
clash = false; clash = false;
// check for collisions // check for collisions
for (size_t i = 0; i != RT_SIZE; i++) { for (size_t i = 0; i != RT_SIZE; i++) {
uint32_t index = uint32_t_msh(rt[i], BITS, seed); uint32_t index = uint32_t_msh(rt[i], BITS, seed);
if (buf[index]) { if (buf[index]) {
if (i > max) { if (i > max) {
// printf("\nMax: %llu\n", i); // printf("\nMax: %llu\n", i);
max = i; max = i;
} }
clash = true; clash = true;
break; break;
} else { }
buf[index] = true; else {
} buf[index] = true;
} }
}
// exit if non-clashing seed found // exit if non-clashing seed found
if (!clash) { if (!clash) {
break; break;
} }
// seed must be odd
seed += 2;
} while (seed != UINT32_MAX); // is odd, so will be hit
if (!clash) { // seed must be odd
printf("\nSeed found: %u", seed); seed += 2;
for (size_t i = 0; i != RT_SIZE; i++) { } while (seed != UINT32_MAX); // is odd, so will be hit
char name[5];
memcpy(name, &rt[i], sizeof(uint32_t));
name[4] = '\0';
uint32_t index = uint32_t_msh(rt[i], BITS, seed);
if (i % PER_LINE == 0)
printf("\n");
else
printf(" ");
printf("RT_%s = %3u,", name, index);
}
} else {
printf("Seed not found. Max: %llu\n", max);
}
return 0; if (!clash) {
printf("\nSeed found: %u", seed);
for (size_t i = 0; i != RT_SIZE; i++) {
char name[5];
memcpy(name, &rt[i], sizeof(uint32_t));
name[4] = '\0';
uint32_t index = uint32_t_msh(rt[i], BITS, seed);
if (i % PER_LINE == 0)
printf("\n");
else
printf(" ");
printf("RT_%s = %3u,", name, index);
}
}
else {
printf("Seed not found. Max: %llu\n", max);
}
return 0;
} }