Merge branch 'fallout4' into 'master'

ESM4 Loader: initial Fallout 4 support

See merge request OpenMW/openmw!3347
macos_ci_fix
psi29a 1 year ago
commit 16628a766f

@ -329,8 +329,7 @@ namespace EsmTool
readTypedRecord<ESM4::HeadPart>(params, reader);
return true;
case ESM4::REC_IDLE:
// FIXME: ESM4::IdleAnimation::load does not work with Oblivion.esm
// readTypedRecord<ESM4::IdleAnimation>(params, reader);
readTypedRecord<ESM4::IdleAnimation>(params, reader);
return true;
break;
case ESM4::REC_IDLM:

@ -40,7 +40,7 @@ namespace
{
void contentSubdirs(const QString& path, QStringList& dirs)
{
QStringList fileFilter{ "*.esm", "*.esp", "*.omwaddon", "*.bsa", "*.omwscripts" };
QStringList fileFilter{ "*.esm", "*.esp", "*.omwaddon", "*.bsa", "*.ba2", "*.omwscripts" };
QStringList dirFilter{ "bookart", "icons", "meshes", "music", "sound", "textures" };
QDir currentDir(path);
@ -722,13 +722,14 @@ void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState sel
void Launcher::DataFilesPage::addArchivesFromDir(const QString& path)
{
QDir dir(path, "*.bsa");
QStringList archiveFilter{ "*.bsa", "*.ba2" };
QDir dir(path);
std::unordered_set<QString> archives;
for (int i = 0; i < ui.archiveListWidget->count(); ++i)
archives.insert(ui.archiveListWidget->item(i)->text());
for (const auto& fileinfo : dir.entryInfoList())
for (const auto& fileinfo : dir.entryInfoList(archiveFilter))
{
const auto absPath = fileinfo.absoluteFilePath();
if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BSAVER_UNKNOWN)

@ -100,11 +100,25 @@ namespace ESM4
std::uint16_t bleedoutOverride;
};
struct ACBS_FO4
{
std::uint32_t flags;
std::int16_t xpOffset;
std::int16_t levelOrMult;
std::uint16_t calcMinlevel;
std::uint16_t calcMaxlevel;
std::int16_t dispositionBase;
std::uint16_t templateFlags;
std::uint16_t bleedoutOverride;
std::uint16_t padding;
};
union ActorBaseConfig
{
ACBS_TES4 tes4;
ACBS_FO3 fo3;
ACBS_TES5 tes5;
ACBS_FO4 fo4;
};
struct ActorFaction

File diff suppressed because it is too large Load Diff

@ -58,8 +58,26 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader)
reader.get(mScale);
break;
case ESM4::SUB_XOWN:
reader.getFormId(mOwner);
{
switch (subHdr.dataSize)
{
case 4:
reader.getFormId(mOwner);
break;
case 12:
{
reader.getFormId(mOwner);
std::uint32_t dummy;
reader.get(dummy); // Unknown
reader.get(dummy); // No crime flag, FO4
break;
}
default:
reader.skipSubRecordData();
break;
}
break;
}
case ESM4::SUB_XESP:
reader.getFormId(mEsp.parent);
reader.get(mEsp.flags);
@ -93,6 +111,20 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader)
case ESM4::SUB_SCHR: // FO3
case ESM4::SUB_TNAM: // FO3
case ESM4::SUB_XATO: // FONV
case ESM4::SUB_MNAM: // FO4
case ESM4::SUB_XATP: // FO4
case ESM4::SUB_XCNT: // FO4
case ESM4::SUB_XEMI: // FO4
case ESM4::SUB_XFVC: // FO4
case ESM4::SUB_XHLT: // FO4
case ESM4::SUB_XHTW: // FO4
case ESM4::SUB_XLKT: // FO4
case ESM4::SUB_XLYR: // FO4
case ESM4::SUB_XMBR: // FO4
case ESM4::SUB_XMSP: // FO4
case ESM4::SUB_XPLK: // FO4
case ESM4::SUB_XRFG: // FO4
case ESM4::SUB_XRNK: // FO4
reader.skipSubRecordData();
break;
default:

@ -71,14 +71,19 @@ void ESM4::Activator::load(ESM4::Reader& reader)
case ESM4::SUB_XATO:
reader.getZString(mActivationPrompt);
break; // FONV
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDS:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_FNAM:
case ESM4::SUB_KNAM:
case ESM4::SUB_KSIZ:
@ -87,6 +92,18 @@ void ESM4::Activator::load(ESM4::Reader& reader)
case ESM4::SUB_PNAM:
case ESM4::SUB_VMAD:
case ESM4::SUB_WNAM:
case ESM4::SUB_CTDA:
case ESM4::SUB_CIS1:
case ESM4::SUB_CIS2:
case ESM4::SUB_CITC:
case ESM4::SUB_NVNM:
case ESM4::SUB_ATTX: // FO4
case ESM4::SUB_FTYP: // FO4
case ESM4::SUB_NTRM: // FO4
case ESM4::SUB_PTRN: // FO4
case ESM4::SUB_PRPS: // FO4
case ESM4::SUB_RADR: // FO4
case ESM4::SUB_STCP: // FO4
reader.skipSubRecordData();
break;
default:

@ -88,15 +88,30 @@ void ESM4::Potion::load(ESM4::Reader& reader)
case ESM4::SUB_ZNAM:
reader.getFormId(mDropSound);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_EFID:
case ESM4::SUB_EFIT:
case ESM4::SUB_CTDA:
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_MODS:
case ESM4::SUB_OBND:
case ESM4::SUB_ETYP: // FO3
case ESM4::SUB_DESC:
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_DNAM: // FO4
case ESM4::SUB_PTRN: // FO4
case ESM4::SUB_CUSD: // FO4
reader.skipSubRecordData();
break;
default:

@ -48,36 +48,42 @@ void ESM4::Ammunition::load(ESM4::Reader& reader)
reader.getLocalizedString(mFullName);
break;
case ESM4::SUB_DATA:
// FO3/FNV or TES4
if (subHdr.dataSize == 13 || subHdr.dataSize == 18)
switch (subHdr.dataSize)
{
reader.get(mData.mSpeed);
reader.get(mData.mFlags);
mData.mFlags &= 0xFF;
reader.get(mData.mValue);
if (subHdr.dataSize == 13)
reader.get(mData.mClipRounds);
else
case 18: // TES4
case 13: // FO3/FNV
{
reader.get(mData.mWeight);
std::uint16_t damageInt;
reader.get(damageInt);
mData.mDamage = static_cast<float>(damageInt);
reader.get(mData.mSpeed);
reader.get(mData.mFlags);
mData.mFlags &= 0xFF;
reader.get(mData.mValue);
if (subHdr.dataSize == 13)
reader.get(mData.mClipRounds);
else
{
reader.get(mData.mWeight);
std::uint16_t damageInt;
reader.get(damageInt);
mData.mDamage = static_cast<float>(damageInt);
}
break;
}
}
// TES5/SSE
else if (subHdr.dataSize == 16 || subHdr.dataSize == 20)
{
reader.getFormId(mData.mProjectile);
reader.get(mData.mFlags);
reader.get(mData.mDamage);
reader.get(mData.mValue);
if (subHdr.dataSize == 20)
case 16: // TES5
case 20: // SSE
reader.getFormId(mData.mProjectile);
reader.get(mData.mFlags);
reader.get(mData.mDamage);
reader.get(mData.mValue);
if (subHdr.dataSize == 20)
reader.get(mData.mWeight);
break;
case 8:
reader.get(mData.mValue);
reader.get(mData.mWeight);
}
else
{
reader.skipSubRecordData();
break;
default:
reader.skipSubRecordData();
break;
}
break;
case ESM4::SUB_DAT2:
@ -94,6 +100,13 @@ void ESM4::Ammunition::load(ESM4::Reader& reader)
reader.skipSubRecordData();
}
break;
case ESM4::SUB_DNAM:
reader.getFormId(mData.mProjectile);
reader.get(mData.mFlags);
mData.mFlags &= 0xFF;
reader.get(mData.mDamage);
reader.get(mData.mHealth);
break;
case ESM4::SUB_ICON:
reader.getZString(mIcon);
break;
@ -133,10 +146,25 @@ void ESM4::Ammunition::load(ESM4::Reader& reader)
case ESM4::SUB_SCRI:
reader.getFormId(mScript);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_OBND:
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_PTRN: // FO4
case ESM4::SUB_NAM1: // FO4 casing model data
case ESM4::SUB_NAM2: //
reader.skipSubRecordData();
break;
default:

@ -53,6 +53,7 @@ namespace ESM4
ESM::FormId mProjectile;
ESM::FormId mConsumedAmmo;
float mConsumedPercentage{ 0.f };
std::uint32_t mHealth{ 0u };
};
ESM::FormId mId; // from the header

@ -56,8 +56,10 @@ void ESM4::AnimObject::load(ESM4::Reader& reader)
case ESM4::SUB_MODB:
reader.get(mBoundRadius);
break;
case ESM4::SUB_MODT: // TES5 only
case ESM4::SUB_MODS: // TES5 only
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
reader.skipSubRecordData();
break;
default:

@ -83,24 +83,35 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader)
reader.get(mBodyTemplate.unknown3); // probably padding
reader.get(mBodyTemplate.type);
break;
case ESM4::SUB_BOD2: // TES5
case ESM4::SUB_BOD2: // TES5+
reader.get(mBodyTemplate.bodyPart);
mBodyTemplate.flags = 0;
mBodyTemplate.unknown1 = 0; // probably padding
mBodyTemplate.unknown2 = 0; // probably padding
mBodyTemplate.unknown3 = 0; // probably padding
reader.get(mBodyTemplate.type);
mBodyTemplate.type = 0;
if (subHdr.dataSize == 8)
reader.get(mBodyTemplate.type);
break;
case ESM4::SUB_DNAM:
case ESM4::SUB_MO2T: // FIXME: should group with MOD2
case ESM4::SUB_MO2S: // FIXME: should group with MOD2
case ESM4::SUB_MO2C: // FIXME: should group with MOD2
case ESM4::SUB_MO2F: // FIXME: should group with MOD2
case ESM4::SUB_MO3T: // FIXME: should group with MOD3
case ESM4::SUB_MO3S: // FIXME: should group with MOD3
case ESM4::SUB_MO3C: // FIXME: should group with MOD3
case ESM4::SUB_MO3F: // FIXME: should group with MOD3
case ESM4::SUB_MOSD: // FO3 // FIXME: should group with MOD3
case ESM4::SUB_MO4T: // FIXME: should group with MOD4
case ESM4::SUB_MO4S: // FIXME: should group with MOD4
case ESM4::SUB_MO4C: // FIXME: should group with MOD4
case ESM4::SUB_MO4F: // FIXME: should group with MOD4
case ESM4::SUB_MO5T:
case ESM4::SUB_MO5S:
case ESM4::SUB_MO5C:
case ESM4::SUB_MO5F:
case ESM4::SUB_NAM2: // txst formid male
case ESM4::SUB_NAM3: // txst formid female
case ESM4::SUB_SNDD: // footset sound formid
@ -114,6 +125,10 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader)
case ESM4::SUB_MODS: // FO3 // FIXME: should group with MODL
case ESM4::SUB_MODD: // FO3 // FIXME: should group with MODL
case ESM4::SUB_OBND: // FO3
case ESM4::SUB_BSMB: // FO4
case ESM4::SUB_BSMP: // FO4
case ESM4::SUB_BSMS: // FO4
case ESM4::SUB_ONAM: // FO4
reader.skipSubRecordData();
break;
default:

