diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index 122ca2f3af..914a40699e 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -4,6 +4,9 @@ set(ESMTOOL labels.cpp record.hpp record.cpp + arguments.hpp + tes4.hpp + tes4.cpp ) source_group(apps\\esmtool FILES ${ESMTOOL}) diff --git a/apps/esmtool/arguments.hpp b/apps/esmtool/arguments.hpp new file mode 100644 index 0000000000..96b4bb8f04 --- /dev/null +++ b/apps/esmtool/arguments.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_ESMTOOL_ARGUMENTS_H +#define OPENMW_ESMTOOL_ARGUMENTS_H + +#include +#include + +#include + +namespace EsmTool +{ + struct Arguments + { + std::optional mRawFormat; + bool quiet_given; + bool loadcells_given; + bool plain_given; + + std::string mode; + std::string encoding; + std::string filename; + std::string outname; + + std::vector types; + std::string name; + }; +} + +#endif diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index d6708fbfb4..3eb78f3e41 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -7,17 +7,29 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include #include "record.hpp" #include "labels.hpp" +#include "arguments.hpp" +#include "tes4.hpp" -#define ESMTOOL_VERSION 1.2 +namespace +{ + +using namespace EsmTool; + +constexpr unsigned majorVersion = 1; +constexpr unsigned minorVersion = 3; // Create a local alias for brevity namespace bpo = boost::program_options; @@ -36,23 +48,6 @@ struct ESMData }; -// Based on the legacy struct -struct Arguments -{ - bool raw_given; - bool quiet_given; - bool loadcells_given; - bool plain_given; - - std::string mode; - std::string encoding; - std::string filename; - std::string outname; - - std::vector types; - std::string name; -}; - bool parseOptions (int argc, char** argv, Arguments &info) { bpo::options_description desc("Inspect and extract from Morrowind ES files (ESM, ESP, ESS)\nSyntax: esmtool [options] mode infile [outfile]\nAllowed modes:\n dump\t Dumps all readable data from the input file.\n clone\t Clones the input file to the output file.\n comp\t Compares the given files.\n\nAllowed options"); @@ -60,7 +55,10 @@ bool parseOptions (int argc, char** argv, Arguments &info) desc.add_options() ("help,h", "print help message.") ("version,v", "print version information and quit.") - ("raw,r", "Show an unformatted list of all records and subrecords.") + ("raw,r", bpo::value(), + "Show an unformatted list of all records and subrecords of given format:\n" + "\n\tTES3" + "\n\tTES4") // The intention is that this option would interact better // with other modes including clone, dump, and raw. ("type,t", bpo::value< std::vector >(), @@ -122,7 +120,7 @@ bool parseOptions (int argc, char** argv, Arguments &info) } if (variables.count ("version")) { - std::cout << "ESMTool version " << ESMTOOL_VERSION << std::endl; + std::cout << "ESMTool version " << majorVersion << '.' << minorVersion << std::endl; return false; } if (!variables.count("mode")) @@ -164,7 +162,9 @@ bool parseOptions (int argc, char** argv, Arguments &info) if (variables["input-file"].as< std::vector >().size() > 1) info.outname = variables["input-file"].as< std::vector >()[1]; - info.raw_given = variables.count ("raw") != 0; + if (const auto it = variables.find("raw"); it != variables.end()) + info.mRawFormat = ESM::parseFormat(it->second.as()); + info.quiet_given = variables.count ("quiet") != 0; info.loadcells_given = variables.count ("loadcells") != 0; info.plain_given = variables.count("plain") != 0; @@ -181,13 +181,14 @@ bool parseOptions (int argc, char** argv, Arguments &info) return true; } -void printRaw(ESM::ESMReader &esm); void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMData* data); int load(const Arguments& info, ESMData* data); int clone(const Arguments& info); int comp(const Arguments& info); +} + int main(int argc, char**argv) { try @@ -217,6 +218,9 @@ int main(int argc, char**argv) return 0; } +namespace +{ + void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMData* data) { bool quiet = (info.quiet_given || info.mode == "clone"); @@ -284,8 +288,11 @@ void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMDa } } -void printRaw(ESM::ESMReader &esm) +void printRawTes3(const std::string& path) { + std::cout << "TES3 RAW file listing: " << path << '\n'; + ESM::ESMReader esm; + esm.openRaw(path); while(esm.hasMoreRecs()) { ESM::NAME n = esm.getRecName(); @@ -305,35 +312,23 @@ void printRaw(ESM::ESMReader &esm) } } -int load(const Arguments& info, ESMData* data) +int loadTes3(const Arguments& info, std::unique_ptr&& stream, ESMData* data) { + std::cout << "Loading TES3 file: " << info.filename << '\n'; + ESM::ESMReader esm; ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); esm.setEncoder(&encoder); - std::string filename = info.filename; - std::cout << "Loading file: " << filename << '\n'; - std::unordered_set skipped; - try { - - if(info.raw_given && info.mode == "dump") - { - std::cout << "RAW file listing:\n"; - - esm.openRaw(filename); - - printRaw(esm); - - return 0; - } - + try + { bool quiet = (info.quiet_given || info.mode == "clone"); bool loadCells = (info.loadcells_given || info.mode == "clone"); bool save = (info.mode == "clone"); - esm.open(filename); + esm.open(std::move(stream), info.filename); if (data != nullptr) { @@ -422,7 +417,49 @@ int load(const Arguments& info, ESMData* data) return 0; } -#include +int load(const Arguments& info, ESMData* data) +{ + if (info.mRawFormat.has_value() && info.mode == "dump") + { + switch (*info.mRawFormat) + { + case ESM::Format::Tes3: + printRawTes3(info.filename); + break; + case ESM::Format::Tes4: + std::cout << "Printing raw TES4 file is not supported: " << info.filename << "\n"; + break; + } + return 0; + } + + auto stream = Files::openBinaryInputFileStream(info.filename); + if (!stream->is_open()) + { + std::cout << "Failed to open file: " << std::strerror(errno) << '\n'; + return -1; + } + + const ESM::Format format = ESM::readFormat(*stream); + stream->seekg(0); + + switch (format) + { + case ESM::Format::Tes3: + return loadTes3(info, std::move(stream), data); + case ESM::Format::Tes4: + if (data != nullptr) + { + std::cout << "Collecting data from esm file is not supported for TES4\n"; + return -1; + } + return loadTes4(info, std::move(stream)); + } + + std::cout << "Unsupported ESM format: " << ESM::NAME(format).toStringView() << '\n'; + + return -1; +} int clone(const Arguments& info) { @@ -526,9 +563,6 @@ int comp(const Arguments& info) Arguments fileOne; Arguments fileTwo; - fileOne.raw_given = false; - fileTwo.raw_given = false; - fileOne.mode = "clone"; fileTwo.mode = "clone"; @@ -560,3 +594,5 @@ int comp(const Arguments& info) return 0; } + +} diff --git a/apps/esmtool/tes4.cpp b/apps/esmtool/tes4.cpp new file mode 100644 index 0000000000..3f213ff0b7 --- /dev/null +++ b/apps/esmtool/tes4.cpp @@ -0,0 +1,329 @@ +#include "tes4.hpp" +#include "arguments.hpp" +#include "labels.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace EsmTool +{ + namespace + { + struct Params + { + const bool mQuite; + + explicit Params(const Arguments& info) + : mQuite(info.quiet_given || info.mode == "clone") + {} + }; + + std::string toString(ESM4::GroupType type) + { + switch (type) + { + case ESM4::Grp_RecordType: return "RecordType"; + case ESM4::Grp_WorldChild: return "WorldChild"; + case ESM4::Grp_InteriorCell: return "InteriorCell"; + case ESM4::Grp_InteriorSubCell: return "InteriorSubCell"; + case ESM4::Grp_ExteriorCell: return "ExteriorCell"; + case ESM4::Grp_ExteriorSubCell: return "ExteriorSubCell"; + case ESM4::Grp_CellChild: return "CellChild"; + case ESM4::Grp_TopicChild: return "TopicChild"; + case ESM4::Grp_CellPersistentChild: return "CellPersistentChild"; + case ESM4::Grp_CellTemporaryChild: return "CellTemporaryChild"; + case ESM4::Grp_CellVisibleDistChild: return "CellVisibleDistChild"; + } + + return "Unknown (" + std::to_string(type) + ")"; + } + + template > + struct HasFormId : std::false_type {}; + + template + struct HasFormId> : std::true_type {}; + + template + constexpr bool hasFormId = HasFormId::value; + + template > + struct HasFlags : std::false_type {}; + + template + struct HasFlags> : std::true_type {}; + + template + constexpr bool hasFlags = HasFlags::value; + + template + void readTypedRecord(const Params& params, ESM4::Reader& reader) + { + reader.getRecordData(); + + T value; + value.load(reader); + + if (params.mQuite) + return; + + std::cout << "\n Record: " << ESM::NAME(reader.hdr().record.typeId).toStringView(); + if constexpr (hasFormId) + std::cout << ' ' << value.mFormId; + if constexpr (hasFlags) + std::cout << "\n Record flags: " << recordFlags(value.mFlags); + std::cout << '\n'; + } + + void readRecord(const Params& params, ESM4::Reader& reader) + { + switch (static_cast(reader.hdr().record.typeId)) + { + case ESM4::REC_AACT: break; + case ESM4::REC_ACHR: return readTypedRecord(params, reader); + case ESM4::REC_ACRE: return readTypedRecord(params, reader); + case ESM4::REC_ACTI: return readTypedRecord(params, reader); + case ESM4::REC_ADDN: break; + case ESM4::REC_ALCH: return readTypedRecord(params, reader); + case ESM4::REC_ALOC: return readTypedRecord(params, reader); + case ESM4::REC_AMMO: return readTypedRecord(params, reader); + case ESM4::REC_ANIO: return readTypedRecord(params, reader); + case ESM4::REC_APPA: return readTypedRecord(params, reader); + case ESM4::REC_ARMA: return readTypedRecord(params, reader); + case ESM4::REC_ARMO: return readTypedRecord(params, reader); + case ESM4::REC_ARTO: break; + case ESM4::REC_ASPC: return readTypedRecord(params, reader); + case ESM4::REC_ASTP: break; + case ESM4::REC_AVIF: break; + case ESM4::REC_BOOK: return readTypedRecord(params, reader); + case ESM4::REC_BPTD: return readTypedRecord(params, reader); + case ESM4::REC_CAMS: break; + case ESM4::REC_CCRD: break; + case ESM4::REC_CELL: return readTypedRecord(params, reader); + case ESM4::REC_CLAS: return readTypedRecord(params, reader); + case ESM4::REC_CLFM: return readTypedRecord(params, reader); + case ESM4::REC_CLMT: break; + case ESM4::REC_CLOT: return readTypedRecord(params, reader); + case ESM4::REC_CMNY: break; + case ESM4::REC_COBJ: break; + case ESM4::REC_COLL: break; + case ESM4::REC_CONT: return readTypedRecord(params, reader); + case ESM4::REC_CPTH: break; + case ESM4::REC_CREA: return readTypedRecord(params, reader); + case ESM4::REC_CSTY: break; + case ESM4::REC_DEBR: break; + case ESM4::REC_DIAL: return readTypedRecord(params, reader); + case ESM4::REC_DLBR: break; + case ESM4::REC_DLVW: break; + case ESM4::REC_DOBJ: return readTypedRecord(params, reader); + case ESM4::REC_DOOR: return readTypedRecord(params, reader); + case ESM4::REC_DUAL: break; + case ESM4::REC_ECZN: break; + case ESM4::REC_EFSH: break; + case ESM4::REC_ENCH: break; + case ESM4::REC_EQUP: break; + case ESM4::REC_EXPL: break; + case ESM4::REC_EYES: return readTypedRecord(params, reader); + case ESM4::REC_FACT: break; + case ESM4::REC_FLOR: return readTypedRecord(params, reader); + case ESM4::REC_FLST: return readTypedRecord(params, reader); + case ESM4::REC_FSTP: break; + case ESM4::REC_FSTS: break; + case ESM4::REC_FURN: return readTypedRecord(params, reader); + case ESM4::REC_GLOB: return readTypedRecord(params, reader); + case ESM4::REC_GMST: break; + case ESM4::REC_GRAS: return readTypedRecord(params, reader); + case ESM4::REC_GRUP: break; + case ESM4::REC_HAIR: return readTypedRecord(params, reader); + case ESM4::REC_HAZD: break; + case ESM4::REC_HDPT: return readTypedRecord(params, reader); + case ESM4::REC_IDLE: + // FIXME: ESM4::IdleAnimation::load does not work with Oblivion.esm + // return readTypedRecord(params, reader); + break; + case ESM4::REC_IDLM: return readTypedRecord(params, reader); + case ESM4::REC_IMAD: break; + case ESM4::REC_IMGS: break; + case ESM4::REC_IMOD: return readTypedRecord(params, reader); + case ESM4::REC_INFO: return readTypedRecord(params, reader); + case ESM4::REC_INGR: return readTypedRecord(params, reader); + case ESM4::REC_IPCT: break; + case ESM4::REC_IPDS: break; + case ESM4::REC_KEYM: return readTypedRecord(params, reader); + case ESM4::REC_KYWD: break; + case ESM4::REC_LAND: return readTypedRecord(params, reader); + case ESM4::REC_LCRT: break; + case ESM4::REC_LCTN: break; + case ESM4::REC_LGTM: return readTypedRecord(params, reader); + case ESM4::REC_LIGH: return readTypedRecord(params, reader); + case ESM4::REC_LSCR: break; + case ESM4::REC_LTEX: return readTypedRecord(params, reader); + case ESM4::REC_LVLC: return readTypedRecord(params, reader); + case ESM4::REC_LVLI: return readTypedRecord(params, reader); + case ESM4::REC_LVLN: return readTypedRecord(params, reader); + case ESM4::REC_LVSP: break; + case ESM4::REC_MATO: return readTypedRecord(params, reader); + case ESM4::REC_MATT: break; + case ESM4::REC_MESG: break; + case ESM4::REC_MGEF: break; + case ESM4::REC_MISC: return readTypedRecord(params, reader); + case ESM4::REC_MOVT: break; + case ESM4::REC_MSET: return readTypedRecord(params, reader); + case ESM4::REC_MSTT: return readTypedRecord(params, reader); + case ESM4::REC_MUSC: return readTypedRecord(params, reader); + case ESM4::REC_MUST: break; + case ESM4::REC_NAVI: return readTypedRecord(params, reader); + case ESM4::REC_NAVM: return readTypedRecord(params, reader); + case ESM4::REC_NOTE: return readTypedRecord(params, reader); + case ESM4::REC_NPC_: return readTypedRecord(params, reader); + case ESM4::REC_OTFT: return readTypedRecord(params, reader); + case ESM4::REC_PACK: return readTypedRecord(params, reader); + case ESM4::REC_PERK: break; + case ESM4::REC_PGRD: return readTypedRecord(params, reader); + case ESM4::REC_PGRE: return readTypedRecord(params, reader); + case ESM4::REC_PHZD: break; + case ESM4::REC_PROJ: break; + case ESM4::REC_PWAT: return readTypedRecord(params, reader); + case ESM4::REC_QUST: return readTypedRecord(params, reader); + case ESM4::REC_RACE: return readTypedRecord(params, reader); + case ESM4::REC_REFR: return readTypedRecord(params, reader); + case ESM4::REC_REGN: return readTypedRecord(params, reader); + case ESM4::REC_RELA: break; + case ESM4::REC_REVB: break; + case ESM4::REC_RFCT: break; + case ESM4::REC_ROAD: return readTypedRecord(params, reader); + case ESM4::REC_SBSP: return readTypedRecord(params, reader); + case ESM4::REC_SCEN: break; + case ESM4::REC_SCOL: return readTypedRecord(params, reader); + case ESM4::REC_SCPT: return readTypedRecord(params, reader); + case ESM4::REC_SCRL: return readTypedRecord(params, reader); + case ESM4::REC_SGST: return readTypedRecord(params, reader); + case ESM4::REC_SHOU: break; + case ESM4::REC_SLGM: return readTypedRecord(params, reader); + case ESM4::REC_SMBN: break; + case ESM4::REC_SMEN: break; + case ESM4::REC_SMQN: break; + case ESM4::REC_SNCT: break; + case ESM4::REC_SNDR: return readTypedRecord(params, reader); + case ESM4::REC_SOPM: break; + case ESM4::REC_SOUN: return readTypedRecord(params, reader); + case ESM4::REC_SPEL: break; + case ESM4::REC_SPGD: break; + case ESM4::REC_STAT: return readTypedRecord(params, reader); + case ESM4::REC_TACT: return readTypedRecord(params, reader); + case ESM4::REC_TERM: return readTypedRecord(params, reader); + case ESM4::REC_TES4: return readTypedRecord(params, reader); + case ESM4::REC_TREE: return readTypedRecord(params, reader); + case ESM4::REC_TXST: return readTypedRecord(params, reader); + case ESM4::REC_VTYP: break; + case ESM4::REC_WATR: break; + case ESM4::REC_WEAP: return readTypedRecord(params, reader); + case ESM4::REC_WOOP: break; + case ESM4::REC_WRLD: return readTypedRecord(params, reader); + case ESM4::REC_WTHR: break; + } + + if (!params.mQuite) + std::cout << "\n Unsupported record: " << ESM::NAME(reader.hdr().record.typeId).toStringView() << '\n'; + + reader.skipRecordData(); + } + + bool readItem(const Params& params, ESM4::Reader& reader); + + bool readGroup(const Params& params, ESM4::Reader& reader) + { + const ESM4::RecordHeader& header = reader.hdr(); + + if (!params.mQuite) + std::cout << "\nGroup: " << toString(static_cast(header.group.type)) + << " " << ESM::NAME(header.group.typeId).toStringView() << '\n'; + + switch (static_cast(header.group.type)) + { + case ESM4::Grp_RecordType: + case ESM4::Grp_InteriorCell: + case ESM4::Grp_InteriorSubCell: + case ESM4::Grp_ExteriorCell: + case ESM4::Grp_ExteriorSubCell: + reader.enterGroup(); + return readItem(params, reader); + case ESM4::Grp_WorldChild: + case ESM4::Grp_CellChild: + case ESM4::Grp_TopicChild: + case ESM4::Grp_CellPersistentChild: + case ESM4::Grp_CellTemporaryChild: + case ESM4::Grp_CellVisibleDistChild: + reader.adjustGRUPFormId(); + reader.enterGroup(); + if (!reader.hasMoreRecs()) + return false; + return readItem(params, reader); + } + + reader.skipGroup(); + + return true; + } + + bool readItem(const Params& params, ESM4::Reader& reader) + { + if (!reader.getRecordHeader() || !reader.hasMoreRecs()) + return false; + + const ESM4::RecordHeader& header = reader.hdr(); + + if (header.record.typeId == ESM4::REC_GRUP) + return readGroup(params, reader); + + readRecord(params, reader); + return true; + } + } + + int loadTes4(const Arguments& info, std::unique_ptr&& stream) + { + std::cout << "Loading TES4 file: " << info.filename << '\n'; + + try + { + const ToUTF8::StatelessUtf8Encoder encoder(ToUTF8::calculateEncoding(info.encoding)); + ESM4::Reader reader(std::move(stream), info.filename); + reader.setEncoder(&encoder); + const Params params(info); + + if (!params.mQuite) + { + std::cout << "Author: " << reader.getAuthor() << '\n' + << "Description: " << reader.getDesc() << '\n' + << "File format version: " << reader.esmVersion() << '\n'; + + if (const std::vector& masterData = reader.getGameFiles(); !masterData.empty()) + { + std::cout << "Masters:" << '\n'; + for (const auto& master : masterData) + std::cout << " " << master.name << ", " << master.size << " bytes\n"; + } + } + + while (reader.hasMoreRecs()) + { + reader.exitGroupCheck(); + if (!readItem(params, reader)) + break; + } + } + catch (const std::exception& e) + { + std::cout << "\nERROR:\n\n " << e.what() << std::endl; + return -1; + } + + return 0; + } +} diff --git a/apps/esmtool/tes4.hpp b/apps/esmtool/tes4.hpp new file mode 100644 index 0000000000..8149b26049 --- /dev/null +++ b/apps/esmtool/tes4.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESMTOOL_TES4_H +#define OPENMW_ESMTOOL_TES4_H + +#include +#include +#include + +namespace EsmTool +{ + struct Arguments; + + int loadTes4(const Arguments& info, std::unique_ptr&& stream); +} + +#endif diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7121bde103..32c7c9535f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -80,7 +80,7 @@ add_component_dir (to_utf8 to_utf8 ) -add_component_dir(esm attr common defs esmcommon reader records util luascripts) +add_component_dir(esm attr common defs esmcommon reader records util luascripts format) add_component_dir (esm3 esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell diff --git a/components/esm/format.cpp b/components/esm/format.cpp new file mode 100644 index 0000000000..b41d81d57d --- /dev/null +++ b/components/esm/format.cpp @@ -0,0 +1,40 @@ +#include "format.hpp" + +#include + +namespace ESM +{ + namespace + { + bool isValidFormat(std::uint32_t value) + { + return value == static_cast(Format::Tes3) + || value == static_cast(Format::Tes4); + } + + Format toFormat(std::uint32_t value) + { + if (!isValidFormat(value)) + throw std::runtime_error("Invalid format: " + std::to_string(value)); + return static_cast(value); + } + } + + Format readFormat(std::istream& stream) + { + std::uint32_t format = 0; + stream.read(reinterpret_cast(&format), sizeof(format)); + if (stream.gcount() != sizeof(format)) + throw std::runtime_error("Not enough bytes to read file header"); + return toFormat(format); + } + + Format parseFormat(std::string_view value) + { + if (value.size() != sizeof(std::uint32_t)) + throw std::logic_error("Invalid format value: " + std::string(value)); + std::uint32_t format; + std::memcpy(&format, value.data(), sizeof(std::uint32_t)); + return toFormat(format); + } +} diff --git a/components/esm/format.hpp b/components/esm/format.hpp new file mode 100644 index 0000000000..acd93b4075 --- /dev/null +++ b/components/esm/format.hpp @@ -0,0 +1,23 @@ +#ifndef COMPONENT_ESM_FORMAT_H +#define COMPONENT_ESM_FORMAT_H + +#include "defs.hpp" + +#include +#include +#include + +namespace ESM +{ + enum class Format : std::uint32_t + { + Tes3 = fourCC("TES3"), + Tes4 = fourCC("TES4"), + }; + + Format readFormat(std::istream& stream); + + Format parseFormat(std::string_view value); +} + +#endif diff --git a/components/esm4/common.hpp b/components/esm4/common.hpp index b92a48e070..11286e4b03 100644 --- a/components/esm4/common.hpp +++ b/components/esm4/common.hpp @@ -76,7 +76,7 @@ namespace ESM4 REC_DOBJ = fourCC("DOBJ"), // Default Object Manager REC_DOOR = fourCC("DOOR"), // Door REC_DUAL = fourCC("DUAL"), // Dual Cast Data (possibly unused) - //REC_ECZN = fourCC("ECZN"), // Encounter Zone + REC_ECZN = fourCC("ECZN"), // Encounter Zone REC_EFSH = fourCC("EFSH"), // Effect Shader REC_ENCH = fourCC("ENCH"), // Enchantment REC_EQUP = fourCC("EQUP"), // Equip Slot (flag-type values) @@ -93,7 +93,7 @@ namespace ESM4 REC_GRAS = fourCC("GRAS"), // Grass REC_GRUP = fourCC("GRUP"), // Form Group REC_HAIR = fourCC("HAIR"), // Hair - //REC_HAZD = fourCC("HAZD"), // Hazard + REC_HAZD = fourCC("HAZD"), // Hazard REC_HDPT = fourCC("HDPT"), // Head Part REC_IDLE = fourCC("IDLE"), // Idle Animation REC_IDLM = fourCC("IDLM"), // Idle Marker diff --git a/components/esm4/records.hpp b/components/esm4/records.hpp new file mode 100644 index 0000000000..ae2a7a78f7 --- /dev/null +++ b/components/esm4/records.hpp @@ -0,0 +1,84 @@ +#ifndef COMPONENTS_ESM4_RECORDS_H +#define COMPONENTS_ESM4_RECORDS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif