Starting ESS importer for Morrowind save files

openmw-35
scrawl 9 years ago
parent fc663addfa
commit 031eec4550

@ -58,6 +58,7 @@ option(BUILD_BSATOOL "build BSA extractor" ON)
option(BUILD_ESMTOOL "build ESM inspector" ON)
option(BUILD_LAUNCHER "build Launcher" ON)
option(BUILD_MWINIIMPORTER "build MWiniImporter" ON)
option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON)
option(BUILD_OPENCS "build OpenMW Construction Set" ON)
option(BUILD_WIZARD "build Installation Wizard" ON)
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
@ -414,6 +415,9 @@ IF(NOT WIN32 AND NOT APPLE)
IF(BUILD_MWINIIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" )
ENDIF(BUILD_MWINIIMPORTER)
IF(BUILD_ESSIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-essimporter" DESTINATION "${BINDIR}" )
ENDIF(BUILD_ESSIMPORTER)
IF(BUILD_OPENCS)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" )
ENDIF(BUILD_OPENCS)
@ -472,6 +476,9 @@ if(WIN32)
IF(BUILD_MWINIIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/mwiniimport.exe" DESTINATION ".")
ENDIF(BUILD_MWINIIMPORTER)
IF(BUILD_ESSIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-essimporter.exe" DESTINATION ".")
ENDIF(BUILD_ESSIMPORTER)
IF(BUILD_OPENCS)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/opencs.exe" DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION ".")
@ -582,6 +589,10 @@ if (BUILD_MWINIIMPORTER)
add_subdirectory( apps/mwiniimporter )
endif()
if (BUILD_ESSIMPORTER)
add_subdirectory (apps/essimporter )
endif()
if (BUILD_OPENCS)
add_subdirectory (apps/opencs)
endif()

@ -0,0 +1,25 @@
set(ESSIMPORTER_FILES
main.cpp
importer.cpp
importplayer.cpp
importnpcc.cpp
importcrec.cpp
importcellref.cpp
importacdt.hpp
importercontext.cpp
converter.cpp
)
add_executable(openmw-essimporter
${ESSIMPORTER_FILES}
)
target_link_libraries(openmw-essimporter
${Boost_LIBRARIES}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(openmw-essimporter gcov)
endif()

@ -0,0 +1,40 @@
#include "converter.hpp"
#include <OgreImage.h>
namespace
{
void convertImage(char* data, int size, int width, int height, Ogre::PixelFormat pf, const std::string& out)
{
Ogre::Image screenshot;
Ogre::DataStreamPtr stream (new Ogre::MemoryDataStream(data, size));
screenshot.loadRawData(stream, width, height, 1, pf);
screenshot.save(out);
}
}
namespace ESSImport
{
struct MAPH
{
unsigned int size;
unsigned int value;
};
void ConvertFMAP::read(ESM::ESMReader &esm)
{
MAPH maph;
esm.getHNT(maph, "MAPH");
std::vector<char> data;
esm.getSubNameIs("MAPD");
esm.getSubHeader();
data.resize(esm.getSubSize());
esm.getExact(&data[0], data.size());
convertImage(&data[0], data.size(), maph.size, maph.size, Ogre::PF_BYTE_RGB, "map.tga");
}
}

@ -0,0 +1,262 @@
#ifndef OPENMW_ESSIMPORT_CONVERTER_H
#define OPENMW_ESSIMPORT_CONVERTER_H
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/loadcell.hpp>
#include <components/esm/loadbook.hpp>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadglob.hpp>
#include "importcrec.hpp"
#include "importercontext.hpp"
#include "importcellref.hpp"
namespace ESSImport
{
class Converter
{
public:
/// @return the order for writing this converter's records to the output file, in relation to other converters
virtual int getStage() { return 1; }
virtual ~Converter() {}
void setContext(Context& context) { mContext = &context; }
virtual void read(ESM::ESMReader& esm)
{
}
/// Called after the input file has been read in completely, which may be necessary
/// if the conversion process relies on information in other records
virtual void write(ESM::ESMWriter& esm)
{
}
protected:
Context* mContext;
};
/// Default converter: simply reads the record and writes it unmodified to the output
template <typename T>
class DefaultConverter : public Converter
{
public:
virtual int getStage() { return 0; }
virtual void read(ESM::ESMReader& esm)
{
std::string id = esm.getHNString("NAME");
T record;
record.load(esm);
mRecords[id] = record;
}
virtual void write(ESM::ESMWriter& esm)
{
for (typename std::map<std::string, T>::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it)
{
esm.startRecord(T::sRecordId);
esm.writeHNString("NAME", it->first);
it->second.save(esm);
esm.endRecord(T::sRecordId);
}
}
protected:
std::map<std::string, T> mRecords;
};
class ConvertNPC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
// this is always the player
ESM::NPC npc;
std::string id = esm.getHNString("NAME");
assert (id == "player");
npc.load(esm);
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel;
mContext->mPlayerBase = npc;
std::map<const int, float> empty;
for (std::vector<std::string>::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it)
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[*it] = empty;
}
};
class ConvertGlobal : public DefaultConverter<ESM::Global>
{
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
ESM::Global global;
global.load(esm);
if (Misc::StringUtils::ciEqual(id, "gamehour"))
mContext->mHour = global.mValue.getFloat();
if (Misc::StringUtils::ciEqual(id, "day"))
mContext->mDay = global.mValue.getInteger();
if (Misc::StringUtils::ciEqual(id, "month"))
mContext->mMonth = global.mValue.getInteger();
if (Misc::StringUtils::ciEqual(id, "year"))
mContext->mYear = global.mValue.getInteger();
mRecords[id] = global;
}
};
class ConvertClass : public DefaultConverter<ESM::Class>
{
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
ESM::Class class_;
class_.load(esm);
if (id == "NEWCLASSID_CHARGEN")
mContext->mCustomPlayerClassName = class_.mName;
mRecords[id] = class_;
}
};
class ConvertBook : public DefaultConverter<ESM::Book>
{
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
ESM::Book book;
book.load(esm);
if (book.mData.mSkillID == -1)
mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(id));
mRecords[id] = book;
}
};
class ConvertNPCC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
NPCC npcc;
npcc.load(esm);
if (id == "PlayerSaveGame")
{
mContext->mPlayer.mObject.mNpcStats.mReputation = npcc.mNPDT.mReputation;
}
}
};
class ConvertREFR : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
REFR refr;
refr.load(esm);
assert(refr.mRefID == "PlayerSaveGame");
mContext->mPlayer.mObject.mPosition = refr.mPos;
ESM::CreatureStats& cStats = mContext->mPlayer.mObject.mCreatureStats;
for (int i=0; i<3; ++i)
{
int writeIndex = translateDynamicIndex(i);
cStats.mDynamic[writeIndex].mBase = refr.mACDT.mDynamic[i][1];
cStats.mDynamic[writeIndex].mMod = refr.mACDT.mDynamic[i][1];
cStats.mDynamic[writeIndex].mCurrent = refr.mACDT.mDynamic[i][0];
}
for (int i=0; i<8; ++i)
{
cStats.mAttributes[i].mBase = refr.mACDT.mAttributes[i][1];
cStats.mAttributes[i].mMod = refr.mACDT.mAttributes[i][0];
cStats.mAttributes[i].mCurrent = refr.mACDT.mAttributes[i][0];
}
ESM::NpcStats& npcStats = mContext->mPlayer.mObject.mNpcStats;
for (int i=0; i<ESM::Skill::Length; ++i)
{
npcStats.mSkills[i].mRegular.mMod = refr.mSkills[i][1];
npcStats.mSkills[i].mRegular.mCurrent = refr.mSkills[i][1];
npcStats.mSkills[i].mRegular.mBase = refr.mSkills[i][0];
}
}
// OpenMW uses Health,Magicka,Fatigue, MW uses Health,Fatigue,Magicka
int translateDynamicIndex(int mwIndex)
{
if (mwIndex == 1)
return 2;
else if (mwIndex == 2)
return 1;
return mwIndex;
}
};
class ConvertPCDT : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
PCDT pcdt;
pcdt.load(esm);
mContext->mPlayer.mBirthsign = pcdt.mBirthsign;
mContext->mPlayer.mObject.mNpcStats.mBounty = pcdt.mBounty;
for (std::vector<PCDT::FNAM>::const_iterator it = pcdt.mFactions.begin(); it != pcdt.mFactions.end(); ++it)
{
ESM::NpcStats::Faction faction;
faction.mExpelled = it->mFlags & 0x2;
faction.mRank = it->mRank;
faction.mReputation = it->mReputation;
mContext->mPlayer.mObject.mNpcStats.mFactions[it->mFactionName.toString()] = faction;
}
}
};
class ConvertCREC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
CREC crec;
crec.load(esm);
}
};
class ConvertFMAP : public Converter
{
public:
virtual void read(ESM::ESMReader &esm);
};
class ConvertCell : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
{
ESM::Cell cell;
std::string id = esm.getHNString("NAME");
cell.load(esm, false);
CellRef ref;
while (esm.hasMoreSubs())
{
ref.load (esm);
if (esm.isNextSub("DELE"))
std::cout << "deleted ref " << ref.mIndexedRefId << std::endl;
}
}
};
}
#endif

