mirror of
https://github.com/ModOrganizer2/modorganizer-game_bethesda.git
synced 2026-07-27 14:08:44 -07:00
Merge game repositories into a single one.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
---
|
||||
# We'll use defaults from the LLVM style, but with 4 columns indentation.
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 2
|
||||
---
|
||||
Language: Cpp
|
||||
DeriveLineEnding: false
|
||||
UseCRLF: true
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Left
|
||||
AlignConsecutiveAssignments: true
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: Empty
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AccessModifierOffset: -2
|
||||
AlignTrailingComments: true
|
||||
SpacesBeforeTrailingComments: 2
|
||||
NamespaceIndentation: Inner
|
||||
MaxEmptyLinesToKeep: 1
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: true
|
||||
AfterControlStatement: false
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterExternBlock: true
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: true
|
||||
ColumnLimit: 88
|
||||
ForEachMacros: ['Q_FOREACH', 'foreach']
|
||||
@@ -0,0 +1,15 @@
|
||||
484afb68695eadfa57f1cf9ffc9b6e166ee20174
|
||||
59cc8639ca60de146e19de64f9dbf4380f52bcf3
|
||||
dba50ca6cecfd73fc917bf19325947fdc06ab2c3
|
||||
a070d542b2185c9568e309e540b8e61178b88ad4
|
||||
99cc4566477a37dc07eeb6282ee5e4307a6d1914
|
||||
2414eb383ad508992ab17d958f3959dc1a566748
|
||||
7cce6c37f23c886f388e98828d496edd6c5d72f6
|
||||
d8d13d2f4b54eaa4409ae97c168e0e4600c743c6
|
||||
8cd4ed42e757921dcf18e6bf0a9d82fbc586a95a
|
||||
2055815ed96457fc559b3ab2323403f3b20cbe00
|
||||
168c64df2fb816d78e6cda9cec12ac4749ab4497
|
||||
64d324bb78c54ed9d6cecb8e21aceb626ee8f36a
|
||||
c4d7eaab7f15f8b1954cfeb56c3314f9b0e9c7ec
|
||||
632ce9843dc9c263203ce4f376ebcfb1672cf1c3
|
||||
891a7103e31bf928441ef8ae5a45cd1b21f1abb0
|
||||
@@ -0,0 +1,7 @@
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
* text=auto
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
*.cpp text eol=crlf
|
||||
*.h text eol=crlf
|
||||
@@ -0,0 +1,17 @@
|
||||
name: Build GameBryo Library
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: Build GameBryo Library
|
||||
uses: ModOrganizer2/build-with-mob-action@master
|
||||
with:
|
||||
mo2-third-parties: lz4 zlib
|
||||
mo2-dependencies: cmake_common uibase
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Lint GameBryo Library
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check format
|
||||
uses: ModOrganizer2/check-formatting-action@master
|
||||
with:
|
||||
check-path: "."
|
||||
@@ -0,0 +1,5 @@
|
||||
edit
|
||||
CMakeLists.txt.user
|
||||
/msbuild.log
|
||||
/*std*.log
|
||||
/*build
|
||||
@@ -0,0 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
if(DEFINED DEPENDENCIES_DIR)
|
||||
include(${DEPENDENCIES_DIR}/modorganizer_super/cmake_common/mo2.cmake)
|
||||
else()
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake_common/mo2.cmake)
|
||||
endif()
|
||||
|
||||
project(game_gamebryo)
|
||||
|
||||
add_subdirectory(src/gamebryo)
|
||||
add_subdirectory(src/creation)
|
||||
@@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
add_library(game_creation STATIC)
|
||||
mo2_configure_library(game_creation
|
||||
WARNINGS OFF
|
||||
TRANSLATIONS ON
|
||||
PUBLIC_DEPENDS uibase
|
||||
PRIVATE_DEPENDS lz4)
|
||||
target_link_libraries(game_creation PUBLIC game_gamebryo)
|
||||
mo2_install_target(game_creation)
|
||||
@@ -0,0 +1,183 @@
|
||||
#include "creationgameplugins.h"
|
||||
#include <ipluginlist.h>
|
||||
#include <report.h>
|
||||
#include <safewritefile.h>
|
||||
#include <scopeguard.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QSet>
|
||||
#include <QStringEncoder>
|
||||
#include <QStringList>
|
||||
|
||||
using MOBase::IOrganizer;
|
||||
using MOBase::IPluginGame;
|
||||
using MOBase::IPluginList;
|
||||
using MOBase::reportError;
|
||||
using MOBase::SafeWriteFile;
|
||||
|
||||
CreationGamePlugins::CreationGamePlugins(IOrganizer* organizer)
|
||||
: GamebryoGamePlugins(organizer)
|
||||
{}
|
||||
|
||||
QStringList CreationGamePlugins::getLoadOrder()
|
||||
{
|
||||
QString loadOrderPath = organizer()->profile()->absolutePath() + "/loadorder.txt";
|
||||
QString pluginsPath = organizer()->profile()->absolutePath() + "/plugins.txt";
|
||||
|
||||
bool loadOrderIsNew = !m_LastRead.isValid() || !QFileInfo(loadOrderPath).exists() ||
|
||||
QFileInfo(loadOrderPath).lastModified() > m_LastRead;
|
||||
bool pluginsIsNew =
|
||||
!m_LastRead.isValid() || QFileInfo(pluginsPath).lastModified() > m_LastRead;
|
||||
|
||||
if (loadOrderIsNew || !pluginsIsNew) {
|
||||
return readLoadOrderList(m_Organizer->pluginList(), loadOrderPath);
|
||||
} else {
|
||||
return readPluginList(m_Organizer->pluginList());
|
||||
}
|
||||
}
|
||||
|
||||
void CreationGamePlugins::writePluginList(const IPluginList* pluginList,
|
||||
const QString& filePath)
|
||||
{
|
||||
SafeWriteFile file(filePath);
|
||||
|
||||
QStringEncoder encoder(QStringConverter::Encoding::System);
|
||||
|
||||
file->resize(0);
|
||||
|
||||
file->write(
|
||||
encoder.encode("# This file was automatically generated by Mod Organizer.\r\n"));
|
||||
|
||||
bool invalidFileNames = false;
|
||||
int writtenCount = 0;
|
||||
|
||||
QStringList plugins = pluginList->pluginNames();
|
||||
std::sort(plugins.begin(), plugins.end(),
|
||||
[pluginList](const QString& lhs, const QString& rhs) {
|
||||
return pluginList->priority(lhs) < pluginList->priority(rhs);
|
||||
});
|
||||
|
||||
QStringList PrimaryPlugins = organizer()->managedGame()->primaryPlugins();
|
||||
QStringList DLCPlugins = organizer()->managedGame()->DLCPlugins();
|
||||
QSet<QString> ManagedMods =
|
||||
QSet<QString>(PrimaryPlugins.begin(), PrimaryPlugins.end());
|
||||
QSet<QString> DLCSet = QSet<QString>(DLCPlugins.begin(), DLCPlugins.end());
|
||||
ManagedMods.subtract(DLCSet);
|
||||
PrimaryPlugins.append(QList<QString>(ManagedMods.begin(), ManagedMods.end()));
|
||||
|
||||
// TODO: do not write plugins in OFFICIAL_FILES container
|
||||
for (const QString& pluginName : plugins) {
|
||||
if (!PrimaryPlugins.contains(pluginName, Qt::CaseInsensitive)) {
|
||||
if (pluginList->state(pluginName) == IPluginList::STATE_ACTIVE) {
|
||||
auto result = encoder.encode(pluginName);
|
||||
if (encoder.hasError()) {
|
||||
invalidFileNames = true;
|
||||
qCritical("invalid plugin name %s", qUtf8Printable(pluginName));
|
||||
} else {
|
||||
file->write("*");
|
||||
file->write(result);
|
||||
}
|
||||
file->write("\r\n");
|
||||
++writtenCount;
|
||||
} else {
|
||||
auto result = encoder.encode(pluginName);
|
||||
if (encoder.hasError()) {
|
||||
invalidFileNames = true;
|
||||
qCritical("invalid plugin name %s", qUtf8Printable(pluginName));
|
||||
} else {
|
||||
file->write(result);
|
||||
}
|
||||
file->write("\r\n");
|
||||
++writtenCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidFileNames) {
|
||||
reportError(QObject::tr("Some of your plugins have invalid names! These "
|
||||
"plugins can not be loaded by the game. Please see "
|
||||
"mo_interface.log for a list of affected plugins "
|
||||
"and rename them."));
|
||||
}
|
||||
|
||||
file->commit();
|
||||
}
|
||||
|
||||
QStringList CreationGamePlugins::readPluginList(MOBase::IPluginList* pluginList)
|
||||
{
|
||||
const auto plugins = pluginList->pluginNames();
|
||||
const auto primaryPlugins = organizer()->managedGame()->primaryPlugins();
|
||||
QStringList loadOrder(primaryPlugins);
|
||||
|
||||
for (const QString& pluginName : loadOrder) {
|
||||
if (pluginList->state(pluginName) != IPluginList::STATE_MISSING) {
|
||||
pluginList->setState(pluginName, IPluginList::STATE_ACTIVE);
|
||||
}
|
||||
}
|
||||
|
||||
QString filePath = organizer()->profile()->absolutePath() + "/plugins.txt";
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qWarning("%s not found", qUtf8Printable(filePath));
|
||||
return loadOrder;
|
||||
}
|
||||
ON_BLOCK_EXIT([&]() {
|
||||
file.close();
|
||||
});
|
||||
|
||||
if (file.size() == 0) {
|
||||
// MO stores at least a header in the file. if it's completely empty the
|
||||
// file is broken
|
||||
qWarning("%s empty", qUtf8Printable(filePath));
|
||||
return loadOrder;
|
||||
}
|
||||
|
||||
QStringList pluginsFound;
|
||||
while (!file.atEnd()) {
|
||||
QByteArray line = file.readLine();
|
||||
QString pluginName;
|
||||
if ((line.size() > 0) && (line.at(0) != '#')) {
|
||||
pluginName = QStringEncoder(QStringConverter::Encoding::System)
|
||||
.encode(line.trimmed().constData());
|
||||
}
|
||||
if (!primaryPlugins.contains(pluginName, Qt::CaseInsensitive)) {
|
||||
if (pluginName.startsWith('*')) {
|
||||
pluginName.remove(0, 1);
|
||||
if (pluginName.size() > 0) {
|
||||
pluginList->setState(pluginName, IPluginList::STATE_ACTIVE);
|
||||
pluginsFound.append(pluginName);
|
||||
if (!loadOrder.contains(pluginName, Qt::CaseInsensitive)) {
|
||||
loadOrder.append(pluginName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (pluginName.size() > 0) {
|
||||
pluginList->setState(pluginName, IPluginList::STATE_INACTIVE);
|
||||
pluginsFound.append(pluginName);
|
||||
if (!loadOrder.contains(pluginName, Qt::CaseInsensitive)) {
|
||||
loadOrder.append(pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pluginName.remove(0, 1);
|
||||
pluginsFound.append(pluginName);
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
// set all plugins not found inactive
|
||||
for (const auto& pluginName : plugins) {
|
||||
if (!pluginsFound.contains(pluginName, Qt::CaseInsensitive)) {
|
||||
pluginList->setState(pluginName, IPluginList::STATE_INACTIVE);
|
||||
}
|
||||
}
|
||||
|
||||
return loadOrder;
|
||||
}
|
||||
|
||||
bool CreationGamePlugins::lightPluginsAreSupported()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef CREATIONGAMEPLUGINS_H
|
||||
#define CREATIONGAMEPLUGINS_H
|
||||
|
||||
#include <gamebryogameplugins.h>
|
||||
#include <imoinfo.h>
|
||||
#include <iplugingame.h>
|
||||
#include <map>
|
||||
|
||||
class CreationGamePlugins : public GamebryoGamePlugins
|
||||
{
|
||||
public:
|
||||
CreationGamePlugins(MOBase::IOrganizer* organizer);
|
||||
|
||||
protected:
|
||||
virtual void writePluginList(const MOBase::IPluginList* pluginList,
|
||||
const QString& filePath) override;
|
||||
virtual QStringList readPluginList(MOBase::IPluginList* pluginList) override;
|
||||
virtual QStringList getLoadOrder() override;
|
||||
virtual bool lightPluginsAreSupported() override;
|
||||
};
|
||||
|
||||
#endif // CREATIONGAMEPLUGINS_H
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en_US">
|
||||
<context>
|
||||
<name>QObject</name>
|
||||
<message>
|
||||
<location filename="creationgameplugins.cpp" line="97"/>
|
||||
<source>Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
@@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
add_library(game_gamebryo STATIC)
|
||||
mo2_configure_library(game_gamebryo
|
||||
WARNINGS OFF
|
||||
TRANSLATIONS ON
|
||||
AUTOMOC ON
|
||||
PUBLIC_DEPENDS uibase
|
||||
PRIVATE_DEPENDS zlib lz4)
|
||||
mo2_install_target(game_gamebryo)
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "dummybsa.h"
|
||||
#include <QFile>
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
static void writeUlong(unsigned char* buffer, int offset, unsigned long value)
|
||||
{
|
||||
union
|
||||
{
|
||||
unsigned long ulValue;
|
||||
unsigned char cValue[4];
|
||||
};
|
||||
ulValue = value;
|
||||
memcpy(buffer + offset, cValue, 4);
|
||||
}
|
||||
|
||||
static void writeUlonglong(unsigned char* buffer, int offset, unsigned long long value)
|
||||
{
|
||||
union
|
||||
{
|
||||
unsigned long long ullValue;
|
||||
unsigned char cValue[8];
|
||||
};
|
||||
ullValue = value;
|
||||
memcpy(buffer + offset, cValue, 8);
|
||||
}
|
||||
|
||||
static unsigned long genHashInt(const unsigned char* pos, const unsigned char* end)
|
||||
{
|
||||
unsigned long hash = 0;
|
||||
for (; pos < end; ++pos) {
|
||||
hash *= 0x1003f;
|
||||
hash += *pos;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static unsigned long long genHash(const char* fileName)
|
||||
{
|
||||
char fileNameLower[MAX_PATH + 1];
|
||||
int i = 0;
|
||||
for (; i < MAX_PATH && fileName[i] != '\0'; ++i) {
|
||||
fileNameLower[i] = static_cast<char>(tolower(fileName[i]));
|
||||
if (fileNameLower[i] == '\\') {
|
||||
fileNameLower[i] = '/';
|
||||
}
|
||||
}
|
||||
fileNameLower[i] = '\0';
|
||||
|
||||
unsigned char* fileNameLowerU = reinterpret_cast<unsigned char*>(fileNameLower);
|
||||
|
||||
char* ext = strrchr(fileNameLower, '.');
|
||||
if (ext == nullptr) {
|
||||
ext = fileNameLower + strlen(fileNameLower);
|
||||
}
|
||||
unsigned char* extU = reinterpret_cast<unsigned char*>(ext);
|
||||
|
||||
int length = ext - fileNameLower;
|
||||
|
||||
unsigned long long hash = 0ULL;
|
||||
|
||||
if (length > 0) {
|
||||
hash = *(extU - 1) | ((length > 2 ? *(ext - 2) : 0) << 8) | (length << 16) |
|
||||
(fileNameLowerU[0] << 24);
|
||||
}
|
||||
|
||||
if (strlen(ext) > 0) {
|
||||
if (strcmp(ext + 1, "kf") == 0) {
|
||||
hash |= 0x80;
|
||||
} else if (strcmp(ext + 1, "nif") == 0) {
|
||||
hash |= 0x8000;
|
||||
} else if (strcmp(ext + 1, "dds") == 0) {
|
||||
hash |= 0x8080;
|
||||
} else if (strcmp(ext + 1, "wav") == 0) {
|
||||
hash |= 0x80000000;
|
||||
}
|
||||
|
||||
unsigned long long temp =
|
||||
static_cast<unsigned long long>(genHashInt(fileNameLowerU + 1, extU - 2));
|
||||
temp += static_cast<unsigned long long>(genHashInt(extU, extU + strlen(ext)));
|
||||
|
||||
hash |= (temp & 0xFFFFFFFF) << 32;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
DummyBSA::DummyBSA(unsigned long bsaVersion)
|
||||
: m_Version(bsaVersion), m_FolderName(""), m_FileName("dummy.dds"),
|
||||
m_TotalFileNameLength(0)
|
||||
{}
|
||||
|
||||
void DummyBSA::writeHeader(QFile& file)
|
||||
{
|
||||
unsigned char header[] = {
|
||||
'B', 'S', 'A', '\0', // magic string
|
||||
0xDE, 0xAD, 0xBE, 0xEF, // version - insert later
|
||||
0x24, 0x00, 0x00, 0x00, // offset to folder recors. header size is static
|
||||
0xDE, 0xAD, 0xBE, 0xEF, // archive flags - insert later
|
||||
0x01, 0x00, 0x00, 0x00, // folder count
|
||||
0x01, 0x00, 0x00, 0x00, // file count
|
||||
0xDE, 0xAD, 0xBE, 0xEF, // total folder names length - insert later
|
||||
0xDE, 0xAD, 0xBE, 0xEF, // total file names length - insert later
|
||||
0xDE, 0xAD, 0xBE, 0xEF // file flags - insert later
|
||||
};
|
||||
|
||||
writeUlong(header, 4, m_Version);
|
||||
writeUlong(header, 12, 0x01 | 0x02); // has directories and has files.
|
||||
writeUlong(header, 24,
|
||||
static_cast<unsigned long>(m_FolderName.length()) +
|
||||
1); // empty folder name
|
||||
writeUlong(header, 28, m_TotalFileNameLength); // single character file name
|
||||
|
||||
writeUlong(header, 32, 2); // has dds
|
||||
|
||||
file.write(reinterpret_cast<char*>(header), sizeof(header));
|
||||
}
|
||||
|
||||
void DummyBSA::writeFolderRecord(QFile& file, const std::string& folderName)
|
||||
{
|
||||
unsigned char folderRecord[] = {
|
||||
0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // folder hash
|
||||
0x01, 0x00, 0x00, 0x00, // file count
|
||||
0xDE, 0xAD, 0xBE, 0xEF, // offset to folder name
|
||||
};
|
||||
// we'd usually have to sort folders be the hash value generated here
|
||||
writeUlonglong(folderRecord, 0, genHash(folderName.c_str()));
|
||||
writeUlong(folderRecord, 12,
|
||||
0x34 + m_TotalFileNameLength); // TODO: this should be calculated properly
|
||||
|
||||
file.write(reinterpret_cast<char*>(folderRecord), sizeof(folderRecord));
|
||||
}
|
||||
|
||||
void DummyBSA::writeFileRecord(QFile& file, const std::string& fileName)
|
||||
{
|
||||
unsigned char fileRecord[] = {
|
||||
0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // file name hash
|
||||
0xDE, 0xAD, 0xBE, 0xEF, // size
|
||||
0xDE, 0xAD, 0xBE, 0xEF, // offset to file data
|
||||
};
|
||||
|
||||
// we'd usually have to sort files by the value generated here
|
||||
writeUlonglong(fileRecord, 0, genHash(fileName.c_str()));
|
||||
writeUlong(fileRecord, 8, 0);
|
||||
writeUlong(
|
||||
fileRecord, 12,
|
||||
0x44 + static_cast<unsigned long>(fileName.length() + 1) +
|
||||
4); // after this record we expect the filename and 4 bytes of file size
|
||||
|
||||
file.write(reinterpret_cast<char*>(fileRecord), sizeof(fileRecord));
|
||||
}
|
||||
|
||||
void DummyBSA::writeFileRecordBlocks(QFile& file, const std::string& folderName)
|
||||
{
|
||||
file.write(folderName.c_str(), folderName.length() + 1);
|
||||
|
||||
writeFileRecord(file, m_FileName);
|
||||
}
|
||||
|
||||
void DummyBSA::write(const QString& fileName)
|
||||
{
|
||||
QFile file(fileName);
|
||||
file.open(QIODevice::WriteOnly);
|
||||
|
||||
m_TotalFileNameLength = static_cast<unsigned long>(m_FileName.length() + 1);
|
||||
|
||||
writeHeader(file);
|
||||
writeFolderRecord(file, m_FolderName);
|
||||
writeFileRecordBlocks(file, m_FolderName);
|
||||
file.write(m_FileName.c_str(), m_FileName.length() + 1);
|
||||
char fileSize[] = {0x00, 0x00, 0x00, 0x00};
|
||||
file.write(fileSize, sizeof(fileSize));
|
||||
file.close();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DUMMYBSA_H
|
||||
#define DUMMYBSA_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
* @brief Class for creating a dummy bsa used for archive invalidation
|
||||
**/
|
||||
class DummyBSA
|
||||
{
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief constructor
|
||||
*
|
||||
**/
|
||||
DummyBSA(unsigned long bsaVersion);
|
||||
|
||||
/**
|
||||
* @brief write to the specified file
|
||||
*
|
||||
* @param fileName name of the file to write to
|
||||
**/
|
||||
void write(const QString& fileName);
|
||||
|
||||
private:
|
||||
void writeHeader(QFile& file);
|
||||
void writeFolderRecord(QFile& file, const std::string& folderName);
|
||||
void writeFileRecord(QFile& file, const std::string& fileName);
|
||||
void writeFileRecordBlocks(QFile& file, const std::string& folderName);
|
||||
|
||||
private:
|
||||
unsigned long m_Version;
|
||||
std::string m_FolderName;
|
||||
std::string m_FileName;
|
||||
unsigned long m_TotalFileNameLength;
|
||||
};
|
||||
|
||||
#endif // DUMMYBSA_H
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en_US">
|
||||
<context>
|
||||
<name>GamebryoModDataContent</name>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="15"/>
|
||||
<source>Plugins (ESP/ESM/ESL)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="16"/>
|
||||
<source>Optional Plugins</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="17"/>
|
||||
<source>Interface</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="18"/>
|
||||
<source>Meshes</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="19"/>
|
||||
<source>Bethesda Archive</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="20"/>
|
||||
<source>Scripts (Papyrus)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="21"/>
|
||||
<source>Script Extender Plugin</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="22"/>
|
||||
<source>Script Extender Files</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="23"/>
|
||||
<source>SkyProc Patcher</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="24"/>
|
||||
<source>Sound or Music</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="25"/>
|
||||
<source>Textures</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="26"/>
|
||||
<source>MCM Configuration</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="27"/>
|
||||
<source>INI Files</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="28"/>
|
||||
<source>FaceGen Data</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryomoddatacontent.cpp" line="29"/>
|
||||
<source>ModGroup Files</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GamebryoSaveGameInfoWidget</name>
|
||||
<message>
|
||||
<location filename="gamebryosavegameinfowidget.ui" line="39"/>
|
||||
<source>Save #</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegameinfowidget.ui" line="51"/>
|
||||
<source>Character</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegameinfowidget.ui" line="63"/>
|
||||
<source>Level</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegameinfowidget.ui" line="75"/>
|
||||
<source>Location</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegameinfowidget.ui" line="87"/>
|
||||
<source>Date</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegameinfowidget.cpp" line="78"/>
|
||||
<source>Has Script Extender Data</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegameinfowidget.cpp" line="83"/>
|
||||
<source>Missing ESPs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegameinfowidget.cpp" line="116"/>
|
||||
<location filename="gamebryosavegameinfowidget.cpp" line="154"/>
|
||||
<location filename="gamebryosavegameinfowidget.cpp" line="193"/>
|
||||
<source>None</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegameinfowidget.cpp" line="122"/>
|
||||
<source>Missing ESHs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegameinfowidget.cpp" line="161"/>
|
||||
<source>Missing ESLs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QObject</name>
|
||||
<message>
|
||||
<location filename="gamebryogameplugins.cpp" line="130"/>
|
||||
<source>Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegame.cpp" line="48"/>
|
||||
<source>%1, #%2, Level %3, %4</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegame.cpp" line="102"/>
|
||||
<source>failed to open %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamebryosavegame.cpp" line="112"/>
|
||||
<source>wrong file format - expected %1 got '%2' for %3</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamegamebryo.cpp" line="318"/>
|
||||
<source>failed to query registry path (preflight): %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="gamegamebryo.cpp" line="326"/>
|
||||
<source>failed to query registry path (read): %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
@@ -0,0 +1,133 @@
|
||||
#include "gamebryobsainvalidation.h"
|
||||
|
||||
#include "dummybsa.h"
|
||||
#include "iplugingame.h"
|
||||
#include "iprofile.h"
|
||||
#include "registry.h"
|
||||
#include <imoinfo.h>
|
||||
#include <utility.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QStringList>
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
GamebryoBSAInvalidation::GamebryoBSAInvalidation(MOBase::DataArchives* dataArchives,
|
||||
const QString& iniFilename,
|
||||
MOBase::IPluginGame const* game)
|
||||
: m_DataArchives(dataArchives), m_IniFileName(iniFilename), m_Game(game)
|
||||
{}
|
||||
|
||||
bool GamebryoBSAInvalidation::isInvalidationBSA(const QString& bsaName)
|
||||
{
|
||||
static QStringList invalidation{invalidationBSAName()};
|
||||
|
||||
for (const QString& file : invalidation) {
|
||||
if (file.compare(bsaName, Qt::CaseInsensitive) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GamebryoBSAInvalidation::deactivate(MOBase::IProfile* profile)
|
||||
{
|
||||
prepareProfile(profile);
|
||||
}
|
||||
|
||||
void GamebryoBSAInvalidation::activate(MOBase::IProfile* profile)
|
||||
{
|
||||
prepareProfile(profile);
|
||||
}
|
||||
|
||||
bool GamebryoBSAInvalidation::prepareProfile(MOBase::IProfile* profile)
|
||||
{
|
||||
bool dirty = false;
|
||||
QString basePath = profile->localSettingsEnabled()
|
||||
? profile->absolutePath()
|
||||
: m_Game->documentsDirectory().absolutePath();
|
||||
QString iniFilePath = basePath + "/" + m_IniFileName;
|
||||
WCHAR setting[MAX_PATH];
|
||||
|
||||
// write bInvalidateOlderFiles = 1, if needed
|
||||
if (!::GetPrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"0", setting,
|
||||
MAX_PATH, iniFilePath.toStdWString().c_str()) ||
|
||||
wcstol(setting, nullptr, 10) != 1) {
|
||||
dirty = true;
|
||||
if (!MOBase::WriteRegistryValue(L"Archive", L"bInvalidateOlderFiles", L"1",
|
||||
iniFilePath.toStdWString().c_str())) {
|
||||
qWarning("failed to activate BSA invalidation in \"%s\"",
|
||||
qUtf8Printable(m_IniFileName));
|
||||
}
|
||||
}
|
||||
|
||||
if (profile->invalidationActive(nullptr)) {
|
||||
|
||||
// add the dummy bsa to the archive string, if needed
|
||||
QStringList archives = m_DataArchives->archives(profile);
|
||||
bool bsaInstalled = false;
|
||||
for (const QString& archive : archives) {
|
||||
if (isInvalidationBSA(archive)) {
|
||||
bsaInstalled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bsaInstalled) {
|
||||
m_DataArchives->addArchive(profile, 0, invalidationBSAName());
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// create the dummy bsa if necessary
|
||||
QString bsaFile = m_Game->dataDirectory().absoluteFilePath(invalidationBSAName());
|
||||
if (!QFile::exists(bsaFile)) {
|
||||
DummyBSA bsa(bsaVersion());
|
||||
bsa.write(bsaFile);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// write SInvalidationFile = "", if needed
|
||||
if (::GetPrivateProfileStringW(L"Archive", L"SInvalidationFile",
|
||||
L"ArchiveInvalidation.txt", setting, MAX_PATH,
|
||||
iniFilePath.toStdWString().c_str()) ||
|
||||
wcscmp(setting, L"") != 0) {
|
||||
dirty = true;
|
||||
if (!MOBase::WriteRegistryValue(L"Archive", L"SInvalidationFile", L"",
|
||||
iniFilePath.toStdWString().c_str())) {
|
||||
qWarning("failed to activate BSA invalidation in \"%s\"",
|
||||
qUtf8Printable(m_IniFileName));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// remove the dummy bsa from the archive string, if needed
|
||||
QStringList archivesBefore = m_DataArchives->archives(profile);
|
||||
for (const QString& archive : archivesBefore) {
|
||||
if (isInvalidationBSA(archive)) {
|
||||
m_DataArchives->removeArchive(profile, archive);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// delete the dummy bsa, if needed
|
||||
QString bsaFile = m_Game->dataDirectory().absoluteFilePath(invalidationBSAName());
|
||||
if (QFile::exists(bsaFile)) {
|
||||
MOBase::shellDeleteQuiet(bsaFile);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// write SInvalidationFile = "ArchiveInvalidation.txt", if needed
|
||||
if (!::GetPrivateProfileStringW(L"Archive", L"SInvalidationFile", L"", setting,
|
||||
MAX_PATH, iniFilePath.toStdWString().c_str()) ||
|
||||
wcscmp(setting, L"ArchiveInvalidation.txt") != 0) {
|
||||
dirty = true;
|
||||
if (!MOBase::WriteRegistryValue(L"Archive", L"SInvalidationFile",
|
||||
L"ArchiveInvalidation.txt",
|
||||
iniFilePath.toStdWString().c_str())) {
|
||||
qWarning("failed to activate BSA invalidation in \"%s\"",
|
||||
qUtf8Printable(m_IniFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dirty;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef GAMEBRYOBSAINVALIDATION_H
|
||||
#define GAMEBRYOBSAINVALIDATION_H
|
||||
|
||||
#include <QString>
|
||||
#include <bsainvalidation.h>
|
||||
#include <dataarchives.h>
|
||||
#include <memory>
|
||||
|
||||
namespace MOBase
|
||||
{
|
||||
class IPluginGame;
|
||||
}
|
||||
|
||||
class GamebryoBSAInvalidation : public MOBase::BSAInvalidation
|
||||
{
|
||||
public:
|
||||
GamebryoBSAInvalidation(MOBase::DataArchives* dataArchives,
|
||||
const QString& iniFilename, MOBase::IPluginGame const* game);
|
||||
|
||||
virtual bool isInvalidationBSA(const QString& bsaName) override;
|
||||
virtual void deactivate(MOBase::IProfile* profile) override;
|
||||
virtual void activate(MOBase::IProfile* profile) override;
|
||||
virtual bool prepareProfile(MOBase::IProfile* profile) override;
|
||||
|
||||
private:
|
||||
virtual QString invalidationBSAName() const = 0;
|
||||
virtual unsigned long
|
||||
bsaVersion() const = 0; // 0x67 for oblivion, 0x68 for everything else
|
||||
|
||||
private:
|
||||
MOBase::DataArchives* m_DataArchives;
|
||||
QString m_IniFileName;
|
||||
MOBase::IPluginGame const* m_Game;
|
||||
};
|
||||
|
||||
#endif // GAMEBRYOBSAINVALIDATION_H
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "gamebryodataarchives.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <registry.h>
|
||||
#include <utility.h>
|
||||
|
||||
#include "gamegamebryo.h"
|
||||
|
||||
GamebryoDataArchives::GamebryoDataArchives(const GameGamebryo* game) : m_Game{game} {}
|
||||
|
||||
QDir GamebryoDataArchives::gameDirectory() const
|
||||
{
|
||||
return QDir(m_Game->gameDirectory()).absolutePath();
|
||||
}
|
||||
|
||||
QDir GamebryoDataArchives::localGameDirectory() const
|
||||
{
|
||||
return QDir(m_Game->myGamesPath()).absolutePath();
|
||||
}
|
||||
|
||||
QStringList GamebryoDataArchives::getArchivesFromKey(const QString& iniFile,
|
||||
const QString& key,
|
||||
const int size) const
|
||||
{
|
||||
wchar_t* buffer = new wchar_t[size];
|
||||
QStringList result;
|
||||
std::wstring iniFileW = QDir::toNativeSeparators(iniFile).toStdWString();
|
||||
|
||||
// epic ms fail: GetPrivateProfileString uses errno (for whatever reason) to signal a
|
||||
// fail since the return value has a different meaning (number of bytes copied).
|
||||
// HOWEVER, it will not set errno to 0 if NO error occured
|
||||
errno = 0;
|
||||
|
||||
if (::GetPrivateProfileStringW(L"Archive", key.toStdWString().c_str(), L"", buffer,
|
||||
size, iniFileW.c_str()) != 0) {
|
||||
result.append(QString::fromStdWString(buffer).split(','));
|
||||
}
|
||||
|
||||
for (int i = 0; i < result.count(); ++i) {
|
||||
result[i] = result[i].trimmed();
|
||||
}
|
||||
delete[] buffer;
|
||||
return result;
|
||||
}
|
||||
|
||||
void GamebryoDataArchives::setArchivesToKey(const QString& iniFile, const QString& key,
|
||||
const QString& value)
|
||||
{
|
||||
if (!MOBase::WriteRegistryValue(L"Archive", key.toStdWString().c_str(),
|
||||
value.toStdWString().c_str(),
|
||||
iniFile.toStdWString().c_str())) {
|
||||
qWarning("failed to set archives in \"%s\"", qUtf8Printable(iniFile));
|
||||
}
|
||||
}
|
||||
|
||||
void GamebryoDataArchives::addArchive(MOBase::IProfile* profile, int index,
|
||||
const QString& archiveName)
|
||||
{
|
||||
QStringList current = archives(profile);
|
||||
if (current.contains(archiveName, Qt::CaseInsensitive)) {
|
||||
return;
|
||||
}
|
||||
|
||||
current.insert(index != INT_MAX ? index : current.size(), archiveName);
|
||||
|
||||
writeArchiveList(profile, current);
|
||||
}
|
||||
|
||||
void GamebryoDataArchives::removeArchive(MOBase::IProfile* profile,
|
||||
const QString& archiveName)
|
||||
{
|
||||
QStringList current = archives(profile);
|
||||
if (!current.contains(archiveName, Qt::CaseInsensitive)) {
|
||||
return;
|
||||
}
|
||||
current.removeAll(archiveName);
|
||||
|
||||
writeArchiveList(profile, current);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef GAMEBRYODATAARCHIVES_H
|
||||
#define GAMEBRYODATAARCHIVES_H
|
||||
|
||||
#include <QDir>
|
||||
|
||||
#include "dataarchives.h"
|
||||
|
||||
class GameGamebryo;
|
||||
|
||||
class GamebryoDataArchives : public MOBase::DataArchives
|
||||
{
|
||||
|
||||
public:
|
||||
GamebryoDataArchives(const GameGamebryo* game);
|
||||
|
||||
virtual void addArchive(MOBase::IProfile* profile, int index,
|
||||
const QString& archiveName) override;
|
||||
virtual void removeArchive(MOBase::IProfile* profile,
|
||||
const QString& archiveName) override;
|
||||
|
||||
protected:
|
||||
QDir gameDirectory() const;
|
||||
QDir localGameDirectory() const;
|
||||
|
||||
QStringList getArchivesFromKey(const QString& iniFile, const QString& key,
|
||||
int size = 256) const;
|
||||
void setArchivesToKey(const QString& iniFile, const QString& key,
|
||||
const QString& value);
|
||||
|
||||
private:
|
||||
const GameGamebryo* m_Game;
|
||||
|
||||
virtual void writeArchiveList(MOBase::IProfile* profile,
|
||||
const QStringList& before) = 0;
|
||||
};
|
||||
|
||||
#endif // GAMEBRYODATAARCHIVES_H
|
||||
@@ -0,0 +1,248 @@
|
||||
#include "gamebryogameplugins.h"
|
||||
#include <imodinterface.h>
|
||||
#include <iplugingame.h>
|
||||
#include <ipluginlist.h>
|
||||
#include <report.h>
|
||||
#include <safewritefile.h>
|
||||
#include <scopeguard.h>
|
||||
#include <utility.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include <QStringEncoder>
|
||||
#include <QStringList>
|
||||
|
||||
using MOBase::IOrganizer;
|
||||
using MOBase::IPluginList;
|
||||
using MOBase::reportError;
|
||||
using MOBase::SafeWriteFile;
|
||||
|
||||
GamebryoGamePlugins::GamebryoGamePlugins(IOrganizer* organizer) : m_Organizer(organizer)
|
||||
{}
|
||||
|
||||
void GamebryoGamePlugins::writePluginLists(const IPluginList* pluginList)
|
||||
{
|
||||
if (!m_LastRead.isValid()) {
|
||||
// attempt to write uninitialized plugin lists
|
||||
return;
|
||||
}
|
||||
|
||||
writePluginList(pluginList, m_Organizer->profile()->absolutePath() + "/plugins.txt");
|
||||
writeLoadOrderList(pluginList,
|
||||
m_Organizer->profile()->absolutePath() + "/loadorder.txt");
|
||||
|
||||
m_LastRead = QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
void GamebryoGamePlugins::readPluginLists(MOBase::IPluginList* pluginList)
|
||||
{
|
||||
QString loadOrderPath = organizer()->profile()->absolutePath() + "/loadorder.txt";
|
||||
QString pluginsPath = organizer()->profile()->absolutePath() + "/plugins.txt";
|
||||
|
||||
bool loadOrderIsNew = !m_LastRead.isValid() || !QFileInfo(loadOrderPath).exists() ||
|
||||
QFileInfo(loadOrderPath).lastModified() > m_LastRead;
|
||||
bool pluginsIsNew =
|
||||
!m_LastRead.isValid() || QFileInfo(pluginsPath).lastModified() > m_LastRead;
|
||||
|
||||
if (loadOrderIsNew || !pluginsIsNew) {
|
||||
// read both files if they are both new or both older than the last read
|
||||
QStringList loadOrder = readLoadOrderList(pluginList, loadOrderPath);
|
||||
pluginList->setLoadOrder(loadOrder);
|
||||
readPluginList(pluginList);
|
||||
} else {
|
||||
// If the plugins is new but not loadorder, we must reparse the load order from the
|
||||
// plugin files
|
||||
QStringList loadOrder = readPluginList(pluginList);
|
||||
pluginList->setLoadOrder(loadOrder);
|
||||
}
|
||||
|
||||
m_LastRead = QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
QStringList GamebryoGamePlugins::getLoadOrder()
|
||||
{
|
||||
QString loadOrderPath = organizer()->profile()->absolutePath() + "/loadorder.txt";
|
||||
QString pluginsPath = organizer()->profile()->absolutePath() + "/plugins.txt";
|
||||
|
||||
bool loadOrderIsNew = !m_LastRead.isValid() || !QFileInfo(loadOrderPath).exists() ||
|
||||
QFileInfo(loadOrderPath).lastModified() > m_LastRead;
|
||||
bool pluginsIsNew =
|
||||
!m_LastRead.isValid() || QFileInfo(pluginsPath).lastModified() > m_LastRead;
|
||||
|
||||
if (loadOrderIsNew || !pluginsIsNew) {
|
||||
return readLoadOrderList(m_Organizer->pluginList(), loadOrderPath);
|
||||
} else {
|
||||
return readPluginList(m_Organizer->pluginList());
|
||||
}
|
||||
}
|
||||
|
||||
void GamebryoGamePlugins::writePluginList(const MOBase::IPluginList* pluginList,
|
||||
const QString& filePath)
|
||||
{
|
||||
return writeList(pluginList, filePath, false);
|
||||
}
|
||||
|
||||
void GamebryoGamePlugins::writeLoadOrderList(const MOBase::IPluginList* pluginList,
|
||||
const QString& filePath)
|
||||
{
|
||||
return writeList(pluginList, filePath, true);
|
||||
}
|
||||
|
||||
void GamebryoGamePlugins::writeList(const IPluginList* pluginList,
|
||||
const QString& filePath, bool loadOrder)
|
||||
{
|
||||
SafeWriteFile file(filePath);
|
||||
|
||||
QStringEncoder encoder = loadOrder
|
||||
? QStringEncoder(QStringConverter::Encoding::Utf8)
|
||||
: QStringEncoder(QStringConverter::Encoding::System);
|
||||
|
||||
file->resize(0);
|
||||
|
||||
file->write(
|
||||
encoder.encode("# This file was automatically generated by Mod Organizer.\r\n"));
|
||||
|
||||
bool invalidFileNames = false;
|
||||
int writtenCount = 0;
|
||||
|
||||
QStringList plugins = pluginList->pluginNames();
|
||||
std::sort(plugins.begin(), plugins.end(),
|
||||
[pluginList](const QString& lhs, const QString& rhs) {
|
||||
return pluginList->priority(lhs) < pluginList->priority(rhs);
|
||||
});
|
||||
|
||||
for (const QString& pluginName : plugins) {
|
||||
if (loadOrder || (pluginList->state(pluginName) == IPluginList::STATE_ACTIVE)) {
|
||||
auto result = encoder.encode(pluginName);
|
||||
if (encoder.hasError()) {
|
||||
invalidFileNames = true;
|
||||
qCritical("invalid plugin name %s", qUtf8Printable(pluginName));
|
||||
} else {
|
||||
file->write(result);
|
||||
}
|
||||
file->write("\r\n");
|
||||
++writtenCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidFileNames) {
|
||||
reportError(QObject::tr("Some of your plugins have invalid names! These "
|
||||
"plugins can not be loaded by the game. Please see "
|
||||
"mo_interface.log for a list of affected plugins "
|
||||
"and rename them."));
|
||||
}
|
||||
|
||||
if (writtenCount == 0) {
|
||||
qWarning("plugin list would be empty, this is almost certainly wrong. Not "
|
||||
"saving.");
|
||||
} else {
|
||||
file->commit();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList GamebryoGamePlugins::readLoadOrderList(MOBase::IPluginList* pluginList,
|
||||
const QString& filePath)
|
||||
{
|
||||
QStringList pluginNames = organizer()->managedGame()->primaryPlugins();
|
||||
|
||||
std::set<QString> pluginLookup;
|
||||
for (auto&& name : pluginNames) {
|
||||
pluginLookup.insert(name.toLower());
|
||||
}
|
||||
|
||||
const auto b = MOBase::forEachLineInFile(filePath, [&](QString s) {
|
||||
if (!pluginLookup.contains(s.toLower())) {
|
||||
pluginLookup.insert(s);
|
||||
pluginNames.push_back(std::move(s));
|
||||
}
|
||||
});
|
||||
|
||||
if (!b) {
|
||||
return readPluginList(pluginList);
|
||||
}
|
||||
|
||||
return pluginNames;
|
||||
}
|
||||
|
||||
QStringList GamebryoGamePlugins::readPluginList(MOBase::IPluginList* pluginList)
|
||||
{
|
||||
QStringList primary = organizer()->managedGame()->primaryPlugins();
|
||||
for (const QString& pluginName : primary) {
|
||||
if (pluginList->state(pluginName) != IPluginList::STATE_MISSING) {
|
||||
pluginList->setState(pluginName, IPluginList::STATE_ACTIVE);
|
||||
}
|
||||
}
|
||||
QStringList plugins = pluginList->pluginNames();
|
||||
QStringList pluginsClone(plugins);
|
||||
// Do not sort the primary plugins. Their load order should be locked as defined in
|
||||
// "primaryPlugins".
|
||||
for (const auto& plugin : pluginsClone) {
|
||||
if (primary.contains(plugin, Qt::CaseInsensitive))
|
||||
plugins.removeAll(plugin);
|
||||
}
|
||||
|
||||
// Always use filetime loadorder to get the actual load order
|
||||
std::sort(plugins.begin(), plugins.end(),
|
||||
[&](const QString& lhs, const QString& rhs) {
|
||||
MOBase::IModInterface* lhm =
|
||||
organizer()->modList()->getMod(pluginList->origin(lhs));
|
||||
MOBase::IModInterface* rhm =
|
||||
organizer()->modList()->getMod(pluginList->origin(rhs));
|
||||
QDir lhd = organizer()->managedGame()->dataDirectory();
|
||||
QDir rhd = organizer()->managedGame()->dataDirectory();
|
||||
if (lhm != nullptr)
|
||||
lhd = lhm->absolutePath();
|
||||
if (rhm != nullptr)
|
||||
rhd = rhm->absolutePath();
|
||||
QString lhp = lhd.absoluteFilePath(lhs);
|
||||
QString rhp = rhd.absoluteFilePath(rhs);
|
||||
return QFileInfo(lhp).lastModified() < QFileInfo(rhp).lastModified();
|
||||
});
|
||||
|
||||
// Determine plugin active state by the plugins.txt file.
|
||||
bool pluginsTxtExists = true;
|
||||
QString filePath = organizer()->profile()->absolutePath() + "/plugins.txt";
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
pluginsTxtExists = false;
|
||||
}
|
||||
ON_BLOCK_EXIT([&]() {
|
||||
file.close();
|
||||
});
|
||||
|
||||
if (file.size() == 0) {
|
||||
// MO stores at least a header in the file. if it's completely empty the
|
||||
// file is broken
|
||||
pluginsTxtExists = false;
|
||||
}
|
||||
|
||||
QStringList activePlugins;
|
||||
QStringList inactivePlugins;
|
||||
if (pluginsTxtExists) {
|
||||
while (!file.atEnd()) {
|
||||
QByteArray line = file.readLine();
|
||||
QString pluginName;
|
||||
if ((line.size() > 0) && (line.at(0) != '#')) {
|
||||
QStringEncoder encoder(QStringConverter::Encoding::System);
|
||||
pluginName = encoder.encode(line.trimmed().constData());
|
||||
}
|
||||
if (pluginName.size() > 0) {
|
||||
pluginList->setState(pluginName, IPluginList::STATE_ACTIVE);
|
||||
activePlugins.push_back(pluginName);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& pluginName : plugins) {
|
||||
if (!activePlugins.contains(pluginName, Qt::CaseInsensitive)) {
|
||||
pluginList->setState(pluginName, IPluginList::STATE_INACTIVE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const QString& pluginName : plugins) {
|
||||
pluginList->setState(pluginName, IPluginList::STATE_INACTIVE);
|
||||
}
|
||||
}
|
||||
|
||||
return primary + plugins;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user