forked from mirror/openmw-tes3mp
Starting ESS importer for Morrowind save files
This commit is contained in:
parent
fc663addfa
commit
031eec4550
24 changed files with 1160 additions and 11 deletions
|
@ -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()
|
||||
|
|
25
apps/essimporter/CMakeLists.txt
Normal file
25
apps/essimporter/CMakeLists.txt
Normal file
|
@ -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()
|
40
apps/essimporter/converter.cpp
Normal file
40
apps/essimporter/converter.cpp
Normal file
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
262
apps/essimporter/converter.hpp
Normal file
262
apps/essimporter/converter.hpp
Normal file
|
@ -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
|
25
apps/essimporter/importacdt.hpp
Normal file
25
apps/essimporter/importacdt.hpp
Normal file
|
@ -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
|
28
apps/essimporter/importcellref.cpp
Normal file
28
apps/essimporter/importcellref.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
33
apps/essimporter/importcellref.hpp
Normal file
33
apps/essimporter/importcellref.hpp
Normal file
|
@ -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
|
13
apps/essimporter/importcrec.cpp
Normal file
13
apps/essimporter/importcrec.cpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#include "importcrec.hpp"
|
||||
|
||||
#include <components/esm/esmreader.hpp>
|
||||
|
||||
namespace ESSImport
|
||||
{
|
||||
|
||||
void CREC::load(ESM::ESMReader &esm)
|
||||
{
|
||||
esm.getHNT(mIndex, "INDX");
|
||||
}
|
||||
|
||||
}
|
22
apps/essimporter/importcrec.hpp
Normal file
22
apps/essimporter/importcrec.hpp
Normal file
|
@ -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
|
306
apps/essimporter/importer.cpp
Normal file
306
apps/essimporter/importer.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
25
apps/essimporter/importer.hpp
Normal file
25
apps/essimporter/importer.hpp
Normal file
|
@ -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
apps/essimporter/importercontext.cpp
Normal file
0
apps/essimporter/importercontext.cpp
Normal file
65
apps/essimporter/importercontext.hpp
Normal file
65
apps/essimporter/importercontext.hpp
Normal file
|
@ -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
|
18
apps/essimporter/importnpcc.cpp
Normal file
18
apps/essimporter/importnpcc.cpp
Normal file
|
@ -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?
|
||||
}
|
||||
|
||||
}
|
29
apps/essimporter/importnpcc.hpp
Normal file
29
apps/essimporter/importnpcc.hpp
Normal file
|
@ -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
|
59
apps/essimporter/importplayer.cpp
Normal file
59
apps/essimporter/importplayer.cpp
Normal file
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
58
apps/essimporter/importplayer.hpp
Normal file
58
apps/essimporter/importplayer.hpp
Normal file
|
@ -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
|
60
apps/essimporter/main.cpp
Normal file
60
apps/essimporter/main.cpp
Normal file
|
@ -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
|
||||
* 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, KLST - no clue
|
||||
* 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…
Reference in a new issue