@ -0,0 +1,25 @@
#ifndef OPENMW_ESSIMPORT_ACDT_H
#define OPENMW_ESSIMPORT_ACDT_H
namespace ESSImport
{
/// Actor data, shared by (at least) REFR and CellRef
struct ACDT
{
unsigned char mUnknown1[40];
float mDynamic[3][2];
unsigned char mUnknown2[16];
float mAttributes[8][2];
unsigned char mUnknown3[120];
};
/// Unknown, shared by (at least) REFR and CellRef
struct ACSC
{
unsigned char unknown[112];
};
}
#endif

@ -0,0 +1,28 @@
#include "importcellref.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void CellRef::load(ESM::ESMReader &esm)
{
esm.getHNT(mRefNum.mIndex, "FRMR"); // TODO: adjust RefNum
mIndexedRefId = esm.getHNString("NAME");
esm.getHNT(mACDT, "ACDT");
ACSC acsc;
esm.getHNOT(acsc, "ACSC");
esm.getHNOT(acsc, "ACSL");
if (esm.isNextSub("CRED"))
esm.skipHSub();
if (esm.isNextSub("ND3D"))
esm.skipHSub();
esm.getHNOT(mPos, "DATA", 24);
}
}

@ -0,0 +1,33 @@
#ifndef OPENMW_ESSIMPORT_CELLREF_H
#define OPENMW_ESSIMPORT_CELLREF_H
#include <string>
#include <components/esm/cellref.hpp>
#include "importacdt.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
// Not sure if we can share any code with ESM::CellRef here
struct CellRef
{
std::string mIndexedRefId;
ESM::RefNum mRefNum;
ACDT mACDT;
ESM::Position mPos;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,13 @@
#include "importcrec.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void CREC::load(ESM::ESMReader &esm)
{
esm.getHNT(mIndex, "INDX");
}
}