@ -35,9 +35,7 @@ void ESM4::Armor::load(ESM4::Reader& reader)
{
mId = reader.getFormIdFromHeader();
mFlags = reader.hdr().record.flags;
std::uint32_t esmVer = reader.esmVersion();
mIsFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134;
uint16_t currentIndex = 0xFFFF;
while (reader.getSubRecordHeader())
{
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
@ -51,32 +49,46 @@ void ESM4::Armor::load(ESM4::Reader& reader)
break;
case ESM4::SUB_DATA:
{
// if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170)
if (subHdr.dataSize == 8) // FO3 has 12 bytes even though VER_094
{
reader.get(mData.value);
reader.get(mData.weight);
mIsFO3 = true;
}
else if (mIsFONV || subHdr.dataSize == 12)
{
reader.get(mData.value);
reader.get(mData.health);
reader.get(mData.weight);
}
else
switch (subHdr.dataSize)
{
reader.get(mData); // TES4
mIsTES4 = true;
case 14: // TES4
reader.get(mData);
break;
case 12: // FO3, FNV, FO4
reader.get(mData.value);
reader.get(mData.health);
reader.get(mData.weight);
break;
case 8: // TES5
reader.get(mData.value);
reader.get(mData.weight);
break;
default:
reader.skipSubRecordData();
break;
}
break;
}
case ESM4::SUB_MODL: // seems only for Dawnguard/Dragonborn?
case ESM4::SUB_INDX: // FO4
{
// if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV)
if (subHdr.dataSize == 4) // FO3 has zstring even though VER_094
reader.getFormId(mAddOns.emplace_back());
reader.get(currentIndex);
break;
}
case ESM4::SUB_MODL:
{
if (subHdr.dataSize == 4)
{
// Assuming TES5
if (currentIndex == 0xFFFF)
reader.getFormId(mAddOns.emplace_back());
// FO4
else
{
if (mAddOns.size() <= currentIndex)
mAddOns.resize(currentIndex + 1);
reader.getFormId(mAddOns[currentIndex]);
}
}
else
{
if (!reader.getZString(mModelMale))
@ -135,10 +147,21 @@ void ESM4::Armor::load(ESM4::Reader& reader)
break;
}
case ESM4::SUB_BOD2:
reader.get(mArmorFlags);
reader.get(mGeneralFlags);
mGeneralFlags &= 0x0000000f; // 0 (light), 1 (heavy) or 2 (none)
mGeneralFlags |= TYPE_TES5;
// FO4, TES5
if (subHdr.dataSize == 4 || subHdr.dataSize == 8)
{
reader.get(mArmorFlags);
if (subHdr.dataSize == 8)
{
reader.get(mGeneralFlags);
mGeneralFlags &= 0x0000000f; // 0 (light), 1 (heavy) or 2 (none)
mGeneralFlags |= TYPE_TES5;
}
}
else
{
reader.skipSubRecordData();
}
break;
case ESM4::SUB_SCRI:
reader.getFormId(mScriptId);
@ -191,6 +214,24 @@ void ESM4::Armor::load(ESM4::Reader& reader)
case ESM4::SUB_MO3S: // FO3
case ESM4::SUB_BNAM: // FONV
case ESM4::SUB_SNAM: // FONV
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_APPR: // FO4
case ESM4::SUB_DAMA: // FO4
case ESM4::SUB_FNAM: // FO4
case ESM4::SUB_INRD: // FO4
case ESM4::SUB_PTRN: // FO4
case ESM4::SUB_OBTE: // FO4 object template start
case ESM4::SUB_OBTF:
case ESM4::SUB_OBTS:
case ESM4::SUB_STOP: // FO4 object template end
reader.skipSubRecordData();
break;
default:

@ -152,10 +152,6 @@ namespace ESM4
ESM::FormId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
bool mIsTES4; // TODO: check that these match the general flags
bool mIsFO3;
bool mIsFONV;
std::string mEditorId;
std::string mFullName;
std::string mModelMale;

@ -56,10 +56,16 @@ void ESM4::AcousticSpace::load(ESM4::Reader& reader)
case ESM4::SUB_INAM:
reader.get(mIsInterior);
break;
case ESM4::SUB_WNAM: // usually 0 for FONV (maybe # of close Actors for Walla to trigger)
case ESM4::SUB_XTRI:
std::uint8_t isInterior;
reader.get(isInterior);
mIsInterior = isInterior;
break;
case ESM4::SUB_WNAM:
{
std::uint32_t dummy;
reader.get(dummy);
// usually 0 for FONV (maybe # of close Actors for Walla to trigger) (4 bytes)
// Weather attenuation in FO4 (2 bytes)
reader.skipSubRecordData();
break;
}
case ESM4::SUB_BNAM: // TES5 reverb formid

@ -53,22 +53,33 @@ void ESM4::Book::load(ESM4::Reader& reader)
break;
case ESM4::SUB_DATA:
{
reader.get(mData.flags);
// if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170)
if (subHdr.dataSize == 16) // FO3 has 10 bytes even though VER_094
switch (subHdr.dataSize)
{
static std::uint8_t dummy;
reader.get(mData.type);
reader.get(dummy);
reader.get(dummy);
reader.get(mData.teaches);
case 10: // TES4, FO3, FNV
reader.get(mData.flags);
reader.get(mData.bookSkill);
reader.get(mData.value);
reader.get(mData.weight);
break;
case 16: // TES5
{
reader.get(mData.flags);
reader.get(mData.type);
std::uint16_t dummy;
reader.get(dummy);
reader.get(mData.teaches);
reader.get(mData.value);
reader.get(mData.weight);
break;
}
case 8: // FO4
reader.get(mData.value);
reader.get(mData.weight);
break;
default:
reader.skipSubRecordData();
break;
}
else
{
reader.get(mData.bookSkill);
}
reader.get(mData.value);
reader.get(mData.weight);
break;
}
case ESM4::SUB_ICON:
@ -94,14 +105,30 @@ void ESM4::Book::load(ESM4::Reader& reader)
break;
case ESM4::SUB_ZNAM:
reader.getFormId(mDropSound);
break; // TODO: does this exist?
case ESM4::SUB_MODT:
break;
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_OBND:
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_CNAM:
case ESM4::SUB_INAM:
case ESM4::SUB_VMAD:
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_DNAM: // FO4
case ESM4::SUB_FIMD: // FO4
case ESM4::SUB_MICO: // FO4
case ESM4::SUB_PTRN: // FO4
reader.skipSubRecordData();
break;
default:

@ -78,7 +78,11 @@ void ESM4::BodyPartData::load(ESM4::Reader& reader)
reader.getZString(bodyPart.mIKStartNode);
break;
case ESM4::SUB_BPND:
reader.get(bodyPart.mData);
if (subHdr.dataSize == sizeof(bodyPart.mData))
reader.get(bodyPart.mData);
// FIXME: FO4
else
reader.skipSubRecordData();
break;
case ESM4::SUB_NAM1:
reader.getZString(bodyPart.mLimbReplacementModel);
@ -90,8 +94,18 @@ void ESM4::BodyPartData::load(ESM4::Reader& reader)
break;
case ESM4::SUB_NAM5:
case ESM4::SUB_RAGA: // ragdoll
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODT:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_BNAM: // FO4
case ESM4::SUB_CNAM: // FO4
case ESM4::SUB_DNAM: // FO4
case ESM4::SUB_ENAM: // FO4
case ESM4::SUB_FNAM: // FO4
case ESM4::SUB_INAM: // FO4
case ESM4::SUB_JNAM: // FO4
case ESM4::SUB_NAM2: // FO4
reader.skipSubRecordData();
break;
default:

@ -71,7 +71,6 @@ void ESM4::Cell::load(ESM4::Reader& reader)
// (may be easier to update the context before saving?)
reader.setCurrCell(formId); // save for LAND (and other children) to access later
std::uint32_t esmVer = reader.esmVersion();
bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134;
bool isSkyrim = (esmVer == ESM::VER_170 || esmVer == ESM::VER_094);
while (reader.getSubRecordHeader())
@ -106,16 +105,8 @@ void ESM4::Cell::load(ESM4::Reader& reader)
uint32_t flags;
reader.get(mX);
reader.get(mY);
#if 0
std::string padding;
padding.insert(0, reader.stackSize()*2, ' ');
std::cout << padding << "CELL group " << ESM4::printLabel(reader.grp().label, reader.grp().type) << std::endl;
std::cout << padding << "CELL formId " << std::hex << reader.hdr().record.id << std::endl;
std::cout << padding << "CELL X " << std::dec << mX << ", Y " << mY << std::endl;
#endif
if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV)
if (subHdr.dataSize == 12)
reader.get(flags); // not in Obvlivion, nor FO3/FONV
if (subHdr.dataSize == 12)
reader.get(flags);
// Remember cell grid for later (loading LAND, NAVM which should be CELL temporary children)
// Note that grids only apply for external cells. For interior cells use the cell's formid.
@ -128,28 +119,16 @@ void ESM4::Cell::load(ESM4::Reader& reader)
break;
case ESM4::SUB_DATA:
{
if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV)
if (subHdr.dataSize == 2)
reader.get(mCellFlags);
else
{
if (subHdr.dataSize != 1)
throw std::runtime_error("CELL unexpected DATA flag size");
std::uint8_t value = 0;
reader.get(value);
mCellFlags = value;
}
if (subHdr.dataSize == 2)
reader.get(mCellFlags);
else
{
if (subHdr.dataSize != 1)
throw std::runtime_error("CELL unexpected DATA flag size");
std::uint8_t value = 0;
reader.get(value); // 8 bits in Obvlivion
reader.get(value);
mCellFlags = value;
}
#if 0
std::string padding;
padding.insert(0, reader.stackSize()*2, ' ');
std::cout << padding << "flags: " << std::hex << mCellFlags << std::endl;
#endif
break;
}
case ESM4::SUB_XCLR: // for exterior cells
@ -167,8 +146,26 @@ void ESM4::Cell::load(ESM4::Reader& reader)
break;
}
case ESM4::SUB_XOWN:
reader.getFormId(mOwner);
{
switch (subHdr.dataSize)
{
case 4:
reader.getFormId(mOwner);
break;
case 12:
{
reader.getFormId(mOwner);
std::uint32_t dummy;
reader.get(dummy); // Unknown
reader.get(dummy); // No crime flag, FO4
break;
}
default:
reader.skipSubRecordData();
break;
}
break;
}
case ESM4::SUB_XGLB:
reader.getFormId(mGlobal);
break; // Oblivion only?
@ -183,21 +180,21 @@ void ESM4::Cell::load(ESM4::Reader& reader)
break;
case ESM4::SUB_XCLL:
{
if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV)
switch (subHdr.dataSize)
{
if (subHdr.dataSize == 40) // FO3/FONV
case 36: // TES4
reader.get(&mLighting, 36);
break;
case 40: // FO3/FNV
case 92: // TES5 (FIXME)
case 136: // FO4 (FIXME)
reader.get(mLighting);
else if (subHdr.dataSize == 92) // TES5
{
reader.get(mLighting);
reader.skipSubRecordData(52); // FIXME
}
else
reader.skipSubRecordData(subHdr.dataSize - 40);
break;
default:
reader.skipSubRecordData();
break;
}
else
reader.get(&mLighting, 36); // TES4
break;
}
case ESM4::SUB_XCMT:
@ -227,9 +224,18 @@ void ESM4::Cell::load(ESM4::Reader& reader)
case ESM4::SUB_XEZN:
case ESM4::SUB_XWEM:
case ESM4::SUB_XILL:
case ESM4::SUB_XRNK: // Oblivion only?
case ESM4::SUB_XRNK:
case ESM4::SUB_XCET: // FO3
case ESM4::SUB_IMPF: // FO3 Zeta
case ESM4::SUB_CNAM: // FO4
case ESM4::SUB_PCMB: // FO4
case ESM4::SUB_RVIS: // FO4
case ESM4::SUB_VISI: // FO4
case ESM4::SUB_XGDR: // FO4
case ESM4::SUB_XILW: // FO4
case ESM4::SUB_XCRI: // FO4
case ESM4::SUB_XPRI: // FO4
case ESM4::SUB_ZNAM: // FO4
reader.skipSubRecordData();
break;
default:

@ -55,6 +55,7 @@ void ESM4::Class::load(ESM4::Reader& reader)
break;
case ESM4::SUB_DATA:
case ESM4::SUB_ATTR:
case ESM4::SUB_PRPS:
reader.skipSubRecordData();
break;
default:

@ -56,6 +56,9 @@ void ESM4::Colour::load(ESM4::Reader& reader)
case ESM4::SUB_FNAM:
reader.get(mPlayable);
break;
case ESM4::SUB_CTDA:
reader.skipSubRecordData();
break;
default:
throw std::runtime_error("ESM4::CLFM::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
}

@ -74,18 +74,32 @@ void ESM4::Container::load(ESM4::Reader& reader)
case ESM4::SUB_MODB:
reader.get(mBoundRadius);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODS: // TES5 only
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_VMAD: // TES5 only
case ESM4::SUB_OBND: // TES5 only
case ESM4::SUB_COCT: // TES5 only
case ESM4::SUB_COED: // TES5 only
case ESM4::SUB_DEST: // FONV
case ESM4::SUB_DSTD: // FONV
case ESM4::SUB_DSTF: // FONV
case ESM4::SUB_DMDL: // FONV
case ESM4::SUB_DMDT: // FONV
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_ONAM:
case ESM4::SUB_RNAM: // FONV
case ESM4::SUB_TNAM:
case ESM4::SUB_FTYP: // FO4
case ESM4::SUB_NTRM: // FO4
case ESM4::SUB_PRPS: // FO4
case ESM4::SUB_PTRN: // FO4
reader.skipSubRecordData();
break;
default:

@ -46,7 +46,7 @@ void ESM4::Dialogue::load(ESM4::Reader& reader)
reader.getZString(mEditorId);
break;
case ESM4::SUB_FULL:
reader.getZString(mTopicName);
reader.getLocalizedString(mTopicName);
break;
case ESM4::SUB_QSTI:
reader.getFormId(mQuests.emplace_back());
@ -87,6 +87,7 @@ void ESM4::Dialogue::load(ESM4::Reader& reader)
case ESM4::SUB_BNAM: // TES5
case ESM4::SUB_SNAM: // TES5
case ESM4::SUB_TIFC: // TES5
case ESM4::SUB_KNAM: // FO4
reader.skipSubRecordData();
break;
default:

@ -71,15 +71,27 @@ void ESM4::Door::load(ESM4::Reader& reader)
case ESM4::SUB_MODB:
reader.get(mBoundRadius);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_OBND:
case ESM4::SUB_VMAD:
case ESM4::SUB_DEST: // FO3
case ESM4::SUB_DSTD: // FO3
case ESM4::SUB_DSTF: // FO3
case ESM4::SUB_DMDL: // FO3
case ESM4::SUB_DMDT: // FO3
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_CNAM: // FO4
case ESM4::SUB_NTRM: // FO4
case ESM4::SUB_ONAM: // FO4
case ESM4::SUB_PTRN: // FO4
reader.skipSubRecordData();
break;
default:

@ -65,13 +65,29 @@ void ESM4::Flora::load(ESM4::Reader& reader)
case ESM4::SUB_MODB:
reader.get(mBoundRadius);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_FNAM:
case ESM4::SUB_OBND:
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_PNAM:
case ESM4::SUB_RNAM:
case ESM4::SUB_VMAD:
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_PRPS: // FO4
case ESM4::SUB_PTRN: // FO4
case ESM4::SUB_ATTX: // FO4
reader.skipSubRecordData();
break;
default:

@ -44,6 +44,9 @@ void ESM4::FormIdList::load(ESM4::Reader& reader)
case ESM4::SUB_EDID:
reader.getZString(mEditorId);
break;
case ESM4::SUB_FULL:
reader.getLocalizedString(mFullName);
break;
case ESM4::SUB_LNAM:
reader.getFormId(mObjects.emplace_back());
break;

@ -45,6 +45,7 @@ namespace ESM4
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId;
std::string mFullName;
std::vector<ESM::FormId> mObjects;

@ -45,8 +45,14 @@ void ESM4::Furniture::load(ESM4::Reader& reader)
reader.getZString(mEditorId);
break;
case ESM4::SUB_FULL:
reader.getLocalizedString(mFullName);
{
std::string name;
reader.getLocalizedString(name);
// FIXME: subsequent FULL subrecords name object combinations (FO4)
if (mFullName.empty())
mFullName = name;
break;
}
case ESM4::SUB_MODL:
reader.getZString(mModel);
break;
@ -59,10 +65,19 @@ void ESM4::Furniture::load(ESM4::Reader& reader)
case ESM4::SUB_MODB:
reader.get(mBoundRadius);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_ENAM:
case ESM4::SUB_FNAM:
case ESM4::SUB_FNMK:
@ -70,13 +85,33 @@ void ESM4::Furniture::load(ESM4::Reader& reader)
case ESM4::SUB_KNAM:
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_MODS:
case ESM4::SUB_NAM0:
case ESM4::SUB_OBND:
case ESM4::SUB_PNAM:
case ESM4::SUB_VMAD:
case ESM4::SUB_WBDT:
case ESM4::SUB_XMRK:
case ESM4::SUB_PRPS:
case ESM4::SUB_CTDA:
case ESM4::SUB_CIS1:
case ESM4::SUB_CIS2:
case ESM4::SUB_APPR: // FO4
case ESM4::SUB_ATTX: // FO4
case ESM4::SUB_CITC: // FO4
case ESM4::SUB_CNTO: // FO4
case ESM4::SUB_COCT: // FO4
case ESM4::SUB_COED: // FO4
case ESM4::SUB_FTYP: // FO4
case ESM4::SUB_NAM1: // FO4
case ESM4::SUB_NTRM: // FO4
case ESM4::SUB_NVNM: // FO4
case ESM4::SUB_PTRN: // FO4
case ESM4::SUB_SNAM: // FO4
case ESM4::SUB_WNAM: // FO4
case ESM4::SUB_OBTE: // FO4 object template start
case ESM4::SUB_OBTF:
case ESM4::SUB_OBTS:
case ESM4::SUB_STOP: // FO4 object template end
reader.skipSubRecordData();
break;
default:

@ -41,7 +41,13 @@ namespace ESM4
case 's':
{
std::string value;
reader.getZString(value);
reader.getLocalizedString(value);
return value;
}
case 'u':
{
std::uint32_t value = 0;
reader.get(value);
return value;
}
default:

@ -14,7 +14,7 @@ namespace ESM4
struct GameSetting
{
using Data = std::variant<std::monostate, bool, float, std::int32_t, std::string>;
using Data = std::variant<std::monostate, bool, float, std::int32_t, std::string, std::uint32_t>;
ESM::FormId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details

@ -53,7 +53,10 @@ void ESM4::Grass::load(ESM4::Reader& reader)
case ESM4::SUB_MODB:
reader.get(mBoundRadius);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_OBND:
reader.skipSubRecordData();
break;

@ -88,10 +88,13 @@ void ESM4::HeadPart::load(ESM4::Reader& reader)
reader.getFormId(mBaseTexture);
break;
case ESM4::SUB_PNAM:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODT:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_RNAM:
case ESM4::SUB_CNAM:
case ESM4::SUB_CTDA:
reader.skipSubRecordData();
break;
default:

@ -51,9 +51,29 @@ void ESM4::IdleAnimation::load(ESM4::Reader& reader)
reader.getZString(mEvent);
break;
case ESM4::SUB_ANAM:
reader.getFormId(mParent);
reader.getFormId(mPrevious);
{
switch (subHdr.dataSize)
{
case 1: // TES4
{
// Animation group section
uint8_t dummy;
reader.get(dummy);
break;
}
case 8: // Everything else
{
// These IDs go into DATA for TES4
reader.getFormId(mParent);
reader.getFormId(mPrevious);
break;
}
default:
reader.skipSubRecordData();
break;
}
break;
}
case ESM4::SUB_MODL:
reader.getZString(mModel);
break;
@ -61,7 +81,14 @@ void ESM4::IdleAnimation::load(ESM4::Reader& reader)
reader.get(mBoundRadius);
break;
case ESM4::SUB_CTDA: // formId
case ESM4::SUB_DATA: // formId
case ESM4::SUB_CTDT:
case ESM4::SUB_CIS1:
case ESM4::SUB_CIS2:
case ESM4::SUB_DATA:
case ESM4::SUB_MODD:
case ESM4::SUB_MODS:
case ESM4::SUB_MODT:
case ESM4::SUB_GNAM: // FO4
reader.skipSubRecordData();
break;
default:

@ -79,8 +79,13 @@ void ESM4::IdleMarker::load(ESM4::Reader& reader)
reader.getZString(mModel);
break;
case ESM4::SUB_OBND: // object bounds
case ESM4::SUB_MODT:
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_QNAM:
reader.skipSubRecordData();
break;
default:

@ -71,7 +71,7 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader)
break;
}
case ESM4::SUB_NAM1:
reader.getZString(mResponse);
reader.getLocalizedString(mResponse);
break; // response text
case ESM4::SUB_NAM2:
reader.getZString(mNotes);
@ -185,6 +185,7 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader)
case ESM4::SUB_TCFU: // FONV
case ESM4::SUB_TIFC: // TES5
case ESM4::SUB_TWAT: // TES5
case ESM4::SUB_CIS1: // TES5
case ESM4::SUB_CIS2: // TES5
case ESM4::SUB_CNAM: // TES5
case ESM4::SUB_ENAM: // TES5
@ -195,6 +196,23 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader)
case ESM4::SUB_ONAM: // TES5
case ESM4::SUB_QNAM: // TES5 for mScript
case ESM4::SUB_RNAM: // TES5
case ESM4::SUB_ALFA: // FO4
case ESM4::SUB_GNAM: // FO4
case ESM4::SUB_GREE: // FO4
case ESM4::SUB_INAM: // FO4
case ESM4::SUB_INCC: // FO4
case ESM4::SUB_INTV: // FO4
case ESM4::SUB_IOVR: // FO4
case ESM4::SUB_MODQ: // FO4
case ESM4::SUB_NAM0: // FO4
case ESM4::SUB_NAM4: // FO4
case ESM4::SUB_NAM9: // FO4
case ESM4::SUB_SRAF: // FO4
case ESM4::SUB_TIQS: // FO4
case ESM4::SUB_TNAM: // FO4
case ESM4::SUB_TRDA: // FO4
case ESM4::SUB_TSCE: // FO4
case ESM4::SUB_WZMD: // FO4
reader.skipSubRecordData();
break;
default:

