Skip to content

Commit

Permalink
Starfield Creation Update: Convert overlay/override flags to medium f…
Browse files Browse the repository at this point in the history
…lags (#23)

* Various updates for the Creation update
- Remove light warnings and plugins txt enabler checks
- Remove overlay code for new 'medium' plugin type
* Updates for new starfield save data
* Add version check to parse medium plugins
* Add creation kit to default executables
* Move BlueprintShips-Starfield.esm to primary plugins
  • Loading branch information
Silarn authored Jun 13, 2024
1 parent 052fa01 commit 6936659
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 164 deletions.
142 changes: 9 additions & 133 deletions src/gamestarfield.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ QList<ExecutableInfo> GameStarfield::executables() const
->gameFeature<MOBase::ScriptExtender>()
->loaderName()))
<< ExecutableInfo("Starfield", findInGameFolder(binaryName()))
<< ExecutableInfo("Creation Kit", findInGameFolder("CreationKit.exe"))
.withSteamAppId("2722710")
<< ExecutableInfo("LOOT", QFileInfo(getLootPath()))
.withArgument("--game=\"Starfield\"");
}
Expand Down Expand Up @@ -138,21 +140,9 @@ QList<PluginSetting> GameStarfield::settings() const
"enable_esp_warning",
tr("Show a warning when ESP plugins are enabled in the load order."),
true)
<< PluginSetting(
"enable_esl_warning",
tr("Show a warning when light plugins are enabled in the load order."),
true)
<< PluginSetting("enable_overlay_warning",
tr("Show a warning when overlay-flagged plugins ar enabled "
"in the load order."),
true)
<< PluginSetting("enable_management_warnings",
tr("Show a warning when plugins.txt management is invalid."),
true)
<< PluginSetting("bypass_plugins_enabler_check",
tr("Bypass check for Plugins.txt Enabler. This may be useful "
"if you use the ASI loader."),
false)
<< PluginSetting("enable_loot_sorting",
tr("As of this release LOOT Starfield support is minimal to "
"nonexistant. Toggle this to enable it anyway."),
Expand Down Expand Up @@ -229,8 +219,10 @@ QStringList GameStarfield::testFilePlugins() const

QStringList GameStarfield::primaryPlugins() const
{
QStringList plugins = {"Starfield.esm", "Constellation.esm", "OldMars.esm",
"SFBGS006.esm", "SFBGS007.esm", "SFBGS008.esm"};
QStringList plugins = {"Starfield.esm", "Constellation.esm",
"OldMars.esm", "BlueprintShips-Starfield.esm",
"SFBGS003.esm", "SFBGS006.esm",
"SFBGS007.esm", "SFBGS008.esm"};

auto testPlugins = testFilePlugins();
if (loadOrderMechanism() == LoadOrderMechanism::None) {
Expand All @@ -245,7 +237,7 @@ QStringList GameStarfield::primaryPlugins() const

QStringList GameStarfield::enabledPlugins() const
{
return {"BlueprintShips-Starfield.esm"};
return {};
}

QStringList GameStarfield::gameVariants() const
Expand Down Expand Up @@ -317,9 +309,7 @@ IPluginGame::SortMechanism GameStarfield::sortMechanism() const

IPluginGame::LoadOrderMechanism GameStarfield::loadOrderMechanism() const
{
if (!testFilePresent() &&
(pluginsTxtEnablerPresent() ||
m_Organizer->pluginSetting(name(), "bypass_plugins_enabler_check").toBool()))
if (!testFilePresent())
return IPluginGame::LoadOrderMechanism::PluginsTxt;
return IPluginGame::LoadOrderMechanism::None;
}
Expand All @@ -342,17 +332,9 @@ std::vector<unsigned int> GameStarfield::activeProblems() const
if (m_Organizer->pluginSetting(name(), "enable_esp_warning").toBool() &&
activeESP())
result.push_back(PROBLEM_ESP);
if (m_Organizer->pluginSetting(name(), "enable_esl_warning").toBool() &&
activeESL())
result.push_back(PROBLEM_ESL);
if (m_Organizer->pluginSetting(name(), "enable_overlay_warning").toBool() &&
activeOverlay())
result.push_back(PROBLEM_OVERLAY);
if (m_Organizer->pluginSetting(name(), "enable_management_warnings").toBool()) {
if (testFilePresent())
result.push_back(PROBLEM_TEST_FILE);
else if (!pluginsTxtEnablerPresent())
result.push_back(PROBLEM_PLUGINS_TXT);
}
}
return result;
Expand All @@ -379,88 +361,20 @@ bool GameStarfield::activeESP() const
return false;
}

bool GameStarfield::activeESL() const
{
m_Active_ESLs.clear();
std::set<QString> enabledPlugins;

QStringList esps = m_Organizer->findFiles("", [](const QString& fileName) -> bool {
return fileName.endsWith(".esp", FileNameComparator::CaseSensitivity) ||
fileName.endsWith(".esm", FileNameComparator::CaseSensitivity) ||
fileName.endsWith(".esl", FileNameComparator::CaseSensitivity);
});

for (const QString& esp : esps) {
QString baseName = QFileInfo(esp).fileName();
if (primaryPlugins().contains(baseName, Qt::CaseInsensitive))
continue;
if (m_Organizer->pluginList()->state(baseName) == IPluginList::STATE_ACTIVE &&
!m_Organizer->pluginList()->hasNoRecords(baseName))
if (m_Organizer->pluginList()->hasLightExtension(baseName) ||
m_Organizer->pluginList()->isLightFlagged(baseName))
m_Active_ESLs.insert(baseName);
}

if (!m_Active_ESLs.empty())
return true;
return false;
}

bool GameStarfield::activeOverlay() const
{
m_Active_Overlays.clear();
std::set<QString> enabledPlugins;

QStringList esps = m_Organizer->findFiles("", [](const QString& fileName) -> bool {
return fileName.endsWith(".esp", FileNameComparator::CaseSensitivity) ||
fileName.endsWith(".esm", FileNameComparator::CaseSensitivity) ||
fileName.endsWith(".esl", FileNameComparator::CaseSensitivity);
});

for (const QString& esp : esps) {
QString baseName = QFileInfo(esp).fileName();
if (primaryPlugins().contains(baseName, Qt::CaseInsensitive))
continue;
if (m_Organizer->pluginList()->state(baseName) == IPluginList::STATE_ACTIVE) {
if (m_Organizer->pluginList()->isOverlayFlagged(baseName))
m_Active_Overlays.insert(baseName);
}
}

if (!m_Active_Overlays.empty())
return true;
return false;
}

bool GameStarfield::testFilePresent() const
{
if (!testFilePlugins().isEmpty())
return true;
return false;
}

bool GameStarfield::pluginsTxtEnablerPresent() const
{
auto files = m_Organizer->findFiles("sfse\\plugins", {"sfpluginstxtenabler.dll"});
files += m_Organizer->findFiles("", {"sfpluginstxtenabler.asi"});
if (files.isEmpty())
return false;
return true;
}

QString GameStarfield::shortDescription(unsigned int key) const
{
switch (key) {
case PROBLEM_ESP:
return tr("You have active ESP plugins in Starfield");
case PROBLEM_ESL:
return tr("You have active ESL plugins in Starfield");
case PROBLEM_OVERLAY:
return tr("You have active overlay plugins");
case PROBLEM_TEST_FILE:
return tr("sTestFile entries are present");
case PROBLEM_PLUGINS_TXT:
return tr("Plugins.txt Enabler missing");
}
return "";
}
Expand All @@ -483,56 +397,18 @@ QString GameStarfield::fullDescription(unsigned int key) const
"<h4>Current ESPs:</h4><p>%1</p>")
.arg(espInfo);
}
case PROBLEM_ESL: {
QString eslInfo = SetJoin(m_Active_ESLs, ", ");
return tr("<p>Light plugins work differently in Starfield. They use a different "
"base form ID compared with standard plugin files.</p>"
"<p>What this means is that you can't just change a standard plugin to a "
"light plugin at will, it can and will break any dependent plugin. If "
"you do so, be absolutely certain no other plugins use that plugin as a "
"master.</p>"
"<p>Notably, xEdit does not currently support saving or loading ESL "
"files under these conditions.<p>"
"<h4>Current ESLs:</h4><p>%1</p>")
.arg(eslInfo);
}
case PROBLEM_OVERLAY: {
QString overlayInfo = SetJoin(m_Active_Overlays, ", ");
return tr("<p>Overlay-flagged plugins are not currently recommended. In theory, "
"they should allow you to update existing records without utilizing "
"additional load order slots. Unfortunately, it appears that the game "
"still allocates the slots as if these were standard plugins. Therefore, "
"at the moment there is no real use for this plugin flag.</p>"
"<p>Notably, xEdit does not currently support saving or loading "
"overlay-flagged files under these conditions.</p>"
"<h4>Current Overlays:</h4><p>%1</p>")
.arg(overlayInfo);
}
case PROBLEM_TEST_FILE: {
return tr("<p>You have plugin managment enabled but you still have sTestFile "
"settings in your StarfieldCustom.ini. These must be removed or the game "
"will not read the plugins.txt file. Management is still disabled.</p>");
}
case PROBLEM_PLUGINS_TXT: {
return tr("<p>You have plugin management turned on but do not have the Plugins.txt "
"Enabler SFSE plugin installed. Plugin file management for Starfield "
"will not work without this SFSE plugin.</p>");
}
}
return "";
}