@ -0,0 +1,22 @@
#ifndef OPENMW_ESSIMPORT_CREC_H
#define OPENMW_ESSIMPORT_CREC_H
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// Creature changes
struct CREC
{
int mIndex;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,306 @@
#include "importer.hpp"
#include <OgreRoot.h>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/defs.hpp>
#include <components/esm/savedgame.hpp>
#include <components/esm/player.hpp>
#include <components/esm/loadalch.hpp>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadspel.hpp>
#include <components/esm/loadarmo.hpp>
#include <components/esm/loadweap.hpp>
#include <components/esm/loadclot.hpp>
#include <components/esm/loadench.hpp>
#include <components/esm/loadweap.hpp>
#include <components/esm/loadlevlist.hpp>
#include <components/esm/loadglob.hpp>
#include "importercontext.hpp"
#include "converter.hpp"
namespace
{
void writeScreenshot(const ESM::Header& fileHeader, ESM::SavedGame& out)
{
Ogre::Image screenshot;
std::vector<unsigned char> screenshotData = fileHeader.mSCRS; // MemoryDataStream doesn't work with const data :(
Ogre::DataStreamPtr screenshotStream (new Ogre::MemoryDataStream(&screenshotData[0], screenshotData.size()));
screenshot.loadRawData(screenshotStream, 128, 128, 1, Ogre::PF_BYTE_BGRA);
Ogre::DataStreamPtr encoded = screenshot.encode("jpg");
out.mScreenshot.resize(encoded->size());
encoded->read(&out.mScreenshot[0], encoded->size());
}
}
namespace ESSImport
{
Importer::Importer(const std::string &essfile, const std::string &outfile)
: mEssFile(essfile)
, mOutFile(outfile)
{
}
struct File
{
struct Subrecord
{
std::string mName;
size_t mFileOffset;
std::vector<unsigned char> mData;
};
struct Record
{
std::string mName;
size_t mFileOffset;
std::vector<Subrecord> mSubrecords;
};
std::vector<Record> mRecords;
};
void read(const std::string& filename, File& file)
{
ESM::ESMReader esm;
esm.open(filename);
while (esm.hasMoreRecs())
{
ESM::NAME n = esm.getRecName();
esm.getRecHeader();
File::Record rec;
rec.mName = n.toString();
rec.mFileOffset = esm.getFileOffset();
while (esm.hasMoreSubs())
{
File::Subrecord sub;
esm.getSubName();
esm.getSubHeader();
sub.mFileOffset = esm.getFileOffset();
sub.mName = esm.retSubName().toString();
sub.mData.resize(esm.getSubSize());
esm.getExact(&sub.mData[0], sub.mData.size());
rec.mSubrecords.push_back(sub);
}
file.mRecords.push_back(rec);
}
}
void Importer::compare()
{
// data that always changes should be blacklisted
std::set<std::pair<std::string, std::string> > blacklist;
blacklist.insert(std::make_pair("GLOB", "FLTV")); // gamehour
blacklist.insert(std::make_pair("REFR", "DATA")); // player position
File file1;
read(mEssFile, file1);
File file2;
read(mOutFile, file2); // todo rename variable
for (unsigned int i=0; i<file1.mRecords.size(); ++i)
{
File::Record rec = file1.mRecords[i];
if (i >= file2.mRecords.size())
{
std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset;
return;
}
File::Record rec2 = file2.mRecords[i];
if (rec.mName != rec2.mName)
{
std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl;
return; // TODO: try to recover
}
// FIXME: use max(size1, size2)
for (unsigned int j=0; j<rec.mSubrecords.size(); ++j)
{
File::Subrecord sub = rec.mSubrecords[j];
if (j >= rec2.mSubrecords.size())
{
std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset;
return;
}
File::Subrecord sub2 = rec2.mSubrecords[j];
if (sub.mName != sub2.mName)
{
std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl;
return; // TODO: try to recover
}
if (sub.mData != sub2.mData)
{
if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end())
continue;
std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl;
std::cout << "Data 1:" << std::endl;
for (unsigned int k=0; k<sub.mData.size(); ++k)
{
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)sub.mData[k] << " ";
}
std::cout << std::endl;
std::cout << "Data 2:" << std::endl;
for (unsigned int k=0; k<sub2.mData.size(); ++k)
{
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)sub2.mData[k] << " ";
}
std::cout << std::endl;
}
}
}
}
void Importer::run()
{
Ogre::LogManager logman;
Ogre::Root root;
ESM::ESMReader esm;
esm.open(mEssFile);
Context context;
std::map<std::string, ESM::Global> globals;
const unsigned int recREFR = ESM::FourCC<'R','E','F','R'>::value;
const unsigned int recPCDT = ESM::FourCC<'P','C','D','T'>::value;
const unsigned int recFMAP = ESM::FourCC<'F','M','A','P'>::value;
std::map<unsigned int, boost::shared_ptr<Converter> > converters;
converters[ESM::REC_GLOB] = boost::shared_ptr<Converter>(new ConvertGlobal());
converters[ESM::REC_BOOK] = boost::shared_ptr<Converter>(new ConvertBook());
converters[ESM::REC_NPC_] = boost::shared_ptr<Converter>(new ConvertNPC());
converters[ESM::REC_NPCC] = boost::shared_ptr<Converter>(new ConvertNPCC());
converters[ESM::REC_CREC] = boost::shared_ptr<Converter>(new ConvertCREC());
converters[recREFR] = boost::shared_ptr<Converter>(new ConvertREFR());
converters[recPCDT] = boost::shared_ptr<Converter>(new ConvertPCDT());
converters[recFMAP] = boost::shared_ptr<Converter>(new ConvertFMAP());
converters[ESM::REC_CELL] = boost::shared_ptr<Converter>(new ConvertCell());
converters[ESM::REC_ALCH] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Potion>());
converters[ESM::REC_CLAS] = boost::shared_ptr<Converter>(new ConvertClass());
converters[ESM::REC_SPEL] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Spell>());
converters[ESM::REC_ARMO] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Armor>());
converters[ESM::REC_WEAP] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
converters[ESM::REC_CLOT] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Clothing>());
converters[ESM::REC_ENCH] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Enchantment>());
converters[ESM::REC_WEAP] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
converters[ESM::REC_LEVC] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::CreatureLevList>());
converters[ESM::REC_LEVI] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::ItemLevList>());
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
it != converters.end(); ++it)
{
it->second->setContext(context);
}
while (esm.hasMoreRecs())
{
ESM::NAME n = esm.getRecName();
esm.getRecHeader();
std::map<unsigned int, boost::shared_ptr<Converter> >::iterator it = converters.find(n.val);
if (it != converters.end())
{
it->second->read(esm);
}
else
{
std::cerr << "unknown record " << n.toString() << std::endl;
esm.skipRecord();
}
}
const ESM::Header& header = esm.getHeader();
ESM::ESMWriter writer;
writer.setFormat (ESM::Header::CurrentFormat);
std::ofstream stream(mOutFile.c_str(), std::ios::binary);
// all unused
writer.setVersion(0);
writer.setType(0);
writer.setAuthor("");
writer.setDescription("");
writer.setRecordCount (0);
writer.save (stream);
ESM::SavedGame profile;
for (std::vector<ESM::Header::MasterData>::const_iterator it = header.mMaster.begin();
it != header.mMaster.end(); ++it)
{
profile.mContentFiles.push_back(it->name);
}
profile.mDescription = esm.getDesc();
profile.mInGameTime.mDay = context.mDay;
profile.mInGameTime.mGameHour = context.mHour;
profile.mInGameTime.mMonth = context.mMonth;
profile.mInGameTime.mYear = context.mYear;
profile.mPlayerCell = header.mGameData.mCurrentCell.toString();
if (context.mPlayerBase.mClass == "NEWCLASSID_CHARGEN")
profile.mPlayerClassName = context.mCustomPlayerClassName;
else
profile.mPlayerClassId = context.mPlayerBase.mClass;
profile.mPlayerLevel = context.mPlayerBase.mNpdt52.mLevel;
profile.mPlayerName = header.mGameData.mPlayerName.toString();
writeScreenshot(header, profile);
writer.startRecord (ESM::REC_SAVE);
profile.save (writer);
writer.endRecord (ESM::REC_SAVE);
// Writing order should be Dynamic Store -> Cells -> Player,
// so that references to dynamic records can be recognized when loading
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
it != converters.end(); ++it)
{
if (it->second->getStage() != 0)
continue;
it->second->write(writer);
}
writer.startRecord(ESM::REC_NPC_);
writer.writeHNString("NAME", "player");
context.mPlayerBase.save(writer);
writer.endRecord(ESM::REC_NPC_);
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
it != converters.end(); ++it)
{
if (it->second->getStage() != 1)
continue;
it->second->write(writer);
}
writer.startRecord(ESM::REC_PLAY);
context.mPlayer.save(writer);
writer.endRecord(ESM::REC_PLAY);
}
}

