/*
Copyright (C) 2012 Sebastian Herbord. All rights reserved.
This file is part of Mod Organizer.
Mod Organizer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Mod Organizer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see .
*/
#include "savegamegamebyro.h"
#include
#include
#include
#include "gameinfo.h"
#include
#include
#include
using namespace MOShared;
template
void FileRead(QFile &file, T &value)
{
int read = file.read(reinterpret_cast(&value), sizeof(T));
if (read != sizeof(T)) {
throw std::runtime_error("unexpected end of file");
}
}
template
void FileSkip(QFile &file, int count = 1)
{
char ignore[sizeof(T)];
for (int i = 0; i < count; ++i) {
if (file.read(ignore, sizeof(T)) != sizeof(T)) {
throw std::runtime_error("unexpected end of file");
}
}
}
QString ReadBString(QFile &file)
{
char buffer[256];
file.read(buffer, 1); // size including zero termination
unsigned char size = buffer[0];
file.read(buffer, size);
return QString::fromLatin1(buffer, size);
}
QString ReadFOSString(QFile &file, bool delimiter)
{
union {
char lengthBuffer[2];
unsigned short length;
};
file.read(lengthBuffer, 2);
if (delimiter) {
FileSkip(file); // 0x7c
}
char *buffer = new char[length];
file.read(buffer, length);
QString result = QString::fromLatin1(buffer, length);
delete [] buffer;
return result;
}
SaveGameGamebryo::SaveGameGamebryo(QObject *parent)
: SaveGame(parent), m_Plugins()
{
}
SaveGameGamebryo::SaveGameGamebryo(QObject *parent, const QString &fileName)
: SaveGame(parent, fileName), m_Plugins()
{
readFile(fileName);
}
SaveGameGamebryo::SaveGameGamebryo(const SaveGameGamebryo& reference)
: SaveGame(reference), m_Plugins(reference.m_Plugins)
{
}
SaveGameGamebryo& SaveGameGamebryo::operator=(const SaveGameGamebryo &reference)
{
if (&reference != this) {
SaveGame::operator =(reference);
m_Plugins = reference.m_Plugins;
}
return *this;
}
SaveGameGamebryo::~SaveGameGamebryo()
{
}
QStringList SaveGameGamebryo::attachedFiles() const
{
QStringList result;
QString seFileFile = fileName().mid(0).replace(".ess", ".skse");
QFileInfo seFile(seFileFile);
if (seFile.exists()) {
result.append(seFile.absoluteFilePath());
}
return result;
}
void SaveGameGamebryo::readSkyrimFile(QFile &saveFile)
{
char fileID[14];
saveFile.read(fileID, 13);
fileID[13] = '\0';
if (strncmp(fileID, "TESV_SAVEGAME", 13) != 0) {
throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData());
}
FileSkip(saveFile); // header size
FileSkip(saveFile); // header version, -> 8
FileRead(saveFile, m_SaveNumber);
m_PCName = ReadFOSString(saveFile, false);
unsigned long temp;
FileRead(saveFile, temp); // player level
m_PCLevel = static_cast(temp);
m_PCLocation = ReadFOSString(saveFile, false);
ReadFOSString(saveFile, false); // playtime as ascii hhh.mm.ss
ReadFOSString(saveFile, false); // race name (i.e. BretonRace)
FileSkip(saveFile); // ???
FileSkip(saveFile, 2); // ???
FileSkip(saveFile, 8); // filetime
// FileSkip(saveFile, 18); // ??? 18 bytes of data. not completely random, maybe a time stamp? maybe
unsigned long width, height;
FileRead(saveFile, width); // 320
FileRead(saveFile, height); // 192
QScopedArrayPointer buffer(new unsigned char[width * height * 3]);
saveFile.read(reinterpret_cast(buffer.data()), width * height * 3);
// why do I have to copy here? without the copy, the buffer seems to get deleted after the
// temporary vanishes, but Qts implicit sharing should handle that?
m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy();
FileSkip(saveFile); // form version
FileSkip(saveFile); // plugin info size
unsigned char pluginCount;
FileRead(saveFile, pluginCount);
for (int i = 0; i < pluginCount; ++i) {
m_Plugins.push_back(ReadFOSString(saveFile, false));
}
}
void SaveGameGamebryo::readESSFile(QFile &saveFile)
{
char fileID[13];
unsigned char versionMinor;
unsigned long headerVersion, saveHeaderSize;
// *** format is different for fallout!
saveFile.read(fileID, 12);
fileID[12] = '\0';
FileSkip(saveFile); FileRead(saveFile, versionMinor);
FileSkip(saveFile); // modified time
FileRead(saveFile, headerVersion); FileRead(saveFile, saveHeaderSize);
if (strncmp(fileID, "TES4SAVEGAME", 12) != 0) {
throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData());
}
FileRead(saveFile, m_SaveNumber);
m_PCName = ReadBString(saveFile);
FileRead(saveFile, m_PCLevel);
m_PCLocation = ReadBString(saveFile);
FileSkip(saveFile); // game days
FileSkip(saveFile); // game ticks
FileRead(saveFile, m_CreationTime);
unsigned long size;
FileRead(saveFile, size); // screenshot size
unsigned long width, height;
FileRead(saveFile, width); FileRead(saveFile, height);
QScopedArrayPointer buffer(new unsigned char[width * height * 3]);
saveFile.read(reinterpret_cast(buffer.data()), width * height * 3);
// why do I have to copy here? without the copy, the buffer seems to get deleted after the
// temporary vanishes, but Qts implicit sharing should handle that?
m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy();
unsigned char pluginCount;
FileRead(saveFile, pluginCount);
for (int i = 0; i < pluginCount; ++i) {
QString name = ReadBString(saveFile);
m_Plugins.push_back(name);
}
}
void SaveGameGamebryo::readFOSFile(QFile &saveFile, bool newVegas)
{
char fileID[13];
saveFile.read(fileID, 12);
// the signature is only 11 characters, the 12th is random?
fileID[11] = '\0';
if (strncmp(fileID, "FO3SAVEGAME", 11) != 0) {
throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData());
}
char ignore = 0x00;
while (ignore != 0x7c) {
FileRead(saveFile, ignore); // unknown
}
if (newVegas) {
ignore = 0x00;
// in new vegas there is another block of uninteresting (?) information
FileSkip(saveFile); // 0x7c
while (ignore != 0x7c) {
FileRead(saveFile, ignore); // unknown
}
}
unsigned long width, height;
FileRead(saveFile, width);
FileSkip(saveFile); // 0x7c
FileRead(saveFile, height);
FileSkip(saveFile); // 0x7c
FileRead(saveFile, m_SaveNumber);
FileSkip(saveFile); // 0x7c
m_PCName = ReadFOSString(saveFile, true);
FileSkip(saveFile); // 0x7c
ReadFOSString(saveFile, true);
FileSkip(saveFile); // 0x7c
long Level;
FileRead(saveFile, Level);
m_PCLevel = Level;
FileSkip(saveFile); // 0x7c
m_PCLocation = ReadFOSString(saveFile, true);
FileSkip(saveFile); // 0x7c
ReadFOSString(saveFile, true); // playtime
FileSkip(saveFile);
QScopedArrayPointer buffer(new unsigned char[width * height * 3]);
saveFile.read(reinterpret_cast(buffer.data()), width * height * 3);
// why do I have to copy here? without the copy, the buffer seems to get deleted after the
// temporary vanishes, but Qts implicit sharing should handle that?
m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).scaledToWidth(256);
FileSkip(saveFile, 5); // unknown
unsigned char pluginCount = 0;
FileRead(saveFile, pluginCount);
FileSkip(saveFile); // 0x7c
for (int i = 0; i < pluginCount; ++i) {
QString name = ReadFOSString(saveFile, true);
m_Plugins.push_back(name);
FileSkip(saveFile); // 0x7c
}
}
void SaveGameGamebryo::setCreationTime(const QString &fileName)
{
QFileInfo creationTime(fileName);
QDateTime modified = creationTime.lastModified();
memset(&m_CreationTime, 0, sizeof(SYSTEMTIME));
m_CreationTime.wDay = static_cast(modified.date().day());
m_CreationTime.wDayOfWeek = static_cast(modified.date().dayOfWeek());
m_CreationTime.wMonth = static_cast(modified.date().month());
m_CreationTime.wYear =static_cast( modified.date().year());
m_CreationTime.wHour = static_cast(modified.time().hour());
m_CreationTime.wMinute = static_cast(modified.time().minute());
m_CreationTime.wSecond = static_cast(modified.time().second());
m_CreationTime.wMilliseconds = static_cast(modified.time().msec());
}
void SaveGameGamebryo::readFile(const QString &fileName)
{
m_FileName = fileName;
QFile saveFile(fileName);
if (!saveFile.open(QIODevice::ReadOnly)) {
throw std::runtime_error(QObject::tr("failed to open %1").arg(fileName).toUtf8().constData());
}
switch (GameInfo::instance().getType()) {
case GameInfo::TYPE_FALLOUT3: {
setCreationTime(fileName);
readFOSFile(saveFile, false);
} break;
case GameInfo::TYPE_FALLOUTNV: {
setCreationTime(fileName);
readFOSFile(saveFile, true);
} break;
case GameInfo::TYPE_OBLIVION: {
readESSFile(saveFile);
} break;
case GameInfo::TYPE_SKYRIM: {
setCreationTime(fileName);
readSkyrimFile(saveFile);
} break;
}
saveFile.close();
}