bool GameStarfield::hasGuidedFix(unsigned int key) const
{
if (key == PROBLEM_PLUGINS_TXT)
return true;
return false;
}

void GameStarfield::startGuidedFix(unsigned int key) const
{
if (key == PROBLEM_PLUGINS_TXT) {
QDesktopServices::openUrl(
QUrl("https://www.nexusmods.com/starfield/mods/4157?tab=files"));
}
}
void GameStarfield::startGuidedFix(unsigned int key) const {}
12 changes: 2 additions & 10 deletions src/gamestarfield.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,13 @@ class GameStarfield : public GameGamebryo, public MOBase::IPluginDiagnose

private:
bool activeESP() const;
bool activeESL() const;
bool activeOverlay() const;
bool testFilePresent() const;
bool pluginsTxtEnablerPresent() const;

private:
static const unsigned int PROBLEM_ESP = 1;
static const unsigned int PROBLEM_ESL = 2;
static const unsigned int PROBLEM_OVERLAY = 3;
static const unsigned int PROBLEM_TEST_FILE = 4;
static const unsigned int PROBLEM_PLUGINS_TXT = 5;
static const unsigned int PROBLEM_ESP = 1;
static const unsigned int PROBLEM_TEST_FILE = 2;

mutable std::set<QString> m_Active_ESPs;
mutable std::set<QString> m_Active_ESLs;
mutable std::set<QString> m_Active_Overlays;
};