@ -0,0 +1,25 @@
#ifndef OPENMW_ESSIMPORTER_IMPORTER_H
#define OPENMW_ESSIMPORTER_IMPORTER_H
#include <string>
namespace ESSImport
{
class Importer
{
public:
Importer(const std::string& essfile, const std::string& outfile);
void run();
void compare();
private:
std::string mEssFile;
std::string mOutFile;
};
}
#endif

@ -0,0 +1,65 @@
#ifndef OPENMW_ESSIMPORT_CONTEXT_H
#define OPENMW_ESSIMPORT_CONTEXT_H
#include <components/esm/loadnpc.hpp>
#include "importnpcc.hpp"
#include "importplayer.hpp"
#include <components/esm/player.hpp>
namespace ESSImport
{
struct Context
{
ESM::Player mPlayer;
ESM::NPC mPlayerBase;
std::string mCustomPlayerClassName;
int mDay, mMonth, mYear;
float mHour;
Context()
{
mPlayer.mAutoMove = 0;
ESM::CellId playerCellId;
playerCellId.mPaged = true;
playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0;
mPlayer.mCellId = playerCellId;
//mPlayer.mLastKnownExteriorPosition
mPlayer.mHasMark = 0; // TODO
mPlayer.mCurrentCrimeId = 0; // TODO
mPlayer.mObject.mCount = 1;
mPlayer.mObject.mEnabled = 1;
mPlayer.mObject.mHasLocals = false;
mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame
mPlayer.mObject.mCreatureStats.mHasAiSettings = true;
mPlayer.mObject.mCreatureStats.mDead = false;
mPlayer.mObject.mCreatureStats.mDied = false;
mPlayer.mObject.mCreatureStats.mKnockdown = false;
mPlayer.mObject.mCreatureStats.mKnockdownOneFrame = false;
mPlayer.mObject.mCreatureStats.mKnockdownOverOneFrame = false;
mPlayer.mObject.mCreatureStats.mHitRecovery = false;
mPlayer.mObject.mCreatureStats.mBlock = false;
mPlayer.mObject.mCreatureStats.mMovementFlags = 0;
mPlayer.mObject.mCreatureStats.mAttackStrength = 0.f;
mPlayer.mObject.mCreatureStats.mFallHeight = 0.f;
mPlayer.mObject.mCreatureStats.mRecalcDynamicStats = false;
mPlayer.mObject.mCreatureStats.mDrawState = 0;
mPlayer.mObject.mCreatureStats.mDeathAnimation = 0;
mPlayer.mObject.mNpcStats.mIsWerewolf = false;
mPlayer.mObject.mNpcStats.mTimeToStartDrowning = 20;
mPlayer.mObject.mNpcStats.mLevelProgress = 0;
mPlayer.mObject.mNpcStats.mDisposition = 0;
mPlayer.mObject.mNpcStats.mTimeToStartDrowning = 20;
mPlayer.mObject.mNpcStats.mReputation = 0;
mPlayer.mObject.mNpcStats.mCrimeId = -1;
mPlayer.mObject.mNpcStats.mWerewolfKills = 0;
mPlayer.mObject.mNpcStats.mProfit = 0;
}
};
}
#endif