@ -95,8 +95,10 @@ void ESM4::Ingredient::load(ESM4::Reader& reader)
reader.adjustFormId(mEffect.formId);
break;
}
case ESM4::SUB_MODT:
case ESM4::SUB_MODS: // Dragonborn only?
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_EFID:
case ESM4::SUB_EFIT:
case ESM4::SUB_OBND:
@ -106,6 +108,15 @@ void ESM4::Ingredient::load(ESM4::Reader& reader)
case ESM4::SUB_YNAM:
case ESM4::SUB_ZNAM:
case ESM4::SUB_ETYP: // FO3
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
{
reader.skipSubRecordData();
break;

@ -71,11 +71,24 @@ void ESM4::Key::load(ESM4::Reader& reader)
case ESM4::SUB_ZNAM:
reader.getFormId(mDropSound);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_OBND:
case ESM4::SUB_VMAD:
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_PTRN: // FO4
reader.skipSubRecordData();
break;
default:

@ -94,7 +94,7 @@ void ESM4::Land::load(ESM4::Reader& reader)
mDataTypes |= LAND_VCLR;
break;
}
case ESM4::SUA_BTXT:
case ESM4::SUB_BTXT:
{
BTXT base;
if (reader.getExact(base))
@ -191,6 +191,9 @@ void ESM4::Land::load(ESM4::Reader& reader)
}
break;
}
case ESM4::SUB_MPCD: // FO4
reader.skipSubRecordData();
break;
default:
throw std::runtime_error("ESM4::LAND::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
}

