Fixed a bug in espr_stats where the esp_stats structure was not being properly initialised.
Also, significantly sped up the header printing functions.
This commit is contained in:
@@ -58,6 +58,9 @@ extern "C" {
|
||||
// There are 10 Group Types
|
||||
#define GTS_SIZE 10
|
||||
|
||||
#define LIT(L) (const struct str_lit) STR_LIT(L)
|
||||
#define STR_LIT(L) { .lit = L , .size = sizeof( L ) - 1 }
|
||||
|
||||
|
||||
//
|
||||
// === FORWARD DEFS ===
|
||||
@@ -68,6 +71,8 @@ extern "C" {
|
||||
typedef struct group Group;
|
||||
typedef struct record Record;
|
||||
typedef struct field Field;
|
||||
typedef struct meta_node MetaNode;
|
||||
|
||||
|
||||
//
|
||||
// === SIMPLE TYPES ===
|
||||
@@ -83,8 +88,27 @@ extern "C" {
|
||||
uint32_t uint;
|
||||
};
|
||||
|
||||
struct str_lit {
|
||||
const char *const lit;
|
||||
const int size;
|
||||
const char _pad[4];
|
||||
};
|
||||
|
||||
// Inner type for Record Flag String LUT. Indexed by flag bit.
|
||||
typedef const char *const rfs_inner[RFS_INNER_SIZE];
|
||||
typedef const struct str_lit rfs_inner[RFS_INNER_SIZE];
|
||||
|
||||
struct esp_stats {
|
||||
size_t decompressed_size;
|
||||
uint32_t group_count;
|
||||
uint32_t record_count;
|
||||
};
|
||||
|
||||
struct str_buf {
|
||||
char *buf;
|
||||
int size;
|
||||
char _pad[4];
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// === ENUMS ===
|
||||
@@ -177,6 +201,37 @@ extern "C" {
|
||||
void *data;
|
||||
};
|
||||
|
||||
/* Meta Nodes are used for constructing a more flexible tree structure
|
||||
* on top of the natural structure of ESP/ESM files.
|
||||
*
|
||||
* Meta Nodes do not create a pure tree structure, rather they have pointers to
|
||||
* their parent and first child, and children have pointers 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.
|
||||
*
|
||||
* 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
|
||||
* without first shifting all of the data after the point of modification.
|
||||
*
|
||||
* Modifications that change data size are:
|
||||
* - Adding or deleting a group or record
|
||||
* - Adding or deleting a field in a record
|
||||
* - Changing a variable length field with data of different length
|
||||
*
|
||||
* 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 reconstruct a
|
||||
* contiguous view of discontiguous memory.
|
||||
*/
|
||||
struct meta_node {
|
||||
Node n;
|
||||
MetaNode *parent;
|
||||
MetaNode *child;
|
||||
MetaNode *prev;
|
||||
MetaNode *next;
|
||||
};
|
||||
|
||||
//
|
||||
// === BINARY DATA OVERLAYS ===
|
||||
//
|
||||
@@ -240,7 +295,7 @@ extern "C" {
|
||||
extern const enum record_type group_order[GO_SIZE];
|
||||
|
||||
// Printable strings for group types
|
||||
extern const char *const group_type_strings[GTS_SIZE];
|
||||
extern const struct str_lit group_type_strings[GTS_SIZE];
|
||||
|
||||
//
|
||||
// === FUNCTIONS ===
|
||||
@@ -267,15 +322,26 @@ extern "C" {
|
||||
*/
|
||||
void espr_print(char *data, size_t size);
|
||||
|
||||
/* Calculates the size of the esp data if all of the compressed records are
|
||||
* decompressed.
|
||||
/* Calculates the number of groups and records in the esp/esm file and the
|
||||
* size of the esp/esm if all of the compressed records were decompressed.
|
||||
*/
|
||||
size_t espr_decompressed_size(char *data, size_t size);
|
||||
struct esp_stats espr_stats(char *data, size_t size);
|
||||
|
||||
/* Counts the number of formids present in the esp/esm data. This should be
|
||||
* equal to the number of records.
|
||||
*/
|
||||
size_t espr_formid_count(char *data, size_t size);
|
||||
// Calculates the number of formid's in an esm/esp from the stats
|
||||
inline uint32_t espr_formid_count(struct esp_stats stats) {
|
||||
return stats.record_count;
|
||||
}
|
||||
|
||||
// Calculates the number of nodes in the esp/esm from the stats
|
||||
inline uint32_t espr_node_count(struct esp_stats stats) {
|
||||
return stats.record_count + stats.group_count;
|
||||
}
|
||||
|
||||
// Calculates the size of a MetaNode tree constructed over the esp/esm for
|
||||
// which the stats were generated.
|
||||
inline size_t espr_tree_size(struct esp_stats stats) {
|
||||
return sizeof(MetaNode) * espr_node_count(stats);
|
||||
}
|
||||
|
||||
/* Copies the data from `data` to `buf` decompressing compressed fields as
|
||||
* it does so. buf_size should be the value returned from
|
||||
@@ -283,6 +349,8 @@ extern "C" {
|
||||
*/
|
||||
void espr_decompress(char *data, size_t size, char *buf, size_t buf_size);
|
||||
|
||||
|
||||
|
||||
// End C++ guard
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0.If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http ://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ESPReader.h"
|
||||
|
||||
typedef struct meta_node MetaNode;
|
||||
|
||||
/* Meta Nodes are used for constructing a more flexible tree structure
|
||||
* on top of the natural structure of ESP/ESM files.
|
||||
*
|
||||
* Meta Nodes do not create a pure tree structure, rather they have pointers to
|
||||
* their parent and first child, and children have pointers 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.
|
||||
*
|
||||
* 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
|
||||
* without first shifting all of the data after the point of modification.
|
||||
*
|
||||
* Modifications that change data size are:
|
||||
* - Adding or deleting a group or record
|
||||
* - Adding or deleting a field in a record
|
||||
* - Changing a variable length field with data of different length
|
||||
*
|
||||
* 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 reconstruct a
|
||||
* contiguous view of discontiguous memory.
|
||||
*/
|
||||
struct meta_node {
|
||||
Node n;
|
||||
MetaNode *parent;
|
||||
MetaNode *child;
|
||||
MetaNode *prev;
|
||||
MetaNode *next;
|
||||
};
|
||||
|
||||
408
espReader/LUT.c
408
espReader/LUT.c
@@ -209,76 +209,76 @@ const uint8_t rth2rt[RT_HASH_SIZE] = {
|
||||
[RT_WTHR] = WTHR,
|
||||
};
|
||||
|
||||
const char *const group_type_strings[GTS_SIZE] = {
|
||||
"Top Type",
|
||||
"World Children",
|
||||
"Interior Cell Block",
|
||||
"Interior Cell Sub-Block",
|
||||
"Exterior Cell",
|
||||
"Exterior Cell Sub-Block",
|
||||
"Cell Children",
|
||||
"Topic Children",
|
||||
"Cell Persistent Children",
|
||||
"Cell Temporary Children",
|
||||
const struct str_lit group_type_strings[GTS_SIZE] = {
|
||||
STR_LIT("Top Type"),
|
||||
STR_LIT("World Children"),
|
||||
STR_LIT("Interior Cell Block"),
|
||||
STR_LIT("Interior Cell Sub-Block"),
|
||||
STR_LIT("Exterior Cell"),
|
||||
STR_LIT("Exterior Cell Sub-Block"),
|
||||
STR_LIT("Cell Children"),
|
||||
STR_LIT("Topic Children"),
|
||||
STR_LIT("Cell Persistent Children"),
|
||||
STR_LIT("Cell Temporary Children"),
|
||||
};
|
||||
|
||||
// Non-REFR flags
|
||||
rfs_inner achr = {
|
||||
[9] = "Starts Dead",
|
||||
[10] = "Persistent",
|
||||
[11] = "Initially Disabled",
|
||||
[25] = "No AI Acquire",
|
||||
[29] = "Don't Havok Settle",
|
||||
[30] = "ACHR Unknown 30",
|
||||
[9] = STR_LIT("Starts Dead"),
|
||||
[10] = STR_LIT("Persistent"),
|
||||
[11] = STR_LIT("Initially Disabled"),
|
||||
[25] = STR_LIT("No AI Acquire"),
|
||||
[29] = STR_LIT("Don't Havok Settle"),
|
||||
[30] = STR_LIT("ACHR Unknown 30"),
|
||||
};
|
||||
|
||||
rfs_inner acti = {
|
||||
[6] = "Has Tree LOD",
|
||||
[8] = "Must Update Anims",
|
||||
[9] = "Hidden From Local Map",
|
||||
[15] = "Has Distant LOD",
|
||||
[16] = "Random Anim Start",
|
||||
[17] = "Dangerous",
|
||||
[20] = "Ignore Object Interaction",
|
||||
[23] = "Is Marker",
|
||||
[25] = "Obstacle",
|
||||
[26] = "NavMesh Generation - Filter",
|
||||
[27] = "NavMesh Generation - Bounding Box",
|
||||
[29] = "Child Can Use",
|
||||
[30] = "NavMesh Generation - Ground",
|
||||
[6] = STR_LIT("Has Tree LOD"),
|
||||
[8] = STR_LIT("Must Update Anims"),
|
||||
[9] = STR_LIT("Hidden From Local Map"),
|
||||
[15] = STR_LIT("Has Distant LOD"),
|
||||
[16] = STR_LIT("Random Anim Start"),
|
||||
[17] = STR_LIT("Dangerous"),
|
||||
[20] = STR_LIT("Ignore Object Interaction"),
|
||||
[23] = STR_LIT("Is Marker"),
|
||||
[25] = STR_LIT("Obstacle"),
|
||||
[26] = STR_LIT("NavMesh Generation - Filter"),
|
||||
[27] = STR_LIT("NavMesh Generation - Bounding Box"),
|
||||
[29] = STR_LIT("Child Can Use"),
|
||||
[30] = STR_LIT("NavMesh Generation - Ground"),
|
||||
};
|
||||
|
||||
rfs_inner tact = {
|
||||
[9] = "Hidden From Local Map",
|
||||
[16] = "Random Anim Start",
|
||||
[17] = "Radio Station",
|
||||
[9] = STR_LIT("Hidden From Local Map"),
|
||||
[16] = STR_LIT("Random Anim Start"),
|
||||
[17] = STR_LIT("Radio Station"),
|
||||
};
|
||||
|
||||
rfs_inner alch = {
|
||||
[29] = "Medicine",
|
||||
[29] = STR_LIT("Medicine"),
|
||||
};
|
||||
|
||||
rfs_inner ammo = {
|
||||
[2] = "Non-Playable",
|
||||
[2] = STR_LIT("Non-Playable"),
|
||||
};
|
||||
|
||||
rfs_inner anio = {
|
||||
[9] = "ANIO Unknown 9",
|
||||
[9] = STR_LIT("ANIO Unknown 9"),
|
||||
};
|
||||
|
||||
rfs_inner armo = {
|
||||
[2] = "Non-Playable",
|
||||
[6] = "Shield",
|
||||
[10] = "ARMO Unknown 10",
|
||||
[15] = "ARMO Unknown 15",
|
||||
[2] = STR_LIT("Non-Playable"),
|
||||
[6] = STR_LIT("Shield"),
|
||||
[10] = STR_LIT("ARMO Unknown 10"),
|
||||
[15] = STR_LIT("ARMO Unknown 15"),
|
||||
};
|
||||
|
||||
#define REFERENCE_RECORD { \
|
||||
[7] = "Turn Off Fire", \
|
||||
[10] = "Persistent", \
|
||||
[11] = "Initially Disabled", \
|
||||
[28] = "Reflected by Auto Water", \
|
||||
[30] = "No Respawn", \
|
||||
[7] = STR_LIT("Turn Off Fire"), \
|
||||
[10] = STR_LIT("Persistent"), \
|
||||
[11] = STR_LIT("Initially Disabled"), \
|
||||
[28] = STR_LIT("Reflected by Auto Water"), \
|
||||
[30] = STR_LIT("No Respawn"), \
|
||||
}
|
||||
|
||||
rfs_inner parw = REFERENCE_RECORD;
|
||||
@@ -293,187 +293,187 @@ rfs_inner pmis = REFERENCE_RECORD;
|
||||
#undef REFERENCE_RECORD
|
||||
|
||||
rfs_inner cell = {
|
||||
[10] = "Persistent",
|
||||
[17] = "Off Limits",
|
||||
[18] = "Compressed",
|
||||
[19] = "Can't Wait",
|
||||
[10] = STR_LIT("Persistent"),
|
||||
[17] = STR_LIT("Off Limits"),
|
||||
[18] = STR_LIT("Compressed"),
|
||||
[19] = STR_LIT("Can't Wait"),
|
||||
};
|
||||
|
||||
rfs_inner cont = {
|
||||
[15] = "Has Distant LOD",
|
||||
[16] = "Random Anim Start",
|
||||
[25] = "Obstacle",
|
||||
[26] = "NavMesh Generation - Filter",
|
||||
[27] = "NavMesh Generation - Bounding Box",
|
||||
[30] = "NavMesh Generation - Ground",
|
||||
[15] = STR_LIT("Has Distant LOD"),
|
||||
[16] = STR_LIT("Random Anim Start"),
|
||||
[25] = STR_LIT("Obstacle"),
|
||||
[26] = STR_LIT("NavMesh Generation - Filter"),
|
||||
[27] = STR_LIT("NavMesh Generation - Bounding Box"),
|
||||
[30] = STR_LIT("NavMesh Generation - Ground"),
|
||||
};
|
||||
|
||||
rfs_inner csty = {
|
||||
[19] = "Allow Dual Wielding",
|
||||
[19] = STR_LIT("Allow Dual Wielding"),
|
||||
};
|
||||
|
||||
rfs_inner door = {
|
||||
[15] = "Has Distant LOD",
|
||||
[16] = "Random Anim Start",
|
||||
[23] = "Is Marker",
|
||||
[15] = STR_LIT("Has Distant LOD"),
|
||||
[16] = STR_LIT("Random Anim Start"),
|
||||
[23] = STR_LIT("Is Marker"),
|
||||
};
|
||||
|
||||
rfs_inner eyes = {
|
||||
[2] = "Non-Playable",
|
||||
[2] = STR_LIT("Non-Playable"),
|
||||
};
|
||||
|
||||
rfs_inner furn = {
|
||||
[7] = "Is Perch",
|
||||
[16] = "Has Distant LOD",
|
||||
[23] = "Is Marker",
|
||||
[28] = "Must Exit To Talk",
|
||||
[29] = "Child Can Use",
|
||||
[7] = STR_LIT("Is Perch"),
|
||||
[16] = STR_LIT("Has Distant LOD"),
|
||||
[23] = STR_LIT("Is Marker"),
|
||||
[28] = STR_LIT("Must Exit To Talk"),
|
||||
[29] = STR_LIT("Child Can Use"),
|
||||
};
|
||||
|
||||
rfs_inner glob = {
|
||||
[6] = "Constant",
|
||||
[6] = STR_LIT("Constant"),
|
||||
};
|
||||
|
||||
rfs_inner hdpt = {
|
||||
[2] = "Non-Playable",
|
||||
[2] = STR_LIT("Non-Playable"),
|
||||
};
|
||||
|
||||
rfs_inner mstt = {
|
||||
[8] = "Must Update Anims",
|
||||
[9] = "Hidden From Local Map",
|
||||
[15] = "Has Distant LOD",
|
||||
[16] = "Random Anim Start",
|
||||
[19] = "Has Currents",
|
||||
[25] = "Obstacle",
|
||||
[26] = "NavMesh Generation - Filter",
|
||||
[27] = "NavMesh Generation - Bounding Box",
|
||||
[30] = "NavMesh Generation - Ground",
|
||||
[8] = STR_LIT("Must Update Anims"),
|
||||
[9] = STR_LIT("Hidden From Local Map"),
|
||||
[15] = STR_LIT("Has Distant LOD"),
|
||||
[16] = STR_LIT("Random Anim Start"),
|
||||
[19] = STR_LIT("Has Currents"),
|
||||
[25] = STR_LIT("Obstacle"),
|
||||
[26] = STR_LIT("NavMesh Generation - Filter"),
|
||||
[27] = STR_LIT("NavMesh Generation - Bounding Box"),
|
||||
[30] = STR_LIT("NavMesh Generation - Ground"),
|
||||
};
|
||||
|
||||
rfs_inner idlm = {
|
||||
[29] = "Child Can Use",
|
||||
[29] = STR_LIT("Child Can Use"),
|
||||
};
|
||||
|
||||
rfs_inner slgm = {
|
||||
[17] = "Can Hold NPC Soul",
|
||||
[17] = STR_LIT("Can Hold NPC Soul"),
|
||||
};
|
||||
|
||||
rfs_inner navm = {
|
||||
[18] = "Compressed",
|
||||
[26] = "AutoGen",
|
||||
[31] = "NavmeshGenCell",
|
||||
[18] = STR_LIT("Compressed"),
|
||||
[26] = STR_LIT("AutoGen"),
|
||||
[31] = STR_LIT("NavmeshGenCell"),
|
||||
};
|
||||
|
||||
rfs_inner perk = {
|
||||
[2] = "Non-Playable",
|
||||
[2] = STR_LIT("Non-Playable"),
|
||||
};
|
||||
|
||||
rfs_inner shou = {
|
||||
[7] = "Treat Spells As Powers",
|
||||
[7] = STR_LIT("Treat Spells As Powers"),
|
||||
};
|
||||
|
||||
rfs_inner rela = {
|
||||
[6] = "Secret",
|
||||
[6] = STR_LIT("Secret"),
|
||||
};
|
||||
|
||||
rfs_inner clfm = {
|
||||
[2] = "Non-Playable",
|
||||
[2] = STR_LIT("Non-Playable"),
|
||||
};
|
||||
|
||||
rfs_inner info = {
|
||||
[13] = "Actor Changed",
|
||||
[13] = STR_LIT("Actor Changed"),
|
||||
};
|
||||
|
||||
rfs_inner keym = {
|
||||
[2] = "Non-Playable",
|
||||
[2] = STR_LIT("Non-Playable"),
|
||||
};
|
||||
|
||||
rfs_inner land = {
|
||||
[18] = "Compressed",
|
||||
[18] = STR_LIT("Compressed"),
|
||||
};
|
||||
|
||||
rfs_inner ligh = {
|
||||
[16] = "Random Anim Start",
|
||||
[17] = "Portal-strict",
|
||||
[25] = "Obstacle",
|
||||
[16] = STR_LIT("Random Anim Start"),
|
||||
[17] = STR_LIT("Portal-strict"),
|
||||
[25] = STR_LIT("Obstacle"),
|
||||
};
|
||||
|
||||
rfs_inner lscr = {
|
||||
[10] = "Displays In Main Menu",
|
||||
[10] = STR_LIT("Displays In Main Menu"),
|
||||
};
|
||||
|
||||
rfs_inner misc = {
|
||||
[2] = "Non-Playable",
|
||||
[2] = STR_LIT("Non-Playable"),
|
||||
};
|
||||
|
||||
rfs_inner npc_ = {
|
||||
[10] = "NPC_ Unknown 10",
|
||||
[18] = "Compressed",
|
||||
[19] = "NPC_ Unknown 19",
|
||||
[29] = "Bleedout Override",
|
||||
[10] = STR_LIT("NPC_ Unknown 10"),
|
||||
[18] = STR_LIT("Compressed"),
|
||||
[19] = STR_LIT("NPC_ Unknown 19"),
|
||||
[29] = STR_LIT("Bleedout Override"),
|
||||
};
|
||||
|
||||
rfs_inner race = {
|
||||
[19] = "Critter (?)",
|
||||
[19] = STR_LIT("Critter (?)"),
|
||||
};
|
||||
|
||||
// these are generic refr flags for any reference type not handled by the refr
|
||||
// specific flag lut
|
||||
rfs_inner refr = {
|
||||
[10] = "Persistent",
|
||||
[11] = "Initially Disabled",
|
||||
[16] = "Is Full LOD",
|
||||
[26] = "Filter (Collision Geometry)",
|
||||
[27] = "Bounding Box (Collision Geometry)",
|
||||
[30] = "Ground",
|
||||
[31] = "Multibound",
|
||||
[10] = STR_LIT("Persistent"),
|
||||
[11] = STR_LIT("Initially Disabled"),
|
||||
[16] = STR_LIT("Is Full LOD"),
|
||||
[26] = STR_LIT("Filter (Collision Geometry)"),
|
||||
[27] = STR_LIT("Bounding Box (Collision Geometry)"),
|
||||
[30] = STR_LIT("Ground"),
|
||||
[31] = STR_LIT("Multibound"),
|
||||
};
|
||||
|
||||
rfs_inner regn = {
|
||||
[6] = "Border Region",
|
||||
[6] = STR_LIT("Border Region"),
|
||||
};
|
||||
|
||||
rfs_inner stat = {
|
||||
[2] = "Never Fades",
|
||||
[5] = "Deleted",
|
||||
[6] = "Has Tree LOD",
|
||||
[7] = "Add-On LOD Object",
|
||||
[9] = "Hidden From Local Map",
|
||||
[11] = "STAT Unknown 11",
|
||||
[15] = "Has Distant LOD",
|
||||
[16] = "STAT Unknown 16",
|
||||
[17] = "Uses HD LOD Texture",
|
||||
[19] = "Has Currents",
|
||||
[23] = "Is Marker",
|
||||
[25] = "Obstacle",
|
||||
[26] = "NavMesh Generation - Filter",
|
||||
[27] = "NavMesh Generation - Bounding Box",
|
||||
[28] = "Show In World Map",
|
||||
[30] = "NavMesh Generation - Ground",
|
||||
[2] = STR_LIT("Never Fades"),
|
||||
[5] = STR_LIT("Deleted"),
|
||||
[6] = STR_LIT("Has Tree LOD"),
|
||||
[7] = STR_LIT("Add-On LOD Object"),
|
||||
[9] = STR_LIT("Hidden From Local Map"),
|
||||
[11] = STR_LIT("STAT Unknown 11"),
|
||||
[15] = STR_LIT("Has Distant LOD"),
|
||||
[16] = STR_LIT("STAT Unknown 16"),
|
||||
[17] = STR_LIT("Uses HD LOD Texture"),
|
||||
[19] = STR_LIT("Has Currents"),
|
||||
[23] = STR_LIT("Is Marker"),
|
||||
[25] = STR_LIT("Obstacle"),
|
||||
[26] = STR_LIT("NavMesh Generation - Filter"),
|
||||
[27] = STR_LIT("NavMesh Generation - Bounding Box"),
|
||||
[28] = STR_LIT("Show In World Map"),
|
||||
[30] = STR_LIT("NavMesh Generation - Ground"),
|
||||
};
|
||||
|
||||
rfs_inner tes4 = {
|
||||
[0] = "ESM",
|
||||
[1] = "Altered",
|
||||
[2] = "Checked",
|
||||
[3] = "Active",
|
||||
[4] = "Optimized File",
|
||||
[5] = "Temp ID Owner",
|
||||
[7] = "Localized",
|
||||
[8] = "Precalc Data Only",
|
||||
[9] = "ESL",
|
||||
[0] = STR_LIT("ESM"),
|
||||
[1] = STR_LIT("Altered"),
|
||||
[2] = STR_LIT("Checked"),
|
||||
[3] = STR_LIT("Active"),
|
||||
[4] = STR_LIT("Optimized File"),
|
||||
[5] = STR_LIT("Temp ID Owner"),
|
||||
[7] = STR_LIT("Localized"),
|
||||
[8] = STR_LIT("Precalc Data Only"),
|
||||
[9] = STR_LIT("ESL"),
|
||||
};
|
||||
|
||||
rfs_inner tree = {
|
||||
[15] = "Has Distant LOD",
|
||||
[15] = STR_LIT("Has Distant LOD"),
|
||||
};
|
||||
|
||||
rfs_inner weap = {
|
||||
[2] = "Non-Playable",
|
||||
[2] = STR_LIT("Non-Playable"),
|
||||
};
|
||||
|
||||
rfs_inner wrld = {
|
||||
[19] = "Can't Wait",
|
||||
[19] = STR_LIT("Can't Wait"),
|
||||
};
|
||||
|
||||
rfs_inner *const rfs[RT_SIZE] = {
|
||||
@@ -524,18 +524,18 @@ rfs_inner *const rfs[RT_SIZE] = {
|
||||
|
||||
// REFR flags depend on what its NAME field references
|
||||
#define REFR_GROUP1 { \
|
||||
[9] = "Hidden From Local Map", \
|
||||
[10] = "Persistent", \
|
||||
[11] = "Initially Disabled", \
|
||||
[13] = "Sky Marker", \
|
||||
[15] = "Visible When Distant", \
|
||||
[16] = "Is Full LOD", \
|
||||
[26] = "Filter (Collision Geometry)", \
|
||||
[27] = "Bounding Box (Collision Geometry)", \
|
||||
[28] = "Reflected By Auto Water", \
|
||||
[29] = "Don't Havok Settle", \
|
||||
[30] = "No Respawn", \
|
||||
[31] = "Multibound", \
|
||||
[9] = STR_LIT("Hidden From Local Map"), \
|
||||
[10] = STR_LIT("Persistent"), \
|
||||
[11] = STR_LIT("Initially Disabled"), \
|
||||
[13] = STR_LIT("Sky Marker"), \
|
||||
[15] = STR_LIT("Visible When Distant"), \
|
||||
[16] = STR_LIT("Is Full LOD"), \
|
||||
[26] = STR_LIT("Filter (Collision Geometry)"), \
|
||||
[27] = STR_LIT("Bounding Box (Collision Geometry)"), \
|
||||
[28] = STR_LIT("Reflected By Auto Water"), \
|
||||
[29] = STR_LIT("Don't Havok Settle"), \
|
||||
[30] = STR_LIT("No Respawn"), \
|
||||
[31] = STR_LIT("Multibound"), \
|
||||
}
|
||||
|
||||
rfs_inner r_acti = REFR_GROUP1;
|
||||
@@ -546,78 +546,78 @@ rfs_inner r_flor = REFR_GROUP1;
|
||||
#undef REFR_GROUP1
|
||||
|
||||
rfs_inner r_cont = {
|
||||
[10] = "Persistent",
|
||||
[11] = "Initially Disabled",
|
||||
[16] = "Is Full LOD",
|
||||
[25] = "No AI Acquire",
|
||||
[26] = "Filter (Collision Geometry)",
|
||||
[27] = "Bounding Box (Collision Geometry)",
|
||||
[28] = "Reflected By Auto Water",
|
||||
[29] = "Don't Havok Settle",
|
||||
[30] = "Ground",
|
||||
[31] = "Multibound",
|
||||
[10] = STR_LIT("Persistent"),
|
||||
[11] = STR_LIT("Initially Disabled"),
|
||||
[16] = STR_LIT("Is Full LOD"),
|
||||
[25] = STR_LIT("No AI Acquire"),
|
||||
[26] = STR_LIT("Filter (Collision Geometry)"),
|
||||
[27] = STR_LIT("Bounding Box (Collision Geometry)"),
|
||||
[28] = STR_LIT("Reflected By Auto Water"),
|
||||
[29] = STR_LIT("Don't Havok Settle"),
|
||||
[30] = STR_LIT("Ground"),
|
||||
[31] = STR_LIT("Multibound"),
|
||||
};
|
||||
|
||||
rfs_inner r_door = {
|
||||
[6] = "Hidden From Local Map",
|
||||
[8] = "Inaccessible",
|
||||
[10] = "Persistent",
|
||||
[11] = "Initially Disabled",
|
||||
[16] = "Is Full LOD",
|
||||
[26] = "Filter (Collision Geometry)",
|
||||
[27] = "Bounding Box (Collision Geometry)",
|
||||
[28] = "Reflected By Auto Water",
|
||||
[29] = "Don't Havok Settle",
|
||||
[30] = "No Respawn",
|
||||
[31] = "Multibound",
|
||||
[6] = STR_LIT("Hidden From Local Map"),
|
||||
[8] = STR_LIT("Inaccessible"),
|
||||
[10] = STR_LIT("Persistent"),
|
||||
[11] = STR_LIT("Initially Disabled"),
|
||||
[16] = STR_LIT("Is Full LOD"),
|
||||
[26] = STR_LIT("Filter (Collision Geometry)"),
|
||||
[27] = STR_LIT("Bounding Box (Collision Geometry)"),
|
||||
[28] = STR_LIT("Reflected By Auto Water"),
|
||||
[29] = STR_LIT("Don't Havok Settle"),
|
||||
[30] = STR_LIT("No Respawn"),
|
||||
[31] = STR_LIT("Multibound"),
|
||||
};
|
||||
|
||||
rfs_inner r_ligh = {
|
||||
[8] = "Doesn't Light Water",
|
||||
[9] = "Casts Shadows",
|
||||
[10] = "Persistent",
|
||||
[11] = "Initially Disabled",
|
||||
[16] = "Never Fades",
|
||||
[17] = "Doesn't Light Landscape",
|
||||
[25] = "No AI Acquire",
|
||||
[28] = "Reflected By Auto Water",
|
||||
[29] = "Don't Havok Settle",
|
||||
[30] = "No Respawn",
|
||||
[31] = "Multibound",
|
||||
[8] = STR_LIT("Doesn't Light Water"),
|
||||
[9] = STR_LIT("Casts Shadows"),
|
||||
[10] = STR_LIT("Persistent"),
|
||||
[11] = STR_LIT("Initially Disabled"),
|
||||
[16] = STR_LIT("Never Fades"),
|
||||
[17] = STR_LIT("Doesn't Light Landscape"),
|
||||
[25] = STR_LIT("No AI Acquire"),
|
||||
[28] = STR_LIT("Reflected By Auto Water"),
|
||||
[29] = STR_LIT("Don't Havok Settle"),
|
||||
[30] = STR_LIT("No Respawn"),
|
||||
[31] = STR_LIT("Multibound"),
|
||||
};
|
||||
|
||||
rfs_inner r_mstt = {
|
||||
[9] = "Motion Blur",
|
||||
[10] = "Persistent",
|
||||
[11] = "Initially Disabled",
|
||||
[16] = "Is Full LOD",
|
||||
[26] = "Filter (Collision Geometry)",
|
||||
[27] = "Bounding Box (Collision Geometry)",
|
||||
[28] = "Reflected By Auto Water",
|
||||
[29] = "Don't Havok Settle",
|
||||
[30] = "No Respawn",
|
||||
[31] = "Multibound",
|
||||
[9] = STR_LIT("Motion Blur"),
|
||||
[10] = STR_LIT("Persistent"),
|
||||
[11] = STR_LIT("Initially Disabled"),
|
||||
[16] = STR_LIT("Is Full LOD"),
|
||||
[26] = STR_LIT("Filter (Collision Geometry)"),
|
||||
[27] = STR_LIT("Bounding Box (Collision Geometry)"),
|
||||
[28] = STR_LIT("Reflected By Auto Water"),
|
||||
[29] = STR_LIT("Don't Havok Settle"),
|
||||
[30] = STR_LIT("No Respawn"),
|
||||
[31] = STR_LIT("Multibound"),
|
||||
};
|
||||
|
||||
rfs_inner r_addn = {
|
||||
[10] = "Persistent",
|
||||
[11] = "Initially Disabled",
|
||||
[16] = "Is Full LOD",
|
||||
[28] = "Reflected By Auto Water",
|
||||
[29] = "Don't Havok Settle",
|
||||
[30] = "No Respawn",
|
||||
[31] = "Multibound",
|
||||
[10] = STR_LIT("Persistent"),
|
||||
[11] = STR_LIT("Initially Disabled"),
|
||||
[16] = STR_LIT("Is Full LOD"),
|
||||
[28] = STR_LIT("Reflected By Auto Water"),
|
||||
[29] = STR_LIT("Don't Havok Settle"),
|
||||
[30] = STR_LIT("No Respawn"),
|
||||
[31] = STR_LIT("Multibound"),
|
||||
};
|
||||
|
||||
#define REFR_GROUP2 { \
|
||||
[10] = "Persistent", \
|
||||
[11] = "Initially Disabled", \
|
||||
[16] = "Is Full LOD", \
|
||||
[25] = "No AI Acquire", \
|
||||
[28] = "Reflected By Auto Water", \
|
||||
[29] = "Don't Havok Settle", \
|
||||
[30] = "No Respawn", \
|
||||
[31] = "Multibound", \
|
||||
[10] = STR_LIT("Persistent"), \
|
||||
[11] = STR_LIT("Initially Disabled"), \
|
||||
[16] = STR_LIT("Is Full LOD"), \
|
||||
[25] = STR_LIT("No AI Acquire"), \
|
||||
[28] = STR_LIT("Reflected By Auto Water"), \
|
||||
[29] = STR_LIT("Don't Havok Settle"), \
|
||||
[30] = STR_LIT("No Respawn"), \
|
||||
[31] = STR_LIT("Multibound"), \
|
||||
}
|
||||
|
||||
rfs_inner r_alch = REFR_GROUP2;
|
||||
@@ -630,6 +630,8 @@ rfs_inner r_misc = REFR_GROUP2;
|
||||
rfs_inner r_slgm = REFR_GROUP2;
|
||||
rfs_inner r_weap = REFR_GROUP2;
|
||||
|
||||
#undef REFR_GROUP2
|
||||
|
||||
rfs_inner *const rfs_r[RT_SIZE] = {
|
||||
[AACT] = CP(NULL ),[ACHR] = CP(NULL ),[ACTI] = CP(r_acti),
|
||||
[ADDN] = CP(r_addn),[ALCH] = CP(r_alch),[AMMO] = CP(r_ammo),
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
*/
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
#include <io.h>
|
||||
|
||||
#include "zlib.h"
|
||||
|
||||
@@ -18,6 +20,8 @@
|
||||
// === CONSTANTS ===
|
||||
//
|
||||
|
||||
#define STDOUT_FILENO 1
|
||||
|
||||
// timestamp field access
|
||||
const uint16_t day_mask = 0x1F;
|
||||
const uint16_t month_mask = 0xF;
|
||||
@@ -41,17 +45,18 @@ void print_group_header(Group *header);
|
||||
void print_record_header(Record *header);
|
||||
|
||||
// Printer helpers
|
||||
void print_group_label(Group *header);
|
||||
void print_record_flags(Record *header);
|
||||
void print_timestamp(uint16_t ts);
|
||||
void print_type(Type4 type);
|
||||
void print_type4(Type4 val);
|
||||
void litcopy(struct str_buf *sb, struct str_lit lit);
|
||||
void num_str(struct str_buf *sb, unsigned long num, int radix);
|
||||
void type_str(struct str_buf *sb, Type4 type);
|
||||
void timestamp_str(struct str_buf *sb, uint16_t timestamp);
|
||||
void group_label_str(struct str_buf *sb, Group *header);
|
||||
void record_flags_str(struct str_buf *sb, Record *header);
|
||||
void sb_write(int fp, struct str_buf sb_pre, struct str_buf sb_post);
|
||||
|
||||
// Utilities
|
||||
Timestamp convert_ts(uint16_t ts);
|
||||
void print_callback(Node n, void *data, void **carry_out);
|
||||
void dc_size_cb(Node n, void *data, void **carry_out);
|
||||
void formid_count_cb(Node n, void *data, void **carry_out);
|
||||
void print_cb(Node n, void *data, void **carry_out);
|
||||
void stats_cb(Node n, void *data, void **carry_out);
|
||||
void decompress_pre(Node n, void *data, void **carry_out);
|
||||
void decompress_post(Node n, void *data, void **carry_in);
|
||||
|
||||
@@ -85,169 +90,6 @@ void espr_walk(char *data, size_t size, struct walker_callbacks cb) {
|
||||
assert(data == data_start + size);
|
||||
}
|
||||
|
||||
void espr_print(char *data, size_t size) {
|
||||
struct walker_callbacks cb = { .pre = print_callback };
|
||||
espr_walk(data, size, cb);
|
||||
}
|
||||
|
||||
void print_callback(Node n, void *data, void **carry_out) {
|
||||
(void)data;
|
||||
(void)carry_out;
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
size_t espr_decompressed_size(char *data, size_t size) {
|
||||
size_t dc_size = 0;
|
||||
struct walker_callbacks cb = { .pre = dc_size_cb, .data = &dc_size };
|
||||
espr_walk(data, size, cb);
|
||||
return dc_size;
|
||||
}
|
||||
|
||||
// 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)carry_out;
|
||||
size_t *dc_size = data;
|
||||
switch (n.type) {
|
||||
case NT_GROUP:
|
||||
// Only add header size for groups, internals will be walked
|
||||
*dc_size += sizeof(Group);
|
||||
break;
|
||||
case NT_RECORD:
|
||||
// Add the whole record and header, records are leaf-ish
|
||||
*dc_size += sizeof(Record);
|
||||
if (n.header.record->flags & COMPRESSED_FLAG) {
|
||||
// Read decompressed size
|
||||
*dc_size += *((uint32_t *)n.data);
|
||||
}
|
||||
else
|
||||
*dc_size += n.header.record->size;
|
||||
break;
|
||||
default:
|
||||
assert(false); // invalid node type
|
||||
}
|
||||
}
|
||||
|
||||
size_t espr_formid_count(char *data, size_t size) {
|
||||
size_t count = 0;
|
||||
struct walker_callbacks cb = { .pre = formid_count_cb, .data = &count };
|
||||
espr_walk(data, size, cb);
|
||||
return count;
|
||||
}
|
||||
|
||||
/* FormID <-> Record relationship should be bijective. I do not believe
|
||||
* groups have formids, and every record should have a unique formid,
|
||||
* otherwise there would be clashes in the id space.
|
||||
*/
|
||||
void formid_count_cb(Node n, void *data, void **carry_out) {
|
||||
(void)carry_out;
|
||||
size_t *count = data;
|
||||
if (n.type == NT_RECORD) {
|
||||
(*count)++;
|
||||
}
|
||||
}
|
||||
|
||||
struct decom {
|
||||
char *buf;
|
||||
size_t remaining;
|
||||
};
|
||||
|
||||
void espr_decompress(char *data, size_t size, char *buf, size_t buf_size) {
|
||||
struct decom s = { .buf = buf, .remaining = buf_size };
|
||||
struct walker_callbacks cb =
|
||||
{ .pre = decompress_pre, .post = decompress_post, .data = &s };
|
||||
espr_walk(data, size, cb);
|
||||
}
|
||||
|
||||
void decompress_pre(Node n, void *decom_ptr, void **carry_out) {
|
||||
struct decom *d = decom_ptr;
|
||||
|
||||
switch (n.type) {
|
||||
case NT_RECORD:
|
||||
// compressed record
|
||||
if (n.header.record->flags & COMPRESSED_FLAG) {
|
||||
// copy header
|
||||
memcpy(d->buf, n.header.record, sizeof(Record));
|
||||
|
||||
// copied header reference
|
||||
Record *header = (Record *)d->buf;
|
||||
|
||||
// update decom struct
|
||||
d->remaining -= sizeof(Record);
|
||||
d->buf += sizeof(Record);
|
||||
|
||||
// decompress directly into buffer
|
||||
// first 4 bytes are the decompressed size
|
||||
const uint32_t dc_size = *((uint32_t *)n.data);
|
||||
uint32_t to_copy = dc_size;
|
||||
uint32_t cur_size = n.header.record->size - 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
|
||||
);
|
||||
assert(ret == Z_OK);
|
||||
assert(to_copy == dc_size);
|
||||
|
||||
// update decom struct
|
||||
d->remaining -= dc_size;
|
||||
d->buf += dc_size;
|
||||
|
||||
// update header data size
|
||||
header->size = dc_size;
|
||||
|
||||
// unset compressed flag
|
||||
header->flags &= ~COMPRESSED_FLAG;
|
||||
}
|
||||
else {
|
||||
// copy record
|
||||
size_t record_size = sizeof(Record) + n.header.record->size;
|
||||
memcpy(d->buf, n.header.record, record_size);
|
||||
|
||||
// update decom
|
||||
d->remaining -= record_size;
|
||||
d->buf += record_size;
|
||||
}
|
||||
break;
|
||||
case NT_GROUP:
|
||||
// copy header, contents will be copied while walking
|
||||
memcpy(d->buf, n.header.group, sizeof(Group));
|
||||
|
||||
// save copied header location for post-walk group size recalc
|
||||
*carry_out = (void *)d->buf;
|
||||
|
||||
// update decom
|
||||
d->buf += sizeof(Group);
|
||||
d->remaining -= sizeof(Group);
|
||||
|
||||
break;
|
||||
default:
|
||||
assert(false); // invalid node type
|
||||
}
|
||||
}
|
||||
|
||||
void decompress_post(Node n, void *decom_ptr, void **carry_in) {
|
||||
struct decom *d = decom_ptr;
|
||||
|
||||
// only need to handle group resize
|
||||
if (n.type == NT_GROUP) {
|
||||
Group *g = (Group *)(*carry_in);
|
||||
uint32_t new_size = (uint32_t)((char *)d->buf - (char *)g);
|
||||
g->size = new_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Unknown data will be some concatenation of groups and records.
|
||||
*
|
||||
* `walk_concat` will call the appropriate walking function
|
||||
@@ -332,58 +174,299 @@ char *walk_record(char *data, struct walker_callbacks cb) {
|
||||
return data;
|
||||
}
|
||||
|
||||
void espr_print(char *data, size_t size) {
|
||||
struct walker_callbacks cb = { .pre = print_cb };
|
||||
espr_walk(data, size, cb);
|
||||
}
|
||||
|
||||
void print_cb(Node n, void *data, void **carry_out) {
|
||||
(void)data;
|
||||
(void)carry_out;
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
struct esp_stats espr_stats(char *data, size_t size) {
|
||||
struct esp_stats stats = { 0 };
|
||||
struct walker_callbacks cb = { .pre = stats_cb, .data = &stats };
|
||||
espr_walk(data, size, cb);
|
||||
return stats;
|
||||
}
|
||||
|
||||
/* Tallies up the group and record count. Calculates uncompressed size; groups
|
||||
* only need their header size tallied as their data size will be handled by
|
||||
* further walking of the tree.
|
||||
*/
|
||||
void stats_cb(Node n, void *data, void **carry_out) {
|
||||
(void)carry_out;
|
||||
struct esp_stats *stats = data;
|
||||
switch (n.type) {
|
||||
case NT_GROUP:
|
||||
stats->group_count++;
|
||||
stats->decompressed_size += sizeof(Group);
|
||||
break;
|
||||
case NT_RECORD:
|
||||
stats->record_count++;
|
||||
stats->decompressed_size += sizeof(Record);
|
||||
if (n.header.record->flags & COMPRESSED_FLAG) {
|
||||
// uncompressed size is stored in the first 4 bytes of data
|
||||
stats->decompressed_size += *((uint32_t *)n.data);
|
||||
} else {
|
||||
stats->decompressed_size += n.header.record->size;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false); // invalid node type
|
||||
}
|
||||
}
|
||||
|
||||
struct decom {
|
||||
char *buf;
|
||||
size_t remaining;
|
||||
};
|
||||
|
||||
void espr_decompress(char *data, size_t size, char *buf, size_t buf_size) {
|
||||
struct decom s = { .buf = buf, .remaining = buf_size };
|
||||
struct walker_callbacks cb =
|
||||
{ .pre = decompress_pre, .post = decompress_post, .data = &s };
|
||||
espr_walk(data, size, cb);
|
||||
}
|
||||
|
||||
/* Handles the copying of groups and records, and the decompression of
|
||||
* compressed record data.
|
||||
*
|
||||
* For groups it copies only the header as group data will be handled by further
|
||||
* walking. The destination prior to copying will also be saved to carry_out for
|
||||
* groups so that decompress_post can correctly update the size of the copied
|
||||
* group.
|
||||
*
|
||||
* For uncompressed records it simply copies the entirety of the record to the
|
||||
* destination. For compressed records in copies the header first and then
|
||||
* directly decompresses the compressed record into the destination.
|
||||
*/
|
||||
void decompress_pre(Node n, void *decom_ptr, void **carry_out) {
|
||||
struct decom *d = decom_ptr;
|
||||
|
||||
switch (n.type) {
|
||||
case NT_RECORD:
|
||||
// compressed record
|
||||
if (n.header.record->flags & COMPRESSED_FLAG) {
|
||||
// copy header
|
||||
memcpy(d->buf, n.header.record, sizeof(Record));
|
||||
|
||||
// copied header reference
|
||||
Record *header = (Record *)d->buf;
|
||||
|
||||
// update decom struct
|
||||
d->remaining -= sizeof(Record);
|
||||
d->buf += sizeof(Record);
|
||||
|
||||
// decompress directly into buffer
|
||||
// first 4 bytes are the decompressed size
|
||||
const uint32_t dc_size = *((uint32_t *)n.data);
|
||||
uint32_t to_copy = dc_size;
|
||||
uint32_t cur_size = n.header.record->size - 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
|
||||
);
|
||||
assert(ret == Z_OK);
|
||||
assert(to_copy == dc_size);
|
||||
|
||||
// update decom struct
|
||||
d->remaining -= dc_size;
|
||||
d->buf += dc_size;
|
||||
|
||||
// update header data size
|
||||
header->size = dc_size;
|
||||
|
||||
// unset compressed flag
|
||||
header->flags &= ~COMPRESSED_FLAG;
|
||||
}
|
||||
else {
|
||||
// copy record
|
||||
size_t record_size = sizeof(Record) + n.header.record->size;
|
||||
memcpy(d->buf, n.header.record, record_size);
|
||||
|
||||
// update decom
|
||||
d->remaining -= record_size;
|
||||
d->buf += record_size;
|
||||
}
|
||||
break;
|
||||
case NT_GROUP:
|
||||
// copy header, contents will be copied while walking
|
||||
memcpy(d->buf, n.header.group, sizeof(Group));
|
||||
|
||||
// save copied header location for post-walk group size recalc
|
||||
*carry_out = (void *)d->buf;
|
||||
|
||||
// update decom
|
||||
d->buf += sizeof(Group);
|
||||
d->remaining -= sizeof(Group);
|
||||
|
||||
break;
|
||||
default:
|
||||
assert(false); // invalid node type
|
||||
}
|
||||
}
|
||||
|
||||
/* Handles recalculating group size after decompression. The location of the
|
||||
* the group's copied header will be passed in in carry_in and can be used both
|
||||
* to access the copied group header and calculate the new size of the group
|
||||
* based on the difference between the current destination pointer and the
|
||||
* group header pointer.
|
||||
*/
|
||||
void decompress_post(Node n, void *decom_ptr, void **carry_in) {
|
||||
struct decom *d = decom_ptr;
|
||||
|
||||
// only need to handle group resize
|
||||
if (n.type == NT_GROUP) {
|
||||
Group *g = (Group *)(*carry_in);
|
||||
uint32_t new_size = (uint32_t)((char *)d->buf - (char *)g);
|
||||
g->size = new_size;
|
||||
}
|
||||
}
|
||||
|
||||
void print_group_header(Group *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);
|
||||
// Guess at enough with significant margin
|
||||
char buf[1024] = { 0 };
|
||||
const struct str_buf sb_pre = { .buf = buf, .size = sizeof(buf) };
|
||||
struct str_buf sb = sb_pre;
|
||||
|
||||
// literals
|
||||
struct str_lit
|
||||
l1 = STR_LIT("--- HEADER: GROUP ---"),
|
||||
l2 = STR_LIT("\nType: "),
|
||||
l3 = STR_LIT("\nSize: "),
|
||||
l4 = STR_LIT("\nLabel: "),
|
||||
l5 = STR_LIT("\nGroup type: "),
|
||||
l6 = STR_LIT("\nTimestamp: "),
|
||||
l7 = STR_LIT("\nVersion Control Info: "),
|
||||
l8 = STR_LIT("\nUnknown: "),
|
||||
l9 = STR_LIT("\n");
|
||||
|
||||
struct str_lit gt = group_type_strings[header->type];
|
||||
|
||||
// construct output
|
||||
litcopy(&sb, l1);
|
||||
litcopy(&sb, l2); type_str(&sb, header->grup);
|
||||
litcopy(&sb, l3); num_str(&sb, header->size, 10);
|
||||
litcopy(&sb, l4); group_label_str(&sb, header);
|
||||
litcopy(&sb, l5); litcopy(&sb, gt);
|
||||
litcopy(&sb, l6); timestamp_str(&sb, header->timestamp);
|
||||
litcopy(&sb, l7); num_str(&sb, header->vcinfo, 16);
|
||||
litcopy(&sb, l8); num_str(&sb, header->unknown, 16);
|
||||
litcopy(&sb, l9);
|
||||
|
||||
sb_write(STDOUT_FILENO, sb_pre, sb);
|
||||
}
|
||||
|
||||
void print_record_header(Record *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 litcopy(struct str_buf *sb, struct str_lit lit) {
|
||||
assert(sb->size >= lit.size);
|
||||
memcpy(sb->buf, lit.lit, lit.size);
|
||||
sb->size -= lit.size;
|
||||
sb->buf += lit.size;
|
||||
}
|
||||
|
||||
void print_group_label(Group *header) {
|
||||
printf("Label: ");
|
||||
void num_str(struct str_buf *sb, unsigned long num, int radix) {
|
||||
errno_t ret = _ultoa_s(num, sb->buf, sb->size, radix);
|
||||
assert(ret == 0);
|
||||
int len = (int)strlen(sb->buf);
|
||||
sb->size -= len;
|
||||
sb->buf += len;
|
||||
}
|
||||
|
||||
void type_str(struct str_buf *sb, Type4 type) {
|
||||
assert(sb->size >= 4);
|
||||
for (size_t i = 0; i != 4; i++)
|
||||
sb->buf[i] = type.bytes[i];
|
||||
sb->buf += 4;
|
||||
sb->size -= 4;
|
||||
}
|
||||
|
||||
void group_label_str(struct str_buf *sb, Group *header) {
|
||||
switch (header->type) {
|
||||
case GT_TOP:
|
||||
print_type4(header->label.type);
|
||||
type_str(sb, header->label.type);
|
||||
break;
|
||||
case GT_INTERIOR_CELL_BLOCK:
|
||||
case GT_INTERIOR_CELL_SUBBLOCK:
|
||||
printf("%d", header->label.number);
|
||||
num_str(sb, header->label.number, 10);
|
||||
break;
|
||||
case GT_EXTERIOR_CELL_BLOCK:
|
||||
case GT_EXTERIOR_CELL_SUBBLOCK:
|
||||
printf("X: %d, Y: %d",
|
||||
header->label.coord[1], header->label.coord[0]);
|
||||
litcopy(sb, LIT("X: ")); num_str(sb, header->label.coord[1], 10);
|
||||
litcopy(sb, LIT("Y: ")); num_str(sb, header->label.coord[0], 10);
|
||||
break;
|
||||
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);
|
||||
litcopy(sb, LIT("FormID["));
|
||||
num_str(sb, header->label.formid, 16);
|
||||
litcopy(sb, LIT("]"));
|
||||
break;
|
||||
default:
|
||||
assert(false); // invalid group type
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void print_record_flags(Record *header) {
|
||||
printf("Flags:\n");
|
||||
void timestamp_str(struct str_buf *sb, uint16_t timestamp) {
|
||||
Timestamp ts = convert_ts(timestamp);
|
||||
litcopy(sb, LIT("20x")); 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) {
|
||||
int size = sb_pre.size - sb_post.size;
|
||||
assert(size >= 0);
|
||||
int ret = _write(fp, sb_pre.buf, size);
|
||||
assert(ret == size);
|
||||
}
|
||||
|
||||
void print_record_header(Record *header) {
|
||||
char buf[1024] = { 0 };
|
||||
const struct str_buf sb_pre = { .buf = buf, .size = sizeof(buf) };
|
||||
struct str_buf sb = sb_pre;
|
||||
|
||||
const struct str_lit
|
||||
l1 = LIT("--- HEADER: RECORD ---"),
|
||||
l2 = LIT("\nType: "),
|
||||
l3 = LIT("\nFlags: "),
|
||||
l4 = LIT("\nFormID: "),
|
||||
l5 = LIT("\nTimestamp: "),
|
||||
l6 = LIT("\nVersion Control Info: "),
|
||||
l7 = LIT("\nVersion: "),
|
||||
l8 = LIT("\nUnknown: "),
|
||||
l9 = LIT("\n");
|
||||
|
||||
litcopy(&sb, l1);
|
||||
litcopy(&sb, l2); type_str(&sb, header->type);
|
||||
litcopy(&sb, l3); record_flags_str(&sb, header);
|
||||
litcopy(&sb, l4); num_str(&sb, header->formid, 16);
|
||||
litcopy(&sb, l5); timestamp_str(&sb, header->timestamp);
|
||||
litcopy(&sb, l6); num_str(&sb, header->vcinfo, 16);
|
||||
litcopy(&sb, l7); num_str(&sb, header->version, 10);
|
||||
litcopy(&sb, l8); num_str(&sb, header->unknown, 16);
|
||||
litcopy(&sb, l9);
|
||||
|
||||
sb_write(STDOUT_FILENO, sb_pre, sb);
|
||||
}
|
||||
|
||||
void record_flags_str(struct str_buf *sb, Record *header) {
|
||||
uint32_t flags = header->flags;
|
||||
const uint32_t type = header->type.uint;
|
||||
|
||||
@@ -400,9 +483,9 @@ void print_record_flags(Record *header) {
|
||||
// will always be >= 0 as flags is not 0
|
||||
int highest = 31 - __lzcnt(flags);
|
||||
assert(highest >= 0);
|
||||
const char *const str = (*flag_lut)[highest];
|
||||
if (str) {
|
||||
printf(" - %s\n", str);
|
||||
const struct str_lit lit = (*flag_lut)[highest];
|
||||
if (lit.lit) {
|
||||
litcopy(sb, LIT("\n - ")); litcopy(sb, lit);
|
||||
flags -= ((uint32_t)1) << highest;
|
||||
}
|
||||
else
|
||||
@@ -411,6 +494,7 @@ void print_record_flags(Record *header) {
|
||||
}
|
||||
}
|
||||
|
||||
// slow path
|
||||
if (flags != 0) {
|
||||
printf("\n\nOriginal flags: %08x\n", header->flags);
|
||||
printf("Unhandled flags: %08x\n", flags);
|
||||
@@ -418,24 +502,12 @@ void print_record_flags(Record *header) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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]);
|
||||
}
|
||||
|
||||
/* Converts the bit-packed/encoded timestamp used in esp/esm files into day,
|
||||
* month and year. See UESP for further explanation.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
Timestamp convert_ts(uint16_t ts) {
|
||||
/*
|
||||
const uint8_t day = (uint8_t)(ts & day_mask);
|
||||
|
||||
@@ -141,7 +141,6 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ESPReader.h" />
|
||||
<ClInclude Include="ESPTree.h" />
|
||||
<ClInclude Include="msh.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
<ClInclude Include="msh.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ESPTree.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Reader.c">
|
||||
|
||||
Reference in New Issue
Block a user