A little more formatting.

This commit is contained in:
2022-09-08 12:30:59 +10:00
parent ff95aeafbf
commit 2d5c85f0e8
3 changed files with 751 additions and 741 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);
struct esp_stats stats = espr_stats(buffer, size); struct esp_stats stats = espr_stats(buffer, size);
char *decompressed = malloc(stats.decompressed_size); char *decompressed = malloc(stats.decompressed_size);
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);
return 0; return 0;
} }

View File

@@ -37,13 +37,13 @@ extern "C" {
// There are 127 record types + a NONE type used for sanity checks // There are 127 record types + a NONE type used for sanity checks
#define RT_SIZE 128 #define RT_SIZE 128
/* RT hash seed was externally calculated s.t. the fourcc codes perfectly hash /* 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. * into 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 * This allows for hard coded lookup tables for the fourcc codes in a relatively
* small space. * small space.
* *
* A minimal perfect hash is also possible with an intermediate seed table, * 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, * though I'm not sure which is faster, if it's worth trying to speed this up,
* etc. * etc.
*/ */
#define RT_HASH_SIZE 512 #define RT_HASH_SIZE 512
@@ -62,220 +62,229 @@ extern "C" {
#define STR_LIT(L) { .lit = L , .size = sizeof( L ) - 1 } #define STR_LIT(L) { .lit = L , .size = sizeof( L ) - 1 }
// //
// === 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;
typedef struct meta_node MetaNode; typedef struct meta_node MetaNode;
// //
// === SIMPLE TYPES === // === SIMPLE TYPES ===
// //
// 3 byte ID, upper byte is determined at run time and is used to reference // 3 byte ID, upper byte is determined at run time and is used to
// across esp/esm files // reference across esp/esm files
typedef uint32_t formid; typedef uint32_t formid;
// char[4] with uint32_t access, used to access fourcc values // char[4] with uint32_t access, used to access fourcc values
union type4 { union type4 {
char bytes[4]; char bytes[4];
uint32_t uint; uint32_t uint;
}; };
struct str_lit { struct str_lit {
const char *const lit; const char *const lit;
const int size; const int size;
const char _pad[4]; const char _pad[4];
}; };
// Inner type for Record Flag String LUT. Indexed by flag bit. // Inner type for Record Flag String LUT. Indexed by flag bit.
typedef const struct str_lit rfs_inner[RFS_INNER_SIZE]; typedef const struct str_lit rfs_inner[RFS_INNER_SIZE];
struct esp_stats { struct esp_stats {
size_t decompressed_size; size_t decompressed_size;
uint32_t group_count; uint32_t group_count;
uint32_t record_count; uint32_t record_count;
}; };
struct str_buf { struct str_buf {
char *buf; char *buf;
int size; int size;
char _pad[4]; char _pad[4];
}; };
struct sized_buf { struct sized_buf {
char *data; char *data;
size_t size; size_t size;
}; };
// //
// === ENUMS === // === ENUMS ===
// //
// Tag for generic node tagged union // Tag for generic node tagged union
enum node_type { enum node_type {
NT_GROUP, NT_GROUP,
NT_RECORD, NT_RECORD,
}; };
// Record type enum // Record type enum
enum record_type { enum record_type {
NONE, 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,
}; };
// Group type enum // Group type enum
enum group_type { enum group_type {
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 in the esp/esm tree // Generic node in the esp/esm tree
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 *data; char *data;
enum node_type type; enum node_type type;
uint32_t _pad; uint32_t _pad;
}; };
// Used for passing around parsed timestamps // Used for passing around parsed timestamps
struct timestamp { struct timestamp {
uint16_t year; uint16_t year;
uint8_t month; uint8_t month;
uint8_t day; uint8_t day;
}; };
/* Given to espr_walk. /* Given to espr_walk.
* *
* pre is called before the children of the current node have been walked * pre is called before the children of the current node have been
* post is called after the children of the current node have been walked * walked post is called after the children of the current node have
* * been walked
* carry_out and carry_in is a pointer to a void * on the stack that can be *
* used for passing data between pre and post for a node. * carry_out and carry_in is a pointer to a void * on the stack that can
* * be used for passing data between pre and post for a node.
* data is a pointer that the user can supply when calling espr_walk that *
* will be passed to pre and post when they are called. * data is a pointer that the user can supply when calling espr_walk
*/ * that will be passed to pre and post when they are called.
struct walker_callbacks { */
void (*pre)(Node n, void *data, void **carry_out, void *from_parent, void **to_children); struct walker_callbacks {
void (*post)(Node n, void *data, void *carry_in); void (*pre)(
void *data; Node n,
}; void *data,
void **carry_out,
void *from_parent,
void **to_children
);
void (*post)(Node n, void *data, void *carry_in);
void *data;
};
/* Meta Nodes are used for constructing a more flexible tree structure /* Meta Nodes are used for constructing a more flexible tree structure
* on top of the natural structure of ESP/ESM files. * on top of the natural structure of ESP/ESM files.
* *
* Meta Nodes do not create a pure tree structure, rather they have pointers to * Meta Nodes do not create a pure tree structure, rather they have
* their parent and first child, and children have pointers backwards and * pointers to their parent and first child, and children have pointers
* forward through a linked list of all of the children of the parent node. * backwards and forward through a linked list of all of the children of
* * the parent node.
* There is no root node as such, rather there is a root linked list for which *
* all of the Meta Nodes have no parents. * There is no root node as such, rather there is a root linked list for
* * which all of the Meta Nodes have no parents.
* While the ESP/ESM buffer can be modified in-place, any modification that *
* changes the size of the stored data cannot be directly written to the buffer * While the ESP/ESM buffer can be modified in-place, any modification
* without first shifting all of the data after the point of modification. * that changes the size of the stored data cannot be directly written
* * to the buffer without first shifting all of the data after the point
* Modifications that change data size are: * of modification.
* - Adding or deleting a group or record *
* - Adding or deleting a field in a record * Modifications that change data size are:
* - Changing a variable length field with data of different length * - Adding or deleting a group or record
* * - Adding or deleting a field in a record
* With a Meta Node you can instead allocate new, arbitrarily sized memory for * - Changing a variable length field with data of different length
* the node data. The Meta Node tree can then be walked to reconstruct a *
* contiguous view of discontiguous memory. * With a Meta Node you can instead allocate new, arbitrarily sized
*/ * memory for the node data. The Meta Node tree can then be walked to
struct meta_node { * reconstruct a contiguous view of discontiguous memory.
Node n; */
MetaNode *parent; struct meta_node {
MetaNode *first_child; Node n;
MetaNode *last_child; MetaNode *parent;
MetaNode *prev; MetaNode *first_child;
MetaNode *next; MetaNode *last_child;
}; MetaNode *prev;
MetaNode *next;
};
// //
// === 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, do not rely 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)
@@ -284,77 +293,78 @@ extern "C" {
// //
// 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_SIZE]; extern rfs_inner *const rfs[RT_SIZE];
extern rfs_inner *const rfs_refr[RT_SIZE]; extern rfs_inner *const rfs_refr[RT_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 struct str_lit group_type_strings[GTS_SIZE]; extern const struct str_lit group_type_strings[GTS_SIZE];
// //
// === FUNCTIONS === // === FUNCTIONS ===
// //
// hashes type value into a record type enum value // hashes type value into a record type enum value
inline uint32_t rt_hash(uint32_t type) { inline uint32_t rt_hash(uint32_t type) {
return rth2rt[uint32_t_msh(type, RT_HASH_BITS, RT_HASH_SEED)]; return rth2rt[uint32_t_msh(type, RT_HASH_BITS, RT_HASH_SEED)];
} }
/* `espr_walk` walks through the tree structure of the esp/esm binary data /* `espr_walk` walks through the tree structure of the esp/esm binary
* starting at `data` of `size` bytes. * data starting at `data` of `size` bytes.
* *
* `cb` is a callback that takes a `Node` to process. `pt` is a pointer to * `cb` is a callback that takes a `Node` to process. `pt` is a pointer
* arbitrary data that is passed on to `cb` whenever it is called. * to arbitrary data that is passed on to `cb` whenever it is called.
* *
* Data is walked sequentially. Nodes passed to `cb` will be strictly * Data is walked sequentially. Nodes passed to `cb` will be strictly
* increasing in terms of memory location within the buffer. * increasing in terms of memory location within the buffer.
*/ */
void espr_walk(char *data, size_t size, struct walker_callbacks cb); 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 /* `espr_print` prints the header of every group and record in the given
* esp/esm binary data. * esp/esm binary data.
*/ */
void espr_print(char *data, size_t size); void espr_print(char *data, size_t size);
/* Calculates the number of groups and records in the esp/esm file and the /* Calculates the number of groups and records in the esp/esm file and
* size of the esp/esm if all of the compressed records were decompressed. * the size of the esp/esm if all of the compressed records were
*/ * decompressed.
struct esp_stats espr_stats(char *data, size_t size); */
struct esp_stats espr_stats(char *data, size_t size);
// Calculates the number of formid's in an esm/esp from the stats // Calculates the number of formid's in an esm/esp from the stats
inline uint32_t espr_formid_count(struct esp_stats stats) { inline uint32_t espr_formid_count(struct esp_stats stats) {
return stats.record_count; return stats.record_count;
} }
// Calculates the number of nodes in the esp/esm from the stats // Calculates the number of nodes in the esp/esm from the stats
inline uint32_t espr_node_count(struct esp_stats stats) { inline uint32_t espr_node_count(struct esp_stats stats) {
return stats.record_count + stats.group_count; return stats.record_count + stats.group_count;
} }
// Calculates the size of a MetaNode tree constructed over the esp/esm for // Calculates the size of a MetaNode tree constructed over the esp/esm
// which the stats were generated. // for which the stats were generated.
inline size_t espr_tree_size(struct esp_stats stats) { inline size_t espr_tree_size(struct esp_stats stats) {
return sizeof(MetaNode) * espr_node_count(stats); return sizeof(MetaNode) * espr_node_count(stats);
} }
/* Copies the data from `data` to `buf` decompressing compressed fields as /* Copies the data from `data` to `buf` decompressing compressed fields
* it does so. buf_size should be the value returned from * as it does so. buf_size should be the value returned from
* `espr_decompressed_size`, and `buf` should be at least of that size. * `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); 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); MetaNode *espr_create_tree(struct sized_buf in, struct sized_buf tree);
// End C++ guard // End C++ guard
#ifdef __cplusplus #ifdef __cplusplus

File diff suppressed because it is too large Load Diff