@ -35,9 +35,6 @@ void ESM4::Light::load(ESM4::Reader& reader)
{
mId = reader.getFormIdFromHeader();
mFlags = reader.hdr().record.flags;
std::uint32_t esmVer = reader.esmVersion();
bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134;
while (reader.getSubRecordHeader())
{
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
@ -51,31 +48,31 @@ void ESM4::Light::load(ESM4::Reader& reader)
break;
case ESM4::SUB_DATA:
{
// FIXME: TES4 might be uint32 as well, need to check
if (isFONV || (esmVer == ESM::VER_094 && subHdr.dataSize == 32) /*FO3*/)
if (subHdr.dataSize != 32 && subHdr.dataSize != 48 && subHdr.dataSize != 64)
{
reader.get(mData.time); // uint32
reader.skipSubRecordData();
break;
}
else
reader.get(mData.duration); // float
reader.get(mData.time);
reader.get(mData.radius);
reader.get(mData.colour);
reader.get(mData.flags);
// if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170)
if (subHdr.dataSize == 48)
reader.get(mData.falloff);
reader.get(mData.FOV);
// TES5, FO4
if (subHdr.dataSize >= 48)
{
reader.get(mData.falloff);
reader.get(mData.FOV);
reader.get(mData.nearClip);
reader.get(mData.frequency);
reader.get(mData.intensityAmplitude);
reader.get(mData.movementAmplitude);
}
else if (subHdr.dataSize == 32) // TES4
{
reader.get(mData.falloff);
reader.get(mData.FOV);
if (subHdr.dataSize == 64)
{
reader.get(mData.constant);
reader.get(mData.scalar);
reader.get(mData.exponent);
reader.get(mData.godRaysNearClip);
}
}
reader.get(mData.value);
reader.get(mData.weight);
@ -99,15 +96,29 @@ void ESM4::Light::load(ESM4::Reader& reader)
case ESM4::SUB_FNAM:
reader.get(mFade);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_OBND:
case ESM4::SUB_VMAD: // Dragonborn only?
case ESM4::SUB_DEST: // Destruction data start
case ESM4::SUB_DSTD:
case ESM4::SUB_VMAD:
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTF: // Destruction data end
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_LNAM: // FO4
case ESM4::SUB_MICO: // FO4
case ESM4::SUB_NAM0: // FO4
case ESM4::SUB_PRPS: // FO4
case ESM4::SUB_PTRN: // FO4
case ESM4::SUB_WGDR: // FO4
reader.skipSubRecordData();
break;
default:

@ -56,8 +56,7 @@ namespace ESM4
struct Data
{
std::uint32_t time; // FO/FONV only
float duration = -1;
std::int32_t time;
std::uint32_t radius;
std::uint32_t colour; // RGBA
// flags:
@ -74,10 +73,14 @@ namespace ESM4
std::int32_t flags;
float falloff = 1.f;
float FOV = 90; // FIXME: FOV in degrees or radians?
float nearClip; // TES5 only
float frequency; // TES5 only
float intensityAmplitude; // TES5 only
float movementAmplitude; // TES5 only
float nearClip; // TES5+
float frequency; // TES5+
float intensityAmplitude; // TES5+
float movementAmplitude; // TES5+
float constant; // FO4
float scalar; // FO4
float exponent; // FO4
float godRaysNearClip; // FO4
std::uint32_t value; // gold
float weight;
};

@ -35,8 +35,6 @@ void ESM4::LandTexture::load(ESM4::Reader& reader)
{
mId = reader.getFormIdFromHeader();
mFlags = reader.hdr().record.flags;
std::uint32_t esmVer = reader.esmVersion();
bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134;
while (reader.getSubRecordHeader())
{
@ -48,25 +46,18 @@ void ESM4::LandTexture::load(ESM4::Reader& reader)
break;
case ESM4::SUB_HNAM:
{
if (isFONV)
switch (subHdr.dataSize)
{
reader.skipSubRecordData(); // FIXME: skip FONV for now
break;
}
if ((reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170)
&& subHdr.dataSize == 2) // FO3 is VER_094 but dataSize 3
{
reader.get(mHavokFriction);
reader.get(mHavokRestitution);
}
else
{
if (subHdr.dataSize != 3)
throw std::runtime_error("LTEX unexpected HNAM size, expected 3");
reader.get(mHavokMaterial);
reader.get(mHavokFriction);
reader.get(mHavokRestitution);
case 3: // TES4, FO3, FNV
reader.get(mHavokMaterial);
[[fallthrough]];
case 2:
reader.get(mHavokFriction);
reader.get(mHavokRestitution);
break;
default:
reader.skipSubRecordData();
break;
}
break;
}
@ -77,14 +68,14 @@ void ESM4::LandTexture::load(ESM4::Reader& reader)
reader.get(mTextureSpecular);
break;
case ESM4::SUB_GNAM:
reader.getFormId(mGrass);
reader.getFormId(mGrass.emplace_back());
break;
case ESM4::SUB_TNAM:
reader.getFormId(mTexture);
break; // TES5 only
break; // TES5, FO4
case ESM4::SUB_MNAM:
reader.getFormId(mMaterial);
break; // TES5 only
break; // TES5, FO4
default:
throw std::runtime_error("ESM4::LTEX::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
}

@ -49,14 +49,14 @@ namespace ESM4
std::uint8_t mHavokRestitution;
std::uint8_t mTextureSpecular; // default 30
ESM::FormId mGrass;
std::vector<ESM::FormId> mGrass;
// ------ TES4 only -----
std::string mTextureFile;
std::uint8_t mHavokMaterial;
// ------ TES5 only -----
// ------ TES5, FO4 -----
ESM::FormId mTexture;
ESM::FormId mMaterial;

@ -80,6 +80,10 @@ void ESM4::LevelledItem::load(ESM4::Reader& reader)
case ESM4::SUB_OBND: // FO3/FONV
case ESM4::SUB_COED: // FO3/FONV
case ESM4::SUB_LVLG: // FO3/FONV
case ESM4::SUB_LLKC: // FO4
case ESM4::SUB_LVLM: // FO4
case ESM4::SUB_LVSG: // FO4
case ESM4::SUB_ONAM: // FO4
reader.skipSubRecordData();
break;
default:

@ -91,7 +91,13 @@ void ESM4::LevelledNpc::load(ESM4::Reader& reader)
}
case ESM4::SUB_COED: // owner
case ESM4::SUB_OBND: // object bounds
case ESM4::SUB_MODT: // model texture data
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_LLKC: // FO4
case ESM4::SUB_LVLG: // FO4
case ESM4::SUB_LVLM: // FO4
reader.skipSubRecordData();
break;
default:

@ -49,6 +49,10 @@ void ESM4::Material::load(ESM4::Reader& reader)
break;
case ESM4::SUB_DNAM:
case ESM4::SUB_DATA:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
reader.skipSubRecordData();
break;
default:

@ -71,19 +71,28 @@ void ESM4::MiscItem::load(ESM4::Reader& reader)
case ESM4::SUB_ZNAM:
reader.getFormId(mDropSound);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_MODS:
case ESM4::SUB_OBND:
case ESM4::SUB_VMAD:
case ESM4::SUB_RNAM: // FONV
case ESM4::SUB_DEST: // Destruction data start
case ESM4::SUB_DSTD:
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTF: // Destruction data end
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_CDIX: // FO4
case ESM4::SUB_CVPA: // FO4
case ESM4::SUB_FIMD: // FO4
case ESM4::SUB_PTRN: // FO4
reader.skipSubRecordData();
break;
default:

@ -44,6 +44,9 @@ void ESM4::MovableStatic::load(ESM4::Reader& reader)
case ESM4::SUB_EDID:
reader.getZString(mEditorId);
break;
case ESM4::SUB_FULL:
reader.getLocalizedString(mFullName);
break;
case ESM4::SUB_MODL:
reader.getZString(mModel);
break;
@ -53,16 +56,26 @@ void ESM4::MovableStatic::load(ESM4::Reader& reader)
case ESM4::SUB_SNAM:
reader.getFormId(mLoopingSound);
break;
case ESM4::SUB_DEST: // destruction data
case ESM4::SUB_OBND: // object bounds
case ESM4::SUB_MODT: // model texture data
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_OBND: // object bounds
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_VMAD:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_FULL:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_MODB:
case ESM4::SUB_PRPS:
case ESM4::SUB_PTRN: // FO4
reader.skipSubRecordData();
break;
default:

@ -43,6 +43,7 @@ namespace ESM4
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId;
std::string mFullName;
std::string mModel;
std::int8_t mData;

@ -249,6 +249,13 @@ void ESM4::Navigation::load(ESM4::Reader& reader)
}
case ESM4::SUB_NVPP:
{
// FIXME: this is both the version for FO4 and for some TES4 files
// How to distinguish?
if (esmVer == ESM::VER_100)
{
reader.skipSubRecordData();
break;
}
std::uint32_t total;
std::uint32_t count;
reader.get(total);
@ -333,9 +340,11 @@ void ESM4::Navigation::load(ESM4::Reader& reader)
}
case ESM4::SUB_NVMI: // multiple
{
if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV)
// Can only read TES4 navmesh data
// Note FO4 FIXME above
if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV || esmVer == ESM::VER_100)
{
reader.skipSubRecordData(); // FIXME: FO3/FONV have different form of NavMeshInfo
reader.skipSubRecordData();
break;
}

@ -194,8 +194,8 @@ void ESM4::NavMesh::load(ESM4::Reader& reader)
mId = reader.getFormIdFromHeader();
mFlags = reader.hdr().record.flags;
std::uint32_t esmVer = reader.esmVersion();
// std::cout << "NavMesh 0x" << std::hex << this << std::endl; // FIXME
std::uint32_t subSize = 0; // for XXXX sub record
// FIXME: debugging only
#if 0
@ -206,10 +206,19 @@ void ESM4::NavMesh::load(ESM4::Reader& reader)
#endif
while (reader.getSubRecordHeader())
{
switch (reader.subRecordHeader().typeId)
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
switch (subHdr.typeId)
{
case ESM4::SUB_NVNM:
{
// See FIXME in ESM4::Navigation::load.
// FO4 updates the format
if (esmVer == ESM::VER_100)
{
reader.skipSubRecordData();
break;
}
NVNMstruct nvnm;
nvnm.load(reader);
mData.push_back(nvnm); // FIXME try swap
@ -218,24 +227,6 @@ void ESM4::NavMesh::load(ESM4::Reader& reader)
case ESM4::SUB_ONAM:
case ESM4::SUB_PNAM:
case ESM4::SUB_NNAM:
{
if (subSize)
{
reader.skipSubRecordData(subSize); // special post XXXX
reader.updateRecordRead(subSize); // WARNING: manual update
subSize = 0;
}
else
{
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
Log(Debug::Verbose) << ESM::printName(subHdr.typeId) << " skipping...";
reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip
}
break;
}
case ESM4::SUB_XXXX:
reader.get(subSize);
break;
case ESM4::SUB_NVER: // FO3
case ESM4::SUB_DATA: // FO3
case ESM4::SUB_NVVX: // FO3
@ -245,11 +236,11 @@ void ESM4::NavMesh::load(ESM4::Reader& reader)
case ESM4::SUB_NVGD: // FO3
case ESM4::SUB_NVEX: // FO3
case ESM4::SUB_EDID: // FO3
case ESM4::SUB_MNAM: // FO4
reader.skipSubRecordData();
break;
default:
throw std::runtime_error(
"ESM4::NAVM::load - Unknown subrecord " + ESM::printName(reader.subRecordHeader().typeId));
throw std::runtime_error("ESM4::NAVM::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
}
}
// std::cout << "num nvnm " << std::dec << mData.size() << std::endl; // FIXME

@ -53,13 +53,29 @@ void ESM4::Note::load(ESM4::Reader& reader)
case ESM4::SUB_ICON:
reader.getZString(mIcon);
break;
case ESM4::SUB_DATA:
case ESM4::SUB_MODB:
reader.get(mBoundRadius);
break;
case ESM4::SUB_YNAM:
reader.getFormId(mPickUpSound);
break;
case ESM4::SUB_ZNAM:
reader.getFormId(mDropSound);
break;
case ESM4::SUB_DATA:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_ONAM:
case ESM4::SUB_SNAM:
case ESM4::SUB_TNAM:
case ESM4::SUB_XNAM:
case ESM4::SUB_OBND:
case ESM4::SUB_VMAD:
case ESM4::SUB_DNAM: // FO4
case ESM4::SUB_PNAM: // FO4
case ESM4::SUB_PTRN: // FO4
reader.skipSubRecordData();
break;
default:

@ -48,6 +48,11 @@ namespace ESM4
std::string mModel;
std::string mIcon;
float mBoundRadius;
ESM::FormId mPickUpSound;
ESM::FormId mDropSound;
void load(ESM4::Reader& reader);
// void save(ESM4::Writer& writer) const;

@ -73,7 +73,11 @@ void ESM4::Npc::load(ESM4::Reader& reader)
break;
case ESM4::SUB_SNAM:
{
reader.get(mFaction);
// FO4, FO76
if (subHdr.dataSize == 5)
reader.get(&mFaction, 5);
else
reader.get(mFaction);
reader.adjustFormId(mFaction.faction);
break;
}
@ -99,7 +103,7 @@ void ESM4::Npc::load(ESM4::Reader& reader)
//
case ESM4::SUB_AIDT:
{
if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV)
if (subHdr.dataSize != 12)
{
reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip
break;
@ -110,24 +114,28 @@ void ESM4::Npc::load(ESM4::Reader& reader)
}
case ESM4::SUB_ACBS:
{
// if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV)
if (subHdr.dataSize == 24)
reader.get(mBaseConfig);
else
reader.get(&mBaseConfig, 16); // TES4
switch (subHdr.dataSize)
{
case 16: // TES4
case 24: // FO3/FNV, TES5
case 20: // FO4
reader.get(&mBaseConfig, subHdr.dataSize);
break;
default:
reader.skipSubRecordData();
break;
}
break;
}
case ESM4::SUB_DATA:
{
if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV)
{
if (subHdr.dataSize != 0) // FIXME FO3
reader.skipSubRecordData();
break; // zero length
}
if (subHdr.dataSize == 0)
break;
reader.get(&mData, 33); // FIXME: check packing
if (subHdr.dataSize == 33)
reader.get(&mData, 33); // FIXME: check packing
else // FIXME FO3
reader.skipSubRecordData();
break;
}
case ESM4::SUB_ZNAM:
@ -144,6 +152,7 @@ void ESM4::Npc::load(ESM4::Reader& reader)
break;
case ESM4::SUB_WNAM:
{
// FIXME: should be read into mWornArmor for FO4
if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170)
reader.getFormId(mWornArmor);
else
@ -218,6 +227,11 @@ void ESM4::Npc::load(ESM4::Reader& reader)
break;
}
case ESM4::SUB_BCLF:
{
reader.getFormId(mBeardColourId);
break;
}
case ESM4::SUB_COCT: // TES5
{
std::uint32_t count;
@ -234,33 +248,15 @@ void ESM4::Npc::load(ESM4::Reader& reader)
case ESM4::SUB_DPLT:
reader.getFormId(mDefaultPkg);
break; // AI package list
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF:
{
#if 0
std::vector<unsigned char> dataBuf(subHdr.dataSize);
reader.get(dataBuf.data(), subHdr.dataSize);
std::ostringstream ss;
ss << mEditorId << " " << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n";
for (std::size_t i = 0; i < subHdr.dataSize; ++i)
{
if (dataBuf[i] > 64 && dataBuf[i] < 91) // looks like printable ascii char
ss << (char)(dataBuf[i]) << " ";
else
ss << std::setfill('0') << std::setw(2) << std::hex << (int)(dataBuf[i]);
if ((i & 0x000f) == 0xf) // wrap around
ss << "\n";
else if (i < (size_t)(subHdr.dataSize - 1)) // quiesce gcc
ss << " ";
}
std::cout << ss.str() << std::endl;
#else
reader.skipSubRecordData();
#endif
break;
}
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_NAM6: // height mult
case ESM4::SUB_NAM7: // weight mult
case ESM4::SUB_ATKR:
@ -295,6 +291,42 @@ void ESM4::Npc::load(ESM4::Reader& reader)
case ESM4::SUB_EAMT: // FO3
case ESM4::SUB_NAM4: // FO3
case ESM4::SUB_COED: // FO3
case ESM4::SUB_APPR: // FO4
case ESM4::SUB_ATKS: // FO4
case ESM4::SUB_ATKT: // FO4
case ESM4::SUB_ATKW: // FO4
case ESM4::SUB_ATTX: // FO4
case ESM4::SUB_FTYP: // FO4
case ESM4::SUB_LTPT: // FO4
case ESM4::SUB_LTPC: // FO4
case ESM4::SUB_MWGT: // FO4
case ESM4::SUB_NTRM: // FO4
case ESM4::SUB_PFRN: // FO4
case ESM4::SUB_PRPS: // FO4
case ESM4::SUB_PTRN: // FO4
case ESM4::SUB_STCP: // FO4
case ESM4::SUB_TETI: // FO4
case ESM4::SUB_TEND: // FO4
case ESM4::SUB_TPTA: // FO4
case ESM4::SUB_OBTE: // FO4 object template start
case ESM4::SUB_OBTF: //
case ESM4::SUB_OBTS: //
case ESM4::SUB_STOP: // FO4 object template end
case ESM4::SUB_OCOR: // FO4 new package lists start
case ESM4::SUB_GWOR: //
case ESM4::SUB_FCPL: //
case ESM4::SUB_RCLR: // FO4 new package lists end
case ESM4::SUB_CS2D: // FO4 actor sound subrecords
case ESM4::SUB_CS2E: //
case ESM4::SUB_CS2F: //
case ESM4::SUB_CS2H: //
case ESM4::SUB_CS2K: // FO4 actor sound subrecords end
case ESM4::SUB_MSDK: // FO4 morph subrecords start
case ESM4::SUB_MSDV: //
case ESM4::SUB_MRSV: //
case ESM4::SUB_FMRI: //
case ESM4::SUB_FMRS: //
case ESM4::SUB_FMIN: // FO4 morph subrecords end
reader.skipSubRecordData();
break;
default:

@ -187,6 +187,7 @@ namespace ESM4
float mHairLength;
HairColour mHairColour; // TES4/FO3/FONV
ESM::FormId mHairColourId; // TES5
ESM::FormId mBeardColourId; // FO4
ESM::FormId mDeathItem;
std::vector<ESM::FormId> mSpell;

@ -62,6 +62,7 @@ void ESM4::PlacedGrenade::load(ESM4::Reader& reader)
case ESM4::SUB_XPWR:
case ESM4::SUB_XDCR:
case ESM4::SUB_XLKR:
case ESM4::SUB_XLKT: // FO4
case ESM4::SUB_XCLP:
case ESM4::SUB_XAPD:
case ESM4::SUB_XAPR:
@ -72,6 +73,21 @@ void ESM4::PlacedGrenade::load(ESM4::Reader& reader)
case ESM4::SUB_XIBS:
case ESM4::SUB_XSCL:
case ESM4::SUB_DATA:
case ESM4::SUB_VMAD:
case ESM4::SUB_MNAM: // FO4
case ESM4::SUB_XAMC: // FO4
case ESM4::SUB_XASP: // FO4
case ESM4::SUB_XATP: // FO4
case ESM4::SUB_XCVR: // FO4
case ESM4::SUB_XFVC: // FO4
case ESM4::SUB_XHTW: // FO4
case ESM4::SUB_XIS2: // FO4
case ESM4::SUB_XLOD: // FO4
case ESM4::SUB_XLRL: // FO4
case ESM4::SUB_XLRT: // FO4
case ESM4::SUB_XLYR: // FO4
case ESM4::SUB_XMSP: // FO4
case ESM4::SUB_XRFG: // FO4
reader.skipSubRecordData();
break;
default:

@ -142,6 +142,7 @@ void ESM4::Quest::load(ESM4::Reader& reader)
case ESM4::SUB_ALSP: // TES5
case ESM4::SUB_ALST: // TES5
case ESM4::SUB_ALUA: // TES5
case ESM4::SUB_CIS1: // TES5
case ESM4::SUB_CIS2: // TES5
case ESM4::SUB_CNTO: // TES5
case ESM4::SUB_COCT: // TES5
@ -155,6 +156,19 @@ void ESM4::Quest::load(ESM4::Reader& reader)
case ESM4::SUB_SPOR: // TES5
case ESM4::SUB_VMAD: // TES5
case ESM4::SUB_VTCK: // TES5
case ESM4::SUB_ALCC: // FO4
case ESM4::SUB_ALCS: // FO4
case ESM4::SUB_ALDI: // FO4
case ESM4::SUB_ALFV: // FO4
case ESM4::SUB_ALLA: // FO4
case ESM4::SUB_ALMI: // FO4
case ESM4::SUB_GNAM: // FO4
case ESM4::SUB_GWOR: // FO4
case ESM4::SUB_LNAM: // FO4
case ESM4::SUB_NAM2: // FO4
case ESM4::SUB_OCOR: // FO4
case ESM4::SUB_SNAM: // FO4
case ESM4::SUB_XNAM: // FO4
reader.skipSubRecordData();
break;
default:

@ -139,7 +139,7 @@ void ESM4::Race::load(ESM4::Reader& reader)
reader.get(mWeightFemale);
reader.get(mRaceFlags);
}
else if (subHdr.dataSize >= 128 && subHdr.dataSize <= 164) // TES5
else if (subHdr.dataSize == 128 || subHdr.dataSize == 164) // TES5
{
mIsTES5 = true;
@ -188,7 +188,7 @@ void ESM4::Race::load(ESM4::Reader& reader)
reader.get(dummy); // angular tolerance
reader.get(dummy2); // flags
if (subHdr.dataSize > 128)
if (subHdr.dataSize == 164)
{
reader.get(dummy2); // unknown 1
reader.get(dummy2); // unknown 2
@ -217,7 +217,7 @@ void ESM4::Race::load(ESM4::Reader& reader)
break;
}
case ESM4::SUB_CNAM: // Only in TES4?
case ESM4::SUB_CNAM:
// CNAM SNAM VNAM
// Sheogorath 0x0 0000 98 2b 10011000 00101011
// Golden Saint 0x3 0011 26 46 00100110 01000110
@ -287,7 +287,7 @@ void ESM4::Race::load(ESM4::Reader& reader)
}
else if (isTES4)
mHeadParts.resize(9); // assumed based on Construction Set
else
else // Optimized for TES5
{
mHeadPartIdsMale.resize(5);
mHeadPartIdsFemale.resize(5);
@ -315,7 +315,11 @@ void ESM4::Race::load(ESM4::Reader& reader)
}
case ESM4::SUB_MODL:
{
if (curr_part == 0) // head part
if (currentIndex == 0xffffffff)
{
reader.skipSubRecordData();
}
else if (curr_part == 0) // head part
{
if (isMale || isTES4)
reader.getZString(mHeadParts[currentIndex].mesh);
@ -351,7 +355,11 @@ void ESM4::Race::load(ESM4::Reader& reader)
break; // always 0x0000?
case ESM4::SUB_ICON:
{
if (curr_part == 0) // head part
if (currentIndex == 0xffffffff)
{
reader.skipSubRecordData();
}
else if (curr_part == 0) // head part
{
if (isMale || isTES4)
reader.getZString(mHeadParts[currentIndex].texture);
@ -407,10 +415,16 @@ void ESM4::Race::load(ESM4::Reader& reader)
//
case ESM4::SUB_HNAM:
{
std::size_t numHairChoices = subHdr.dataSize / sizeof(ESM::FormId32);
mHairChoices.resize(numHairChoices);
for (unsigned int i = 0; i < numHairChoices; ++i)
reader.getFormId(mHairChoices.at(i));
// FIXME: this is a texture name in FO4
if (subHdr.dataSize % sizeof(ESM::FormId32) != 0)
reader.skipSubRecordData();
else
{
std::size_t numHairChoices = subHdr.dataSize / sizeof(ESM::FormId32);
mHairChoices.resize(numHairChoices);
for (unsigned int i = 0; i < numHairChoices; ++i)
reader.getFormId(mHairChoices.at(i));
}
break;
}
@ -550,14 +564,23 @@ void ESM4::Race::load(ESM4::Reader& reader)
break;
}
case ESM4::SUB_BOD2: // TES5
case ESM4::SUB_BOD2:
{
reader.get(mBodyTemplate.bodyPart);
mBodyTemplate.flags = 0;
mBodyTemplate.unknown1 = 0; // probably padding
mBodyTemplate.unknown2 = 0; // probably padding
mBodyTemplate.unknown3 = 0; // probably padding
reader.get(mBodyTemplate.type);
if (subHdr.dataSize == 8 || subHdr.dataSize == 4) // TES5, FO4
{
reader.get(mBodyTemplate.bodyPart);
mBodyTemplate.flags = 0;
mBodyTemplate.unknown1 = 0; // probably padding
mBodyTemplate.unknown2 = 0; // probably padding
mBodyTemplate.unknown3 = 0; // probably padding
mBodyTemplate.type = 0;
if (subHdr.dataSize == 8)
reader.get(mBodyTemplate.type);
}
else
{
reader.skipSubRecordData();
}
break;
}
@ -566,11 +589,22 @@ void ESM4::Race::load(ESM4::Reader& reader)
ESM::FormId formId;
reader.getFormId(formId);
// FIXME: no order? head, mouth, eyes, brow, hair
if (isMale)
mHeadPartIdsMale[currentIndex] = formId;
else
mHeadPartIdsFemale[currentIndex] = formId;
if (currentIndex != 0xffffffff)
{
// FIXME: no order? head, mouth, eyes, brow, hair
if (isMale)
{
if (currentIndex >= mHeadPartIdsMale.size())
mHeadPartIdsMale.resize(currentIndex + 1);
mHeadPartIdsMale[currentIndex] = formId;
}
else
{
if (currentIndex >= mHeadPartIdsFemale.size())
mHeadPartIdsFemale.resize(currentIndex + 1);
mHeadPartIdsFemale[currentIndex] = formId;
}
}
// std::cout << mEditorId << (isMale ? " male head " : " female head ")
// << formIdToString(formId) << " " << currentIndex << std::endl; // FIXME
@ -668,13 +702,69 @@ void ESM4::Race::load(ESM4::Reader& reader)
case ESM4::SUB_WKMV:
case ESM4::SUB_SPMV:
case ESM4::SUB_ATKR:
case ESM4::SUB_CTDA:
case ESM4::SUB_CIS1:
case ESM4::SUB_CIS2:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
//
case ESM4::SUB_YNAM: // FO3
case ESM4::SUB_NAM2: // FO3
case ESM4::SUB_VTCK: // FO3
case ESM4::SUB_MODT: // FO3
case ESM4::SUB_MODD: // FO3
case ESM4::SUB_ONAM: // FO3
case ESM4::SUB_APPR: // FO4
case ESM4::SUB_ATKS: // FO4
case ESM4::SUB_ATKT: // FO4
case ESM4::SUB_ATKW: // FO4
case ESM4::SUB_BMMP: // FO4
case ESM4::SUB_BSMB: // FO4
case ESM4::SUB_BSMP: // FO4
case ESM4::SUB_BSMS: // FO4
case ESM4::SUB_FMRI: // FO4
case ESM4::SUB_FMRN: // FO4
case ESM4::SUB_HLTX: // FO4
case ESM4::SUB_MLSI: // FO4
case ESM4::SUB_MPGN: // FO4
case ESM4::SUB_MPGS: // FO4
case ESM4::SUB_MPPC: // FO4
case ESM4::SUB_MPPF: // FO4
case ESM4::SUB_MPPI: // FO4
case ESM4::SUB_MPPK: // FO4
case ESM4::SUB_MPPM: // FO4
case ESM4::SUB_MPPN: // FO4
case ESM4::SUB_MPPT: // FO4
case ESM4::SUB_MSID: // FO4
case ESM4::SUB_MSM0: // FO4
case ESM4::SUB_MSM1: // FO4
case ESM4::SUB_NNAM: // FO4
case ESM4::SUB_NTOP: // FO4
case ESM4::SUB_PRPS: // FO4
case ESM4::SUB_PTOP: // FO4
case ESM4::SUB_QSTI: // FO4
case ESM4::SUB_RBPC: // FO4
case ESM4::SUB_SADD: // FO4
case ESM4::SUB_SAKD: // FO4
case ESM4::SUB_SAPT: // FO4
case ESM4::SUB_SGNM: // FO4
case ESM4::SUB_SRAC: // FO4
case ESM4::SUB_SRAF: // FO4
case ESM4::SUB_STCP: // FO4
case ESM4::SUB_STKD: // FO4
case ESM4::SUB_TETI: // FO4
case ESM4::SUB_TTEB: // FO4
case ESM4::SUB_TTEC: // FO4
case ESM4::SUB_TTED: // FO4
case ESM4::SUB_TTEF: // FO4
case ESM4::SUB_TTET: // FO4
case ESM4::SUB_TTGE: // FO4
case ESM4::SUB_TTGP: // FO4
case ESM4::SUB_UNWP: // FO4
case ESM4::SUB_WMAP: // FO4
case ESM4::SUB_ZNAM: // FO4
reader.skipSubRecordData();
break;
default:

@ -38,9 +38,6 @@ void ESM4::Reference::load(ESM4::Reader& reader)
mFlags = reader.hdr().record.flags;
mParent = reader.currCell();
std::uint32_t esmVer = reader.esmVersion();
bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134;
ESM::FormId mid;
ESM::FormId sid;
@ -69,8 +66,26 @@ void ESM4::Reference::load(ESM4::Reader& reader)
reader.get(mScale);
break;
case ESM4::SUB_XOWN:
reader.getFormId(mOwner);
{
switch (subHdr.dataSize)
{
case 4:
reader.getFormId(mOwner);
break;
case 12:
{
reader.getFormId(mOwner);
std::uint32_t dummy;
reader.get(dummy); // Unknown
reader.get(dummy); // No crime flag, FO4
break;
}
default:
reader.skipSubRecordData();
break;
}
break;
}
case ESM4::SUB_XGLB:
reader.getFormId(mGlobal);
break;
@ -87,13 +102,27 @@ void ESM4::Reference::load(ESM4::Reader& reader)
}
case ESM4::SUB_XTEL:
{
reader.getFormId(mDoor.destDoor);
reader.get(mDoor.destPos);
if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV)
reader.get(mDoor.flags); // not in Obvlivion
else
mDoor.flags = 0;
// std::cout << "REFR dest door: " << formIdToString(mDoor.destDoor) << std::endl;// FIXME
switch (subHdr.dataSize)
{
case 28:
case 32: // FO3, FNV, TES5
case 36: // FO4
{
reader.getFormId(mDoor.destDoor);
reader.get(mDoor.destPos);
mDoor.flags = 0;
if (subHdr.dataSize >= 32)
{
reader.get(mDoor.flags);
if (subHdr.dataSize == 36)
reader.getFormId(mDoor.transitionInterior);
}
break;
}
default:
reader.skipSubRecordData();
break;
}
break;
}
case ESM4::SUB_XSED:
@ -201,6 +230,7 @@ void ESM4::Reference::load(ESM4::Reader& reader)
break; // FONV
case ESM4::SUB_XRDO: // FO3
{
// FIXME: completely different meaning in FO4
reader.get(mRadio.rangeRadius);
reader.get(mRadio.broadcastRange);
reader.get(mRadio.staticPercentage);
@ -288,7 +318,7 @@ void ESM4::Reference::load(ESM4::Reader& reader)
case ESM4::SUB_XTRI:
case ESM4::SUB_XWCN:
case ESM4::SUB_XWCU:
case ESM4::SUB_XATR: // Dawnguard only?
case ESM4::SUB_XATR:
case ESM4::SUB_XHLT: // Unofficial Oblivion Patch
case ESM4::SUB_XCHG: // thievery.exp
case ESM4::SUB_XHLP: // FO3
@ -312,6 +342,19 @@ void ESM4::Reference::load(ESM4::Reader& reader)
case ESM4::SUB_XSRD: // FONV
case ESM4::SUB_WMI1: // FONV
case ESM4::SUB_XLRL: // Unofficial Skyrim Patch
case ESM4::SUB_XASP: // FO4
case ESM4::SUB_XATP: // FO4
case ESM4::SUB_XBSD: // FO4
case ESM4::SUB_XCVR: // FO4
case ESM4::SUB_XCZR: // FO4
case ESM4::SUB_XLKT: // FO4
case ESM4::SUB_XLYR: // FO4
case ESM4::SUB_XMSP: // FO4
case ESM4::SUB_XPDD: // FO4
case ESM4::SUB_XPLK: // FO4
case ESM4::SUB_XRFG: // FO4
case ESM4::SUB_XWPG: // FO4
case ESM4::SUB_XWPN: // FO4
// if (mFormId == 0x0007e90f) // XPRM XPOD
// if (mBaseObj == 0x17) //XPRM XOCP occlusion plane data XMBO bound half extents
reader.skipSubRecordData();

@ -61,6 +61,7 @@ namespace ESM4
ESM::FormId destDoor;
ESM::Position destPos;
std::uint32_t flags = 0; // 0x01 no alarm (only in TES5)
ESM::FormId transitionInterior;
};
struct RadioStationData

@ -122,6 +122,8 @@ void ESM4::Region::load(ESM4::Reader& reader)
case ESM4::SUB_RDSB: // FONV
case ESM4::SUB_RDSI: // FONV
case ESM4::SUB_NVMI: // TES5
case ESM4::SUB_ANAM: // FO4
case ESM4::SUB_RLDM: // FO4
// RDAT skipping... following is a map
// RDMP skipping... map name
//

@ -46,11 +46,19 @@ void ESM4::StaticCollection::load(ESM4::Reader& reader)
case ESM4::SUB_EDID:
reader.getZString(mEditorId);
break;
case ESM4::SUB_FULL:
reader.getLocalizedString(mFullName);
break;
case ESM4::SUB_OBND:
case ESM4::SUB_MODL:
case ESM4::SUB_MODL: // Model data start
case ESM4::SUB_MODT:
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_ONAM:
case ESM4::SUB_DATA:
case ESM4::SUB_FLTR: // FO4
case ESM4::SUB_PTRN: // FO4
reader.skipSubRecordData();
break;
default:

@ -46,6 +46,7 @@ namespace ESM4
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId;
std::string mFullName;
void load(ESM4::Reader& reader);
// void save(ESM4::Writer& writer) const;

@ -68,10 +68,25 @@ void ESM4::SoundReference::load(ESM4::Reader& reader)
reader.get(mLoopInfo);
break;
case ESM4::SUB_BNAM:
reader.get(mData);
{
if (subHdr.dataSize == 6)
reader.get(mData);
else if (subHdr.dataSize == 4)
reader.getFormId(mBaseDescriptor);
else
reader.skipSubRecordData();
break;
}
case ESM4::SUB_CIS1:
case ESM4::SUB_CIS2:
case ESM4::SUB_CNAM: // CRC32 hash
case ESM4::SUB_DNAM: // FO4
case ESM4::SUB_FNAM: // unknown
case ESM4::SUB_INTV: // FO4
case ESM4::SUB_ITMC: // FO4
case ESM4::SUB_ITME: // FO4
case ESM4::SUB_ITMS: // FO4
case ESM4::SUB_NNAM: // FO4
reader.skipSubRecordData();
break;
default:

@ -67,6 +67,7 @@ namespace ESM4
ESM::FormId mSoundCategory; // SNCT
ESM::FormId mSoundId; // another SNDR
ESM::FormId mOutputModel; // SOPM
ESM::FormId mBaseDescriptor; // BNAM
std::string mSoundFile;
LoopInfo mLoopInfo;

@ -65,6 +65,7 @@ void ESM4::Sound::load(ESM4::Reader& reader)
case ESM4::SUB_GNAM: // FO3
case ESM4::SUB_HNAM: // FO3
case ESM4::SUB_RNAM: // FONV
case ESM4::SUB_REPT: // FO4
reader.skipSubRecordData();
break;
default:

@ -44,6 +44,9 @@ void ESM4::Static::load(ESM4::Reader& reader)
case ESM4::SUB_EDID:
reader.getZString(mEditorId);
break;
case ESM4::SUB_FULL:
reader.getLocalizedString(mFullName);
break;
case ESM4::SUB_MODL:
reader.getZString(mModel);
break;
@ -81,11 +84,18 @@ void ESM4::Static::load(ESM4::Reader& reader)
}
break;
}
case ESM4::SUB_MODC: // More model data
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_OBND:
case ESM4::SUB_DNAM:
case ESM4::SUB_BRUS: // FONV
case ESM4::SUB_RNAM: // FONV
case ESM4::SUB_FTYP: // FO4
case ESM4::SUB_NVNM: // FO4
case ESM4::SUB_PRPS: // FO4
case ESM4::SUB_PTRN: // FO4
case ESM4::SUB_VMAD: // FO4
reader.skipSubRecordData();
break;
default:

@ -47,6 +47,7 @@ namespace ESM4
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId;
std::string mFullName;
std::string mModel;
float mBoundRadius;

@ -62,15 +62,23 @@ void ESM4::TalkingActivator::load(ESM4::Reader& reader)
case ESM4::SUB_MODL:
reader.getZString(mModel);
break;
case ESM4::SUB_DEST: // FO3 destruction
case ESM4::SUB_DSTD: // FO3 destruction
case ESM4::SUB_DSTF: // FO3 destruction
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_FNAM:
case ESM4::SUB_PNAM:
case ESM4::SUB_MODT: // texture file hash?
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_OBND:
case ESM4::SUB_VMAD:
case ESM4::SUB_MODS:
reader.skipSubRecordData();
break;
default:

@ -57,7 +57,11 @@ void ESM4::Terminal::load(ESM4::Reader& reader)
reader.getFormId(mPasswordNote);
break;
case ESM4::SUB_SNAM:
reader.getFormId(mSound);
if (subHdr.dataSize == 4)
reader.getFormId(mSound);
// FIXME: FO4 sound marker params
else
reader.skipSubRecordData();
break;
case ESM4::SUB_MODL:
reader.getZString(mModel);
@ -68,9 +72,14 @@ void ESM4::Terminal::load(ESM4::Reader& reader)
case ESM4::SUB_DNAM: // difficulty
case ESM4::SUB_ANAM: // flags
case ESM4::SUB_CTDA:
case ESM4::SUB_CIS1:
case ESM4::SUB_CIS2:
case ESM4::SUB_INAM:
case ESM4::SUB_ITXT: // Menu Item
case ESM4::SUB_MODT: // texture hash?
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_SCDA:
case ESM4::SUB_SCHR:
case ESM4::SUB_SCRO:
@ -80,7 +89,25 @@ void ESM4::Terminal::load(ESM4::Reader& reader)
case ESM4::SUB_SLSD:
case ESM4::SUB_TNAM:
case ESM4::SUB_OBND:
case ESM4::SUB_MODS: // FONV
case ESM4::SUB_VMAD:
case ESM4::SUB_KSIZ:
case ESM4::SUB_KWDA:
case ESM4::SUB_BSIZ: // FO4
case ESM4::SUB_BTXT: // FO4
case ESM4::SUB_COCT: // FO4
case ESM4::SUB_CNTO: // FO4
case ESM4::SUB_FNAM: // FO4
case ESM4::SUB_ISIZ: // FO4
case ESM4::SUB_ITID: // FO4
case ESM4::SUB_MNAM: // FO4
case ESM4::SUB_NAM0: // FO4
case ESM4::SUB_PRPS: // FO4
case ESM4::SUB_PTRN: // FO4
case ESM4::SUB_UNAM: // FO4
case ESM4::SUB_VNAM: // FO4
case ESM4::SUB_WBDT: // FO4
case ESM4::SUB_WNAM: // FO4
case ESM4::SUB_XMRK: // FO4
reader.skipSubRecordData();
break;
default:

@ -99,6 +99,7 @@ void ESM4::Header::load(ESM4::Reader& reader)
case ESM4::SUB_INCC:
case ESM4::SUB_OFST: // Oblivion only?
case ESM4::SUB_DELE: // Oblivion only?
case ESM4::SUB_TNAM: // Fallout 4 (CK only)
reader.skipSubRecordData();
break;
default:

@ -53,7 +53,10 @@ void ESM4::Tree::load(ESM4::Reader& reader)
case ESM4::SUB_MODB:
reader.get(mBoundRadius);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_CNAM:
case ESM4::SUB_BNAM:
case ESM4::SUB_SNAM:

@ -68,6 +68,9 @@ void ESM4::TextureSet::load(ESM4::Reader& reader)
case ESM4::SUB_TX07:
reader.getZString(mSpecular);
break;
case ESM4::SUB_MNAM:
reader.getZString(mMaterial);
break;
case ESM4::SUB_DNAM:
case ESM4::SUB_DODT:
case ESM4::SUB_OBND: // object bounds

@ -53,6 +53,7 @@ namespace ESM4
std::string mEnvMap;
std::string mUnknown;
std::string mSpecular;
std::string mMaterial;
void load(ESM4::Reader& reader);
// void save(ESM4::Writer& writer) const;

@ -109,7 +109,10 @@ void ESM4::Weapon::load(ESM4::Reader& reader)
case ESM4::SUB_ZNAM:
reader.getFormId(mDropSound);
break;
case ESM4::SUB_MODT:
case ESM4::SUB_MODT: // Model data
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_BAMT:
case ESM4::SUB_BIDS:
case ESM4::SUB_INAM:
@ -132,16 +135,17 @@ void ESM4::Weapon::load(ESM4::Reader& reader)
case ESM4::SUB_WNAM:
case ESM4::SUB_XNAM: // Dawnguard only?
case ESM4::SUB_NNAM:
case ESM4::SUB_MODS:
case ESM4::SUB_NAM0: // FO3
case ESM4::SUB_REPL: // FO3
case ESM4::SUB_MOD2: // FO3
case ESM4::SUB_MO2T: // FO3
case ESM4::SUB_MO2S: // FO3
case ESM4::SUB_NAM6: // FO3
case ESM4::SUB_MOD4: // FO3
case ESM4::SUB_MO4T: // FO3
case ESM4::SUB_MO4S: // FO3
case ESM4::SUB_MOD4: // First person model data
case ESM4::SUB_MO4T:
case ESM4::SUB_MO4S:
case ESM4::SUB_MO4C:
case ESM4::SUB_MO4F: // First person model data end
case ESM4::SUB_BIPL: // FO3
case ESM4::SUB_NAM7: // FO3
case ESM4::SUB_MOD3: // FO3
@ -149,11 +153,15 @@ void ESM4::Weapon::load(ESM4::Reader& reader)
case ESM4::SUB_MO3S: // FO3
case ESM4::SUB_MODD: // FO3
// case ESM4::SUB_MOSD: // FO3
case ESM4::SUB_DEST: // FO3
case ESM4::SUB_DSTD: // FO3
case ESM4::SUB_DSTF: // FO3
case ESM4::SUB_DMDL: // FO3
case ESM4::SUB_DMDT: // FO3
case ESM4::SUB_DAMC: // Destructible
case ESM4::SUB_DEST:
case ESM4::SUB_DMDC:
case ESM4::SUB_DMDL:
case ESM4::SUB_DMDT:
case ESM4::SUB_DMDS:
case ESM4::SUB_DSTA:
case ESM4::SUB_DSTD:
case ESM4::SUB_DSTF: // Destructible end
case ESM4::SUB_VATS: // FONV
case ESM4::SUB_VANM: // FONV
case ESM4::SUB_MWD1: // FONV
@ -176,6 +184,21 @@ void ESM4::Weapon::load(ESM4::Reader& reader)
case ESM4::SUB_WNM6: // FONV
case ESM4::SUB_WNM7: // FONV
case ESM4::SUB_EFSD: // FONV DeadMoney
case ESM4::SUB_APPR: // FO4
case ESM4::SUB_DAMA: // FO4
case ESM4::SUB_FLTR: // FO4
case ESM4::SUB_FNAM: // FO4
case ESM4::SUB_INRD: // FO4
case ESM4::SUB_LNAM: // FO4
case ESM4::SUB_MASE: // FO4
case ESM4::SUB_PTRN: // FO4
case ESM4::SUB_STCP: // FO4
case ESM4::SUB_WAMD: // FO4
case ESM4::SUB_WZMD: // FO4
case ESM4::SUB_OBTE: // FO4 object template start
case ESM4::SUB_OBTF:
case ESM4::SUB_OBTS:
case ESM4::SUB_STOP: // FO4 object template end
reader.skipSubRecordData();
break;
default:

@ -45,8 +45,6 @@ void ESM4::World::load(ESM4::Reader& reader)
// corrupted by the use of ignore flag (TODO: should check to verify).
reader.setCurrWorld(mId); // save for CELL later
std::uint32_t subSize = 0; // for XXXX sub record
std::uint32_t esmVer = reader.esmVersion();
// bool isTES4 = (esmVer == ESM::VER_080 || esmVer == ESM::VER_100);
// bool isFONV = (esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134);
@ -64,9 +62,9 @@ void ESM4::World::load(ESM4::Reader& reader)
case ESM4::SUB_FULL:
reader.getLocalizedString(mFullName);
break;
case ESM4::SUB_WCTR:
case ESM4::SUB_WCTR: // TES5+
reader.get(mCenterCell);
break; // Center cell, TES5 only
break;
case ESM4::SUB_WNAM:
reader.getFormId(mParent);
break;
@ -75,7 +73,7 @@ void ESM4::World::load(ESM4::Reader& reader)
break; // sound, Oblivion only?
case ESM4::SUB_ICON:
reader.getZString(mMapFile);
break; // map filename, Oblivion only?
break;
case ESM4::SUB_CNAM:
reader.getFormId(mClimate);
break;
@ -144,19 +142,6 @@ void ESM4::World::load(ESM4::Reader& reader)
reader.get(mParentUseFlags);
break;
case ESM4::SUB_OFST:
if (subSize)
{
reader.skipSubRecordData(subSize); // special post XXXX
reader.updateRecordRead(subSize); // WARNING: manually update
subSize = 0;
}
else
reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip
break;
case ESM4::SUB_XXXX:
reader.get(subSize);
break;
case ESM4::SUB_RNAM: // multiple
case ESM4::SUB_MHDT:
case ESM4::SUB_LTMP:
@ -164,18 +149,23 @@ void ESM4::World::load(ESM4::Reader& reader)
case ESM4::SUB_XLCN:
case ESM4::SUB_NAM3:
case ESM4::SUB_NAM4:
case ESM4::SUB_MODL:
case ESM4::SUB_NAMA:
case ESM4::SUB_ONAM:
case ESM4::SUB_TNAM:
case ESM4::SUB_UNAM:
case ESM4::SUB_XWEM:
case ESM4::SUB_MODT: // from Dragonborn onwards?
case ESM4::SUB_MODL: // Model data start
case ESM4::SUB_MODT:
case ESM4::SUB_MODC:
case ESM4::SUB_MODS:
case ESM4::SUB_MODF: // Model data end
case ESM4::SUB_INAM: // FO3
case ESM4::SUB_NNAM: // FO3
case ESM4::SUB_XNAM: // FO3
case ESM4::SUB_IMPS: // FO3 Anchorage
case ESM4::SUB_IMPF: // FO3 Anchorage
case ESM4::SUB_CLSZ: // FO4
case ESM4::SUB_WLEV: // FO4
reader.skipSubRecordData();
break;
default:

@ -61,11 +61,11 @@ namespace ESM4
switch (type)
{
case LocalizedStringType::Strings:
return u8"_English.STRINGS";
return u8".STRINGS";
case LocalizedStringType::ILStrings:
return u8"_English.ILSTRINGS";
return u8".ILSTRINGS";
case LocalizedStringType::DLStrings:
return u8"_English.DLSTRINGS";
return u8".DLSTRINGS";
}
throw std::logic_error("Unsupported LocalizedStringType: " + std::to_string(static_cast<int>(type)));
@ -322,34 +322,49 @@ namespace ESM4
void Reader::buildLStringIndex(LocalizedStringType stringType, const std::u8string& prefix)
{
static const std::filesystem::path strings("Strings");
const std::u8string language(u8"_En");
const std::u8string altLanguage(u8"_English");
const std::u8string suffix(getStringsSuffix(stringType));
std::filesystem::path path = strings / (prefix + suffix);
std::filesystem::path path = strings / (prefix + language + suffix);
if (mVFS != nullptr)
{
const std::string vfsPath = Files::pathToUnicodeString(path);
std::string vfsPath = Files::pathToUnicodeString(path);
if (!mVFS->exists(vfsPath))
{
path = strings / (prefix + altLanguage + suffix);
vfsPath = Files::pathToUnicodeString(path);
}
if (mIgnoreMissingLocalizedStrings && !mVFS->exists(vfsPath))
if (mVFS->exists(vfsPath))
{
Log(Debug::Warning) << "Ignore missing VFS strings file: " << vfsPath;
const Files::IStreamPtr stream = mVFS->get(vfsPath);
buildLStringIndex(stringType, *stream);
return;
}
const Files::IStreamPtr stream = mVFS->get(vfsPath);
buildLStringIndex(stringType, *stream);
return;
if (mIgnoreMissingLocalizedStrings)
{
Log(Debug::Warning) << "Ignore missing VFS strings file: " << vfsPath;
return;
}
}
const std::filesystem::path fsPath = mCtx.filename.parent_path() / path;
std::filesystem::path fsPath = mCtx.filename.parent_path() / path;
if (!std::filesystem::exists(fsPath))
{
path = strings / (prefix + altLanguage + suffix);
fsPath = mCtx.filename.parent_path() / path;
}
if (mIgnoreMissingLocalizedStrings && !std::filesystem::exists(fsPath))
if (std::filesystem::exists(fsPath))
{
Log(Debug::Warning) << "Ignore missing strings file: " << fsPath;
const Files::IStreamPtr stream = Files::openConstrainedFileStream(fsPath);
buildLStringIndex(stringType, *stream);
return;
}
const Files::IStreamPtr stream = Files::openConstrainedFileStream(fsPath);
buildLStringIndex(stringType, *stream);
if (mIgnoreMissingLocalizedStrings)
Log(Debug::Warning) << "Ignore missing strings file: " << fsPath;
}
void Reader::buildLStringIndex(LocalizedStringType stringType, std::istream& stream)
@ -558,9 +573,6 @@ namespace ESM4
{
bool result = false;
// NOTE: some SubRecords have 0 dataSize (e.g. SUB_RDSD in one of REC_REGN records in Oblivion.esm).
// Also SUB_XXXX has zero dataSize and the following 4 bytes represent the actual dataSize
// - hence it require manual updtes to mCtx.recordRead via updateRecordRead()
// See ESM4::NavMesh and ESM4::World.
if (mCtx.recordHeader.record.dataSize - mCtx.recordRead >= sizeof(mCtx.subRecordHeader))
{
result = getExact(mCtx.subRecordHeader);
@ -582,6 +594,20 @@ namespace ESM4
return false;
}
// Extended storage subrecord redefines the following subrecord's size.
// Would need to redesign the loader to support that, so skip over both subrecords.
if (result && mCtx.subRecordHeader.typeId == ESM4::SUB_XXXX)
{
std::uint32_t extDataSize;
get(extDataSize);
if (!getSubRecordHeader())
return false;
skipSubRecordData(extDataSize);
mCtx.recordRead += extDataSize - mCtx.subRecordHeader.dataSize;
return getSubRecordHeader();
}
return result;
}

Loading…
Cancel
Save