@ -0,0 +1,18 @@
#include "importnpcc.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void NPCC::load(ESM::ESMReader &esm)
{
esm.getHNT(mNPDT, "NPDT");
// container:
// XIDX
// XHLT - condition
// WIDX - equipping?
}
}

@ -0,0 +1,29 @@
#ifndef OPENMW_ESSIMPORT_NPCC_H
#define OPENMW_ESSIMPORT_NPCC_H
#include <components/esm/loadnpc.hpp>
#include <components/esm/loadcont.hpp>
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
struct NPCC : public ESM::NPC
{
struct NPDT
{
unsigned char unknown[2];
unsigned char mReputation;
unsigned char unknown2[5];
} mNPDT;
void load(ESM::ESMReader &esm);
};
}
#endif

@ -0,0 +1,59 @@
#include "importplayer.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void REFR::load(ESM::ESMReader &esm)
{
esm.getHNT(mRefNum.mIndex, "FRMR");
mRefID = esm.getHNString("NAME");
if (esm.isNextSub("STPR"))
esm.skipHSub(); // ESS TODO
esm.getHNT(mACDT, "ACDT");
ACSC acsc;
esm.getHNOT(acsc, "ACSC");
esm.getHNOT(acsc, "ACSL");
esm.getHNExact(mSkills, 27*2*sizeof(int), "CHRD");
if (esm.isNextSub("ND3D"))
esm.skipHSub(); // ESS TODO (1 byte)
esm.getHNOT(mPos, "DATA", 24);
}
void PCDT::load(ESM::ESMReader &esm)
{
if (esm.isNextSub("PNAM"))
esm.skipHSub();
if (esm.isNextSub("SNAM"))
esm.skipHSub();
if (esm.isNextSub("NAM9"))
esm.skipHSub();
mBounty = 0;
esm.getHNOT(mBounty, "CNAM");
mBirthsign = esm.getHNOString("BNAM");
if (esm.isNextSub("ENAM"))
esm.skipHSub();
while (esm.isNextSub("FNAM"))
{
FNAM fnam;
esm.getHT(fnam);
mFactions.push_back(fnam);
}
if (esm.isNextSub("KNAM"))
esm.skipHSub();
}
}

