mirror of https://github.com/OpenMW/openmw.git
Starting ESS importer for Morrowind save files
parent
fc663addfa
commit
031eec4550
@ -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;
|
||||
}
|
Loading…
Reference in New Issue