#endif // GAMEStarfield_H
2 changes: 1 addition & 1 deletion src/starfieldgameplugins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ StarfieldGamePlugins::StarfieldGamePlugins(MOBase::IOrganizer* organizer)
: CreationGamePlugins(organizer)
{}

bool StarfieldGamePlugins::overridePluginsAreSupported()
bool StarfieldGamePlugins::mediumPluginsAreSupported()
{
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion src/starfieldgameplugins.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class StarfieldGamePlugins : public CreationGamePlugins
StarfieldGamePlugins(MOBase::IOrganizer* organizer);

protected:
virtual bool overridePluginsAreSupported() override;
virtual bool mediumPluginsAreSupported() override;
virtual void writePluginList(const MOBase::IPluginList* pluginList,
const QString& filePath) override;
virtual QStringList readPluginList(MOBase::IPluginList* pluginList) override;
Expand Down
39 changes: 22 additions & 17 deletions src/starfieldsavegame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
#include "gamestarfield.h"

StarfieldSaveGame::StarfieldSaveGame(QString const& fileName, GameStarfield const* game)
: GamebryoSaveGame(fileName, game, true)
: GamebryoSaveGame(fileName, game, true, true)
{
FileWrapper file(getFilepath(), "BCPS");

getData(file);
FILETIME creationTime;
fetchInformationFields(file, m_SaveNumber, m_PCName, m_PCLevel, m_PCLocation,
creationTime);
unsigned char saveVersion;
fetchInformationFields(file, m_SaveNumber, saveVersion, m_PCName, m_PCLevel,
m_PCLocation, creationTime);
file.closeCompressedData();
file.close();

Expand Down Expand Up @@ -51,18 +52,18 @@ void StarfieldSaveGame::getData(FileWrapper& file) const
}

void StarfieldSaveGame::fetchInformationFields(
FileWrapper& file, unsigned long& saveNumber, QString& playerName,
unsigned short& playerLevel, QString& playerLocation, FILETIME& creationTime) const
FileWrapper& file, unsigned long& saveNumber, unsigned char& saveVersion,
QString& playerName, unsigned short& playerLevel, QString& playerLocation,
FILETIME& creationTime) const
{
char fileID[12]; // SFS_SAVEGAME
unsigned int headerSize;
unsigned int version;
unsigned char unknown;
// file.read(fileID, 12);
headerSize = file.readInt(12);
version = file.readInt();
unknown = file.readChar();
saveNumber = file.readInt();
headerSize = file.readInt(12);
saveNumber = file.readInt();
saveVersion = file.readChar();
version = file.readInt();
file.read(playerName);

unsigned int temp;
Expand Down Expand Up @@ -91,29 +92,33 @@ std::unique_ptr<GamebryoSaveGame::DataFields> StarfieldSaveGame::fetchDataFields

getData(file);
FILETIME creationTime;
unsigned char saveVersion;

{
QString dummyName, dummyLocation;
unsigned short dummyLevel;
unsigned long dummySaveNumber;
FILETIME dummyTime;

fetchInformationFields(file, dummySaveNumber, dummyName, dummyLevel, dummyLocation,
dummyTime);
fetchInformationFields(file, dummySaveNumber, saveVersion, dummyName, dummyLevel,
dummyLocation, dummyTime);
}

bool extraInfo = saveVersion >= 122;
QStringList gamePlugins = m_Game->primaryPlugins() + m_Game->enabledPlugins();

QString ignore;
std::unique_ptr<DataFields> fields = std::make_unique<DataFields>();

// fields->Screenshot = file.readImage(384, true);

uint8_t saveGameVersion = file.readChar(12);
file.readChar(12);
file.read(ignore); // game version
file.read(ignore); // game version again?
file.readInt(); // plugin info size

fields->Plugins = file.readPlugins();
fields->LightPlugins = file.readLightPlugins();
fields->Plugins = file.readPlugins(0, extraInfo, gamePlugins);
fields->LightPlugins = file.readLightPlugins(0, extraInfo, gamePlugins);
if (saveVersion >= 122)
fields->MediumPlugins = file.readMediumPlugins(0, extraInfo, gamePlugins);
file.closeCompressedData();
file.close();

Expand Down
5 changes: 3 additions & 2 deletions src/starfieldsavegame.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ class StarfieldSaveGame : public GamebryoSaveGame
void getData(FileWrapper& file) const;

void fetchInformationFields(FileWrapper& file, unsigned long& saveNumber,
QString& playerName, unsigned short& playerLevel,
QString& playerLocation, FILETIME& creationTime) const;
unsigned char& saveVersion, QString& playerName,
unsigned short& playerLevel, QString& playerLocation,
FILETIME& creationTime) const;

std::unique_ptr<DataFields> fetchDataFields() const override;
};
Expand Down

0 comments on commit 6936659

Please sign in to comment.