@ -0,0 +1,58 @@
#ifndef OPENMW_ESSIMPORT_PLAYER_H
#define OPENMW_ESSIMPORT_PLAYER_H
#include <vector>
#include <string>
#include <components/esm/defs.hpp>
#include <components/esm/cellref.hpp>
#include <components/esm/esmcommon.hpp>
#include "importacdt.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// Player-agnostic player data
struct REFR
{
ACDT mACDT;
std::string mRefID;
ESM::Position mPos;
ESM::RefNum mRefNum;
int mSkills[27][2];
float mAttributes[8][2];
void load(ESM::ESMReader& esm);
};
/// Other player data
struct PCDT
{
int mBounty;
std::string mBirthsign;
struct FNAM
{
unsigned char mRank;
unsigned char mUnknown1[3];
int mReputation;
unsigned char mFlags; // 0x1: unknown, 0x2: expelled
unsigned char mUnknown2[3];
ESM::NAME32 mFactionName;
};
std::vector<FNAM> mFactions;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,60 @@
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include "importer.hpp"
namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
int main(int argc, const char** argv)
{
try
{
bpo::options_description desc("Syntax: openmw-essimporter <options> infile.ess outfile.omwsave\nAllowed options");
bpo::positional_options_description p_desc;
desc.add_options()
("help,h", "produce help message")
("mwsave,m", bpo::value<std::string>(), "morrowind .ess save file")
("output,o", bpo::value<std::string>(), "output file (.omwsave)")
("compare,c", "compare two .ess files")
;
p_desc.add("mwsave", 1).add("output", 1);
bpo::variables_map vm;
bpo::parsed_options parsed = bpo::command_line_parser(argc, argv)
.options(desc)
.positional(p_desc)
.run();
bpo::store(parsed, vm);
if(vm.count("help") || !vm.count("mwsave") || !vm.count("output")) {
std::cout << desc;
return 0;
}
bpo::notify(vm);
std::string essFile = vm["mwsave"].as<std::string>();
std::string outputFile = vm["output"].as<std::string>();
ESSImport::Importer importer(essFile, outputFile);
if (vm.count("compare"))
importer.compare();
else
importer.run();
}
catch (std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}

@ -36,6 +36,7 @@ public:
const std::string getAuthor() const { return mHeader.mData.author.toString(); }
const std::string getDesc() const { return mHeader.mData.desc.toString(); }
const std::vector<Header::MasterData> &getGameFiles() const { return mHeader.mMaster; }
const Header& getHeader() const { return mHeader; }
int getFormat() const;
const NAME &retSubName() const { return mCtx.subName; }
uint32_t getSubSize() const { return mCtx.leftSub; }

@ -111,6 +111,11 @@ void Cell::loadData(ESMReader &esm)
}
esm.getHNT(mData, "DATA", 12);
// ess only, unknown
char nam8[32];
if (esm.isNextSub("NAM8"))
esm.getHExact(nam8, 32);
}
void Cell::postLoad(ESMReader &esm)

@ -17,26 +17,29 @@ class ESMWriter;
*
* Some general observations about savegames:
*
* Magical items/potions/spells/etc are added normally as new ALCH,
* SPEL, etc. records, with unique numeric identifiers.
*
* Books with ability enhancements are listed in the save if they have
* been read.
*
* GLOB records set global variables.
*
* SCPT records do not define new scripts, but assign values to the
* variables of existing ones.
*
* STLN - stolen items, ONAM is the owner
*
* GAME - contains a GMDT (game data) of unknown format
*
* VFXM, SPLM, KLST - no clue
* GAME - weather data
* struct GMDT
{
char mCellName[64];
int mFogColour;
float mFogDensity;
int mCurrentWeather, mNextWeather;
int mWeatherTransition; // 0-100 transition between weathers, top 3 bytes may be garbage
float mTimeOfNextTransition; // weather changes when gamehour == timeOfNextTransition
int masserPhase, secundaPhase; // top 3 bytes may be garbage
};
*
* VFXM, SPLM - no clue
* KLST - kill counter
*
* PCDT - seems to contain a lot of DNAMs, strings?
*
* FMAP - MAPH and MAPD, probably map data.
* FMAP - MAPH and MAPD, global map image.
*
* JOUR - the entire journal in html
*
@ -44,6 +47,8 @@ class ESMWriter;
* ones you have done or begun.
*
* REGN - lists all regions in the game, even unvisited ones.
* notable differences to Regions in ESM files: mMapColor may be missing, and includes an unknown WNAM subrecord.
*
*
* The DIAL/INFO blocks contain changes to characters' dialog status.
*

@ -92,6 +92,32 @@ void Script::load(ESMReader &esm)
if (esm.isNextSub("SCVR")) {
esm.skipHSub();
}
// ESS only, TODO: move
if (esm.isNextSub("SLCS"))
{
esm.getSubHeader();
char unknown[12];
esm.getExact(unknown, 12);
}
if (esm.isNextSub("SLSD"))
{
float read;
esm.getSubHeader();
// values of variables?
for (int i=0; i<mData.mNumShorts; ++i)
esm.getExact(&read, 2);
for (int i=0; i<mData.mNumLongs; ++i)
esm.getExact(&read, 4);
for (int i=0; i<mData.mNumFloats; ++i)
esm.getExact(&read, 4);
}
if (esm.isNextSub("RNAM"))
{
// RefNum? something to do with targetted scripts?
int refnum;
esm.getHT(refnum);
}
}
void Script::save(ESMWriter &esm) const
{

@ -45,6 +45,25 @@ void ESM::Header::load (ESMReader &esm)
m.size = esm.getHNLong ("DATA");
mMaster.push_back (m);
}
if (esm.isNextSub("GMDT"))
{
esm.getHT(mGameData);
}
if (esm.isNextSub("SCRD"))
{
esm.getSubHeader();
mSCRD.resize(esm.getSubSize());
if (mSCRD.size())
esm.getExact(&mSCRD[0], mSCRD.size());
}
if (esm.isNextSub("SCRS"))
{
esm.getSubHeader();
mSCRS.resize(esm.getSubSize());
if (mSCRS.size())
esm.getExact(&mSCRS[0], mSCRS.size());
}
}
void ESM::Header::save (ESMWriter &esm)

@ -39,6 +39,20 @@ namespace ESM
int index; // Position of the parent file in the global list of loaded files
};
struct GMDT
{
float mCurrentHealth;
float mMaximumHealth;
float mHour;
unsigned char unknown1[12];
NAME64 mCurrentCell;
unsigned char unknown2[4];
NAME32 mPlayerName;
};
GMDT mGameData; // Used in .ess savegames only
std::vector<unsigned char> mSCRD; // Used in .ess savegames only, screenshot?
std::vector<unsigned char> mSCRS; // Used in .ess savegames only, screenshot?
Data mData;
int mFormat;
std::vector<MasterData> mMaster;

Loading…
Cancel
Save